diff --git a/.github/bors.toml b/.github/bors.toml deleted file mode 100644 index cb1361c014..0000000000 --- a/.github/bors.toml +++ /dev/null @@ -1,19 +0,0 @@ -# List of commit statuses that must pass on the merge commit before it is -# pushed to master. -status = [ - "ci-format (ubuntu-latest)", "ci-build (ubuntu-latest)", "ci-tests (ubuntu-latest)", "ci-qemu", - "ci-build (macos-latest)", "ci-tests (macos-latest)" -] - -# List of PR labels that may not be attached to a PR when it is r+-ed. -block_labels = [ - "blocked", -] - -# Number of seconds from when a merge commit is created to when its statuses -# must pass. (Default = 3600). -#timeout_sec = 7200 - -# If set to true, and if the PR branch is on the same repository that bors-ng -# itself is on, the branch will be deleted. -delete_merged_branches = true diff --git a/.github/labeler.yml b/.github/labeler.yml index 303cb15816..0778ec3234 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + # This is a configuration file for the labeler github action. # The labeler action runs uses this configuration to automatically # label each PR submitted to the Tock repository, by applying labels @@ -28,6 +32,26 @@ WG-OpenTitan: - boards/opentitan/**/* - chips/earlgrey/**/* - chips/lowrisc/**/* + - doc/wg/opentitan/**/* + +WG-Network: + - capsules/extra/src/ble_advertising_driver.rs + - capsules/extra/src/can.rs + - capsules/extra/src/ieee802154/**/* + - capsules/extra/src/net/**/* + - capsules/extra/src/rf233.rs + - capsules/extra/src/rf233_const.rs + - chips/apollo3/src/ble.rs + - chips/litex/src/liteeth.rs + - chips/nrf52/src/ble_radio.rs + - chips/nrf52840/src/ieee802154_radio.rs + - chips/stm32f429zi/src/can_registers.rs + - chips/stm32f4xx/src/can.rs + - chips/virtio/src/devices/virtio_net.rs + - doc/wg/network/**/* + - kernel/src/hil/ble_advertising.rs + - kernel/src/hil/can.rs + - kernel/src/hil/radio.rs # add kernel label unless already covered by hil label kernel: @@ -36,3 +60,6 @@ kernel: # add documentation label only if all changes are in doc/ documentation: - all: ['doc/**/*'] + +component: + - boards/components/**/* diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index 5f8f0d9d42..2e179aa664 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + # This workflow calculates size diffs for the compiled binary of each supported tock board name: Benchmarks @@ -10,6 +14,9 @@ on: # A workflow run is made up of one or more jobs that can run sequentially or in parallel # If you add additional jobs, remember to add them to bors.toml +permissions: + contents: read + jobs: benchmarks: # The type of runner that the job will run on @@ -17,12 +24,9 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 # pulls version from rust-toolchain file - with: - components: rustfmt, clippy + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v1 + uses: actions/setup-python@v4 with: python-version: '3.x' - name: Install dependencies diff --git a/.github/workflows/ci-nightly.yml b/.github/workflows/ci-nightly.yml new file mode 100644 index 0000000000..46d25f08bd --- /dev/null +++ b/.github/workflows/ci-nightly.yml @@ -0,0 +1,88 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +name: tock-nightly-ci + +on: + schedule: + - cron: "0 0 * * *" + +env: + TERM: xterm # Makes tput work in actions output + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +# If you add additional jobs, remember to add them to bors.toml +permissions: + contents: read + issues: write + +jobs: + ci-build: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v3 + + - name: ci-job-syntax + run: make ci-job-syntax + - name: ci-job-compilation + run: make ci-job-compilation + - name: ci-job-debug-support-targets + run: make ci-job-debug-support-targets + + - name: ci-job-collect-artifacts + run: make ci-job-collect-artifacts + - name: upload-build-artifacts + uses: actions/upload-artifact@v3 + with: + name: build-artifacts + path: tools/ci-artifacts + + ci-tests: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + + steps: + - name: Update package repositories + run: | + sudo apt update + if: matrix.os == 'ubuntu-latest' + - name: Install dependencies for ubuntu-latest + run: | + sudo apt install libudev-dev libzmq3-dev + if: matrix.os == 'ubuntu-latest' + - name: Install dependencies for macos-latest + run: | + brew install zeromq + if: matrix.os == 'macos-latest' + - uses: actions/checkout@v3 + - name: ci-job-libraries + run: make ci-job-libraries + - name: ci-job-archs + run: make ci-job-archs + - name: ci-job-kernel + run: make ci-job-kernel + - name: ci-job-chips + run: make ci-job-chips + - name: ci-job-tools + run: make ci-job-tools + - name: Create Issue on Failed workflow + if: failure() + uses: dacbd/create-issue-action@main + with: + token: ${{ github.token }} + title: Nightly CI failed + body: | + ### Context + [Failed Run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}) + [Codebase](https://github.com/${{ github.repository }}/tree/${{ github.sha }}) + Workflow name - `${{ github.workflow }}` + Job - `${{ github.job }}` + status - `${{ job.status }}` + assignees: tock/core-wg diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a081388dc3..6732130e97 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,8 @@ -# This workflow contains all tock-ci, seperated into jobs +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +# This workflow contains all tock-ci, separated into jobs name: tock-ci env: @@ -7,12 +11,17 @@ env: # Controls when the action will run. Triggers the workflow on push or pull request # events but only for the master branch on: - push: - branches-ignore: [ staging.tmp, trying.tmp ] # Run CI for all branches except bors tmp branches + push: # Run CI for all branches except GitHub merge queue tmp branches + branches-ignore: + - "gh-readonly-queue/**" pull_request: # Run CI for PRs on any branch + merge_group: # Run CI for the GitHub merge queue # A workflow run is made up of one or more jobs that can run sequentially or in parallel # If you add additional jobs, remember to add them to bors.toml +permissions: + contents: read + jobs: ci-format: strategy: @@ -23,15 +32,14 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 # pulls version from rust-toolchain file - - uses: actions/setup-node@v1 - with: - components: rustfmt - - name: ci-job-format + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + - name: ci-job-format run: make ci-job-format - - name: ci-markdown-toc - run: make ci-job-markdown-toc + - name: ci-job-markdown-toc + run: make ci-job-markdown-toc + - name: ci-job-readme-check + run: make ci-job-readme-check ci-clippy: strategy: @@ -42,34 +50,28 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 # pulls version from rust-toolchain file - with: - components: clippy - - name: ci-job-clippy + - uses: actions/checkout@v3 + - name: ci-job-clippy run: make ci-job-clippy ci-build: strategy: matrix: - os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - - - name: ci-job-syntax - run: make ci-job-syntax - - name: ci-job-compilation - run: make ci-job-compilation - - name: ci-job-debug-support-targets - run: make ci-job-debug-support-targets - - - name: ci-job-collect-artifacts - run: make ci-job-collect-artifacts + - uses: actions/checkout@v3 + - name: ci-job-syntax + run: make ci-job-syntax + - name: ci-job-compilation + run: make ci-job-compilation + - name: ci-job-debug-support-targets + run: make ci-job-debug-support-targets + - name: ci-job-collect-artifacts + run: make ci-job-collect-artifacts - name: upload-build-artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v3 with: name: build-artifacts path: tools/ci-artifacts @@ -77,7 +79,7 @@ jobs: ci-tests: strategy: matrix: - os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest] runs-on: ${{ matrix.os }} steps: @@ -89,25 +91,27 @@ jobs: run: | sudo apt install libudev-dev libzmq3-dev if: matrix.os == 'ubuntu-latest' - - name: Install dependencies for macos-latest - run: | - brew install zeromq - if: matrix.os == 'macos-latest' - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - - name: ci-job-libraries - run: make ci-job-libraries - - name: ci-job-archs - run: make ci-job-archs - - name: ci-job-kernel - run: make ci-job-kernel - - name: ci-job-chips - run: make ci-job-chips - - name: ci-job-tools - run: make ci-job-tools + - uses: actions/checkout@v3 + - name: ci-job-libraries + run: make ci-job-libraries + - name: ci-job-archs + run: make ci-job-archs + - name: ci-job-kernel + run: make ci-job-kernel + - name: ci-job-capsules + run: make ci-job-capsules + - name: ci-job-chips + run: make ci-job-chips + - name: ci-job-tools + run: make ci-job-tools + - name: ci-job-cargo-test-build + run: make ci-job-cargo-test-build ci-qemu: - runs-on: ubuntu-latest + strategy: + matrix: + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} steps: - name: Update package repositories @@ -117,7 +121,6 @@ jobs: continue-on-error: true run: | sudo apt install meson - - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 - - name: ci-job-qemu - run: make ci-job-qemu + - uses: actions/checkout@v3 + - name: ci-job-qemu + run: make ci-job-qemu diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 23956a02fb..d7d87957db 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -1,11 +1,21 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + name: "Pull Request Labeler" on: - pull_request_target +permissions: + contents: read + jobs: triage: + permissions: + contents: read # for actions/labeler to determine modified files + pull-requests: write # for actions/labeler to add labels to PRs runs-on: ubuntu-latest steps: - - uses: actions/labeler@main + - uses: actions/labeler@v4.3.0 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/litex_sim.yml b/.github/workflows/litex_sim.yml index 2e48ddb722..f2877aea3f 100644 --- a/.github/workflows/litex_sim.yml +++ b/.github/workflows/litex_sim.yml @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + # This workflow contains the litex-ci-runner job, which uses the LiteX Verilated # simulation to run a Tock kernel and perform various tests using libtock-c # example applications. @@ -9,12 +13,17 @@ env: # Controls when the action will run. Triggers the workflow on push or pull # request events but only for the master branch on: - push: - branches-ignore: [ staging.tmp, trying.tmp ] # Run CI for all branches except bors tmp branches + push: # Run CI for all branches except GitHub merge queue tmp branches + branches-ignore: + - "gh-readonly-queue/**" pull_request: # Run CI for PRs on any branch + merge_group: # Run CI for the GitHub merge queue # A workflow run is made up of one or more jobs that can run sequentially or in parallel # If you add additional jobs, remember to add them to bors.toml +permissions: + contents: read + jobs: litex-sim-ci: strategy: @@ -30,56 +39,36 @@ jobs: # that other steps (such as the Rust toolchain) depend on files # in this repo. - name: Checkout the current repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 # Install basic packages required for the GitHub actions workflow - name: Update packages and install dependencies run: | sudo apt update - sudo apt install python3-pip python3-venv \ + sudo apt install python3-pip python3-venv gcc-riscv64-unknown-elf \ verilator libevent-dev libjson-c-dev libz-dev libzmq3-dev - # Uses a custom RISCV toolchain containing the required headers. This - # follows the libtock-c GitHub actions definition. - - name: Setup RISC-V GCC toolchain - run: | - pushd $HOME - wget -q http://cs.virginia.edu/~bjc8c/archive/gcc-riscv64-unknown-elf-8.3.0-ubuntu.zip - echo "2c82a8f3ac77bf2b24d66abff3aa5e873750c76de24c77e12dae91b9d2f4da27 gcc-riscv64-unknown-elf-8.3.0-ubuntu.zip" | sha256sum -c - unzip gcc-riscv64-unknown-elf-8.3.0-ubuntu.zip - echo "$HOME/gcc-riscv64-unknown-elf-8.3.0-ubuntu/bin" >> $GITHUB_PATH - popd - - # Setup a Rust toolchain (pulls version from rust-toolchain file) - - name: Setup Rust toolchain - uses: actions-rs/toolchain@v1 - with: - components: rustfmt, clippy - - # Install elf2tabl to be able to build userspace apps + # Install elf2tab to be able to build userspace apps - name: Install elf2tab run: | - cargo install elf2tab + cargo install elf2tab@0.12.0 # Install tockloader, which is used to prepare binaries with userspace # applications. - name: Install tockloader run: | - pip3 install "tockloader==1.8.0" + pip3 install tockloader==1.12.0 # Clone tock-litex support repository under ./tock-litex, check out the # targeted release. - name: Checkout the tock-litex repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: lschuermann/tock-litex - # Currently pointing to a fixed commit, switch to a tag on the next - # release with the Python requirements.txt included. - # - # Also, currently using the dev/litex-sim-ci branch, which - # pulls in a forked LiteX with GPIO support in the - # simulation and the simctrl modules - ref: da3f33968ae018c33dd10d705fd7e3ca04e5e056 + # The pinned revision is different from the targeted release as + # documented in the LiteX boards, as the CI requires special patches + # to LiteX for interacting with the simulation: + ref: 2024011101-tock-ci-1 path: tock-litex # Install all of the required Python packages from the tock-litex' @@ -87,10 +76,13 @@ jobs: - name: Install Python packages pinned by the tock-litex revision run: | pushd tock-litex - # Unfortunately the valentyusb package fails to install because - # it does not have a setup.py. Thus ignore it, we don't need it - # here anyways (would need it for LiteX unit tests though). - sed -i '/valentyusb/d' ./requirements.txt + # Migen is the DSL which the LiteX ecosystem uses as its + # hardware-description language. It effectively provides a set of + # Python classes and constructs which can be translated into Verilog. + # It is not a package of the LiteX ecosystem, and thus not in the + # requirements.txt, but it is required to be present on the system. + # It should not require any specific or patched version. + pip3 install migen==0.9.2 pip3 install -r requirements.txt popd @@ -105,15 +97,15 @@ jobs: # Revision to checkout defined in the main tock repository in # .libtock_c_ci_rev - name: Checkout libtock-c CI revision - uses: actions/checkout@v2 + uses: actions/checkout@v3 with: repository: tock/libtock-c # Pins a libtock-c revision for LiteX CI tests. In case of # bugs fixed in libtock-c, backwards-incompatible changes in # Tock or new tests this might need to be updated. # - # libtock-c of December 13, 2021, 2:18 PM GMT-5 - ref: fb3ffa6234659a660bffc8fc33e938a30270576a + # libtock-c of Feb 9, 2024, 3:08 PM EST + ref: 0e72c922e13cd83a1aaae7a5f587099d45815e6a path: libtock-c - name: Build libtock-c apps @@ -122,8 +114,8 @@ jobs: # memory addresses such that tockloader can place the non-PIC apps # into the kernel binary properly. export TOCK_TARGETS="\ - rv32i|rv32i.0x00080060.0x40008000|0x00080060|0x40008000 - rv32i|rv32i.0x00088060.0x40010000|0x00088060|0x40010000" + rv32imc|rv32imc.0x00080080.0x40008000|0x00080080|0x40008000 + rv32imc|rv32imc.0x00088080.0x40010000|0x00088080|0x40010000" export LIBTOCK_C_APPS="\ c_hello \ tests/console_timeout \ diff --git a/.github/workflows/mergequeue_docs.yml b/.github/workflows/mergequeue_docs.yml new file mode 100644 index 0000000000..6cf8794b62 --- /dev/null +++ b/.github/workflows/mergequeue_docs.yml @@ -0,0 +1,34 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +# Netlify's own CI builds both deploy previews for PRs, as well as the +# production deploy for the master branch. We use this workflow purely as we +# can't have Netlify build the wildcard gh-readonly-queue/* branches. This +# workflow thus ensures that docs build successfully (albeit not in the exact +# same environment as Netlify's). +# +# See issue #3428 for more information. + +name: docs-ci +env: + TERM: dumb # Identical to Netlify build environment + +on: + merge_group: + +permissions: + contents: read + +jobs: + ci-docs: + strategy: + matrix: + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v3 + - # This also sets up the rustup environment + name: ci-netlify-build + run: tools/netlify-build.sh diff --git a/.github/workflows/tockbot-nightly.yml b/.github/workflows/tockbot-nightly.yml new file mode 100644 index 0000000000..158373505d --- /dev/null +++ b/.github/workflows/tockbot-nightly.yml @@ -0,0 +1,134 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +name: "Tockbot" + +on: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + inputs: + dispatch-job: + description: 'Which job to execute (choose between "all", "maint-nightly")' + required: true + default: 'all' + dry-run: + description: 'Whether to execute the jobs as dry-run' + required: true + default: true + +jobs: + dispatcher: + runs-on: ubuntu-latest + + # This job determines which other jobs should be run: + outputs: + run-maint-nightly: ${{ steps.dispatch-logic.outputs.run-maint-nightly }} + dry-run: ${{ steps.dispatch-logic.outputs.dry-run }} + + steps: + # On pushes we want to check whether any changes have been made + # to the Tockbot code base. Disabled for now: + - uses: actions/checkout@v4 + + # Dispatcher business logic: + - name: Dispatch Tockbot Jobs + id: dispatch-logic + env: + DISPATCH_JOB: ${{ github.event.inputs.dispatch-job }} + DISPATCH_DRY_RUN: ${{ github.event.inputs.dry-run }} + run: | + if [ "$GITHUB_EVENT_NAME" == "workflow_dispatch" ]; then + if [ "$DISPATCH_DRY_RUN" == "true" ]; then + echo "dry-run=true" >> $GITHUB_OUTPUT + elif [ "$DISPATCH_DRY_RUN" == "false" ]; then + echo "dry-run=false" >> $GITHUB_OUTPUT + else + echo "Error: dry-run not a boolean: \"$DISPATCH_DRY_RUN\"" >&2 + exit 1 + fi + + if [ "$DISPATCH_JOB" == "all" ]; then + echo "run-maint-nightly=true" >> $GITHUB_OUTPUT + elif [ "$DISPATCH_JOB" == "maint-nightly" ]; then + echo "run-maint-nightly=true" >> $GITHUB_OUTPUT + else + echo "Error: unknown job \"$DISPATCH_JOB\"" >&2 + exit 1 + fi + elif [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then + echo "dry-run=true" >> $GITHUB_OUTPUT + echo "run-maint-nightly=true" >> $GITHUB_OUTPUT + elif [ "$GITHUB_EVENT_NAME" == "schedule" ]; then + echo "dry-run=false" >> $GITHUB_OUTPUT + echo "run-maint-nightly=true" >> $GITHUB_OUTPUT + else + echo "Error: unknown event name \"$GITHUB_EVENT_NAME\"" >&2 + exit 1 + fi + + maint-nightly: + runs-on: ubuntu-latest + + # Only run this job if the dispatcher determined to schedule the + # "maint-nightly" or "dry-run" jobs: + needs: dispatcher + if: ${{ needs.dispatcher.outputs.run-maint-nightly == 'true' && needs.dispatcher.outputs.dry-run != 'true' }} + + permissions: + # Give GITHUB_TOKEN write permissions to modify PRs and issues: + pull-requests: write + issues: write + + steps: + # Requires a tock checkout to run from: + - uses: actions/checkout@v4 + + # Setup Python and install dependencies: + - uses: actions/setup-python@v5 + - name: Install Python Dependencies + run: pip install -r tools/tockbot/requirements.txt + + # Run nightly tockbot maintenance: + - name: Nightly Tockbot Maintenance + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DRY_RUN: ${{ needs.dispatcher.outputs.dry-run == 'true' && '-n' || '' }} + run: | + cd tools/tockbot/ + ./tockbot.py -v $DRY_RUN maint-nightly -c ./maint_nightly.yaml + + # We'd like to avoid duplicating this, either by using conditionals in the + # permissions key, or by using YAML anchors, neither of which are supported by + # GH Actions... + maint-nightly-dry-run: + runs-on: ubuntu-latest + + # Only run this job if the dispatcher determined to schedule the + # "maint-nightly" or "dry-run" jobs: + needs: dispatcher + if: ${{ needs.dispatcher.outputs.run-maint-nightly == 'true' && needs.dispatcher.outputs.dry-run == 'true' }} + + permissions: + # Dry-run, read-only access: + pull-requests: read + issues: read + + steps: + # Requires a tock checkout to run from: + - uses: actions/checkout@v4 + + # Setup Python and install dependencies: + - uses: actions/setup-python@v5 + - name: Install Python Dependencies + run: pip install -r tools/tockbot/requirements.txt + + # Run nightly tockbot maintenance: + - name: Nightly Tockbot Maintenance + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DRY_RUN: ${{ needs.dispatcher.outputs.dry-run == 'true' && '-n' || '' }} + run: | + cd tools/tockbot/ + ./tockbot.py -v $DRY_RUN maint-nightly -c ./maint_nightly.yaml diff --git a/.gitignore b/.gitignore index 7986adc68c..a704fcfe78 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + target build/* *.rlib @@ -34,6 +38,9 @@ Temporary Items ## Log Files *.log +## Vim tags files +tags + # Files generated by rustfmt *.bk @@ -67,8 +74,8 @@ package-lock.json .netlify # OpenTitan test build -boards/opentitan/earlgrey-nexysvideo/binary -boards/opentitan/earlgrey-cw310/binary +boards/opentitan/earlgrey-*/binary* +boards/opentitan/earlgrey-*/verilator_build/ # FuseSoC build files boards/swervolf/binary.hex @@ -78,3 +85,6 @@ boards/swervolf/fusesoc_libraries/ # ESP C3 temp hex boards/esp32-c3-devkitM-1/binary.hex + +# Redboard Artemis Nano test binary +boards/apollo3/redboard_artemis_nano/redboard-artemis-nano-tests.bin diff --git a/.lcignore b/.lcignore new file mode 100644 index 0000000000..14c91de026 --- /dev/null +++ b/.lcignore @@ -0,0 +1,32 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. +# Copyright Google LLC 2022. + +/.git/ +/COPYRIGHT +/LICENSE-APACHE +/LICENSE-MIT + +# Files that do not support comments. +*.json +*.md + +# We're a bit more permissive in the doc/ directory. +/doc/**/*.svg +/doc/**/*.txt + +# Files that are not authored by Tock contributors. +/boards/apollo3/ambiq/am_defines.py +/boards/apollo3/ambiq/ambiq_bin2board.py +/boards/apollo3/ambiq/keys_info.py +/boards/arty_e21/core/sifive_coreip_E21_AHB_rtl_eval_v19_05p1_release_arty_a7_100t.mcs +/boards/arty_e21/core/sifive_coreip_E21_FPGA_Evaluation_v19_02_rc0.mcs +/tools/sha256sum/src/main.rs + +# These files are part of the license checker's test suite, and are designed to +# produce license checker errors. +/tools/license-checker/testdata/error_missing.rs +/tools/license-checker/testdata/many_errors.rs +/tools/license-checker/testdata/no_copyright.rs +/tools/license-checker/testdata/no_spdx.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 4c86406b2c..e20461eb81 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,9 @@ { + "files.insertFinalNewline": true, + "editor.defaultFormatter": "rust-lang.rust-analyzer", "editor.formatOnSave": true, - "rust-client.channel": "nightly-2021-12-04", + "rust-analyzer.server.extraEnv": { + "RUSTUP_TOOLCHAIN": "nightly-2024-07-08" + }, + "rust-analyzer.check.allTargets": false, } diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b91a898f0..9a7b7fd845 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,104 @@ +New in 2.1 +========== + +Tock 2.1 has seen numerous changes from Tock 2.0. In particular, the new system +call interface introduced with Tock 2.0 has been refined to provide more +guarantees to processes with respect to sharing and unsharing buffers and +upcalls. Other changes include the introduction of a _userspace-readable allow_ +system call, support for new HILs and boards, and various other bug-fixes and +improvements to code size and documentation. + + * Breaking Changes + + - The implemented encoding of the system call return variant "Success with + u32 and u64" has been changed to match the specification of + [TRD 104](https://github.com/tock/tock/blob/master/doc/reference/trd104-syscalls.md). + Accordingly, the name of the `SyscallReturnVariant` enum variant has been + changed from `SuccessU64U32` to `SuccessU32U64` + (https://github.com/tock/tock/pull/3175). + + - `VirtualMuxAlarm`s now require the `setup()` function to be called in + board set up code after they are created + (https://github.com/tock/tock/pull/2866). + + * Noteworthy Changes + + - Subscribe and allow operations are no longer handled by capsules + themselves, but through the kernel's `Grant` logic itself + (https://github.com/tock/tock/pull/2906). This change has multiple + implications for users of Tock: + + - The `Grant` type accepts the number of read-only and read-write allow + buffers, as well as the number of subscribe upcalls. It will reserve a + fixed amount of space per `Grant` to store the respective allow and + subscribe state. Thus, to make efficient use of `Grant` space, allow + buffer and subscribe upcall numbers should be assigned in a non-sparse + fashion. + + - Legal allow and subscribe calls can no longer be refused by a capsule. + This implies that it is always possible for an application to cause the + kernel to relinquish a previously shared buffer through an `allow` + operation. Similarly, `subscribe` can now be used to infallibly ensure + that a given upcall will not be scheduled by the kernel any longer, + although already enqueued calls to a given upcall function can still be + delivered even after a `subscribe` operation. The precise semantics + around these system calls are described in + [TRD 104](https://github.com/tock/tock/blob/ffa5ce02bb6e2d9f187c7bebccf33905d9c993ec/doc/reference/trd104-syscalls.md). + + - Introduction of a new userspace-readable allow system call, where apps + are explicitly allowed to read buffers shared with the kernel (defined in + a [draft TRD](https://github.com/tock/tock/blob/b2053517b4029a6b16360e34937a05138fdc07c1/doc/reference/trd-userspace-readable-allow-syscalls.md)). + + - Introduction of a read-only state mechanism to convey information to + processes without explicit system calls + (https://github.com/tock/tock/pull/2381). + + - Improvements to kernel code size (e.g., + https://github.com/tock/tock/pull/2836, + https://github.com/tock/tock/pull/2849, + https://github.com/tock/tock/pull/2759, + https://github.com/tock/tock/pull/2823). + + * New HILs + + - `hasher` + - `public_key_crypto` + + * New Platforms + + - OpenTitan EarlGrey CW310 + - Redboard Red-V B + - STM32F429I Discovery development board + - QEMU RISC-V 32-bit "virt" Platform + + * Deprecated Platforms + + - OpenTitan EarlGrey NexysVideo + + * Known Issues + + - This release was tagged despite several known bugs in non-tier-1 boards, + so as to avoid delaying the release. These include: + + - Raspberry Pi Pico: process faults when running IPC examples: + https://github.com/tock/tock/issues/3183 + + - The cortex-m exception handler does not correctly handle all possible + exception entry cases. This is not known to currently manifest on any + examples, but could with unlucky timing: + https://github.com/tock/tock/issues/3109 + + - STM32F303 Discovery: `adc` app runs, but eventually hangs in the app + (seems to be caught in the exit loop, but not sure why it gets there) + + - STM32F303 Discovery: kernel panics lead to only a partial printout of + the panic message before the board enters a reboot loop + + - weact_f401ccu6: `gpio` example fails to generate interrupts on the + input pin. This board is likely to be deprecated soon anyway, as it is + no longer available for sale. + + New in 2.0 ========== diff --git a/Cargo.toml b/Cargo.toml index 1d1cc36ff1..6679bed79a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,11 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [workspace] members = [ "arch/cortex-m", + "arch/cortex-v7m", "arch/cortex-m0", "arch/cortex-m0p", "arch/cortex-m3", @@ -12,10 +17,10 @@ members = [ "boards/nano_rp2040_connect", "boards/arty_e21", "boards/opentitan/earlgrey-cw310", - "boards/opentitan/earlgrey-nexysvideo", "boards/esp32-c3-devkitM-1", "boards/clue_nrf52840", "boards/hail", + "boards/hifive_inventor", "boards/hifive1", "boards/imix", "boards/imxrt1050-evkb", @@ -23,23 +28,43 @@ members = [ "boards/litex/sim", "boards/msp_exp432p401r", "boards/microbit_v2", + "boards/wm1110dev", + "boards/makepython-nrf52840", "boards/nordic/nrf52840dk", "boards/nordic/nrf52840_dongle", "boards/nordic/nrf52dk", + "boards/sma_q3", "boards/nucleo_f429zi", "boards/nucleo_f446re", + "boards/particle_boron", "boards/pico_explorer_base", "boards/raspberry_pi_pico", - "boards/redboard_artemis_nano", + "boards/apollo3/redboard_artemis_atp", + "boards/apollo3/redboard_artemis_nano", + "boards/apollo3/lora_things_plus", + "boards/redboard_redv", "boards/stm32f3discovery", "boards/stm32f412gdiscovery", + "boards/stm32f429idiscovery", "boards/teensy40", "boards/nano33ble", + "boards/nano33ble_rev2", + "boards/qemu_rv32_virt", "boards/swervolf", "boards/weact_f401ccu6/", - "capsules", + "boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256", + "boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf", + "boards/configurations/nrf52840dk/nrf52840dk-test-kernel", + "boards/tutorials/nrf52840dk-hotp-tutorial", + "boards/tutorials/nrf52840dk-thread-tutorial", + "capsules/aes_gcm", + "capsules/core", + "capsules/extra", + "capsules/system", "chips/apollo3", "chips/arty_e21_chip", + "chips/e310_g002", + "chips/e310_g003", "chips/e310x", "chips/earlgrey", "chips/esp32", @@ -54,6 +79,7 @@ members = [ "chips/nrf52833", "chips/nrf52840", "chips/nrf5x", + "chips/qemu_rv32_virt_chip", "chips/rp2040", "chips/sam4l", "chips/sifive", @@ -65,6 +91,7 @@ members = [ "chips/stm32f4xx", "chips/swerv", "chips/swervolf-eh1", + "chips/virtio", "kernel", "libraries/enum_primitive", "libraries/riscv-csr", @@ -72,18 +99,13 @@ members = [ "libraries/tock-register-interface", "libraries/tickv", ] -exclude = [ - "tools/alert_codes", - "tools/board-runner", - "tools/qemu", - "tools/litex-ci-runner", - "tools/qemu-runner", - "tools/sha256sum", - "tools/usb/bulk-echo", - "tools/usb/bulk-echo-fast", - "tools/usb/bulk-test", - "tools/usb/control-test", -] +exclude = ["tools/"] +resolver = "2" + +[workspace.package] +version = "0.1.0" +authors = ["Tock Project Developers "] +edition = "2021" [profile.dev] panic = "abort" @@ -97,3 +119,183 @@ lto = true opt-level = "z" debug = true codegen-units = 1 + +# CLIPPY CONFIGURATION +# +# We first disallow all lints in a particular group, then re-allow each one +# Tock does not comply with or we do not want to use. +# +# For each group there are three sections: +# 1. The first section are lints we almost certainly don't want. +# 2. The second section are lints we may not want, we probably have to see the +# resulting diff. +# 3. The third section are lints that we do want we just need to fixup the code +# to pass the lint checks. +# +# There are some lints we specifically do not want: +# +# - `clippy::if_same_then_else`: There are often good reasons to enumerate +# different states that have the same effect. +# - `clippy::manual_unwrap_or_default`: As of Apr 2024, this lint has many false +# positives. +[workspace.lints.clippy] +restriction = "allow" + +if_same_then_else = "allow" +manual_unwrap_or_default = "allow" + + +# COMPLEXITY LINTS +complexity = { level = "deny", priority = -1 } + +too_many_arguments = "allow" +type_complexity = "allow" +option_map_unit_fn = "allow" +nonminimal_bool = "allow" +identity-op = "allow" +while-let-loop = "allow" +only_used_in_recursion = "allow" +manual-range-patterns = "allow" +manual-flatten = "allow" + + +zero_prefixed_literal = "allow" + + +match-single-binding = "allow" + + +# STYLE +style = { level = "deny", priority = -1 } + +blocks_in_conditions = "allow" +collapsible_else_if = "allow" +collapsible_if = "allow" +collapsible_match = "allow" +comparison_chain = "allow" +enum-variant-names = "allow" +field-reassign-with-default = "allow" +get_first = "allow" +len_without_is_empty = "allow" +len_zero = "allow" +manual-map = "allow" +manual_range_contains = "allow" +match_like_matches_macro = "allow" +module_inception = "allow" +new-ret-no-self = "allow" +new_without_default = "allow" +redundant_closure = "allow" +result_unit_err = "allow" +single_match = "allow" +upper_case_acronyms = "allow" + + +declare-interior-mutable-const = "allow" +let_and_return = "allow" +missing_safety_doc = "allow" +needless-range-loop = "allow" +option_map_or_none = "allow" +redundant_pattern_matching = "allow" +unusual-byte-groupings = "allow" +doc_lazy_continuation = "allow" + + +# PERF +perf = { level = "deny", priority = -1 } + +large-enum-variant = "allow" + + +# CARGO +cargo = { level = "deny", priority = -1 } + +cargo_common_metadata = "allow" +negative-feature-names = "allow" + + +# NURSERY +nursery = { level = "deny", priority = -1 } + +use_self = "allow" +option_if_let_else = "allow" +cognitive_complexity = "allow" +or_fun_call = "allow" +collection_is_never_read = "allow" + + +manual_clamp = "allow" +unused_peekable = "allow" +branches_sharing_code = "allow" + + +missing_const_for_fn = "allow" +redundant_pub_crate = "allow" +equatable_if_let = "allow" +fallible_impl_from = "allow" +derive_partial_eq_without_eq = "allow" +empty_line_after_doc_comments = "allow" +trait_duplication_in_bounds = "allow" +useless_let_if_seq = "allow" +as_ptr_cast_mut = "allow" +unnecessary_struct_initialization = "allow" + + +# PEDANTIC +pedantic = { level = "deny", priority = -1 } + +doc_markdown = "allow" +missing_errors_doc = "allow" +if_not_else = "allow" +cast_sign_loss = "allow" +too_many_lines = "allow" +must_use_candidate = "allow" +manual_let_else = "allow" +single_match_else = "allow" +inline_always = "allow" +module_name_repetitions = "allow" +unnested-or-patterns = "allow" +redundant_else = "allow" +return_self_not_must_use = "allow" +match_same_arms = "allow" +explicit_iter_loop = "allow" +similar_names = "allow" +unnecessary_wraps = "allow" +manual_assert = "allow" +transmute_ptr_to_ptr = "allow" +struct_excessive_bools = "allow" +fn_params_excessive_bools = "allow" +trivially_copy_pass_by_ref = "allow" +borrow_as_ptr = "allow" +tuple_array_conversions = "allow" +verbose_bit_mask = "allow" +large_types_passed_by_value = "allow" +no_mangle_with_rust_abi = "allow" +struct_field_names = "allow" + + +cast_lossless = "allow" +cast_possible_truncation = "allow" +cast_precision_loss = "allow" +range_plus_one = "allow" +missing_panics_doc = "allow" +match_wildcard_for_single_variants = "allow" +unused_self = "allow" +cast-possible-wrap = "allow" +uninlined_format_args = "allow" +unreadable_literal = "allow" +needless_pass_by_value = "allow" +items_after_statements = "allow" +ref_option_ref = "allow" +match_bool = "allow" +redundant_closure_for_method_calls = "allow" +no_effect_underscore_binding = "allow" +iter_without_into_iter = "allow" + + +semicolon_if_nothing_returned = "allow" +ptr_as_ptr = "allow" +ptr_cast_constness = "allow" +mut_mut = "allow" +cast_ptr_alignment = "allow" +used_underscore_binding = "allow" +checked_conversions = "allow" diff --git a/Makefile b/Makefile index 997f65a4a9..20e400cd05 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,13 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # For more information on Tock's make system and the CI setup, see the docs at # https://github.com/tock/tock/tree/master/doc/CodeReview.md#3-continuous-integration ################################################################################ ## -## Interal support that needs to run first +## Internal support that needs to run first ## # First, need to fill out some variables that the Makefile will use @@ -48,9 +52,12 @@ usage: @echo " doc: Builds Tock documentation for all boards" @echo " stack: Prints a basic stack frame analysis for all boards" @echo " clean: Clean all builds" - @echo " format: Runs the rustfmt tool on all kernel sources" + @echo " format-check: Checks for formatting errors in kernel sources" @echo " list: Lists available boards" @echo + @echo "We also define the following aliases:" + @echo " format: cargo fmt" + @echo @echo "The make system also drives all continuous integration and testing:" @echo " $$(tput bold)prepush$$(tput sgr0): Fast checks to run before pushing changes upstream" @echo " ci-all: Run all continuous integration tests (possibly slow!)" @@ -157,27 +164,29 @@ allstack stack stack-analysis: do $(MAKE) --no-print-directory -C "boards/$$f" stack-analysis || exit 1;\ done +.PHONY: licensecheck +licensecheck: + $(call banner,License checker) + @cargo run --manifest-path=tools/license-checker/Cargo.toml --release ## Commands .PHONY: clean clean: @echo "$$(tput bold)Clean top-level Cargo workspace" && cargo clean - @for f in `./tools/list_tools.sh`;\ - do echo "$$(tput bold)Clean tools/$$f";\ - cargo clean --manifest-path "tools/$$f/Cargo.toml" || exit 1;\ - done + @echo "$$(tput bold)Clean tools Cargo workspace" && cargo clean --manifest-path tools/Cargo.toml @echo "$$(tput bold)Clean rustdoc" && rm -rf doc/rustdoc @echo "$$(tput bold)Clean ci-artifacts" && rm -rf tools/ci-artifacts .PHONY: fmt format -fmt format: tools/.format_fresh - $(call banner,Formatting complete) +fmt format: + $(call banner,Running \"cargo fmt\" -- for a complete format check run \"make format-check\") + cargo fmt -# Get a list of all rust source files (everything fmt operates on) -$(eval RUST_FILES_IN_TREE := $(shell (git ls-files | grep '\.rs$$') || find . -type f -name '*.rs')) -tools/.format_fresh: $(RUST_FILES_IN_TREE) - @./tools/run_cargo_fmt.sh $(TOCK_FORMAT_MODE) - @touch tools/.format_fresh +.PHONY: format-check +format-check: + $(call banner,Formatting checker) + @./tools/check_format.sh + $(call banner,Check for formatting complete) .PHONY: list list: @@ -192,7 +201,7 @@ list: ## Meta-Targets -# Run all possible CI. If this passses locally, all cloud CI *must* pass as well. +# Run all possible CI. If this passes locally, all cloud CI *must* pass as well. .PHONY: ci-all ci-all:\ ci-runner-github\ @@ -210,9 +219,10 @@ ci-nosetup: # This is designed for developers, to be run often and before submitting code upstream. .PHONY: prepush prepush:\ - format\ + format-check\ ci-job-clippy\ - ci-job-syntax + ci-job-syntax\ + licensecheck $(call banner,Pre-Push checks all passed!) # Note: Tock runs additional and more intense CI checks on all PRs. # If one of these error, you can run `make ci-job-NAME` to test locally. @@ -268,12 +278,14 @@ ci: ci-help ## These each correspond to a 'status check' line in GitHub PR UX. ## ## These recipes *must not* contain rules, they simply collect jobs. - -# n.b. This *replicates* configuration in the github workflow file -# to allow the GitHub UX to show these subtasks correctly +## +## NOTE: If you modify these, you must also modify the ci.yml CI workflow file +## in `.github/workflows`. This *replicates* configuration in the github +## workflow file to allow the GitHub UX to show these subtasks correctly. .PHONY: ci-runner-github ci-runner-github:\ ci-runner-github-format\ + ci-runner-github-clippy\ ci-runner-github-build\ ci-runner-github-tests\ ci-runner-github-qemu @@ -282,17 +294,21 @@ ci-runner-github:\ .PHONY: ci-runner-github-format ci-runner-github-format:\ ci-job-format\ - ci-job-clippy\ - ci-job-markdown-toc + ci-job-markdown-toc\ + ci-job-readme-check $(call banner,CI-Runner: GitHub format runner DONE) +.PHONY: ci-runner-github-clippy +ci-runner-github-clippy:\ + ci-job-clippy + $(call banner,CI-Runner: GitHub clippy runner DONE) + .PHONY: ci-runner-github-build ci-runner-github-build:\ ci-job-syntax\ ci-job-compilation\ ci-job-debug-support-targets\ - ci-job-collect-artifacts\ - ci-job-cargo-test-build + ci-job-collect-artifacts $(call banner,CI-Runner: GitHub build runner DONE) .PHONY: ci-runner-github-tests @@ -303,7 +319,8 @@ ci-runner-github-tests:\ ci-job-capsules\ ci-job-chips\ ci-job-tools\ - ci-job-miri + ci-job-cargo-test-build\ + ci-job-miri # EXPERIMENTAL $(call banner,CI-Runner: GitHub tests runner DONE) .PHONY: ci-runner-github-qemu @@ -337,14 +354,8 @@ ci-runner-netlify:\ ### ci-runner-github-format jobs: .PHONY: ci-job-format -ci-job-format: - $(call banner,CI-Job: Format Check) - @CI=true TOCK_FORMAT_MODE=diff $(MAKE) format - -.PHONY: ci-job-clippy -ci-job-clippy: - $(call banner,CI-Job: Clippy) - @CI=true ./tools/run_clippy.sh +ci-job-format: licensecheck format-check + $(call banner,CI-Job: Format Check DONE) define ci_setup_markdown_toc $(call banner,CI-Setup: Install markdown-toc) @@ -361,25 +372,44 @@ ci-setup-markdown-toc: define ci_job_markdown_toc $(call banner,CI-Job: Markdown Table of Contents Validation) - @CI=true PATH="node_modules/.bin:${PATH}" tools/toc.sh + @NOWARNINGS=true PATH="node_modules/.bin:${PATH}" tools/toc.sh endef .PHONY: ci-job-markdown-toc ci-job-markdown-toc: ci-setup-markdown-toc $(if $(CI_JOB_MARKDOWN),$(call ci_job_markdown_toc)) +define ci_job_readme_check + $(call banner,CI-Job: README Validation) + tools/check_boards_readme.py + tools/check_capsule_readme.py + tools/check-for-readmes.sh +endef + +.PHONY: ci-job-readme-check +ci-job-readme-check: + $(call ci_job_readme_check) + + + +### ci-runner-github-clippy jobs: +.PHONY: ci-job-clippy +ci-job-clippy: + $(call banner,CI-Job: Clippy) + @cargo clippy -- -D warnings + ### ci-runner-github-build jobs: .PHONY: ci-job-syntax ci-job-syntax: $(call banner,CI-Job: Syntax) - @CI=true $(MAKE) allcheck + @NOWARNINGS=true $(MAKE) allcheck .PHONY: ci-job-compilation ci-job-compilation: $(call banner,CI-Job: Compilation) - @CI=true $(MAKE) allboards + @NOWARNINGS=true $(MAKE) allboards .PHONY: ci-job-debug-support-targets ci-job-debug-support-targets: @@ -389,9 +419,9 @@ ci-job-debug-support-targets: # work, but don't build them for every board. # # The choice of building for the nrf52dk was chosen by random die roll. - @CI=true $(MAKE) -C boards/nordic/nrf52dk lst - @CI=true $(MAKE) -C boards/nordic/nrf52dk debug - @CI=true $(MAKE) -C boards/nordic/nrf52dk debug-lst + @NOWARNINGS=true $(MAKE) -C boards/nordic/nrf52dk lst + @NOWARNINGS=true $(MAKE) -C boards/nordic/nrf52dk debug + @NOWARNINGS=true $(MAKE) -C boards/nordic/nrf52dk debug-lst .PHONY: ci-job-collect-artifacts ci-job-collect-artifacts: ci-job-compilation @@ -413,10 +443,11 @@ ci-job-collect-artifacts: ci-job-compilation .PHONY: ci-job-libraries ci-job-libraries: $(call banner,CI-Job: Libraries) - @cd libraries/enum_primitive && CI=true RUSTFLAGS="-D warnings" cargo test - @cd libraries/riscv-csr && CI=true RUSTFLAGS="-D warnings" cargo test - @cd libraries/tock-cells && CI=true RUSTFLAGS="-D warnings" cargo test - @cd libraries/tock-register-interface && CI=true RUSTFLAGS="-D warnings" cargo test + @cd libraries/enum_primitive && NOWARNINGS=true RUSTFLAGS="-D warnings" cargo test + @cd libraries/riscv-csr && NOWARNINGS=true RUSTFLAGS="-D warnings" cargo test + @cd libraries/tock-cells && NOWARNINGS=true RUSTFLAGS="-D warnings" cargo test + @cd libraries/tock-register-interface && NOWARNINGS=true RUSTFLAGS="-D warnings" cargo test + @cd libraries/tickv && NOWARNINGS=true RUSTFLAGS="-D warnings" cargo test .PHONY: ci-job-archs ci-job-archs: @@ -424,20 +455,22 @@ ci-job-archs: @for arch in `./tools/list_archs.sh`;\ do echo "$$(tput bold)Test $$arch";\ cd arch/$$arch;\ - CI=true RUSTFLAGS="-D warnings" TOCK_KERNEL_VERSION=ci_test cargo test || exit 1;\ + NOWARNINGS=true RUSTFLAGS="-D warnings" TOCK_KERNEL_VERSION=ci_test cargo test || exit 1;\ cd ../..;\ done .PHONY: ci-job-kernel ci-job-kernel: $(call banner,CI-Job: Kernel) - @cd kernel && CI=true RUSTFLAGS="-D warnings" TOCK_KERNEL_VERSION=ci_test cargo test + @cd kernel && NOWARNINGS=true RUSTFLAGS="-D warnings" TOCK_KERNEL_VERSION=ci_test cargo test .PHONY: ci-job-capsules ci-job-capsules: $(call banner,CI-Job: Capsules) @# Capsule initialization depends on board/chip specific imports, so ignore doc tests - @cd capsules && CI=true RUSTFLAGS="-D warnings" TOCK_KERNEL_VERSION=ci_test cargo test --lib --examples + @cd capsules/core && NOWARNINGS=true RUSTFLAGS="-D warnings" TOCK_KERNEL_VERSION=ci_test cargo test + @cd capsules/extra && NOWARNINGS=true RUSTFLAGS="-D warnings" TOCK_KERNEL_VERSION=ci_test cargo test + @cd capsules/system && NOWARNINGS=true RUSTFLAGS="-D warnings" TOCK_KERNEL_VERSION=ci_test cargo test .PHONY: ci-job-chips ci-job-chips: @@ -445,7 +478,7 @@ ci-job-chips: @for chip in `./tools/list_chips.sh`;\ do echo "$$(tput bold)Test $$chip";\ cd chips/$$chip;\ - CI=true RUSTFLAGS="-D warnings" TOCK_KERNEL_VERSION=ci_test cargo test || exit 1;\ + NOWARNINGS=true RUSTFLAGS="-D warnings" TOCK_KERNEL_VERSION=ci_test cargo test || exit 1;\ cd ../..;\ done @@ -477,12 +510,8 @@ ci-setup-tools: define ci_job_tools $(call banner,CI-Job: Tools) - @for tool in `./tools/list_tools.sh`;\ - do echo "$$(tput bold)Build & Test $$tool";\ - cd tools/$$tool;\ - CI=true RUSTFLAGS="-D warnings" cargo build --all-targets || exit 1;\ - cd - > /dev/null;\ - done + @NOWARNINGS=true RUSTFLAGS="-D warnings" \ + cargo test --all-targets --manifest-path=tools/Cargo.toml --workspace || exit 1 endef .PHONY: ci-job-tools @@ -490,33 +519,31 @@ ci-job-tools: ci-setup-tools $(if $(CI_JOB_TOOLS),$(call ci_job_tools)) -.PHONY: ci-setup-miri -ci-setup-miri: - @rustup component list | grep miri | grep -q installed || rustup component add miri - .PHONY: ci-job-miri -ci-job-miri: ci-setup-miri +ci-job-miri: $(call banner,CI-Job: Miri) # # Note: This is highly experimental and limited at the moment. # @# Hangs forever during `Building` for this one :shrug: - @#cd libraries/tock-register-interface && CI=true cargo miri test - @cd kernel && CI=true cargo miri test - @for a in $$(tools/list_archs.sh); do cd arch/$$a && CI=true cargo miri test && cd ../..; done - @cd capsules && CI=true cargo miri test - @for c in $$(tools/list_chips.sh); do cd chips/$$c && CI=true cargo miri test && cd ../..; done + @#cd libraries/tock-register-interface && NOWARNINGS=true cargo miri test + @cd kernel && NOWARNINGS=true cargo miri test + @for a in $$(tools/list_archs.sh); do cd arch/$$a && NOWARNINGS=true cargo miri test && cd ../..; done + @cd capsules/core && NOWARNINGS=true cargo miri test + @cd capsules/extra && NOWARNINGS=true cargo miri test + @cd capsules/system && NOWARNINGS=true cargo miri test + @for c in $$(tools/list_chips.sh); do cd chips/$$c && NOWARNINGS=true cargo miri test && cd ../..; done .PHONY: ci-job-cargo-test-build ci-job-cargo-test-build: @$(MAKE) NO_RUN="--no-run" -C "boards/opentitan/earlgrey-cw310" test - @$(MAKE) NO_RUN="--no-run" -C "boards/opentitan/earlgrey-nexysvideo" test @$(MAKE) NO_RUN="--no-run" -C "boards/esp32-c3-devkitM-1" test -### ci-runner-github-qemu jobs: -QEMU_COMMIT_HASH=af531756d25541a1b3b3d9a14e72e7fedd941a2e + +### ci-runner-github-qemu jobs: +QEMU_COMMIT_HASH=1600b9f46b1bd08b00fe86c46ef6dbb48cbe10d6 define ci_setup_qemu_riscv $(call banner,CI-Setup: Build QEMU) @# Use the latest QEMU as it has OpenTitan support @@ -527,22 +554,6 @@ define ci_setup_qemu_riscv @$(MAKE) -C "tools/qemu/build" -j2 || (echo "You might need to install some missing packages" || exit 127) endef -define ci_setup_qemu_opentitan - $(call banner,CI-Setup: Get OpenTitan boot ROM image) - # Download OpenTitan image. The latest image URL is available at - # https://storage.googleapis.com/artifacts.opentitan.org/latest.txt - # We download a fixed version so that new OpenTitan images do not - # unexpectedly change OpenTitan's behavior in our CI. - @printf "Downloading OpenTitan boot ROM\n" - @pwd=$$(pwd) && \ - temp=$$(mktemp -d) && \ - cd $$temp && \ - curl 'https://storage.googleapis.com/artifacts.opentitan.org/opentitan-earlgrey_silver_release_v5-157-ga28c280bb.tar.xz' \ - --output opentitan-dist.tar.xz; \ - tar -xf opentitan-dist.tar.xz; \ - mv opentitan-earlgrey_silver_release_*/sw/device/boot_rom/boot_rom_fpga_nexysvideo.elf $$pwd/tools/qemu-runner/opentitan-boot-rom.elf -endef - .PHONY: ci-setup-qemu ci-setup-qemu: $(call ci_setup_helper,\ @@ -551,44 +562,35 @@ ci-setup-qemu: Clone QEMU and run its build scripts,\ ci_setup_qemu_riscv,\ CI_JOB_QEMU_RISCV) - $(call ci_setup_helper,\ - [[ $$(cksum tools/qemu-runner/opentitan-boot-rom.elf | cut -d" " -f1) == "328682170" ]] && echo yes,\ - Download opentitan archive and unpack a ROM image,\ - ci_setup_qemu_opentitan,\ - CI_JOB_QEMU_OPENTITAN) - $(if $(CI_JOB_QEMU_RISCV),$(if $(CI_JOB_QEMU_OPENTITAN),$(eval CI_JOB_QEMU := true))) - - + $(if $(CI_JOB_QEMU_RISCV),$(eval CI_JOB_QEMU := true)) define ci_job_qemu $(call banner,CI-Job: QEMU) @cd tools/qemu-runner;\ PATH="$(shell pwd)/tools/qemu/build/riscv32-softmmu/:${PATH}"\ - CI=true cargo run + NOWARNINGS=true cargo run @cd boards/opentitan/earlgrey-cw310;\ PATH="$(shell pwd)/tools/qemu/build/riscv32-softmmu/:${PATH}"\ make test - @cd boards/opentitan/earlgrey-nexysvideo;\ - PATH="$(shell pwd)/tools/qemu/build/riscv32-softmmu/:${PATH}"\ - make test endef .PHONY: ci-job-qemu ci-job-qemu: ci-setup-qemu $(if $(CI_JOB_QEMU),$(call ci_job_qemu)) -.PHONY: board-release-test -board-release-test: - @cd tools/board-runner;\ - cargo run ${TARGET} ### ci-runner-netlify jobs: .PHONY: ci-job-rustdoc ci-job-rustdoc: $(call banner,CI-Job: Rustdoc Documentation) - @CI=true tools/build-all-docs.sh + @NOWARNINGS=true tools/build-all-docs.sh ## End CI rules ## -################################################################### +################################################################################ + +.PHONY: board-release-test +board-release-test: + @cd tools/board-runner;\ + cargo run ${TARGET} diff --git a/README.md b/README.md index 84a5be36dc..095c70df79 100644 --- a/README.md +++ b/README.md @@ -2,97 +2,55 @@ [![tock-ci](https://github.com/tock/tock/workflows/tock-ci/badge.svg)][tock-ci] [![slack](https://img.shields.io/badge/slack-tockos-informational)][slack] - -Tock is an embedded operating system designed for running multiple concurrent, mutually -distrustful applications on Cortex-M and RISC-V based embedded platforms. -Tock's design -centers around protection, both from potentially malicious applications and -from device drivers. Tock uses two mechanisms to protect different components -of the operating system. First, the kernel and device drivers are written in -Rust, a systems programming language that provides compile-time memory safety, -type safety and strict aliasing. Tock uses Rust to protect the kernel (e.g. the -scheduler and hardware abstraction layer) from platform specific device drivers -as well as isolate device drivers from each other. Second, Tock uses memory -protection units to isolate applications from each other and the kernel. +[![book](https://img.shields.io/badge/book-Tock_Book-green)][tock-book] + +Tock is an embedded operating system designed for running multiple concurrent, +mutually distrustful applications on Cortex-M and RISC-V based embedded +platforms. Tock's design centers around protection, both from potentially +malicious applications and from device drivers. Tock uses two mechanisms to +protect different components of the operating system. First, the kernel and +device drivers are written in Rust, a systems programming language that provides +compile-time memory safety and type safety. Tock uses Rust to protect the kernel +(e.g. the scheduler and hardware abstraction layer) from platform specific +device drivers as well as isolate device drivers from each other. Second, Tock +uses memory protection units to isolate applications from each other and the +kernel. [tock-ci]: https://github.com/tock/tock/actions?query=branch%3Amaster+workflow%3Atock-ci -Tock 2.0! +Tock 2.x! --------- -Tock is now on its second major release! Here are some 2.0 highlights, and see -the [release notes](https://github.com/tock/tock/releases/tag/release-2.0) for -more detail, or the [changelog](CHANGELOG.md#new-in-20) for the complete set of -changes. - -- Revamped system call interface. -- Support for 11 new hardware platforms. -- Updated kernel types. -- Many new and improved HILs. - -As 2.0 includes many breaking changes, to use the new kernel you will need to -ensure you have updated versions of userspace apps and the various Tock tools. -We recommend if you are using git that you do a `git pull`, and if you are using -tagged releases be sure to update to the 2.0 release. - -Initially, only [libtock-c](https://github.com/tock/libtock-c) is compatible with -Tock 2.0. Work on supporting Tock 2.0 for libtock-rs is [under -development](https://github.com/tock/libtock-rs/issues/322). - - -Learn More ----------- - -How would you like to get started? - -### Learn How Tock Works - -Tock is documented in the [doc](doc) folder. Read through the guides there to -learn about the overview and design of Tock, its implementation, and much -more. - - -### Use Tock - -Follow our [getting started guide](doc/Getting_Started.md) to set up your -system to compile Tock. +Tock is now on its second major release! For a summary of the latest new +features and improvements, check out the [changelog](CHANGELOG.md). -Head to the [hardware page](https://www.tockos.org/hardware/) -to learn about the hardware platforms Tock supports. Also check out the -[Tock Book](https://book.tockos.org) for a step-by-step introduction to getting -Tock up and running. -Find example applications that run on top of the Tock kernel written in both -[Rust](https://github.com/tock/libtock-rs) and -[C](https://github.com/tock/libtock-c). - - -### Develop Tock - -Read our [getting started guide](doc/Getting_Started.md) to get the correct -version of the Rust compiler, then look through the `/kernel`, `/capsules`, -`/chips`, and `/boards` directories. There are also generated [source code -docs](https://docs.tockos.org). +Getting Started +--------------- -We encourage contributions back to Tock and are happy to accept pull requests -for anything from small documentation fixes to whole new platforms. -For details, check out our [Contributing Guide](.github/CONTRIBUTING.md). -To get started, please do not hesitate to submit a PR. We'll happily guide you -through any needed changes. +There are a variety of resources for learning about Tock, contributing to the +project, and getting help. + +- About Tock + - [The Tock Book][tock-book]: online tutorials and documentation + - [Getting Started with Secure Embedded Systems][book-systems]: Tock textbook +- Developing Tock + - [Tock API Docs][tockapidoc] + - [Contributing Guide](.github/CONTRIBUTING.md) + - [Code Review Guidelines](doc/CodeReview.md) +- Getting Help + - [Slack Channel][slack] + - [Email List](https://lists.tockos.org) + - [Tock Blog](https://www.tockos.org/blog/) + - [@talkingtock](https://twitter.com/talkingtock) +[slack]: https://join.slack.com/t/tockos/shared_invite/enQtNDE5ODQyNDU4NTE1LWVjNTgzMTMwYzA1NDI1MjExZjljMjFmOTMxMGIwOGJlMjk0ZTI4YzY0NTYzNWM0ZmJmZGFjYmY5MTJiMDBlOTk -### Keep Up To Date +[tock-book]: https://book.tockos.org -Check out the [blog](https://www.tockos.org/blog/) where the **Talking Tock** -post series highlights what's new in Tock. Also, follow -[@talkingtock](https://twitter.com/talkingtock) on Twitter. +[book-systems]: https://link.springer.com/book/10.1007/978-1-4842-7789-8 -You can also browse our -[email group](https://groups.google.com/forum/#!forum/tock-dev) -and our [Slack][slack] to see -discussions on Tock development. - -[slack]: https://join.slack.com/t/tockos/shared_invite/enQtNDE5ODQyNDU4NTE1LWVjNTgzMTMwYzA1NDI1MjExZjljMjFmOTMxMGIwOGJlMjk0ZTI4YzY0NTYzNWM0ZmJmZGFjYmY5MTJiMDBlOTk +[tockapidoc]: https://docs.tockos.org Code of Conduct @@ -102,9 +60,9 @@ The Tock project adheres to the Rust [Code of Conduct][coc]. All contributors, community members, and visitors are expected to familiarize themselves with the Code of Conduct and to follow these standards in all -Tock-affiliated environments, which includes but is not limited to -repositories, chats, and meetup events. For moderation issues, please contact -members of the @tock/core-wg. +Tock-affiliated environments, which includes but is not limited to repositories, +chats, and meetup events. For moderation issues, please contact members of the +@tock/core-wg. [coc]: https://www.rust-lang.org/conduct.html @@ -148,7 +106,7 @@ Amit Levy, Bradford Campbell, Branden Ghena, Daniel B. Giffin, Pat Pannuto, Prab
Other Tock-related papers -

There are also two shorter papers that look at potential limitations of the Rust language for embedded software development. The earlier PLOS paper lays out challenges and the later APSys paper lays out potential solutions. Some persons describing work on programming languages and type theory may benefit from these references, but generally, most work should cite the SOSP paper above.

+

There are two shorter papers that look at potential limitations of the Rust language for embedded software development. The earlier PLOS paper lays out challenges and the later APSys paper lays out potential solutions. Some persons describing work on programming languages and type theory may benefit from these references, but generally, most work should cite the SOSP paper above.

APSys: The Case for Writing a Kernel in Rust

 @inproceedings{levy17rustkernel,
@@ -187,6 +145,25 @@ Amit Levy, Bradford Campbell, Branden Ghena, Daniel B. Giffin, Pat Pannuto, Prab
 	address = {New York, NY, USA},
 	conference-url = {http://plosworkshop.org/2015/},
 	author = {Levy, Amit and Andersen, Michael P and Campbell, Bradford and Culler, David and Dutta, Prabal and Ghena, Branden and Levis, Philip and Pannuto, Pat},
+}
+

There is also a paper on the Tock security model. The threat model documentation in the docs/ folder is the source of truth for the current Tock threat model, but this paper represents a snapshot of the reasoning behind the Tock threat model and details how it compares to those in similar embedded OSes.

+

EuroSec: Tiered Trust for useful embedded systems security

+
+@inproceedings{10.1145/3517208.3523752,
+	author = {Ayers, Hudson and Dutta, Prabal and Levis, Philip and Levy, Amit and Pannuto, Pat and Van Why, Johnathan and Watson, Jean-Luc},
+	title = {Tiered Trust for Useful Embedded Systems Security},
+	year = {2022},
+	isbn = {9781450392556},
+	publisher = {Association for Computing Machinery},
+	address = {New York, NY, USA},
+	url = {https://doi.org/10.1145/3517208.3523752},
+	doi = {10.1145/3517208.3523752},
+	booktitle = {Proceedings of the 15th European Workshop on Systems Security},
+	pages = {15–21},
+	numpages = {7},
+	keywords = {security, embedded systems, operating systems, IoT},
+	location = {Rennes, France},
+	series = {EuroSec '22}
 }
diff --git a/arch/cortex-m/Cargo.toml b/arch/cortex-m/Cargo.toml index 4a02795beb..6b1a94ad4d 100644 --- a/arch/cortex-m/Cargo.toml +++ b/arch/cortex-m/Cargo.toml @@ -1,8 +1,15 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "cortexm" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] kernel = { path = "../../kernel" } + +[lints] +workspace = true diff --git a/arch/cortex-m/src/dcb.rs b/arch/cortex-m/src/dcb.rs new file mode 100644 index 0000000000..5cb43c1a07 --- /dev/null +++ b/arch/cortex-m/src/dcb.rs @@ -0,0 +1,100 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! ARM Debug Control Block +//! +//! +//! Implementation matches `ARM DDI 0403E.e` + +use kernel::utilities::registers::interfaces::ReadWriteable; +use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite, WriteOnly}; +use kernel::utilities::StaticRef; + +register_structs! { + DcbRegisters { + /// Debug Halting Control and Status Register + (0x00 => dhcsr: ReadWrite), + + /// Debug Core Register Selector Register + (0x04 => dcrsr: WriteOnly), + + /// Debug Core Register Data Register + (0x08 => dcrdr: ReadWrite), + + /// Debug Exception and Monitor Control Register + (0xC => demcr: ReadWrite), + + (0x10 => @END), + } +} + +register_bitfields![u32, + DebugHaltingControlAndStatus [ + /// Debug key. 0xA05F must be written to enable write access to bits 15 through 0. + /// WO. + DBGKEY OFFSET(16) NUMBITS(16), + + /// Is 1 if at least one reset happend since last read of this register. Is cleared to 0 on + /// read. + /// RO. + S_RESET_ST OFFSET(25) NUMBITS(1), + + /// Is 1 if at least one instruction was retired since last read of this register. + /// It is cleared to 0 after a read of this register. + /// RO. + S_RETIRE_ST OFFSET(24) NUMBITS(1), + + /// Is 1 when the processor is locked up doe tu an unrecoverable instruction. + /// RO. + S_LOCKUP OFFSET(20) NUMBITS(4), + + /// Is 1 if processor is in debug state. + /// RO. + S_SLEEP OFFSET(18) NUMBITS(1), + + /// Is used as a handshake flag for transfers through DCRDR. Writing to DCRSR clears this + /// bit to 0. Is 0 if there is a transfer that has not completed and 1 on completion of the DCRSR transfer. + /// + /// RW. + S_REGREADY OFFSET(16) NUMBITS(1), + ], + DebugCoreRegisterSelector [ + DBGTMP OFFSET(0) NUMBITS(32) + ], + DebugCoreRegisterData [ + DBGTMP OFFSET(0) NUMBITS(32) + ], + DebugExceptionAndMonitorControl [ + /// Write 1 to globally enable all DWT and ITM features. + TRCENA OFFSET(24) NUMBITS(1), + + /// Debug monitor semaphore bit. + /// Monitor software defined. + MON_REQ OFFSET(19) NUMBITS(1), + + /// Write 1 to make step request pending. + MON_STEP OFFSET(18) NUMBITS(1), + /// Write 0 to clear the pending state of the DebugMonitor exception. + /// Writing 1 pends the exception. + MON_PEND OFFSET(17) NUMBITS(1), + + /// Write 1 to enable DebugMonitor exception. + MON_EN OFFSET(16) NUMBITS(1), + ], +]; + +const DCB: StaticRef = unsafe { StaticRef::new(0xE000EDF0 as *const DcbRegisters) }; + +/// Enable the Debug and Trace unit `DWT` +/// This has to be enabled before using any feature of the `DWT` +pub fn enable_debug_and_trace() { + DCB.demcr + .modify(DebugExceptionAndMonitorControl::TRCENA::SET); +} + +/// Disable the Debug and Trace unit `DWT` +pub fn disable_debug_and_trace() { + DCB.demcr + .modify(DebugExceptionAndMonitorControl::TRCENA::CLEAR); +} diff --git a/arch/cortex-m/src/dwt.rs b/arch/cortex-m/src/dwt.rs new file mode 100644 index 0000000000..398404efa3 --- /dev/null +++ b/arch/cortex-m/src/dwt.rs @@ -0,0 +1,479 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! ARM Data Watchpoint and Trace Unit +//! +//! + +use super::dcb; +use kernel::hil; +use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; +use kernel::utilities::registers::{register_bitfields, register_structs, ReadOnly, ReadWrite}; +use kernel::utilities::StaticRef; + +register_structs! { + /// In an ARMv7-M processor, a System Control Block (SCB) in the SCS + /// provides key status information and control features for the processor. + DwtRegisters { + // Control Register + (0x00 => ctrl: ReadWrite), + + // Cycle Count Register + (0x04 => cyccnt: ReadWrite), + + // CPI Count Register + (0x08 => cpicnt: ReadWrite), + + // Exception Overhead Register + (0x0C => exccnt: ReadWrite), + + // Sleep Count Register + (0x10 => sleepcnt: ReadWrite), + + // LSU Count Register + (0x14 => lsucnt: ReadWrite), + + // Folder-Instruction Count Register + (0x18 => foldcnt: ReadWrite), + + // Program Count Sample Register + (0x1C => pcsr: ReadOnly), + + // Comparator Register0 + (0x20 => comp0: ReadWrite), + + // Mask Regsiter0 + // The maximum mask size is 32KB + (0x24 => mask0: ReadWrite), + + // Function Register0 + (0x28 => function0: ReadWrite), + + (0x2c => _reserved0), + + // Comparator Register1 + (0x30 => comp1: ReadWrite), + + // Mask Regsiter1 + // The maximum mask size is 32KB + (0x34 => mask1: ReadWrite), + + // Function Register1 + (0x38 => function1: ReadWrite), + + (0x3c => _reserved1), + + // Comparator Register2 + (0x40 => comp2: ReadWrite), + + // Mask Regsiter2 + // The maximum mask size is 32KB + (0x44 => mask2: ReadWrite), + + // Function Register2 + (0x48 => function2: ReadWrite), + + (0x4c => _reserved2), + + // Comparator Register3 + (0x50 => comp3: ReadWrite), + + // Mask Regsiter3 + // The maximum mask size is 33KB + (0x54 => mask3: ReadWrite), + + // Function Register3 + (0x58 => function3: ReadWrite), + + (0x5c => @END), + } +} + +register_bitfields![u32, + Control [ + /// Number of Camparators implemented. + /// RO. + NUMCOMP OFFSET(28) NUMBITS(4), + + /// Shows if trace sampling and exception tracing is implemented + /// Is 0 if supported, is 1 if it is not. + /// RO + NOTRCPKT OFFSET(27) NUMBITS(1), + + /// Shows if external match signals ([`CMPMATCH`]) are implemented + /// Is 0 if supported, is 1 if it is not. + /// RO + NOEXITTRIG OFFSET(26) NUMBITS(1), + + /// Shows if the cycle counter is implemented + /// Is 0 if supported, is 1 if it is not. + /// RO + NOCYCCNT OFFSET(25) NUMBITS(1), + + /// Shows if profiling counters are supported. + /// Is 0 if supported, is 1 if it is not. + /// RO + NOPERFCNT OFFSET(24) NUMBITS(1), + + /// Writing 1 enables event counter packets generation if [`PCSAMPLENA`] is set to 0. + /// Defaults to 0b0 on reset. + /// WARN: This bit is UNKNOWN if `NOTPRCPKT` or `NOCYCCNT` is read as one. + /// RW + CYCEVTENA OFFSET(22) NUMBITS(1), + + /// Writing 1 enables generation of folded instruction counter overflow event. Defaults to + /// 0b0 on reset. + /// WARN: This bit is UNKNOWN if `NOPERFCNT` reads as one. + /// RW + FOLDEVTENA OFFSET(21) NUMBITS(1), + + /// Writing 1 enables generation of LSU counter overflow event. + /// Defaults to 0b0 on reset. + /// WARN: This bit is UNKNOWN if `NOPERFCNT` reads as one. + /// RW + LSUEVTENA OFFSET(20) NUMBITS(1), + + /// Writing 1 enables generation of sleep counter overflow event. + /// Defaults to 0b0 on reset. + /// WARN: This bit is UNKNOWN if `NOPERFCNT` reads as one. + /// RW + SLEEPEVTENA OFFSET(19) NUMBITS(1), + + /// Writing 1 enables generation of exception overhead counter overflow event. + /// Defaults to 0b0 on reset. + /// WARN: This bit is UNKNOWN if `NOPERFCNT` reads as one. + /// RW + EXCEVTENA OFFSET(18) NUMBITS(1), + + /// Writing 1 enables generation of the CPI counter overlow event. + /// Defaults to 0b0 on reset. + /// WARN: This bit is UNKNOWN if `NOPERFCNT` reads as one. + /// RW + CPIEVTENA OFFSET(17) NUMBITS(1), + + /// Writing 1 enables generation of exception trace. + /// Defaults to 0b0 on reset. + /// WARN: This bit is UNKNOWN if `NOTRCPKT` reads as one. + /// RW + EXCTRCENA OFFSET(16) NUMBITS(1), + + /// Writing 1 enables use of [`POSTCNT`] counter as a timer for Periodic PC sample packet + /// generation. + /// Defaults to 0b0 on reset. + /// WARN: This bit is UNKNOWN if `NOTRCPKT` or `NOCYCCNT` read as one. + /// RW + PCSAMPLENA OFFSET(12) NUMBITS(1), + + /// Determines the position of synchronisation packet counter tap on the `CYCCNT` counter + /// and thus the synchronisation packet rate. + /// Defaults to UNKNOWN on reset. + /// WARN: This bit is UNKNOWN if `NOCYCCNT` reads as one. + /// RW + SYNCTAP OFFSET(10) NUMBITS(2), + + /// Determines the position of the `POSTCNT` tap on the `CYCCNT` counter. + /// Defaults to UNKNOWN on reset. + /// WARN: This bit is UNKNOWN if `NOCYCCNT` reads as one. + /// RW + CYCTAP OFFSET(9) NUMBITS(1), + + /// Initial value for the `POSTCNT` counter. + /// Defaults to UNKNOWN on reset. + /// WARN: This bit is UNKNOWN if `NOCYCCNT` reads as one. + /// RW + POSTINIT OFFSET(8) NUMBITS(4), + + /// Reload value for the `POSTCNT` counter. + /// Defaults to UNKNOWN on reset. + /// WARN: This bit is UNKNOWN if `NOCYCCNT` reads as one. + /// RW + POSTPRESET OFFSET(1) NUMBITS(4), + + /// Writing 1 enables `CYCCNT`. + /// Defaults to 0b0 on reset. + /// WARN: This bit is UNKNOWN if `NOCYCCNT` reads as one. + /// RW + CYCNTENA OFFSET(0) NUMBITS(1), + ], + + CycleCount[ + /// When enabled, increases on each processor clock cycle when Control::CYCNTENA and + /// DEMCRL::TRCENA read as one. + /// Wraps to zero on overflow. + CYCCNT OFFSET(0) NUMBITS(32), + ], + + CpiCount[ + /// Base instruction overhead counter. + CPICNT OFFSET(0) NUMBITS(8), + ], + + ExceptionOverheadCount[ + /// Counts cycles spent in exception processing. + EXCCNT OFFSET(0) NUMBITS(8), + ], + + SleepCount[ + /// Counts each cycle the processor is sleeping. + SLEEPCNT OFFSET(0) NUMBITS(8), + ], + + LsuCount[ + /// Counts additional cycles required to excecute all load store instructions + LSUCNT OFFSET(0) NUMBITS(8), + ], + + FoldedInstructionCount[ + /// Increments by one for each instruction that takes 0 cycles to excecute. + FOLDCNT OFFSET(0) NUMBITS(8), + ], + + ProgramCounterSample[ + /// Samples current value of the program counter + /// RO. + EIASAMPLE OFFSET(0) NUMBITS(32), + ], + + Comparator0[ + /// Reference value for comparator 0. + COMP OFFSET(0) NUMBITS(32), + ], + + Comparator0Mask[ + /// Size of ignore mask aplied to the access address for address range matching by comparator 0. + /// WARN: Maximum Mask size is IMPLEMENTATION DEFINED. + MASK OFFSET(0) NUMBITS(5), + ], + + Comparator0Function[ + /// Is one if comparator matches. Reading the register clears it to 0. + /// RO. + MATCHED OFFSET(24) NUMBITS(1), + + /// Second comparator number for linked address comparison. + /// Works, when `DATAVMATCH` and `LNK1ENA` read as one. + /// RW. + DATAVADDR1 OFFSET(16) NUMBITS(4), + + /// Comparator number for linked address comparison. + /// Works, when `DATAVMATCH` reads as one. + /// RW. + DATAVADDR0 OFFSET(12) NUMBITS(4), + + /// Size of data comparision (Byte, Halfword, Word). + /// RW. + DATAVSIZE OFFSET(10) NUMBITS(2), + + /// Reads as one if a second linked comparator is supported. + LNK1ENA OFFSET(9) NUMBITS(1), + + /// Enables data value comparison + /// When 0: Perform address comparison, when 1: data value comparison. + /// RW. + DATAVMATCH OFFSET(8) NUMBITS(1), + + /// Enable cycle count comparision for comparator 0. + /// WARN: Only supported by FUNCTION0 + /// RW. + CYCMATCH OFFSET(7) NUMBITS(1), + + /// Write 1 to enable generation of data trace address packets. + /// WARN: If `Control::NOTRCPKT` reads as zero, this bit is UNKNOWN. + /// RW. + EMITRANGE OFFSET(5) NUMBITS(1), + + /// Selects action taken on comparator match. + /// Resets to 0b0000. + /// RW. + FUNCTION OFFSET(0) NUMBITS(4), + ], + + Comparator1[ + /// Reference value for comparator 0. + COMP OFFSET(0) NUMBITS(32), + ], + + Comparator1Mask[ + /// Size of ignore mask aplied to the access address for address range matching by comparator 0. + /// WARN: Maximum Mask size is IMPLEMENTATION DEFINED. + MASK OFFSET(0) NUMBITS(5), + ], + + Comparator1Function[ + /// Is one if comparator matches. Reading the register clears it to 0. + /// RO. + MATCHED OFFSET(24) NUMBITS(1), + + /// Second comparator number for linked address comparison. + /// Works, when `DATAVMATCH` and `LNK1ENA` read as one. + /// RW. + DATAVADDR1 OFFSET(16) NUMBITS(4), + + /// Comparator number for linked address comparison. + /// Works, when `DATAVMATCH` reads as one. + /// RW. + DATAVADDR0 OFFSET(12) NUMBITS(4), + + /// Size of data comparision (Byte, Halfword, Word). + /// RW. + DATAVSIZE OFFSET(10) NUMBITS(2), + + /// Reads as one if a second linked comparator is supported. + LNK1ENA OFFSET(9) NUMBITS(1), + + /// Enables data value comparison + /// When 0: Perform address comparison, when 1: data value comparison. + /// RW. + DATAVMATCH OFFSET(8) NUMBITS(1), + + /// Write 1 to enable generation of data trace address packets. + /// WARN: If `Control::NOTRCPKT` reads as zero, this bit is UNKNOWN. + /// RW. + EMITRANGE OFFSET(5) NUMBITS(1), + + /// Selects action taken on comparator match. + /// Resets to 0b0000. + /// RW. + FUNCTION OFFSET(0) NUMBITS(4), + ], + + Comparator2[ + /// Reference value for comparator 0. + COMP OFFSET(0) NUMBITS(32), + ], + + Comparator2Mask[ + /// Size of ignore mask aplied to the access address for address range matching by comparator 0. + /// WARN: Maximum Mask size is IMPLEMENTATION DEFINED. + MASK OFFSET(0) NUMBITS(5), + ], + + Comparator2Function[ + /// Is one if comparator matches. Reading the register clears it to 0. + /// RO. + MATCHED OFFSET(24) NUMBITS(1), + + /// Second comparator number for linked address comparison. + /// Works, when `DATAVMATCH` and `LNK1ENA` read as one. + /// RW. + DATAVADDR1 OFFSET(16) NUMBITS(4), + + /// Comparator number for linked address comparison. + /// Works, when `DATAVMATCH` reads as one. + /// RW. + DATAVADDR0 OFFSET(12) NUMBITS(4), + + /// Size of data comparision (Byte, Halfword, Word). + /// RW. + DATAVSIZE OFFSET(10) NUMBITS(2), + + /// Reads as one if a second linked comparator is supported. + LNK1ENA OFFSET(9) NUMBITS(1), + + /// Enables data value comparison + /// When 0: Perform address comparison, when 1: data value comparison. + /// RW. + DATAVMATCH OFFSET(8) NUMBITS(1), + + /// Write 1 to enable generation of data trace address packets. + /// WARN: If `Control::NOTRCPKT` reads as zero, this bit is UNKNOWN. + /// RW. + EMITRANGE OFFSET(5) NUMBITS(1), + + /// Selects action taken on comparator match. + /// Resets to 0b0000. + /// RW. + FUNCTION OFFSET(0) NUMBITS(4), + ], + + Comparator3[ + /// Reference value for comparator 0. + COMP OFFSET(0) NUMBITS(32), + ], + + Comparator3Mask[ + /// Size of ignore mask aplied to the access address for address range matching by comparator 0. + /// WARN: Maximum Mask size is IMPLEMENTATION DEFINED. + MASK OFFSET(0) NUMBITS(5), + ], + + Comparator3Function[ + /// Is one if comparator matches. Reading the register clears it to 0. + /// RO. + MATCHED OFFSET(24) NUMBITS(1), + + /// Second comparator number for linked address comparison. + /// Works, when `DATAVMATCH` and `LNK1ENA` read as one. + /// RW. + DATAVADDR1 OFFSET(16) NUMBITS(4), + + /// Comparator number for linked address comparison. + /// Works, when `DATAVMATCH` reads as one. + /// RW. + DATAVADDR0 OFFSET(12) NUMBITS(4), + + /// Size of data comparision (Byte, Halfword, Word). + /// RW. + DATAVSIZE OFFSET(10) NUMBITS(2), + + /// Reads as one if a second linked comparator is supported. + LNK1ENA OFFSET(9) NUMBITS(1), + + /// Enables data value comparison + /// When 0: Perform address comparison, when 1: data value comparison. + /// RW. + DATAVMATCH OFFSET(8) NUMBITS(1), + + /// Write 1 to enable generation of data trace address packets. + /// WARN: If `Control::NOTRCPKT` reads as zero, this bit is UNKNOWN. + /// RW. + EMITRANGE OFFSET(5) NUMBITS(1), + + /// Selects action taken on comparator match. + /// Resets to 0b0000. + /// RW. + FUNCTION OFFSET(0) NUMBITS(4), + ], + +]; + +const DWT: StaticRef = unsafe { StaticRef::new(0xE0001000 as *const DwtRegisters) }; + +pub struct Dwt { + registers: StaticRef, +} + +impl Dwt { + pub const fn new() -> Self { + Self { registers: DWT } + } + + /// Returns wether a cycle counter is present on the chip. + pub fn is_cycle_counter_present(&self) -> bool { + DWT.ctrl.read(Control::NOCYCCNT) == 0 + } +} + +impl hil::hw_debug::CycleCounter for Dwt { + fn start(&self) { + if self.is_cycle_counter_present() { + // The cycle counter has to be enabled in the DCB block + dcb::enable_debug_and_trace(); + self.registers.ctrl.modify(Control::CYCNTENA::SET); + } + } + + fn stop(&self) { + self.registers.ctrl.modify(Control::CYCNTENA::CLEAR); + } + + fn count(&self) -> u64 { + self.registers.cyccnt.read(CycleCount::CYCCNT) as u64 + } + + fn reset(&self) { + self.registers.ctrl.modify(Control::CYCNTENA::CLEAR); // disable the counter + self.registers.cyccnt.set(0); // reset the counter + } +} diff --git a/arch/cortex-m/src/lib.rs b/arch/cortex-m/src/lib.rs index 820f6e9245..83f43d4c5d 100644 --- a/arch/cortex-m/src/lib.rs +++ b/arch/cortex-m/src/lib.rs @@ -1,13 +1,20 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Generic support for all Cortex-M platforms. #![crate_name = "cortexm"] #![crate_type = "rlib"] -#![feature(asm, asm_sym)] -#![feature(naked_functions)] #![no_std] use core::fmt::Write; +#[cfg(all(target_arch = "arm", target_os = "none"))] +use core::arch::global_asm; + +pub mod dcb; +pub mod dwt; pub mod mpu; pub mod nvic; pub mod scb; @@ -15,200 +22,103 @@ pub mod support; pub mod syscall; pub mod systick; -/// These constants are defined in the linker script. +// These constants are defined in the linker script. extern "C" { - static _estack: u32; - static mut _sstack: u32; - static mut _szero: u32; - static mut _ezero: u32; - static mut _etext: u32; - static mut _srelocate: u32; - static mut _erelocate: u32; -} - -/// The `systick_handler` is called when the systick interrupt occurs, signaling -/// that an application executed for longer than its timeslice. This interrupt -/// handler is no longer responsible for signaling to the kernel thread that an -/// interrupt has occurred, but is slightly more efficient than the -/// `generic_isr` handler on account of not needing to mark the interrupt as -/// pending. -#[cfg(all( - target_arch = "arm", - target_feature = "v7", - target_feature = "thumb-mode", - target_os = "none" -))] -#[naked] -pub unsafe extern "C" fn systick_handler_arm_v7m() { - asm!( - " - // Set thread mode to privileged to switch back to kernel mode. - mov r0, #0 - msr CONTROL, r0 - /* CONTROL writes must be followed by ISB */ - /* http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0321a/BIHFJCAC.html */ - isb - - movw LR, #0xFFF9 - movt LR, #0xFFFF - - // This will resume in the switch to user function where application state - // is saved and the scheduler can choose what to do next. - bx LR - ", - options(noreturn) - ); -} - -/// This is called after a `svc` instruction, both when switching to userspace -/// and when userspace makes a system call. -#[cfg(all( - target_arch = "arm", - target_feature = "v7", - target_feature = "thumb-mode", - target_os = "none" -))] -#[naked] -pub unsafe extern "C" fn svc_handler_arm_v7m() { - asm!( - " - // First check to see which direction we are going in. If the link register - // is something other than 0xfffffff9, then we are coming from an app which - // has called a syscall. - cmp lr, #0xfffffff9 - bne 100f // to_kernel - - // If we get here, then this is a context switch from the kernel to the - // application. Set thread mode to unprivileged to run the application. - mov r0, #1 - msr CONTROL, r0 - /* CONTROL writes must be followed by ISB */ - /* http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0321a/BIHFJCAC.html */ - isb - - // This is a special address to return Thread mode with Process stack - movw lr, #0xfffd - movt lr, #0xffff - // Switch to the app. - bx lr - - 100: // to_kernel - // An application called a syscall. We mark this in the global variable - // `SYSCALL_FIRED` which is stored in the syscall file. - // `UserspaceKernelBoundary` will use this variable to decide why the app - // stopped executing. - ldr r0, =SYSCALL_FIRED - mov r1, #1 - str r1, [r0, #0] - - // Set thread mode to privileged as we switch back to the kernel. - mov r0, #0 - msr CONTROL, r0 - /* CONTROL writes must be followed by ISB */ - /* http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0321a/BIHFJCAC.html */ - isb - - // This is a special address to return Thread mode with Main stack - movw LR, #0xFFF9 - movt LR, #0xFFFF - bx lr", - options(noreturn) - ); + static _szero: *const u32; + static _ezero: *const u32; + static _etext: *const u32; + static _srelocate: *const u32; + static _erelocate: *const u32; } -/// All ISRs are caught by this handler. This must ensure the interrupt is -/// disabled (per Tock's interrupt model) and then as quickly as possible resume -/// the main thread (i.e. leave the interrupt context). The interrupt will be -/// marked as pending and handled when the scheduler checks if there are any -/// pending interrupts. +/// Trait to encapsulate differences in between Cortex-M variants /// -/// If the ISR is called while an app is running, this will switch control to -/// the kernel. -#[cfg(all( - target_arch = "arm", - target_feature = "v7", - target_feature = "thumb-mode", - target_os = "none" -))] -#[naked] -pub unsafe extern "C" fn generic_isr_arm_v7m() { - asm!( - " - // Set thread mode to privileged to ensure we are executing as the kernel. - // This may be redundant if the interrupt happened while the kernel code - // was executing. - mov r0, #0 - msr CONTROL, r0 - /* CONTROL writes must be followed by ISB */ - /* http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0321a/BIHFJCAC.html */ - isb - - // This is a special address to return Thread mode with Main stack - movw LR, #0xFFF9 - movt LR, #0xFFFF - - // Now need to disable the interrupt that fired in the NVIC to ensure it - // does not trigger again before the scheduler has a chance to handle it. We - // do this here in assembly for performance. - // - // The general idea is: - // 1. Get the index of the interrupt that occurred. - // 2. Set the disable bit for that interrupt in the NVIC. - - // Find the ISR number (`index`) by looking at the low byte of the IPSR - // registers. - mrs r0, IPSR // r0 = Interrupt Program Status Register (IPSR) - and r0, #0xff // r0 = r0 & 0xFF - sub r0, #16 // ISRs start at 16, so subtract 16 to get zero-indexed. - - // Now disable that interrupt in the NVIC. - // High level: - // r0 = index - // NVIC.ICER[r0 / 32] = 1 << (r0 & 31) - // - lsrs r2, r0, #5 // r2 = r0 / 32 - - // r0 = 1 << (r0 & 31) - movs r3, #1 // r3 = 1 - and r0, r0, #31 // r0 = r0 & 31 - lsl r0, r3, r0 // r0 = r3 << r0 - - // Load the ICER register address. - mov r3, #0xe180 // r3 = &NVIC.ICER - movt r3, #0xe000 - - // Here: - // - `r2` is index / 32 - // - `r3` is &NVIC.ICER - // - `r0` is 1 << (index & 31) - // - // So we just do: - // - // `*(r3 + r2 * 4) = r0` - // - str r0, [r3, r2, lsl #2] - - /* The pending bit in ISPR might be reset by hardware for pulse interrupts - * at this point. So set it here again so the interrupt does not get lost - * in service_pending_interrupts() - * */ - /* r3 = &NVIC.ISPR */ - mov r3, #0xe200 - movt r3, #0xe000 - /* Set pending bit */ - str r0, [r3, r2, lsl #2] - - // Now we can return from the interrupt context and resume what we were - // doing. If an app was executing we will switch to the kernel so it can - // choose whether to service the interrupt. - bx lr - ", - options(noreturn) - ); +/// This trait contains functions and other associated data (constants) which +/// differs in between different Cortex-M architecture variants (e.g. Cortex-M0, +/// Cortex-M4, etc.). It is not designed to be implemented by an instantiable +/// type and passed around as a runtime-accessible object, but is to be used as +/// a well-defined collection of functions and data to be exposed to +/// architecture-dependent code paths. Hence, implementations can use an `enum` +/// without variants to implement this trait. +/// +/// The fact that some functions are proper trait functions, while others are +/// exposed via associated constants is necessitated by the associated const +/// functions being `#\[naked\]`. To wrap these functions in proper trait +/// functions would require these trait functions to also be `#\[naked\]` to +/// avoid generating a function prologue and epilogue, and we'd have to call the +/// respective backing function from within an asm! block. The associated +/// constants allow us to simply reference any function in scope and export it +/// through the provided CortexMVariant trait infrastructure. +// This approach outlined above has some significant benefits over passing +// functions via symbols, as done before this change (tock/tock#3080): +// +// - By using a trait carrying proper first-level Rust functions, the type +// signatures of the trait and implementing functions are properly +// validated. Before these changes, some Cortex-M variants previously used +// incorrect type signatures (e.g. `*mut u8` instead of `*const usize`) for +// the user_stack argument. It also ensures that all functions are provided by +// a respective sub-architecture at compile time, instead of throwing linker +// errors. +// +// - Determining the respective functions at compile time, Rust might be able to +// perform more aggressive inlining, especially if more device-specific proper +// Rust functions (non hardware-exposed symbols, i.e. not fault or interrupt +// handlers) were to be added. +// +// - Most importantly, this avoid ambiguity with respect to a compiler fence +// being inserted by the compiler around calls to switch_to_user. The asm! +// macro in that function call will cause Rust to emit a compiler fence given +// the nomem option is not passed, but the opaque extern "C" function call +// obscured that code path. While this is probably fine and Rust is obliged to +// generate a compiler fence when switching to C code, having a traceable code +// path for Rust to the asm! macro will remove any remaining ambiguity and +// allow us to argue against requiring volatile accesses to userspace memory +// (during context switches). See tock/tock#2582 for further discussion of +// this issue. +pub trait CortexMVariant { + /// All ISRs not caught by a more specific handler are caught by this + /// handler. This must ensure the interrupt is disabled (per Tock's + /// interrupt model) and then as quickly as possible resume the main thread + /// (i.e. leave the interrupt context). The interrupt will be marked as + /// pending and handled when the scheduler checks if there are any pending + /// interrupts. + /// + /// If the ISR is called while an app is running, this will switch control + /// to the kernel. + const GENERIC_ISR: unsafe extern "C" fn(); + + /// The `systick_handler` is called when the systick interrupt occurs, + /// signaling that an application executed for longer than its + /// timeslice. This interrupt handler is no longer responsible for signaling + /// to the kernel thread that an interrupt has occurred, but is slightly + /// more efficient than the `generic_isr` handler on account of not needing + /// to mark the interrupt as pending. + const SYSTICK_HANDLER: unsafe extern "C" fn(); + + /// This is called after a `svc` instruction, both when switching to + /// userspace and when userspace makes a system call. + const SVC_HANDLER: unsafe extern "C" fn(); + + /// Hard fault handler. + const HARD_FAULT_HANDLER: unsafe extern "C" fn(); + + /// Assembly function called from `UserspaceKernelBoundary` to switch to an + /// an application. This handles storing and restoring application state + /// before and after the switch. + unsafe fn switch_to_user( + user_stack: *const usize, + process_regs: &mut [usize; 8], + ) -> *const usize; + + /// Format and display architecture-specific state useful for debugging. + /// + /// This is generally used after a `panic!()` to aid debugging. + unsafe fn print_cortexm_state(writer: &mut dyn Write); } #[cfg(all(target_arch = "arm", target_os = "none"))] pub unsafe extern "C" fn unhandled_interrupt() { + use core::arch::asm; let mut interrupt_number: u32; // IPSR[8:0] holds the currently active interrupt @@ -223,17 +133,24 @@ pub unsafe extern "C" fn unhandled_interrupt() { panic!("Unhandled Interrupt. ISR {} is active.", interrupt_number); } -/// Assembly function to initialize the .bss and .data sections in RAM. -/// -/// We need to (unfortunately) do these operations in assembly because it is -/// not valid to run Rust code without RAM initialized. -/// -/// See https://github.com/tock/tock/issues/2222 for more information. #[cfg(all(target_arch = "arm", target_os = "none"))] -#[naked] -pub unsafe extern "C" fn initialize_ram_jump_to_main() { - asm!( - " +extern "C" { + /// Assembly function to initialize the .bss and .data sections in RAM. + /// + /// We need to (unfortunately) do these operations in assembly because it is + /// not valid to run Rust code without RAM initialized. + /// + /// See for more information. + pub fn initialize_ram_jump_to_main(); +} + +#[cfg(all(target_arch = "arm", target_os = "none"))] +global_asm!( +" + .section .initialize_ram_jump_to_main, \"ax\" + .global initialize_ram_jump_to_main + .thumb_func + initialize_ram_jump_to_main: // Start by initializing .bss memory. The Tock linker script defines // `_szero` and `_ezero` to mark the .bss segment. ldr r0, ={sbss} // r0 = first address of .bss @@ -275,349 +192,12 @@ pub unsafe extern "C" fn initialize_ram_jump_to_main() { // board initialization takes place and Rust code starts. bl main ", - sbss = sym _szero, - ebss = sym _ezero, - sdata = sym _srelocate, - edata = sym _erelocate, - etext = sym _etext, - options(noreturn) - ); -} - -/// Assembly function called from `UserspaceKernelBoundary` to switch to an -/// an application. This handles storing and restoring application state before -/// and after the switch. -#[cfg(all( - target_arch = "arm", - target_feature = "v7", - target_feature = "thumb-mode", - target_os = "none" -))] -#[no_mangle] -pub unsafe extern "C" fn switch_to_user_arm_v7m( - mut user_stack: *const usize, - process_regs: &mut [usize; 8], -) -> *const usize { - asm!( - " - // Rust `asm!()` macro (as of May 2021) will not let us mark r6, r7 and r9 - // as clobbers. r6 and r9 is used internally by LLVM, and r7 is used for - // the frame pointer. However, in the process of restoring and saving the - // process's registers, we do in fact clobber r6, r7 and r9. So, we work - // around this by doing our own manual saving of r6 using r2, r7 using r3, - // r9 using r12, and then mark those as clobbered. - mov r2, r6 - mov r3, r7 - mov r12, r9 - - // The arguments passed in are: - // - `r0` is the bottom of the user stack - // - `r1` is a reference to `CortexMStoredState.regs` - - // Load bottom of stack into Process Stack Pointer. - msr psp, r0 // PSP = r0 - - // Load non-hardware-stacked registers from the process stored state. Ensure - // that the address register (right now r1) is stored in a callee saved - // register. - ldmia r1, {{r4-r11}} - - // SWITCH - svc 0xff // It doesn't matter which SVC number we use here as it has no - // defined meaning for the Cortex-M syscall interface. Data being - // returned from a syscall is transfered on the app's stack. - - // When execution returns here we have switched back to the kernel from the - // application. - - // Push non-hardware-stacked registers into the saved state for the - // application. - stmia r1, {{r4-r11}} - - // Update the user stack pointer with the current value after the - // application has executed. - mrs r0, PSP // r0 = PSP - - // Need to restore r6, r7 and r12 since we clobbered them when switching to and - // from the app. - mov r6, r2 - mov r7, r3 - mov r9, r12 - ", - inout("r0") user_stack, - in("r1") process_regs, - out("r2") _, out("r3") _, out("r4") _, out("r5") _, out("r8") _, out("r10") _, - out("r11") _, out("r12") _); - - user_stack -} - -#[cfg(all( - target_arch = "arm", - target_feature = "v7", - target_feature = "thumb-mode", - target_os = "none" -))] -#[inline(never)] -unsafe fn kernel_hardfault_arm_v7m(faulting_stack: *mut u32) -> ! { - let stacked_r0: u32 = *faulting_stack.offset(0); - let stacked_r1: u32 = *faulting_stack.offset(1); - let stacked_r2: u32 = *faulting_stack.offset(2); - let stacked_r3: u32 = *faulting_stack.offset(3); - let stacked_r12: u32 = *faulting_stack.offset(4); - let stacked_lr: u32 = *faulting_stack.offset(5); - let stacked_pc: u32 = *faulting_stack.offset(6); - let stacked_xpsr: u32 = *faulting_stack.offset(7); - - let mode_str = "Kernel"; - - let shcsr: u32 = core::ptr::read_volatile(0xE000ED24 as *const u32); - let cfsr: u32 = core::ptr::read_volatile(0xE000ED28 as *const u32); - let hfsr: u32 = core::ptr::read_volatile(0xE000ED2C as *const u32); - let mmfar: u32 = core::ptr::read_volatile(0xE000ED34 as *const u32); - let bfar: u32 = core::ptr::read_volatile(0xE000ED38 as *const u32); - - let iaccviol = (cfsr & 0x01) == 0x01; - let daccviol = (cfsr & 0x02) == 0x02; - let munstkerr = (cfsr & 0x08) == 0x08; - let mstkerr = (cfsr & 0x10) == 0x10; - let mlsperr = (cfsr & 0x20) == 0x20; - let mmfarvalid = (cfsr & 0x80) == 0x80; - - let ibuserr = ((cfsr >> 8) & 0x01) == 0x01; - let preciserr = ((cfsr >> 8) & 0x02) == 0x02; - let impreciserr = ((cfsr >> 8) & 0x04) == 0x04; - let unstkerr = ((cfsr >> 8) & 0x08) == 0x08; - let stkerr = ((cfsr >> 8) & 0x10) == 0x10; - let lsperr = ((cfsr >> 8) & 0x20) == 0x20; - let bfarvalid = ((cfsr >> 8) & 0x80) == 0x80; - - let undefinstr = ((cfsr >> 16) & 0x01) == 0x01; - let invstate = ((cfsr >> 16) & 0x02) == 0x02; - let invpc = ((cfsr >> 16) & 0x04) == 0x04; - let nocp = ((cfsr >> 16) & 0x08) == 0x08; - let unaligned = ((cfsr >> 16) & 0x100) == 0x100; - let divbysero = ((cfsr >> 16) & 0x200) == 0x200; - - let vecttbl = (hfsr & 0x02) == 0x02; - let forced = (hfsr & 0x40000000) == 0x40000000; - - let ici_it = (((stacked_xpsr >> 25) & 0x3) << 6) | ((stacked_xpsr >> 10) & 0x3f); - let thumb_bit = ((stacked_xpsr >> 24) & 0x1) == 1; - let exception_number = (stacked_xpsr & 0x1ff) as usize; - - panic!( - "{} HardFault.\r\n\ - \tKernel version {}\r\n\ - \tr0 0x{:x}\r\n\ - \tr1 0x{:x}\r\n\ - \tr2 0x{:x}\r\n\ - \tr3 0x{:x}\r\n\ - \tr12 0x{:x}\r\n\ - \tlr 0x{:x}\r\n\ - \tpc 0x{:x}\r\n\ - \tprs 0x{:x} [ N {} Z {} C {} V {} Q {} GE {}{}{}{} ; ICI.IT {} T {} ; Exc {}-{} ]\r\n\ - \tsp 0x{:x}\r\n\ - \ttop of stack 0x{:x}\r\n\ - \tbottom of stack 0x{:x}\r\n\ - \tSHCSR 0x{:x}\r\n\ - \tCFSR 0x{:x}\r\n\ - \tHSFR 0x{:x}\r\n\ - \tInstruction Access Violation: {}\r\n\ - \tData Access Violation: {}\r\n\ - \tMemory Management Unstacking Fault: {}\r\n\ - \tMemory Management Stacking Fault: {}\r\n\ - \tMemory Management Lazy FP Fault: {}\r\n\ - \tInstruction Bus Error: {}\r\n\ - \tPrecise Data Bus Error: {}\r\n\ - \tImprecise Data Bus Error: {}\r\n\ - \tBus Unstacking Fault: {}\r\n\ - \tBus Stacking Fault: {}\r\n\ - \tBus Lazy FP Fault: {}\r\n\ - \tUndefined Instruction Usage Fault: {}\r\n\ - \tInvalid State Usage Fault: {}\r\n\ - \tInvalid PC Load Usage Fault: {}\r\n\ - \tNo Coprocessor Usage Fault: {}\r\n\ - \tUnaligned Access Usage Fault: {}\r\n\ - \tDivide By Zero: {}\r\n\ - \tBus Fault on Vector Table Read: {}\r\n\ - \tForced Hard Fault: {}\r\n\ - \tFaulting Memory Address: (valid: {}) {:#010X}\r\n\ - \tBus Fault Address: (valid: {}) {:#010X}\r\n\ - ", - mode_str, - option_env!("TOCK_KERNEL_VERSION").unwrap_or("unknown"), - stacked_r0, - stacked_r1, - stacked_r2, - stacked_r3, - stacked_r12, - stacked_lr, - stacked_pc, - stacked_xpsr, - (stacked_xpsr >> 31) & 0x1, - (stacked_xpsr >> 30) & 0x1, - (stacked_xpsr >> 29) & 0x1, - (stacked_xpsr >> 28) & 0x1, - (stacked_xpsr >> 27) & 0x1, - (stacked_xpsr >> 19) & 0x1, - (stacked_xpsr >> 18) & 0x1, - (stacked_xpsr >> 17) & 0x1, - (stacked_xpsr >> 16) & 0x1, - ici_it, - thumb_bit, - exception_number, - ipsr_isr_number_to_str(exception_number), - faulting_stack as u32, - (_estack as *const ()) as u32, - (&_sstack as *const u32) as u32, - shcsr, - cfsr, - hfsr, - iaccviol, - daccviol, - munstkerr, - mstkerr, - mlsperr, - ibuserr, - preciserr, - impreciserr, - unstkerr, - stkerr, - lsperr, - undefinstr, - invstate, - invpc, - nocp, - unaligned, - divbysero, - vecttbl, - forced, - mmfarvalid, - mmfar, - bfarvalid, - bfar - ); -} - -#[cfg(all( - target_arch = "arm", - target_feature = "v7", - target_feature = "thumb-mode", - target_os = "none" -))] -/// Continue the hardfault handler. This function is not `#[naked]`, meaning we can mix -/// `asm!()` and Rust. We separate this logic to not have to write the entire fault -/// handler entirely in assembly. -unsafe extern "C" fn hard_fault_handler_arm_v7m_continued( - faulting_stack: *mut u32, - kernel_stack: u32, - stack_overflow: u32, -) { - if kernel_stack != 0 { - if stack_overflow != 0 { - // Panic to show the correct error. - panic!("kernel stack overflow"); - } else { - // Show the normal kernel hardfault message. - kernel_hardfault_arm_v7m(faulting_stack); - } - } else { - // Hard fault occurred in an app, not the kernel. The app should be - // marked as in an error state and handled by the kernel. - asm!( - " - /* Read the relevant SCB registers. */ - ldr r0, =SCB_REGISTERS /* Global variable address */ - ldr r1, =0xE000ED14 /* SCB CCR register address */ - ldr r2, [r1, #0] /* CCR */ - str r2, [r0, #0] - ldr r2, [r1, #20] /* CFSR */ - str r2, [r0, #4] - ldr r2, [r1, #24] /* HFSR */ - str r2, [r0, #8] - ldr r2, [r1, #32] /* MMFAR */ - str r2, [r0, #12] - ldr r2, [r1, #36] /* BFAR */ - str r2, [r0, #16] - - ldr r0, =APP_HARD_FAULT /* Global variable address */ - mov r1, #1 /* r1 = 1 */ - str r1, [r0, #0] /* APP_HARD_FAULT = 1 */ - - /* Set thread mode to privileged */ - mov r0, #0 - msr CONTROL, r0 - /* CONTROL writes must be followed by ISB */ - /* http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0321a/BIHFJCAC.html */ - isb - - movw LR, #0xFFF9 - movt LR, #0xFFFF", - out("r1") _, - out("r0") _, - out("r2") _, - options(nostack), - ); - } -} - -#[cfg(all( - target_arch = "arm", - target_feature = "v7", - target_feature = "thumb-mode", - target_os = "none" -))] -#[naked] -pub unsafe extern "C" fn hard_fault_handler_arm_v7m() { - // First need to determine if this a kernel fault or a userspace fault, and store - // the unmodified stack pointer. Place these values in registers, then call - // a non-naked function, to allow for use of rust code alongside inline asm. - // Because calling a function increases the stack pointer, we have to check for a kernel - // stack overflow and adjust the stack pointer before we branch - - asm!( - "mov r1, 0 /* r1 = 0 */", - "tst lr, #4 /* bitwise AND link register to 0b100 */", - "itte eq /* if lr==4, run next two instructions, else, run 3rd instruction. */", - "mrseq r0, msp /* r0 = kernel stack pointer */", - "addeq r1, 1 /* r1 = 1, kernel was executing */", - "mrsne r0, psp /* r0 = userland stack pointer */", - // Need to determine if we had a stack overflow before we push anything - // on to the stack. We check this by looking at the BusFault Status - // Register's (BFSR) `LSPERR` and `STKERR` bits to see if the hardware - // had any trouble stacking important registers to the stack during the - // fault. If so, then we cannot use this stack while handling this fault - // or we will trigger another fault. - "ldr r3, =0xE000ED29 /* SCB BFSR register address */", - "ldrb r3, [r3] /* r3 = BFSR */", - "tst r3, #0x30 /* r3 = BFSR & 0b00110000; LSPERR & STKERR bits */", - "ite ne /* check if the result of that bitwise AND was not 0 */", - "movne r2, #1 /* BFSR & 0b00110000 != 0; r2 = 1 */", - "moveq r2, #0 /* BFSR & 0b00110000 == 0; r2 = 0 */", - "and r5, r1, r2 /* bitwise and r2 and r1, store in r5 */ ", - "cmp r5, #1 /* update condition codes to reflect if r2 == 1 && r1 == 1 */", - "itt eq /* if r5==1 run the next 2 instructions, else skip to branch */", - // if true, The hardware couldn't use the stack, so we have no saved data and - // we cannot use the kernel stack as is. We just want to report that - // the kernel's stack overflowed, since that is essential for - // debugging. - // - // To make room for a panic!() handler stack, we just re-use the - // kernel's original stack. This should in theory leave the bottom - // of the stack where the problem occurred untouched should one want - // to further debug. - "ldreq r4, ={} /* load _estack into r4 */", - "moveq sp, r4 /* Set the stack pointer to _estack */", - // finally, branch to non-naked handler - // per ARM calling convention, faulting stack is passed in r0, kernel_stack in r1, - // and whether there was a stack overflow in r2 - "b {}", // branch to function - "bx lr", // if continued function returns, we need to manually branch to link register - sym _estack, sym hard_fault_handler_arm_v7m_continued, - options(noreturn), // asm block never returns, so no need to mark clobbers - ); -} + sbss = sym _szero, + ebss = sym _ezero, + sdata = sym _srelocate, + edata = sym _erelocate, + etext = sym _etext, +); pub unsafe fn print_cortexm_state(writer: &mut dyn Write) { let _ccr = syscall::SCB_REGISTERS[0]; @@ -797,28 +377,6 @@ pub unsafe fn print_cortexm_state(writer: &mut dyn Write) { } } -// Table 2.5 -// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/CHDBIBGJ.html -pub fn ipsr_isr_number_to_str(isr_number: usize) -> &'static str { - match isr_number { - 0 => "Thread Mode", - 1 => "Reserved", - 2 => "NMI", - 3 => "HardFault", - 4 => "MemManage", - 5 => "BusFault", - 6 => "UsageFault", - 7..=10 => "Reserved", - 11 => "SVCall", - 12 => "Reserved for Debug", - 13 => "Reserved", - 14 => "PendSV", - 15 => "SysTick", - 16..=255 => "IRQn", - _ => "(Unknown! Illegal value?)", - } -} - /////////////////////////////////////////////////////////////////// // Mock implementations for running tests on CI. // @@ -826,40 +384,12 @@ pub fn ipsr_isr_number_to_str(isr_number: usize) -> &'static str { // ARM assembly since it will not compile. /////////////////////////////////////////////////////////////////// -#[cfg(not(any(target_arch = "arm", target_os = "none")))] -pub unsafe extern "C" fn systick_handler_arm_v7m() { - unimplemented!() -} - -#[cfg(not(any(target_arch = "arm", target_os = "none")))] -pub unsafe extern "C" fn svc_handler_arm_v7m() { - unimplemented!() -} - -#[cfg(not(any(target_arch = "arm", target_os = "none")))] -pub unsafe extern "C" fn generic_isr_arm_v7m() { - unimplemented!() -} - -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] pub unsafe extern "C" fn unhandled_interrupt() { unimplemented!() } -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] pub unsafe extern "C" fn initialize_ram_jump_to_main() { unimplemented!() } - -#[cfg(not(any(target_arch = "arm", target_os = "none")))] -pub unsafe extern "C" fn switch_to_user_arm_v7m( - _user_stack: *const u8, - _process_regs: &mut [usize; 8], -) -> *const usize { - unimplemented!() -} - -#[cfg(not(any(target_arch = "arm", target_os = "none")))] -pub unsafe extern "C" fn hard_fault_handler_arm_v7m() { - unimplemented!() -} diff --git a/arch/cortex-m/src/mpu.rs b/arch/cortex-m/src/mpu.rs index 2a45958520..e1cfaede1a 100644 --- a/arch/cortex-m/src/mpu.rs +++ b/arch/cortex-m/src/mpu.rs @@ -1,18 +1,21 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the memory protection unit for the Cortex-M0+, Cortex-M3, //! Cortex-M4, and Cortex-M7 use core::cell::Cell; use core::cmp; use core::fmt; +use core::num::NonZeroUsize; -use kernel; use kernel::platform::mpu; use kernel::utilities::cells::OptionalCell; use kernel::utilities::math; use kernel::utilities::registers::interfaces::{Readable, Writeable}; use kernel::utilities::registers::{register_bitfields, FieldValue, ReadOnly, ReadWrite}; use kernel::utilities::StaticRef; -use kernel::ProcessId; /// MPU Registers for the Cortex-M3, Cortex-M4 and Cortex-M7 families /// Described in section 4.5 of @@ -132,27 +135,40 @@ const MPU_BASE_ADDRESS: StaticRef = pub struct MPU { /// MMIO reference to MPU registers. registers: StaticRef, + /// Monotonically increasing counter for allocated regions, used + /// to assign unique IDs to `CortexMConfig` instances. + config_count: Cell, /// Optimization logic. This is used to indicate which application the MPU /// is currently configured for so that the MPU can skip updating when the /// kernel returns to the same app. - hardware_is_configured_for: OptionalCell, + hardware_is_configured_for: OptionalCell, } impl MPU { pub const unsafe fn new() -> Self { Self { registers: MPU_BASE_ADDRESS, + config_count: Cell::new(NonZeroUsize::MIN), hardware_is_configured_for: OptionalCell::empty(), } } + + // Function useful for boards where the bootloader sets up some + // MPU configuration that conflicts with Tock's configuration: + pub unsafe fn clear_mpu(&self) { + self.registers.ctrl.write(Control::ENABLE::CLEAR); + } } /// Per-process struct storing MPU configuration for cortex-m MPUs. /// /// The cortex-m MPU has eight regions, all of which must be configured (though /// unused regions may be configured as disabled). This struct caches the result -/// of region configuration calculation +/// of region configuration calculation. pub struct CortexMConfig { + /// Unique ID for this configuration, assigned from a + /// monotonically increasing counter in the MPU struct. + id: NonZeroUsize, /// The computed region configuration for this process. regions: [CortexMRegion; NUM_REGIONS], /// Has the configuration changed since the last time the this process @@ -160,21 +176,11 @@ pub struct CortexMConfig { is_dirty: Cell, } -const APP_MEMORY_REGION_NUM: usize = 0; - -impl Default for CortexMConfig { - fn default() -> Self { - // a bit of a hack to initialize array without unsafe - let mut ret = Self { - regions: [CortexMRegion::empty(0); NUM_REGIONS], - is_dirty: Cell::new(true), - }; - for i in 0..NUM_REGIONS { - ret.regions[i] = CortexMRegion::empty(i); - } - ret - } -} +/// Records the index of the last region used for application RAM memory. +/// Regions 0-APP_MEMORY_REGION_MAX_NUM are used for application RAM. Regions +/// with indices above APP_MEMORY_REGION_MAX_NUM can be used for other MPU +/// needs. +const APP_MEMORY_REGION_MAX_NUM: usize = 1; impl fmt::Display for CortexMConfig { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -233,7 +239,7 @@ impl fmt::Display for CortexMConfig { impl CortexMConfig { fn unused_region_number(&self) -> Option { for (number, region) in self.regions.iter().enumerate() { - if number == APP_MEMORY_REGION_NUM { + if number <= APP_MEMORY_REGION_MAX_NUM { continue; } if let None = region.location() { @@ -312,7 +318,7 @@ impl CortexMRegion { // To compute the mask, we start with all subregions disabled and enable // the ones in the inclusive range [min_subregion, max_subregion]. if let Some((min_subregion, max_subregion)) = subregions { - let mask = (min_subregion..=max_subregion).fold(u8::max_value(), |res, i| { + let mask = (min_subregion..=max_subregion).fold(u8::MAX, |res, i| { // Enable subregions bit by bit (1 ^ 1 == 0) res ^ (1 << i) }); @@ -321,8 +327,8 @@ impl CortexMRegion { CortexMRegion { location: Some((logical_start, logical_size)), - base_address: base_address, - attributes: attributes, + base_address, + attributes, } } @@ -360,11 +366,7 @@ impl CortexMRegion { None => return false, }; - if region_start < other_end && other_start < region_end { - true - } else { - false - } + region_start < other_end && other_start < region_end } } @@ -373,10 +375,6 @@ impl mpu::MPU { type MpuConfig = CortexMConfig; - fn clear_mpu(&self) { - self.registers.ctrl.write(Control::ENABLE::CLEAR); - } - fn enable_app_mpu(&self) { // Enable the MPU, disable it during HardFault/NMI handlers, and allow // privileged code access to all unprotected memory. @@ -395,6 +393,31 @@ impl mpu::MPU self.registers.mpu_type.read(Type::DREGION) as usize } + fn new_config(&self) -> Option { + let id = self.config_count.get(); + self.config_count.set(id.checked_add(1)?); + + // Allocate the regions with index `0` first, then use `reset_config` to + // write the properly-indexed `CortexMRegion`s: + let mut ret = CortexMConfig { + id, + regions: [CortexMRegion::empty(0); NUM_REGIONS], + is_dirty: Cell::new(true), + }; + + self.reset_config(&mut ret); + + Some(ret) + } + + fn reset_config(&self, config: &mut Self::MpuConfig) { + for i in 0..NUM_REGIONS { + config.regions[i] = CortexMRegion::empty(i); + } + + config.is_dirty.set(true); + } + fn allocate_region( &self, unallocated_memory_start: *const u8, @@ -446,7 +469,7 @@ impl mpu::MPU let tz = start.trailing_zeros(); if tz < 32 { // Find the largest power of two that divides `start` - (1 as usize) << tz + 1_usize << tz } else { // This case means `start` is 0. let mut ceil = math::closest_power_of_two(size as u32) as usize; @@ -534,7 +557,7 @@ impl mpu::MPU .find(|(_idx, r)| **r == region) .ok_or(())?; - if idx == APP_MEMORY_REGION_NUM { + if idx <= APP_MEMORY_REGION_MAX_NUM { return Err(()); } @@ -544,6 +567,9 @@ impl mpu::MPU Ok(()) } + // When allocating memory for apps, we use two regions, each a power of two + // in size. By using two regions we halve their size, and also halve their + // alignment restrictions. fn allocate_app_memory_region( &self, unallocated_memory_start: *const u8, @@ -554,7 +580,8 @@ impl mpu::MPU permissions: mpu::Permissions, config: &mut Self::MpuConfig, ) -> Option<(*const u8, usize)> { - // Check that no previously allocated regions overlap the unallocated memory. + // Check that no previously allocated regions overlap the unallocated + // memory. for region in config.regions.iter() { if region.overlaps(unallocated_memory_start, unallocated_memory_size) { return None; @@ -567,88 +594,110 @@ impl mpu::MPU initial_app_memory_size + initial_kernel_memory_size, ); - // Size must be a power of two, so: https://www.youtube.com/watch?v=ovo6zwv6DX4 - let mut region_size = math::closest_power_of_two(memory_size as u32) as usize; - let exponent = math::log_base_two(region_size as u32); - - if exponent < 8 { - // Region sizes must be 256 Bytes or larger in order to support subregions - region_size = 256; + // Size must be a power of two, so: + // https://www.youtube.com/watch?v=ovo6zwv6DX4. + let mut memory_size_po2 = math::closest_power_of_two(memory_size as u32) as usize; + let exponent = math::log_base_two(memory_size_po2 as u32); + + // Check for compliance with the constraints of the MPU. + if exponent < 9 { + // Region sizes must be 256 bytes or larger to support subregions. + // Since we are using two regions, and each must be at least 256 + // bytes, we need the entire memory region to be at least 512 bytes. + memory_size_po2 = 512; } else if exponent > 32 { - // Region sizes must be 4GB or smaller + // Region sizes must be 4GB or smaller. return None; } - // The region should start as close as possible to the start of the unallocated memory. + // Region size is the actual size the MPU region will be set to, and is + // half of the total power of two size we are allocating to the app. + let mut region_size = memory_size_po2 / 2; + + // The region should start as close as possible to the start of the + // unallocated memory. let mut region_start = unallocated_memory_start as usize; - // If the start and length don't align, move region up until it does + // If the start and length don't align, move region up until it does. if region_start % region_size != 0 { region_start += region_size - (region_start % region_size); } - // We allocate an MPU region exactly over the process memory block, and we disable - // subregions at the end of this region to disallow access to the memory past the app - // break. As the app break later increases, we will be able to linearly grow - // the logical region covering app-owned memory by enabling more and more subregions. - // The Cortex-M MPU supports 8 subregions, so the size of this logical region is always a - // multiple of an eighth of the MPU region length. + // We allocate two MPU regions exactly over the process memory block, + // and we disable subregions at the end of this region to disallow + // access to the memory past the app break. As the app break later + // increases, we will be able to linearly grow the logical region + // covering app-owned memory by enabling more and more subregions. The + // Cortex-M MPU supports 8 subregions per region, so the size of this + // logical region is always a multiple of a sixteenth of the MPU region + // length. // Determine the number of subregions to enable. - let mut num_subregions_used = { - if initial_kernel_memory_size == 0 { - 8 - } else { - initial_app_memory_size * 8 / region_size + 1 - } - }; + // Want `round_up(app_memory_size / subregion_size)`. + let mut num_enabled_subregions = initial_app_memory_size * 8 / region_size + 1; let subregion_size = region_size / 8; - // Calculates the end address of the enabled subregions and the initial kernel memory break. - let subregions_end = region_start + num_subregions_used * subregion_size; - let kernel_memory_break = region_start + region_size - initial_kernel_memory_size; + // Calculates the end address of the enabled subregions and the initial + // kernel memory break. + let subregions_enabled_end = region_start + num_enabled_subregions * subregion_size; + let kernel_memory_break = region_start + memory_size_po2 - initial_kernel_memory_size; - // If the last subregion covering app-owned memory overlaps the start of kernel-owned - // memory, we make the entire process memory block twice as big so there is plenty of space - // between app-owned and kernel-owned memory. - if subregions_end > kernel_memory_break { + // If the last subregion covering app-owned memory overlaps the start of + // kernel-owned memory, we make the entire process memory block twice as + // big so there is plenty of space between app-owned and kernel-owned + // memory. + if subregions_enabled_end > kernel_memory_break { region_size *= 2; if region_start % region_size != 0 { region_start += region_size - (region_start % region_size); } - num_subregions_used = { - if initial_kernel_memory_size == 0 { - 8 - } else { - initial_app_memory_size * 8 / region_size + 1 - } - }; + num_enabled_subregions = initial_app_memory_size * 8 / region_size + 1; } // Make sure the region fits in the unallocated memory. - if region_start + region_size + if region_start + memory_size_po2 > (unallocated_memory_start as usize) + unallocated_memory_size { return None; } - let region = CortexMRegion::new( + // Get the number of subregions enabled in each of the two MPU regions. + let num_enabled_subregions0 = cmp::min(num_enabled_subregions, 8); + let num_enabled_subregions1 = num_enabled_subregions.saturating_sub(8); + + let region0 = CortexMRegion::new( region_start as *const u8, region_size, region_start as *const u8, region_size, - APP_MEMORY_REGION_NUM, - Some((0, num_subregions_used - 1)), + 0, + Some((0, num_enabled_subregions0 - 1)), permissions, ); - config.regions[APP_MEMORY_REGION_NUM] = region; + // We cannot have a completely unused MPU region + let region1 = if num_enabled_subregions1 == 0 { + CortexMRegion::empty(1) + } else { + CortexMRegion::new( + (region_start + region_size) as *const u8, + region_size, + (region_start + region_size) as *const u8, + region_size, + 1, + Some((0, num_enabled_subregions1 - 1)), + permissions, + ) + }; + + config.regions[0] = region0; + config.regions[1] = region1; config.is_dirty.set(true); - Some((region_start as *const u8, region_size)) + Some((region_start as *const u8, memory_size_po2)) } fn update_app_memory_region( @@ -658,13 +707,10 @@ impl mpu::MPU permissions: mpu::Permissions, config: &mut Self::MpuConfig, ) -> Result<(), ()> { - let (region_start, region_size) = match config.regions[APP_MEMORY_REGION_NUM].location() { - Some((start, size)) => (start as usize, size), - None => { - // Error: Process tried to update app memory MPU region before it was created. - return Err(()); - } - }; + // Get first region, or error if the process tried to update app memory + // MPU region before it was created. + let (region_start_ptr, region_size) = config.regions[0].location().ok_or(())?; + let region_start = region_start_ptr as usize; let app_memory_break = app_memory_break as usize; let kernel_memory_break = kernel_memory_break as usize; @@ -676,61 +722,67 @@ impl mpu::MPU // Number of bytes the process wants access to. let app_memory_size = app_memory_break - region_start; - // Number of bytes the kernel has reserved. - let kernel_memory_size = region_start + region_size - kernel_memory_break; // There are eight subregions for every region in the Cortex-M3/4 MPU. let subregion_size = region_size / 8; // Determine the number of subregions to enable. - let num_subregions_used = { - if kernel_memory_size == 0 { - // We can give all of the memory to the app, i.e. enable - // all eight subregions. - 8 - } else { - // Calculate the minimum number of subregions needed to cover - // the `app_memory_size`. - // - // Want `round_up(app_memory_size / subregion_size)`. - (app_memory_size + subregion_size - 1) / subregion_size - } - }; + // Want `round_up(app_memory_size / subregion_size)`. + let num_enabled_subregions = (app_memory_size + subregion_size - 1) / subregion_size; - let subregions_end = region_start + subregion_size * num_subregions_used; + let subregions_enabled_end = region_start + subregion_size * num_enabled_subregions; - // If we can no longer cover app memory with an MPU region without overlapping kernel - // memory, we fail. - if subregions_end > kernel_memory_break { + // If we can no longer cover app memory with an MPU region without + // overlapping kernel memory, we fail. + if subregions_enabled_end > kernel_memory_break { return Err(()); } - let region = CortexMRegion::new( + // Get the number of subregions enabled in each of the two MPU regions. + let num_enabled_subregions0 = cmp::min(num_enabled_subregions, 8); + let num_enabled_subregions1 = num_enabled_subregions.saturating_sub(8); + + let region0 = CortexMRegion::new( region_start as *const u8, region_size, region_start as *const u8, region_size, - APP_MEMORY_REGION_NUM, - Some((0, num_subregions_used - 1)), + 0, + Some((0, num_enabled_subregions0 - 1)), permissions, ); - config.regions[APP_MEMORY_REGION_NUM] = region; + let region1 = if num_enabled_subregions1 == 0 { + CortexMRegion::empty(1) + } else { + CortexMRegion::new( + (region_start + region_size) as *const u8, + region_size, + (region_start + region_size) as *const u8, + region_size, + 1, + Some((0, num_enabled_subregions1 - 1)), + permissions, + ) + }; + + config.regions[0] = region0; + config.regions[1] = region1; config.is_dirty.set(true); Ok(()) } - fn configure_mpu(&self, config: &Self::MpuConfig, app_id: &ProcessId) { + fn configure_mpu(&self, config: &Self::MpuConfig) { // If the hardware is already configured for this app and the app's MPU // configuration has not changed, then skip the hardware update. - if !self.hardware_is_configured_for.contains(app_id) || config.is_dirty.get() { + if !self.hardware_is_configured_for.contains(&config.id) || config.is_dirty.get() { // Set MPU regions for region in config.regions.iter() { self.registers.rbar.write(region.base_address()); self.registers.rasr.write(region.attributes()); } - self.hardware_is_configured_for.set(*app_id); + self.hardware_is_configured_for.set(config.id); config.is_dirty.set(false); } } diff --git a/arch/cortex-m/src/nvic.rs b/arch/cortex-m/src/nvic.rs index 4853e994ef..37faa87e52 100644 --- a/arch/cortex-m/src/nvic.rs +++ b/arch/cortex-m/src/nvic.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Cortex-M NVIC //! //! Most NVIC configuration is in the NVIC registers: @@ -29,11 +33,11 @@ macro_rules! interrupt_mask { let mut low_interrupt: u128 = 0; $( if ($interrupt < 128) { - low_interrupt = low_interrupt | (1 << $interrupt) as u128 + low_interrupt |= (1 << $interrupt) as u128 } else { - high_interrupt = high_interrupt | (1 << ($interrupt-128)) as u128 + high_interrupt |= (1 << ($interrupt-128)) as u128 } );+ (high_interrupt, low_interrupt) diff --git a/arch/cortex-m/src/scb.rs b/arch/cortex-m/src/scb.rs index 9dce9adc8d..b5727ab123 100644 --- a/arch/cortex-m/src/scb.rs +++ b/arch/cortex-m/src/scb.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! ARM System Control Block //! //! @@ -110,7 +114,7 @@ register_bitfields![u32, /// WO. PENDSTCLR OFFSET(25) NUMBITS(1), - /// Whether an excpetion will be serviced when existing debug state. + /// Whether an exception will be serviced when existing debug state. /// RO. ISRPREEMPT OFFSET(23) NUMBITS(1), @@ -298,6 +302,7 @@ pub unsafe fn set_vector_table_offset(offset: *const ()) { /// Disable the FPU #[cfg(all(target_arch = "arm", target_os = "none"))] pub unsafe fn disable_fpca() { + use core::arch::asm; SCB.cpacr .modify(CoprocessorAccessControl::CP10::CLEAR + CoprocessorAccessControl::CP11::CLEAR); @@ -311,7 +316,7 @@ pub unsafe fn disable_fpca() { } // Mock implementation for tests on Travis-CI. -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] pub unsafe fn disable_fpca() { // Dummy read register, to satisfy the `Readable` trait import on // non-ARM platforms. diff --git a/arch/cortex-m/src/support.rs b/arch/cortex-m/src/support.rs index 18352ee853..7e748bde38 100644 --- a/arch/cortex-m/src/support.rs +++ b/arch/cortex-m/src/support.rs @@ -1,9 +1,14 @@ -use core::ops::FnOnce; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::scb; #[cfg(all(target_arch = "arm", target_os = "none"))] #[inline(always)] /// NOP instruction pub fn nop() { + use core::arch::asm; unsafe { asm!("nop", options(nomem, nostack, preserves_flags)); } @@ -13,6 +18,7 @@ pub fn nop() { #[inline(always)] /// WFI instruction pub unsafe fn wfi() { + use core::arch::asm; asm!("wfi", options(nomem, preserves_flags)); } @@ -21,6 +27,7 @@ pub unsafe fn atomic(f: F) -> R where F: FnOnce() -> R, { + use core::arch::asm; // Set PRIMASK asm!("cpsid i", options(nomem, nostack)); @@ -32,22 +39,33 @@ where } // Mock implementations for tests on Travis-CI. -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] /// NOP instruction (mock) pub fn nop() { unimplemented!() } -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] /// WFI instruction (mock) pub unsafe fn wfi() { unimplemented!() } -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] pub unsafe fn atomic(_f: F) -> R where F: FnOnce() -> R, { unimplemented!() } + +pub fn reset() -> ! { + unsafe { + scb::reset(); + } + loop { + // This is required to avoid the empty loop clippy + // warning #[warn(clippy::empty_loop)] + nop(); + } +} diff --git a/arch/cortex-m/src/syscall.rs b/arch/cortex-m/src/syscall.rs index cbd0ccc978..18b2130365 100644 --- a/arch/cortex-m/src/syscall.rs +++ b/arch/cortex-m/src/syscall.rs @@ -1,12 +1,19 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the architecture-specific portions of the kernel-userland //! system call interface. use core::fmt::Write; +use core::marker::PhantomData; use core::mem::{self, size_of}; use core::ops::Range; -use core::ptr::{read_volatile, write_volatile}; +use core::ptr::{self, addr_of, addr_of_mut, read_volatile, write_volatile}; use kernel::errorcode::ErrorCode; +use crate::CortexMVariant; + /// This is used in the syscall handler. When set to 1 this means the /// svc_handler was called. Marked `pub` because it is used in the cortex-m* /// specific handler. @@ -31,11 +38,6 @@ pub static mut APP_HARD_FAULT: usize = 0; #[used] pub static mut SCB_REGISTERS: [u32; 5] = [0; 5]; -#[allow(improper_ctypes)] -extern "C" { - pub fn switch_to_user(user_stack: *const usize, process_regs: &mut [usize; 8]) -> *const usize; -} - // Space for 8 u32s: r0-r3, r12, lr, pc, and xPSR const SVC_FRAME_SIZE: usize = 32; @@ -111,15 +113,15 @@ impl core::convert::TryFrom<&[u8]> for CortexMStoredState { /// Implementation of the `UserspaceKernelBoundary` for the Cortex-M non-floating point /// architecture. -pub struct SysCall(); +pub struct SysCall(PhantomData); -impl SysCall { - pub const unsafe fn new() -> SysCall { - SysCall() +impl SysCall { + pub const unsafe fn new() -> SysCall { + SysCall(PhantomData) } } -impl kernel::syscall::UserspaceKernelBoundary for SysCall { +impl kernel::syscall::UserspaceKernelBoundary for SysCall { type StoredState = CortexMStoredState; fn initial_process_app_brk_size(&self) -> usize { @@ -229,13 +231,13 @@ impl kernel::syscall::UserspaceKernelBoundary for SysCall { // - Instruction addresses require `|1` to indicate thumb code // - Stack offset 4 is R12, which the syscall interface ignores let stack_bottom = state.psp as *mut usize; - write_volatile(stack_bottom.offset(7), state.psr); //......... -> APSR - write_volatile(stack_bottom.offset(6), callback.pc | 1); //... -> PC - write_volatile(stack_bottom.offset(5), state.yield_pc | 1); // -> LR - write_volatile(stack_bottom.offset(3), callback.argument3); // -> R3 - write_volatile(stack_bottom.offset(2), callback.argument2); // -> R2 - write_volatile(stack_bottom.offset(1), callback.argument1); // -> R1 - write_volatile(stack_bottom.offset(0), callback.argument0); // -> R0 + ptr::write(stack_bottom.offset(7), state.psr); //......... -> APSR + ptr::write(stack_bottom.offset(6), callback.pc | 1); //... -> PC + ptr::write(stack_bottom.offset(5), state.yield_pc | 1); // -> LR + ptr::write(stack_bottom.offset(3), callback.argument3); // -> R3 + ptr::write(stack_bottom.offset(2), callback.argument2); // -> R2 + ptr::write(stack_bottom.offset(1), callback.argument1); // -> R1 + ptr::write(stack_bottom.offset(0), callback.argument0); // -> R0 Ok(()) } @@ -246,7 +248,7 @@ impl kernel::syscall::UserspaceKernelBoundary for SysCall { app_brk: *const u8, state: &mut CortexMStoredState, ) -> (kernel::syscall::ContextSwitchReason, Option<*const u8>) { - let new_stack_pointer = switch_to_user(state.psp as *const usize, &mut state.regs); + let new_stack_pointer = A::switch_to_user(state.psp as *const usize, &mut state.regs); // We need to keep track of the current stack pointer. state.psp = new_stack_pointer as usize; @@ -262,13 +264,13 @@ impl kernel::syscall::UserspaceKernelBoundary for SysCall { // Check to see if the fault handler was called while the process was // running. - let app_fault = read_volatile(&APP_HARD_FAULT); - write_volatile(&mut APP_HARD_FAULT, 0); + let app_fault = read_volatile(&*addr_of!(APP_HARD_FAULT)); + write_volatile(&mut *addr_of_mut!(APP_HARD_FAULT), 0); // Check to see if the svc_handler was called and the process called a // syscall. - let syscall_fired = read_volatile(&SYSCALL_FIRED); - write_volatile(&mut SYSCALL_FIRED, 0); + let syscall_fired = read_volatile(&*addr_of!(SYSCALL_FIRED)); + write_volatile(&mut *addr_of_mut!(SYSCALL_FIRED), 0); // Now decide the reason based on which flags were set. let switch_reason = if app_fault == 1 || invalid_stack_pointer { @@ -280,20 +282,20 @@ impl kernel::syscall::UserspaceKernelBoundary for SysCall { // syscall (i.e. we return a value to the app immediately) then this // will have no effect. If we are doing something like `yield()`, // however, then we need to have this state. - state.yield_pc = read_volatile(new_stack_pointer.offset(6)); - state.psr = read_volatile(new_stack_pointer.offset(7)); + state.yield_pc = ptr::read(new_stack_pointer.offset(6)); + state.psr = ptr::read(new_stack_pointer.offset(7)); // Get the syscall arguments and return them along with the syscall. // It's possible the app did something invalid, in which case we put // the app in the fault state. - let r0 = read_volatile(new_stack_pointer.offset(0)); - let r1 = read_volatile(new_stack_pointer.offset(1)); - let r2 = read_volatile(new_stack_pointer.offset(2)); - let r3 = read_volatile(new_stack_pointer.offset(3)); + let r0 = ptr::read(new_stack_pointer.offset(0)); + let r1 = ptr::read(new_stack_pointer.offset(1)); + let r2 = ptr::read(new_stack_pointer.offset(2)); + let r3 = ptr::read(new_stack_pointer.offset(3)); // Get the actual SVC number. - let pcptr = read_volatile((new_stack_pointer as *const *const u16).offset(6)); - let svc_instr = read_volatile(pcptr.offset(-1)); + let pcptr = ptr::read((new_stack_pointer as *const *const u16).offset(6)); + let svc_instr = ptr::read(pcptr.offset(-1)); let svc_num = (svc_instr & 0xff) as u8; // Use the helper function to convert these raw values into a Tock @@ -337,14 +339,14 @@ impl kernel::syscall::UserspaceKernelBoundary for SysCall { 0xBAD00BAD, ) } else { - let r0 = read_volatile(stack_pointer.offset(0)); - let r1 = read_volatile(stack_pointer.offset(1)); - let r2 = read_volatile(stack_pointer.offset(2)); - let r3 = read_volatile(stack_pointer.offset(3)); - let r12 = read_volatile(stack_pointer.offset(4)); - let lr = read_volatile(stack_pointer.offset(5)); - let pc = read_volatile(stack_pointer.offset(6)); - let xpsr = read_volatile(stack_pointer.offset(7)); + let r0 = ptr::read(stack_pointer.offset(0)); + let r1 = ptr::read(stack_pointer.offset(1)); + let r2 = ptr::read(stack_pointer.offset(2)); + let r3 = ptr::read(stack_pointer.offset(3)); + let r12 = ptr::read(stack_pointer.offset(4)); + let lr = ptr::read(stack_pointer.offset(5)); + let pc = ptr::read(stack_pointer.offset(6)); + let xpsr = ptr::read(stack_pointer.offset(7)); (r0, r1, r2, r3, r12, lr, pc, xpsr) }; diff --git a/arch/cortex-m/src/systick.rs b/arch/cortex-m/src/systick.rs index 6046259804..215d0183d8 100644 --- a/arch/cortex-m/src/systick.rs +++ b/arch/cortex-m/src/systick.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! ARM Cortex-M SysTick peripheral. use kernel::utilities::registers::interfaces::{Readable, Writeable}; @@ -58,8 +62,7 @@ pub struct SysTick { } const BASE_ADDR: *const SystickRegisters = 0xE000E010 as *const SystickRegisters; -const SYSTICK_BASE: StaticRef = - unsafe { StaticRef::new(BASE_ADDR as *const SystickRegisters) }; +const SYSTICK_BASE: StaticRef = unsafe { StaticRef::new(BASE_ADDR) }; impl SysTick { /// Initialize the `SysTick` with default values diff --git a/arch/cortex-m0/Cargo.toml b/arch/cortex-m0/Cargo.toml index f304f4815c..61505c5c80 100644 --- a/arch/cortex-m0/Cargo.toml +++ b/arch/cortex-m0/Cargo.toml @@ -1,9 +1,16 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "cortexm0" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] kernel = { path = "../../kernel" } cortexm = { path = "../cortex-m" } + +[lints] +workspace = true diff --git a/arch/cortex-m0/src/lib.rs b/arch/cortex-m0/src/lib.rs index dbaf32a662..9a9d6f1517 100644 --- a/arch/cortex-m0/src/lib.rs +++ b/arch/cortex-m0/src/lib.rs @@ -1,42 +1,95 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Shared implementations for ARM Cortex-M0 MCUs. #![crate_name = "cortexm0"] #![crate_type = "rlib"] -#![feature(asm, asm_sym, naked_functions)] #![no_std] +use core::fmt::Write; + +#[cfg(all(target_arch = "arm", target_os = "none"))] +use core::arch::global_asm; + // Re-export the base generic cortex-m functions here as they are // valid on cortex-m0. pub use cortexm::support; pub use cortexm::nvic; -pub use cortexm::print_cortexm_state as print_cortexm0_state; pub use cortexm::syscall; -extern "C" { - // _estack is not really a function, but it makes the types work - // You should never actually invoke it!! - fn _estack(); - static mut _sstack: u32; - static mut _szero: u32; - static mut _ezero: u32; - static mut _etext: u32; - static mut _srelocate: u32; - static mut _erelocate: u32; +#[cfg(all(target_arch = "arm", target_os = "none"))] +struct HardFaultStackedRegisters { + r0: u32, + r1: u32, + r2: u32, + r3: u32, + r12: u32, + lr: u32, + pc: u32, + xpsr: u32, +} + +#[cfg(all(target_arch = "arm", target_os = "none"))] +/// Handle a hard fault that occurred in the kernel. This function is invoked +/// by the naked hard_fault_handler function. +unsafe extern "C" fn hard_fault_handler_kernel(faulting_stack: *mut u32) -> ! { + let hardfault_stacked_registers = HardFaultStackedRegisters { + r0: *faulting_stack.offset(0), + r1: *faulting_stack.offset(1), + r2: *faulting_stack.offset(2), + r3: *faulting_stack.offset(3), + r12: *faulting_stack.offset(4), + lr: *faulting_stack.offset(5), + pc: *faulting_stack.offset(6), + xpsr: *faulting_stack.offset(7), + }; + + panic!( + "Kernel HardFault.\r\n\ + \tKernel version {}\r\n\ + \tr0 0x{:x}\r\n\ + \tr1 0x{:x}\r\n\ + \tr2 0x{:x}\r\n\ + \tr3 0x{:x}\r\n\ + \tr12 0x{:x}\r\n\ + \tlr 0x{:x}\r\n\ + \tpc 0x{:x}\r\n\ + \txpsr 0x{:x}\r\n\ + ", + option_env!("TOCK_KERNEL_VERSION").unwrap_or("unknown"), + hardfault_stacked_registers.r0, + hardfault_stacked_registers.r1, + hardfault_stacked_registers.r2, + hardfault_stacked_registers.r3, + hardfault_stacked_registers.r12, + hardfault_stacked_registers.lr, + hardfault_stacked_registers.pc, + hardfault_stacked_registers.xpsr + ); } // Mock implementation for tests on Travis-CI. -#[cfg(not(any(target_arch = "arm", target_os = "none")))] -pub unsafe extern "C" fn generic_isr() { +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +unsafe extern "C" fn generic_isr() { unimplemented!() } #[cfg(all(target_arch = "arm", target_os = "none"))] -#[naked] -/// All ISRs are caught by this handler which disables the NVIC and switches to the kernel. -pub unsafe extern "C" fn generic_isr() { - asm!( - " +extern "C" { + /// All ISRs are caught by this handler which disables the NVIC and switches to the kernel. + pub fn generic_isr(); +} + +#[cfg(all(target_arch = "arm", target_os = "none"))] +global_asm!( + " + .section .generic_isr, \"ax\" + .global generic_isr + .thumb_func + generic_isr: /* Skip saving process state if not coming from user-space */ ldr r0, 300f // MEXC_RETURN_PSP cmp lr, r0 @@ -110,28 +163,34 @@ pub unsafe extern "C" fn generic_isr() { 200: // MEXC_RETURN_MSP .word 0xFFFFFFF9 300: // MEXC_RETURN_PSP - .word 0xFFFFFFFD", - options(noreturn) - ); -} + .word 0xFFFFFFFD" +); // Mock implementation for tests on Travis-CI. -#[cfg(not(any(target_arch = "arm", target_os = "none")))] -pub unsafe extern "C" fn systick_handler() { +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +unsafe extern "C" fn systick_handler_m0() { unimplemented!() } -/// The `systick_handler` is called when the systick interrupt occurs, signaling -/// that an application executed for longer than its timeslice. This interrupt -/// handler is no longer responsible for signaling to the kernel thread that an -/// interrupt has occurred, but is slightly more efficient than the -/// `generic_isr` handler on account of not needing to mark the interrupt as -/// pending. #[cfg(all(target_arch = "arm", target_os = "none"))] -#[naked] -pub unsafe extern "C" fn systick_handler() { - asm!( - " +extern "C" { + /// The `systick_handler` is called when the systick interrupt occurs, signaling + /// that an application executed for longer than its timeslice. This interrupt + /// handler is no longer responsible for signaling to the kernel thread that an + /// interrupt has occurred, but is slightly more efficient than the + /// `generic_isr` handler on account of not needing to mark the interrupt as + /// pending. + pub fn systick_handler_m0(); +} + +#[cfg(all(target_arch = "arm", target_os = "none"))] +global_asm!( + " + .section .systick_handler_m0, \"ax\" + .global systick_handler_m0 + .thumb_func + systick_handler_m0: + // Set thread mode to privileged to switch back to kernel mode. movs r0, #0 msr CONTROL, r0 @@ -147,22 +206,27 @@ pub unsafe extern "C" fn systick_handler() { .align 4 100: // ST_EXC_RETURN_MSP .word 0xFFFFFFF9 - ", - options(noreturn) - ); -} + " +); // Mock implementation for tests on Travis-CI. -#[cfg(not(any(target_arch = "arm", target_os = "none")))] -pub unsafe extern "C" fn svc_handler() { +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +unsafe extern "C" fn svc_handler() { unimplemented!() } #[cfg(all(target_arch = "arm", target_os = "none"))] -#[naked] -pub unsafe extern "C" fn svc_handler() { - asm!( - " +extern "C" { + pub fn svc_handler(); +} + +#[cfg(all(target_arch = "arm", target_os = "none"))] +global_asm!( + " + .section .svc_handler, \"ax\" + .global svc_handler + .thumb_func +svc_handler: ldr r0, 200f // EXC_RETURN_MSP cmp lr, r0 bne 100f @@ -181,27 +245,141 @@ pub unsafe extern "C" fn svc_handler() { .word 0xFFFFFFF9 300: // EXC_RETURN_PSP .word 0xFFFFFFFD - ", - options(noreturn) - ); -} + " +); // Mock implementation for tests on Travis-CI. -#[cfg(not(any(target_arch = "arm", target_os = "none")))] -pub unsafe extern "C" fn switch_to_user( - _user_stack: *const u8, - _process_regs: &mut [usize; 8], -) -> *mut u8 { +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +unsafe extern "C" fn hard_fault_handler() { unimplemented!() } #[cfg(all(target_arch = "arm", target_os = "none"))] -#[no_mangle] -pub unsafe extern "C" fn switch_to_user( - mut user_stack: *const u8, - process_regs: &mut [usize; 8], -) -> *mut u8 { - asm!(" +extern "C" { + pub fn hard_fault_handler(); +} + +#[cfg(all(target_arch = "arm", target_os = "none"))] +// If `kernel_stack` is non-zero, then hard-fault occurred in +// kernel, otherwise the hard-fault occurred in user. +global_asm!( +" + .section .hard_fault_handler, \"ax\" + .global hard_fault_handler + .thumb_func + hard_fault_handler: + /* + * Will be incremented to 1 when we determine that it was a fault + * in the kernel + */ + movs r1, #0 + /* + * r2 is used for testing and r3 is used to store lr + */ + mov r3, lr + + movs r2, #4 + tst r3, r2 + beq 100f + +// _hardfault_psp: + mrs r0, psp + b 200f + +100: // _hardfault_msp + mrs r0, msp + adds r1, #1 + +200: // _hardfault_exit + + // If the hard-fault occurred while executing the kernel (r1 != 0), + // jump to the non-naked kernel hard fault handler. This handler + // MUST NOT return. The faulting stack is passed as the first argument + // (r0). + cmp r1, #0 // Check if app (r1==0) or kernel (r1==1) fault. + beq 400f // If app fault, skip to app handling. + ldr r2, ={kernel_hard_fault_handler} // Load address of fault handler. + bx r2 // Branch to the non-naked fault handler. + +400: // _hardfault_app + // Otherwise, store that a hardfault occurred in an app, store some CPU + // state and finally return to the kernel stack: + ldr r0, =APP_HARD_FAULT + movs r1, #1 /* Fault */ + str r1, [r0, #0] + + /* + * NOTE: + * ----- + * + * Even though ARMv6-M SCB and Control registers + * are different from ARMv7-M, they are still compatible + * with each other. So, we can keep the same code as + * ARMv7-M. + * + * ARMv6-M however has no _privileged_ mode. + */ + + /* Read the SCB registers. */ + ldr r0, =SCB_REGISTERS + ldr r1, =0xE000ED14 + ldr r2, [r1, #0] /* CCR */ + str r2, [r0, #0] + ldr r2, [r1, #20] /* CFSR */ + str r2, [r0, #4] + ldr r2, [r1, #24] /* HFSR */ + str r2, [r0, #8] + ldr r2, [r1, #32] /* MMFAR */ + str r2, [r0, #12] + ldr r2, [r1, #36] /* BFAR */ + str r2, [r0, #16] + + /* Set thread mode to privileged */ + movs r0, #0 + msr CONTROL, r0 + /* No ISB required on M0 */ + /* http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0321a/BIHFJCAC.html */ + + // Load the FEXC_RETURN_MSP LR address and return to it, to switch to the + // kernel (MSP) stack: + ldr r0, 300f + mov lr, r0 + bx lr + + .align 4 +300: // FEXC_RETURN_MSP + .word 0xFFFFFFF9 + ", + kernel_hard_fault_handler = sym hard_fault_handler_kernel, +); + +// Enum with no variants to ensure that this type is not instantiable. It is +// only used to pass architecture-specific constants and functions via the +// `CortexMVariant` trait. +pub enum CortexM0 {} + +impl cortexm::CortexMVariant for CortexM0 { + const GENERIC_ISR: unsafe extern "C" fn() = generic_isr; + const SYSTICK_HANDLER: unsafe extern "C" fn() = systick_handler_m0; + const SVC_HANDLER: unsafe extern "C" fn() = svc_handler; + const HARD_FAULT_HANDLER: unsafe extern "C" fn() = hard_fault_handler; + + // Mock implementation for tests on Travis-CI. + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + unsafe fn switch_to_user( + _user_stack: *const usize, + _process_regs: &mut [usize; 8], + ) -> *const usize { + unimplemented!() + } + + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn switch_to_user( + mut user_stack: *const usize, + process_regs: &mut [usize; 8], + ) -> *const usize { + use core::arch::asm; + asm!(" // Rust `asm!()` macro (as of May 2021) will not let us mark r6, r7 and r9 // as clobbers. r6 and r9 is used internally by LLVM, and r7 is used for // the frame pointer. However, in the process of restoring and saving the @@ -259,163 +437,11 @@ pub unsafe extern "C" fn switch_to_user( out("r2") _, out("r3") _, out("r4") _, out("r5") _, out("r8") _, out("r10") _, out("r11") _, out("r12") _); - user_stack as *mut u8 -} - -#[cfg(all(target_arch = "arm", target_os = "none"))] -struct HardFaultStackedRegisters { - r0: u32, - r1: u32, - r2: u32, - r3: u32, - r12: u32, - lr: u32, - pc: u32, - xpsr: u32, -} - -#[cfg(all(target_arch = "arm", target_os = "none"))] -#[inline(never)] -unsafe fn kernel_hardfault(faulting_stack: *mut u32) { - let hardfault_stacked_registers = HardFaultStackedRegisters { - r0: *faulting_stack.offset(0), - r1: *faulting_stack.offset(1), - r2: *faulting_stack.offset(2), - r3: *faulting_stack.offset(3), - r12: *faulting_stack.offset(4), - lr: *faulting_stack.offset(5), - pc: *faulting_stack.offset(6), - xpsr: *faulting_stack.offset(7), - }; - - panic!( - "Kernel HardFault.\r\n\ - \tKernel version {}\r\n\ - \tr0 0x{:x}\r\n\ - \tr1 0x{:x}\r\n\ - \tr2 0x{:x}\r\n\ - \tr3 0x{:x}\r\n\ - \tr12 0x{:x}\r\n\ - \tlr 0x{:x}\r\n\ - \tpc 0x{:x}\r\n\ - \txpsr 0x{:x}\r\n\ - ", - option_env!("TOCK_KERNEL_VERSION").unwrap_or("unknown"), - hardfault_stacked_registers.r0, - hardfault_stacked_registers.r1, - hardfault_stacked_registers.r2, - hardfault_stacked_registers.r3, - hardfault_stacked_registers.r12, - hardfault_stacked_registers.lr, - hardfault_stacked_registers.pc, - hardfault_stacked_registers.xpsr - ); -} - -// Mock implementation for tests on Travis-CI. -#[cfg(not(any(target_arch = "arm", target_os = "none")))] -pub unsafe extern "C" fn hard_fault_handler() { - unimplemented!() -} - -#[cfg(all(target_arch = "arm", target_os = "none"))] -/// Continue the hardfault handler. This function is not `#[naked]`, meaning we -/// can mix `asm!()` and Rust. We separate this logic to not have to write the -/// entire fault handler entirely in assembly. -unsafe extern "C" fn hard_fault_handler_continued(faulting_stack: *mut u32, kernel_stack: u32) { - if kernel_stack != 0 { - kernel_hardfault(faulting_stack); - } else { - // hard fault occurred in an app, not the kernel. The app should be - // marked as in an error state and handled by the kernel - asm!( - " - ldr r0, =APP_HARD_FAULT - movs r1, #1 /* Fault */ - str r1, [r0, #0] - - /* - * NOTE: - * ----- - * - * Even though ARMv6-M SCB and Control registers - * are different from ARMv7-M, they are still compatible - * with each other. So, we can keep the same code as - * ARMv7-M. - * - * ARMv6-M however has no _privileged_ mode. - */ - - /* Read the SCB registers. */ - ldr r0, =SCB_REGISTERS - ldr r1, =0xE000ED14 - ldr r2, [r1, #0] /* CCR */ - str r2, [r0, #0] - ldr r2, [r1, #20] /* CFSR */ - str r2, [r0, #4] - ldr r2, [r1, #24] /* HFSR */ - str r2, [r0, #8] - ldr r2, [r1, #32] /* MMFAR */ - str r2, [r0, #12] - ldr r2, [r1, #36] /* BFAR */ - str r2, [r0, #16] - - /* Set thread mode to privileged */ - movs r0, #0 - msr CONTROL, r0 - /* No ISB required on M0 */ - /* http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0321a/BIHFJCAC.html */ - - ldr r0, 100f - mov lr, r0 - b 200f // freturn - .align 4 - 100: // FEXC_RETURN_MSP - .word 0xFFFFFFF9 - 200: // freturn - ", - out("r1") _, - out("r0") _, - out("r2") _, - options(nostack), - ); + user_stack } -} - -#[cfg(all(target_arch = "arm", target_os = "none"))] -#[naked] -pub unsafe extern "C" fn hard_fault_handler() { - // If `kernel_stack` is non-zero, then hard-fault occurred in - // kernel, otherwise the hard-fault occurred in user. - asm!(" - /* - * Will be incremented to 1 when we determine that it was a fault - * in the kernel - */ - movs r1, #0 - /* - * r2 is used for testing and r3 is used to store lr - */ - mov r3, lr - - movs r2, #4 - tst r3, r2 - beq 100f -// _hardfault_psp: - mrs r0, psp - b 200f - -100: // _hardfault_msp - mrs r0, msp - adds r1, #1 - -200: // _hardfault_exit - - b {} // Branch to the non-naked fault handler. - bx lr // If continued function returns, we need to manually branch to - // link register. - ", - sym hard_fault_handler_continued, - options(noreturn)); + #[inline] + unsafe fn print_cortexm_state(writer: &mut dyn Write) { + cortexm::print_cortexm_state(writer) + } } diff --git a/arch/cortex-m0p/Cargo.toml b/arch/cortex-m0p/Cargo.toml index a3c8bba51f..7fd6380564 100644 --- a/arch/cortex-m0p/Cargo.toml +++ b/arch/cortex-m0p/Cargo.toml @@ -1,10 +1,17 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "cortexm0p" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] kernel = { path = "../../kernel" } cortexm = { path = "../cortex-m" } cortexm0 = { path = "../cortex-m0" } + +[lints] +workspace = true diff --git a/arch/cortex-m0p/src/lib.rs b/arch/cortex-m0p/src/lib.rs index 493814647d..30c9958860 100644 --- a/arch/cortex-m0p/src/lib.rs +++ b/arch/cortex-m0p/src/lib.rs @@ -1,11 +1,18 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Shared implementations for ARM Cortex-M0+ MCUs. #![crate_name = "cortexm0p"] #![crate_type = "rlib"] -#![feature(asm)] -#![feature(naked_functions)] #![no_std] +use core::fmt::Write; + +#[cfg(all(target_arch = "arm", target_os = "none"))] +use core::arch::global_asm; + pub mod mpu { pub type MPU = cortexm::mpu::MPU<8, 256>; } @@ -17,35 +24,30 @@ pub use cortexm::support; pub use cortexm::initialize_ram_jump_to_main; pub use cortexm::interrupt_mask; pub use cortexm::nvic; -pub use cortexm::print_cortexm_state as print_cortexm0_state; pub use cortexm::scb; -pub use cortexm::syscall; pub use cortexm::systick; pub use cortexm::unhandled_interrupt; -pub use cortexm0::generic_isr; -pub use cortexm0::hard_fault_handler; -pub use cortexm0::systick_handler; +pub use cortexm::CortexMVariant; +use cortexm0::CortexM0; // Mock implementation for tests on Travis-CI. -#[cfg(not(any(target_arch = "arm", target_os = "none")))] -pub unsafe extern "C" fn switch_to_user( - _user_stack: *const u8, - _process_regs: &mut [usize; 8], -) -> *mut u8 { +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +pub unsafe extern "C" fn svc_handler_m0p() { unimplemented!() } -// Mock implementation for tests on Travis-CI. -#[cfg(not(any(target_arch = "arm", target_os = "none")))] -pub unsafe extern "C" fn svc_handler() { - unimplemented!() +#[cfg(all(target_arch = "arm", target_os = "none"))] +extern "C" { + pub fn svc_handler_m0p(); } #[cfg(all(target_arch = "arm", target_os = "none"))] -#[naked] -pub unsafe extern "C" fn svc_handler() { - asm!( - " +global_asm!( + " + .section .svc_handler_m0p, \"ax\" + .global svc_handler_m0p + .thumb_func +svc_handler_m0p: ldr r0, 100f // EXC_RETURN_MSP cmp lr, r0 bne 300f // to_kernel @@ -72,7 +74,42 @@ pub unsafe extern "C" fn svc_handler() { .word 0xFFFFFFF9 200: // EXC_RETURN_PSP .word 0xFFFFFFFD - ", - options(noreturn) - ); + " +); + +// Enum with no variants to ensure that this type is not instantiable. It is +// only used to pass architecture-specific constants and functions via the +// `CortexMVariant` trait. +pub enum CortexM0P {} + +impl cortexm::CortexMVariant for CortexM0P { + const GENERIC_ISR: unsafe extern "C" fn() = CortexM0::GENERIC_ISR; + const SYSTICK_HANDLER: unsafe extern "C" fn() = CortexM0::SYSTICK_HANDLER; + const SVC_HANDLER: unsafe extern "C" fn() = svc_handler_m0p; + const HARD_FAULT_HANDLER: unsafe extern "C" fn() = CortexM0::HARD_FAULT_HANDLER; + + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn switch_to_user( + user_stack: *const usize, + process_regs: &mut [usize; 8], + ) -> *const usize { + CortexM0::switch_to_user(user_stack, process_regs) + } + + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + unsafe fn switch_to_user( + _user_stack: *const usize, + _process_regs: &mut [usize; 8], + ) -> *const usize { + unimplemented!() + } + + #[inline] + unsafe fn print_cortexm_state(writer: &mut dyn Write) { + cortexm::print_cortexm_state(writer) + } +} + +pub mod syscall { + pub type SysCall = cortexm::syscall::SysCall; } diff --git a/arch/cortex-m3/Cargo.toml b/arch/cortex-m3/Cargo.toml index 6e27d2a9ec..d31a7f7572 100644 --- a/arch/cortex-m3/Cargo.toml +++ b/arch/cortex-m3/Cargo.toml @@ -1,9 +1,17 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "cortexm3" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] kernel = { path = "../../kernel" } cortexm = { path = "../cortex-m" } +cortexv7m = { path = "../cortex-v7m" } + +[lints] +workspace = true diff --git a/arch/cortex-m3/src/lib.rs b/arch/cortex-m3/src/lib.rs index 711ee43510..8cbd4e3bd6 100644 --- a/arch/cortex-m3/src/lib.rs +++ b/arch/cortex-m3/src/lib.rs @@ -1,41 +1,61 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Shared implementations for ARM Cortex-M3 MCUs. #![crate_name = "cortexm3"] #![crate_type = "rlib"] #![no_std] +use core::fmt::Write; + pub mod mpu { pub type MPU = cortexm::mpu::MPU<8, 32>; } -// Re-export the base generic cortex-m functions here as they are -// valid on cortex-m3. -pub use cortexm::support; - -pub use cortexm::generic_isr_arm_v7m as generic_isr; -pub use cortexm::hard_fault_handler_arm_v7m as hard_fault_handler; +pub use cortexm::initialize_ram_jump_to_main; +pub use cortexm::interrupt_mask; pub use cortexm::nvic; -pub use cortexm::print_cortexm_state as print_cortexm3_state; pub use cortexm::scb; -pub use cortexm::svc_handler_arm_v7m as svc_handler; -pub use cortexm::syscall; +pub use cortexm::support; pub use cortexm::systick; -pub use cortexm::systick_handler_arm_v7m as systick_handler; - -/// Provide a `switch_to_user` function with exactly that name for syscall.rs. -#[cfg(all(target_arch = "arm", target_os = "none"))] -#[no_mangle] -pub unsafe extern "C" fn switch_to_user( - mut user_stack: *const usize, - process_regs: &mut [usize; 8], -) -> *const usize { - cortexm::switch_to_user_arm_v7m(user_stack, process_regs) +pub use cortexm::unhandled_interrupt; +pub use cortexm::CortexMVariant; + +// Enum with no variants to ensure that this type is not instantiable. It is +// only used to pass architecture-specific constants and functions via the +// `CortexMVariant` trait. +pub enum CortexM3 {} + +impl cortexm::CortexMVariant for CortexM3 { + const GENERIC_ISR: unsafe extern "C" fn() = cortexv7m::generic_isr_arm_v7m; + const SYSTICK_HANDLER: unsafe extern "C" fn() = cortexv7m::systick_handler_arm_v7m; + const SVC_HANDLER: unsafe extern "C" fn() = cortexv7m::svc_handler_arm_v7m; + const HARD_FAULT_HANDLER: unsafe extern "C" fn() = cortexv7m::hard_fault_handler_arm_v7m; + + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn switch_to_user( + user_stack: *const usize, + process_regs: &mut [usize; 8], + ) -> *const usize { + cortexv7m::switch_to_user_arm_v7m(user_stack, process_regs) + } + + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + unsafe fn switch_to_user( + _user_stack: *const usize, + _process_regs: &mut [usize; 8], + ) -> *const usize { + unimplemented!() + } + + #[inline] + unsafe fn print_cortexm_state(writer: &mut dyn Write) { + cortexm::print_cortexm_state(writer) + } } -#[cfg(not(any(target_arch = "arm", target_os = "none")))] -pub unsafe extern "C" fn switch_to_user( - _user_stack: *const u8, - _process_regs: &mut [usize; 8], -) -> *const usize { - unimplemented!() +pub mod syscall { + pub type SysCall = cortexm::syscall::SysCall; } diff --git a/arch/cortex-m4/Cargo.toml b/arch/cortex-m4/Cargo.toml index fb25254a70..dc9f60641a 100644 --- a/arch/cortex-m4/Cargo.toml +++ b/arch/cortex-m4/Cargo.toml @@ -1,9 +1,17 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "cortexm4" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] kernel = { path = "../../kernel" } cortexm = { path = "../cortex-m" } +cortexv7m = { path = "../cortex-v7m" } + +[lints] +workspace = true diff --git a/arch/cortex-m4/src/lib.rs b/arch/cortex-m4/src/lib.rs index 9c4323ab2f..077439e52e 100644 --- a/arch/cortex-m4/src/lib.rs +++ b/arch/cortex-m4/src/lib.rs @@ -1,43 +1,61 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Shared implementations for ARM Cortex-M4 MCUs. #![crate_name = "cortexm4"] #![crate_type = "rlib"] #![no_std] +use core::fmt::Write; + pub mod mpu { pub type MPU = cortexm::mpu::MPU<8, 32>; } -// Re-export the base generic cortex-m functions here as they are -// valid on cortex-m4. -pub use cortexm::support; - -pub use cortexm::generic_isr_arm_v7m as generic_isr; -pub use cortexm::hard_fault_handler_arm_v7m as hard_fault_handler; +pub use cortexm::dwt; pub use cortexm::initialize_ram_jump_to_main; pub use cortexm::nvic; -pub use cortexm::print_cortexm_state as print_cortexm4_state; pub use cortexm::scb; -pub use cortexm::svc_handler_arm_v7m as svc_handler; -pub use cortexm::syscall; +pub use cortexm::support; pub use cortexm::systick; -pub use cortexm::systick_handler_arm_v7m as systick_handler; pub use cortexm::unhandled_interrupt; +pub use cortexm::CortexMVariant; + +// Enum with no variants to ensure that this type is not instantiable. It is +// only used to pass architecture-specific constants and functions via the +// `CortexMVariant` trait. +pub enum CortexM4 {} + +impl cortexm::CortexMVariant for CortexM4 { + const GENERIC_ISR: unsafe extern "C" fn() = cortexv7m::generic_isr_arm_v7m; + const SYSTICK_HANDLER: unsafe extern "C" fn() = cortexv7m::systick_handler_arm_v7m; + const SVC_HANDLER: unsafe extern "C" fn() = cortexv7m::svc_handler_arm_v7m; + const HARD_FAULT_HANDLER: unsafe extern "C" fn() = cortexv7m::hard_fault_handler_arm_v7m; + + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn switch_to_user( + user_stack: *const usize, + process_regs: &mut [usize; 8], + ) -> *const usize { + cortexv7m::switch_to_user_arm_v7m(user_stack, process_regs) + } + + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + unsafe fn switch_to_user( + _user_stack: *const usize, + _process_regs: &mut [usize; 8], + ) -> *const usize { + unimplemented!() + } -/// Provide a `switch_to_user` function with exactly that name for syscall.rs. -#[cfg(all(target_arch = "arm", target_os = "none"))] -#[no_mangle] -pub unsafe extern "C" fn switch_to_user( - user_stack: *const usize, - process_regs: &mut [usize; 8], -) -> *const usize { - cortexm::switch_to_user_arm_v7m(user_stack, process_regs) + #[inline] + unsafe fn print_cortexm_state(writer: &mut dyn Write) { + cortexm::print_cortexm_state(writer) + } } -#[cfg(not(any(target_arch = "arm", target_os = "none")))] -pub unsafe extern "C" fn switch_to_user( - _user_stack: *const u8, - _process_regs: &mut [usize; 8], -) -> *const usize { - unimplemented!() +pub mod syscall { + pub type SysCall = cortexm::syscall::SysCall; } diff --git a/arch/cortex-m7/Cargo.toml b/arch/cortex-m7/Cargo.toml index 2e2e13fd0b..1d67d23e37 100644 --- a/arch/cortex-m7/Cargo.toml +++ b/arch/cortex-m7/Cargo.toml @@ -1,9 +1,17 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "cortexm7" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] kernel = { path = "../../kernel" } cortexm = { path = "../cortex-m" } +cortexv7m = { path = "../cortex-v7m" } + +[lints] +workspace = true diff --git a/arch/cortex-m7/src/lib.rs b/arch/cortex-m7/src/lib.rs index bd1045399a..2a9fcbb39f 100644 --- a/arch/cortex-m7/src/lib.rs +++ b/arch/cortex-m7/src/lib.rs @@ -1,43 +1,60 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Shared implementations for ARM Cortex-M7 MCUs. #![crate_name = "cortexm7"] #![crate_type = "rlib"] #![no_std] +use core::fmt::Write; + pub mod mpu { pub type MPU = cortexm::mpu::MPU<16, 32>; // Cortex-M7 MPU has 16 regions } -// Re-export the base generic cortex-m functions here as they are -// valid on cortex-m7. -pub use cortexm::support; - -pub use cortexm::generic_isr_arm_v7m as generic_isr; -pub use cortexm::hard_fault_handler_arm_v7m as hard_fault_handler; pub use cortexm::initialize_ram_jump_to_main; pub use cortexm::nvic; -pub use cortexm::print_cortexm_state as print_cortexm7_state; pub use cortexm::scb; -pub use cortexm::svc_handler_arm_v7m as svc_handler; -pub use cortexm::syscall; +pub use cortexm::support; pub use cortexm::systick; -pub use cortexm::systick_handler_arm_v7m as systick_handler; pub use cortexm::unhandled_interrupt; +pub use cortexm::CortexMVariant; + +// Enum with no variants to ensure that this type is not instantiable. It is +// only used to pass architecture-specific constants and functions via the +// `CortexMVariant` trait. +pub enum CortexM7 {} + +impl cortexm::CortexMVariant for CortexM7 { + const GENERIC_ISR: unsafe extern "C" fn() = cortexv7m::generic_isr_arm_v7m; + const SYSTICK_HANDLER: unsafe extern "C" fn() = cortexv7m::systick_handler_arm_v7m; + const SVC_HANDLER: unsafe extern "C" fn() = cortexv7m::svc_handler_arm_v7m; + const HARD_FAULT_HANDLER: unsafe extern "C" fn() = cortexv7m::hard_fault_handler_arm_v7m; + + #[cfg(all(target_arch = "arm", target_os = "none"))] + unsafe fn switch_to_user( + user_stack: *const usize, + process_regs: &mut [usize; 8], + ) -> *const usize { + cortexv7m::switch_to_user_arm_v7m(user_stack, process_regs) + } + + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + unsafe fn switch_to_user( + _user_stack: *const usize, + _process_regs: &mut [usize; 8], + ) -> *const usize { + unimplemented!() + } -/// Provide a `switch_to_user` function with exactly that name for syscall.rs. -#[cfg(all(target_arch = "arm", target_os = "none"))] -#[no_mangle] -pub unsafe extern "C" fn switch_to_user( - user_stack: *const usize, - process_regs: &mut [usize; 8], -) -> *const usize { - cortexm::switch_to_user_arm_v7m(user_stack, process_regs) + #[inline] + unsafe fn print_cortexm_state(writer: &mut dyn Write) { + cortexm::print_cortexm_state(writer) + } } -#[cfg(not(any(target_arch = "arm", target_os = "none")))] -pub unsafe extern "C" fn switch_to_user( - _user_stack: *const u8, - _process_regs: &mut [usize; 8], -) -> *const usize { - unimplemented!() +pub mod syscall { + pub type SysCall = cortexm::syscall::SysCall; } diff --git a/arch/cortex-v7m/Cargo.toml b/arch/cortex-v7m/Cargo.toml new file mode 100644 index 0000000000..d6750ddcd9 --- /dev/null +++ b/arch/cortex-v7m/Cargo.toml @@ -0,0 +1,15 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +[package] +name = "cortexv7m" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[dependencies] +kernel = { path = "../../kernel" } + +[lints] +workspace = true diff --git a/arch/cortex-v7m/README.md b/arch/cortex-v7m/README.md new file mode 100644 index 0000000000..600d903938 --- /dev/null +++ b/arch/cortex-v7m/README.md @@ -0,0 +1,10 @@ +Cortex-M v7m Architecture +========================= + +This crate includes shared low-level code for the Cortex-M v7m family of CPU +architectures. + +Boards and chips should not depend on this crate directly. Instead, all of the +relevant modules and features should be exported through the specific Cortex-M +crates (e.g. Cortex-M4), and chips and boards should depend on the more specific +crate. diff --git a/arch/cortex-v7m/src/lib.rs b/arch/cortex-v7m/src/lib.rs new file mode 100644 index 0000000000..0f1e14e7f5 --- /dev/null +++ b/arch/cortex-v7m/src/lib.rs @@ -0,0 +1,585 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Generic support for all Cortex-M platforms. + +#![crate_name = "cortexv7m"] +#![crate_type = "rlib"] +#![no_std] + +#[cfg(all(target_arch = "arm", target_os = "none"))] +use core::arch::global_asm; + +// These constants are defined in the linker script. +extern "C" { + static _estack: u8; + static _sstack: u8; +} + +#[cfg(all(target_arch = "arm", target_os = "none"))] +extern "C" { + /// ARMv7-M systick handler function. + /// + /// For documentation of this function, please see + /// `CortexMVariant::SYSTICK_HANDLER`. + pub fn systick_handler_arm_v7m(); +} + +#[cfg(all(target_arch = "arm", target_os = "none"))] +global_asm!( + " + .section .systick_handler_arm_v7m, \"ax\" + .global systick_handler_arm_v7m + .thumb_func + systick_handler_arm_v7m: + // Use the CONTROL register to set the thread mode to privileged to switch + // back to kernel mode. + // + // CONTROL[1]: Stack status + // 0 = Default stack (MSP) is used + // 1 = Alternate stack is used + // CONTROL[0]: Mode + // 0 = Privileged in thread mode + // 1 = User state in thread mode + mov r0, #0 // r0 = 0 + msr CONTROL, r0 // CONTROL = 0 + // CONTROL writes must be followed by an Instruction Synchronization Barrier + // (ISB). https://developer.arm.com/documentation/dai0321/latest + isb // synchronization barrier + + // Set the link register to the special EXC_RETURN value of 0xFFFFFFF9 which + // instructs the CPU to run in thread mode with the main (kernel) stack. + ldr lr, =0xFFFFFFF9 // LR = 0xFFFFFFF9 + + // This will resume in the switch_to_user function where application state + // is saved and the scheduler can choose what to do next. + bx lr + " +); + +#[cfg(all(target_arch = "arm", target_os = "none"))] +extern "C" { + /// Handler of `svc` instructions on ARMv7-M. + /// + /// For documentation of this function, please see + /// `CortexMVariant::SVC_HANDLER`. + pub fn svc_handler_arm_v7m(); +} + +#[cfg(all(target_arch = "arm", target_os = "none"))] +global_asm!( + " + .section .svc_handler_arm_v7m, \"ax\" + .global svc_handler_arm_v7m + .thumb_func + svc_handler_arm_v7m: + // First check to see which direction we are going in. If the link register + // is something other than 0xFFFFFFF9, then we are coming from an app which + // has called a syscall. + cmp lr, #0xFFFFFFF9 // LR ≟ 0xFFFFFFF9 + bne 100f // to_kernel // if LR != 0xFFFFFFF9, jump to to_kernel + + // If we get here, then this is a context switch from the kernel to the + // application. Use the CONTROL register to set the thread mode to + // unprivileged to run the application. + // + // CONTROL[1]: Stack status + // 0 = Default stack (MSP) is used + // 1 = Alternate stack is used + // CONTROL[0]: Mode + // 0 = Privileged in thread mode + // 1 = User state in thread mode + mov r0, #1 // r0 = 1 + msr CONTROL, r0 // CONTROL = 1 + // CONTROL writes must be followed by an Instruction Synchronization Barrier + // (ISB). https://developer.arm.com/documentation/dai0321/latest + isb + + // Set the link register to the special EXC_RETURN value of 0xFFFFFFFD which + // instructs the CPU to run in thread mode with the process stack. + ldr lr, =0xFFFFFFFD // LR = 0xFFFFFFFD + + // Switch to the app. + bx lr + + 100: // to_kernel + // An application called a syscall. We mark this in the global variable + // `SYSCALL_FIRED` which is stored in the syscall file. + // `UserspaceKernelBoundary` will use this variable to decide why the app + // stopped executing. + ldr r0, =SYSCALL_FIRED // r0 = &SYSCALL_FIRED + mov r1, #1 // r1 = 1 + str r1, [r0] // *SYSCALL_FIRED = 1 + + // Use the CONTROL register to set the thread mode to privileged to switch + // back to kernel mode. + // + // CONTROL[1]: Stack status + // 0 = Default stack (MSP) is used + // 1 = Alternate stack is used + // CONTROL[0]: Mode + // 0 = Privileged in thread mode + // 1 = User state in thread mode + mov r0, #0 // r0 = 0 + msr CONTROL, r0 // CONTROL = 0 + // CONTROL writes must be followed by an Instruction Synchronization Barrier + // (ISB). https://developer.arm.com/documentation/dai0321/latest + isb + + // Set the link register to the special EXC_RETURN value of 0xFFFFFFF9 which + // instructs the CPU to run in thread mode with the main (kernel) stack. + ldr lr, =0xFFFFFFF9 // LR = 0xFFFFFFF9 + + // Return to the kernel. + bx lr + " +); + +#[cfg(all(target_arch = "arm", target_os = "none"))] +extern "C" { + /// Generic interrupt handler for ARMv7-M instruction sets. + /// + /// For documentation of this function, see `CortexMVariant::GENERIC_ISR`. + pub fn generic_isr_arm_v7m(); +} +#[cfg(all(target_arch = "arm", target_os = "none"))] +global_asm!( + " + .section .generic_isr_arm_v7m, \"ax\" + .global generic_isr_arm_v7m + .thumb_func + generic_isr_arm_v7m: + // Use the CONTROL register to set the thread mode to privileged to ensure + // we are executing as the kernel. This may be redundant if the interrupt + // happened while the kernel code was executing. + // + // CONTROL[1]: Stack status + // 0 = Default stack (MSP) is used + // 1 = Alternate stack is used + // CONTROL[0]: Mode + // 0 = Privileged in thread mode + // 1 = User state in thread mode + mov r0, #0 // r0 = 0 + msr CONTROL, r0 // CONTROL = 0 + // CONTROL writes must be followed by an Instruction Synchronization Barrier + // (ISB). https://developer.arm.com/documentation/dai0321/latest + isb + + // Set the link register to the special EXC_RETURN value of 0xFFFFFFF9 which + // instructs the CPU to run in thread mode with the main (kernel) stack. + ldr lr, =0xFFFFFFF9 // LR = 0xFFFFFFF9 + + // Now need to disable the interrupt that fired in the NVIC to ensure it + // does not trigger again before the scheduler has a chance to handle it. We + // do this here in assembly for performance. + // + // The general idea is: + // 1. Get the index of the interrupt that occurred. + // 2. Set the disable bit for that interrupt in the NVIC. + + // Find the ISR number (`index`) by looking at the low byte of the IPSR + // registers. + mrs r0, IPSR // r0 = Interrupt Program Status Register (IPSR) + and r0, #0xff // r0 = r0 & 0xFF; Get lowest 8 bits + sub r0, #16 // r0 = r0 - 16; ISRs start at 16, so subtract 16 to get zero-indexed. + + // Now disable that interrupt in the NVIC. + // High level: + // r0 = index + // NVIC.ICER[r0 / 32] = 1 << (r0 & 31) + lsrs r2, r0, #5 // r2 = r0 / 32 + // r0 = 1 << (r0 & 31) + movs r3, #1 // r3 = 1 + and r0, r0, #31 // r0 = r0 & 31 + lsl r0, r3, r0 // r0 = r3 << r0 + + // Load the ICER register address. + ldr r3, =0xe000e180 // r3 = &NVIC.ICER + + // Here: + // - `r2` is index / 32 + // - `r3` is &NVIC.ICER + // - `r0` is 1 << (index & 31) + str r0, [r3, r2, lsl #2] // *(r3 + r2 * 4) = r0 + + // The pending bit in ISPR might be reset by hardware for pulse interrupts + // at this point. So set it here again so the interrupt does not get lost in + // `service_pending_interrupts()`. + ldr r3, =0xe000e200 // r3 = &NVIC.ISPR + str r0, [r3, r2, lsl #2] // *(r3 + r2 * 4) = r0 + + // Now we can return from the interrupt context and resume what we were + // doing. If an app was executing we will switch to the kernel so it can + // choose whether to service the interrupt. + bx lr + "); + +/// Assembly function to switch into userspace and store/restore application +/// state. +/// +/// For documentation of this function, please see +/// `CortexMVariant::switch_to_user`. +#[cfg(all(target_arch = "arm", target_os = "none"))] +pub unsafe fn switch_to_user_arm_v7m( + mut user_stack: *const usize, + process_regs: &mut [usize; 8], +) -> *const usize { + use core::arch::asm; + asm!( + " + // Rust `asm!()` macro (as of May 2021) will not let us mark r6, r7 and r9 + // as clobbers. r6 and r9 is used internally by LLVM, and r7 is used for + // the frame pointer. However, in the process of restoring and saving the + // process's registers, we do in fact clobber r6, r7 and r9. So, we work + // around this by doing our own manual saving of r6 using r2, r7 using r3, + // r9 using r12, and then mark those as clobbered. + mov r2, r6 // r2 = r6 + mov r3, r7 // r3 = r7 + mov r12, r9 // r12 = r9 + + // The arguments passed in are: + // - `r0` is the bottom of the user stack + // - `r1` is a reference to `CortexMStoredState.regs` + + // Load bottom of stack into Process Stack Pointer. + msr psp, r0 // PSP = r0 + + // Load non-hardware-stacked registers from the process stored state. Ensure + // that the address register (right now r1) is stored in a callee saved + // register. + ldmia r1, {{r4-r11}} // r4 = r1[0], r5 = r1[1], ... + + // Generate a SVC exception to handle the context switch from kernel to + // userspace. It doesn't matter which SVC number we use here as it is not + // used in the exception handler. Data being returned from a syscall is + // transferred on the app's stack. + svc 0xff + + // When execution returns here we have switched back to the kernel from the + // application. + + // Push non-hardware-stacked registers into the saved state for the + // application. + stmia r1, {{r4-r11}} // r1[0] = r4, r1[1] = r5, ... + + // Update the user stack pointer with the current value after the + // application has executed. + mrs r0, PSP // r0 = PSP + + // Need to restore r6, r7 and r12 since we clobbered them when switching to + // and from the app. + mov r6, r2 // r6 = r2 + mov r7, r3 // r7 = r3 + mov r9, r12 // r9 = r12 + ", + inout("r0") user_stack, + in("r1") process_regs, + out("r2") _, out("r3") _, out("r4") _, out("r5") _, out("r8") _, out("r10") _, + out("r11") _, out("r12") _); + + user_stack +} + +#[cfg(all(target_arch = "arm", target_os = "none"))] +/// Continue the hardfault handler for all hard-faults that occurred +/// during kernel execution. This function must never return. +unsafe extern "C" fn hard_fault_handler_arm_v7m_kernel( + faulting_stack: *mut u32, + stack_overflow: u32, +) -> ! { + if stack_overflow != 0 { + // Panic to show the correct error. + panic!("kernel stack overflow"); + } else { + // Show the normal kernel hardfault message. + let stacked_r0: u32 = *faulting_stack.offset(0); + let stacked_r1: u32 = *faulting_stack.offset(1); + let stacked_r2: u32 = *faulting_stack.offset(2); + let stacked_r3: u32 = *faulting_stack.offset(3); + let stacked_r12: u32 = *faulting_stack.offset(4); + let stacked_lr: u32 = *faulting_stack.offset(5); + let stacked_pc: u32 = *faulting_stack.offset(6); + let stacked_xpsr: u32 = *faulting_stack.offset(7); + + let mode_str = "Kernel"; + + let shcsr: u32 = core::ptr::read_volatile(0xE000ED24 as *const u32); + let cfsr: u32 = core::ptr::read_volatile(0xE000ED28 as *const u32); + let hfsr: u32 = core::ptr::read_volatile(0xE000ED2C as *const u32); + let mmfar: u32 = core::ptr::read_volatile(0xE000ED34 as *const u32); + let bfar: u32 = core::ptr::read_volatile(0xE000ED38 as *const u32); + + let iaccviol = (cfsr & 0x01) == 0x01; + let daccviol = (cfsr & 0x02) == 0x02; + let munstkerr = (cfsr & 0x08) == 0x08; + let mstkerr = (cfsr & 0x10) == 0x10; + let mlsperr = (cfsr & 0x20) == 0x20; + let mmfarvalid = (cfsr & 0x80) == 0x80; + + let ibuserr = ((cfsr >> 8) & 0x01) == 0x01; + let preciserr = ((cfsr >> 8) & 0x02) == 0x02; + let impreciserr = ((cfsr >> 8) & 0x04) == 0x04; + let unstkerr = ((cfsr >> 8) & 0x08) == 0x08; + let stkerr = ((cfsr >> 8) & 0x10) == 0x10; + let lsperr = ((cfsr >> 8) & 0x20) == 0x20; + let bfarvalid = ((cfsr >> 8) & 0x80) == 0x80; + + let undefinstr = ((cfsr >> 16) & 0x01) == 0x01; + let invstate = ((cfsr >> 16) & 0x02) == 0x02; + let invpc = ((cfsr >> 16) & 0x04) == 0x04; + let nocp = ((cfsr >> 16) & 0x08) == 0x08; + let unaligned = ((cfsr >> 16) & 0x100) == 0x100; + let divbysero = ((cfsr >> 16) & 0x200) == 0x200; + + let vecttbl = (hfsr & 0x02) == 0x02; + let forced = (hfsr & 0x40000000) == 0x40000000; + + let ici_it = (((stacked_xpsr >> 25) & 0x3) << 6) | ((stacked_xpsr >> 10) & 0x3f); + let thumb_bit = ((stacked_xpsr >> 24) & 0x1) == 1; + let exception_number = (stacked_xpsr & 0x1ff) as usize; + + panic!( + "{} HardFault.\r\n\ + \tKernel version {}\r\n\ + \tr0 0x{:x}\r\n\ + \tr1 0x{:x}\r\n\ + \tr2 0x{:x}\r\n\ + \tr3 0x{:x}\r\n\ + \tr12 0x{:x}\r\n\ + \tlr 0x{:x}\r\n\ + \tpc 0x{:x}\r\n\ + \tprs 0x{:x} [ N {} Z {} C {} V {} Q {} GE {}{}{}{} ; ICI.IT {} T {} ; Exc {}-{} ]\r\n\ + \tsp 0x{:x}\r\n\ + \ttop of stack 0x{:x}\r\n\ + \tbottom of stack 0x{:x}\r\n\ + \tSHCSR 0x{:x}\r\n\ + \tCFSR 0x{:x}\r\n\ + \tHSFR 0x{:x}\r\n\ + \tInstruction Access Violation: {}\r\n\ + \tData Access Violation: {}\r\n\ + \tMemory Management Unstacking Fault: {}\r\n\ + \tMemory Management Stacking Fault: {}\r\n\ + \tMemory Management Lazy FP Fault: {}\r\n\ + \tInstruction Bus Error: {}\r\n\ + \tPrecise Data Bus Error: {}\r\n\ + \tImprecise Data Bus Error: {}\r\n\ + \tBus Unstacking Fault: {}\r\n\ + \tBus Stacking Fault: {}\r\n\ + \tBus Lazy FP Fault: {}\r\n\ + \tUndefined Instruction Usage Fault: {}\r\n\ + \tInvalid State Usage Fault: {}\r\n\ + \tInvalid PC Load Usage Fault: {}\r\n\ + \tNo Coprocessor Usage Fault: {}\r\n\ + \tUnaligned Access Usage Fault: {}\r\n\ + \tDivide By Zero: {}\r\n\ + \tBus Fault on Vector Table Read: {}\r\n\ + \tForced Hard Fault: {}\r\n\ + \tFaulting Memory Address: (valid: {}) {:#010X}\r\n\ + \tBus Fault Address: (valid: {}) {:#010X}\r\n\ + ", + mode_str, + option_env!("TOCK_KERNEL_VERSION").unwrap_or("unknown"), + stacked_r0, + stacked_r1, + stacked_r2, + stacked_r3, + stacked_r12, + stacked_lr, + stacked_pc, + stacked_xpsr, + (stacked_xpsr >> 31) & 0x1, + (stacked_xpsr >> 30) & 0x1, + (stacked_xpsr >> 29) & 0x1, + (stacked_xpsr >> 28) & 0x1, + (stacked_xpsr >> 27) & 0x1, + (stacked_xpsr >> 19) & 0x1, + (stacked_xpsr >> 18) & 0x1, + (stacked_xpsr >> 17) & 0x1, + (stacked_xpsr >> 16) & 0x1, + ici_it, + thumb_bit, + exception_number, + ipsr_isr_number_to_str(exception_number), + faulting_stack as u32, + core::ptr::addr_of!(_estack) as u32, + core::ptr::addr_of!(_sstack) as u32, + shcsr, + cfsr, + hfsr, + iaccviol, + daccviol, + munstkerr, + mstkerr, + mlsperr, + ibuserr, + preciserr, + impreciserr, + unstkerr, + stkerr, + lsperr, + undefinstr, + invstate, + invpc, + nocp, + unaligned, + divbysero, + vecttbl, + forced, + mmfarvalid, + mmfar, + bfarvalid, + bfar + ); + } +} + +#[cfg(all(target_arch = "arm", target_os = "none"))] +extern "C" { + /// ARMv7-M hardfault handler. + /// + /// For documentation of this function, please see + /// `CortexMVariant::HARD_FAULT_HANDLER_HANDLER`. + pub fn hard_fault_handler_arm_v7m(); +} + +#[cfg(all(target_arch = "arm", target_os = "none"))] +// First need to determine if this a kernel fault or a userspace fault, and store +// the unmodified stack pointer. Place these values in registers, then call +// a non-naked function, to allow for use of rust code alongside inline asm. +// Because calling a function increases the stack pointer, we have to check for a kernel +// stack overflow and adjust the stack pointer before we branch +global_asm!( + " + .section .hard_fault_handler_arm_v7m, \"ax\" + .global hard_fault_handler_arm_v7m + .thumb_func + hard_fault_handler_arm_v7m: + mov r2, 0 // r2 = 0 + tst lr, #4 // bitwise AND link register to 0b100 + itte eq // if lr==4, run next two instructions, else, run 3rd instruction. + mrseq r0, msp // r0 = kernel stack pointer + addeq r2, 1 // r2 = 1, kernel was executing + mrsne r0, psp // r0 = userland stack pointer + // Need to determine if we had a stack overflow before we push anything + // on to the stack. We check this by looking at the BusFault Status + // Register's (BFSR) `LSPERR` and `STKERR` bits to see if the hardware + // had any trouble stacking important registers to the stack during the + // fault. If so, then we cannot use this stack while handling this fault + // or we will trigger another fault. + ldr r3, =0xE000ED29 // SCB BFSR register address + ldrb r3, [r3] // r3 = BFSR + tst r3, #0x30 // r3 = BFSR & 0b00110000; LSPERR & STKERR bits + ite ne // check if the result of that bitwise AND was not 0 + movne r1, #1 // BFSR & 0b00110000 != 0; r1 = 1 + moveq r1, #0 // BFSR & 0b00110000 == 0; r1 = 0 + and r5, r2, r1 // bitwise and r1 and r2, store in r5 + cmp r5, #1 // update condition codes to reflect if r1 == 1 && r2 == 1 + itt eq // if r5==1 run the next 2 instructions, else skip to branch + // if true, The hardware couldn't use the stack, so we have no saved data and + // we cannot use the kernel stack as is. We just want to report that + // the kernel's stack overflowed, since that is essential for + // debugging. + // + // To make room for a panic!() handler stack, we just re-use the + // kernel's original stack. This should in theory leave the bottom + // of the stack where the problem occurred untouched should one want + // to further debug. + ldreq r4, ={estack} // load _estack into r4 + moveq sp, r4 // Set the stack pointer to _estack + // finally, if the fault occurred in privileged mode (r2 == 1), branch + // to non-naked handler. + cmp r2, #0 + // Per ARM calling convention, faulting stack is passed in r0, whether + // there was a stack overflow in r1. This function must never return. + bne {kernel_hard_fault_handler} // branch to kernel hard fault handler + // Otherwise, the hard fault occurred in userspace. In this case, read + // the relevant SCB registers: + ldr r0, =SCB_REGISTERS // Global variable address + ldr r1, =0xE000ED14 // SCB CCR register address + ldr r2, [r1, #0] // CCR + str r2, [r0, #0] + ldr r2, [r1, #20] // CFSR + str r2, [r0, #4] + ldr r2, [r1, #24] // HFSR + str r2, [r0, #8] + ldr r2, [r1, #32] // MMFAR + str r2, [r0, #12] + ldr r2, [r1, #36] // BFAR + str r2, [r0, #16] + + ldr r0, =APP_HARD_FAULT // Global variable address + mov r1, #1 // r1 = 1 + str r1, [r0, #0] // APP_HARD_FAULT = 1 + + // Set thread mode to privileged + mov r0, #0 + msr CONTROL, r0 + // CONTROL writes must be followed by ISB + // http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dai0321a/BIHFJCAC.html + isb + + // Switch to the kernel (MSP) stack: + movw lr, #0xFFF9 + movt lr, #0xFFFF + bx lr", + estack = sym _estack, + kernel_hard_fault_handler = sym hard_fault_handler_arm_v7m_kernel, +); + +// Table 2.5 +// http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/CHDBIBGJ.html +pub fn ipsr_isr_number_to_str(isr_number: usize) -> &'static str { + match isr_number { + 0 => "Thread Mode", + 1 => "Reserved", + 2 => "NMI", + 3 => "HardFault", + 4 => "MemManage", + 5 => "BusFault", + 6 => "UsageFault", + 7..=10 => "Reserved", + 11 => "SVCall", + 12 => "Reserved for Debug", + 13 => "Reserved", + 14 => "PendSV", + 15 => "SysTick", + 16..=255 => "IRQn", + _ => "(Unknown! Illegal value?)", + } +} + +/////////////////////////////////////////////////////////////////// +// Mock implementations for running tests on CI. +// +// Since tests run on the local architecture, we have to remove any +// ARM assembly since it will not compile. +/////////////////////////////////////////////////////////////////// + +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +pub unsafe extern "C" fn systick_handler_arm_v7m() { + unimplemented!() +} + +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +pub unsafe extern "C" fn svc_handler_arm_v7m() { + unimplemented!() +} + +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +pub unsafe extern "C" fn generic_isr_arm_v7m() { + unimplemented!() +} + +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +pub unsafe extern "C" fn switch_to_user_arm_v7m( + _user_stack: *const u8, + _process_regs: &mut [usize; 8], +) -> *const usize { + unimplemented!() +} + +#[cfg(not(all(target_arch = "arm", target_os = "none")))] +pub unsafe extern "C" fn hard_fault_handler_arm_v7m() { + unimplemented!() +} diff --git a/arch/riscv/Cargo.toml b/arch/riscv/Cargo.toml index a53fc3cfba..006f45842c 100644 --- a/arch/riscv/Cargo.toml +++ b/arch/riscv/Cargo.toml @@ -1,11 +1,18 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "riscv" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] kernel = { path = "../../kernel" } tock-registers = { path = "../../libraries/tock-register-interface" } riscv-csr = { path = "../../libraries/riscv-csr" } + +[lints] +workspace = true diff --git a/arch/riscv/src/csr/mcause.rs b/arch/riscv/src/csr/mcause.rs index 80a31c98e4..b3dad3676a 100644 --- a/arch/riscv/src/csr/mcause.rs +++ b/arch/riscv/src/csr/mcause.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::utilities::registers::{register_bitfields, LocalRegisterCopy}; register_bitfields![usize, diff --git a/arch/riscv/src/csr/mcycle.rs b/arch/riscv/src/csr/mcycle.rs index 0e9fdf6012..7384bb33d7 100644 --- a/arch/riscv/src/csr/mcycle.rs +++ b/arch/riscv/src/csr/mcycle.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::utilities::registers::register_bitfields; // mcycle is the lower XLEN bits of the number of elapsed cycles @@ -9,7 +13,7 @@ register_bitfields![usize, // `mcycleh` is the higher XLEN bits of the number of elapsed cycles. // It does not exist on riscv64. -#[cfg(any(target_arch = "riscv32", not(target_os = "none")))] +#[cfg(not(target_arch = "riscv64"))] register_bitfields![usize, pub mcycleh [ mcycleh OFFSET(0) NUMBITS(crate::XLEN) [] diff --git a/arch/riscv/src/csr/mepc.rs b/arch/riscv/src/csr/mepc.rs index daf8e21ad3..b6811308eb 100644 --- a/arch/riscv/src/csr/mepc.rs +++ b/arch/riscv/src/csr/mepc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::utilities::registers::register_bitfields; // mepc contains address of instruction where trap occurred diff --git a/arch/riscv/src/csr/mie.rs b/arch/riscv/src/csr/mie.rs index 57fef8a90a..bf7b2ef113 100644 --- a/arch/riscv/src/csr/mie.rs +++ b/arch/riscv/src/csr/mie.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::utilities::registers::register_bitfields; // mtvec contains the address(es) of the trap handler diff --git a/arch/riscv/src/csr/minstret.rs b/arch/riscv/src/csr/minstret.rs index b14af89503..a930418998 100644 --- a/arch/riscv/src/csr/minstret.rs +++ b/arch/riscv/src/csr/minstret.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::utilities::registers::register_bitfields; // minstret is the lower XLEN bits of the number of elapsed instructions @@ -9,7 +13,7 @@ register_bitfields![usize, // `minstreth` is the higher XLEN bits of the number of elapsed instructions. // It does not exist on riscv64. -#[cfg(any(target_arch = "riscv32", not(target_os = "none")))] +#[cfg(not(target_arch = "riscv64"))] register_bitfields![usize, pub minstreth [ minstreth OFFSET(0) NUMBITS(crate::XLEN) [] diff --git a/arch/riscv/src/csr/mip.rs b/arch/riscv/src/csr/mip.rs index a3d566e3e9..89dd46ab7c 100644 --- a/arch/riscv/src/csr/mip.rs +++ b/arch/riscv/src/csr/mip.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::utilities::registers::register_bitfields; register_bitfields![usize, diff --git a/arch/riscv/src/csr/mod.rs b/arch/riscv/src/csr/mod.rs index 1c6c1185bd..ca8b3018f0 100644 --- a/arch/riscv/src/csr/mod.rs +++ b/arch/riscv/src/csr/mod.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tock Register interface for using CSR registers. use riscv_csr::csr::{ @@ -39,38 +43,38 @@ pub mod utvec; // something (as it would be if compiled for a host OS). pub struct CSR { - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub minstreth: ReadWriteRiscvCsr, pub minstret: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub mcycleh: ReadWriteRiscvCsr, pub mcycle: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub pmpcfg0: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub pmpcfg1: ReadWriteRiscvCsr, pub pmpcfg2: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub pmpcfg3: ReadWriteRiscvCsr, pub pmpcfg4: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub pmpcfg5: ReadWriteRiscvCsr, pub pmpcfg6: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub pmpcfg7: ReadWriteRiscvCsr, pub pmpcfg8: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub pmpcfg9: ReadWriteRiscvCsr, pub pmpcfg10: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub pmpcfg11: ReadWriteRiscvCsr, pub pmpcfg12: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub pmpcfg13: ReadWriteRiscvCsr, pub pmpcfg14: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub pmpcfg15: ReadWriteRiscvCsr, pub pmpaddr0: ReadWriteRiscvCsr, @@ -148,7 +152,7 @@ pub struct CSR { pub mstatus: ReadWriteRiscvCsr, pub mseccfg: ReadWriteRiscvCsr, - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub mseccfgh: ReadWriteRiscvCsr, pub utvec: ReadWriteRiscvCsr, @@ -157,37 +161,37 @@ pub struct CSR { // Define the "addresses" of each CSR register. pub const CSR: &CSR = &CSR { - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] minstreth: ReadWriteRiscvCsr::new(), minstret: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] mcycleh: ReadWriteRiscvCsr::new(), mcycle: ReadWriteRiscvCsr::new(), pmpcfg0: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pmpcfg1: ReadWriteRiscvCsr::new(), pmpcfg2: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pmpcfg3: ReadWriteRiscvCsr::new(), pmpcfg4: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pmpcfg5: ReadWriteRiscvCsr::new(), pmpcfg6: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pmpcfg7: ReadWriteRiscvCsr::new(), pmpcfg8: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pmpcfg9: ReadWriteRiscvCsr::new(), pmpcfg10: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pmpcfg11: ReadWriteRiscvCsr::new(), pmpcfg12: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pmpcfg13: ReadWriteRiscvCsr::new(), pmpcfg14: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pmpcfg15: ReadWriteRiscvCsr::new(), pmpaddr0: ReadWriteRiscvCsr::new(), @@ -265,7 +269,7 @@ pub const CSR: &CSR = &CSR { mstatus: ReadWriteRiscvCsr::new(), mseccfg: ReadWriteRiscvCsr::new(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] mseccfgh: ReadWriteRiscvCsr::new(), utvec: ReadWriteRiscvCsr::new(), @@ -274,7 +278,7 @@ pub const CSR: &CSR = &CSR { impl CSR { // resets the cycle counter to 0 - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub fn reset_cycle_counter(&self) { // Write lower first so that we don't overflow before writing the upper CSR.mcycle.write(mcycle::mcycle::mcycle.val(0)); @@ -288,7 +292,7 @@ impl CSR { } // reads the cycle counter - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] pub fn read_cycle_counter(&self) -> u64 { let (mut top, mut bot): (usize, usize); @@ -315,28 +319,28 @@ impl CSR { pub fn pmpconfig_get(&self, index: usize) -> usize { match index { 0 => self.pmpcfg0.get(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 1 => self.pmpcfg1.get(), 2 => self.pmpcfg2.get(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 3 => self.pmpcfg3.get(), 4 => self.pmpcfg4.get(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 5 => self.pmpcfg5.get(), 6 => self.pmpcfg6.get(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 7 => self.pmpcfg7.get(), 8 => self.pmpcfg8.get(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 9 => self.pmpcfg9.get(), 10 => self.pmpcfg10.get(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 11 => self.pmpcfg11.get(), 12 => self.pmpcfg12.get(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 13 => self.pmpcfg13.get(), 14 => self.pmpcfg14.get(), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 15 => self.pmpcfg15.get(), _ => unreachable!(), } @@ -345,28 +349,28 @@ impl CSR { pub fn pmpconfig_set(&self, index: usize, value: usize) { match index { 0 => self.pmpcfg0.set(value), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 1 => self.pmpcfg1.set(value), 2 => self.pmpcfg2.set(value), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 3 => self.pmpcfg3.set(value), 4 => self.pmpcfg4.set(value), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 5 => self.pmpcfg5.set(value), 6 => self.pmpcfg6.set(value), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 7 => self.pmpcfg7.set(value), 8 => self.pmpcfg8.set(value), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 9 => self.pmpcfg9.set(value), 10 => self.pmpcfg10.set(value), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 11 => self.pmpcfg11.set(value), 12 => self.pmpcfg12.set(value), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 13 => self.pmpcfg13.set(value), 14 => self.pmpcfg14.set(value), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 15 => self.pmpcfg15.set(value), _ => unreachable!(), } @@ -379,28 +383,28 @@ impl CSR { ) { match index { 0 => self.pmpcfg0.modify(field), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 1 => self.pmpcfg1.modify(field), 2 => self.pmpcfg2.modify(field), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 3 => self.pmpcfg3.modify(field), 4 => self.pmpcfg4.modify(field), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 5 => self.pmpcfg5.modify(field), 6 => self.pmpcfg6.modify(field), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 7 => self.pmpcfg7.modify(field), 8 => self.pmpcfg8.modify(field), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 9 => self.pmpcfg9.modify(field), 10 => self.pmpcfg10.modify(field), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 11 => self.pmpcfg11.modify(field), 12 => self.pmpcfg12.modify(field), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 13 => self.pmpcfg13.modify(field), 14 => self.pmpcfg14.modify(field), - #[cfg(any(target_arch = "riscv32", not(target_os = "none")))] + #[cfg(not(target_arch = "riscv64"))] 15 => self.pmpcfg15.modify(field), _ => unreachable!(), } diff --git a/arch/riscv/src/csr/mscratch.rs b/arch/riscv/src/csr/mscratch.rs index 946bbebf1d..88d2c4b2e5 100644 --- a/arch/riscv/src/csr/mscratch.rs +++ b/arch/riscv/src/csr/mscratch.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::utilities::registers::register_bitfields; register_bitfields![usize, diff --git a/arch/riscv/src/csr/mseccfg.rs b/arch/riscv/src/csr/mseccfg.rs index 618842fc15..7f6b02bc4e 100644 --- a/arch/riscv/src/csr/mseccfg.rs +++ b/arch/riscv/src/csr/mseccfg.rs @@ -1,16 +1,20 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::utilities::registers::register_bitfields; // Default to 32 bit if compiling for debug/testing. -#[cfg(any(target_arch = "riscv32", not(target_os = "none")))] +#[cfg(not(target_arch = "riscv64"))] register_bitfields![usize, pub mseccfg [ mml OFFSET(0) NUMBITS(1) [], - mwmp OFFSET(1) NUMBITS(1) [], + mmwp OFFSET(1) NUMBITS(1) [], rlb OFFSET(2) NUMBITS(1) [], ] ]; -#[cfg(any(target_arch = "riscv32", not(target_os = "none")))] +#[cfg(not(target_arch = "riscv64"))] register_bitfields![usize, pub mseccfgh [ // This isn't a real entry, it just avoids compilation errors @@ -21,8 +25,8 @@ register_bitfields![usize, #[cfg(target_arch = "riscv64")] register_bitfields![usize, pub mseccfg [ - MML OFFSET(0) NUMBITS(1) [], - MMWP OFFSET(1) NUMBITS(1) [], - RLB OFFSET(2) NUMBITS(1) [], + mml OFFSET(0) NUMBITS(1) [], + mmwp OFFSET(1) NUMBITS(1) [], + rlb OFFSET(2) NUMBITS(1) [], ] ]; diff --git a/arch/riscv/src/csr/mstatus.rs b/arch/riscv/src/csr/mstatus.rs index 5fdf1bd17a..358f886092 100644 --- a/arch/riscv/src/csr/mstatus.rs +++ b/arch/riscv/src/csr/mstatus.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::utilities::registers::register_bitfields; register_bitfields![usize, diff --git a/arch/riscv/src/csr/mtval.rs b/arch/riscv/src/csr/mtval.rs index 1b75736b89..b52543802c 100644 --- a/arch/riscv/src/csr/mtval.rs +++ b/arch/riscv/src/csr/mtval.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::utilities::registers::register_bitfields; // mtval contains the address of an exception diff --git a/arch/riscv/src/csr/mtvec.rs b/arch/riscv/src/csr/mtvec.rs index 64c7a930f9..6d3b20afaa 100644 --- a/arch/riscv/src/csr/mtvec.rs +++ b/arch/riscv/src/csr/mtvec.rs @@ -1,4 +1,8 @@ -use kernel::utilities::registers::{register_bitfields, LocalRegisterCopy}; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use kernel::utilities::registers::register_bitfields; // mtvec contains the address(es) of the trap handler register_bitfields![usize, @@ -10,13 +14,3 @@ register_bitfields![usize, ] ] ]; - -trait MtvecHelpers { - fn get_trap_address(&self) -> usize; -} - -impl MtvecHelpers for LocalRegisterCopy { - fn get_trap_address(&self) -> usize { - self.read(mtvec::trap_addr) << 2 - } -} diff --git a/arch/riscv/src/csr/pmpaddr.rs b/arch/riscv/src/csr/pmpaddr.rs index b11b9bfa0e..ac62830cf0 100644 --- a/arch/riscv/src/csr/pmpaddr.rs +++ b/arch/riscv/src/csr/pmpaddr.rs @@ -1,7 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::utilities::registers::register_bitfields; // Default to 32 bit if compiling for debug/testing. -#[cfg(any(target_arch = "riscv32", not(target_os = "none")))] +#[cfg(not(target_arch = "riscv64"))] register_bitfields![usize, pub pmpaddr [ addr OFFSET(0) NUMBITS(crate::XLEN) [] diff --git a/arch/riscv/src/csr/pmpconfig.rs b/arch/riscv/src/csr/pmpconfig.rs index e95e1f4ef6..a341be5f13 100644 --- a/arch/riscv/src/csr/pmpconfig.rs +++ b/arch/riscv/src/csr/pmpconfig.rs @@ -1,7 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::utilities::registers::register_bitfields; // Default to 32 bit if compiling for debug/testing. -#[cfg(any(target_arch = "riscv32", not(target_os = "none")))] +#[cfg(any( + target_arch = "riscv32", + all(not(target_arch = "riscv32"), not(target_arch = "riscv64")) +))] register_bitfields![usize, pub pmpcfg [ r0 OFFSET(0) NUMBITS(1) [], diff --git a/arch/riscv/src/csr/stvec.rs b/arch/riscv/src/csr/stvec.rs index 85b295f848..e6db1317fd 100644 --- a/arch/riscv/src/csr/stvec.rs +++ b/arch/riscv/src/csr/stvec.rs @@ -1,4 +1,8 @@ -use kernel::utilities::registers::{register_bitfields, LocalRegisterCopy}; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use kernel::utilities::registers::register_bitfields; // stvec contains the address(es) of the trap handler register_bitfields![usize, @@ -10,13 +14,3 @@ register_bitfields![usize, ] ] ]; - -trait StvecHelpers { - fn get_trap_address(&self) -> usize; -} - -impl StvecHelpers for LocalRegisterCopy { - fn get_trap_address(&self) -> usize { - self.read(stvec::trap_addr) << 2 - } -} diff --git a/arch/riscv/src/csr/utvec.rs b/arch/riscv/src/csr/utvec.rs index 70f92d087a..d1e2cb2af7 100644 --- a/arch/riscv/src/csr/utvec.rs +++ b/arch/riscv/src/csr/utvec.rs @@ -1,4 +1,8 @@ -use kernel::utilities::registers::{register_bitfields, LocalRegisterCopy}; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use kernel::utilities::registers::register_bitfields; // utvec contains the address(es) of the trap handler register_bitfields![usize, @@ -10,13 +14,3 @@ register_bitfields![usize, ] ] ]; - -trait UtvecHelpers { - fn get_trap_address(&self) -> usize; -} - -impl UtvecHelpers for LocalRegisterCopy { - fn get_trap_address(&self) -> usize { - self.read(utvec::trap_addr) << 2 - } -} diff --git a/arch/riscv/src/lib.rs b/arch/riscv/src/lib.rs index bac6438c92..dedfb1ad0d 100644 --- a/arch/riscv/src/lib.rs +++ b/arch/riscv/src/lib.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Shared support for RISC-V architectures. #![crate_name = "riscv"] @@ -13,5 +17,8 @@ pub const XLEN: usize = 64; // Default to 32 bit if no architecture is specified of if this is being // compiled for testing on a different architecture. -#[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64", target_os = "none")))] +#[cfg(not(all( + any(target_arch = "riscv32", target_arch = "riscv64"), + target_os = "none" +)))] pub const XLEN: usize = 32; diff --git a/arch/rv32i/Cargo.toml b/arch/rv32i/Cargo.toml index eabc066bf7..515a28a8fe 100644 --- a/arch/rv32i/Cargo.toml +++ b/arch/rv32i/Cargo.toml @@ -1,8 +1,12 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "rv32i" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] kernel = { path = "../../kernel" } @@ -10,3 +14,6 @@ tock-registers = { path = "../../libraries/tock-register-interface" } riscv-csr = { path = "../../libraries/riscv-csr" } riscv = { path = "../riscv" } + +[lints] +workspace = true diff --git a/arch/rv32i/src/clic.rs b/arch/rv32i/src/clic.rs index dbf56fe0ce..210571af1d 100644 --- a/arch/rv32i/src/clic.rs +++ b/arch/rv32i/src/clic.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Core Local Interrupt Control peripheral driver. use kernel::utilities::registers::interfaces::{Readable, Writeable}; @@ -282,7 +286,7 @@ impl Clic { /// This is outside of the `Clic` struct because it has to be called from the /// trap handler which does not have a reference to the CLIC object. pub unsafe fn disable_interrupt(index: u32) { - let regs: &ClicRegisters = &*CLIC_BASE; + let regs: &ClicRegisters = &CLIC_BASE; match index { 3 => regs.clicintie.msip.write(inten::IntEn::CLEAR), diff --git a/arch/rv32i/src/epmp.rs b/arch/rv32i/src/epmp.rs deleted file mode 100644 index 4429eed8a0..0000000000 --- a/arch/rv32i/src/epmp.rs +++ /dev/null @@ -1,827 +0,0 @@ -//! Implementation of the enhanced physical memory protection unit (ePMP). -//! -//! ## Implementation -//! -//! We use the PMP Top of Region (TOR) alignment as there are alignment issues -//! with NAPOT. NAPOT would allow us to protect more memory regions (with NAPOT -//! each PMP region can be a memory region), but the problem with NAPOT is the -//! address must be aligned to the size, which results in wasted memory. To -//! avoid this wasted memory we use TOR and each memory region uses two physical -//! PMP regions. - -use crate::csr; -use core::cell::Cell; -use core::{cmp, fmt}; -use kernel::platform::mpu; -use kernel::utilities::cells::{MapCell, OptionalCell}; -use kernel::utilities::registers::interfaces::ReadWriteable; -use kernel::utilities::registers::{self, register_bitfields}; -use kernel::ProcessId; - -// Generic PMP config -register_bitfields![u8, - pub pmpcfg [ - r OFFSET(0) NUMBITS(1) [], - w OFFSET(1) NUMBITS(1) [], - x OFFSET(2) NUMBITS(1) [], - a OFFSET(3) NUMBITS(2) [ - OFF = 0, - TOR = 1, - NA4 = 2, - NAPOT = 3 - ], - l OFFSET(7) NUMBITS(1) [] - ] -]; - -/// Main PMP struct. -/// -/// Tock will ignore locked PMP regions. Note that Tock will not make any -/// attempt to avoid access faults from locked regions. -/// -/// `MAX_AVAILABLE_REGIONS_OVER_TWO`: The number of PMP regions divided by 2. -/// The RISC-V spec mandates that there must be either 0, 16 or 64 PMP -/// regions implemented. If you are using this PMP struct we are assuming -/// there is more then 0 implemented. So this value should be either 8 or 32. -/// -/// If however you know the exact number of PMP regions implemented by your -/// platform and it's not going to change you can just specify the number. -/// This means that Tock won't be able to dynamically handle more regions, -/// but it will reduce runtime space requirements. -/// Note: that this does not mean all PMP regions are connected. -/// Some of the regions can be WARL (Write Any Read Legal). All this means -/// is that accessing `NUM_REGIONS` won't cause a fault. -pub struct PMP { - /// The application that the MPU was last configured for. Used (along with - /// the `is_dirty` flag) to determine if MPU can skip writing the - /// configuration to hardware. - last_configured_for: MapCell, - /// This is a 64-bit mask of locked regions. - /// Each bit that is set in this mask indicates that the region is locked - /// and cannot be used by Tock. - locked_region_mask: Cell, - /// This is the total number of avaliable regions. - /// This will be between 0 and MAX_AVAILABLE_REGIONS_OVER_TWO * 2 depending - /// on the hardware and previous boot stages. - num_regions: usize, -} - -impl PMP { - pub unsafe fn new() -> Self { - // RISC-V PMP can support from 0 to 64 PMP regions - // Let's figure out how many are supported. - // We count any regions that are locked as unsupported - let mut num_regions = 0; - let mut locked_region_mask = 0; - - for i in 0..(MAX_AVAILABLE_REGIONS_OVER_TWO * 2) { - // Read the current value - let pmpcfg_og = csr::CSR.pmpconfig_get(i / 4); - - // Flip R, W, X bits - let pmpcfg_new = pmpcfg_og ^ (3 << ((i % 4) * 8)); - csr::CSR.pmpconfig_set(i / 4, pmpcfg_new); - - // Check if the bits are set - let pmpcfg_check = csr::CSR.pmpconfig_get(i / 4); - - // Check if the changes stuck - if pmpcfg_check == pmpcfg_og { - // If we get here then our changes didn't stick, let's figure - // out why - - // Check if the locked bit is set - if pmpcfg_og & ((1 << 7) << ((i % 4) * 8)) > 0 { - // The bit is locked. Mark this regions as not usable - locked_region_mask |= 1 << i; - } else { - // The locked bit isn't set - // This region must not be connected, which means we have run out - // of usable regions, break the loop - break; - } - } else { - // Found a working region - num_regions += 1; - } - - // Reset back to how we found it - csr::CSR.pmpconfig_set(i / 4, pmpcfg_og); - } - - Self { - last_configured_for: MapCell::empty(), - num_regions, - locked_region_mask: Cell::new(locked_region_mask), - } - } -} - -impl fmt::Display - for PMP -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fn bit_str<'a>(cfg: u8, bit: u8, on_str: &'a str, off_str: &'a str) -> &'a str { - match cfg & bit { - 0 => off_str, - _ => on_str, - } - } - - fn enabled_str<'a>(cfg: u8) -> &'a str { - if cfg & pmpcfg::a::OFF.mask() == pmpcfg::a::OFF.value { - "OFF" - } else if cfg & pmpcfg::a::TOR.mask() == pmpcfg::a::TOR.value { - "TOR" - } else if cfg & pmpcfg::a::NA4.mask() == pmpcfg::a::NA4.value { - "NA4" - } else if cfg & pmpcfg::a::NAPOT.mask() == pmpcfg::a::NAPOT.value { - "NAPOT" - } else { - unreachable!() - } - } - - write!(f, " ePMP regions:\r\n")?; - - for i in 0..(MAX_AVAILABLE_REGIONS_OVER_TWO * 2) { - // Read the current value - let pmpcfg = (csr::CSR.pmpconfig_get(i / 4) >> ((i % 4) * 8)) as u8; - let pmpaddr0 = if i > 0 { - csr::CSR.pmpaddr_get(i - 1) << 2 - } else { - 0 - }; - let pmpaddr1 = csr::CSR.pmpaddr_get(i) << 2; - - write!( - f, - " [{}]: addr={:#010X}, end={:#010X}, cfg={:#X} ({}) ({}{}{}{})\r\n", - i, - pmpaddr0, - pmpaddr1, - pmpcfg, - enabled_str(pmpcfg), - bit_str(pmpcfg, pmpcfg::l::SET.value, "l", "-"), - bit_str(pmpcfg, pmpcfg::r::SET.value, "r", "-"), - bit_str(pmpcfg, pmpcfg::w::SET.value, "w", "-"), - bit_str(pmpcfg, pmpcfg::x::SET.value, "x", "-"), - )?; - } - - Ok(()) - } -} - -/// Struct storing configuration for a RISC-V PMP region. -#[derive(Copy, Clone)] -pub struct PMPRegion { - location: (*const u8, usize), - cfg: registers::FieldValue, -} - -impl PartialEq for PMPRegion { - fn eq(&self, other: &mpu::Region) -> bool { - self.location.0 == other.start_address() && self.location.1 == other.size() - } -} - -impl fmt::Display for PMPRegion { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fn bit_str<'a>(reg: &PMPRegion, bit: u8, on_str: &'a str, off_str: &'a str) -> &'a str { - match reg.cfg.value & bit as u8 { - 0 => off_str, - _ => on_str, - } - } - - write!( - f, - "addr={:p}, size={:#010X}, cfg={:#X} ({}{}{}{})", - self.location.0, - self.location.1, - u8::from(self.cfg), - bit_str(self, pmpcfg::l::SET.value, "l", "-"), - bit_str(self, pmpcfg::r::SET.value, "r", "-"), - bit_str(self, pmpcfg::w::SET.value, "w", "-"), - bit_str(self, pmpcfg::x::SET.value, "x", "-"), - ) - } -} - -impl PMPRegion { - /// Create a new PMPRegion for use by apps - fn new_app(start: *const u8, size: usize, permissions: mpu::Permissions) -> Option { - // Determine access and execute permissions - let pmpcfg = match permissions { - mpu::Permissions::ReadWriteExecute => { - // App has read/write/execute, kernel can't access - pmpcfg::l::CLEAR + pmpcfg::r::SET + pmpcfg::w::SET + pmpcfg::x::SET + pmpcfg::a::TOR - } - mpu::Permissions::ReadWriteOnly => { - // App and kernel can both read/write - pmpcfg::l::CLEAR - + pmpcfg::r::SET - + pmpcfg::w::SET - + pmpcfg::x::CLEAR - + pmpcfg::a::TOR - } - mpu::Permissions::ReadExecuteOnly => { - // App has read/execute, kernel can't access - pmpcfg::l::CLEAR - + pmpcfg::r::SET - + pmpcfg::w::CLEAR - + pmpcfg::x::SET - + pmpcfg::a::TOR - } - mpu::Permissions::ReadOnly => { - // App has read, kernel can't access - pmpcfg::l::CLEAR - + pmpcfg::r::SET - + pmpcfg::w::CLEAR - + pmpcfg::x::CLEAR - + pmpcfg::a::TOR - } - mpu::Permissions::ExecuteOnly => { - // App has execute only, kernel can't access - pmpcfg::l::CLEAR - + pmpcfg::r::CLEAR - + pmpcfg::w::CLEAR - + pmpcfg::x::SET - + pmpcfg::a::TOR - } - }; - - Some(PMPRegion { - location: (start, size), - cfg: pmpcfg, - }) - } - - /// Create a new PMPRegion for use by the kernel - fn new_kernel( - start: *const u8, - size: usize, - permissions: mpu::Permissions, - ) -> Option { - // Determine access and execute permissions - let pmpcfg = match permissions { - mpu::Permissions::ReadWriteExecute => { - // Not supported - return None; - } - mpu::Permissions::ReadWriteOnly => { - // Kernel can read/write, app can't access - pmpcfg::l::SET + pmpcfg::r::SET + pmpcfg::w::SET + pmpcfg::x::CLEAR + pmpcfg::a::TOR - } - mpu::Permissions::ReadExecuteOnly => { - // Kernel can read/execute, app can't access - pmpcfg::l::SET + pmpcfg::r::SET + pmpcfg::w::CLEAR + pmpcfg::x::SET + pmpcfg::a::TOR - } - mpu::Permissions::ReadOnly => { - // Kernel can read, app can't access - pmpcfg::l::SET - + pmpcfg::r::SET - + pmpcfg::w::CLEAR - + pmpcfg::x::CLEAR - + pmpcfg::a::TOR - } - mpu::Permissions::ExecuteOnly => { - // Kernel can execute, app can't access - pmpcfg::l::SET - + pmpcfg::r::CLEAR - + pmpcfg::w::CLEAR - + pmpcfg::x::SET - + pmpcfg::a::TOR - } - }; - - Some(PMPRegion { - location: (start, size), - cfg: pmpcfg, - }) - } - - fn location(&self) -> (*const u8, usize) { - self.location - } - - fn overlaps(&self, other_start: *const u8, other_size: usize) -> bool { - let other_start = other_start as usize; - let other_end = other_start + other_size; - - let (region_start, region_size) = self.location; - - let (region_start, region_end) = { - let region_start = region_start as usize; - let region_end = region_start + region_size; - (region_start, region_end) - }; - - if region_start < other_end && other_start < region_end { - true - } else { - false - } - } -} - -/// Struct storing region configuration for RISCV PMP. -pub struct PMPConfig { - /// Array of PMP regions. Each region requires two physical entries. - regions: [Option; MAX_AVAILABLE_REGIONS_OVER_TWO], - /// Indicates if the configuration has changed since the last time it was - /// written to hardware. - is_dirty: Cell, - /// Which region index is used for app memory (if it has been configured). - app_memory_region: OptionalCell, -} - -impl Default - for PMPConfig -{ - /// `NUM_REGIONS` is the number of PMP entries the hardware supports. - /// - /// Since we use TOR, we will use two PMP entries for each region. So the actual - /// number of regions we can protect is `NUM_REGIONS/2`. Limitations of min_const_generics - /// require us to pass both of these values as separate generic consts. - fn default() -> Self { - PMPConfig { - regions: [None; MAX_AVAILABLE_REGIONS_OVER_TWO], - is_dirty: Cell::new(true), - app_memory_region: OptionalCell::empty(), - } - } -} - -impl fmt::Display - for PMPConfig -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, " App ePMP regions:\r\n")?; - for (n, region) in self.regions.iter().enumerate() { - match region { - None => write!(f, " \r\n")?, - Some(region) => write!(f, " [{}]: {}\r\n", n, region)?, - } - } - Ok(()) - } -} - -impl PMPConfig { - /// Get the first unused region - fn unused_region_number(&self, locked_region_mask: u64) -> Option { - for (number, region) in self.regions.iter().enumerate() { - if !self.is_index_locked_or_app(locked_region_mask, number) && region.is_none() { - return Some(number); - } - } - None - } - - /// Get the last unused region - /// The app regions need to be lower then the kernel to ensure they - /// match before the kernel ones. - fn unused_kernel_region_number(&self, locked_region_mask: u64) -> Option { - for (num, region) in self.regions.iter().rev().enumerate() { - let number = MAX_AVAILABLE_REGIONS_OVER_TWO - num - 1; - if !self.is_index_locked_or_app(locked_region_mask, number) && region.is_none() { - return Some(number); - } - } - None - } - - /// Returns true is the specified index is either locked or corresponds to the app region - fn is_index_locked_or_app(&self, locked_region_mask: u64, number: usize) -> bool { - locked_region_mask & (1 << number) > 0 || self.app_memory_region.contains(&number) - } -} - -impl kernel::platform::mpu::MPU - for PMP -{ - type MpuConfig = PMPConfig; - - fn clear_mpu(&self) { - // We want to disable all of the hardware entries, so we use `NUM_REGIONS` here, - // and not `NUM_REGIONS / 2`. - // - // We want to keep the first region configured, so it is excluded from the loops and - // set separately. - for x in 1..(MAX_AVAILABLE_REGIONS_OVER_TWO * 2) { - csr::CSR.pmpaddr_set(x, 0x0); - } - for x in 1..(MAX_AVAILABLE_REGIONS_OVER_TWO * 2 / 4) { - csr::CSR.pmpconfig_set(x, 0); - } - csr::CSR.pmpaddr_set(0, 0xFFFF_FFFF); - // enable R W X fields - csr::CSR.pmpconfig_set( - 0, - (csr::pmpconfig::pmpcfg::r0::SET - + csr::pmpconfig::pmpcfg::w0::SET - + csr::pmpconfig::pmpcfg::x0::SET - + csr::pmpconfig::pmpcfg::a0::TOR) - .value, - ); - // PMP is not configured for any process now - self.last_configured_for.take(); - } - - fn enable_app_mpu(&self) {} - - fn disable_app_mpu(&self) { - for i in 0..self.number_total_regions() { - if self.locked_region_mask.get() & (1 << i) > 0 { - continue; - } - match i % 2 { - 0 => { - csr::CSR.pmpconfig_modify(i / 2, csr::pmpconfig::pmpcfg::a1::OFF); - } - 1 => { - csr::CSR.pmpconfig_modify(i / 2, csr::pmpconfig::pmpcfg::a3::OFF); - } - _ => break, - }; - } - } - - fn number_total_regions(&self) -> usize { - self.num_regions / 2 - } - - fn allocate_region( - &self, - unallocated_memory_start: *const u8, - unallocated_memory_size: usize, - min_region_size: usize, - permissions: mpu::Permissions, - config: &mut Self::MpuConfig, - ) -> Option { - for region in config.regions.iter() { - if region.is_some() { - if region - .unwrap() - .overlaps(unallocated_memory_start, unallocated_memory_size) - { - return None; - } - } - } - - let region_num = config.unused_region_number(self.locked_region_mask.get())?; - - // Logical region - let mut start = unallocated_memory_start as usize; - let mut size = min_region_size; - - // Region start always has to align to 4 bytes - if start % 4 != 0 { - start += 4 - (start % 4); - } - - // Region size always has to align to 4 bytes - if size % 4 != 0 { - size += 4 - (size % 4); - } - - // Regions must be at least 8 bytes - if size < 8 { - size = 8; - } - - let region = PMPRegion::new_app(start as *const u8, size, permissions); - - if region.is_none() { - return None; - } - - config.regions[region_num] = region; - config.is_dirty.set(true); - - Some(mpu::Region::new(start as *const u8, size)) - } - - fn remove_memory_region( - &self, - region: mpu::Region, - config: &mut Self::MpuConfig, - ) -> Result<(), ()> { - let (index, _r) = config - .regions - .iter() - .enumerate() - .find(|(_idx, r)| r.map_or(false, |r| r == region)) - .ok_or(())?; - - if config.is_index_locked_or_app(self.locked_region_mask.get(), index) { - return Err(()); - } - - config.regions[index] = None; - config.is_dirty.set(true); - - Ok(()) - } - - fn allocate_app_memory_region( - &self, - unallocated_memory_start: *const u8, - unallocated_memory_size: usize, - min_memory_size: usize, - initial_app_memory_size: usize, - initial_kernel_memory_size: usize, - permissions: mpu::Permissions, - config: &mut Self::MpuConfig, - ) -> Option<(*const u8, usize)> { - // Check that no previously allocated regions overlap the unallocated memory. - for region in config.regions.iter() { - if region.is_some() { - if region - .unwrap() - .overlaps(unallocated_memory_start, unallocated_memory_size) - { - return None; - } - } - } - - let region_num = if config.app_memory_region.is_some() { - config.app_memory_region.unwrap_or(0) - } else { - config.unused_region_number(self.locked_region_mask.get())? - }; - - // App memory size is what we actual set the region to. So this region - // has to be aligned to 4 bytes. - let mut initial_app_memory_size: usize = initial_app_memory_size; - if initial_app_memory_size % 4 != 0 { - initial_app_memory_size += 4 - (initial_app_memory_size % 4); - } - - // Make sure there is enough memory for app memory and kernel memory. - let mut region_size = cmp::max( - min_memory_size, - initial_app_memory_size + initial_kernel_memory_size, - ) as usize; - - // Region size always has to align to 4 bytes - if region_size % 4 != 0 { - region_size += 4 - (region_size % 4); - } - - // The region should start as close as possible to the start of the unallocated memory. - let region_start = unallocated_memory_start as usize; - - // Make sure the region fits in the unallocated memory. - if region_start + region_size - > (unallocated_memory_start as usize) + unallocated_memory_size - { - return None; - } - - let region = PMPRegion::new_app( - region_start as *const u8, - initial_app_memory_size, - permissions, - ); - - if region.is_none() { - return None; - } - - config.regions[region_num] = region; - - config.app_memory_region.set(region_num); - config.is_dirty.set(true); - - Some((region_start as *const u8, region_size)) - } - - fn update_app_memory_region( - &self, - app_memory_break: *const u8, - kernel_memory_break: *const u8, - permissions: mpu::Permissions, - config: &mut Self::MpuConfig, - ) -> Result<(), ()> { - let region_num = config.app_memory_region.unwrap_or(0); - - let (region_start, _) = match config.regions[region_num] { - Some(region) => region.location(), - None => { - // Error: Process tried to update app memory MPU region before it was created. - return Err(()); - } - }; - - let app_memory_break = app_memory_break as usize; - let kernel_memory_break = kernel_memory_break as usize; - - // Out of memory - if app_memory_break > kernel_memory_break { - return Err(()); - } - - // Get size of updated region - let region_size = app_memory_break - region_start as usize; - - let region = PMPRegion::new_app(region_start as *const u8, region_size, permissions); - - if region.is_none() { - return Err(()); - } - - config.regions[region_num] = region; - config.is_dirty.set(true); - - Ok(()) - } - - fn configure_mpu(&self, config: &Self::MpuConfig, app_id: &ProcessId) { - // Is the PMP already configured for this app? - let last_configured_for_this_app = self - .last_configured_for - .map_or(false, |last_app_id| last_app_id == app_id); - - if !last_configured_for_this_app || config.is_dirty.get() { - for (x, region) in config.regions.iter().enumerate() { - match region { - Some(r) => { - let cfg_val = r.cfg.value as usize; - let start = r.location.0 as usize; - let size = r.location.1; - - let disable_val = (csr::pmpconfig::pmpcfg::r0::CLEAR - + csr::pmpconfig::pmpcfg::w0::CLEAR - + csr::pmpconfig::pmpcfg::x0::CLEAR - + csr::pmpconfig::pmpcfg::a0::CLEAR) - .value; - let (region_shift, other_region_mask) = if x % 2 == 0 { - (0, 0xFFFF_0000) - } else { - (16, 0x0000_FFFF) - }; - // Fully clear the PMP config - csr::CSR.pmpconfig_set( - x / 2, - (disable_val << region_shift) - | (csr::CSR.pmpconfig_get(x / 2) & other_region_mask), - ); - - // Set the address *before* we enable the config - // Otherwise this could take effect and block the kernel from running - csr::CSR.pmpaddr_set(x * 2, (start) >> 2); - csr::CSR.pmpaddr_set((x * 2) + 1, (start + size) >> 2); - - // Enable the configs - csr::CSR.pmpconfig_set( - x / 2, - (cfg_val << 8) << region_shift | (csr::CSR.pmpconfig_get(x / 2)), - ); - } - None => {} - }; - } - } else { - // We were last configured for this app, just re-enable - for (x, region) in config.regions.iter().enumerate() { - match region { - Some(_r) => { - match x % 2 { - 0 => { - csr::CSR.pmpconfig_modify(x / 2, csr::pmpconfig::pmpcfg::a1::TOR); - } - 1 => { - csr::CSR.pmpconfig_modify(x / 2, csr::pmpconfig::pmpcfg::a3::TOR); - } - _ => break, - }; - } - None => {} - }; - } - } - - config.is_dirty.set(false); - self.last_configured_for.put(*app_id); - } -} - -impl kernel::platform::mpu::KernelMPU - for PMP -{ - type KernelMpuConfig = PMPConfig; - - fn allocate_kernel_region( - &self, - memory_start: *const u8, - memory_size: usize, - permissions: mpu::Permissions, - config: &mut Self::KernelMpuConfig, - ) -> Option { - for region in config.regions.iter() { - if region.is_some() { - if region.unwrap().overlaps(memory_start, memory_size) { - return None; - } - } - } - - let region_num = config.unused_kernel_region_number(self.locked_region_mask.get())?; - - // Logical region - let mut start = memory_start as usize; - let mut size = memory_size; - - // Region start always has to align to 4 bytes - if start % 4 != 0 { - start += 4 - (start % 4); - } - - // Region size always has to align to 4 bytes - if size % 4 != 0 { - size += 4 - (size % 4); - } - - // Regions must be at least 8 bytes - if size < 8 { - size = 8; - } - - let region = PMPRegion::new_kernel(start as *const u8, size, permissions); - - if region.is_none() { - return None; - } - - config.regions[region_num] = region; - - // Mark the region as locked so that the app PMP doesn't use it. - let mut mask = self.locked_region_mask.get(); - mask |= 1 << region_num; - self.locked_region_mask.set(mask); - - Some(mpu::Region::new(start as *const u8, size)) - } - - fn enable_kernel_mpu(&self, config: &mut Self::KernelMpuConfig) { - for (i, region) in config.regions.iter().rev().enumerate() { - let x = MAX_AVAILABLE_REGIONS_OVER_TWO - i - 1; - match region { - Some(r) => { - let cfg_val = r.cfg.value as usize; - let start = r.location.0 as usize; - let size = r.location.1; - - match x % 2 { - 0 => { - csr::CSR.pmpaddr_set((x * 2) + 1, (start + size) >> 2); - // Disable access up to the start address - csr::CSR.pmpconfig_modify( - x / 2, - csr::pmpconfig::pmpcfg::r0::CLEAR - + csr::pmpconfig::pmpcfg::w0::CLEAR - + csr::pmpconfig::pmpcfg::x0::CLEAR - + csr::pmpconfig::pmpcfg::a0::CLEAR, - ); - csr::CSR.pmpaddr_set(x * 2, start >> 2); - - // Set access to end address - csr::CSR - .pmpconfig_set(x / 2, cfg_val << 8 | csr::CSR.pmpconfig_get(x / 2)); - } - 1 => { - csr::CSR.pmpaddr_set((x * 2) + 1, (start + size) >> 2); - // Disable access up to the start address - csr::CSR.pmpconfig_modify( - x / 2, - csr::pmpconfig::pmpcfg::r2::CLEAR - + csr::pmpconfig::pmpcfg::w2::CLEAR - + csr::pmpconfig::pmpcfg::x2::CLEAR - + csr::pmpconfig::pmpcfg::a2::CLEAR, - ); - csr::CSR.pmpaddr_set(x * 2, start >> 2); - - // Set access to end address - csr::CSR.pmpconfig_set( - x / 2, - cfg_val << 24 | csr::CSR.pmpconfig_get(x / 2), - ); - } - _ => break, - } - } - None => {} - }; - } - - // Set the Machine Mode Lockdown (mseccfg.MML) bit. - // This is a sticky bit, meaning that once set it cannot be unset - // until a hard reset. - csr::CSR.mseccfg.modify(csr::mseccfg::mseccfg::mml::SET); - } -} diff --git a/arch/rv32i/src/lib.rs b/arch/rv32i/src/lib.rs index 7f710a213b..489aa82aaa 100644 --- a/arch/rv32i/src/lib.rs +++ b/arch/rv32i/src/lib.rs @@ -1,16 +1,21 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Support for the 32-bit RISC-V architecture. #![crate_name = "rv32i"] #![crate_type = "rlib"] -#![feature(asm, asm_sym, const_fn_trait_bound, naked_functions)] #![no_std] use core::fmt::Write; +#[cfg(all(target_arch = "riscv32", target_os = "none"))] +use core::arch::global_asm; + use kernel::utilities::registers::interfaces::{Readable, Writeable}; pub mod clic; -pub mod epmp; pub mod machine_timer; pub mod pmp; pub mod support; @@ -22,8 +27,9 @@ pub use riscv::csr; extern "C" { // Where the end of the stack region is (and hence where the stack should - // start). + // start), and the start of the stack region. static _estack: usize; + static _sstack: usize; // Boundaries of the .bss section. static mut _szero: usize; @@ -37,26 +43,31 @@ extern "C" { static mut _erelocate: usize; // The global pointer, value set in the linker script + #[link_name = "__global_pointer$"] static __global_pointer: usize; } -/// Entry point of all programs (`_start`). -/// -/// This assembly does three functions: -/// -/// 1. It initializes the stack pointer, the frame pointer (needed for closures -/// to work in start_rust) and the global pointer. -/// 2. It initializes the .bss and .data RAM segments. This must be done before -/// any Rust code runs. See https://github.com/tock/tock/issues/2222 for more -/// information. -/// 3. Finally it calls `main()`, the main entry point for Tock boards. #[cfg(all(target_arch = "riscv32", target_os = "none"))] -#[link_section = ".riscv.start"] -#[export_name = "_start"] -#[naked] -pub extern "C" fn _start() { - unsafe { - asm! (" +extern "C" { + // Entry point of all programs (`_start`). + /// + /// This assembly does three functions: + /// + /// 1. It initializes the stack pointer, the frame pointer (needed for closures + /// to work in start_rust) and the global pointer. + /// 2. It initializes the .bss and .data RAM segments. This must be done before + /// any Rust code runs. See for more + /// information. + /// 3. Finally it calls `main()`, the main entry point for Tock boards. + pub fn _start(); +} + +#[cfg(all(target_arch = "riscv32", target_os = "none"))] +global_asm! (" + .section .riscv.start, \"ax\" + .globl _start + _start: + // Set the global pointer register using the variable defined in the // linker script. This register is only set once. The global pointer // is a method for sharing state between the linker and the CPU so @@ -67,20 +78,26 @@ pub extern "C" fn _start() { // https://groups.google.com/a/groups.riscv.org/forum/#!msg/sw-dev/60IdaZj27dY/5MydPLnHAQAJ // https://www.sifive.com/blog/2017/08/28/all-aboard-part-3-linker-relaxation-in-riscv-toolchain/ // - lui gp, %hi({gp}$) // Set the global pointer. - addi gp, gp, %lo({gp}$) // Value set in linker script. + // Disable linker relaxation for code that sets up GP so that this doesn't + // get turned into `mv gp, gp`. + .option push + .option norelax + + la gp, {gp} // Set the global pointer from linker script. + + // Re-enable linker relaxations. + .option pop // Initialize the stack pointer register. This comes directly from // the linker script. - lui sp, %hi({estack}) // Set the initial stack pointer. - addi sp, sp, %lo({estack}) // Value from the linker script. + la sp, {estack} // Set the initial stack pointer. // Set s0 (the frame pointer) to the start of the stack. - add s0, sp, zero + add s0, sp, zero // s0 = sp // Initialize mscratch to 0 so that we know that we are currently // in the kernel. This is used for the check in the trap handler. - csrw 0x340, zero // CSR=0x340=mscratch + csrw 0x340, zero // CSR=0x340=mscratch // INITIALIZE MEMORY @@ -120,17 +137,14 @@ pub extern "C" fn _start() { // code, likely defined in a board's main.rs. j main ", - gp = sym __global_pointer, - estack = sym _estack, - sbss = sym _szero, - ebss = sym _ezero, - sdata = sym _srelocate, - edata = sym _erelocate, - etext = sym _etext, - options(noreturn) - ); - } -} +gp = sym __global_pointer, +estack = sym _estack, +sbss = sym _szero, +ebss = sym _ezero, +sdata = sym _srelocate, +edata = sym _erelocate, +etext = sym _etext, +); /// The various privilege levels in RISC-V. pub enum PermissionMode { @@ -140,125 +154,180 @@ pub enum PermissionMode { Machine = 0x3, } -/// Tell the MCU what address the trap handler is located at. +/// Tell the MCU what address the trap handler is located at, and initialize +/// `mscratch` to zero, indicating kernel execution. /// /// This is a generic implementation. There may be board specific versions as /// some platforms have added more bits to the `mtvec` register. /// /// The trap handler is called on exceptions and for interrupts. -pub unsafe fn configure_trap_handler(mode: PermissionMode) { - match mode { - PermissionMode::Machine => csr::CSR.mtvec.write( - csr::mtvec::mtvec::trap_addr.val(_start_trap as usize >> 2) - + csr::mtvec::mtvec::mode::CLEAR, - ), - PermissionMode::Supervisor => csr::CSR.stvec.write( - csr::stvec::stvec::trap_addr.val(_start_trap as usize >> 2) - + csr::stvec::stvec::mode::CLEAR, - ), - PermissionMode::User => csr::CSR.utvec.write( - csr::utvec::utvec::trap_addr.val(_start_trap as usize >> 2) - + csr::utvec::utvec::mode::CLEAR, - ), - PermissionMode::Reserved => ( - // TODO some sort of error handling? - ), - } +pub unsafe fn configure_trap_handler() { + // Indicate to the trap handler that we are executing kernel code. + csr::CSR.mscratch.set(0); + + // Set the machine-mode trap handler. By not configuing an S-mode or U-mode + // trap handler, this should ensure that all traps are handled by the M-mode + // handler. + csr::CSR.mtvec.write( + csr::mtvec::mtvec::trap_addr.val(_start_trap as usize >> 2) + + csr::mtvec::mtvec::mode::CLEAR, + ); } // Mock implementation for tests on Travis-CI. -#[cfg(not(any(target_arch = "riscv32", target_os = "none")))] +#[cfg(not(all(target_arch = "riscv32", target_os = "none")))] pub extern "C" fn _start_trap() { unimplemented!() } -/// This is the trap handler function. This code is called on all traps, -/// including interrupts, exceptions, and system calls from applications. -/// -/// Tock uses only the single trap handler, and does not use any vectored -/// interrupts or other exception handling. The trap handler has to determine -/// why the trap handler was called, and respond accordingly. Generally, there -/// are two reasons the trap handler gets called: an interrupt occurred or an -/// application called a syscall. -/// -/// In the case of an interrupt while the kernel was executing we only need to -/// save the kernel registers and then run whatever interrupt handling code we -/// need to. If the trap happens while and application was executing, we have to -/// save the application state and then resume the `switch_to()` function to -/// correctly return back to the kernel. #[cfg(all(target_arch = "riscv32", target_os = "none"))] -#[link_section = ".riscv.trap"] -#[export_name = "_start_trap"] -#[naked] -pub extern "C" fn _start_trap() { - unsafe { - asm!( - " - // The first thing we have to do is determine if we came from user - // mode or kernel mode, as we need to save state and proceed - // differently. We cannot, however, use any registers because we do - // not want to lose their contents. So, we rely on `mscratch`. If - // mscratch is 0, then we came from the kernel. If it is >0, then it - // contains the kernel's stack pointer and we came from an app. - // - // We use the csrrw instruction to save the current stack pointer - // so we can retrieve it if necessary. - // - // If we could enter this trap handler twice (for example, - // handling an interrupt while an exception is being - // handled), storing a non-zero value in mscratch - // temporarily could cause a race condition similar to the - // one of PR 2308[1]. - // However, as indicated in section 3.1.6.1 of the RISC-V - // Privileged Spec[2], MIE will be set to 0 when taking a - // trap into machine mode. Therefore, this can only happen - // when causing an exception in the trap handler itself. +extern "C" { + /// This is the trap handler function. This code is called on all traps, + /// including interrupts, exceptions, and system calls from applications. + /// + /// Tock uses only the single trap handler, and does not use any vectored + /// interrupts or other exception handling. The trap handler has to + /// determine why the trap handler was called, and respond + /// accordingly. Generally, there are two reasons the trap handler gets + /// called: an interrupt occurred or an application called a syscall. + /// + /// In the case of an interrupt while the kernel was executing we only need + /// to save the kernel registers and then run whatever interrupt handling + /// code we need to. If the trap happens while an application was executing, + /// we have to save the application state and then resume the `switch_to()` + /// function to correctly return back to the kernel. + /// + /// We implement this distinction through a branch on the value of the + /// `mscratch` CSR. If, at the time the trap was taken, it contains `0`, we + /// assume that the hart is currently executing kernel code. + /// + /// If it contains any other value, we interpret it to be a memory address + /// pointing to a particular data structure: + /// + /// ```text + /// mscratch 0 1 2 3 + /// \->|--------------------------------------------------------------| + /// | scratch word, overwritten with s1 register contents | + /// |--------------------------------------------------------------| + /// | trap handler address, continue trap handler execution here | + /// |--------------------------------------------------------------| + /// ``` + /// + /// Thus, process implementations can define their own strategy for how + /// traps should be handled when they occur during process execution. This + /// global trap handler behavior is well defined. It will: + /// + /// 1. atomically swap s0 and the mscratch CSR, + /// + /// 2. execute the default kernel trap handler if s0 now contains `0` + /// (meaning that the mscratch CSR contained `0` before entering this trap + /// handler), + /// + /// 3. otherwise, save s1 to `0*4(s0)`, and finally + /// + /// 4. load the address at `1*4(s0)` into s1, and jump to it. + /// + /// No registers other than s0, s1 and the mscratch CSR are to be clobbered + /// before continuing execution at the address loaded into the mscratch CSR + /// or the _start_kernel_trap kernel trap handler. Execution with these + /// second-stage trap handlers must continue in the same trap handler + /// context as originally invoked by the trap (e.g., the global trap handler + /// will not execute an mret instruction). It will not modify CSRs that + /// contain information on the trap source or the system state prior to + /// entering the trap handler. + /// + /// We deliberately clobber callee-saved instead of caller-saved registers, + /// as this makes it easier to call other functions as part of the trap + /// handler (for example to to disable interrupts from within Rust + /// code). This global trap handler saves the previous values of these + /// clobbered registers ensuring that they can be restored later. It places + /// new values into these clobbered registers (such as the previous `s0` + /// register contents) that are required to be retained for correctly + /// returning from the trap handler, and as such need to be saved across + /// C-ABI function calls. Loading them into saved registers avoids the need + /// to manually save them across such calls. + /// + /// When a custom trap handler stack is registered in `mscratch`, the custom + /// handler is responsible for restoring the kernel trap handler (by setting + /// mscratch=0) before returning to kernel execution from the trap handler + /// context. + /// + /// If a board or chip must, for whichever reason, use a different global + /// trap handler, it should abide to the above contract and emulate its + /// behavior for all traps and interrupts that are required to be handled by + /// the respective kernel or other trap handler as registered in mscratch. + /// + /// For instance, a chip that does not support non-vectored trap handlers + /// can register a vectored trap handler that routes each trap source to + /// this global trap handler. + /// + /// Alternatively, a board can be allowed to ignore certain traps or + /// interrupts, some or all of the time, provided they are not vital to + /// Tock's execution. These boards may choose to register an alternative + /// handler for some or all trap sources. When this alternative handler is + /// invoked, it may, for instance, choose to ignore a certain trap, access + /// global state (subject to synchronization), etc. It must still abide to + /// the contract as stated above. + pub fn _start_trap(); +} + +#[cfg(all(target_arch = "riscv32", target_os = "none"))] +global_asm!( + " + .section .riscv.trap, \"ax\" + .globl _start_trap + _start_trap: + // This is the global trap handler. By default, Tock expects this + // trap handler to be registered at all times, and that all traps + // and interrupts occurring in all modes of execution (M-, S-, and + // U-mode) will cause this trap handler to be executed. // - // [1] https://github.com/tock/tock/pull/2308 - // [2] https://github.com/riscv/riscv-isa-manual/releases/download/draft-20201222-42dc13a/riscv-privileged.pdf - csrrw sp, 0x340, sp // CSR=0x340=mscratch - bnez sp, 300f // If sp != 0 then we must have come from an app. + // For documentation of its behavior, and how process + // implementations can hook their own trap handler code, see the + // comment on the `extern C _start_trap` symbol above. + // Atomically swap s0 and mscratch: + csrrw s0, mscratch, s0 // s0 = mscratch; mscratch = s0 - // _from_kernel: - // Swap back the zero value for the stack pointer in mscratch - csrrw sp, 0x340, sp // CSR=0x340=mscratch + // If mscratch contained 0, invoke the kernel trap handler. + beq s0, x0, 100f // if s0==x0: goto 100 - // Now, since we want to use the stack to save kernel registers, we + // Else, save the current value of s1 to `0*4(s0)`, load `1*4(s0)` + // into s1 and jump to it (invoking a custom trap handler). + sw s1, 0*4(s0) // *s0 = s1 + lw s1, 1*4(s0) // s1 = *(s0+4) + jr s1 // goto s1 + + 100: // _start_kernel_trap + + // The global trap handler has swapped s0 into mscratch. We can thus + // freely clobber s0 without losing any information. + // + // Since we want to use the stack to save kernel registers, we // first need to make sure that the trap wasn't the result of a // stack overflow, in which case we can't use the current stack - // pointer. We also, however, cannot modify any of the current - // registers until we save them, and we cannot save them to the - // stack until we know the stack is valid. So, we use the mscratch - // trick again to get one register we can use. - - // Save t0's contents to mscratch - csrw 0x340, t0 // CSR=0x340=mscratch + // pointer. Use s0 as a scratch register: // Load the address of the bottom of the stack (`_sstack`) into our - // newly freed-up t0 register. - lui t0, %hi(_sstack) // t0 = _sstack - addi t0, t0, %lo(_sstack) + // newly freed-up s0 register. + la s0, {sstack} // s0 = _sstack // Compare the kernel stack pointer to the bottom of the stack. If // the stack pointer is above the bottom of the stack, then continue // handling the fault as normal. - bgtu sp, t0, 100f // branch if sp > t0 + bgtu sp, s0, 200f // branch if sp > s0 // If we get here, then we did encounter a stack overflow. We are // going to panic at this point, but for that to work we need a // valid stack to run the panic code. We do this by just starting // over with the kernel stack and placing the stack pointer at the // top of the original stack. - lui sp, %hi(_estack) // sp = _estack - addi sp, sp, %lo(_estack) - + la sp, {estack} // sp = _estack - 100: // _from_kernel_continue + 200: // _start_kernel_trap_continue - // Restore t0, and make sure mscratch is set back to 0 (our flag - // tracking that the kernel is executing). - csrrw t0, 0x340, zero // t0=mscratch, mscratch=0 + // Restore s0. We reset mscratch to 0 (kernel trap handler mode) + csrrw s0, mscratch, zero // s0 = mscratch; mscratch = 0 // Make room for the caller saved registers we need to restore after // running any trap handler code. @@ -312,135 +381,29 @@ pub extern "C" fn _start_trap() { // mepc and execution proceeds from there. Since we did not modify // mepc we will return to where the exception occurred. mret - - - - // Handle entering the trap handler from an app differently. - 300: // _from_app - - // At this point all we know is that we entered the trap handler - // from an app. We don't know _why_ we got a trap, it could be from - // an interrupt, syscall, or fault (or maybe something else). - // Therefore we have to be very careful not to overwrite any - // registers before we have saved them. - // - // We ideally want to save registers in the per-process stored state - // struct. However, we don't have a pointer to that yet, and we need - // to use a temporary register to get that address. So, we save s0 - // to the kernel stack before we can it to the proper spot. - sw s0, 0*4(sp) - - // Ideally it would be better to save all of the app registers once - // we return back to the `switch_to_process()` code. However, we - // also potentially need to disable an interrupt in case the app was - // interrupted, so it is safer to just immediately save all of the - // app registers. - // - // We do this by retrieving the stored state pointer from the kernel - // stack and storing the necessary values in it. - lw s0, 1*4(sp) // Load the stored state pointer into s0. - sw x1, 0*4(s0) // ra - sw x3, 2*4(s0) // gp - sw x4, 3*4(s0) // tp - sw x5, 4*4(s0) // t0 - sw x6, 5*4(s0) // t1 - sw x7, 6*4(s0) // t2 - sw x9, 8*4(s0) // s1 - sw x10, 9*4(s0) // a0 - sw x11, 10*4(s0) // a1 - sw x12, 11*4(s0) // a2 - sw x13, 12*4(s0) // a3 - sw x14, 13*4(s0) // a4 - sw x15, 14*4(s0) // a5 - sw x16, 15*4(s0) // a6 - sw x17, 16*4(s0) // a7 - sw x18, 17*4(s0) // s2 - sw x19, 18*4(s0) // s3 - sw x20, 19*4(s0) // s4 - sw x21, 20*4(s0) // s5 - sw x22, 21*4(s0) // s6 - sw x23, 22*4(s0) // s7 - sw x24, 23*4(s0) // s8 - sw x25, 24*4(s0) // s9 - sw x26, 25*4(s0) // s10 - sw x27, 26*4(s0) // s11 - sw x28, 27*4(s0) // t3 - sw x29, 28*4(s0) // t4 - sw x30, 29*4(s0) // t5 - sw x31, 30*4(s0) // t6 - // Now retrieve the original value of s0 and save that as well. - lw t0, 0*4(sp) - sw t0, 7*4(s0) // s0,fp - - // We also need to store the app stack pointer, mcause, and mepc. We - // need to store mcause because we use that to determine why the app - // stopped executing and returned to the kernel. We store mepc - // because it is where we need to return to in the app at some - // point. We need to store mtval in case the app faulted and we need - // mtval to help with debugging. - csrr t0, 0x340 // CSR=0x340=mscratch - sw t0, 1*4(s0) // Save the app sp to the stored state struct - csrr t0, 0x341 // CSR=0x341=mepc - sw t0, 31*4(s0) // Save the PC to the stored state struct - csrr t0, 0x343 // CSR=0x343=mtval - sw t0, 33*4(s0) // Save mtval to the stored state struct - - // Save mcause last, as we depend on it being loaded in t0 below - csrr t0, 0x342 // CSR=0x342=mcause - sw t0, 32*4(s0) // Save mcause to the stored state struct, leave in t0 - - // Now we need to check if this was an interrupt, and if it was, - // then we need to disable the interrupt before returning from this - // trap handler so that it does not fire again. If mcause is greater - // than or equal to zero this was not an interrupt (i.e. the most - // significant bit is not 1). - bge t0, zero, 200f - // Copy mcause into a0 and then call the interrupt disable function. - mv a0, t0 - jal ra, _disable_interrupt_trap_rust_from_app - - 200: // _from_app_continue - // Now determine the address of _return_to_kernel and resume the - // context switching code. We need to load _return_to_kernel into - // mepc so we can use it to return to the context switch code. - lw t0, 2*4(sp) // Load _return_to_kernel into t0. - csrw 0x341, t0 // CSR=0x341=mepc - - // Ensure that mscratch is 0. This makes sure that we know that on - // a future trap that we came from the kernel. - csrw 0x340, zero // CSR=0x340=mscratch - - // Need to set mstatus.MPP to 0b11 so that we stay in machine mode. - csrr t0, 0x300 // CSR=0x300=mstatus - li t1, 0x1800 // Load 0b11 to the MPP bits location in t1 - or t0, t0, t1 // Set the MPP bits to one - csrw 0x300, t0 // CSR=0x300=mstatus - - // Use mret to exit the trap handler and return to the context - // switching code. - mret - ", - options(noreturn) - ); - } -} + ", + estack = sym _estack, + sstack = sym _sstack, +); /// RISC-V semihosting needs three exact instructions in uncompressed form. /// -/// See https://github.com/riscv/riscv-semihosting-spec/blob/main/riscv-semihosting-spec.adoc#11-semihosting-trap-instruction-sequence -/// for more details on the three insturctions. +/// See +/// for more details on the three instructions. /// /// In order to work with semihosting we include the assembly here /// where we are able to disable compressed instruction support. This /// follows the example used in the Linux kernel: -/// https://elixir.bootlin.com/linux/v5.12.10/source/arch/riscv/include/asm/jump_label.h#L21 +/// /// as suggested by the RISC-V developers: -/// https://groups.google.com/a/groups.riscv.org/g/isa-dev/c/XKkYacERM04/m/CdpOcqtRAgAJ +/// #[cfg(all(target_arch = "riscv32", target_os = "none"))] pub unsafe fn semihost_command(command: usize, arg0: usize, arg1: usize) -> usize { + use core::arch::asm; let res; asm!( " + .balign 16 .option push .option norelax .option norvc @@ -458,7 +421,7 @@ pub unsafe fn semihost_command(command: usize, arg0: usize, arg1: usize) -> usiz } // Mock implementation for tests on Travis-CI. -#[cfg(not(any(target_arch = "riscv32", target_os = "none")))] +#[cfg(not(all(target_arch = "riscv32", target_os = "none")))] pub unsafe fn semihost_command(_command: usize, _arg0: usize, _arg1: usize) -> usize { unimplemented!() } diff --git a/arch/rv32i/src/machine_timer.rs b/arch/rv32i/src/machine_timer.rs index 1f649b7e1c..913218bf6b 100644 --- a/arch/rv32i/src/machine_timer.rs +++ b/arch/rv32i/src/machine_timer.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! RISC-V Generic Machine Timer use kernel::hil::time::{Ticks, Ticks64}; diff --git a/arch/rv32i/src/pmp.rs b/arch/rv32i/src/pmp.rs index b1f23cc718..606d4a055b 100644 --- a/arch/rv32i/src/pmp.rs +++ b/arch/rv32i/src/pmp.rs @@ -1,28 +1,27 @@ -//! Implementation of the physical memory protection unit (PMP). -//! -//! ## Implementation -//! -//! We use the PMP Top of Region (TOR) alignment as there are alignment issues -//! with NAPOT. NAPOT would allow us to protect more memory regions (with NAPOT -//! each PMP region can be a memory region), but the problem with NAPOT is the -//! address must be aligned to the size, which results in wasted memory. To -//! avoid this wasted memory we use TOR and each memory region uses two physical -//! PMP regions. +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. use core::cell::Cell; -use core::cmp; -use core::fmt; +use core::num::NonZeroUsize; +use core::ops::Range; +use core::{cmp, fmt}; + +use kernel::platform::mpu; use kernel::utilities::cells::OptionalCell; +use kernel::utilities::registers::{register_bitfields, LocalRegisterCopy}; use crate::csr; -use kernel::platform::mpu; -use kernel::utilities::cells::MapCell; -use kernel::utilities::registers::{self, register_bitfields}; -use kernel::ProcessId; -// Generic PMP config register_bitfields![u8, - pub pmpcfg [ + /// Generic `pmpcfg` octet. + /// + /// A PMP entry is configured through `pmpaddrX` and `pmpcfgX` CSRs, where a + /// single `pmpcfgX` CSRs holds multiple octets, each affecting the access + /// permission, addressing mode and "lock" attributes of a single `pmpaddrX` + /// CSR. This bitfield definition represents a single, `u8`-backed `pmpcfg` + /// octet affecting a single `pmpaddr` entry. + pub pmpcfg_octet [ r OFFSET(0) NUMBITS(1) [], w OFFSET(1) NUMBITS(1) [], x OFFSET(2) NUMBITS(1) [], @@ -36,289 +35,584 @@ register_bitfields![u8, ] ]; -/// Main PMP struct. -/// -/// Tock will ignore locked PMP regions. Note that Tock will not make any -/// attempt to avoid access faults from locked regions. +/// A `pmpcfg` octet for a user-mode (non-locked) TOR-addressed PMP region. /// -/// `MAX_AVAILABLE_REGIONS_OVER_TWO`: The number of PMP regions divided by 2. -/// The RISC-V spec mandates that there must be either 0, 16 or 64 PMP -/// regions implemented. If you are using this PMP struct we are assuming -/// there are more than 0 implemented. So this value should be either 8 or 32. +/// This is a wrapper around a [`pmpcfg_octet`] (`u8`) register type, which +/// guarantees that the wrapped `pmpcfg` octet is always set to be either +/// [`TORUserPMPCFG::OFF`] (set to `0x00`), or in a non-locked, TOR-addressed +/// configuration. /// -/// If however you know the exact number of PMP regions implemented by your -/// platform and it's not going to change you can just specify the number. -/// This means that Tock won't be able to dynamically handle more regions, -/// but it will reduce runtime space requirements. -/// Note: that this does not mean all PMP regions are connected. -/// Some of the regions can be WARL (Write Any Read Legal). All this means -/// is that accessing `NUM_REGIONS` won't cause a fault. -pub struct PMP { - /// The application that the MPU was last configured for. Used (along with - /// the `is_dirty` flag) to determine if MPU can skip writing the - /// configuration to hardware. - last_configured_for: MapCell, - /// This is a 64-bit mask of locked regions. - /// Each bit that is set in this mask indicates that the region is locked - /// and cannot be used by Tock. - locked_region_mask: Cell, - /// This is the total number of avaliable regions. - /// This will be between 0 and MAX_AVAILABLE_REGIONS_OVER_TWO * 2 depending - /// on the hardware and previous boot stages. - num_regions: usize, -} - -impl PMP { - pub unsafe fn new() -> Self { - // RISC-V PMP can support from 0 to 64 PMP regions - // Let's figure out how many are supported. - // We count any regions that are locked as unsupported - let mut num_regions = 0; - let mut locked_region_mask = 0; - - for i in 0..(MAX_AVAILABLE_REGIONS_OVER_TWO * 2) { - // Read the current value - let pmpcfg_og = csr::CSR.pmpconfig_get(i / 4); - - // Flip R, W, X bits - let pmpcfg_new = pmpcfg_og ^ (3 << ((i % 4) * 8)); - csr::CSR.pmpconfig_set(i / 4, pmpcfg_new); - - // Check if the bits are set - let pmpcfg_check = csr::CSR.pmpconfig_get(i / 4); - - // Check if the changes stuck - if pmpcfg_check == pmpcfg_og { - // If we get here then our changes didn't stick, let's figure - // out why - - // Check if the locked bit is set - if pmpcfg_og & ((1 << 7) << ((i % 4) * 8)) > 0 { - // The bit is locked. Mark this regions as not usable - locked_region_mask |= 1 << i; - } else { - // The locked bit isn't set - // This region must not be connected, which means we have run out - // of usable regions, break the loop - break; - } - } else { - // Found a working region - num_regions += 1; - } - - // Reset back to how we found it - csr::CSR.pmpconfig_set(i / 4, pmpcfg_og); - } - - Self { - last_configured_for: MapCell::empty(), - num_regions, - locked_region_mask: Cell::new(locked_region_mask), - } +/// By accepting this type, PMP implements can rely on the above properties to +/// hold by construction and avoid runtime checks. For example, this type is +/// used in the [`TORUserPMP::configure_pmp`] method. +#[derive(Copy, Clone, Debug)] +pub struct TORUserPMPCFG(LocalRegisterCopy); + +impl TORUserPMPCFG { + pub const OFF: TORUserPMPCFG = TORUserPMPCFG(LocalRegisterCopy::new(0)); + + /// Extract the `u8` representation of the [`pmpcfg_octet`] register. + pub fn get(&self) -> u8 { + self.0.get() } -} -/// Struct storing configuration for a RISC-V PMP region. -#[derive(Copy, Clone)] -pub struct PMPRegion { - location: (*const u8, usize), - cfg: registers::FieldValue, -} - -impl PartialEq for PMPRegion { - fn eq(&self, other: &mpu::Region) -> bool { - self.location.0 == other.start_address() && self.location.1 == other.size() + /// Extract a copy of the contained [`pmpcfg_octet`] register. + pub fn get_reg(&self) -> LocalRegisterCopy { + self.0 } } -impl fmt::Display for PMPRegion { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - fn bit_str<'a>(reg: &PMPRegion, bit: u8, on_str: &'a str, off_str: &'a str) -> &'a str { - match reg.cfg.value & bit as u8 { - 0 => off_str, - _ => on_str, - } - } - - write!( - f, - "addr={:p}, size={:#010X}, cfg={:#X} ({}{}{})", - self.location.0, - self.location.1, - u8::from(self.cfg), - bit_str(self, pmpcfg::r::SET.value, "r", "-"), - bit_str(self, pmpcfg::w::SET.value, "w", "-"), - bit_str(self, pmpcfg::x::SET.value, "x", "-"), - ) +impl PartialEq for TORUserPMPCFG { + fn eq(&self, other: &Self) -> bool { + self.0.get() == other.0.get() } } -impl PMPRegion { - fn new(start: *const u8, size: usize, permissions: mpu::Permissions) -> PMPRegion { - // Determine access and execute permissions - let pmpcfg = match permissions { +impl Eq for TORUserPMPCFG {} + +impl From for TORUserPMPCFG { + fn from(p: mpu::Permissions) -> Self { + let fv = match p { mpu::Permissions::ReadWriteExecute => { - pmpcfg::r::SET + pmpcfg::w::SET + pmpcfg::x::SET + pmpcfg::a::TOR + pmpcfg_octet::r::SET + pmpcfg_octet::w::SET + pmpcfg_octet::x::SET } mpu::Permissions::ReadWriteOnly => { - pmpcfg::r::SET + pmpcfg::w::SET + pmpcfg::x::CLEAR + pmpcfg::a::TOR + pmpcfg_octet::r::SET + pmpcfg_octet::w::SET + pmpcfg_octet::x::CLEAR } mpu::Permissions::ReadExecuteOnly => { - pmpcfg::r::SET + pmpcfg::w::CLEAR + pmpcfg::x::SET + pmpcfg::a::TOR + pmpcfg_octet::r::SET + pmpcfg_octet::w::CLEAR + pmpcfg_octet::x::SET } mpu::Permissions::ReadOnly => { - pmpcfg::r::SET + pmpcfg::w::CLEAR + pmpcfg::x::CLEAR + pmpcfg::a::TOR + pmpcfg_octet::r::SET + pmpcfg_octet::w::CLEAR + pmpcfg_octet::x::CLEAR } mpu::Permissions::ExecuteOnly => { - pmpcfg::r::CLEAR + pmpcfg::w::CLEAR + pmpcfg::x::SET + pmpcfg::a::TOR + pmpcfg_octet::r::CLEAR + pmpcfg_octet::w::CLEAR + pmpcfg_octet::x::SET } }; - PMPRegion { - location: (start, size), - cfg: pmpcfg, + TORUserPMPCFG(LocalRegisterCopy::new( + (fv + pmpcfg_octet::l::CLEAR + pmpcfg_octet::a::TOR).value, + )) + } +} + +/// A RISC-V PMP memory region specification, configured in NAPOT mode. +/// +/// This type checks that the supplied `start` and `size` values meet the RISC-V +/// NAPOT requirements, namely that +/// +/// - the region is a power of two bytes in size +/// - the region's start address is aligned to the region size +/// - the region is at least 8 bytes long +/// +/// By accepting this type, PMP implementations can rely on these requirements +/// to be verified. Furthermore, they can use the +/// [`NAPOTRegionSpec::napot_addr`] convenience method to retrieve an `pmpaddrX` +/// CSR value encoding this region's address and length. +#[derive(Copy, Clone, Debug)] +pub struct NAPOTRegionSpec { + start: *const u8, + size: usize, +} + +impl NAPOTRegionSpec { + /// Construct a new [`NAPOTRegionSpec`] + /// + /// This method accepts a `start` address and a region length. It returns + /// `Some(region)` when all constraints specified in the + /// [`NAPOTRegionSpec`]'s documentation are satisfied, otherwise `None`. + pub fn new(start: *const u8, size: usize) -> Option { + if !size.is_power_of_two() || (start as usize) % size != 0 || size < 8 { + None + } else { + Some(NAPOTRegionSpec { start, size }) } } - fn location(&self) -> (*const u8, usize) { - self.location + /// Retrieve the start address of this [`NAPOTRegionSpec`]. + pub fn start(&self) -> *const u8 { + self.start } - fn overlaps(&self, other_start: *const u8, other_size: usize) -> bool { - let other_start = other_start as usize; - let other_end = other_start + other_size; + /// Retrieve the size of this [`NAPOTRegionSpec`]. + pub fn size(&self) -> usize { + self.size + } - let (region_start, region_size) = self.location; + /// Retrieve the end address of this [`NAPOTRegionSpec`]. + pub fn end(&self) -> *const u8 { + unsafe { self.start.add(self.size) } + } - let (region_start, region_end) = { - let region_start = region_start as usize; - let region_end = region_start + region_size; - (region_start, region_end) - }; + /// Retrieve a `pmpaddrX`-CSR compatible representation of this + /// [`NAPOTRegionSpec`]'s address and length. For this value to be valid in + /// a `CSR` register, the `pmpcfgX` octet's `A` (address mode) value + /// belonging to this `pmpaddrX`-CSR must be set to `NAPOT` (0b11). + pub fn napot_addr(&self) -> usize { + ((self.start as usize) + (self.size - 1).overflowing_shr(1).0) + .overflowing_shr(2) + .0 + } +} + +/// A RISC-V PMP memory region specification, configured in TOR mode. +/// +/// This type checks that the supplied `start` and `end` addresses meet the +/// RISC-V TOR requirements, namely that +/// +/// - the region's start address is aligned to a 4-byte boundary +/// - the region's end address is aligned to a 4-byte boundary +/// - the region is at least 4 bytes long +/// +/// By accepting this type, PMP implementations can rely on these requirements +/// to be verified. +#[derive(Copy, Clone, Debug)] +pub struct TORRegionSpec { + start: *const u8, + end: *const u8, +} - if region_start < other_end && other_start < region_end { - true +impl TORRegionSpec { + /// Construct a new [`TORRegionSpec`] + /// + /// This method accepts a `start` and `end` address. It returns + /// `Some(region)` when all constraints specified in the [`TORRegionSpec`]'s + /// documentation are satisfied, otherwise `None`. + pub fn new(start: *const u8, end: *const u8) -> Option { + if (start as usize) % 4 != 0 + || (end as usize) % 4 != 0 + || (end as usize) + .checked_sub(start as usize) + .map_or(true, |size| size < 4) + { + None } else { - false + Some(TORRegionSpec { start, end }) } } + + /// Retrieve the start address of this [`TORRegionSpec`]. + pub fn start(&self) -> *const u8 { + self.start + } + + /// Retrieve the end address of this [`TORRegionSpec`]. + pub fn end(&self) -> *const u8 { + self.end + } } -/// Struct storing region configuration for RISCV PMP. -pub struct PMPConfig { - /// Array of PMP regions. Each region requires two physical entries. - regions: [Option; MAX_AVAILABLE_REGIONS_OVER_TWO], - /// Indicates if the configuration has changed since the last time it was - /// written to hardware. - is_dirty: Cell, - /// Which region index is used for app memory (if it has been configured). - app_memory_region: OptionalCell, +/// Helper method to check if a [`PMPUserMPUConfig`] region overlaps with a +/// region specified by `other_start` and `other_size`. +/// +/// Matching the RISC-V spec this checks `pmpaddr[i-i] <= y < pmpaddr[i]` for TOR +/// ranges. +fn region_overlaps( + region: &(TORUserPMPCFG, *const u8, *const u8), + other_start: *const u8, + other_size: usize, +) -> bool { + // PMP TOR regions are not inclusive on the high end, that is + // pmpaddr[i-i] <= y < pmpaddr[i]. + // + // This happens to coincide with the definition of the Rust half-open Range + // type, which provides a convenient `.contains()` method: + let region_range = Range { + start: region.1 as usize, + end: region.2 as usize, + }; + + let other_range = Range { + start: other_start as usize, + end: other_start as usize + other_size, + }; + + // For a range A to overlap with a range B, either B's first or B's last + // element must be contained in A, or A's first or A's last element must be + // contained in B. As we deal with half-open ranges, ensure that neither + // range is empty. + // + // This implementation is simple and stupid, and can be optimized. We leave + // that as an exercise to the compiler. + !region_range.is_empty() + && !other_range.is_empty() + && (region_range.contains(&other_range.start) + || region_range.contains(&(other_range.end - 1)) + || other_range.contains(®ion_range.start) + || other_range.contains(&(region_range.end - 1))) } -impl Default - for PMPConfig -{ - /// `NUM_REGIONS` is the number of PMP entries the hardware supports. - /// - /// Since we use TOR, we will use two PMP entries for each region. So the actual - /// number of regions we can protect is `NUM_REGIONS/2`. Limitations of min_const_generics - /// require us to pass both of these values as separate generic consts. - fn default() -> Self { - PMPConfig { - regions: [None; MAX_AVAILABLE_REGIONS_OVER_TWO], - is_dirty: Cell::new(true), - app_memory_region: OptionalCell::empty(), +/// Print a table of the configured PMP regions, read from the HW CSRs. +/// +/// # Safety +/// +/// This function is unsafe, as it relies on the PMP CSRs to be accessible, and +/// the hardware to feature `PHYSICAL_ENTRIES` PMP CSR entries. If these +/// conditions are not met, calling this function can result in undefinied +/// behavior (e.g., cause a system trap). +pub unsafe fn format_pmp_entries( + f: &mut fmt::Formatter<'_>, +) -> fmt::Result { + for i in 0..PHYSICAL_ENTRIES { + // Extract the entry's pmpcfgX register value. The pmpcfgX CSRs are + // tightly packed and contain 4 octets beloging to individual + // entries. Convert this into a u8-wide LocalRegisterCopy as a generic register type, independent of the entry's + // offset. + let pmpcfg: LocalRegisterCopy = LocalRegisterCopy::new( + csr::CSR + .pmpconfig_get(i / 4) + .overflowing_shr(((i % 4) * 8) as u32) + .0 as u8, + ); + + // The address interpretation is different for every mode. Return both a + // string indicating the PMP entry's mode, as well as the effective + // start and end address (inclusive) affected by the region. For regions + // that are OFF, we still want to expose the pmpaddrX register value -- + // thus return the raw unshifted value as the addr, and 0 as the + // region's end. + let (start_label, start, end, mode) = match pmpcfg.read_as_enum(pmpcfg_octet::a) { + Some(pmpcfg_octet::a::Value::OFF) => { + let addr = csr::CSR.pmpaddr_get(i); + ("pmpaddr", addr, 0, "OFF ") + } + + Some(pmpcfg_octet::a::Value::TOR) => { + let start = if i > 0 { + csr::CSR.pmpaddr_get(i - 1) + } else { + 0 + }; + + ( + " start", + start.overflowing_shl(2).0, + csr::CSR.pmpaddr_get(i).overflowing_shl(2).0.wrapping_sub(1), + "TOR ", + ) + } + + Some(pmpcfg_octet::a::Value::NA4) => { + let addr = csr::CSR.pmpaddr_get(i).overflowing_shl(2).0; + (" start", addr, addr | 0b11, "NA4 ") + } + + Some(pmpcfg_octet::a::Value::NAPOT) => { + let pmpaddr = csr::CSR.pmpaddr_get(i); + let encoded_size = pmpaddr.trailing_ones(); + if (encoded_size as usize) < (core::mem::size_of_val(&pmpaddr) * 8 - 1) { + let start = pmpaddr - ((1 << encoded_size) - 1); + let end = start + (1 << (encoded_size + 1)) - 1; + ( + " start", + start.overflowing_shl(2).0, + end.overflowing_shl(2).0 | 0b11, + "NAPOT", + ) + } else { + (" start", usize::MIN, usize::MAX, "NAPOT") + } + } + + None => { + // We match on a 2-bit value with 4 variants, so this is + // unreachable. However, don't insert a panic in case this + // doesn't get optimized away: + ("", 0, 0, "") + } + }; + + // Ternary operator shortcut function, to avoid bulky formatting... + fn t(cond: bool, a: T, b: T) -> T { + if cond { + a + } else { + b + } } + + write!( + f, + " [{:02}]: {}={:#010X}, end={:#010X}, cfg={:#04X} ({}) ({}{}{}{})\r\n", + i, + start_label, + start, + end, + pmpcfg.get(), + mode, + t(pmpcfg.is_set(pmpcfg_octet::l), "l", "-"), + t(pmpcfg.is_set(pmpcfg_octet::r), "r", "-"), + t(pmpcfg.is_set(pmpcfg_octet::w), "w", "-"), + t(pmpcfg.is_set(pmpcfg_octet::x), "x", "-"), + )?; } + + Ok(()) } -impl fmt::Display - for PMPConfig -{ +/// A RISC-V PMP implementation exposing a number of TOR memory protection +/// regions to the [`PMPUserMPU`]. +/// +/// The RISC-V PMP is complex and can be used to enforce memory protection in +/// various modes (Machine, Supervisor and User mode). Depending on the exact +/// extension set present (e.g., ePMP) and the machine's security configuration +/// bits, it may expose a vastly different set of constraints and application +/// semantics. +/// +/// Because we can't possibly capture all of this in a single readable, +/// maintainable and efficient implementation, we implement a two-layer system: +/// +/// - a [`TORUserPMP`] is a simple abstraction over some underlying PMP hardware +/// implementation, which exposes an interface to configure regions that are +/// active (enforced) in user-mode and can be configured for arbitrary +/// addresses on a 4-byte granularity. +/// +/// - the [`PMPUserMPU`] takes this abstraction and implements the Tock kernel's +/// [`mpu::MPU`] trait. It worries about re-configuring memory protection when +/// switching processes, allocating memory regions of an appropriate size, +/// etc. +/// +/// Implementors of a chip are free to define their own [`TORUserPMP`] +/// implementations, adhering to their specific PMP layout & constraints, +/// provided they implement this trait. +/// +/// The `MAX_REGIONS` const generic is used to indicate the maximum number of +/// TOR PMP regions available to the [`PMPUserMPU`]. The PMP implementation may +/// provide less regions than indicated through `MAX_REGIONS`, for instance when +/// entries are enforced (locked) in machine mode. The number of available +/// regions may change at runtime. The current number of regions available to +/// the [`PMPUserMPU`] is indicated by the [`TORUserPMP::available_regions`] +/// method. However, when it is known that a number of regions are not available +/// for userspace protection, `MAX_REGIONS` can be used to reduce the memory +/// footprint allocated by stored PMP configurations, as well as the +/// re-configuration overhead. +pub trait TORUserPMP { + /// A placeholder to define const-assertions which are evaluated in + /// [`PMPUserMPU::new`]. This can be used to, for instance, assert that the + /// number of userspace regions does not exceed the number of hardware + /// regions. + const CONST_ASSERT_CHECK: (); + + /// The number of TOR regions currently available for userspace memory + /// protection. Within `[0; MAX_REGIONS]`. + /// + /// The PMP implementation may provide less regions than indicated through + /// `MAX_REGIONS`, for instance when entries are enforced (locked) in + /// machine mode. The number of available regions may change at runtime. The + /// implementation is free to map these regions to arbitrary PMP entries + /// (and change this mapping at runtime), provided that they are enforced + /// when the hart is in user-mode, and other memory regions are generally + /// inaccessible when in user-mode. + /// + /// When allocating regions for kernel-mode protection, and thus reducing + /// the number of regions available to userspace, re-configuring the PMP may + /// fail. This is allowed behavior. However, the PMP must not remove any + /// regions from the user-mode current configuration while it is active + /// ([`TORUserPMP::enable_user_pmp`] has been called, and it has not been + /// disabled through [`TORUserPMP::disable_user_pmp`]). + fn available_regions(&self) -> usize; + + /// Configure the user-mode memory protection. + /// + /// This method configures the user-mode memory protection, to be enforced + /// on a call to [`TORUserPMP::enable_user_pmp`]. + /// + /// PMP implementations where configured regions are only enforced in + /// user-mode may re-configure the PMP on this function invocation and + /// implement [`TORUserPMP::enable_user_pmp`] as a no-op. If configured + /// regions are enforced in machine-mode (for instance when using an ePMP + /// with the machine-mode whitelist policy), the new configuration rules + /// must not apply until [`TORUserPMP::enable_user_pmp`]. + /// + /// The tuples as passed in the `regions` parameter are defined as follows: + /// + /// - first value ([`TORUserPMPCFG`]): the memory protection mode as + /// enforced on the region. A `TORUserPMPCFG` can be created from the + /// [`mpu::Permissions`] type. It is in a format compatible to the pmpcfgX + /// register, guaranteed to not have the lock (`L`) bit set, and + /// configured either as a TOR region (`A = 0b01`), or disabled (all bits + /// set to `0`). + /// + /// - second value (`*const u8`): the region's start addres. As a PMP TOR + /// region has a 4-byte address granularity, this address is rounded down + /// to the next 4-byte boundary. + /// + /// - third value (`*const u8`): the region's end addres. As a PMP TOR + /// region has a 4-byte address granularity, this address is rounded down + /// to the next 4-byte boundary. + /// + /// To disable a region, set its configuration to [`TORUserPMPCFG::OFF`]. In + /// this case, the start and end addresses are ignored and can be set to + /// arbitrary values. + fn configure_pmp( + &self, + regions: &[(TORUserPMPCFG, *const u8, *const u8); MAX_REGIONS], + ) -> Result<(), ()>; + + /// Enable the user-mode memory protection. + /// + /// Enables the memory protection for user-mode, as configured through + /// [`TORUserPMP::configure_pmp`]. Enabling the PMP for user-mode may make + /// the user-mode accessible regions inaccessible to the kernel. For PMP + /// implementations where configured regions are only enforced in user-mode, + /// this method may be implemented as a no-op. + /// + /// If enabling the current configuration is not possible (e.g., because + /// regions have been allocated to the kernel), this function must return + /// `Err(())`. Otherwise, this function returns `Ok(())`. + fn enable_user_pmp(&self) -> Result<(), ()>; + + /// Disable the user-mode memory protection. + /// + /// Disables the memory protection for user-mode. If enabling the user-mode + /// memory protetion made user-mode accessible regions inaccessible to + /// machine-mode, this method should make these regions accessible again. + /// + /// For PMP implementations where configured regions are only enforced in + /// user-mode, this method may be implemented as a no-op. This method is not + /// responsible for making regions inaccessible to user-mode. If previously + /// configured regions must be made inaccessible, + /// [`TORUserPMP::configure_pmp`] must be used to re-configure the PMP + /// accordingly. + fn disable_user_pmp(&self); +} + +/// Struct storing userspace memory protection regions for the [`PMPUserMPU`]. +pub struct PMPUserMPUConfig { + /// PMP config identifier, as generated by the issuing PMP implementation. + id: NonZeroUsize, + /// Indicates if the configuration has changed since the last time it was + /// written to hardware. + is_dirty: Cell, + /// Array of MPU regions. Each region requires two physical PMP entries. + regions: [(TORUserPMPCFG, *const u8, *const u8); MAX_REGIONS], + /// Which region index (into the `regions` array above) is used + /// for app memory (if it has been configured). + app_memory_region: OptionalCell, +} + +impl fmt::Display for PMPUserMPUConfig { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, " PMP regions:\r\n")?; - for (n, region) in self.regions.iter().enumerate() { - match region { - None => write!(f, " \r\n")?, - Some(region) => write!(f, " [{}]: {}\r\n", n, region)?, + // Ternary operator shortcut function, to avoid bulky formatting... + fn t(cond: bool, a: T, b: T) -> T { + if cond { + a + } else { + b } } + + write!( + f, + " PMPUserMPUConfig {{\r\n id: {},\r\n is_dirty: {},\r\n app_memory_region: {:?},\r\n regions:\r\n", + self.id, + self.is_dirty.get(), + self.app_memory_region.get() + )?; + + for (i, (tor_user_pmpcfg, start, end)) in self.regions.iter().enumerate() { + let pmpcfg = tor_user_pmpcfg.get_reg(); + write!( + f, + " #{:02}: start={:#010X}, end={:#010X}, cfg={:#04X} ({}) (-{}{}{})\r\n", + i, + *start as usize, + *end as usize, + pmpcfg.get(), + t(pmpcfg.is_set(pmpcfg_octet::a), "TOR", "OFF"), + t(pmpcfg.is_set(pmpcfg_octet::r), "r", "-"), + t(pmpcfg.is_set(pmpcfg_octet::w), "w", "-"), + t(pmpcfg.is_set(pmpcfg_octet::x), "x", "-"), + )?; + } + + write!(f, " }}\r\n")?; Ok(()) } } -impl PMPConfig { - /// Get the first unused region - fn unused_region_number(&self, locked_region_mask: u64) -> Option { - for (number, region) in self.regions.iter().enumerate() { - if !self.is_index_locked_or_app(locked_region_mask, number) && region.is_none() { - return Some(number); - } - } - None - } +/// Adapter from a generic PMP implementation exposing TOR-type regions to the +/// Tock [`mpu::MPU`] trait. See [`TORUserPMP`]. +pub struct PMPUserMPU + 'static> { + /// Monotonically increasing counter for allocated configurations, used to + /// assign unique IDs to `PMPUserMPUConfig` instances. + config_count: Cell, + /// The configuration that the PMP was last configured for. Used (along with + /// the `is_dirty` flag) to determine if PMP can skip writing the + /// configuration to hardware. + last_configured_for: OptionalCell, + /// Underlying hardware PMP implementation, exposing a number (up to + /// `P::MAX_REGIONS`) of memory protection regions with a 4-byte enforcement + /// granularity. + pub pmp: P, +} - /// Get the last unused region - /// The app regions need to be lower then the kernel to ensure they - /// match before the kernel ones. - fn unused_kernel_region_number(&self, locked_region_mask: u64) -> Option { - // It is important to enumerate first, then reverse otherwise the enumeration index - // won't match the array index - for (number, region) in self.regions.iter().enumerate().rev() { - if !self.is_index_locked_or_app(locked_region_mask, number) && region.is_none() { - return Some(number); - } +impl + 'static> PMPUserMPU { + pub fn new(pmp: P) -> Self { + // Assigning this constant here ensures evaluation of the const + // expression at compile time, and can thus be used to enforce + // compile-time assertions based on the desired PMP configuration. + #[allow(clippy::let_unit_value)] + let _: () = P::CONST_ASSERT_CHECK; + + PMPUserMPU { + config_count: Cell::new(NonZeroUsize::MIN), + last_configured_for: OptionalCell::empty(), + pmp, } - None - } - - /// Returns true is the specified index is either locked or corresponds to the app region - fn is_index_locked_or_app(&self, locked_region_mask: u64, number: usize) -> bool { - locked_region_mask & (1 << number) > 0 || self.app_memory_region.contains(&number) } } -impl kernel::platform::mpu::MPU - for PMP +impl + 'static> kernel::platform::mpu::MPU + for PMPUserMPU { - type MpuConfig = PMPConfig; - - fn clear_mpu(&self) { - // We want to disable all of the hardware entries, so we use `NUM_REGIONS` here, - // and not `NUM_REGIONS / 2`. - // - // We want to keep the first region configured, so it is excluded from the loops and - // set separately. - for x in 1..(MAX_AVAILABLE_REGIONS_OVER_TWO * 2) { - csr::CSR.pmpaddr_set(x, 0x0); - } - for x in 1..(MAX_AVAILABLE_REGIONS_OVER_TWO * 2 / 4) { - csr::CSR.pmpconfig_set(x, 0); - } - csr::CSR.pmpaddr_set(0, 0xFFFF_FFFF); - // enable R W X fields - csr::CSR.pmpconfig_set( - 0, - (csr::pmpconfig::pmpcfg::r0::SET - + csr::pmpconfig::pmpcfg::w0::SET - + csr::pmpconfig::pmpcfg::x0::SET - + csr::pmpconfig::pmpcfg::a0::TOR) - .value, - ); - // PMP is not configured for any process now - self.last_configured_for.take(); + type MpuConfig = PMPUserMPUConfig; + + fn enable_app_mpu(&self) { + // TODO: This operation may fail when the PMP is not exclusively used + // for userspace. Instead of panicing, we should handle this case more + // gracefully and return an error in the `MPU` trait. Process + // infrastructure can then attempt to re-schedule the process later on, + // try to revoke some optional shared memory regions, or suspend the + // process. + self.pmp.enable_user_pmp().unwrap() } - fn enable_app_mpu(&self) {} - fn disable_app_mpu(&self) { - // PMP is not enabled for machine mode, so we don't have to do - // anything + self.pmp.disable_user_pmp() } fn number_total_regions(&self) -> usize { - self.num_regions / 2 + self.pmp.available_regions() + } + + fn new_config(&self) -> Option { + let id = self.config_count.get(); + self.config_count.set(id.checked_add(1)?); + + Some(PMPUserMPUConfig { + id, + regions: [( + TORUserPMPCFG::OFF, + core::ptr::null::(), + core::ptr::null::(), + ); MAX_REGIONS], + is_dirty: Cell::new(true), + app_memory_region: OptionalCell::empty(), + }) + } + + fn reset_config(&self, config: &mut Self::MpuConfig) { + config.regions.iter_mut().for_each(|region| { + *region = ( + TORUserPMPCFG::OFF, + core::ptr::null::(), + core::ptr::null::(), + ) + }); + config.app_memory_region.clear(); + config.is_dirty.set(true); } fn allocate_region( @@ -329,41 +623,89 @@ impl kernel::platform::mpu::MPU permissions: mpu::Permissions, config: &mut Self::MpuConfig, ) -> Option { - for region in config.regions.iter() { - if region.is_some() { - if region - .unwrap() - .overlaps(unallocated_memory_start, unallocated_memory_size) - { - return None; - } - } - } - - let region_num = config.unused_region_number(self.locked_region_mask.get())?; + // Find a free region slot. If we don't have one, abort early: + let region_num = config + .regions + .iter() + .enumerate() + .find(|(_i, (pmpcfg, _, _))| *pmpcfg == TORUserPMPCFG::OFF) + .map(|(i, _)| i)?; - // Logical region + // Now, meet the PMP TOR region constraints. For this, start with the + // provided start address and size, transform them to meet the + // constraints, and then check that we're still within the bounds of the + // provided values: let mut start = unallocated_memory_start as usize; let mut size = min_region_size; - // Region start always has to align to 4 bytes + // Region start always has to align to 4 bytes. Round up to a 4 byte + // boundary if required: if start % 4 != 0 { start += 4 - (start % 4); } - // Region size always has to align to 4 bytes + // Region size always has to align to 4 bytes. Round up to a 4 byte + // boundary if required: if size % 4 != 0 { size += 4 - (size % 4); } - // Regions must be at least 8 bytes - if size < 8 { - size = 8; + // Regions must be at least 4 bytes in size. + if size < 4 { + size = 4; } - let region = PMPRegion::new(start as *const u8, size, permissions); + // Now, check to see whether the adjusted start and size still meet the + // allocation constraints, namely ensure that + // + // start + size <= unallocated_memory_start + unallocated_memory_size + if start + size > (unallocated_memory_start as usize) + unallocated_memory_size { + // We're overflowing the provided memory region, can't make + // allocation. Normally, we'd abort here. + // + // However, a previous implementation of this code was incorrect in + // that performed this check before adjusting the requested region + // size to meet PMP region layout constraints (4 byte alignment for + // start and end address). Existing applications whose end-address + // is aligned on a less than 4-byte bondary would thus be given + // access to additional memory which should be inaccessible. + // Unfortunately, we can't fix this without breaking existing + // applications. Thus, we perform the same insecure hack here, and + // give the apps at most an extra 3 bytes of memory, as long as the + // requested region as no write privileges. + // + // TODO: Remove this logic with as part of + // https://github.com/tock/tock/issues/3544 + let writeable = match permissions { + mpu::Permissions::ReadWriteExecute => true, + mpu::Permissions::ReadWriteOnly => true, + mpu::Permissions::ReadExecuteOnly => false, + mpu::Permissions::ReadOnly => false, + mpu::Permissions::ExecuteOnly => false, + }; - config.regions[region_num] = Some(region); + if writeable + || (start + size + > (unallocated_memory_start as usize) + unallocated_memory_size + 3) + { + return None; + } + } + + // Finally, check that this new region does not overlap with any + // existing configured userspace region: + for region in config.regions.iter() { + if region.0 != TORUserPMPCFG::OFF && region_overlaps(region, start as *const u8, size) { + return None; + } + } + + // All checks passed, store region allocation and mark config as dirty: + config.regions[region_num] = ( + permissions.into(), + start as *const u8, + (start + size) as *const u8, + ); config.is_dirty.set(true); Some(mpu::Region::new(start as *const u8, size)) @@ -374,18 +716,20 @@ impl kernel::platform::mpu::MPU region: mpu::Region, config: &mut Self::MpuConfig, ) -> Result<(), ()> { - let (index, _r) = config + let index = config .regions .iter() .enumerate() - .find(|(_idx, r)| r.map_or(false, |r| r == region)) + .find(|(_i, r)| { + // `start as usize + size` in lieu of a safe pointer offset method + r.0 != TORUserPMPCFG::OFF + && r.1 == region.start_address() + && r.2 == (region.start_address() as usize + region.size()) as *const u8 + }) + .map(|(i, _)| i) .ok_or(())?; - if config.is_index_locked_or_app(self.locked_region_mask.get(), index) { - return Err(()); - } - - config.regions[index] = None; + config.regions[index].0 = TORUserPMPCFG::OFF; config.is_dirty.set(true); Ok(()) @@ -401,64 +745,90 @@ impl kernel::platform::mpu::MPU permissions: mpu::Permissions, config: &mut Self::MpuConfig, ) -> Option<(*const u8, usize)> { - // Check that no previously allocated regions overlap the unallocated memory. - for region in config.regions.iter() { - if region.is_some() { - if region - .unwrap() - .overlaps(unallocated_memory_start, unallocated_memory_size) - { - return None; - } - } + // An app memory region can only be allocated once per `MpuConfig`. + // If we already have one, abort: + if config.app_memory_region.is_some() { + return None; } - let region_num = if config.app_memory_region.is_some() { - config.app_memory_region.unwrap_or(0) - } else { - config.unused_region_number(self.locked_region_mask.get())? - }; + // Find a free region slot. If we don't have one, abort early: + let region_num = config + .regions + .iter() + .enumerate() + .find(|(_i, (pmpcfg, _, _))| *pmpcfg == TORUserPMPCFG::OFF) + .map(|(i, _)| i)?; + + // Now, meet the PMP TOR region constraints for the region specified by + // `initial_app_memory_size` (which is the part of the region actually + // protected by the PMP). For this, start with the provided start + // address and size, transform them to meet the constraints, and then + // check that we're still within the bounds of the provided values: + let mut start = unallocated_memory_start as usize; + let mut pmp_region_size = initial_app_memory_size; - // App memory size is what we actual set the region to. So this region - // has to be aligned to 4 bytes. - let mut initial_app_memory_size: usize = initial_app_memory_size; - if initial_app_memory_size % 4 != 0 { - initial_app_memory_size += 4 - (initial_app_memory_size % 4); + // Region start always has to align to 4 bytes. Round up to a 4 byte + // boundary if required: + if start % 4 != 0 { + start += 4 - (start % 4); } - // Make sure there is enough memory for app memory and kernel memory. - let mut region_size = cmp::max( - min_memory_size, - initial_app_memory_size + initial_kernel_memory_size, - ) as usize; + // Region size always has to align to 4 bytes. Round up to a 4 byte + // boundary if required: + if pmp_region_size % 4 != 0 { + pmp_region_size += 4 - (pmp_region_size % 4); + } - // Region size always has to align to 4 bytes - if region_size % 4 != 0 { - region_size += 4 - (region_size % 4); + // Regions must be at least 4 bytes in size. + if pmp_region_size < 4 { + pmp_region_size = 4; } - // The region should start as close as possible to the start of the unallocated memory. - let region_start = unallocated_memory_start as usize; + // We need to provide a memory block that fits both the initial app and + // kernel memory sections, and is `min_memory_size` bytes + // long. Calculate the length of this block with our new PMP-aliged + // size: + let memory_block_size = cmp::max( + min_memory_size, + pmp_region_size + initial_kernel_memory_size, + ); - // Make sure the region fits in the unallocated memory. - if region_start + region_size - > (unallocated_memory_start as usize) + unallocated_memory_size + // Now, check to see whether the adjusted start and size still meet the + // allocation constraints, namely ensure that + // + // start + memory_block_size + // <= unallocated_memory_start + unallocated_memory_size + // + // , which ensures the PMP constraints didn't push us over the bounds of + // the provided memory region, and we can fit the entire allocation as + // requested by the kernel: + if start + memory_block_size > (unallocated_memory_start as usize) + unallocated_memory_size { + // Overflowing the provided memory region, can't make allocation: return None; } - let region = PMPRegion::new( - region_start as *const u8, - initial_app_memory_size, - permissions, - ); + // Finally, check that this new region does not overlap with any + // existing configured userspace region: + for region in config.regions.iter() { + if region.0 != TORUserPMPCFG::OFF + && region_overlaps(region, start as *const u8, memory_block_size) + { + return None; + } + } - config.regions[region_num] = Some(region); + // All checks passed, store region allocation, indicate the + // app_memory_region, and mark config as dirty: + config.regions[region_num] = ( + permissions.into(), + start as *const u8, + (start + pmp_region_size) as *const u8, + ); config.is_dirty.set(true); + config.app_memory_region.replace(region_num); - config.app_memory_region.set(region_num); - - Some((region_start as *const u8, region_size)) + Some((start as *const u8, memory_block_size)) } fn update_app_memory_region( @@ -468,191 +838,1391 @@ impl kernel::platform::mpu::MPU permissions: mpu::Permissions, config: &mut Self::MpuConfig, ) -> Result<(), ()> { - let region_num = config.app_memory_region.unwrap_or(0); + let region_num = config.app_memory_region.get().ok_or(())?; - let (region_start, _) = match config.regions[region_num] { - Some(region) => region.location(), - None => { - // Error: Process tried to update app memory MPU region before it was created. - return Err(()); - } - }; - - let app_memory_break = app_memory_break as usize; + let mut app_memory_break = app_memory_break as usize; let kernel_memory_break = kernel_memory_break as usize; - // Out of memory + // Ensure that the requested app_memory_break complies with PMP + // alignment constraints, namely that the region's end address is 4 byte + // aligned: + if app_memory_break % 4 != 0 { + app_memory_break += 4 - (app_memory_break % 4); + } + + // Check if the app has run out of memory: if app_memory_break > kernel_memory_break { return Err(()); } - // Get size of updated region - let region_size = app_memory_break - region_start as usize; - - let region = PMPRegion::new(region_start as *const u8, region_size, permissions); - - config.regions[region_num] = Some(region); + // If we're not out of memory, update the region configuration + // accordingly: + config.regions[region_num].0 = permissions.into(); + config.regions[region_num].2 = app_memory_break as *const u8; config.is_dirty.set(true); Ok(()) } - fn configure_mpu(&self, config: &Self::MpuConfig, app_id: &ProcessId) { - // Is the PMP already configured for this app? - let last_configured_for_this_app = self - .last_configured_for - .map_or(false, |last_app_id| last_app_id == app_id); - - // Skip PMP configuration if it is already configured for this app and the MPU - // configuration of this app has not changed. - if !last_configured_for_this_app || config.is_dirty.get() { - for (x, region) in config.regions.iter().enumerate() { - match region { - Some(r) => { - let cfg_val = r.cfg.value as usize; - let start = r.location.0 as usize; - let size = r.location.1; - - let disable_val = (csr::pmpconfig::pmpcfg::r0::CLEAR - + csr::pmpconfig::pmpcfg::w0::CLEAR - + csr::pmpconfig::pmpcfg::x0::CLEAR - + csr::pmpconfig::pmpcfg::a0::OFF) - .value; - let (region_shift, other_region_mask) = if x % 2 == 0 { - (0, 0xFFFF_0000) - } else { - (16, 0x0000_FFFF) - }; - csr::CSR.pmpconfig_set( - x / 2, - (disable_val | cfg_val << 8) << region_shift - | (csr::CSR.pmpconfig_get(x / 2) & other_region_mask), - ); - csr::CSR.pmpaddr_set(x * 2, (start) >> 2); - csr::CSR.pmpaddr_set((x * 2) + 1, (start + size) >> 2); - } - None => {} - }; - } + fn configure_mpu(&self, config: &Self::MpuConfig) { + if !self.last_configured_for.contains(&config.id) || config.is_dirty.get() { + self.pmp.configure_pmp(&config.regions).unwrap(); config.is_dirty.set(false); - self.last_configured_for.put(*app_id); + self.last_configured_for.set(config.id); } } } -/// This is PMP support for kernel regions -/// PMP does not allow a deny by default option, so all regions not marked -/// with the below commands will have full access. -/// This is still a useful implementation as it can be used to limit the -/// kernels access, for example removing execute permission from regions -/// we don't need to execute from and removing write permissions from -/// executable reions. -impl kernel::platform::mpu::KernelMPU - for PMP -{ - type KernelMpuConfig = PMPConfig; +#[cfg(test)] +pub mod test { + use super::{TORUserPMP, TORUserPMPCFG}; + + struct MockTORUserPMP; + impl TORUserPMP for MockTORUserPMP { + // Don't require any const-assertions in the MockTORUserPMP. + const CONST_ASSERT_CHECK: () = (); + + fn available_regions(&self) -> usize { + // For the MockTORUserPMP, we always assume to have the full number + // of MPU_REGIONS available. More advanced tests may want to return + // a different number here (to simulate kernel memory protection) + // and make the configuration fail at runtime, for instance. + MPU_REGIONS + } - fn allocate_kernel_region( - &self, - memory_start: *const u8, - memory_size: usize, - permissions: mpu::Permissions, - config: &mut Self::KernelMpuConfig, - ) -> Option { - for region in config.regions.iter() { - if region.is_some() { - if region.unwrap().overlaps(memory_start, memory_size) { - return None; + fn configure_pmp( + &self, + _regions: &[(TORUserPMPCFG, *const u8, *const u8); MPU_REGIONS], + ) -> Result<(), ()> { + Ok(()) + } + + fn enable_user_pmp(&self) -> Result<(), ()> { + Ok(()) + } // The kernel's MPU trait requires + + fn disable_user_pmp(&self) {} + } + + // TODO: implement more test cases, such as: + // + // - Try to update the app memory break with an invalid pointer below its + // allocation's start address. + + #[test] + fn test_mpu_region_no_overlap() { + use crate::pmp::PMPUserMPU; + use kernel::platform::mpu::{Permissions, MPU}; + + let mpu: PMPUserMPU<8, MockTORUserPMP> = PMPUserMPU::new(MockTORUserPMP); + let mut config = mpu + .new_config() + .expect("Failed to allocate the first MPU config"); + + // Allocate a region which spans from 0x40000000 to 0x80000000 (this + // meets PMP alignment constraints and will work on 32-bit and 64-bit + // systems) + let region_0 = mpu + .allocate_region( + 0x40000000 as *const u8, + 0x40000000, + 0x40000000, + Permissions::ReadWriteOnly, + &mut config, + ) + .expect( + "Failed to allocate a well-aligned R/W MPU region with \ + unallocated_memory_size == min_region_size", + ); + assert!(region_0.start_address() == 0x40000000 as *const u8); + assert!(region_0.size() == 0x40000000); + + // Try to allocate a region adjacent to `region_0`. This should work: + let region_1 = mpu + .allocate_region( + 0x80000000 as *const u8, + 0x10000000, + 0x10000000, + Permissions::ReadExecuteOnly, + &mut config, + ) + .expect( + "Failed to allocate a well-aligned R/W MPU region adjacent to \ + another region", + ); + assert!(region_1.start_address() == 0x80000000 as *const u8); + assert!(region_1.size() == 0x10000000); + + // Remove the previously allocated `region_1`: + mpu.remove_memory_region(region_1, &mut config) + .expect("Failed to remove valid MPU region allocation"); + + // Allocate another region which spans from 0xc0000000 to 0xd0000000 + // (this meets PMP alignment constraints and will work on 32-bit and + // 64-bit systems), but this time allocate it using the + // `allocate_app_memory_region` method. We want a region of `0x20000000` + // bytes, but only the first `0x10000000` should be accessible to the + // app. + let (region_2_start, region_2_size) = mpu + .allocate_app_memory_region( + 0xc0000000 as *const u8, + 0x20000000, + 0x20000000, + 0x10000000, + 0x08000000, + Permissions::ReadWriteOnly, + &mut config, + ) + .expect( + "Failed to allocate a well-aligned R/W app memory MPU region \ + with unallocated_memory_size == min_region_size", + ); + assert!(region_2_start == 0xc0000000 as *const u8); + assert!(region_2_size == 0x20000000); + + // --> General overlap tests involving both regions + + // Now, try to allocate another region that spans over both memory + // regions. This should fail. + assert!(mpu + .allocate_region( + 0x40000000 as *const u8, + 0xc0000000, + 0xc0000000, + Permissions::ReadOnly, + &mut config, + ) + .is_none()); + + // Try to allocate a region that spans over parts of both memory + // regions. This should fail. + assert!(mpu + .allocate_region( + 0x48000000 as *const u8, + 0x80000000, + 0x80000000, + Permissions::ReadOnly, + &mut config, + ) + .is_none()); + + // --> Overlap tests involving a single region (region_0) + // + // We define these in an array, such that we can run the tests with the + // `region_0` defined (to confirm that the allocations are indeed + // refused), and with `region_0` removed (to make sure they would work + // in general). + let overlap_region_0_tests = [ + ( + // Try to allocate a region that is contained within + // `region_0`. This should fail. + 0x41000000 as *const u8, + 0x01000000, + 0x01000000, + Permissions::ReadWriteOnly, + ), + ( + // Try to allocate a region that overlaps with `region_0` in the + // front. This should fail. + 0x38000000 as *const u8, + 0x10000000, + 0x10000000, + Permissions::ReadWriteExecute, + ), + ( + // Try to allocate a region that overlaps with `region_0` in the + // back. This should fail. + 0x48000000 as *const u8, + 0x10000000, + 0x10000000, + Permissions::ExecuteOnly, + ), + ( + // Try to allocate a region that spans over `region_0`. This + // should fail. + 0x38000000 as *const u8, + 0x20000000, + 0x20000000, + Permissions::ReadWriteOnly, + ), + ]; + + // Make sure that the allocation requests fail with `region_0` defined: + for (memory_start, memory_size, length, perms) in overlap_region_0_tests.iter() { + assert!(mpu + .allocate_region(*memory_start, *memory_size, *length, *perms, &mut config,) + .is_none()); + } + + // Now, remove `region_0` and re-run the tests. Every test-case should + // succeed now (in isolation, hence removing the successful allocations): + mpu.remove_memory_region(region_0, &mut config) + .expect("Failed to remove valid MPU region allocation"); + + for region @ (memory_start, memory_size, length, perms) in overlap_region_0_tests.iter() { + let allocation_res = + mpu.allocate_region(*memory_start, *memory_size, *length, *perms, &mut config); + + match allocation_res { + Some(region) => { + mpu.remove_memory_region(region, &mut config) + .expect("Failed to remove valid MPU region allocation"); + } + None => { + panic!( + "Failed to allocate region that does not overlap and \ + should meet alignment constraints: {:?}", + region + ); } } } - let region_num = config.unused_kernel_region_number(self.locked_region_mask.get())?; + // Make sure we can technically allocate a memory region that overlaps + // with the kernel part of the `app_memory_region`. + // + // It is unclear whether this should be supported. + let region_2 = mpu + .allocate_region( + 0xd0000000 as *const u8, + 0x10000000, + 0x10000000, + Permissions::ReadWriteOnly, + &mut config, + ) + .unwrap(); + assert!(region_2.start_address() == 0xd0000000 as *const u8); + assert!(region_2.size() == 0x10000000); + + // Now, we can grow the app memory break into this region: + mpu.update_app_memory_region( + 0xd0000004 as *const u8, + 0xd8000000 as *const u8, + Permissions::ReadWriteOnly, + &mut config, + ) + .expect("Failed to grow the app memory region into an existing other MPU region"); + + // Now, we have two overlapping MPU regions. Remove `region_2`, and try + // to reallocate it as `region_3`. This should fail now, demonstrating + // that we managed to reach an invalid intermediate state: + mpu.remove_memory_region(region_2, &mut config) + .expect("Failed to remove valid MPU region allocation"); + assert!(mpu + .allocate_region( + 0xd0000000 as *const u8, + 0x10000000, + 0x10000000, + Permissions::ReadWriteOnly, + &mut config, + ) + .is_none()); + } +} - // Logical region - let mut start = memory_start as usize; - let mut size = memory_size; +pub mod simple { + use super::{pmpcfg_octet, TORUserPMP, TORUserPMPCFG}; + use crate::csr; + use core::fmt; + use kernel::utilities::registers::{FieldValue, LocalRegisterCopy}; - // Region start always has to align to 4 bytes - if start % 4 != 0 { - start += 4 - (start % 4); + /// A "simple" RISC-V PMP implementation. + /// + /// The SimplePMP does not support locked regions, kernel memory protection, + /// or any ePMP features (using the mseccfg CSR). It is generic over the + /// number of hardware PMP regions available. `AVAILABLE_ENTRIES` is + /// expected to be set to the number of available entries. + /// + /// [`SimplePMP`] implements [`TORUserPMP`] to expose all of its regions as + /// "top of range" (TOR) regions (each taking up two physical PMP entires) + /// for use as a user-mode memory protection mechanism. + /// + /// Notably, [`SimplePMP`] implements `TORUserPMP` over a + /// generic `MPU_REGIONS` where `MPU_REGIONS <= (AVAILABLE_ENTRIES / 2)`. As + /// PMP re-configuration can have a significiant runtime overhead, users are + /// free to specify a small `MPU_REGIONS` const-generic parameter to reduce + /// the runtime overhead induced through PMP configuration, at the cost of + /// having less PMP regions available to use for userspace memory + /// protection. + pub struct SimplePMP; + + impl SimplePMP { + pub unsafe fn new() -> Result { + // The SimplePMP does not support locked regions, kernel memory + // protection, or any ePMP features (using the mseccfg CSR). Ensure + // that we don't find any locked regions. If we don't have locked + // regions and can still successfully execute code, this means that + // we're not in the ePMP machine-mode lockdown mode, and can treat + // our hardware as a regular PMP. + // + // Furthermore, we test whether we can use each entry (i.e. whether + // it actually exists in HW) by flipping the RWX bits. If we can't + // flip them, then `AVAILABLE_ENTRIES` is incorrect. However, this + // is not sufficient to check for locked regions, because of the + // ePMP's rule-lock-bypass bit. If a rule is locked, it might be the + // reason why we can execute code or read-write data in machine mode + // right now. Thus, never try to touch a locked region, as we might + // well revoke access to a kernel region! + for i in 0..AVAILABLE_ENTRIES { + // Read the entry's CSR: + let pmpcfg_csr = csr::CSR.pmpconfig_get(i / 4); + + // Extract the entry's pmpcfg octet: + let pmpcfg: LocalRegisterCopy = LocalRegisterCopy::new( + pmpcfg_csr.overflowing_shr(((i % 4) * 8) as u32).0 as u8, + ); + + // As outlined above, we never touch a locked region. Thus, bail + // out if it's locked: + if pmpcfg.is_set(pmpcfg_octet::l) { + return Err(()); + } + + // Now that it's not locked, we can be sure that regardless of + // any ePMP bits, this region is either ignored or entirely + // denied for machine-mode access. Hence, we can change it in + // arbitrary ways without breaking our own memory access. Try to + // flip the R/W/X bits: + csr::CSR.pmpconfig_set(i / 4, pmpcfg_csr ^ (7 << ((i % 4) * 8))); + + // Check if the CSR changed: + if pmpcfg_csr == csr::CSR.pmpconfig_get(i / 4) { + // Didn't change! This means that this region is not backed + // by HW. Return an error as `AVAILABLE_ENTRIES` is + // incorrect: + return Err(()); + } + + // Finally, turn the region off: + csr::CSR.pmpconfig_set(i / 4, pmpcfg_csr & !(0x18 << ((i % 4) * 8))); + } + + // Hardware PMP is verified to be in a compatible mode / state, and + // has at least `AVAILABLE_ENTRIES` entries. + Ok(SimplePMP) } + } - // Region size always has to align to 4 bytes - if size % 4 != 0 { - size += 4 - (size % 4); + impl TORUserPMP + for SimplePMP + { + // Ensure that the MPU_REGIONS (starting at entry, and occupying two + // entries per region) don't overflow the available entires. + const CONST_ASSERT_CHECK: () = assert!(MPU_REGIONS <= (AVAILABLE_ENTRIES / 2)); + + fn available_regions(&self) -> usize { + // Always assume to have `MPU_REGIONS` usable TOR regions. We don't + // support locked regions, or kernel protection. + MPU_REGIONS } - // Regions must be at least 8 bytes - if size < 8 { - size = 8; + // This implementation is specific for 32-bit systems. We use + // `u32::from_be_bytes` and then cast to usize, as it manages to compile + // on 64-bit systems as well. However, this implementation will not work + // on RV64I systems, due to the changed pmpcfgX CSR layout. + fn configure_pmp( + &self, + regions: &[(TORUserPMPCFG, *const u8, *const u8); MPU_REGIONS], + ) -> Result<(), ()> { + // Could use `iter_array_chunks` once that's stable. + let mut regions_iter = regions.iter(); + let mut i = 0; + + while let Some(even_region) = regions_iter.next() { + let odd_region_opt = regions_iter.next(); + + if let Some(odd_region) = odd_region_opt { + // We can configure two regions at once which, given that we + // start at index 0 (an even offset), translates to a single + // CSR write for the pmpcfgX register: + csr::CSR.pmpconfig_set( + i / 2, + u32::from_be_bytes([ + odd_region.0.get(), + TORUserPMPCFG::OFF.get(), + even_region.0.get(), + TORUserPMPCFG::OFF.get(), + ]) as usize, + ); + + // Now, set the addresses of the respective regions, if they + // are enabled, respectively: + if even_region.0 != TORUserPMPCFG::OFF { + csr::CSR + .pmpaddr_set(i * 2 + 0, (even_region.1 as usize).overflowing_shr(2).0); + csr::CSR + .pmpaddr_set(i * 2 + 1, (even_region.2 as usize).overflowing_shr(2).0); + } + + if odd_region.0 != TORUserPMPCFG::OFF { + csr::CSR + .pmpaddr_set(i * 2 + 2, (odd_region.1 as usize).overflowing_shr(2).0); + csr::CSR + .pmpaddr_set(i * 2 + 3, (odd_region.2 as usize).overflowing_shr(2).0); + } + + i += 2; + } else { + // TODO: check overhead of code + // Modify the first two pmpcfgX octets for this region: + csr::CSR.pmpconfig_modify( + i / 2, + FieldValue::::new( + 0x0000FFFF, + 0, + u32::from_be_bytes([ + 0, + 0, + even_region.0.get(), + TORUserPMPCFG::OFF.get(), + ]) as usize, + ), + ); + + // Set the addresses if the region is enabled: + if even_region.0 != TORUserPMPCFG::OFF { + csr::CSR + .pmpaddr_set(i * 2 + 0, (even_region.1 as usize).overflowing_shr(2).0); + csr::CSR + .pmpaddr_set(i * 2 + 1, (even_region.2 as usize).overflowing_shr(2).0); + } + + i += 1; + } + } + + Ok(()) } - let region = PMPRegion::new(start as *const u8, size, permissions); + fn enable_user_pmp(&self) -> Result<(), ()> { + // No-op. The SimplePMP does not have any kernel-enforced regions. + Ok(()) + } - config.regions[region_num] = Some(region); + fn disable_user_pmp(&self) { + // No-op. The SimplePMP does not have any kernel-enforced regions. + } + } - // Mark the region as locked so that the app PMP doesn't use it. - let mut mask = self.locked_region_mask.get(); - mask |= 1 << region_num; - self.locked_region_mask.set(mask); + impl fmt::Display for SimplePMP { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, " PMP hardware configuration -- entries: \r\n")?; + unsafe { super::format_pmp_entries::(f) } + } + } +} - Some(mpu::Region::new(start as *const u8, size)) +pub mod kernel_protection { + use super::{pmpcfg_octet, NAPOTRegionSpec, TORRegionSpec, TORUserPMP, TORUserPMPCFG}; + use crate::csr; + use core::fmt; + use kernel::utilities::registers::{FieldValue, LocalRegisterCopy}; + + // ---------- Kernel memory-protection PMP memory region wrapper types ----- + // + // These types exist primarily to avoid argument confusion in the + // [`KernelProtectionPMP`] constructor, which accepts the addresses of these + // memory regions as arguments. They further encode whether a region must + // adhere to the `NAPOT` or `TOR` addressing mode constraints: + + /// The flash memory region address range. + /// + /// Configured in the PMP as a `NAPOT` region. + #[derive(Copy, Clone, Debug)] + pub struct FlashRegion(pub NAPOTRegionSpec); + + /// The RAM region address range. + /// + /// Configured in the PMP as a `NAPOT` region. + #[derive(Copy, Clone, Debug)] + pub struct RAMRegion(pub NAPOTRegionSpec); + + /// The MMIO region address range. + /// + /// Configured in the PMP as a `NAPOT` region. + #[derive(Copy, Clone, Debug)] + pub struct MMIORegion(pub NAPOTRegionSpec); + + /// The PMP region specification for the kernel `.text` section. + /// + /// This is to be made accessible to machine-mode as read-execute. + /// Configured in the PMP as a `TOR` region. + #[derive(Copy, Clone, Debug)] + pub struct KernelTextRegion(pub TORRegionSpec); + + /// A RISC-V PMP implementation which supports machine-mode (kernel) memory + /// protection, with a fixed number of "kernel regions" (such as `.text`, + /// flash, RAM and MMIO). + /// + /// This implementation will configure the PMP in the following way: + /// + /// ```text + /// |-------+-----------------------------------------+-------+---+-------| + /// | ENTRY | REGION / ADDR | MODE | L | PERMS | + /// |-------+-----------------------------------------+-------+---+-------| + /// | 0 | / \ | OFF | | | + /// | 1 | \ Userspace TOR region #0 / | TOR | | ????? | + /// | | | | | | + /// | 2 | / \ | OFF | | | + /// | 3 | \ Userspace TOR region #1 / | TOR | | ????? | + /// | | | | | | + /// | 4 ... | / \ | | | | + /// | n - 8 | \ Userspace TOR region #x / | | | | + /// | | | | | | + /// | n - 7 | "Deny-all" user-mode rule (all memory) | NAPOT | | ----- | + /// | | | | | | + /// | n - 6 | --------------------------------------- | OFF | X | ----- | + /// | n - 5 | Kernel .text section | TOR | X | R/X | + /// | | | | | | + /// | n - 4 | FLASH (spanning kernel & apps) | NAPOT | X | R | + /// | | | | | | + /// | n - 3 | RAM (spanning kernel & apps) | NAPOT | X | R/W | + /// | | | | | | + /// | n - 2 | MMIO | NAPOT | X | R/W | + /// | | | | | | + /// | n - 1 | "Deny-all" machine-mode (all memory) | NAPOT | X | ----- | + /// |-------+-----------------------------------------+-------+---+-------| + /// ``` + /// + /// This implementation does not use any `mseccfg` protection bits (ePMP + /// functionality). To protect machine-mode (kernel) memory regions, regions + /// must be marked as locked. However, locked regions apply to both user- + /// and machine-mode. Thus, region `n - 7` serves as a "deny-all" user-mode + /// rule, which prohibits all accesses not explicitly allowed through rules + /// `< n - 7`. Kernel memory is made accessible underneath this "deny-all" + /// region, which does not apply to machine-mode. + /// + /// This PMP implementation supports the [`TORUserPMP`] interface with + /// `MPU_REGIONS <= ((AVAILABLE_ENTRIES - 7) / 2)`, to leave sufficient + /// space for the "deny-all" and kernel regions. This constraint is enforced + /// through the [`KernelProtectionPMP::CONST_ASSERT_CHECK`] associated + /// constant, which MUST be evaluated by the consumer of the [`TORUserPMP`] + /// trait (usually the [`PMPUserMPU`](super::PMPUserMPU) implementation). + pub struct KernelProtectionPMP; + + impl KernelProtectionPMP { + pub unsafe fn new( + flash: FlashRegion, + ram: RAMRegion, + mmio: MMIORegion, + kernel_text: KernelTextRegion, + ) -> Result { + for i in 0..AVAILABLE_ENTRIES { + // Read the entry's CSR: + let pmpcfg_csr = csr::CSR.pmpconfig_get(i / 4); + + // Extract the entry's pmpcfg octet: + let pmpcfg: LocalRegisterCopy = LocalRegisterCopy::new( + pmpcfg_csr.overflowing_shr(((i % 4) * 8) as u32).0 as u8, + ); + + // As outlined above, we never touch a locked region. Thus, bail + // out if it's locked: + if pmpcfg.is_set(pmpcfg_octet::l) { + return Err(()); + } + + // Now that it's not locked, we can be sure that regardless of + // any ePMP bits, this region is either ignored or entirely + // denied for machine-mode access. Hence, we can change it in + // arbitrary ways without breaking our own memory access. Try to + // flip the R/W/X bits: + csr::CSR.pmpconfig_set(i / 4, pmpcfg_csr ^ (7 << ((i % 4) * 8))); + + // Check if the CSR changed: + if pmpcfg_csr == csr::CSR.pmpconfig_get(i / 4) { + // Didn't change! This means that this region is not backed + // by HW. Return an error as `AVAILABLE_ENTRIES` is + // incorrect: + return Err(()); + } + + // Finally, turn the region off: + csr::CSR.pmpconfig_set(i / 4, pmpcfg_csr & !(0x18 << ((i % 4) * 8))); + } + + // ----------------------------------------------------------------- + // Hardware PMP is verified to be in a compatible mode & state, and + // has at least `AVAILABLE_ENTRIES` entries. + // ----------------------------------------------------------------- + + // Now we need to set up the various kernel memory protection + // regions, and the deny-all userspace region (n - 8), never + // modified. + + // Helper to modify an arbitrary PMP entry. Because we don't know + // AVAILABLE_ENTRIES in advance, there's no good way to + // optimize this further. + fn write_pmpaddr_pmpcfg(i: usize, pmpcfg: u8, pmpaddr: usize) { + csr::CSR.pmpaddr_set(i, pmpaddr); + csr::CSR.pmpconfig_modify( + i / 4, + FieldValue::::new( + 0x000000FF_usize, + (i % 4) * 8, + u32::from_be_bytes([0, 0, 0, pmpcfg]) as usize, + ), + ); + } + + // Set the kernel `.text`, flash, RAM and MMIO regions, in no + // particular order, with the exception of `.text` and flash: + // `.text` must precede flash, as otherwise we'd be revoking execute + // permissions temporarily. Given that we can currently execute + // code, this should not have any impact on our accessible memory, + // assuming that the provided regions are not otherwise aliased. + + // MMIO at n - 2: + write_pmpaddr_pmpcfg( + AVAILABLE_ENTRIES - 2, + (pmpcfg_octet::a::NAPOT + + pmpcfg_octet::r::SET + + pmpcfg_octet::w::SET + + pmpcfg_octet::x::CLEAR + + pmpcfg_octet::l::SET) + .into(), + mmio.0.napot_addr(), + ); + + // RAM at n - 3: + write_pmpaddr_pmpcfg( + AVAILABLE_ENTRIES - 3, + (pmpcfg_octet::a::NAPOT + + pmpcfg_octet::r::SET + + pmpcfg_octet::w::SET + + pmpcfg_octet::x::CLEAR + + pmpcfg_octet::l::SET) + .into(), + ram.0.napot_addr(), + ); + + // `.text` at n - 6 and n - 5 (TOR region): + write_pmpaddr_pmpcfg( + AVAILABLE_ENTRIES - 6, + (pmpcfg_octet::a::OFF + + pmpcfg_octet::r::CLEAR + + pmpcfg_octet::w::CLEAR + + pmpcfg_octet::x::CLEAR + + pmpcfg_octet::l::SET) + .into(), + (kernel_text.0.start() as usize) >> 2, + ); + write_pmpaddr_pmpcfg( + AVAILABLE_ENTRIES - 5, + (pmpcfg_octet::a::TOR + + pmpcfg_octet::r::SET + + pmpcfg_octet::w::CLEAR + + pmpcfg_octet::x::SET + + pmpcfg_octet::l::SET) + .into(), + (kernel_text.0.end() as usize) >> 2, + ); + + // flash at n - 4: + write_pmpaddr_pmpcfg( + AVAILABLE_ENTRIES - 4, + (pmpcfg_octet::a::NAPOT + + pmpcfg_octet::r::SET + + pmpcfg_octet::w::CLEAR + + pmpcfg_octet::x::CLEAR + + pmpcfg_octet::l::SET) + .into(), + flash.0.napot_addr(), + ); + + // Now that the kernel has explicit region definitions for any + // memory that it needs to have access to, we can deny other memory + // accesses in our very last rule (n - 1): + write_pmpaddr_pmpcfg( + AVAILABLE_ENTRIES - 1, + (pmpcfg_octet::a::NAPOT + + pmpcfg_octet::r::CLEAR + + pmpcfg_octet::w::CLEAR + + pmpcfg_octet::x::CLEAR + + pmpcfg_octet::l::SET) + .into(), + // the entire address space: + 0x7FFFFFFF, + ); + + // Finally, we configure the non-locked user-mode deny all + // rule. This must never be removed, or otherwise usermode will be + // able to access all locked regions (which are supposed to be + // exclusively accessible to kernel-mode): + write_pmpaddr_pmpcfg( + AVAILABLE_ENTRIES - 7, + (pmpcfg_octet::a::NAPOT + + pmpcfg_octet::r::CLEAR + + pmpcfg_octet::w::CLEAR + + pmpcfg_octet::x::CLEAR + + pmpcfg_octet::l::CLEAR) + .into(), + // the entire address space: + 0x7FFFFFFF, + ); + + // Setup complete + Ok(KernelProtectionPMP) + } } - fn enable_kernel_mpu(&self, config: &mut Self::KernelMpuConfig) { - for (i, region) in config.regions.iter().rev().enumerate() { - let x = MAX_AVAILABLE_REGIONS_OVER_TWO - i - 1; - match region { - Some(r) => { - let cfg_val = r.cfg.value as usize; - let start = r.location.0 as usize; - let size = r.location.1; - - match x % 2 { - 0 => { - csr::CSR.pmpaddr_set((x * 2) + 1, (start + size) >> 2); - // Disable access up to the start address - csr::CSR.pmpconfig_modify( - x / 2, - csr::pmpconfig::pmpcfg::r0::CLEAR - + csr::pmpconfig::pmpcfg::w0::CLEAR - + csr::pmpconfig::pmpcfg::x0::CLEAR - + csr::pmpconfig::pmpcfg::a0::CLEAR, - ); - csr::CSR.pmpaddr_set(x * 2, start >> 2); - - // Set access to end address - csr::CSR - .pmpconfig_set(x / 2, cfg_val << 8 | csr::CSR.pmpconfig_get(x / 2)); - // Lock the CSR - csr::CSR.pmpconfig_modify(x / 2, csr::pmpconfig::pmpcfg::l1::SET); - } - 1 => { - csr::CSR.pmpaddr_set((x * 2) + 1, (start + size) >> 2); - // Disable access up to the start address - csr::CSR.pmpconfig_modify( - x / 2, - csr::pmpconfig::pmpcfg::r2::CLEAR - + csr::pmpconfig::pmpcfg::w2::CLEAR - + csr::pmpconfig::pmpcfg::x2::CLEAR - + csr::pmpconfig::pmpcfg::a2::CLEAR, - ); - csr::CSR.pmpaddr_set(x * 2, start >> 2); - - // Set access to end address - csr::CSR.pmpconfig_set( - x / 2, - cfg_val << 24 | csr::CSR.pmpconfig_get(x / 2), - ); - // Lock the CSR - csr::CSR.pmpconfig_modify(x / 2, csr::pmpconfig::pmpcfg::l3::SET); - } - _ => break, + impl TORUserPMP + for KernelProtectionPMP + { + /// Ensure that the MPU_REGIONS (starting at entry, and occupying two + /// entries per region) don't overflow the available entires, excluding + /// the 7 entires used for implementing the kernel memory protection. + const CONST_ASSERT_CHECK: () = assert!(MPU_REGIONS <= ((AVAILABLE_ENTRIES - 7) / 2)); + + fn available_regions(&self) -> usize { + // Always assume to have `MPU_REGIONS` usable TOR regions. We don't + // support locking additional regions at runtime. + MPU_REGIONS + } + + // This implementation is specific for 32-bit systems. We use + // `u32::from_be_bytes` and then cast to usize, as it manages to compile + // on 64-bit systems as well. However, this implementation will not work + // on RV64I systems, due to the changed pmpcfgX CSR layout. + fn configure_pmp( + &self, + regions: &[(TORUserPMPCFG, *const u8, *const u8); MPU_REGIONS], + ) -> Result<(), ()> { + // Could use `iter_array_chunks` once that's stable. + let mut regions_iter = regions.iter(); + let mut i = 0; + + while let Some(even_region) = regions_iter.next() { + let odd_region_opt = regions_iter.next(); + + if let Some(odd_region) = odd_region_opt { + // We can configure two regions at once which, given that we + // start at index 0 (an even offset), translates to a single + // CSR write for the pmpcfgX register: + csr::CSR.pmpconfig_set( + i / 2, + u32::from_be_bytes([ + odd_region.0.get(), + TORUserPMPCFG::OFF.get(), + even_region.0.get(), + TORUserPMPCFG::OFF.get(), + ]) as usize, + ); + + // Now, set the addresses of the respective regions, if they + // are enabled, respectively: + if even_region.0 != TORUserPMPCFG::OFF { + csr::CSR + .pmpaddr_set(i * 2 + 0, (even_region.1 as usize).overflowing_shr(2).0); + csr::CSR + .pmpaddr_set(i * 2 + 1, (even_region.2 as usize).overflowing_shr(2).0); } + + if odd_region.0 != TORUserPMPCFG::OFF { + csr::CSR + .pmpaddr_set(i * 2 + 2, (odd_region.1 as usize).overflowing_shr(2).0); + csr::CSR + .pmpaddr_set(i * 2 + 3, (odd_region.2 as usize).overflowing_shr(2).0); + } + + i += 2; + } else { + // Modify the first two pmpcfgX octets for this region: + csr::CSR.pmpconfig_modify( + i / 2, + FieldValue::::new( + 0x0000FFFF, + 0, + u32::from_be_bytes([ + 0, + 0, + even_region.0.get(), + TORUserPMPCFG::OFF.get(), + ]) as usize, + ), + ); + + // Set the addresses if the region is enabled: + if even_region.0 != TORUserPMPCFG::OFF { + csr::CSR + .pmpaddr_set(i * 2 + 0, (even_region.1 as usize).overflowing_shr(2).0); + csr::CSR + .pmpaddr_set(i * 2 + 1, (even_region.2 as usize).overflowing_shr(2).0); + } + + i += 1; } - None => {} - }; + } + + Ok(()) + } + + fn enable_user_pmp(&self) -> Result<(), ()> { + // No-op. User-mode regions are never enforced in machine-mode, and + // thus can be configured direct and may stay enabled in + // machine-mode. + Ok(()) + } + + fn disable_user_pmp(&self) { + // No-op. User-mode regions are never enforced in machine-mode, and + // thus can be configured direct and may stay enabled in + // machine-mode. + } + } + + impl fmt::Display for KernelProtectionPMP { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, " PMP hardware configuration -- entries: \r\n")?; + unsafe { super::format_pmp_entries::(f) } + } + } +} + +pub mod kernel_protection_mml_epmp { + use super::{pmpcfg_octet, NAPOTRegionSpec, TORRegionSpec, TORUserPMP, TORUserPMPCFG}; + use crate::csr; + use core::cell::Cell; + use core::fmt; + use kernel::platform::mpu; + use kernel::utilities::registers::interfaces::{Readable, Writeable}; + use kernel::utilities::registers::{FieldValue, LocalRegisterCopy}; + + // ---------- Kernel memory-protection PMP memory region wrapper types ----- + // + // These types exist primarily to avoid argument confusion in the + // [`KernelProtectionMMLEPMP`] constructor, which accepts the addresses of + // these memory regions as arguments. They further encode whether a region + // must adhere to the `NAPOT` or `TOR` addressing mode constraints: + + /// The flash memory region address range. + /// + /// Configured in the PMP as a `NAPOT` region. + #[derive(Copy, Clone, Debug)] + pub struct FlashRegion(pub NAPOTRegionSpec); + + /// The RAM region address range. + /// + /// Configured in the PMP as a `NAPOT` region. + #[derive(Copy, Clone, Debug)] + pub struct RAMRegion(pub NAPOTRegionSpec); + + /// The MMIO region address range. + /// + /// Configured in the PMP as a `NAPOT` region. + #[derive(Copy, Clone, Debug)] + pub struct MMIORegion(pub NAPOTRegionSpec); + + /// The PMP region specification for the kernel `.text` section. + /// + /// This is to be made accessible to machine-mode as read-execute. + /// Configured in the PMP as a `TOR` region. + #[derive(Copy, Clone, Debug)] + pub struct KernelTextRegion(pub TORRegionSpec); + + /// A RISC-V ePMP implementation which supports machine-mode (kernel) memory + /// protection by using the machine-mode lockdown mode (MML), with a fixed + /// number of "kernel regions" (such as `.text`, flash, RAM and MMIO). + /// + /// This implementation will configure the ePMP in the following way: + /// + /// - `mseccfg` CSR: + /// ```text + /// |-------------+-----------------------------------------------+-------| + /// | MSECCFG BIT | LABEL | STATE | + /// |-------------+-----------------------------------------------+-------| + /// | 0 | Machine-Mode Lockdown (MML) | 1 | + /// | 1 | Machine-Mode Whitelist Policy (MMWP) | 1 | + /// | 2 | Rule-Lock Bypass (RLB) | 0 | + /// |-------------+-----------------------------------------------+-------| + /// ``` + /// + /// - `pmpaddrX` / `pmpcfgX` CSRs: + /// ```text + /// |-------+-----------------------------------------+-------+---+-------| + /// | ENTRY | REGION / ADDR | MODE | L | PERMS | + /// |-------+-----------------------------------------+-------+---+-------| + /// | 0 | --------------------------------------- | OFF | X | ----- | + /// | 1 | Kernel .text section | TOR | X | R/X | + /// | | | | | | + /// | 2 | / \ | OFF | | | + /// | 3 | \ Userspace TOR region #0 / | TOR | | ????? | + /// | | | | | | + /// | 4 | / \ | OFF | | | + /// | 5 | \ Userspace TOR region #1 / | TOR | | ????? | + /// | | | | | | + /// | 6 ... | / \ | | | | + /// | n - 4 | \ Userspace TOR region #x / | | | | + /// | | | | | | + /// | n - 3 | FLASH (spanning kernel & apps) | NAPOT | X | R | + /// | | | | | | + /// | n - 2 | RAM (spanning kernel & apps) | NAPOT | X | R/W | + /// | | | | | | + /// | n - 1 | MMIO | NAPOT | X | R/W | + /// |-------+-----------------------------------------+-------+---+-------| + /// ``` + /// + /// Crucially, this implementation relies on an unconfigured hardware PMP + /// implementing the ePMP (`mseccfg` CSR) extension, providing the Machine + /// Lockdown Mode (MML) security bit. This bit is required to ensure that + /// any machine-mode (kernel) protection regions (lock bit set) are only + /// accessible to kernel mode. + pub struct KernelProtectionMMLEPMP { + user_pmp_enabled: Cell, + shadow_user_pmpcfgs: [Cell; MPU_REGIONS], + } + + impl + KernelProtectionMMLEPMP + { + // Start user-mode TOR regions after the first kernel .text region: + const TOR_REGIONS_OFFSET: usize = 1; + + pub unsafe fn new( + flash: FlashRegion, + ram: RAMRegion, + mmio: MMIORegion, + kernel_text: KernelTextRegion, + ) -> Result { + for i in 0..AVAILABLE_ENTRIES { + // Read the entry's CSR: + let pmpcfg_csr = csr::CSR.pmpconfig_get(i / 4); + + // Extract the entry's pmpcfg octet: + let pmpcfg: LocalRegisterCopy = LocalRegisterCopy::new( + pmpcfg_csr.overflowing_shr(((i % 4) * 8) as u32).0 as u8, + ); + + // As outlined above, we never touch a locked region. Thus, bail + // out if it's locked: + if pmpcfg.is_set(pmpcfg_octet::l) { + return Err(()); + } + + // Now that it's not locked, we can be sure that regardless of + // any ePMP bits, this region is either ignored or entirely + // denied for machine-mode access. Hence, we can change it in + // arbitrary ways without breaking our own memory access. Try to + // flip the R/W/X bits: + csr::CSR.pmpconfig_set(i / 4, pmpcfg_csr ^ (7 << ((i % 4) * 8))); + + // Check if the CSR changed: + if pmpcfg_csr == csr::CSR.pmpconfig_get(i / 4) { + // Didn't change! This means that this region is not backed + // by HW. Return an error as `AVAILABLE_ENTRIES` is + // incorrect: + return Err(()); + } + + // Finally, turn the region off: + csr::CSR.pmpconfig_set(i / 4, pmpcfg_csr & !(0x18 << ((i % 4) * 8))); + } + + // ----------------------------------------------------------------- + // Hardware PMP is verified to be in a compatible mode & state, and + // has at least `AVAILABLE_ENTRIES` entries. We have not yet checked + // whether the PMP is actually an _e_PMP. However, we don't want to + // produce a gadget to set RLB, and so the only safe way to test + // this is to set up the PMP regions and then try to enable the + // mseccfg bits. + // ----------------------------------------------------------------- + + // Helper to modify an arbitrary PMP entry. Because we don't know + // AVAILABLE_ENTRIES in advance, there's no good way to + // optimize this further. + fn write_pmpaddr_pmpcfg(i: usize, pmpcfg: u8, pmpaddr: usize) { + // Important to set the address first. Locking the pmpcfg + // register will also lock the adress register! + csr::CSR.pmpaddr_set(i, pmpaddr); + csr::CSR.pmpconfig_modify( + i / 4, + FieldValue::::new( + 0x000000FF_usize, + (i % 4) * 8, + u32::from_be_bytes([0, 0, 0, pmpcfg]) as usize, + ), + ); + } + + // Set the kernel `.text`, flash, RAM and MMIO regions, in no + // particular order, with the exception of `.text` and flash: + // `.text` must precede flash, as otherwise we'd be revoking execute + // permissions temporarily. Given that we can currently execute + // code, this should not have any impact on our accessible memory, + // assuming that the provided regions are not otherwise aliased. + + // `.text` at n - 5 and n - 4 (TOR region): + write_pmpaddr_pmpcfg( + 0, + (pmpcfg_octet::a::OFF + + pmpcfg_octet::r::CLEAR + + pmpcfg_octet::w::CLEAR + + pmpcfg_octet::x::CLEAR + + pmpcfg_octet::l::SET) + .into(), + (kernel_text.0.start() as usize) >> 2, + ); + write_pmpaddr_pmpcfg( + 1, + (pmpcfg_octet::a::TOR + + pmpcfg_octet::r::SET + + pmpcfg_octet::w::CLEAR + + pmpcfg_octet::x::SET + + pmpcfg_octet::l::SET) + .into(), + (kernel_text.0.end() as usize) >> 2, + ); + + // MMIO at n - 1: + write_pmpaddr_pmpcfg( + AVAILABLE_ENTRIES - 1, + (pmpcfg_octet::a::NAPOT + + pmpcfg_octet::r::SET + + pmpcfg_octet::w::SET + + pmpcfg_octet::x::CLEAR + + pmpcfg_octet::l::SET) + .into(), + mmio.0.napot_addr(), + ); + + // RAM at n - 2: + write_pmpaddr_pmpcfg( + AVAILABLE_ENTRIES - 2, + (pmpcfg_octet::a::NAPOT + + pmpcfg_octet::r::SET + + pmpcfg_octet::w::SET + + pmpcfg_octet::x::CLEAR + + pmpcfg_octet::l::SET) + .into(), + ram.0.napot_addr(), + ); + + // flash at n - 3: + write_pmpaddr_pmpcfg( + AVAILABLE_ENTRIES - 3, + (pmpcfg_octet::a::NAPOT + + pmpcfg_octet::r::SET + + pmpcfg_octet::w::CLEAR + + pmpcfg_octet::x::CLEAR + + pmpcfg_octet::l::SET) + .into(), + flash.0.napot_addr(), + ); + + // Finally, attempt to enable the MSECCFG security bits, and verify + // that they have been set correctly. If they have not been set to + // the written value, this means that this hardware either does not + // support ePMP, or it was in some invalid state otherwise. We don't + // need to read back the above regions, as we previous verified that + // none of their entries were locked -- so writing to them must work + // even without RLB set. + // + // Set RLB(2) = 0, MMWP(1) = 1, MML(0) = 1 + csr::CSR.mseccfg.set(0x00000003); + + // Read back the MSECCFG CSR to ensure that the machine's security + // configuration was set properly. If this fails, we have set up the + // PMP in a way that would give userspace access to kernel + // space. The caller of this method must appropriately handle this + // error condition by ensuring that the platform will never execute + // userspace code! + if csr::CSR.mseccfg.get() != 0x00000003 { + return Err(()); + } + + // Setup complete + const DEFAULT_USER_PMPCFG_OCTET: Cell = Cell::new(TORUserPMPCFG::OFF); + Ok(KernelProtectionMMLEPMP { + user_pmp_enabled: Cell::new(false), + shadow_user_pmpcfgs: [DEFAULT_USER_PMPCFG_OCTET; MPU_REGIONS], + }) + } + } + + impl TORUserPMP + for KernelProtectionMMLEPMP + { + // Ensure that the MPU_REGIONS (starting at entry, and occupying two + // entries per region) don't overflow the available entires, excluding + // the 7 entries used for implementing the kernel memory protection: + const CONST_ASSERT_CHECK: () = assert!(MPU_REGIONS <= ((AVAILABLE_ENTRIES - 5) / 2)); + + fn available_regions(&self) -> usize { + // Always assume to have `MPU_REGIONS` usable TOR regions. We don't + // support locking additional regions at runtime. + MPU_REGIONS + } + + // This implementation is specific for 32-bit systems. We use + // `u32::from_be_bytes` and then cast to usize, as it manages to compile + // on 64-bit systems as well. However, this implementation will not work + // on RV64I systems, due to the changed pmpcfgX CSR layout. + fn configure_pmp( + &self, + regions: &[(TORUserPMPCFG, *const u8, *const u8); MPU_REGIONS], + ) -> Result<(), ()> { + // Configure all of the regions' addresses and store their pmpcfg octets + // in our shadow storage. If the user PMP is already enabled, we further + // apply this configuration (set the pmpcfgX CSRs) by running + // `enable_user_pmp`: + for (i, (region, shadow_user_pmpcfg)) in regions + .iter() + .zip(self.shadow_user_pmpcfgs.iter()) + .enumerate() + { + // The ePMP in MML mode does not support read-write-execute + // regions. If such a region is to be configured, abort. As this + // loop here only modifies the shadow state, we can simply abort and + // return an error. We don't make any promises about the ePMP state + // if the configuration files, but it is still being activated with + // `enable_user_pmp`: + if region.0.get() + == >::from( + mpu::Permissions::ReadWriteExecute, + ) + .get() + { + return Err(()); + } + + // Set the CSR addresses for this region (if its not OFF, in which + // case the hardware-configured addresses are irrelevant): + if region.0 != TORUserPMPCFG::OFF { + csr::CSR.pmpaddr_set( + (i + Self::TOR_REGIONS_OFFSET) * 2 + 0, + (region.1 as usize).overflowing_shr(2).0, + ); + csr::CSR.pmpaddr_set( + (i + Self::TOR_REGIONS_OFFSET) * 2 + 1, + (region.2 as usize).overflowing_shr(2).0, + ); + } + + // Store the region's pmpcfg octet: + shadow_user_pmpcfg.set(region.0); + } + + // If the PMP is currently active, apply the changes to the CSRs: + if self.user_pmp_enabled.get() { + self.enable_user_pmp()?; + } + + Ok(()) + } + + fn enable_user_pmp(&self) -> Result<(), ()> { + // We store the "enabled" PMPCFG octets of user regions in the + // `shadow_user_pmpcfg` field, such that we can re-enable the PMP + // without a call to `configure_pmp` (where the `TORUserPMPCFG`s are + // provided by the caller). + + // Could use `iter_array_chunks` once that's stable. + let mut shadow_user_pmpcfgs_iter = self.shadow_user_pmpcfgs.iter(); + let mut i = Self::TOR_REGIONS_OFFSET; + + while let Some(first_region_pmpcfg) = shadow_user_pmpcfgs_iter.next() { + // If we're at a "region" offset divisible by two (where "region" = + // 2 PMP "entries"), then we can configure an entire `pmpcfgX` CSR + // in one operation. As CSR writes are expensive, this is an + // operation worth making: + let second_region_opt = if i % 2 == 0 { + shadow_user_pmpcfgs_iter.next() + } else { + None + }; + + if let Some(second_region_pmpcfg) = second_region_opt { + // We're at an even index and have two regions to configure, so + // do that with a single CSR write: + csr::CSR.pmpconfig_set( + i / 2, + u32::from_be_bytes([ + second_region_pmpcfg.get().get(), + TORUserPMPCFG::OFF.get(), + first_region_pmpcfg.get().get(), + TORUserPMPCFG::OFF.get(), + ]) as usize, + ); + + i += 2; + } else if i % 2 == 0 { + // This is a single region at an even index. Thus, modify the + // first two pmpcfgX octets for this region. + csr::CSR.pmpconfig_modify( + i / 2, + FieldValue::::new( + 0x0000FFFF, + 0, // lower two octets + u32::from_be_bytes([ + 0, + 0, + first_region_pmpcfg.get().get(), + TORUserPMPCFG::OFF.get(), + ]) as usize, + ), + ); + + i += 1; + } else { + // This is a single region at an odd index. Thus, modify the + // latter two pmpcfgX octets for this region. + csr::CSR.pmpconfig_modify( + i / 2, + FieldValue::::new( + 0x0000FFFF, + 16, // higher two octets + u32::from_be_bytes([ + 0, + 0, + first_region_pmpcfg.get().get(), + TORUserPMPCFG::OFF.get(), + ]) as usize, + ), + ); + + i += 1; + } + } + + self.user_pmp_enabled.set(true); + + Ok(()) + } + + fn disable_user_pmp(&self) { + // Simply set all of the user-region pmpcfg octets to OFF: + + let mut user_region_pmpcfg_octet_pairs = + (Self::TOR_REGIONS_OFFSET)..(Self::TOR_REGIONS_OFFSET + MPU_REGIONS); + while let Some(first_region_idx) = user_region_pmpcfg_octet_pairs.next() { + let second_region_opt = if first_region_idx % 2 == 0 { + user_region_pmpcfg_octet_pairs.next() + } else { + None + }; + + if let Some(_second_region_idx) = second_region_opt { + // We're at an even index and have two regions to configure, so + // do that with a single CSR write: + csr::CSR.pmpconfig_set( + first_region_idx / 2, + u32::from_be_bytes([ + TORUserPMPCFG::OFF.get(), + TORUserPMPCFG::OFF.get(), + TORUserPMPCFG::OFF.get(), + TORUserPMPCFG::OFF.get(), + ]) as usize, + ); + } else if first_region_idx % 2 == 0 { + // This is a single region at an even index. Thus, modify the + // first two pmpcfgX octets for this region. + csr::CSR.pmpconfig_modify( + first_region_idx / 2, + FieldValue::::new( + 0x0000FFFF, + 0, // lower two octets + u32::from_be_bytes([ + 0, + 0, + TORUserPMPCFG::OFF.get(), + TORUserPMPCFG::OFF.get(), + ]) as usize, + ), + ); + } else { + // This is a single region at an odd index. Thus, modify the + // latter two pmpcfgX octets for this region. + csr::CSR.pmpconfig_modify( + first_region_idx / 2, + FieldValue::::new( + 0x0000FFFF, + 16, // higher two octets + u32::from_be_bytes([ + 0, + 0, + TORUserPMPCFG::OFF.get(), + TORUserPMPCFG::OFF.get(), + ]) as usize, + ), + ); + } + } + + self.user_pmp_enabled.set(false); + } + } + + impl fmt::Display + for KernelProtectionMMLEPMP + { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + " ePMP configuration:\r\n mseccfg: {:#08X}, user-mode PMP active: {:?}, entries:\r\n", + csr::CSR.mseccfg.get(), + self.user_pmp_enabled.get() + )?; + unsafe { super::format_pmp_entries::(f) }?; + + write!(f, " Shadow PMP entries for user-mode:\r\n")?; + for (i, shadowed_pmpcfg) in self.shadow_user_pmpcfgs.iter().enumerate() { + let (start_pmpaddr_label, startaddr_pmpaddr, endaddr, mode) = + if shadowed_pmpcfg.get() == TORUserPMPCFG::OFF { + ( + "pmpaddr", + csr::CSR.pmpaddr_get((i + Self::TOR_REGIONS_OFFSET) * 2), + 0, + "OFF", + ) + } else { + ( + " start", + csr::CSR + .pmpaddr_get((i + Self::TOR_REGIONS_OFFSET) * 2) + .overflowing_shl(2) + .0, + csr::CSR + .pmpaddr_get((i + Self::TOR_REGIONS_OFFSET) * 2 + 1) + .overflowing_shl(2) + .0 + | 0b11, + "TOR", + ) + }; + + write!( + f, + " [{:02}]: {}={:#010X}, end={:#010X}, cfg={:#04X} ({} ) ({}{}{}{})\r\n", + (i + Self::TOR_REGIONS_OFFSET) * 2 + 1, + start_pmpaddr_label, + startaddr_pmpaddr, + endaddr, + shadowed_pmpcfg.get().get(), + mode, + if shadowed_pmpcfg.get().get_reg().is_set(pmpcfg_octet::l) { + "l" + } else { + "-" + }, + if shadowed_pmpcfg.get().get_reg().is_set(pmpcfg_octet::r) { + "r" + } else { + "-" + }, + if shadowed_pmpcfg.get().get_reg().is_set(pmpcfg_octet::w) { + "w" + } else { + "-" + }, + if shadowed_pmpcfg.get().get_reg().is_set(pmpcfg_octet::x) { + "x" + } else { + "-" + }, + )?; + } + + Ok(()) } } } diff --git a/arch/rv32i/src/support.rs b/arch/rv32i/src/support.rs index b824675575..ad148da50a 100644 --- a/arch/rv32i/src/support.rs +++ b/arch/rv32i/src/support.rs @@ -1,12 +1,16 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Core low-level operations. use crate::csr::{mstatus::mstatus, CSR}; -use core::ops::FnOnce; #[cfg(all(target_arch = "riscv32", target_os = "none"))] #[inline(always)] /// NOP instruction pub fn nop() { + use core::arch::asm; unsafe { asm!("nop", options(nomem, nostack, preserves_flags)); } @@ -16,6 +20,7 @@ pub fn nop() { #[inline(always)] /// WFI instruction pub unsafe fn wfi() { + use core::arch::asm; asm!("wfi", options(nomem, nostack)); } @@ -45,13 +50,13 @@ where } // Mock implementations for tests on Travis-CI. -#[cfg(not(any(target_arch = "riscv32", target_os = "none")))] +#[cfg(not(all(target_arch = "riscv32", target_os = "none")))] /// NOP instruction (mock) pub fn nop() { unimplemented!() } -#[cfg(not(any(target_arch = "riscv32", target_os = "none")))] +#[cfg(not(all(target_arch = "riscv32", target_os = "none")))] /// WFI instruction (mock) pub unsafe fn wfi() { unimplemented!() diff --git a/arch/rv32i/src/syscall.rs b/arch/rv32i/src/syscall.rs index 8b359df162..5ff671443c 100644 --- a/arch/rv32i/src/syscall.rs +++ b/arch/rv32i/src/syscall.rs @@ -1,12 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Kernel-userland system call interface for RISC-V architecture. -use core::convert::TryInto; use core::fmt::Write; use core::mem::size_of; use core::ops::Range; use crate::csr::mcause; -use kernel; use kernel::errorcode::ErrorCode; use kernel::syscall::ContextSwitchReason; @@ -164,7 +166,7 @@ impl kernel::syscall::UserspaceKernelBoundary for SysCall { let (_, r) = state.regs.split_at_mut(R_A0); // This comes with the assumption that the respective - // registers are stored at monotonically increasing indicies + // registers are stored at monotonically increasing indices // in the register slice let (a0slice, r) = r.split_at_mut(R_A1 - R_A0); let (a1slice, r) = r.split_at_mut(R_A2 - R_A1); @@ -201,7 +203,7 @@ impl kernel::syscall::UserspaceKernelBoundary for SysCall { // process is executing then `state.pc` is invalid/useless, but the // application must ignore it anyway since there is nothing logically // for it to return to. So this doesn't hurt anything. - state.regs[R_RA] = state.pc as u32; + state.regs[R_RA] = state.pc; // Save the PC we expect to execute. state.pc = callback.pc as u32; @@ -210,7 +212,7 @@ impl kernel::syscall::UserspaceKernelBoundary for SysCall { } // Mock implementation for tests on Travis-CI. - #[cfg(not(any(target_arch = "riscv32", target_os = "none")))] + #[cfg(not(all(target_arch = "riscv32", target_os = "none")))] unsafe fn switch_to_process( &self, _accessible_memory_start: *const u8, @@ -230,6 +232,7 @@ impl kernel::syscall::UserspaceKernelBoundary for SysCall { _app_brk: *const u8, state: &mut Riscv32iStoredState, ) -> (ContextSwitchReason, Option<*const u8>) { + use core::arch::asm; // We need to ensure that the compiler does not reorder // kernel memory writes to after the userspace context switch // to ensure we provide a consistent memory view of @@ -241,225 +244,374 @@ impl kernel::syscall::UserspaceKernelBoundary for SysCall { // will issue arbitrary memory accesses (acting as a compiler // fence). asm!(" - // Before switching to the app we need to save the kernel registers to - // the kernel stack. We then save the stack pointer in the mscratch - // CSR (0x340) so we can retrieve it after returning to the kernel - // from the app. + // Before switching to the app we need to save some kernel registers + // to the kernel stack, specifically ones which we can't mark as + // clobbered in the asm!() block. We then save the stack pointer in + // the mscratch CSR so we can retrieve it after returning to the + // kernel from the app. // // A few values get saved to the kernel stack, including an app // register temporarily after entering the trap handler. Here is a // memory map to make it easier to keep track: // // ``` - // 34*4(sp): <- original stack pointer - // 33*4(sp): - // 32*4(sp): x31 - // 31*4(sp): x30 - // 30*4(sp): x29 - // 29*4(sp): x28 - // 28*4(sp): x27 - // 27*4(sp): x26 - // 26*4(sp): x25 - // 25*4(sp): x24 - // 24*4(sp): x23 - // 23*4(sp): x22 - // 22*4(sp): x21 - // 21*4(sp): x20 - // 20*4(sp): x19 - // 19*4(sp): x18 - // 18*4(sp): x17 - // 17*4(sp): x16 - // 16*4(sp): x15 - // 15*4(sp): x14 - // 14*4(sp): x13 - // 13*4(sp): x12 - // 12*4(sp): x11 - // 11*4(sp): x10 - // 10*4(sp): x9 - // 9*4(sp): x8 - // 8*4(sp): x7 - // 7*4(sp): x6 - // 6*4(sp): x5 - // 5*4(sp): x4 - // 4*4(sp): x3 - // 3*4(sp): x1 - // 2*4(sp): _return_to_kernel (100) (address to resume after trap) - // 1*4(sp): *state (Per-process StoredState struct) - // 0*4(sp): app s0 <- new stack pointer + // 8*4(sp): <- original stack pointer + // 7*4(sp): + // 6*4(sp): x9 / s1 + // 5*4(sp): x8 / s0 / fp + // 4*4(sp): x4 / tp + // 3*4(sp): x3 / gp + // 2*4(sp): x10 / a0 (*state, Per-process StoredState struct) + // 1*4(sp): custom trap handler address + // 0*4(sp): scratch space, having s1 written to by the trap handler + // <- new stack pointer // ``` - addi sp, sp, -34*4 // Move the stack pointer down to make room. - - sw x1, 3*4(sp) // Save all of the registers on the kernel stack. - sw x3, 4*4(sp) - sw x4, 5*4(sp) - sw x5, 6*4(sp) - sw x6, 7*4(sp) - sw x7, 8*4(sp) - sw x8, 9*4(sp) - sw x9, 10*4(sp) - sw x10, 11*4(sp) - sw x11, 12*4(sp) - sw x12, 13*4(sp) - sw x13, 14*4(sp) - sw x14, 15*4(sp) - sw x15, 16*4(sp) - sw x16, 17*4(sp) - sw x17, 18*4(sp) - sw x18, 19*4(sp) - sw x19, 20*4(sp) - sw x20, 21*4(sp) - sw x21, 22*4(sp) - sw x22, 23*4(sp) - sw x23, 24*4(sp) - sw x24, 25*4(sp) - sw x25, 26*4(sp) - sw x26, 27*4(sp) - sw x27, 28*4(sp) - sw x28, 29*4(sp) - sw x29, 30*4(sp) - sw x30, 31*4(sp) - sw x31, 32*4(sp) - - sw a0, 1*4(sp) // Store process state pointer on stack as well. - // We need to have this available for after the app - // returns to the kernel so we can store its + addi sp, sp, -8*4 // Move the stack pointer down to make room. + + // Save all registers on the kernel stack which cannot be clobbered + // by an asm!() block. These are mostly registers which have a + // designated purpose (e.g. stack pointer) or are used internally + // by LLVM. + sw x9, 6*4(sp) // s1 (used internally by LLVM) + sw x8, 5*4(sp) // fp (can't be clobbered / used as an operand) + sw x4, 4*4(sp) // tp (can't be clobbered / used as an operand) + sw x3, 3*4(sp) // gp (can't be clobbered / used as an operand) + + sw x10, 2*4(sp) // Store process state pointer on stack as well. + // We need to have this available for after the + // app returns to the kernel so we can store its // registers. - // From here on we can't allow the CPU to take interrupts - // anymore, as that might result in the trap handler - // believing that a context switch to userspace already - // occurred (as mscratch is non-zero). Restore the userspace - // state fully prior to enabling interrupts again - // (implicitly using mret). + // Load the address of `_start_app_trap` into `1*4(sp)`. We swap our + // stack pointer into the mscratch CSR and the trap handler will load + // and jump to the address at this offset. + la t0, 100f // t0 = _start_app_trap + sw t0, 1*4(sp) // 1*4(sp) = t0 + + // sw x0, 0*4(sp) // Reserved as scratch space for the trap handler + + // -----> All required registers saved to the stack. + // sp holds the updated stack pointer, a0 the per-process state + + // From here on we can't allow the CPU to take interrupts anymore, as + // we re-route traps to `_start_app_trap` below (by writing our stack + // pointer into the mscratch CSR), and we rely on certain CSRs to not + // be modified or used in their intermediate states (e.g., mepc). + // + // We atomically switch to user-mode and re-enable interrupts using + // the `mret` instruction below. // - // If this is executed _after_ setting mscratch, this result - // in the race condition of [PR - // 2308](https://github.com/tock/tock/pull/2308) + // If interrupts are disabled _after_ setting mscratch, this result in + // the race condition of [PR 2308](https://github.com/tock/tock/pull/2308) // Therefore, clear the following bits in mstatus first: // 0x00000008 -> bit 3 -> MIE (disabling interrupts here) // + 0x00001800 -> bits 11,12 -> MPP (switch to usermode on mret) - li t0, 0x00001808 - csrrc x0, 0x300, t0 // clear bits in mstatus, don't care about read + li t0, 0x00001808 + csrc mstatus, t0 // clear bits in mstatus // Afterwards, set the following bits in mstatus: // 0x00000080 -> bit 7 -> MPIE (enable interrupts on mret) - li t0, 0x00000080 - csrrs x0, 0x300, t0 // set bits in mstatus, don't care about read - + li t0, 0x00000080 + csrs mstatus, t0 // set bits in mstatus - // Store the address to jump back to on the stack so that the trap - // handler knows where to return to after the app stops executing. + // Execute `_start_app_trap` on a trap by setting the mscratch trap + // handler address to our current stack pointer. This stack pointer, + // at `1*4(sp)`, holds the address of `_start_app_trap`. // - // In asm!() we can't use the shorthand `li` pseudo-instruction, as it - // complains about _return_to_kernel (100) not being a constant in the - // required range. - lui t0, %hi(100f) - addi t0, t0, %lo(100f) - sw t0, 2*4(sp) - - csrw 0x340, sp // Save stack pointer in mscratch. This allows - // us to find it when the app returns back to - // the kernel. + // Upon a trap, the global trap handler (_start_trap) will swap `s0` + // with the `mscratch` CSR and, if it contains a non-zero address, + // jump to the address that is now at `1*4(s0)`. This allows us to + // hook a custom trap handler that saves all userspace state: + // + csrw mscratch, sp // Store `sp` in mscratch CSR. Discard the + // prior value, must have been set to zero. // We have to set the mepc CSR with the PC we want the app to start // executing at. This has been saved in Riscv32iStoredState for us // (either when the app returned back to the kernel or in the // `set_process_function()` function). - lw t0, 31*4(a0) // Retrieve the PC from Riscv32iStoredState - csrw 0x341, t0 // Set mepc CSR. This is the PC we want to go to. + lw t0, 31*4(a0) // Retrieve the PC from Riscv32iStoredState + csrw mepc, t0 // Set mepc CSR to the app's PC. // Restore all of the app registers from what we saved. If this is the // first time running the app then most of these values are // irrelevant, However we do need to set the four arguments to the - // `_start_ function in the app. If the app has been executing then this - // allows the app to correctly resume. - mv t0, a0 // Save the state pointer to a specific register. - lw x1, 0*4(t0) // ra - lw x2, 1*4(t0) // sp - lw x3, 2*4(t0) // gp - lw x4, 3*4(t0) // tp - lw x6, 5*4(t0) // t1 - lw x7, 6*4(t0) // t2 - lw x8, 7*4(t0) // s0,fp - lw x9, 8*4(t0) // s1 - lw x10, 9*4(t0) // a0 - lw x11, 10*4(t0) // a1 - lw x12, 11*4(t0) // a2 - lw x13, 12*4(t0) // a3 - lw x14, 13*4(t0) // a4 - lw x15, 14*4(t0) // a5 - lw x16, 15*4(t0) // a6 - lw x17, 16*4(t0) // a7 - lw x18, 17*4(t0) // s2 - lw x19, 18*4(t0) // s3 - lw x20, 19*4(t0) // s4 - lw x21, 20*4(t0) // s5 - lw x22, 21*4(t0) // s6 - lw x23, 22*4(t0) // s7 - lw x24, 23*4(t0) // s8 - lw x25, 24*4(t0) // s9 - lw x26, 25*4(t0) // s10 - lw x27, 26*4(t0) // s11 - lw x28, 27*4(t0) // t3 - lw x29, 28*4(t0) // t4 - lw x30, 29*4(t0) // t5 - lw x31, 30*4(t0) // t6 - lw x5, 4*4(t0) // t0. Do last since we overwrite our pointer. + // `_start_ function in the app. If the app has been executing then + // this allows the app to correctly resume. + + // We do a little switcheroo here, and place the per-process stored + // state pointer into the `sp` register instead of `a0`. Doing so + // allows us to use compressed instructions for all of these loads: + mv sp, a0 // sp <- a0 (per-process stored state) + + lw x1, 0*4(sp) // ra + // ------------------------> sp, do last since we overwrite our pointer + lw x3, 2*4(sp) // gp + lw x4, 3*4(sp) // tp + lw x5, 4*4(sp) // t0 + lw x6, 5*4(sp) // t1 + lw x7, 6*4(sp) // t2 + lw x8, 7*4(sp) // s0,fp + lw x9, 8*4(sp) // s1 + lw x10, 9*4(sp) // a0 + lw x11, 10*4(sp) // a1 + lw x12, 11*4(sp) // a2 + lw x13, 12*4(sp) // a3 + lw x14, 13*4(sp) // a4 + lw x15, 14*4(sp) // a5 + lw x16, 15*4(sp) // a6 + lw x17, 16*4(sp) // a7 + lw x18, 17*4(sp) // s2 + lw x19, 18*4(sp) // s3 + lw x20, 19*4(sp) // s4 + lw x21, 20*4(sp) // s5 + lw x22, 21*4(sp) // s6 + lw x23, 22*4(sp) // s7 + lw x24, 23*4(sp) // s8 + lw x25, 24*4(sp) // s9 + lw x26, 25*4(sp) // s10 + lw x27, 26*4(sp) // s11 + lw x28, 27*4(sp) // t3 + lw x29, 28*4(sp) // t4 + lw x30, 29*4(sp) // t5 + lw x31, 30*4(sp) // t6 + lw x2, 1*4(sp) // sp, overwriting our pointer // Call mret to jump to where mepc points, switch to user mode, and // start running the app. mret + // The global trap handler will jump to this address when catching a + // trap while the app is executing (address loaded into the mscratch + // CSR). + // + // This custom trap handler is responsible for saving application + // state, clearing the custom trap handler (mscratch = 0), and + // restoring the kernel context. + 100: // _start_app_trap + + // At this point all we know is that we entered the trap handler from + // an app. We don't know _why_ we got a trap, it could be from an + // interrupt, syscall, or fault (or maybe something else). Therefore + // we have to be very careful not to overwrite any registers before we + // have saved them. + // + // The global trap handler has swapped the app's `s0` into the + // mscratch CSR, which now contains the address of our stack pointer. + // The global trap handler further clobbered `s1`, which now contains + // the address of `_start_app_trap`. The app's `s1` is saved at + // `0*4(s0)`. + // + // Thus we can clobber `s1` and load the address of the per-process + // stored state: + // + lw s1, 2*4(s0) + + // With the per-process stored state address in `t1`, save all + // non-clobbered registers. Save the `sp` first, then do the same + // switcheroo as above, moving the per-process stored state pointer + // into `sp`. This allows us to use compressed instructions for all + // these stores: + sw x2, 1*4(s1) // Save app's sp + mv sp, s1 // sp <- s1 (per-process stored state) + + // Now, store relative to `sp` (per-process stored state) with + // compressed instructions: + sw x1, 0*4(sp) // ra + // ------------------------> sp, saved above + sw x3, 2*4(sp) // gp + sw x4, 3*4(sp) // tp + sw x5, 4*4(sp) // t0 + sw x6, 5*4(sp) // t1 + sw x7, 6*4(sp) // t2 + // ------------------------> s0, in mscratch right now + // ------------------------> s1, stored at 0*4(s0) right now + sw x10, 9*4(sp) // a0 + sw x11, 10*4(sp) // a1 + sw x12, 11*4(sp) // a2 + sw x13, 12*4(sp) // a3 + sw x14, 13*4(sp) // a4 + sw x15, 14*4(sp) // a5 + sw x16, 15*4(sp) // a6 + sw x17, 16*4(sp) // a7 + sw x18, 17*4(sp) // s2 + sw x19, 18*4(sp) // s3 + sw x20, 19*4(sp) // s4 + sw x21, 20*4(sp) // s5 + sw x22, 21*4(sp) // s6 + sw x23, 22*4(sp) // s7 + sw x24, 23*4(sp) // s8 + sw x25, 24*4(sp) // s9 + sw x26, 25*4(sp) // s10 + sw x27, 26*4(sp) // s11 + sw x28, 27*4(sp) // t3 + sw x29, 28*4(sp) // t4 + sw x30, 29*4(sp) // t5 + sw x31, 30*4(sp) // t6 + + // At this point, we can restore s0 into our stack pointer: + mv sp, s0 + + // Now retrieve the original value of s1 and save that as well. We + // must not clobber s1, our per-process stored state pointer. + lw s0, 0*4(sp) // s0 = app s1 (from trap handler scratch space) + sw s0, 8*4(s1) // Save app s1 to per-process state + + // Retrieve the original value of s0 from the mscratch CSR, save it. + // + // This will also restore the kernel trap handler by writing zero to + // the CSR. `csrrw` allows us to read and write the CSR in a single + // instruction: + csrrw s0, mscratch, zero // s0 <- mscratch[app s0] <- zero + sw s0, 7*4(s1) // Save app s0 to per-process state + + // ------------------------------------------------------------------- + // At this point, the entire app register file is saved. We also + // restored the kernel trap handler. We have restored the following + // kernel registers: + // + // - sp: kernel stack pointer + // - s1: per-process stored state pointer + // + // We avoid clobbering those registers from this point onward. + // ------------------------------------------------------------------- + // We also need to store some other information about the trap reason, + // present in CSRs: + // + // - the app's PC (mepc), + // - the trap reason (mcause), + // - the trap 'value' (mtval, e.g., faulting address). + // + // We need to store mcause because we use that to determine why the + // app stopped executing and returned to the kernel. We store mepc + // because it is where we need to return to in the app at some + // point. We need to store mtval in case the app faulted and we need + // mtval to help with debugging. + // + // We use `s0` as a scratch register, as it fits into the 3-bit + // register argument of RISC-V compressed loads / stores: + + // Save the PC to the stored state struct. We also load the address + // of _return_to_kernel into it, as this will be where we jump on + // the mret instruction, which leaves the trap handler. + la s0, 300f // Load _return_to_kernel into t0. + csrrw s0, mepc, s0 // s0 <- mepc[app pc] <- _return_to_kernel + sw s0, 31*4(s1) // Store app's pc in stored state struct. + + // Save mtval to the stored state struct + csrr s0, mtval + sw s0, 33*4(s1) + + // Save mcause and leave it loaded into a0, as we call a function + // with it below: + csrr a0, mcause + sw a0, 32*4(s1) + + // Depending on the value of a0, we might be calling into a function + // while still in the trap handler. The callee may rely on the `gp`, + // `tp`, and `fp` (s0) registers to be set correctly. Thus we restore + // them here, as we need to do anyways. They are saved registers, + // and so we avoid clobbering them beyond this point. + // + // We do not restore `s1`, as we need to move it back into `a0` + // _after_ potentially invoking the _disable_interrupt_... function. + // LLVM relies on it to not be clobbered internally, but it is not + // part of the RISC-V C ABI, which we need to follow here. + // + lw x8, 5*4(sp) // fp/s0: Restore the frame pointer + lw x4, 4*4(sp) // tp: Restore the thread pointer + lw x3, 3*4(sp) // gp: Restore the global pointer + + // -------------------------------------------------------------------- + // From this point onward, avoid clobbering the following registers: + // + // - x2 / sp: kernel stack pointer + // - x3 / gp: kernel global pointer + // - x4 / tp: kernel thread pointer + // - x8 / s0 / fp: kernel frame pointer + // - x9 / s1: per-process stored state pointer + // + // -------------------------------------------------------------------- + + // Now we need to check if this was an interrupt, and if it was, + // then we need to disable the interrupt before returning from this + // trap handler so that it does not fire again. + // + // If mcause is greater than or equal to zero this was not an + // interrupt (i.e. the most significant bit is not 1). In this case, + // jump to _start_app_trap_continue. + bge a0, zero, 200f + + // This was an interrupt. Call the interrupt disable function, with + // mcause already loaded in a0. + // + // This may clobber all caller-saved registers. However, at this + // stage, we only restored `sp`, `s1`, and the registers above, all of + // which are saved. Thus we don't have to worry about the function + // call clobbering these registers. + // + jal ra, _disable_interrupt_trap_rust_from_app + 200: // _start_app_trap_continue + + // Need to set mstatus.MPP to 0b11 so that we stay in machine mode. + // + // We use `a0` as a scratch register, as we are allowed to clobber it + // here, and it fits into a compressed load instruction. We must avoid + // using restored saved registers like `s0`, etc. + // + li a0, 0x1800 // Load 0b11 to the MPP bits location in a0 + csrs mstatus, a0 // mstatus |= a0 + + // Use mret to exit the trap handler and return to the context + // switching code. We loaded the address of _return_to_kernel + // into mepc above. + mret // This is where the trap handler jumps back to after the app stops // executing. - 100: // _return_to_kernel + 300: // _return_to_kernel // We have already stored the app registers in the trap handler. We - // can restore the kernel registers before resuming kernel code. - lw x1, 3*4(sp) - lw x3, 4*4(sp) - lw x4, 5*4(sp) - lw x5, 6*4(sp) - lw x6, 7*4(sp) - lw x7, 8*4(sp) - lw x8, 9*4(sp) - lw x9, 10*4(sp) - lw x10, 11*4(sp) - lw x11, 12*4(sp) - lw x12, 13*4(sp) - lw x13, 14*4(sp) - lw x14, 15*4(sp) - lw x15, 16*4(sp) - lw x16, 17*4(sp) - lw x17, 18*4(sp) - lw x18, 19*4(sp) - lw x19, 20*4(sp) - lw x20, 21*4(sp) - lw x21, 22*4(sp) - lw x22, 23*4(sp) - lw x23, 24*4(sp) - lw x24, 25*4(sp) - lw x25, 26*4(sp) - lw x26, 27*4(sp) - lw x27, 28*4(sp) - lw x28, 29*4(sp) - lw x29, 30*4(sp) - lw x30, 31*4(sp) - lw x31, 32*4(sp) - - addi sp, sp, 34*4 // Reset kernel stack pointer - ", - - // The register to put the state struct pointer in is not - // particularly relevant, however we must avoid using t0 - // as that is overwritten prior to being accessed - // (although stored and later restored) in the assembly - in("a0") state as *mut Riscv32iStoredState, + // have further restored `gp`, `tp`, `fp`/`s0` and the stack pointer. + // + // The only other non-clobbered registers are `s1` and `a0`, where + // `a0` needs to hold the per-process state pointer currently stored + // in `s1`, and the original value of `s1` is saved on the stack. + // Restore them: + // + mv a0, s1 // a0 = per-process stored state + lw s1, 6*4(sp) // restore s1 (used by LLVM internally) + + // We need thus need to mark all registers as clobbered, except: + // + // - x2 (sp) + // - x3 (gp) + // - x4 (tp) + // - x8 (fp) + // - x9 (s1) + // - x10 (a0) + + addi sp, sp, 8*4 // Reset kernel stack pointer + ", + + // We pass the per-process state struct in a register we are allowed + // to clobber (not s0 or s1), but still fits into 3-bit register + // arguments of compressed load- & store-instructions. + in("x10") state as *mut Riscv32iStoredState, + + // Clobber all registers which can be marked as clobbered, except + // for `a0` / `x10`. By making it retain the value of `&mut state`, + // which we need to stack manually anyway, we can avoid Rust/LLVM + // stacking it redundantly for us. + out("x1") _, out("x5") _, out("x6") _, out("x7") _, out("x11") _, + out("x12") _, out("x13") _, out("x14") _, out("x15") _, out("x16") _, + out("x17") _, out("x18") _, out("x19") _, out("x20") _, out("x21") _, + out("x22") _, out("x23") _, out("x24") _, out("x25") _, out("x26") _, + out("x27") _, out("x28") _, out("x29") _, out("x30") _, out("x31") _, ); let ret = match mcause::Trap::from(state.mcause as usize) { @@ -474,7 +626,7 @@ impl kernel::syscall::UserspaceKernelBoundary for SysCall { mcause::Exception::UserEnvCall | mcause::Exception::MachineEnvCall => { // Need to increment the PC so when we return we start at the correct // instruction. The hardware does not do this for us. - state.pc += 4; + state.pc = state.pc.wrapping_add(4); let syscall = kernel::syscall::Syscall::from_register_arguments( state.regs[R_A4] as u8, diff --git a/boards/Makefile.common b/boards/Makefile.common index 28f371f784..dff3d8281a 100644 --- a/boards/Makefile.common +++ b/boards/Makefile.common @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Force the Shell to be bash as some systems have strange default shells SHELL := bash @@ -19,27 +23,34 @@ RUSTC_SYSROOT := "$(shell rustc --print sysroot)" # Common defaults that specific boards can override, but likely do not need to. # -# The TOOLCHAIN parameter is set to the magic value "llvm-tools-preview", which -# will cause the Makefile to resolve the llvm toolchain installed as part of the -# rustup component "llvm-tools-preview". In case a system toolchain shall be -# used, this can be overridden to specify the toolchain prefix, e.g. "llvm" for +# The TOOLCHAIN parameter is set to the magic value "llvm-tools", which will +# cause the Makefile to resolve the llvm toolchain installed as part of the +# rustup component "llvm-tools". In case a system toolchain shall be used, this +# can be overridden to specify the toolchain prefix, e.g. "llvm" for # llvm-{objdump,objcopy,...} or "arm-none-eabi". -TOOLCHAIN ?= llvm-tools-preview +TOOLCHAIN ?= llvm-tools CARGO ?= cargo +# Not all platforms support the rustup tool. Those that do not can pass +# `NO_RUSTUP=1` to make and then all of the rustup commands will be ignored. ifeq ($(NO_RUSTUP),) -RUSTUP ?= rustup + RUSTUP ?= rustup else -RUSTUP ?= true + RUSTUP ?= true endif -# Default location of target directory (relative to board makefile) -# passed to cargo --target_dir +# Default location of target directory (relative to board makefile) passed to +# cargo `--target_dir`. TARGET_DIRECTORY ?= $(TOCK_ROOT_DIRECTORY)target/ # RUSTC_FLAGS allows boards to define board-specific options. # This will hopefully move into Cargo.toml (or Cargo.toml.local) eventually. # +# - `-Tlayout.ld`: Use the linker script `layout.ld` all boards must provide. +# - `linker=rust-lld`: Tell rustc to use the LLVM linker. This avoids needing +# GCC as a dependency to build the kernel. +# - `linker-flavor=ld.lld`: Use the LLVM lld executable with the `-flavor gnu` +# flag. # - `relocation-model=static`: See https://github.com/tock/tock/pull/2853 # - `-nmagic`: lld by default uses a default page size to align program # sections. Tock expects that program sections are set back-to-back. `-nmagic` @@ -49,6 +60,9 @@ TARGET_DIRECTORY ?= $(TOCK_ROOT_DIRECTORY)target/ # and the downside to `all` is that different functions in the code can end up # with the same address in the binary. However, it can save a fair bit of code # size. +# - `-C symbol-mangling-version=v0`: Opt-in to Rust v0 symbol mangling scheme. +# See https://github.com/rust-lang/rust/issues/60705 and +# https://github.com/tock/tock/issues/3529. RUSTC_FLAGS ?= \ -C link-arg=-Tlayout.ld \ -C linker=rust-lld \ @@ -56,31 +70,36 @@ RUSTC_FLAGS ?= \ -C relocation-model=static \ -C link-arg=-nmagic \ -C link-arg=-icf=all \ + -C symbol-mangling-version=v0 \ # RISC-V-specific flags. ifneq ($(findstring riscv32i, $(TARGET)),) - # NOTE: This flag causes kernel panics on some ARM cores. Since the - # size benefit is almost exclusively for RISC-V, we only apply it for - # those targets. + # NOTE: This flag causes kernel panics on some ARM cores. Since the size + # benefit is almost exclusively for RISC-V, we only apply it for those + # targets. RUSTC_FLAGS += -C force-frame-pointers=no + # Ensure relocations generated is eligible for linker relaxation. + # This provide huge space savings. + RUSTC_FLAGS += -C target-feature=+relax endif -# RUSTC_FLAGS_TOCK by default extends RUSTC_FLAGS with options -# that are global to all Tock boards. +# RUSTC_FLAGS_TOCK by default extends RUSTC_FLAGS with options that are global +# to all Tock boards. # # We use `remap-path-prefix` to remove user-specific filepath strings for error # reporting from appearing in the generated binary. The first line is used for -# remapping the tock directory, and the second line is for remapping paths to the -# source code of the core library, which end up in the binary as a result of our use -# of -Zbuild-std=core. +# remapping the tock directory, and the second line is for remapping paths to +# the source code of the core library, which end up in the binary as a result of +# our use of `-Zbuild-std=core`. RUSTC_FLAGS_TOCK ?= \ $(RUSTC_FLAGS) \ --remap-path-prefix=$(TOCK_ROOT_DIRECTORY)= \ --remap-path-prefix=$(RUSTC_SYSROOT)/lib/rustlib/src/rust/library/core=/core/ -# Disallow warnings for continuous integration builds. Disallowing them here -# ensures that warnings during testing won't prevent compilation from succeeding. -ifeq ($(CI),true) +# Disallow warnings only for continuous integration builds. Disallowing them +# here using the `RUSTC_FLAGS_TOCK` variable ensures that warnings during +# testing won't prevent compilation from succeeding. +ifeq ($(NOWARNINGS),true) RUSTC_FLAGS_TOCK += -D warnings endif @@ -111,106 +130,138 @@ RUSTC_FLAGS_FOR_BIN ?= \ check_defined = $(strip $(foreach 1,$1,$(if $(value $1),,$(error Undefined variable "$1")))) # Check that we know the basics of what we are compiling for. -# `PLATFORM`: The name of the board that the kernel is being compiled for. -# `TARGET` : The Rust target architecture the kernel is being compiled for. +# - `PLATFORM`: The name of the board that the kernel is being compiled for. +# - `TARGET` : The Rust target architecture the kernel is being compiled for. $(call check_defined, PLATFORM) $(call check_defined, TARGET) -# Location of target-specific build +# Location of target-specific build. TARGET_PATH := $(TARGET_DIRECTORY)$(TARGET) -# If environment variable V is non-empty, be verbose. +# If environment variable V or VERBOSE is non-empty, be verbose. ifneq ($(V),) + VERBOSE_MODE = 1 +else ifneq ($(VERBOSE),) + VERBOSE_MODE = 1 +else + VERBOSE_MODE = +endif + +ifeq ($(VERBOSE_MODE),1) Q = - VERBOSE = --verbose + VERBOSE_FLAGS = --verbose DEVNULL = else Q = @ - VERBOSE = + VERBOSE_FLAGS = DEVNULL = > /dev/null endif # Ask git what version of the Tock kernel we are compiling, so we can include # this within the binary. If Tock is not within a git repo then we fallback to # a set string which should be updated with every release. -export TOCK_KERNEL_VERSION := $(shell git describe --tags --always 2> /dev/null || echo "1.4+") +export TOCK_KERNEL_VERSION := $(shell git describe --tags --always 2> /dev/null || echo "2.1+") -# Allow users to opt out of using rustup +# Allow users to opt out of using rustup. ifeq ($(NO_RUSTUP),) # Validate that rustup exists. RUSTUP_ERROR := $(shell $(RUSTUP) --version > /dev/null 2>&1; echo $$?) ifneq ($(RUSTUP_ERROR),0) - $(info Error! rustup not found.) - $(info Please follow the instructions at https://rustup.rs/ to install rustup.) - $(info Alternatively, install all required tools and Rust targets and set NO_RUSTUP to disable this check.) - $(info ) - $(error Rustup required to build Tock.) + $(info Error! rustup not found.) + $(info Please follow the instructions at https://rustup.rs/ to install rustup.) + $(info Alternatively, install all required tools and Rust targets and set NO_RUSTUP=1 to disable this check.) + $(info ) + $(error Rustup required to build Tock.) endif # Validate that rustup is new enough. -MINIMUM_RUSTUP_VERSION := 1.11.0 +MINIMUM_RUSTUP_VERSION := 1.23.0 RUSTUP_VERSION := $(strip $(word 2, $(shell $(RUSTUP) --version 2> /dev/null))) +# Check that the semver script exists. +ifneq (,$(wildcard $(TOCK_ROOT_DIRECTORY)tools/semver.sh)) ifeq ($(shell $(TOCK_ROOT_DIRECTORY)tools/semver.sh $(RUSTUP_VERSION) \< $(MINIMUM_RUSTUP_VERSION)), true) $(warning Required tool `$(RUSTUP)` is out-of-date.) $(warning Running `$(RUSTUP) update` in 3 seconds (ctrl-c to cancel)) - $(shell sleep 3s) + $(shell sleep 3) DUMMY := $(shell $(RUSTUP) update) endif +endif # Verify that various required Rust components are installed. All of these steps # only have to be done once per Rust version, but will take some time when # compiling for the first time. -LLVM_TOOLS_INSTALLED := $(shell $(RUSTUP) component list | grep 'llvm-tools-preview.*(installed)' > /dev/null; echo $$?) -ifeq ($(LLVM_TOOLS_INSTALLED),1) - $(shell $(RUSTUP) component add llvm-tools-preview) -endif -ifneq ($(shell $(RUSTUP) component list | grep rust-src),rust-src (installed)) - $(shell $(RUSTUP) component add rust-src) -endif ifneq ($(shell $(RUSTUP) target list | grep "$(TARGET) (installed)"),$(TARGET) (installed)) - $(shell $(RUSTUP) target add $(TARGET)) + $(warning Request to compile for a missing TARGET, make will install in 5s) + $(warning Consider updating 'targets' in 'rust-toolchain.toml') + $(shell sleep 5s && $(RUSTUP) target add $(TARGET)) endif -endif # $(NO_RUSTUP) ? +endif # $(NO_RUSTUP) -# If the user is using the standard toolchain provided as part of the -# llvm-tools-preview rustup component we need to get the full path. rustup -# should take care of this for us by putting in a proxy in .cargo/bin, but until -# that is setup we workaround it. -ifeq ($(TOOLCHAIN),llvm-tools-preview) +# If the user is using the standard toolchain provided as part of the llvm-tools +# rustup component we need to get the full path. rustup should take care of this +# for us by putting in a proxy in .cargo/bin, but until that is setup we +# workaround it. +ifeq ($(TOOLCHAIN),llvm-tools) TOOLCHAIN = "$(shell dirname $(shell find $(RUSTC_SYSROOT) -name llvm-size))/llvm" endif -# Set variables of the key tools we need to compile a Tock kernel. -SIZE ?= $(TOOLCHAIN)-size -OBJCOPY ?= $(TOOLCHAIN)-objcopy -OBJDUMP ?= $(TOOLCHAIN)-objdump +# Set variables of the key tools we need to compile a Tock kernel. Need to do +# this after we handle if we are using the LLVM tools or not. +SIZE ?= $(TOOLCHAIN)-size +OBJCOPY ?= $(TOOLCHAIN)-objcopy +OBJDUMP ?= $(TOOLCHAIN)-objdump # Set additional flags to produce binary from .elf. -# * --strip-sections prevents enormous binaries when SRAM is below flash. -# * --remove-section .apps prevents the .apps section from being included in the -# kernel binary file. This section is a placeholder for optionally including -# application binaries, and only needs to exist in the .elf. By removing it, -# we prevent the kernel binary from overwriting applications. -OBJCOPY_FLAGS ?= --strip-sections -S --remove-section .apps +# +# - `--strip-sections`: Prevents enormous binaries when SRAM is below flash. +# - `--strip-all`: Remove non-allocated sections outside segments. +# `.gnu.warning*` and `.ARM.attribute` sections are not removed. +# - `--remove-section .apps`: Prevents the .apps section from being included in +# the kernel binary file. This section is a placeholder for optionally +# including application binaries, and only needs to exist in the .elf. By +# removing it, we prevent the kernel binary from overwriting applications. +OBJCOPY_FLAGS ?= --strip-sections --strip-all --remove-section .apps + # This make variable allows board-specific Makefiles to pass down options to # the Cargo build command. For example, in boards//Makefile: # `CARGO_FLAGS += --features=foo` would pass feature `foo` to the top level # Cargo.toml. CARGO_FLAGS ?= -# Add default flags to cargo. Boards can add additional options in CARGO_FLAGS -# We use build-std=core,compiler_builtins to build the std library from source using -# our optimization settings. This leads to significantly smaller binary sizes, and -# makes debugging easier since debug information for the core library is included in -# the resulting .elf file. See https://github.com/tock/tock/pull/2847 for more details. + +# Add default flags to cargo. Boards can add additional options in +# `CARGO_FLAGS`. CARGO_FLAGS_TOCK ?= \ - $(VERBOSE) \ - -Z build-std=core,compiler_builtins \ + $(VERBOSE_FLAGS) \ --target=$(TARGET) \ --package $(PLATFORM) \ --target-dir=$(TARGET_DIRECTORY) $(CARGO_FLAGS) + +# Add default flags to rustdoc. +RUSTDOC_FLAGS_TOCK ?= -D warnings --document-private-items + +# Add flags if we are compiling on nightly. If we are on stable, disallow +# features. +# +# - `-Z build-std=core,compiler_builtins`: Build the std library from source +# using our optimization settings. This leads to significantly smaller binary +# sizes, and makes debugging easier since debug information for the core +# library is included in the resulting .elf file. See +# https://github.com/tock/tock/pull/2847 for more details. +# - `optimize_for_size`: Sets a feature flag in the core library that aims to +# produce smaller implementations for certain algorithms. See +# https://github.com/rust-lang/rust/pull/125011 for more details. +# - `--document-hidden-items`: Document internal interfaces when building +# rustdoc API documentation. +ifneq ($(USE_STABLE_RUST),1) + CARGO_FLAGS_TOCK += \ + -Z build-std=core,compiler_builtins \ + -Z build-std-features="core/optimize_for_size" +endif + # Set the default flags we need for objdump to get a .lst file. OBJDUMP_FLAGS ?= --disassemble-all --source --section-headers --demangle -# Set default flags for size + +# Set default flags for size. SIZE_FLAGS ?= # Need an extra flag for OBJDUMP if we are on a thumb platform. @@ -218,30 +269,49 @@ ifneq (,$(findstring thumb,$(TARGET))) OBJDUMP_FLAGS += --arch-name=thumb endif -# Additional flags that can be passed to cargo bloat via an -# environment variable. Allows users to pass arbitrary flags -# supported by cargo bloat to customize the output. By default, -# pass an empty string. +# Additional flags that can be passed to cargo bloat via an environment +# variable. Allows users to pass arbitrary flags supported by cargo bloat to +# customize the output. By default, pass an empty string. CARGO_BLOAT_FLAGS ?= +# Additional flags that can be passed to print_tock_memory_usage.py via an +# environment variable. By default, pass an empty string. +PTMU_ARGS ?= -# cargo bloat does not support -Z build-std, so we must remove it from cargo flags -# see https://github.com/RazrFalcon/cargo-bloat/issues/62 +# `cargo bloat` does not support `-Z build-std`, so we must remove it from cargo +# flags. See https://github.com/RazrFalcon/cargo-bloat/issues/62. +# As we aren't building the library we also remove the +# `-Z build-std-features="core/optimize_for_size"` argument. CARGO_FLAGS_TOCK_NO_BUILD_STD := $(filter-out -Z build-std=core,$(CARGO_FLAGS_TOCK)) +CARGO_FLAGS_TOCK_NO_BUILD_STD := $(filter-out -Z build-std-features="core/optimize_for_size",$(CARGO_FLAGS_TOCK)) - -# Check whether the system already has a sha256sum application -# present, if not use the custom shipped one +# Check whether the system already has a sha256sum or shasum application +# present. If not, use the custom shipped one. ifeq (, $(shell sha256sum --version 2>/dev/null)) - # No system sha256sum available - SHA256SUM := $(CARGO) run --manifest-path $(TOCK_ROOT_DIRECTORY)tools/sha256sum/Cargo.toml -- 2>/dev/null + ifeq (, $(shell shasum --version 2>/dev/null)) + # No system sha256sum available. + SHA256SUM := $(CARGO) run --manifest-path $(TOCK_ROOT_DIRECTORY)tools/sha256sum/Cargo.toml -- 2>/dev/null + else + # Use shasum found on MacOS. + SHA256SUM := shasum -a 256 + endif else - # Use system sha256sum + # Use system sha256sum. SHA256SUM := sha256sum endif +# virtual-function-elimination reduces the size of binaries, but is still +# experimental and has some possible miscompilation issues. This is only enabled +# for some boards by default, but is exposed via the `VFUNC_ELIM=1` option. +# +# For details on virtual-function-elimination see: https://github.com/rust-lang/rust/pull/96285 +# See https://github.com/rust-lang/rust/issues/68262 for general tracking +ifneq ($(VFUNC_ELIM),) + RUSTC_FLAGS += -C lto -Z virtual-function-elimination +endif + # Dump configuration for verbose builds -ifneq ($(V),) +ifeq ($(VERBOSE_MODE),1) $(info ) $(info *******************************************************) $(info TOCK KERNEL BUILD SYSTEM -- VERBOSE BUILD CONFIGURATION) @@ -290,12 +360,12 @@ all: release # binary. This makes checking for Rust errors much faster. .PHONY: check check: - $(Q)$(CARGO) check $(VERBOSE) $(CARGO_FLAGS_TOCK) + $(Q)$(CARGO) check $(VERBOSE_FLAGS) $(CARGO_FLAGS_TOCK) .PHONY: clean clean:: - $(Q)$(CARGO) clean $(VERBOSE) --target-dir=$(TARGET_DIRECTORY) + $(Q)$(CARGO) clean $(VERBOSE_FLAGS) --target-dir=$(TARGET_DIRECTORY) .PHONY: release release: $(TARGET_PATH)/release/$(PLATFORM).bin @@ -308,9 +378,7 @@ debug-lst: $(TARGET_PATH)/debug/$(PLATFORM).lst .PHONY: doc doc: | target - @# This mess is all to work around rustdoc giving no way to return an - @# error if there are warnings. This effectively simulates that. - $(Q)RUSTDOCFLAGS='-Z unstable-options --document-hidden-items -D warnings' $(CARGO) --color=always doc $(VERBOSE) --release --package $(PLATFORM) --target-dir=$(TARGET_DIRECTORY) 2>&1 | grep -C 9999 warning && (echo "Warnings detected during doc build" && if [[ $$CI == "true" ]]; then echo "Erroring due to CI context" && exit 33; fi) || if [ $$? -eq 33 ]; then exit 1; fi + $(Q)RUSTDOCFLAGS='$(RUSTDOC_FLAGS_TOCK)' $(CARGO) --color=always doc $(VERBOSE_FLAGS) --release --package $(PLATFORM) .PHONY: lst @@ -322,26 +390,25 @@ lst: $(TARGET_PATH)/release/$(PLATFORM).lst show-target: $(info $(TARGET)) -# This rule is a copy of the rule used to build the release target, but -# `cargo rustc` has been replaced with `cargo bloat`. `cargo bloat` replicates the +# This rule is a copy of the rule used to build the release target, but `cargo +# rustc` has been replaced with `cargo bloat`. `cargo bloat` replicates the # interface of `cargo build`, rather than `cargo rustc`, so we need to move -# `RUSTC_FLAGS_FOR_BIN` into the `RUSTFLAGS` environment variable. This only means -# that cargo cannot reuse built dependencies built using `cargo bloat`. -# See the discussion on `RUSTC_FLAGS_FOR_BIN` above for additional details. -# To pass additional flags to `cargo bloat`, populate the -# CARGO_BLOAT_FLAGS environment variable, e.g. call -# `CARGO_BLOAT_FLAGS=--crates make cargobloat` +# `RUSTC_FLAGS_FOR_BIN` into the `RUSTFLAGS` environment variable. This only +# means that cargo cannot reuse built dependencies built using `cargo bloat`. +# See the discussion on `RUSTC_FLAGS_FOR_BIN` above for additional details. To +# pass additional flags to `cargo bloat`, populate the CARGO_BLOAT_FLAGS +# environment variable, e.g. run `CARGO_BLOAT_FLAGS=--crates make cargobloat` .PHONY: cargobloat cargobloat: - $(Q) $(CARGO) install cargo-bloat > /dev/null 2>&1 || echo 'Error: Failed to install cargo-bloat' + $(Q)$(CARGO) install cargo-bloat > /dev/null 2>&1 || echo 'Error: Failed to install cargo-bloat' $(Q)RUSTFLAGS="$(RUSTC_FLAGS_TOCK) $(RUSTC_FLAGS_FOR_BIN)" CARGO_FLAGS="-Z build-std=core" $(CARGO) bloat $(CARGO_FLAGS_TOCK_NO_BUILD_STD) --bin $(PLATFORM) --release $(CARGO_BLOAT_FLAGS) -# To pass additional flags to `cargo bloat`, populate the -# CARGO_BLOAT_FLAGS environment variable, e.g. call -# `CARGO_BLOAT_FLAGS=--crates make cargobloatnoinline` +# To pass additional flags to `cargo cargobloatnoinline`, populate the +# CARGO_BLOAT_FLAGS environment variable, e.g. run `CARGO_BLOAT_FLAGS=--crates +# make cargobloatnoinline` .PHONY: cargobloatnoinline cargobloatnoinline: - $(Q) $(CARGO) install cargo-bloat > \dev\null 2>&1 || echo 'Error: Failed to install cargo-bloat' + $(Q)$(CARGO) install cargo-bloat > \dev\null 2>&1 || echo 'Error: Failed to install cargo-bloat' $(Q)RUSTFLAGS="$(RUSTC_FLAGS_TOCK) -C inline-threshold=0 $(RUSTC_FLAGS_FOR_BIN)" $(CARGO) bloat $(CARGO_FLAGS_TOCK) --bin $(PLATFORM) --release $(CARGO_BLOAT_FLAGS) .PHONY: stack-analysis @@ -352,6 +419,11 @@ stack-analysis: $(Q)$(MAKE) release RUSTC_FLAGS="$(RUSTC_FLAGS) -Z emit-stack-sizes" $(DEVNULL) 2>&1 $(Q)$(TOCK_ROOT_DIRECTORY)/tools/stack_analysis.sh $(TARGET_PATH)/release/$(PLATFORM).elf +# Run the `print_tock_memory_usage.py` script for this board. +.PHONY: memory +memory: $(TARGET_PATH)/release/$(PLATFORM).elf + $(TOCK_ROOT_DIRECTORY)tools/print_tock_memory_usage.py --objdump $(OBJDUMP) -w $(PTMU_ARGS) $< + # Support rules target: @@ -371,7 +443,7 @@ target: $(TOCK_ROOT_DIRECTORY)tools/sha256sum/target/debug/sha256sum: - $(Q)$(CARGO) build $(VERBOSE) --manifest-path $(TOCK_ROOT_DIRECTORY)tools/sha256sum/Cargo.toml + $(Q)$(CARGO) build $(VERBOSE_FLAGS) --manifest-path $(TOCK_ROOT_DIRECTORY)tools/sha256sum/Cargo.toml # Cargo-drivers diff --git a/boards/README.md b/boards/README.md index c7a2a35d9c..4e5ee73a02 100644 --- a/boards/README.md +++ b/boards/README.md @@ -4,34 +4,122 @@ Platforms Supported by Tock The `/boards` directory contains the physical hardware platforms that Tock supports. -| Board | Architecture | MCU | Interface | App deployment | QEMU Support? | -|----------------------------------------------------------------------|-----------------|----------------|------------|----------------|---------------| -| [Hail](hail/README.md) | ARM Cortex-M4 | SAM4LC8BA | Bootloader | tockloader | No | -| [Imix](imix/README.md) | ARM Cortex-M4 | SAM4LC8CA | Bootloader | tockloader | No | -| [Nordic nRF52-DK](nordic/nrf52dk/README.md) | ARM Cortex-M4 | nRF52832 | jLink | tockloader | No | -| [Nordic nRF52840-DK](nordic/nrf52840dk/README.md) | ARM Cortex-M4 | nRF52840 | jLink | tockloader | No | -| [Nordic nRF52840-Dongle](nordic/nrf52840_dongle/README.md) | ARM Cortex-M4 | nRF52840 | jLink | tockloader | No | -| [ACD52832](acd52832/README.md) | ARM Cortex-M4 | nRF52832 | jLink | tockloader | No | -| [Nano 33 BLE](nano33ble/README.md) | ARM Cortex-M4 | nRF52840 | Bootloader | tockloader | No | -| [Nano RP2040 Connect](nano_rp2040_connect/README.md) | ARM Cortex-M0+ | RP2040 | custom | custom | No | -| [Clue nRF52840](clue_nrf52840/README.md) | ARM Cortex-M4 | nRF52840 | Bootloader | tockloader | No | -| [BBC Micro:bit v2](microbit_v2/README.md) | ARM Cortex-M4 | nRF52833 | openocd | tockloader | No | -| [ST Nucleo F446RE](nucleo_f446re/README.md) | ARM Cortex-M4 | STM32F446 | openocd | custom | #1827 | -| [ST Nucleo F429ZI](nucleo_f429zi/README.md) | ARM Cortex-M4 | STM32F429 | openocd | custom | #1827 | -| [STM32F3Discovery kit](stm32f3discovery/README.md) | ARM Cortex-M4 | STM32F303VCT6 | openocd | custom | #1827 | -| [STM32F412G Discovery kit](stm32f412gdiscovery/README.md) | ARM Cortex-M4 | STM32F412G | openocd | custom | #1827 | -| [WeAct F401CCU6 Core Board](weact_f401ccu6/README.md) | ARM Cortex-M4 | STM32F401CCU6 | openocd | custom | No | -| [SparkFun RedBoard Artemis Nano](redboard_artemis_nano/README.md) | ARM Cortex-M4 | Apollo3 | custom | custom | No | -| [i.MX RT 1052 Evaluation Kit](imxrt1050-evkb/README.md) | ARM Cortex-M7 | i.MX RT 1052 | custom | custom | No | -| [Teensy 4.0](teensy40/README.md) | ARM Cortex-M7 | i.MX RT 1062 | custom | custom | No | -| [Pico Explorer Base](pico_explorer_base/README.md) | ARM Cortex-M0+ | RP2040 | openocd | openocd | No | -| [Raspberry Pi Pico](raspberry_pi_pico/README.md) | ARM Cortex-M0+ | RP2040 | openocd | openocd | No | -| [SiFive HiFive1 Rev B](hifive1/README.md) | RISC-V | FE310-G002 | openocd | tockloader | Yes (5.1) | -| [Digilent Arty A-7 100T](arty_e21/README.md) | RISC-V RV32IMAC | SiFive E21 | openocd | tockloader | No | -| [Earlgrey on Nexys Video](earlgrey-nexysvideo/README.md) | RISC-V RV32IMC | EarlGrey | custom | custom | Yes (5.1) | -| [LiteX on Digilent Arty A-7](litex/arty/README.md) | RISC-V RV32I | LiteX+VexRiscV | custom | custom | No | -| [Verilated LiteX Simulation](litex/sim/README.md) | RISC-V RV32I | LiteX+VexRiscv | custom | custom | No | -| [ESP32-C3-DevKitM-1](esp32-c3-devkitM-1/README.md) | RISC-V-ish RV32I| ESP32-C3 | custom | custom | No | +Tock divides boards into three approximate 'tiers' of support. +These tiers are newly defined and are a bit informal as a result, +but the approximate definitions: + + - **Tier 1:** The most feature-complete and thoroughly tested boards. These + are boards used most regularly by core team members or other + highly engaged contributors. They are used as examples in the + [Tock Book](https://book.tockos.org). + - **Tier 2:** Platforms seeing reasonably regular use. These generally + have broader, but still incomplete, peripheral support. + They may also be 'relatives' of Tier 1 boards (e.g. a + less-used varient in the nrf52 family) – likely in good + shape, but not heavily tested. Some Tier 2 boards may + have known issues, which are documented in release notes + during release testing. + - **Tier 3:** New or highly experimental. These should support the minimum + platform requirements laid out in [the Porting + documentation](https://book.tockos.org/development/porting), but + make no promises beyond that. + + - **Other:** See each board for specific details. + +--- + +> #### RISC-V? +> +> Tock has solid support for the RISC-V architecture, but no tier 1 or 2 support +> for any single RISC-V board. If you are interested in running Tock on RISC-V +> there are a few options: +> +> 1. If you would like a cheap RISC-V development board you can use the +> [ESP32-C3-DevKitM-1](esp32-c3-devkitM-1/README.md). This board is +> under active development to move to Tier 2 support. +> 1. For a fully virtual platform on QEMU you can use the +> [QEMU RISC-V 32 bit `virt` platform](qemu_rv32_virt/README.md) board. This +> can be quickly started and run on a host computer. +> 1. For a simulation environment you can use Verilator with +> [OpenTitan Earlgrey on CW310](opentitan/earlgrey-cw310/README.md) or +> [Verilated LiteX Simulation](litex/sim/README.md). +> 1. For an FPGA setup you can use +> [OpenTitan Earlgrey on CW310](opentitan/earlgrey-cw310/README.md) or +> [LiteX on Digilent Arty A-7](litex/arty/README.md). + +--- + +### Tier 1 + +| Board | Architecture | MCU | Interface | App deployment | QEMU Support? | +|-------------------------------------------------------------------|------------------|----------------|------------|-----------------------------|---------------| +| [Hail](hail/README.md) | ARM Cortex-M4 | SAM4LC8BA | Bootloader | tockloader | No | +| [Imix](imix/README.md) | ARM Cortex-M4 | SAM4LC8CA | Bootloader | tockloader | No | +| [Nordic nRF52840-DK](nordic/nrf52840dk/README.md) | ARM Cortex-M4 | nRF52840 | jLink | tockloader | No | +| [Nano 33 BLE](nano33ble/README.md) | ARM Cortex-M4 | nRF52840 | Bootloader | tockloader | No | +| [Nano 33 BLE Rev2](nano33ble_rev2/README.md) | ARM Cortex-M4 | nRF52840 | Bootloader | tockloader | No | +| [BBC Micro:bit v2](microbit_v2/README.md) | ARM Cortex-M4 | nRF52833 | openocd | tockloader | No | +| [Clue nRF52840](clue_nrf52840/README.md) | ARM Cortex-M4 | nRF52840 | Bootloader | tockloader | No | + +### Tier 2 + +| Board | Architecture | MCU | Interface | App deployment | QEMU Support? | +|-------------------------------------------------------------------|------------------|----------------|------------|-----------------------------|---------------| +| [Nordic nRF52-DK](nordic/nrf52dk/README.md) | ARM Cortex-M4 | nRF52832 | jLink | tockloader | No | +| [Nordic nRF52840-Dongle](nordic/nrf52840_dongle/README.md) | ARM Cortex-M4 | nRF52840 | jLink | tockloader | No | +| [Particle Boron](particle_boron/README.md) | ARM Cortex-M4 | nRF52840 | jLink | tockloader | No | +| [ACD52832](acd52832/README.md) | ARM Cortex-M4 | nRF52832 | jLink | tockloader | No | +| [MakePython nRF52840dk](makepython-nrf52840/README.md) | ARM Cortex-M4 | nRF52840 | Bootloader | tockloader | No | +| [ST Nucleo F446RE](nucleo_f446re/README.md) | ARM Cortex-M4 | STM32F446 | openocd | custom | https://github.com/tock/tock/issues/1827 | +| [ST Nucleo F429ZI](nucleo_f429zi/README.md) | ARM Cortex-M4 | STM32F429 | openocd | custom | https://github.com/tock/tock/issues/1827 | +| [STM32F3Discovery kit](stm32f3discovery/README.md) | ARM Cortex-M4 | STM32F303VCT6 | openocd | custom | https://github.com/tock/tock/issues/1827 | +| [STM32F412G Discovery kit](stm32f412gdiscovery/README.md) | ARM Cortex-M4 | STM32F412G | openocd | custom | https://github.com/tock/tock/issues/1827 | +| [STM32F429I Discovery kit](stm32f429idiscovery/README.md) | ARM Cortex-M4 | STM32F429I | openocd | custom | https://github.com/tock/tock/issues/1827 | +| [Pico Explorer Base](pico_explorer_base/README.md) | ARM Cortex-M0+ | RP2040 | openocd | openocd | No | +| [Nano RP2040 Connect](nano_rp2040_connect/README.md) | ARM Cortex-M0+ | RP2040 | custom | custom | No | +| [Raspberry Pi Pico](raspberry_pi_pico/README.md) | ARM Cortex-M0+ | RP2040 | openocd | openocd | No | +| [SparkFun RedBoard Artemis Nano](apollo3/redboard_artemis_nano/README.md) | ARM Cortex-M4 | Apollo3 | custom | custom | No | +| [SparkFun LoRa Thing Plus - expLoRaBLE](apollo3/lora_things_plus/README.md) | ARM Cortex-M4 | Apollo3 | custom | custom | No | +| [SparkFun RedBoard Artemis ATP](apollo3/redboard_artemis_atp/README.md) | ARM Cortex-M4 | Apollo3 | custom | custom | No | +| [SMA Q3](sma_q3/README.md) | ARM Cortex-M4 | nRF52840 | openocd | tockloader | No | +| [Wio WM1110 Development Board](wm1110dev/README.md) | ARM Cortex-M4 | nRF52840 | Bootloader | tockloader | No | + +### Tier 3 + +| Board | Architecture | MCU | Interface | App deployment | QEMU Support? | +|-------------------------------------------------------------------|------------------|----------------|------------|-----------------------------|---------------| +| [WeAct F401CCU6 Core Board](weact_f401ccu6/README.md) | ARM Cortex-M4 | STM32F401CCU6 | openocd | custom | No | +| [SparkFun RedBoard Red-V](redboard_redv/README.md) | RISC-V | FE310-G002 | openocd | tockloader | Yes (5.1) | +| [SiFive HiFive1 Rev B](hifive1/README.md) | RISC-V | FE310-G002 | openocd | tockloader | Yes (5.1) | +| [BBC HiFive Inventor](hifive_inventor/README.md) | RISC-V | FE310-G003 | tockloader | tockloader | No | +| [ESP32-C3-DevKitM-1](esp32-c3-devkitM-1/README.md) | RISC-V-ish RV32I | ESP32-C3 | custom | custom | No | +| [i.MX RT 1052 Evaluation Kit](imxrt1050-evkb/README.md) | ARM Cortex-M7 | i.MX RT 1052 | custom | custom | No | +| [Teensy 4.0](teensy40/README.md) | ARM Cortex-M7 | i.MX RT 1062 | custom | custom | No | +| [Digilent Arty A-7 100T](arty_e21/README.md) | RISC-V RV32IMAC | SiFive E21 | openocd | tockloader | No | +| [MSP432 Evaluation kit MSP432P401R](msp_exp432p401r/README.md) | ARM Cortex-M4 | MSP432P401R | openocd | custom | No | + + +### Other + +An FPGA and Verilator implementation that is well supported and is regularly tested as part of CI. + +| Board | Architecture | MCU | Interface | App deployment | QEMU Support? | +|-------------------------------------------------------------------|------------------|----------------|------------|-----------------------------|---------------| +| [OpenTitan Earlgrey on CW310](opentitan/earlgrey-cw310/README.md) | RISC-V RV32IMC | EarlGrey | custom | custom | Yes (5.1) | + +Virtual hardware platforms that are regularly tested as part of the CI. + +| Board | Architecture | MCU | Interface | App deployment | QEMU Support? | +|-------------------------------------------------------------------|------------------|----------------|------------|-----------------------------|---------------| +| [QEMU RISC-V 32 bit `virt` platform](qemu_rv32_virt/README.md) | RISC-V RV32IMAC | QEMU | custom | custom | Yes (7.2.0) | +| [LiteX on Digilent Arty A-7](litex/arty/README.md) | RISC-V RV32IMC | LiteX+VexRiscV | custom | tockloader (flash-file)[^1] | No | +| [Verilated LiteX Simulation](litex/sim/README.md) | RISC-V RV32IMC | LiteX+VexRiscv | custom | tockloader (flash-file)[^1] | No | +| [SweRVolf](swervolf/README.md) | RISC-V RV32IMC | swervolf-eh1 | custom | tockloader (flash-file)[^1] | No | + +[^1]: Tockloader is not able to interact with this board directly, but + can be used to work on a flash-image of the board, which can in + turn be flashed onto / read from the board. For more specific + information, visit the board's README. # Out of Tree Boards diff --git a/boards/acd52832/Cargo.toml b/boards/acd52832/Cargo.toml index d0d152b372..4e985708b2 100644 --- a/boards/acd52832/Cargo.toml +++ b/boards/acd52832/Cargo.toml @@ -1,14 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "acd52832" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] components = { path = "../components" } cortexm4 = { path = "../../arch/cortex-m4" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } nrf52832 = { path = "../../chips/nrf52832" } nrf52_components = { path = "../nordic/nrf52_components" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/acd52832/Makefile b/boards/acd52832/Makefile index 36c79a12ee..61e86d8692 100644 --- a/boards/acd52832/Makefile +++ b/boards/acd52832/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the nRF development kit TARGET=thumbv7em-none-eabi diff --git a/boards/acd52832/build.rs b/boards/acd52832/build.rs deleted file mode 100644 index 3051054fc6..0000000000 --- a/boards/acd52832/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/acd52832/layout.ld b/boards/acd52832/layout.ld index 1c6b5b52c7..9f56cf4afa 100644 --- a/boards/acd52832/layout.ld +++ b/boards/acd52832/layout.ld @@ -1,3 +1,7 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + /* Memory Space Definitions, 512K flash, 64K ram */ MEMORY { @@ -6,7 +10,6 @@ MEMORY ram (rwx) : ORIGIN = 0x20000000, LENGTH = 64K } -MPU_MIN_ALIGN = 8K; PAGE_SIZE = 4K; INCLUDE ../kernel_layout.ld diff --git a/boards/acd52832/src/io.rs b/boards/acd52832/src/io.rs index 52d5a8fd31..56726ff56a 100644 --- a/boards/acd52832/src/io.rs +++ b/boards/acd52832/src/io.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::panic::PanicInfo; use kernel::debug; @@ -8,7 +12,7 @@ use nrf52832::gpio::Pin; #[cfg(not(test))] #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(_pi: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(_pi: &PanicInfo) -> ! { let led_kernel_pin = &nrf52832::gpio::GPIOPin::new(Pin::P0_22); let led = &mut led::LedLow::new(led_kernel_pin); debug::panic_blink_forever(&mut [led]) diff --git a/boards/acd52832/src/main.rs b/boards/acd52832/src/main.rs index 30f9061a93..e2edea8fa0 100644 --- a/boards/acd52832/src/main.rs +++ b/boards/acd52832/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tock kernel for the Aconno ACD52832 board based on the Nordic nRF52832 MCU. #![no_std] @@ -6,17 +10,17 @@ #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] -use capsules::virtual_alarm::VirtualMuxAlarm; +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil; use kernel::hil::adc::Adc; -use kernel::hil::entropy::Entropy32; +use kernel::hil::buzzer::Buzzer; use kernel::hil::gpio::{Configure, InterruptWithValue, Output}; use kernel::hil::i2c::I2CMaster; use kernel::hil::led::LedLow; -use kernel::hil::rng::Rng; use kernel::hil::time::{Alarm, Counter}; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::scheduler::round_robin::RoundRobinSched; @@ -26,8 +30,6 @@ use nrf52832::gpio::Pin; use nrf52832::interrupt_service::Nrf52832DefaultPeripherals; use nrf52832::rtc::Rtc; -use nrf52_components::ble::BLEComponent; - const LED1_PIN: Pin = Pin::P0_26; const LED2_PIN: Pin = Pin::P0_22; const LED3_PIN: Pin = Pin::P0_23; @@ -44,7 +46,8 @@ pub mod io; // State for loading and holding applications. // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; // Number of concurrent processes this platform supports. const NUM_PROCS: usize = 4; @@ -57,34 +60,53 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] #[link_section = ".stack_buffer"] pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; +type TemperatureDriver = + components::temperature::TemperatureComponentType>; +type RngDriver = components::rng::RngComponentType>; + /// Supported drivers by the platform pub struct Platform { - ble_radio: &'static capsules::ble_advertising_driver::BLE< + ble_radio: &'static capsules_extra::ble_advertising_driver::BLE< 'static, nrf52832::ble_radio::Radio<'static>, VirtualMuxAlarm<'static, Rtc<'static>>, >, - button: &'static capsules::button::Button<'static, nrf52832::gpio::GPIOPin<'static>>, - console: &'static capsules::console::Console<'static>, - gpio: &'static capsules::gpio::GPIO<'static, nrf52832::gpio::GPIOPin<'static>>, - led: &'static capsules::led::LedDriver< + button: &'static capsules_core::button::Button<'static, nrf52832::gpio::GPIOPin<'static>>, + console: &'static capsules_core::console::Console<'static>, + gpio: &'static capsules_core::gpio::GPIO<'static, nrf52832::gpio::GPIOPin<'static>>, + led: &'static capsules_core::led::LedDriver< 'static, LedLow<'static, nrf52832::gpio::GPIOPin<'static>>, 4, >, - rng: &'static capsules::rng::RngDriver<'static>, - temp: &'static capsules::temperature::TemperatureSensor<'static>, - ipc: kernel::ipc::IPC, - alarm: &'static capsules::alarm::AlarmDriver< + rng: &'static RngDriver, + temp: &'static TemperatureDriver, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, VirtualMuxAlarm<'static, nrf52832::rtc::Rtc<'static>>, >, - gpio_async: - &'static capsules::gpio_async::GPIOAsync<'static, capsules::mcp230xx::MCP230xx<'static>>, - light: &'static capsules::ambient_light::AmbientLight<'static>, - buzzer: &'static capsules::buzzer_driver::Buzzer< + gpio_async: &'static capsules_extra::gpio_async::GPIOAsync< + 'static, + capsules_extra::mcp230xx::MCP230xx< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice< + 'static, + nrf52832::i2c::TWI<'static>, + >, + >, + >, + light: &'static capsules_extra::ambient_light::AmbientLight<'static>, + buzzer: &'static capsules_extra::buzzer_driver::Buzzer< 'static, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52832::rtc::Rtc<'static>>, + capsules_extra::buzzer_pwm::PwmBuzzer< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52832::rtc::Rtc<'static>, + >, + capsules_core::virtualizers::virtual_pwm::PwmPinUser<'static, nrf52832::pwm::Pwm>, + >, >, scheduler: &'static RoundRobinSched<'static>, systick: cortexm4::systick::SysTick, @@ -96,17 +118,17 @@ impl SyscallDriverLookup for Platform { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::button::DRIVER_NUM => f(Some(self.button)), - capsules::rng::DRIVER_NUM => f(Some(self.rng)), - capsules::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), - capsules::temperature::DRIVER_NUM => f(Some(self.temp)), - capsules::gpio_async::DRIVER_NUM => f(Some(self.gpio_async)), - capsules::ambient_light::DRIVER_NUM => f(Some(self.light)), - capsules::buzzer_driver::DRIVER_NUM => f(Some(self.buzzer)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::rng::DRIVER_NUM => f(Some(self.rng)), + capsules_extra::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temp)), + capsules_extra::gpio_async::DRIVER_NUM => f(Some(self.gpio_async)), + capsules_extra::ambient_light::DRIVER_NUM => f(Some(self.light)), + capsules_extra::buzzer_driver::DRIVER_NUM => f(Some(self.buzzer)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), _ => f(None), } @@ -125,7 +147,7 @@ impl KernelResources &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -151,23 +173,18 @@ impl KernelResources &'static mut Nrf52832DefaultPeripherals<'static> { - // Initialize chip peripheral drivers +unsafe fn start() -> ( + &'static kernel::Kernel, + Platform, + &'static nrf52832::chip::NRF52<'static, Nrf52832DefaultPeripherals<'static>>, +) { + nrf52832::init(); + let nrf52832_peripherals = static_init!( Nrf52832DefaultPeripherals, Nrf52832DefaultPeripherals::new() ); - nrf52832_peripherals -} - -/// Main function called after RAM initialized. -#[no_mangle] -pub unsafe fn main() { - nrf52832::init(); - - let nrf52832_peripherals = get_peripherals(); - // set up circular peripheral dependencies nrf52832_peripherals.init(); let base_peripherals = &nrf52832_peripherals.nrf52; @@ -176,18 +193,9 @@ pub unsafe fn main() { // functions. let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); - - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 2], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); // Make non-volatile memory writable and activate the reset button let uicr = nrf52832::uicr::Uicr::new(); @@ -210,7 +218,7 @@ pub unsafe fn main() { // let gpio = components::gpio::GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( nrf52832::gpio::GPIOPin, 0 => &nrf52832_peripherals.gpio_port[Pin::P0_25], @@ -222,12 +230,12 @@ pub unsafe fn main() { 6 => &nrf52832_peripherals.gpio_port[Pin::P0_31] ), ) - .finalize(components::gpio_component_buf!(nrf52832::gpio::GPIOPin)); + .finalize(components::gpio_component_static!(nrf52832::gpio::GPIOPin)); // // LEDs // - let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( LedLow<'static, nrf52832::gpio::GPIOPin>, LedLow::new(&nrf52832_peripherals.gpio_port[LED1_PIN]), LedLow::new(&nrf52832_peripherals.gpio_port[LED2_PIN]), @@ -240,7 +248,7 @@ pub unsafe fn main() { // let button = components::button::ButtonComponent::new( board_kernel, - capsules::button::DRIVER_NUM, + capsules_core::button::DRIVER_NUM, components::button_component_helper!( nrf52832::gpio::GPIOPin, // 13 @@ -269,7 +277,9 @@ pub unsafe fn main() { ) ), ) - .finalize(components::button_component_buf!(nrf52832::gpio::GPIOPin)); + .finalize(components::button_component_static!( + nrf52832::gpio::GPIOPin + )); // // RTC for Timers @@ -277,8 +287,8 @@ pub unsafe fn main() { let rtc = &base_peripherals.rtc; let _ = rtc.start(); let mux_alarm = static_init!( - capsules::virtual_alarm::MuxAlarm<'static, nrf52832::rtc::Rtc>, - capsules::virtual_alarm::MuxAlarm::new(&base_peripherals.rtc) + capsules_core::virtualizers::virtual_alarm::MuxAlarm<'static, nrf52832::rtc::Rtc>, + capsules_core::virtualizers::virtual_alarm::MuxAlarm::new(&base_peripherals.rtc) ); rtc.set_alarm_client(mux_alarm); @@ -288,20 +298,26 @@ pub unsafe fn main() { // Virtual alarm for the userspace timers let alarm_driver_virtual_alarm = static_init!( - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52832::rtc::Rtc>, - capsules::virtual_alarm::VirtualMuxAlarm::new(mux_alarm) + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, nrf52832::rtc::Rtc>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm::new(mux_alarm) ); alarm_driver_virtual_alarm.setup(); // Userspace timer driver let alarm = static_init!( - capsules::alarm::AlarmDriver< + capsules_core::alarm::AlarmDriver< 'static, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52832::rtc::Rtc>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52832::rtc::Rtc, + >, >, - capsules::alarm::AlarmDriver::new( + capsules_core::alarm::AlarmDriver::new( alarm_driver_virtual_alarm, - board_kernel.create_grant(capsules::alarm::DRIVER_NUM, &memory_allocation_capability) + board_kernel.create_grant( + capsules_core::alarm::DRIVER_NUM, + &memory_allocation_capability + ) ) ); alarm_driver_virtual_alarm.set_alarm_client(alarm); @@ -311,27 +327,29 @@ pub unsafe fn main() { // // RTT communication channel - let rtt_memory = components::segger_rtt::SeggerRttMemoryComponent::new().finalize(()); + let rtt_memory = components::segger_rtt::SeggerRttMemoryComponent::new() + .finalize(components::segger_rtt_memory_component_static!()); let rtt = components::segger_rtt::SeggerRttComponent::new(mux_alarm, rtt_memory) - .finalize(components::segger_rtt_component_helper!(nrf52832::rtc::Rtc)); + .finalize(components::segger_rtt_component_static!(nrf52832::rtc::Rtc)); // // Virtual UART // // Create a shared UART channel for the console and for kernel debug. - let uart_mux = components::console::UartMuxComponent::new(rtt, 115200, dynamic_deferred_caller) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(rtt, 115200) + .finalize(components::uart_mux_component_static!()); // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); // // I2C Devices @@ -339,14 +357,15 @@ pub unsafe fn main() { // Create shared mux for the I2C bus let i2c_mux = static_init!( - capsules::virtual_i2c::MuxI2C<'static>, - capsules::virtual_i2c::MuxI2C::new(&base_peripherals.twi0, None, dynamic_deferred_caller) + capsules_core::virtualizers::virtual_i2c::MuxI2C<'static, nrf52832::i2c::TWI<'static>>, + capsules_core::virtualizers::virtual_i2c::MuxI2C::new(&base_peripherals.twi1, None,) ); - base_peripherals.twi0.configure( + kernel::deferred_call::DeferredCallClient::register(i2c_mux); + base_peripherals.twi1.configure( nrf52832::pinmux::Pinmux::new(21), nrf52832::pinmux::Pinmux::new(20), ); - base_peripherals.twi0.set_master_client(i2c_mux); + base_peripherals.twi1.set_master_client(i2c_mux); // Configure the MCP23017. Device address 0x20. let mcp_pin0 = static_init!( @@ -360,16 +379,26 @@ pub unsafe fn main() { ) .finalize(); let mcp23017_i2c = static_init!( - capsules::virtual_i2c::I2CDevice, - capsules::virtual_i2c::I2CDevice::new(i2c_mux, 0x40) + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, nrf52832::i2c::TWI<'static>>, + capsules_core::virtualizers::virtual_i2c::I2CDevice::new(i2c_mux, 0x40) + ); + let mcp230xx_buffer = static_init!( + [u8; capsules_extra::mcp230xx::BUFFER_LENGTH], + [0; capsules_extra::mcp230xx::BUFFER_LENGTH] ); let mcp23017 = static_init!( - capsules::mcp230xx::MCP230xx<'static>, - capsules::mcp230xx::MCP230xx::new( + capsules_extra::mcp230xx::MCP230xx< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice< + 'static, + nrf52832::i2c::TWI<'static>, + >, + >, + capsules_extra::mcp230xx::MCP230xx::new( mcp23017_i2c, Some(mcp_pin0), Some(mcp_pin1), - &mut capsules::mcp230xx::BUFFER, + mcp230xx_buffer, 8, 2 ) @@ -384,15 +413,32 @@ pub unsafe fn main() { // Create an array of the GPIO extenders so we can pass them to an // administrative layer that provides a single interface to them all. - let async_gpio_ports = static_init!([&'static capsules::mcp230xx::MCP230xx; 1], [mcp23017]); + let async_gpio_ports = static_init!( + [&'static capsules_extra::mcp230xx::MCP230xx< + capsules_core::virtualizers::virtual_i2c::I2CDevice< + 'static, + nrf52832::i2c::TWI<'static>, + >, + >; 1], + [mcp23017] + ); // `gpio_async` is the object that manages all of the extenders. let gpio_async = static_init!( - capsules::gpio_async::GPIOAsync<'static, capsules::mcp230xx::MCP230xx<'static>>, - capsules::gpio_async::GPIOAsync::new( + capsules_extra::gpio_async::GPIOAsync< + 'static, + capsules_extra::mcp230xx::MCP230xx< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice< + 'static, + nrf52832::i2c::TWI<'static>, + >, + >, + >, + capsules_extra::gpio_async::GPIOAsync::new( async_gpio_ports, board_kernel.create_grant( - capsules::gpio_async::DRIVER_NUM, + capsules_extra::gpio_async::DRIVER_NUM, &memory_allocation_capability, ), ), @@ -406,51 +452,41 @@ pub unsafe fn main() { // BLE // - let ble_radio = BLEComponent::new( + let ble_radio = components::ble::BLEComponent::new( board_kernel, - capsules::ble_advertising_driver::DRIVER_NUM, + capsules_extra::ble_advertising_driver::DRIVER_NUM, &base_peripherals.ble_radio, mux_alarm, ) - .finalize(()); + .finalize(components::ble_component_static!( + nrf52832::rtc::Rtc, + nrf52832::ble_radio::Radio + )); // // Temperature // // Setup internal temperature sensor - let temp = static_init!( - capsules::temperature::TemperatureSensor<'static>, - capsules::temperature::TemperatureSensor::new( - &base_peripherals.temp, - board_kernel.create_grant( - capsules::temperature::DRIVER_NUM, - &memory_allocation_capability - ) - ) - ); - kernel::hil::sensors::TemperatureDriver::set_client(&base_peripherals.temp, temp); + let temp = components::temperature::TemperatureComponent::new( + board_kernel, + capsules_extra::temperature::DRIVER_NUM, + &base_peripherals.temp, + ) + .finalize(components::temperature_component_static!( + nrf52832::temperature::Temp + )); // // RNG // - // Convert hardware RNG to the Random interface. - let entropy_to_random = static_init!( - capsules::rng::Entropy32ToRandom<'static>, - capsules::rng::Entropy32ToRandom::new(&base_peripherals.trng) - ); - base_peripherals.trng.set_client(entropy_to_random); - - // Setup RNG for userspace - let rng = static_init!( - capsules::rng::RngDriver<'static>, - capsules::rng::RngDriver::new( - entropy_to_random, - board_kernel.create_grant(capsules::rng::DRIVER_NUM, &memory_allocation_capability) - ) - ); - entropy_to_random.set_client(rng); + let rng = components::rng::RngComponent::new( + board_kernel, + capsules_core::rng::DRIVER_NUM, + &base_peripherals.trng, + ) + .finalize(components::rng_component_static!(nrf52832::trng::Trng)); // // Light Sensor @@ -463,22 +499,22 @@ pub unsafe fn main() { ); let analog_light_sensor = static_init!( - capsules::analog_sensor::AnalogLightSensor<'static, nrf52832::adc::Adc>, - capsules::analog_sensor::AnalogLightSensor::new( + capsules_extra::analog_sensor::AnalogLightSensor<'static, nrf52832::adc::Adc>, + capsules_extra::analog_sensor::AnalogLightSensor::new( &base_peripherals.adc, analog_light_channel, - capsules::analog_sensor::AnalogLightSensorType::LightDependentResistor, + capsules_extra::analog_sensor::AnalogLightSensorType::LightDependentResistor, ) ); base_peripherals.adc.set_client(analog_light_sensor); // Create userland driver for ambient light sensor let light = static_init!( - capsules::ambient_light::AmbientLight<'static>, - capsules::ambient_light::AmbientLight::new( + capsules_extra::ambient_light::AmbientLight<'static>, + capsules_extra::ambient_light::AmbientLight::new( analog_light_sensor, board_kernel.create_grant( - capsules::ambient_light::DRIVER_NUM, + capsules_extra::ambient_light::DRIVER_NUM, &memory_allocation_capability ) ) @@ -489,12 +525,15 @@ pub unsafe fn main() { // PWM // let mux_pwm = static_init!( - capsules::virtual_pwm::MuxPwm<'static, nrf52832::pwm::Pwm>, - capsules::virtual_pwm::MuxPwm::new(&base_peripherals.pwm0) + capsules_core::virtualizers::virtual_pwm::MuxPwm<'static, nrf52832::pwm::Pwm>, + capsules_core::virtualizers::virtual_pwm::MuxPwm::new(&base_peripherals.pwm0) ); let virtual_pwm_buzzer = static_init!( - capsules::virtual_pwm::PwmPinUser<'static, nrf52832::pwm::Pwm>, - capsules::virtual_pwm::PwmPinUser::new(mux_pwm, nrf52832::pinmux::Pinmux::new(31)) + capsules_core::virtualizers::virtual_pwm::PwmPinUser<'static, nrf52832::pwm::Pwm>, + capsules_core::virtualizers::virtual_pwm::PwmPinUser::new( + mux_pwm, + nrf52832::pinmux::Pinmux::new(31) + ) ); virtual_pwm_buzzer.add_to_mux(); @@ -502,27 +541,52 @@ pub unsafe fn main() { // Buzzer // let virtual_alarm_buzzer = static_init!( - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52832::rtc::Rtc>, - capsules::virtual_alarm::VirtualMuxAlarm::new(mux_alarm) + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, nrf52832::rtc::Rtc>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm::new(mux_alarm) ); virtual_alarm_buzzer.setup(); - let buzzer = static_init!( - capsules::buzzer_driver::Buzzer< + let pwm_buzzer = static_init!( + capsules_extra::buzzer_pwm::PwmBuzzer< 'static, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52832::rtc::Rtc>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52832::rtc::Rtc, + >, + capsules_core::virtualizers::virtual_pwm::PwmPinUser<'static, nrf52832::pwm::Pwm>, >, - capsules::buzzer_driver::Buzzer::new( + capsules_extra::buzzer_pwm::PwmBuzzer::new( virtual_pwm_buzzer, virtual_alarm_buzzer, - capsules::buzzer_driver::DEFAULT_MAX_BUZZ_TIME_MS, + capsules_extra::buzzer_pwm::DEFAULT_MAX_BUZZ_TIME_MS, + ) + ); + + let buzzer = static_init!( + capsules_extra::buzzer_driver::Buzzer< + 'static, + capsules_extra::buzzer_pwm::PwmBuzzer< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52832::rtc::Rtc, + >, + capsules_core::virtualizers::virtual_pwm::PwmPinUser<'static, nrf52832::pwm::Pwm>, + >, + >, + capsules_extra::buzzer_driver::Buzzer::new( + pwm_buzzer, + capsules_extra::buzzer_driver::DEFAULT_MAX_BUZZ_TIME_MS, board_kernel.create_grant( - capsules::buzzer_driver::DRIVER_NUM, + capsules_extra::buzzer_driver::DRIVER_NUM, &memory_allocation_capability ) ) ); - virtual_alarm_buzzer.set_alarm_client(buzzer); + + pwm_buzzer.set_client(buzzer); + + virtual_alarm_buzzer.set_alarm_client(pwm_buzzer); // Start all of the clocks. Low power operation will require a better // approach than this. @@ -537,21 +601,21 @@ pub unsafe fn main() { while !base_peripherals.clock.low_started() {} while !base_peripherals.clock.high_started() {} - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); let platform = Platform { - button: button, - ble_radio: ble_radio, - console: console, - led: led, - gpio: gpio, - rng: rng, - temp: temp, - alarm: alarm, - gpio_async: gpio_async, - light: light, - buzzer: buzzer, + button, + ble_radio, + console, + led, + gpio, + rng, + temp, + alarm, + gpio_async, + light, + buzzer, ipc: kernel::ipc::IPC::new( board_kernel, kernel::ipc::DRIVER_NUM, @@ -570,9 +634,9 @@ pub unsafe fn main() { nrf52832_peripherals.gpio_port[Pin::P0_31].clear(); debug!("Initialization complete. Entering main loop\r"); - debug!("{}", &nrf52832::ficr::FICR_INSTANCE); + debug!("{}", &*addr_of!(nrf52832::ficr::FICR_INSTANCE)); - /// These symbols are defined in the linker script. + // These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -588,14 +652,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_management_capability, ) @@ -604,5 +668,14 @@ pub unsafe fn main() { debug!("{:?}", err); }); - board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); + (board_kernel, platform, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, board, chip) = start(); + board_kernel.kernel_loop(&board, chip, Some(&board.ipc), &main_loop_capability); } diff --git a/boards/apollo3/README.md b/boards/apollo3/README.md new file mode 100644 index 0000000000..a6a2ea8b34 --- /dev/null +++ b/boards/apollo3/README.md @@ -0,0 +1,36 @@ +Apollo3 Boards +============== + +This directory contains all of the Apollo3 boards supported by Tock + + * ambiq - Generic tools for flashing binaries + * redboard_artemis_atp [SparkFun RedBoard Artemis ATP](https://www.sparkfun.com/products/15442) + * redboard_artemis_nano [SparkFun RedBoard Artemis Nano](https://www.sparkfun.com/products/15443) + * lora_things_plus [SparkFun LoRa Thing Plus - expLoRaBLE](https://www.sparkfun.com/products/17506) + +## Hardware differences + +All of the boards use the same SoC, so the Tock board files are overall very +similar and can actually be used interchangably for basic operations. + +The main difference between them is what is broken out via the boards. For +example the Redboard Artemis ATP uses IOM4 for the Qwiic connector while +the Redboard Artemis Nano uses IOM2. + +The GPIO breakouts are also a little different. + +The LoRa Thing Plus also sets up the SX1262 radio, which the other boards +don't have. + +## Configuration differences + +The other difference is Tock configurations. The boards are configured +slightly differently to show a range of different options. + +### I2C + +The Redboard Artemis ATP doesn't setup a `MuxI2C`, like the other boards, +instead it creates a `I2CMasterSlaveDriver`. + +A `I2CMasterSlaveDriver` can also be setup on the Redboard Artemis Nano +instead of a `MuxI2C` as it exposes the correct pins, but it isn't by default. diff --git a/boards/redboard_artemis_nano/ambiq/README.md b/boards/apollo3/ambiq/README.md similarity index 100% rename from boards/redboard_artemis_nano/ambiq/README.md rename to boards/apollo3/ambiq/README.md diff --git a/boards/redboard_artemis_nano/ambiq/am_defines.py b/boards/apollo3/ambiq/am_defines.py similarity index 91% rename from boards/redboard_artemis_nano/ambiq/am_defines.py rename to boards/apollo3/ambiq/am_defines.py index 811c75ddb3..6e781dbf18 100644 --- a/boards/redboard_artemis_nano/ambiq/am_defines.py +++ b/boards/apollo3/ambiq/am_defines.py @@ -1,4 +1,42 @@ #!/usr/bin/env python3 +# +# This file is part of the AmbiqSuite SDK, accessible under +# https://github.com/sparkfun/AmbiqSuiteSDK. +# +# Copyright (c) 2020, Ambiq Micro +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# A copy of this license is available in the root of the AmbiqSuiteSDK +# repository, revision e280cbde3e366509da6768ab95471782a05d2371, under +# `AM-BSD-EULA.txt`. +# +# # Utility functioins import sys diff --git a/boards/redboard_artemis_nano/ambiq/ambiq_bin2board.py b/boards/apollo3/ambiq/ambiq_bin2board.py similarity index 95% rename from boards/redboard_artemis_nano/ambiq/ambiq_bin2board.py rename to boards/apollo3/ambiq/ambiq_bin2board.py index 12aa97b2b1..62223062ec 100644 --- a/boards/redboard_artemis_nano/ambiq/ambiq_bin2board.py +++ b/boards/apollo3/ambiq/ambiq_bin2board.py @@ -1,6 +1,45 @@ #!/usr/bin/env python3 -# Combination of the three steps to take an 'application.bin' file and run it on a SparkFun Artemis module - +# +# This file is part of the AmbiqSuite SDK, accessible under +# https://github.com/sparkfun/AmbiqSuiteSDK. +# +# Copyright (c) 2020, Ambiq Micro +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# A copy of this license is available in the root of the AmbiqSuiteSDK +# repository, revision e280cbde3e366509da6768ab95471782a05d2371, under +# `AM-BSD-EULA.txt`. +# +# +# Combination of the three steps to take an 'application.bin' file and run it on +# a SparkFun Artemis module. +# # Information: # This script performs the three main tasks: # 1. Convert 'application.bin' to an OTA update blob diff --git a/boards/redboard_artemis_nano/ambiq/keys_info.py b/boards/apollo3/ambiq/keys_info.py similarity index 58% rename from boards/redboard_artemis_nano/ambiq/keys_info.py rename to boards/apollo3/ambiq/keys_info.py index a3b088007f..3641793b3e 100644 --- a/boards/redboard_artemis_nano/ambiq/keys_info.py +++ b/boards/apollo3/ambiq/keys_info.py @@ -1,4 +1,41 @@ #!/usr/bin/env python3 +# +# This file is part of the AmbiqSuite SDK, accessible under +# https://github.com/sparkfun/AmbiqSuiteSDK. +# +# Copyright (c) 2020, Ambiq Micro +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# A copy of this license is available in the root of the AmbiqSuiteSDK +# repository, revision e280cbde3e366509da6768ab95471782a05d2371, under +# `AM-BSD-EULA.txt`. + from am_defines import * minAesKeyIdx = 8 diff --git a/boards/apollo3/lora_things_plus/.cargo/config.toml b/boards/apollo3/lora_things_plus/.cargo/config.toml new file mode 100644 index 0000000000..e06b76bd9b --- /dev/null +++ b/boards/apollo3/lora_things_plus/.cargo/config.toml @@ -0,0 +1,6 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[target.'cfg(target_arch = "arm")'] +runner = "./run.sh" diff --git a/boards/apollo3/lora_things_plus/Cargo.toml b/boards/apollo3/lora_things_plus/Cargo.toml new file mode 100644 index 0000000000..440e3be6d6 --- /dev/null +++ b/boards/apollo3/lora_things_plus/Cargo.toml @@ -0,0 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[package] +name = "lora_things_plus" +version.workspace = true +authors.workspace = true +edition.workspace = true +build = "../../build.rs" + + +[dependencies] +components = { path = "../../components" } +cortexm4 = { path = "../../../arch/cortex-m4" } +kernel = { path = "../../../kernel" } +apollo3 = { path = "../../../chips/apollo3" } + +capsules-core = { path = "../../../capsules/core" } +capsules-extra = { path = "../../../capsules/extra" } +capsules-system = { path = "../../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/apollo3/lora_things_plus/Makefile b/boards/apollo3/lora_things_plus/Makefile new file mode 100644 index 0000000000..9d2604a8a1 --- /dev/null +++ b/boards/apollo3/lora_things_plus/Makefile @@ -0,0 +1,52 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +# Makefile for building the tock kernel for the SparkFun LoRa Thing Plus - expLoRaBLE +# +TARGET=thumbv7em-none-eabi +PLATFORM=lora_things_plus + +include ../../Makefile.common + +ifndef PORT + PORT="/dev/ttyUSB0" +endif + +# Default target for installing the kernel. +.PHONY: install +install: flash + +.PHONY: flash +flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin + python ../ambiq/ambiq_bin2board.py --bin $< \ + --load-address-blob 0xE000 \ + -b 115200 -port $(PORT) -r 2 -v \ + --magic-num 0xCB --version 0x0 \ + --load-address-wired 0xC000 \ + -i 6 --options 0x1 + +.PHONY: flash-debug +flash-debug: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).bin + python ../ambiq/ambiq_bin2board.py --bin $< \ + --load-address-blob 0xE000 \ + -b 115200 -port $(PORT) -r 2 -v \ + --magic-num 0xCB --version 0x0 \ + --load-address-wired 0xC000 \ + -i 6 --options 0x1 + +.PHONY: flash-app +flash-app: + python ../ambiq/ambiq_bin2board.py --bin $(APP) \ + --load-address-blob 0x42000 \ + -b 115200 -port $(PORT) -r 2 -v \ + --magic-num 0xCB --version 0x0 \ + --load-address-wired 0x40000 \ + -i 6 --options 0x1 + +.PHONY: test +test: + mkdir -p $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/deps/ + $(Q)cp layout.ld $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/deps/ + $(Q)cp ../../kernel_layout.ld $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/ + $(Q)RUSTFLAGS="$(RUSTC_FLAGS_TOCK)" OBJCOPY=${OBJCOPY} PORT=$(PORT) $(CARGO) test $(CARGO_FLAGS_TOCK_NO_BUILD_STD) $(NO_RUN) --bin $(PLATFORM) --release diff --git a/boards/apollo3/lora_things_plus/README.md b/boards/apollo3/lora_things_plus/README.md new file mode 100644 index 0000000000..2048636e7d --- /dev/null +++ b/boards/apollo3/lora_things_plus/README.md @@ -0,0 +1,139 @@ +SparkFun LoRa Thing Plus - expLoRaBLE +===================================== + +## Board features + + - 21 Multifunctional GPIO + - Thing Plus (or Feather) Form-Factor: + - USB-C Connector + - 2-pin JST Connector for a LiPo Battery (not included) + - 4-pin JST Qwiic Connector + - LoRa + - BLE + +For more details [visit the SparkFun +website](https://www.sparkfun.com/products/17506). + +## Flashing the kernel + +The kernel can be programmed using the Ambiq python scrips. `cd` into `boards/apollo3/lora_things_plus/` +directory and run: + +```shell +$ make flash + +(or) + +$ make flash-debug +``` + +This will flash Tock onto the board via the /dev/ttyUSB0 port. If you would like to use a different port you can specify it from the `PORT` variable. + +```bash +$ PORT=/dev/ttyUSB2 make flash +``` + +This will flash Tock over the SparkFun Variable Loader (SVL) using the Ambiq loader. +The SVL can always be re-flashed if you want to. + + +## Debugging the board + +The SparkFun LoRa Thing Plus exposes JTAG via the small headers in the middle of +the board. See the [SparkFun hookup guide](https://learn.sparkfun.com/tutorials/sparkfun-explorable-hookup-guide/all) for a picture of this. + +SparkFun sell accessories you can use to connecting to this. It appears +something like the J-Link BASE will work, but that hasn't been tested by Tock. + +Instead, Tock has tested debugging with the [Black Magic Probe](https://black-magic.org/). +The Black Magic Probe (BMP) is an easy to use, mostly plug and play, JTAG/SWD debugger +for embedded microcontrollers. + +In order to debug with the BMP, first connect the 2x5 SWD cable to the board +and the BMP. + +Then power on both boards. + +Fire up an ARM GDB instance and attach to the BMP with: + +``` +target extended-remote /dev/ttyACM0 +monitor swdp_scan +attach 1 +``` + +You can then use GDB to debug the board + +## Using LoRa with the board + +Tock itself does not support LoRa, instead we run a LoRa application in +userspace and use the LoRa specific GPIO and SPI syscalls to control the +radio. + +### LoRaMac-node + +LoRaMac-node is written by Semtech the maker of LoRa. It's a commonly used +example implementation. The LoRaMac-node implementation has been tested and +is capable of sending and recieving between two boards running Tock. + +Unfortunately it appears to be unsupported. There is currently a pull request +open to add support to running on Tock: +https://github.com/Lora-net/LoRaMac-node/pull/1390 + +You can build the application by running the following in the `LoRaMac-node` +directory: + +```shell +$ mkdir build +$ cd build +$ cmake -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_TOOLCHAIN_FILE="../cmake/toolchain-arm-none-eabi.cmake" \ + -DAPPLICATION="ping-pong" \ + -DMODULATION="LORA" \ + -DREGION_EU868="OFF" \ + -DREGION_US915="OFF" \ + -DREGION_CN779="OFF" \ + -DREGION_EU433="OFF" \ + -DREGION_AU915="ON" \ + -DREGION_AS923="OFF" \ + -DREGION_CN470="OFF" \ + -DREGION_KR920="OFF" \ + -DREGION_IN865="OFF" \ + -DREGION_RU864="OFF" \ + -DBOARD="Tock" \ + -DUSE_RADIO_DEBUG="ON" .. +$ make +$ elf2tab -n ping-pong --stack 2048 --app-heap 1024 --kernel-heap 1024 --kernel-major 2 --kernel-minor 1 -v ./src/apps/ping-pong/ping-pong +``` + +Then in the Tock repo you can flash the app with: + +```shell +$ make flash; APP=LoRaMac-node/build/src/apps/ping-pong/ping-pong.tbf make flash-app +``` + +### RadioLib + +RadioLib is a Universal wireless communication library for embedded devices. + +RadioLib includes support for the Semtech SX1262 LoRa module and a range of +other protocols. See the +[RadioLib README](https://github.com/jgromes/RadioLib#supported-protocols-and-digital-modes) +for more details. + +RadioLib has upstream Tock support, which can be found here: +https://github.com/jgromes/RadioLib/tree/master/examples/NonArduino/Tock + +The RadioLib example can be built with: + +```shell +$ git clone https://github.com/jgromes/RadioLib.git +$ cd RadioLib/examples/NonArduino/Tock/ +$ ./build.sh +``` + +Then in the Tock repo you can flash the kernel and app with: + +```shell +$ make flash; APP=RadioLib/examples/NonArduino/Tock/build/tock-sx1261.tbf make flash-app +``` diff --git a/boards/apollo3/lora_things_plus/layout.ld b/boards/apollo3/lora_things_plus/layout.ld new file mode 100644 index 0000000000..a2e4bbdd4d --- /dev/null +++ b/boards/apollo3/lora_things_plus/layout.ld @@ -0,0 +1,18 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + +/* We have to reduce all flash lengths by 0x2000 as that is the offset + * we use when writing data to flash with ambiq_bin2board.py. + */ + +MEMORY +{ + rom (rx) : ORIGIN = 0x0000C000, LENGTH = 0x034000 - 0x2000 + prog (rx) : ORIGIN = 0x00040000, LENGTH = 0x100000 - 0x040000 - 0x2000 + ram (rwx) : ORIGIN = 0x10000000, LENGTH = 0x60000 +} + +PAGE_SIZE = 8K; + +INCLUDE ../../kernel_layout.ld diff --git a/boards/apollo3/lora_things_plus/run.sh b/boards/apollo3/lora_things_plus/run.sh new file mode 100755 index 0000000000..8a55087c0f --- /dev/null +++ b/boards/apollo3/lora_things_plus/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +set -ex + +${OBJCOPY} --output-target=binary ${OBJCOPY_FLAGS} ${1} redboard-artemis-nano-tests.bin +python ../ambiq/ambiq_bin2board.py --bin redboard-artemis-nano-tests.bin --load-address-blob 0x40000 -b 115200 -port ${PORT} -r 2 -v --magic-num 0xCB --version 0x0 --load-address-wired 0xc000 -i 6 --options 0x1 + +# If we connect too quickly the UART doesn't work, so add a small delay +sleep 1 +screen ${PORT} 115200 diff --git a/boards/apollo3/lora_things_plus/src/io.rs b/boards/apollo3/lora_things_plus/src/io.rs new file mode 100644 index 0000000000..0dfe7a1060 --- /dev/null +++ b/boards/apollo3/lora_things_plus/src/io.rs @@ -0,0 +1,61 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use core::fmt::Write; +use core::panic::PanicInfo; +use core::ptr::addr_of; +use core::ptr::addr_of_mut; + +use crate::CHIP; +use crate::PROCESSES; +use crate::PROCESS_PRINTER; +use kernel::debug; +use kernel::debug::IoWrite; +use kernel::hil::led; + +/// Writer is used by kernel::debug to panic message to the serial port. +pub struct Writer {} + +/// Global static for debug writer +pub static mut WRITER: Writer = Writer {}; + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> ::core::fmt::Result { + self.write(s.as_bytes()); + Ok(()) + } +} + +impl IoWrite for Writer { + fn write(&mut self, buf: &[u8]) -> usize { + let uart = apollo3::uart::Uart::new_uart_0(); // Aliases memory for uart0. Okay bc we are panicking. + uart.transmit_sync(buf); + buf.len() + } +} + +/// Panic handler. +#[no_mangle] +#[panic_handler] +pub unsafe fn panic_fmt(info: &PanicInfo) -> ! { + // just create a new pin reference here instead of using global + let led_pin = &mut apollo3::gpio::GpioPin::new( + kernel::utilities::StaticRef::new( + apollo3::gpio::GPIO_BASE_RAW as *const apollo3::gpio::GpioRegisters, + ), + apollo3::gpio::Pin::Pin26, + ); + let led = &mut led::LedLow::new(led_pin); + let writer = &mut *addr_of_mut!(WRITER); + + debug::panic( + &mut [led], + writer, + info, + &cortexm4::support::nop, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), + ) +} diff --git a/boards/apollo3/lora_things_plus/src/main.rs b/boards/apollo3/lora_things_plus/src/main.rs new file mode 100644 index 0000000000..a3b80e2e33 --- /dev/null +++ b/boards/apollo3/lora_things_plus/src/main.rs @@ -0,0 +1,569 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Board file for SparkFun LoRa Thing Plus - expLoRaBLE +//! +//! - +//! +//! A Semtech SX1262 is connected as a SPI slave to IOM3 +//! See +//! and for details +//! +//! See +//! and +//! for details on the pin break outs +//! +//! ION0: Qwiic I2C +//! IOM1: Not connected +//! IOM2: Broken out SPI +//! IOM3: Semtech SX1262 +//! Apollo 3 Pin Number | Apollo 3 Name | SX1262 Pin Number | SX1262 Name | SX1262 Description +//! H6 | GPIO 36 | 19 | NSS | SPI slave select +//! J6 | GPIO 38 | 17 | MOSI | SPI slave input +//! J5 | GPIO 43 | 16 | MISO | SPI slave output +//! H5 | GPIO 42 | 18 | SCK | SPI clock input +//! J8 | GPIO 39 | 14 | BUSY | Radio busy indicator +//! J9 | GPIO 40 | 13 | DIO1 | Multipurpose digital I/O +//! H9 | GPIO 47 | 6 | DIO3 | Multipurpose digital I/O +//! J7 | GPIO 44 | 15 | NRESET | Radio reset signal, active low +//! IOM4: Not connected +//! IOM5: Pins used by UART0 + +#![no_std] +// Disable this attribute when documenting, as a workaround for +// https://github.com/rust-lang/rust/issues/62184. +#![cfg_attr(not(doc), no_main)] +#![deny(missing_docs)] +#![feature(custom_test_frameworks)] +#![test_runner(test_runner)] +#![reexport_test_harness_main = "test_main"] + +use core::ptr::addr_of; +use core::ptr::addr_of_mut; + +use apollo3::chip::Apollo3DefaultPeripherals; +use capsules_core::virtualizers::virtual_alarm::MuxAlarm; +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; +use components::bme280::Bme280Component; +use components::ccs811::Ccs811Component; +use kernel::capabilities; +use kernel::component::Component; +use kernel::hil::i2c::I2CMaster; +use kernel::hil::led::LedHigh; +use kernel::hil::spi::SpiMaster; +use kernel::hil::time::Counter; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::scheduler::round_robin::RoundRobinSched; +use kernel::{create_capability, debug, static_init}; + +/// Support routines for debugging I/O. +pub mod io; + +#[cfg(test)] +mod tests; + +// Number of concurrent processes this platform supports. +const NUM_PROCS: usize = 4; + +// Actual memory for holding the active process structures. +static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = [None; 4]; + +// Static reference to chip for panic dumps. +static mut CHIP: Option<&'static apollo3::chip::Apollo3> = None; +// Static reference to process printer for panic dumps. +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; + +// How should the kernel respond when a process faults. +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; + +// Test access to the peripherals +#[cfg(test)] +static mut PERIPHERALS: Option<&'static Apollo3DefaultPeripherals> = None; +// Test access to board +#[cfg(test)] +static mut BOARD: Option<&'static kernel::Kernel> = None; +// Test access to platform +#[cfg(test)] +static mut PLATFORM: Option<&'static LoRaThingsPlus> = None; +// Test access to main loop capability +#[cfg(test)] +static mut MAIN_CAP: Option<&dyn kernel::capabilities::MainLoopCapability> = None; +// Test access to alarm +static mut ALARM: Option<&'static MuxAlarm<'static, apollo3::stimer::STimer<'static>>> = None; +// Test access to sensors +static mut BME280: Option< + &'static capsules_extra::bme280::Bme280< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, apollo3::iom::Iom<'static>>, + >, +> = None; +static mut CCS811: Option<&'static capsules_extra::ccs811::Ccs811<'static>> = None; + +/// Dummy buffer that causes the linker to reserve enough space for the stack. +#[no_mangle] +#[link_section = ".stack_buffer"] +pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; + +const LORA_SPI_DRIVER_NUM: usize = capsules_core::driver::NUM::LoRaPhySPI as usize; +const LORA_GPIO_DRIVER_NUM: usize = capsules_core::driver::NUM::LoRaPhyGPIO as usize; + +type BME280Sensor = components::bme280::Bme280ComponentType< + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, apollo3::iom::Iom<'static>>, +>; +type TemperatureDriver = components::temperature::TemperatureComponentType; +type HumidityDriver = components::humidity::HumidityComponentType; + +/// A structure representing this platform that holds references to all +/// capsules for this platform. +struct LoRaThingsPlus { + alarm: &'static capsules_core::alarm::AlarmDriver< + 'static, + VirtualMuxAlarm<'static, apollo3::stimer::STimer<'static>>, + >, + led: &'static capsules_core::led::LedDriver< + 'static, + LedHigh<'static, apollo3::gpio::GpioPin<'static>>, + 1, + >, + gpio: &'static capsules_core::gpio::GPIO<'static, apollo3::gpio::GpioPin<'static>>, + console: &'static capsules_core::console::Console<'static>, + i2c_master: + &'static capsules_core::i2c_master::I2CMasterDriver<'static, apollo3::iom::Iom<'static>>, + external_spi_controller: &'static capsules_core::spi_controller::Spi< + 'static, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice< + 'static, + apollo3::iom::Iom<'static>, + >, + >, + sx1262_spi_controller: &'static capsules_core::spi_controller::Spi< + 'static, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice< + 'static, + apollo3::iom::Iom<'static>, + >, + >, + sx1262_gpio: &'static capsules_core::gpio::GPIO<'static, apollo3::gpio::GpioPin<'static>>, + ble_radio: &'static capsules_extra::ble_advertising_driver::BLE< + 'static, + apollo3::ble::Ble<'static>, + VirtualMuxAlarm<'static, apollo3::stimer::STimer<'static>>, + >, + temperature: &'static TemperatureDriver, + humidity: &'static HumidityDriver, + air_quality: &'static capsules_extra::air_quality::AirQualitySensor<'static>, + scheduler: &'static RoundRobinSched<'static>, + systick: cortexm4::systick::SysTick, +} + +/// Mapping of integer syscalls to objects that implement syscalls. +impl SyscallDriverLookup for LoRaThingsPlus { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::i2c_master::DRIVER_NUM => f(Some(self.i2c_master)), + capsules_core::spi_controller::DRIVER_NUM => f(Some(self.external_spi_controller)), + LORA_SPI_DRIVER_NUM => f(Some(self.sx1262_spi_controller)), + LORA_GPIO_DRIVER_NUM => f(Some(self.sx1262_gpio)), + capsules_extra::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temperature)), + capsules_extra::humidity::DRIVER_NUM => f(Some(self.humidity)), + capsules_extra::air_quality::DRIVER_NUM => f(Some(self.air_quality)), + _ => f(None), + } + } +} + +impl KernelResources> for LoRaThingsPlus { + type SyscallDriverLookup = Self; + type SyscallFilter = (); + type ProcessFault = (); + type Scheduler = RoundRobinSched<'static>; + type SchedulerTimer = cortexm4::systick::SysTick; + type WatchDog = (); + type ContextSwitchCallback = (); + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + &() + } + fn process_fault(&self) -> &Self::ProcessFault { + &() + } + fn scheduler(&self) -> &Self::Scheduler { + self.scheduler + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + &self.systick + } + fn watchdog(&self) -> &Self::WatchDog { + &() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + &() + } +} + +// Ensure that `setup()` is never inlined +// This helps reduce the stack frame, see https://github.com/tock/tock/issues/3518 +#[inline(never)] +unsafe fn setup() -> ( + &'static kernel::Kernel, + &'static LoRaThingsPlus, + &'static apollo3::chip::Apollo3, + &'static Apollo3DefaultPeripherals, +) { + let peripherals = static_init!(Apollo3DefaultPeripherals, Apollo3DefaultPeripherals::new()); + + // No need to statically allocate mcu/pwr/clk_ctrl because they are only used in main! + let mcu_ctrl = apollo3::mcuctrl::McuCtrl::new(); + let pwr_ctrl = apollo3::pwrctrl::PwrCtrl::new(); + let clkgen = apollo3::clkgen::ClkGen::new(); + + clkgen.set_clock_frequency(apollo3::clkgen::ClockFrequency::Freq48MHz); + + // initialize capabilities + let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability); + let memory_allocation_cap = create_capability!(capabilities::MemoryAllocationCapability); + + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); + + // Power up components + pwr_ctrl.enable_uart0(); + pwr_ctrl.enable_iom0(); + pwr_ctrl.enable_iom2(); + pwr_ctrl.enable_iom3(); + + // Enable PinCfg + peripherals + .gpio_port + .enable_uart(&peripherals.gpio_port[48], &peripherals.gpio_port[49]); + // Enable SDA and SCL for I2C (exposed via Qwiic) + peripherals + .gpio_port + .enable_i2c(&peripherals.gpio_port[6], &peripherals.gpio_port[5]); + // Enable Main SPI + peripherals.gpio_port.enable_spi( + &peripherals.gpio_port[27], + &peripherals.gpio_port[28], + &peripherals.gpio_port[25], + ); + // Enable SPI for SX1262 + peripherals.gpio_port.enable_spi( + &peripherals.gpio_port[42], + &peripherals.gpio_port[38], + &peripherals.gpio_port[43], + ); + // Enable the radio pins + peripherals.gpio_port.enable_sx1262_radio_pins(); + + // Configure kernel debug gpios as early as possible + kernel::debug::assign_gpios(Some(&peripherals.gpio_port[26]), None, None); + + // Create a shared UART channel for the console and for kernel debug. + let uart_mux = components::console::UartMuxComponent::new(&peripherals.uart0, 115200) + .finalize(components::uart_mux_component_static!()); + + // Setup the console. + let console = components::console::ConsoleComponent::new( + board_kernel, + capsules_core::console::DRIVER_NUM, + uart_mux, + ) + .finalize(components::console_component_static!()); + // Create the debugger object that handles calls to `debug!()`. + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); + + // LEDs + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( + LedHigh<'static, apollo3::gpio::GpioPin>, + LedHigh::new(&peripherals.gpio_port[19]), + )); + + // GPIOs + // Details are at: https://github.com/NorthernMechatronics/nmsdk/blob/master/bsp/nm180100evb/bsp_pins.src + let gpio = components::gpio::GpioComponent::new( + board_kernel, + capsules_core::gpio::DRIVER_NUM, + components::gpio_component_helper!( + apollo3::gpio::GpioPin, + 0 => &peripherals.gpio_port[13], // A0 + 1 => &peripherals.gpio_port[12], // A1 + 2 => &peripherals.gpio_port[32], // A2 + 3 => &peripherals.gpio_port[35], // A3 + 4 => &peripherals.gpio_port[34], // A4 + ), + ) + .finalize(components::gpio_component_static!(apollo3::gpio::GpioPin)); + + // Create a shared virtualisation mux layer on top of a single hardware + // alarm. + let _ = peripherals.stimer.start(); + let mux_alarm = components::alarm::AlarmMuxComponent::new(&peripherals.stimer).finalize( + components::alarm_mux_component_static!(apollo3::stimer::STimer), + ); + let alarm = components::alarm::AlarmDriverComponent::new( + board_kernel, + capsules_core::alarm::DRIVER_NUM, + mux_alarm, + ) + .finalize(components::alarm_component_static!(apollo3::stimer::STimer)); + ALARM = Some(mux_alarm); + + // Create a process printer for panic. + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); + PROCESS_PRINTER = Some(process_printer); + + // Init the I2C device attached via Qwiic + let i2c_master_buffer = static_init!( + [u8; capsules_core::i2c_master::BUFFER_LENGTH], + [0; capsules_core::i2c_master::BUFFER_LENGTH] + ); + let i2c_master = static_init!( + capsules_core::i2c_master::I2CMasterDriver<'static, apollo3::iom::Iom<'static>>, + capsules_core::i2c_master::I2CMasterDriver::new( + &peripherals.iom0, + i2c_master_buffer, + board_kernel.create_grant( + capsules_core::i2c_master::DRIVER_NUM, + &memory_allocation_cap + ) + ) + ); + + peripherals.iom0.set_master_client(i2c_master); + peripherals.iom0.enable(); + + let mux_i2c = components::i2c::I2CMuxComponent::new(&peripherals.iom0, None).finalize( + components::i2c_mux_component_static!(apollo3::iom::Iom<'static>), + ); + + let bme280 = Bme280Component::new(mux_i2c, 0x77).finalize( + components::bme280_component_static!(apollo3::iom::Iom<'static>), + ); + let temperature = components::temperature::TemperatureComponent::new( + board_kernel, + capsules_extra::temperature::DRIVER_NUM, + bme280, + ) + .finalize(components::temperature_component_static!(BME280Sensor)); + let humidity = components::humidity::HumidityComponent::new( + board_kernel, + capsules_extra::humidity::DRIVER_NUM, + bme280, + ) + .finalize(components::humidity_component_static!(BME280Sensor)); + BME280 = Some(bme280); + + let ccs811 = Ccs811Component::new(mux_i2c, 0x5B).finalize( + components::ccs811_component_static!(apollo3::iom::Iom<'static>), + ); + let air_quality = components::air_quality::AirQualityComponent::new( + board_kernel, + capsules_extra::temperature::DRIVER_NUM, + ccs811, + ) + .finalize(components::air_quality_component_static!()); + CCS811 = Some(ccs811); + + // Init the broken out SPI controller + let external_mux_spi = components::spi::SpiMuxComponent::new(&peripherals.iom2).finalize( + components::spi_mux_component_static!(apollo3::iom::Iom<'static>), + ); + + let external_spi_controller = components::spi::SpiSyscallComponent::new( + board_kernel, + external_mux_spi, + &peripherals.gpio_port[11], // A5 + capsules_core::spi_controller::DRIVER_NUM, + ) + .finalize(components::spi_syscall_component_static!( + apollo3::iom::Iom<'static> + )); + + // Init the internal SX1262 SPI controller + let sx1262_mux_spi = components::spi::SpiMuxComponent::new(&peripherals.iom3).finalize( + components::spi_mux_component_static!(apollo3::iom::Iom<'static>), + ); + + let sx1262_spi_controller = components::spi::SpiSyscallComponent::new( + board_kernel, + sx1262_mux_spi, + &peripherals.gpio_port[36], // H6 - SX1262 Slave Select + LORA_SPI_DRIVER_NUM, + ) + .finalize(components::spi_syscall_component_static!( + apollo3::iom::Iom<'static> + )); + peripherals + .iom3 + .specify_chip_select(&peripherals.gpio_port[36]) + .unwrap(); + + let sx1262_gpio = components::gpio::GpioComponent::new( + board_kernel, + LORA_GPIO_DRIVER_NUM, + components::gpio_component_helper!( + apollo3::gpio::GpioPin, + 0 => &peripherals.gpio_port[36], // H6 - SX1262 Slave Select + 1 => &peripherals.gpio_port[39], // J8 - SX1262 Radio Busy Indicator + 2 => &peripherals.gpio_port[40], // J9 - SX1262 Multipurpose digital I/O (DIO1) + 3 => &peripherals.gpio_port[47], // H9 - SX1262 Multipurpose digital I/O (DIO3) + 4 => &peripherals.gpio_port[44], // J7 - SX1262 Reset + ), + ) + .finalize(components::gpio_component_static!(apollo3::gpio::GpioPin)); + + // Setup BLE + mcu_ctrl.enable_ble(); + clkgen.enable_ble(); + pwr_ctrl.enable_ble(); + peripherals.ble.setup_clocks(); + mcu_ctrl.reset_ble(); + peripherals.ble.power_up(); + peripherals.ble.ble_initialise(); + + let ble_radio = components::ble::BLEComponent::new( + board_kernel, + capsules_extra::ble_advertising_driver::DRIVER_NUM, + &peripherals.ble, + mux_alarm, + ) + .finalize(components::ble_component_static!( + apollo3::stimer::STimer, + apollo3::ble::Ble, + )); + + mcu_ctrl.print_chip_revision(); + + debug!("Initialization complete. Entering main loop"); + + // These symbols are defined in the linker script. + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + } + + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); + + let systick = cortexm4::systick::SysTick::new_with_calibration(48_000_000); + + let artemis_nano = static_init!( + LoRaThingsPlus, + LoRaThingsPlus { + alarm, + led, + gpio, + console, + i2c_master, + external_spi_controller, + sx1262_spi_controller, + sx1262_gpio, + ble_radio, + temperature, + humidity, + air_quality, + scheduler, + systick, + } + ); + + let chip = static_init!( + apollo3::chip::Apollo3, + apollo3::chip::Apollo3::new(peripherals) + ); + CHIP = Some(chip); + + kernel::process::load_processes( + board_kernel, + chip, + core::slice::from_raw_parts( + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, + ), + core::slice::from_raw_parts_mut( + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, + ), + &mut *addr_of_mut!(PROCESSES), + &FAULT_RESPONSE, + &process_mgmt_cap, + ) + .unwrap_or_else(|err| { + debug!("Error loading processes!"); + debug!("{:?}", err); + }); + + (board_kernel, artemis_nano, chip, peripherals) +} + +/// Main function. +/// +/// This function is called from the arch crate after some very basic RISC-V +/// setup and RAM initialization. +#[no_mangle] +pub unsafe fn main() { + apollo3::init(); + + #[cfg(test)] + test_main(); + + #[cfg(not(test))] + { + let (board_kernel, sf_lora_thing_plus_board, chip, _peripherals) = setup(); + + let main_loop_cap = create_capability!(capabilities::MainLoopCapability); + + board_kernel.kernel_loop( + sf_lora_thing_plus_board, + chip, + None::<&kernel::ipc::IPC<{ NUM_PROCS as u8 }>>, + &main_loop_cap, + ); + } +} + +#[cfg(test)] +use kernel::platform::watchdog::WatchDog; + +#[cfg(test)] +fn test_runner(tests: &[&dyn Fn()]) { + unsafe { + let (board_kernel, sf_lora_thing_plus_board, _chip, peripherals) = setup(); + + BOARD = Some(board_kernel); + PLATFORM = Some(&sf_lora_thing_plus_board); + PERIPHERALS = Some(peripherals); + MAIN_CAP = Some(&create_capability!(capabilities::MainLoopCapability)); + + PLATFORM.map(|p| { + p.watchdog().setup(); + }); + + for test in tests { + test(); + } + } + + loop {} +} diff --git a/boards/apollo3/lora_things_plus/src/tests/environmental_sensors.rs b/boards/apollo3/lora_things_plus/src/tests/environmental_sensors.rs new file mode 100644 index 0000000000..f85ecc7d34 --- /dev/null +++ b/boards/apollo3/lora_things_plus/src/tests/environmental_sensors.rs @@ -0,0 +1,198 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Test that we can get temperature, humidity and pressure from the BME280 +//! chip. +//! This test requires the "SparkFun Environmental Combo Breakout" board +//! connected via the Qwiic connector +//! (https://www.sparkfun.com/products/14348). + +use crate::tests::run_kernel_op; +use crate::{BME280, CCS811}; +use core::cell::Cell; +use kernel::debug; +use kernel::hil::sensors::{ + AirQualityClient, AirQualityDriver, HumidityClient, HumidityDriver, TemperatureClient, + TemperatureDriver, +}; +use kernel::ErrorCode; + +struct SensorTestCallback { + temperature_done: Cell, + humidity_done: Cell, + co2_done: Cell, + tvoc_done: Cell, + calibration_temp: Cell>, + calibration_humidity: Cell>, +} + +unsafe impl Sync for SensorTestCallback {} + +impl<'a> SensorTestCallback { + const fn new() -> Self { + SensorTestCallback { + temperature_done: Cell::new(false), + humidity_done: Cell::new(false), + co2_done: Cell::new(false), + tvoc_done: Cell::new(false), + calibration_temp: Cell::new(None), + calibration_humidity: Cell::new(None), + } + } + + fn reset(&self) { + self.temperature_done.set(false); + self.humidity_done.set(false); + self.co2_done.set(false); + self.tvoc_done.set(false); + } +} + +impl<'a> TemperatureClient for SensorTestCallback { + fn callback(&self, result: Result) { + self.temperature_done.set(true); + self.calibration_temp.set(Some(result.unwrap())); + + debug!("Temperature: {}", result.unwrap()); + } +} + +impl<'a> HumidityClient for SensorTestCallback { + fn callback(&self, value: usize) { + self.humidity_done.set(true); + self.calibration_humidity.set(Some(value as u32)); + + debug!("Humidity: {}", value); + } +} + +impl<'a> AirQualityClient for SensorTestCallback { + fn environment_specified(&self, result: Result<(), ErrorCode>) { + result.unwrap(); + } + + fn co2_data_available(&self, value: Result) { + self.co2_done.set(true); + + debug!("CO2: {} ppm", value.unwrap()); + } + + fn tvoc_data_available(&self, value: Result) { + self.tvoc_done.set(true); + + debug!("TVOC: {} ppb", value.unwrap()); + } +} + +static CALLBACK: SensorTestCallback = SensorTestCallback::new(); + +#[test_case] +fn run_bme280_temperature() { + debug!("check run BME280 Temperature... "); + run_kernel_op(100); + + let bme280 = unsafe { BME280.unwrap() }; + + // Make sure the device is ready for us. + // The setup can take a little bit of time + run_kernel_op(800000); + + TemperatureDriver::set_client(bme280, &CALLBACK); + CALLBACK.reset(); + + bme280.read_temperature().unwrap(); + + run_kernel_op(50000); + assert_eq!(CALLBACK.temperature_done.get(), true); + + debug!(" [ok]"); + run_kernel_op(100); +} + +#[test_case] +fn run_bme280_humidity() { + debug!("check run BME280 Humidity... "); + run_kernel_op(100); + + let bme280 = unsafe { BME280.unwrap() }; + + // Make sure the debice is ready for us. + // The setup can take a little bit of time + run_kernel_op(800000); + + HumidityDriver::set_client(bme280, &CALLBACK); + CALLBACK.reset(); + + bme280.read_humidity().unwrap(); + + run_kernel_op(50000); + assert_eq!(CALLBACK.humidity_done.get(), true); + + debug!(" [ok]"); + run_kernel_op(100); +} + +#[test_case] +fn run_ccs811_co2() { + debug!("check run CCS811 CO2... "); + run_kernel_op(100); + + let ccs811 = unsafe { CCS811.unwrap() }; + + // Make sure the device is ready for us. + // The setup can take a little bit of time + run_kernel_op(800000); + + AirQualityDriver::set_client(ccs811, &CALLBACK); + CALLBACK.reset(); + + ccs811 + .specify_environment( + CALLBACK.calibration_temp.get(), + CALLBACK.calibration_humidity.get(), + ) + .unwrap(); + + run_kernel_op(7000); + + ccs811.read_co2().unwrap(); + + run_kernel_op(7000); + assert_eq!(CALLBACK.co2_done.get(), true); + + debug!(" [ok]"); + run_kernel_op(100); +} + +#[test_case] +fn run_ccs811_tvoc() { + debug!("check run CCS811 TVOC... "); + run_kernel_op(100); + + let ccs811 = unsafe { CCS811.unwrap() }; + + // Make sure the device is ready for us. + // The setup can take a little bit of time + run_kernel_op(800000); + + AirQualityDriver::set_client(ccs811, &CALLBACK); + CALLBACK.reset(); + + ccs811 + .specify_environment( + CALLBACK.calibration_temp.get(), + CALLBACK.calibration_humidity.get(), + ) + .unwrap(); + + run_kernel_op(7000); + + ccs811.read_tvoc().unwrap(); + + run_kernel_op(7000); + assert_eq!(CALLBACK.tvoc_done.get(), true); + + debug!(" [ok]"); + run_kernel_op(100); +} diff --git a/boards/apollo3/lora_things_plus/src/tests/mod.rs b/boards/apollo3/lora_things_plus/src/tests/mod.rs new file mode 100644 index 0000000000..3e81507496 --- /dev/null +++ b/boards/apollo3/lora_things_plus/src/tests/mod.rs @@ -0,0 +1,39 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::BOARD; +use crate::CHIP; +use crate::MAIN_CAP; +use crate::NUM_PROCS; +use crate::PLATFORM; +use kernel::debug; + +fn run_kernel_op(loops: usize) { + unsafe { + for _i in 0..loops { + BOARD.unwrap().kernel_loop_operation( + PLATFORM.unwrap(), + CHIP.unwrap(), + None::<&kernel::ipc::IPC<{ NUM_PROCS as u8 }>>, + true, + MAIN_CAP.unwrap(), + ); + } + } +} + +#[test_case] +fn trivial_assertion() { + debug!("trivial assertion... "); + run_kernel_op(10000); + + assert_eq!(1, 1); + + debug!(" [ok]"); + run_kernel_op(10000); +} + +mod environmental_sensors; +mod multi_alarm; +mod spi_controller; diff --git a/boards/apollo3/lora_things_plus/src/tests/multi_alarm.rs b/boards/apollo3/lora_things_plus/src/tests/multi_alarm.rs new file mode 100644 index 0000000000..62e17f4511 --- /dev/null +++ b/boards/apollo3/lora_things_plus/src/tests/multi_alarm.rs @@ -0,0 +1,92 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Test the behavior of a single alarm. +//! To add this test, include the line +//! ``` +//! multi_alarm_test::run_alarm(alarm_mux); +//! ``` +//! to the OpenTitan boot sequence, where `alarm_mux` is a +//! `capsules_core::virtualizers::virtual_alarm::MuxAlarm`. The test sets up 3 +//! different virtualized alarms of random durations and prints +//! out when they fire. The durations are uniformly random with +//! one caveat, that 1 in 11 is of duration 0; this is to test +//! that alarms whose expiration was in the past due to the +//! latency of software work correctly. + +use crate::tests::run_kernel_op; +use crate::ALARM; +use apollo3::stimer::STimer; +use capsules_core::test::random_alarm::TestRandomAlarm; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use kernel::debug; +use kernel::hil::time::Alarm; +use kernel::static_init; + +static mut TESTS: Option< + [&'static TestRandomAlarm<'static, VirtualMuxAlarm<'static, STimer<'static>>>; 3], +> = None; + +#[test_case] +pub fn run_multi_alarm() { + debug!("start multi alarm test..."); + unsafe { + TESTS = Some(static_init_multi_alarm_test(ALARM.unwrap())); + TESTS.unwrap()[0].run(); + TESTS.unwrap()[1].run(); + TESTS.unwrap()[2].run(); + } + + run_kernel_op(10000); + + unsafe { + assert!(TESTS.unwrap()[0].counter.get() > 15); + assert!(TESTS.unwrap()[1].counter.get() > 30); + assert!(TESTS.unwrap()[2].counter.get() > 80); + } + + debug!(" [ok]"); + run_kernel_op(10000); +} + +unsafe fn static_init_multi_alarm_test( + mux: &'static MuxAlarm<'static, STimer<'static>>, +) -> [&'static TestRandomAlarm<'static, VirtualMuxAlarm<'static, STimer<'static>>>; 3] { + let virtual_alarm1 = static_init!( + VirtualMuxAlarm<'static, STimer<'static>>, + VirtualMuxAlarm::new(mux) + ); + virtual_alarm1.setup(); + + let test1 = static_init!( + TestRandomAlarm<'static, VirtualMuxAlarm<'static, STimer<'static>>>, + TestRandomAlarm::new(virtual_alarm1, 19, 'A', false) + ); + virtual_alarm1.set_alarm_client(test1); + + let virtual_alarm2 = static_init!( + VirtualMuxAlarm<'static, STimer<'static>>, + VirtualMuxAlarm::new(mux) + ); + virtual_alarm2.setup(); + + let test2 = static_init!( + TestRandomAlarm<'static, VirtualMuxAlarm<'static, STimer<'static>>>, + TestRandomAlarm::new(virtual_alarm2, 37, 'B', false) + ); + virtual_alarm2.set_alarm_client(test2); + + let virtual_alarm3 = static_init!( + VirtualMuxAlarm<'static, STimer<'static>>, + VirtualMuxAlarm::new(mux) + ); + virtual_alarm3.setup(); + + let test3 = static_init!( + TestRandomAlarm<'static, VirtualMuxAlarm<'static, STimer<'static>>>, + TestRandomAlarm::new(virtual_alarm3, 89, 'C', false) + ); + virtual_alarm3.set_alarm_client(test3); + [&*test1, &*test2, &*test3] +} diff --git a/boards/apollo3/lora_things_plus/src/tests/spi_controller.rs b/boards/apollo3/lora_things_plus/src/tests/spi_controller.rs new file mode 100644 index 0000000000..c36eecd760 --- /dev/null +++ b/boards/apollo3/lora_things_plus/src/tests/spi_controller.rs @@ -0,0 +1,231 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::tests::run_kernel_op; +use crate::PERIPHERALS; +use core::cell::Cell; +#[allow(unused_imports)] +use kernel::hil::spi::{ClockPhase, ClockPolarity}; +use kernel::hil::spi::{SpiMaster, SpiMasterClient}; +use kernel::static_init; +use kernel::utilities::cells::TakeCell; +use kernel::{debug, ErrorCode}; + +struct SpiHostCallback { + transfer_done: Cell, + tx_len: Cell, + tx_data: TakeCell<'static, [u8]>, + rx_data: TakeCell<'static, [u8]>, +} + +impl<'a> SpiHostCallback { + fn new(tx_data: &'static mut [u8], rx_data: &'static mut [u8]) -> Self { + SpiHostCallback { + transfer_done: Cell::new(false), + tx_len: Cell::new(0), + tx_data: TakeCell::new(tx_data), + rx_data: TakeCell::new(rx_data), + } + } + + fn reset(&self) { + self.transfer_done.set(false); + self.tx_len.set(0); + } +} + +impl<'a> SpiMasterClient for SpiHostCallback { + fn read_write_done( + &self, + tx_data: &'static mut [u8], + rx_done: Option<&'static mut [u8]>, + tx_len: usize, + rc: Result<(), ErrorCode>, + ) { + // Transfer Complete + assert_eq!(rc, Ok(())); + assert_eq!(tx_len, self.tx_len.get()); + + // Capture Buffers + match rx_done { + Some(rx_buf) => { + self.rx_data.replace(rx_buf); + } + None => { + panic!("RX Buffer Lost"); + } + } + + self.tx_data.replace(tx_data); + + if self.tx_len.get() == tx_len { + self.transfer_done.set(true); + } + } +} + +unsafe fn static_init_test_cb() -> &'static SpiHostCallback { + let rx_data = static_init!([u8; 32], [0; 32]); + + let tx_data = static_init!( + [u8; 32], + [ + 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, + 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, + 0x58, 0x57, 0x40, + ] + ); + + static_init!(SpiHostCallback, SpiHostCallback::new(tx_data, rx_data)) +} + +unsafe fn static_init_test_partial_cb() -> &'static SpiHostCallback { + let rx_data = static_init!([u8; 513], [0; 513]); + // Buffer Size to exceed TXFIFO (Force partial transfers) + let tx_data = static_init!( + [u8; 513], + [ + 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, + 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, + 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, + 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, + 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, + 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, + 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, + 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, + 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, + 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, + 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, + 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, + 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, + 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, + 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, + 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, + 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, + 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, + 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, + 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, + 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, + 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, + 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, + 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, + 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, + 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, + 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, + 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, + 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, + 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, + 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, + 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, + 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, + 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, + 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, + 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0x40, + ] + ); + + static_init!(SpiHostCallback, SpiHostCallback::new(tx_data, rx_data)) +} + +/// Tests transferring a data set that exceeds the TXFIFO (256) +/// The driver must do 3 transfers (256, 256, 1) to transfer the full 513 byte +/// dataset. This tests partial transfers and continued offset write outs. +#[test_case] +fn spi_host_transfer_partial() { + let perf = unsafe { PERIPHERALS.unwrap() }; + let spi_host = &perf.iom2; + + let cb = unsafe { static_init_test_partial_cb() }; + + debug!("[SPI] Setup iom2 partial_transfer... "); + run_kernel_op(100); + + spi_host.set_client(cb); + cb.reset(); + + let tx = cb.tx_data.take().unwrap(); + let rx = cb.rx_data.take().unwrap(); + cb.tx_len.set(tx.len()); + + // Set iom2 Configs + spi_host + .specify_chip_select( + &perf.gpio_port[11], // A5 + ) + .ok(); + spi_host.set_rate(100000).ok(); + spi_host.set_polarity(ClockPolarity::IdleLow).ok(); + spi_host.set_phase(ClockPhase::SampleLeading).ok(); + + assert_eq!( + spi_host.read_write_bytes(tx, Some(rx), cb.tx_len.get()), + Ok(()) + ); + run_kernel_op(5000); + + assert_eq!(cb.transfer_done.get(), true); + + run_kernel_op(100); + debug!(" [ok]"); + run_kernel_op(100); +} + +/// Tests two single transfers that do not exceed the TXFIFO +/// The second test, is to ensure that the driver is left in a clean state +/// after a transfer (reset internal offsets and counts etc...) +#[test_case] +fn spi_host_transfer_single() { + let perf = unsafe { PERIPHERALS.unwrap() }; + let spi_host = &perf.iom2; + + let cb = unsafe { static_init_test_cb() }; + + debug!("[SPI] Setup iom2 transfers... "); + run_kernel_op(100); + spi_host.set_client(cb); + cb.reset(); + + let tx = cb.tx_data.take().unwrap(); + let rx = cb.rx_data.take().unwrap(); + cb.tx_len.set(tx.len()); + + // Set iom2 Configs + // spi_host.specify_chip_select(0).ok(); + spi_host.set_rate(100000).ok(); + spi_host.set_polarity(ClockPolarity::IdleLow).ok(); + spi_host.set_phase(ClockPhase::SampleLeading).ok(); + + assert_eq!( + spi_host.read_write_bytes(tx, Some(rx), cb.tx_len.get()), + Ok(()) + ); + run_kernel_op(5000); + + assert_eq!(cb.transfer_done.get(), true); + + run_kernel_op(100); + debug!(" [ok]"); + run_kernel_op(100); + + debug!("[SPI] Setup iom2 transfer...x2 "); + run_kernel_op(100); + + cb.reset(); + + let tx2 = cb.tx_data.take().unwrap(); + let rx2 = cb.rx_data.take().unwrap(); + cb.tx_len.set(tx2.len()); + + assert_eq!( + spi_host.read_write_bytes(tx2, Some(rx2), cb.tx_len.get()), + Ok(()) + ); + run_kernel_op(5000); + + assert_eq!(cb.transfer_done.get(), true); + + run_kernel_op(100); + debug!(" [ok]"); + run_kernel_op(100); +} diff --git a/boards/apollo3/redboard_artemis_atp/.cargo/config.toml b/boards/apollo3/redboard_artemis_atp/.cargo/config.toml new file mode 100644 index 0000000000..e06b76bd9b --- /dev/null +++ b/boards/apollo3/redboard_artemis_atp/.cargo/config.toml @@ -0,0 +1,6 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[target.'cfg(target_arch = "arm")'] +runner = "./run.sh" diff --git a/boards/apollo3/redboard_artemis_atp/Cargo.toml b/boards/apollo3/redboard_artemis_atp/Cargo.toml new file mode 100644 index 0000000000..3254d4b14c --- /dev/null +++ b/boards/apollo3/redboard_artemis_atp/Cargo.toml @@ -0,0 +1,23 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[package] +name = "redboard_artemis_atp" +version.workspace = true +authors.workspace = true +edition.workspace = true +build = "../../build.rs" + +[dependencies] +components = { path = "../../components" } +cortexm4 = { path = "../../../arch/cortex-m4" } +kernel = { path = "../../../kernel" } +apollo3 = { path = "../../../chips/apollo3" } + +capsules-core = { path = "../../../capsules/core" } +capsules-extra = { path = "../../../capsules/extra" } +capsules-system = { path = "../../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/apollo3/redboard_artemis_atp/Makefile b/boards/apollo3/redboard_artemis_atp/Makefile new file mode 100644 index 0000000000..bf174f462e --- /dev/null +++ b/boards/apollo3/redboard_artemis_atp/Makefile @@ -0,0 +1,52 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +# Makefile for building the tock kernel for the RedBoard Artemis ATP platform +# +TARGET=thumbv7em-none-eabi +PLATFORM=redboard_artemis_atp + +include ../../Makefile.common + +ifndef PORT + PORT="/dev/ttyUSB0" +endif + +# Default target for installing the kernel. +.PHONY: install +install: flash + +.PHONY: flash +flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin + python ../ambiq/ambiq_bin2board.py --bin $< \ + --load-address-blob 0xE000 \ + -b 115200 -port $(PORT) -r 2 -v \ + --magic-num 0xCB --version 0x0 \ + --load-address-wired 0xC000 \ + -i 6 --options 0x1 + +.PHONY: flash-debug +flash-debug: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).bin + python ../ambiq/ambiq_bin2board.py --bin $< \ + --load-address-blob 0xE000 \ + -b 115200 -port $(PORT) -r 2 -v \ + --magic-num 0xCB --version 0x0 \ + --load-address-wired 0xC000 \ + -i 6 --options 0x1 + +.PHONY: flash-app +flash-app: + python ../ambiq/ambiq_bin2board.py --bin $(APP) \ + --load-address-blob 0x42000 \ + -b 115200 -port $(PORT) -r 2 -v \ + --magic-num 0xCB --version 0x0 \ + --load-address-wired 0x40000 \ + -i 6 --options 0x1 + +.PHONY: test +test: + mkdir -p $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/deps/ + $(Q)cp layout.ld $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/deps/ + $(Q)cp ../../kernel_layout.ld $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/ + $(Q)RUSTFLAGS="$(RUSTC_FLAGS_TOCK)" OBJCOPY=${OBJCOPY} PORT=$(PORT) $(CARGO) test $(CARGO_FLAGS_TOCK_NO_BUILD_STD) $(NO_RUN) --bin $(PLATFORM) --release diff --git a/boards/apollo3/redboard_artemis_atp/README.md b/boards/apollo3/redboard_artemis_atp/README.md new file mode 100644 index 0000000000..ce5edc3fa4 --- /dev/null +++ b/boards/apollo3/redboard_artemis_atp/README.md @@ -0,0 +1,84 @@ +SparkFun RedBoard Artemis ATP +============================= + +The RedBoard Artemis ATP is affectionately called 'All the Pins!' at SparkFun. +The Artemis module has 48 GPIO and this board breaks out absolutely every one +of them in a familiar Mega like form factor. + +## Board features + + - 17 GPIO - all interrupt capable + - 8 ADC channels with 14-bit precision + - 17 PWM channels + - 2 UARTs + - 4 I2C buses + - 2 SPI buses + - PDM Digital Microphone + - Qwiic Connector + +For more details [visit the SparkFun +website](https://www.sparkfun.com/products/15442). + +## Flashing the kernel + +The kernel can be programmed using the Ambiq python scrips. `cd` into `boards/apollo3/sparkfun_redboard_artemis_atp` +directory and run: + +```shell +$ make flash + +(or) + +$ make flash-debug # To flash the debug build +``` + +This will flash Tock onto the board via the /dev/ttyUSB0 port. If you would like to use a different port you can specify it from the `PORT` variable. + +```bash +$ PORT=/dev/ttyUSB2 make flash +``` + +This will flash Tock over the SparkFun Variable Loader (SVL) using the Ambiq loader. +The SVL can always be re-flashed if you want to. + +## I2C Master/Slave support + +To use I2C master/slave support the pins need to manually be wired together. This +is because the IOS uses GPIO 0 and 1, while the IOM uses GPIO 40 and 39 (exposed) +via the Qwiic connector. + +To do this use a [Qwiic connector breakout](https://www.sparkfun.com/products/14425). + +The yellow cable of the connector should be connected to GPIO 0. This is the +SCL line, which can then be connected to other devices. + +The blue cable of the connector should be connected to GPIO 1. This is the +SDA line, which can then be connected to other devices. + +## Debugging the board + +The RedBoard Artemis ATP exposes JTAG via the header in the middle of +the board. See the [SparkFun hookup guide](https://learn.sparkfun.com/tutorials/hookup-guide-for-the-sparkfun-redboard-artemis-atp) for a picture of this. + +SparkFun sell accessories you can use to connecting to this. It appears +something like the J-Link BASE will work, but that hasn't been tested by Tock. + +Instead, Tock has tested debugging with the [Black Magic Probe](https://black-magic.org/). +The Black Magic Probe (BMP) is an easy to use, mostly plug and play, JTAG/SWD debugger +for embedded microcontrollers. + +In order to debug with the BMP, first connect the 2x5 SWD cable to the RedBoard +and the BMP. The ribbon on the RedBoard should face towards the USB +connection and on the BMP away from the USB connection. + +Then power on both boards. + +Fire up an ARM GDB instance and attach to the BMP with: + +``` +target extended-remote /dev/ttyACM0 +monitor swdp_scan +attach 1 +``` + +You can then use GDB to debug the RedBoard diff --git a/boards/apollo3/redboard_artemis_atp/layout.ld b/boards/apollo3/redboard_artemis_atp/layout.ld new file mode 100644 index 0000000000..a2e4bbdd4d --- /dev/null +++ b/boards/apollo3/redboard_artemis_atp/layout.ld @@ -0,0 +1,18 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + +/* We have to reduce all flash lengths by 0x2000 as that is the offset + * we use when writing data to flash with ambiq_bin2board.py. + */ + +MEMORY +{ + rom (rx) : ORIGIN = 0x0000C000, LENGTH = 0x034000 - 0x2000 + prog (rx) : ORIGIN = 0x00040000, LENGTH = 0x100000 - 0x040000 - 0x2000 + ram (rwx) : ORIGIN = 0x10000000, LENGTH = 0x60000 +} + +PAGE_SIZE = 8K; + +INCLUDE ../../kernel_layout.ld diff --git a/boards/apollo3/redboard_artemis_atp/run.sh b/boards/apollo3/redboard_artemis_atp/run.sh new file mode 100755 index 0000000000..787a2cec64 --- /dev/null +++ b/boards/apollo3/redboard_artemis_atp/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +set -ex + +${OBJCOPY} --output-target=binary ${OBJCOPY_FLAGS} ${1} redboard-artemis-atp-tests.bin +python ../ambiq/ambiq_bin2board.py --bin redboard-artemis-atp-tests.bin --load-address-blob 0x40000 -b 115200 -port ${PORT} -r 2 -v --magic-num 0xCB --version 0x0 --load-address-wired 0xc000 -i 6 --options 0x1 + +# If we connect too quickly the UART doesn't work, so add a small delay +sleep 1 +screen ${PORT} 115200 diff --git a/boards/redboard_artemis_nano/src/io.rs b/boards/apollo3/redboard_artemis_atp/src/io.rs similarity index 72% rename from boards/redboard_artemis_nano/src/io.rs rename to boards/apollo3/redboard_artemis_atp/src/io.rs index 777635a6d4..63ecf035f3 100644 --- a/boards/redboard_artemis_nano/src/io.rs +++ b/boards/apollo3/redboard_artemis_atp/src/io.rs @@ -1,10 +1,15 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; +use core::ptr::addr_of; +use core::ptr::addr_of_mut; use crate::CHIP; use crate::PROCESSES; use crate::PROCESS_PRINTER; -use apollo3; use kernel::debug; use kernel::debug::IoWrite; use kernel::hil::led; @@ -23,16 +28,17 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { let uart = apollo3::uart::Uart::new_uart_0(); // Aliases memory for uart0. Okay bc we are panicking. uart.transmit_sync(buf); + buf.len() } } /// Panic handler. #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(info: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(info: &PanicInfo) -> ! { // just create a new pin reference here instead of using global let led_pin = &mut apollo3::gpio::GpioPin::new( kernel::utilities::StaticRef::new( @@ -41,15 +47,15 @@ pub unsafe extern "C" fn panic_fmt(info: &PanicInfo) -> ! { apollo3::gpio::Pin::Pin19, ); let led = &mut led::LedLow::new(led_pin); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led], writer, info, &cortexm4::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/apollo3/redboard_artemis_atp/src/main.rs b/boards/apollo3/redboard_artemis_atp/src/main.rs new file mode 100644 index 0000000000..d74647a9ea --- /dev/null +++ b/boards/apollo3/redboard_artemis_atp/src/main.rs @@ -0,0 +1,468 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Board file for SparkFun Redboard Artemis ATP +//! +//! - + +#![no_std] +// Disable this attribute when documenting, as a workaround for +// https://github.com/rust-lang/rust/issues/62184. +#![cfg_attr(not(doc), no_main)] +#![deny(missing_docs)] +#![feature(custom_test_frameworks)] +#![test_runner(test_runner)] +#![reexport_test_harness_main = "test_main"] + +use core::ptr::addr_of; +use core::ptr::addr_of_mut; + +use apollo3::chip::Apollo3DefaultPeripherals; +use capsules_core::i2c_master_slave_driver::I2CMasterSlaveDriver; +use capsules_core::virtualizers::virtual_alarm::MuxAlarm; +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; +use kernel::capabilities; +use kernel::component::Component; +use kernel::hil::i2c::I2CMaster; +use kernel::hil::i2c::I2CSlave; +use kernel::hil::led::LedHigh; +use kernel::hil::time::Counter; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::scheduler::round_robin::RoundRobinSched; +use kernel::{create_capability, debug, static_init}; + +/// Support routines for debugging I/O. +pub mod io; + +#[cfg(test)] +mod tests; + +// Number of concurrent processes this platform supports. +const NUM_PROCS: usize = 4; + +// Actual memory for holding the active process structures. +static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = [None; 4]; + +// Static reference to chip for panic dumps. +static mut CHIP: Option<&'static apollo3::chip::Apollo3> = None; +// Static reference to process printer for panic dumps. +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; + +// How should the kernel respond when a process faults. +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; + +// Test access to the peripherals +#[cfg(test)] +static mut PERIPHERALS: Option<&'static Apollo3DefaultPeripherals> = None; +// Test access to board +#[cfg(test)] +static mut BOARD: Option<&'static kernel::Kernel> = None; +// Test access to platform +#[cfg(test)] +static mut PLATFORM: Option<&'static RedboardArtemisAtp> = None; +// Test access to main loop capability +#[cfg(test)] +static mut MAIN_CAP: Option<&dyn kernel::capabilities::MainLoopCapability> = None; +// Test access to alarm +static mut ALARM: Option<&'static MuxAlarm<'static, apollo3::stimer::STimer<'static>>> = None; + +/// Dummy buffer that causes the linker to reserve enough space for the stack. +#[no_mangle] +#[link_section = ".stack_buffer"] +pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; + +/// A structure representing this platform that holds references to all +/// capsules for this platform. +struct RedboardArtemisAtp { + alarm: &'static capsules_core::alarm::AlarmDriver< + 'static, + VirtualMuxAlarm<'static, apollo3::stimer::STimer<'static>>, + >, + led: &'static capsules_core::led::LedDriver< + 'static, + LedHigh<'static, apollo3::gpio::GpioPin<'static>>, + 1, + >, + gpio: &'static capsules_core::gpio::GPIO<'static, apollo3::gpio::GpioPin<'static>>, + console: &'static capsules_core::console::Console<'static>, + i2c_master_slave: &'static capsules_core::i2c_master_slave_driver::I2CMasterSlaveDriver< + 'static, + capsules_core::i2c_master_slave_combo::I2CMasterSlaveCombo< + 'static, + apollo3::iom::Iom<'static>, + apollo3::ios::Ios<'static>, + >, + >, + spi_controller: &'static capsules_core::spi_controller::Spi< + 'static, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice< + 'static, + apollo3::iom::Iom<'static>, + >, + >, + ble_radio: &'static capsules_extra::ble_advertising_driver::BLE< + 'static, + apollo3::ble::Ble<'static>, + VirtualMuxAlarm<'static, apollo3::stimer::STimer<'static>>, + >, + scheduler: &'static RoundRobinSched<'static>, + systick: cortexm4::systick::SysTick, +} + +/// Mapping of integer syscalls to objects that implement syscalls. +impl SyscallDriverLookup for RedboardArtemisAtp { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::i2c_master_slave_driver::DRIVER_NUM => f(Some(self.i2c_master_slave)), + capsules_core::spi_controller::DRIVER_NUM => f(Some(self.spi_controller)), + capsules_extra::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), + _ => f(None), + } + } +} + +impl KernelResources> for RedboardArtemisAtp { + type SyscallDriverLookup = Self; + type SyscallFilter = (); + type ProcessFault = (); + type Scheduler = RoundRobinSched<'static>; + type SchedulerTimer = cortexm4::systick::SysTick; + type WatchDog = (); + type ContextSwitchCallback = (); + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + &() + } + fn process_fault(&self) -> &Self::ProcessFault { + &() + } + fn scheduler(&self) -> &Self::Scheduler { + self.scheduler + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + &self.systick + } + fn watchdog(&self) -> &Self::WatchDog { + &() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + &() + } +} + +// Ensure that `setup()` is never inlined +// This helps reduce the stack frame, see https://github.com/tock/tock/issues/3518 +#[inline(never)] +unsafe fn setup() -> ( + &'static kernel::Kernel, + &'static RedboardArtemisAtp, + &'static apollo3::chip::Apollo3, + &'static Apollo3DefaultPeripherals, +) { + let peripherals = static_init!(Apollo3DefaultPeripherals, Apollo3DefaultPeripherals::new()); + + // No need to statically allocate mcu/pwr/clk_ctrl because they are only used in main! + let mcu_ctrl = apollo3::mcuctrl::McuCtrl::new(); + let pwr_ctrl = apollo3::pwrctrl::PwrCtrl::new(); + let clkgen = apollo3::clkgen::ClkGen::new(); + + clkgen.set_clock_frequency(apollo3::clkgen::ClockFrequency::Freq48MHz); + + // initialize capabilities + let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability); + let memory_allocation_cap = create_capability!(capabilities::MemoryAllocationCapability); + + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); + + // Power up components + pwr_ctrl.enable_uart0(); + pwr_ctrl.enable_iom0(); + pwr_ctrl.enable_iom4(); + pwr_ctrl.enable_ios(); + + // Enable PinCfg + peripherals + .gpio_port + .enable_uart(&peripherals.gpio_port[48], &peripherals.gpio_port[49]); + // Enable SDA and SCL for I2C4 (exposed via Qwiic) + peripherals + .gpio_port + .enable_i2c(&peripherals.gpio_port[40], &peripherals.gpio_port[39]); + // Enable I2C slave device + peripherals + .gpio_port + .enable_i2c_slave(&peripherals.gpio_port[1], &peripherals.gpio_port[0]); + // Enable Main SPI + peripherals.gpio_port.enable_spi( + &peripherals.gpio_port[5], + &peripherals.gpio_port[7], + &peripherals.gpio_port[6], + ); + + // Configure kernel debug gpios as early as possible + kernel::debug::assign_gpios( + Some(&peripherals.gpio_port[19]), // Blue LED + None, + None, + ); + + // Create a shared UART channel for the console and for kernel debug. + let uart_mux = components::console::UartMuxComponent::new(&peripherals.uart0, 115200) + .finalize(components::uart_mux_component_static!()); + + // Setup the console. + let console = components::console::ConsoleComponent::new( + board_kernel, + capsules_core::console::DRIVER_NUM, + uart_mux, + ) + .finalize(components::console_component_static!()); + // Create the debugger object that handles calls to `debug!()`. + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); + + // LEDs + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( + LedHigh<'static, apollo3::gpio::GpioPin>, + LedHigh::new(&peripherals.gpio_port[19]), + )); + + // GPIOs + // These are also ADC channels, but let's expose them as GPIOs + let gpio = components::gpio::GpioComponent::new( + board_kernel, + capsules_core::gpio::DRIVER_NUM, + components::gpio_component_helper!( + apollo3::gpio::GpioPin, + 0 => &peripherals.gpio_port[2], // D2 + 1 => &peripherals.gpio_port[8], // D8 + ), + ) + .finalize(components::gpio_component_static!(apollo3::gpio::GpioPin)); + + // Create a shared virtualisation mux layer on top of a single hardware + // alarm. + let _ = peripherals.stimer.start(); + let mux_alarm = components::alarm::AlarmMuxComponent::new(&peripherals.stimer).finalize( + components::alarm_mux_component_static!(apollo3::stimer::STimer), + ); + let alarm = components::alarm::AlarmDriverComponent::new( + board_kernel, + capsules_core::alarm::DRIVER_NUM, + mux_alarm, + ) + .finalize(components::alarm_component_static!(apollo3::stimer::STimer)); + ALARM = Some(mux_alarm); + + // Create a process printer for panic. + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); + PROCESS_PRINTER = Some(process_printer); + + let i2c_master_slave_combo = static_init!( + capsules_core::i2c_master_slave_combo::I2CMasterSlaveCombo< + 'static, + apollo3::iom::Iom<'static>, + apollo3::ios::Ios<'static>, + >, + capsules_core::i2c_master_slave_combo::I2CMasterSlaveCombo::new( + &peripherals.iom4, + &peripherals.ios + ) + ); + + let i2c_master_buffer = static_init!([u8; 32], [0; 32]); + let i2c_slave_buffer1 = static_init!([u8; 32], [0; 32]); + let i2c_slave_buffer2 = static_init!([u8; 32], [0; 32]); + + let i2c_master_slave = static_init!( + I2CMasterSlaveDriver< + capsules_core::i2c_master_slave_combo::I2CMasterSlaveCombo< + 'static, + apollo3::iom::Iom<'static>, + apollo3::ios::Ios<'static>, + >, + >, + I2CMasterSlaveDriver::new( + i2c_master_slave_combo, + i2c_master_buffer, + i2c_slave_buffer1, + i2c_slave_buffer2, + board_kernel.create_grant( + capsules_core::i2c_master_slave_driver::DRIVER_NUM, + &memory_allocation_cap + ), + ) + ); + + i2c_master_slave_combo.set_master_client(i2c_master_slave); + i2c_master_slave_combo.set_slave_client(i2c_master_slave); + + peripherals.iom4.enable(); + + // Init the SPI controller + let mux_spi = components::spi::SpiMuxComponent::new(&peripherals.iom0).finalize( + components::spi_mux_component_static!(apollo3::iom::Iom<'static>), + ); + + // The IOM0 expects an auto chip select on pin D11 or D15 + // We already use manual CS control for other Apollo3 boards, so + // let's use A13 as it's broken out next to the SPI ports + let spi_controller = components::spi::SpiSyscallComponent::new( + board_kernel, + mux_spi, + &peripherals.gpio_port[13], // A13 + capsules_core::spi_controller::DRIVER_NUM, + ) + .finalize(components::spi_syscall_component_static!( + apollo3::iom::Iom<'static> + )); + + // Setup BLE + mcu_ctrl.enable_ble(); + clkgen.enable_ble(); + pwr_ctrl.enable_ble(); + peripherals.ble.setup_clocks(); + mcu_ctrl.reset_ble(); + peripherals.ble.power_up(); + peripherals.ble.ble_initialise(); + + let ble_radio = components::ble::BLEComponent::new( + board_kernel, + capsules_extra::ble_advertising_driver::DRIVER_NUM, + &peripherals.ble, + mux_alarm, + ) + .finalize(components::ble_component_static!( + apollo3::stimer::STimer, + apollo3::ble::Ble, + )); + + mcu_ctrl.print_chip_revision(); + + debug!("Initialization complete. Entering main loop"); + + // These symbols are defined in the linker script. + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + } + + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); + + let systick = cortexm4::systick::SysTick::new_with_calibration(48_000_000); + + let artemis_atp = static_init!( + RedboardArtemisAtp, + RedboardArtemisAtp { + alarm, + led, + gpio, + console, + i2c_master_slave, + spi_controller, + ble_radio, + scheduler, + systick, + } + ); + + let chip = static_init!( + apollo3::chip::Apollo3, + apollo3::chip::Apollo3::new(peripherals) + ); + CHIP = Some(chip); + + kernel::process::load_processes( + board_kernel, + chip, + core::slice::from_raw_parts( + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, + ), + core::slice::from_raw_parts_mut( + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, + ), + &mut *addr_of_mut!(PROCESSES), + &FAULT_RESPONSE, + &process_mgmt_cap, + ) + .unwrap_or_else(|err| { + debug!("Error loading processes!"); + debug!("{:?}", err); + }); + + (board_kernel, artemis_atp, chip, peripherals) +} + +/// Main function. +/// +/// This function is called from the arch crate after some very basic RISC-V +/// setup and RAM initialization. +#[no_mangle] +pub unsafe fn main() { + apollo3::init(); + + #[cfg(test)] + test_main(); + + #[cfg(not(test))] + { + let (board_kernel, esp32_c3_board, chip, _peripherals) = setup(); + + let main_loop_cap = create_capability!(capabilities::MainLoopCapability); + + board_kernel.kernel_loop( + esp32_c3_board, + chip, + None::<&kernel::ipc::IPC<{ NUM_PROCS as u8 }>>, + &main_loop_cap, + ); + } +} + +#[cfg(test)] +use kernel::platform::watchdog::WatchDog; + +#[cfg(test)] +fn test_runner(tests: &[&dyn Fn()]) { + unsafe { + let (board_kernel, esp32_c3_board, _chip, peripherals) = setup(); + + BOARD = Some(board_kernel); + PLATFORM = Some(&esp32_c3_board); + PERIPHERALS = Some(peripherals); + MAIN_CAP = Some(&create_capability!(capabilities::MainLoopCapability)); + + PLATFORM.map(|p| { + p.watchdog().setup(); + }); + + for test in tests { + test(); + } + } + + loop {} +} diff --git a/boards/apollo3/redboard_artemis_atp/src/tests/i2c_slave.rs b/boards/apollo3/redboard_artemis_atp/src/tests/i2c_slave.rs new file mode 100644 index 0000000000..b26551f397 --- /dev/null +++ b/boards/apollo3/redboard_artemis_atp/src/tests/i2c_slave.rs @@ -0,0 +1,121 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::tests::run_kernel_op; +use crate::PERIPHERALS; +use core::cell::Cell; +use kernel::debug; +use kernel::hil::i2c::I2CHwSlaveClient; +use kernel::hil::i2c::I2CSlave; +use kernel::hil::i2c::SlaveTransmissionType; +use kernel::static_init; +use kernel::utilities::cells::TakeCell; + +struct I2CSlaveCallback { + master_write_done: Cell, + send_data: TakeCell<'static, [u8]>, + master_read_done: Cell, + received_data: TakeCell<'static, [u8]>, +} + +impl<'a> I2CSlaveCallback { + fn new(send_data: &'static mut [u8], received_data: &'static mut [u8]) -> Self { + I2CSlaveCallback { + master_write_done: Cell::new(false), + send_data: TakeCell::new(send_data), + master_read_done: Cell::new(false), + received_data: TakeCell::new(received_data), + } + } + + fn reset(&self) { + self.master_write_done.set(false); + self.master_read_done.set(false); + } +} + +impl<'a> I2CHwSlaveClient for I2CSlaveCallback { + fn command_complete( + &self, + buffer: &'static mut [u8], + length: usize, + transmission_type: SlaveTransmissionType, + ) { + match transmission_type { + SlaveTransmissionType::Write => { + self.master_write_done.set(true); + debug!("Was Sent: {buffer:x?}"); + } + SlaveTransmissionType::Read => { + self.master_read_done.set(true); + } + } + } + + fn read_expected(&self) { + unimplemented!() + } + + fn write_expected(&self) { + unimplemented!() + } +} + +unsafe fn static_init_test_cb() -> &'static I2CSlaveCallback { + let received_data = static_init!([u8; 8], [0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7]); + let send_data = static_init!([u8; 8], [0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7]); + + static_init!( + I2CSlaveCallback, + I2CSlaveCallback::new(send_data, received_data) + ) +} + +#[test_case] +fn i2c_slave_receive() { + let perf = unsafe { PERIPHERALS.unwrap() }; + let i2c_slave = &perf.ios; + let cb = unsafe { static_init_test_cb() }; + let received_data = cb.received_data.take().unwrap(); + let send_data = cb.send_data.take().unwrap(); + + debug!("[I2C] Setup ios to receive... "); + run_kernel_op(100); + + i2c_slave.set_slave_client(cb); + cb.reset(); + + debug!(" [I2C] Enable... "); + i2c_slave.enable(); + run_kernel_op(100); + + debug!(" [I2C] Set address... "); + assert_eq!(i2c_slave.set_address(0x41), Ok(())); + run_kernel_op(100); + + debug!(" [I2C] read_send... "); + i2c_slave.read_send(send_data, send_data.len()).unwrap(); + run_kernel_op(100); + + debug!(" [I2C] Starting listen... "); + i2c_slave.listen(); + run_kernel_op(100); + + debug!(" [I2C] Run... "); + run_kernel_op(5000); + + debug!(" [I2C] write_receive... "); + i2c_slave + .write_receive(received_data, received_data.len()) + .unwrap(); + run_kernel_op(5000_00); + + // If there is an I2C master device you can uncomment this to + // ensure we recieve the data. + // assert_eq!(cb.master_write_done.get(), true); + + run_kernel_op(100); + debug!(" [ok]"); + run_kernel_op(100); +} diff --git a/boards/apollo3/redboard_artemis_atp/src/tests/mod.rs b/boards/apollo3/redboard_artemis_atp/src/tests/mod.rs new file mode 100644 index 0000000000..a264f29776 --- /dev/null +++ b/boards/apollo3/redboard_artemis_atp/src/tests/mod.rs @@ -0,0 +1,38 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::BOARD; +use crate::CHIP; +use crate::MAIN_CAP; +use crate::NUM_PROCS; +use crate::PLATFORM; +use kernel::debug; + +fn run_kernel_op(loops: usize) { + unsafe { + for _i in 0..loops { + BOARD.unwrap().kernel_loop_operation( + PLATFORM.unwrap(), + CHIP.unwrap(), + None::<&kernel::ipc::IPC<{ NUM_PROCS as u8 }>>, + true, + MAIN_CAP.unwrap(), + ); + } + } +} + +#[test_case] +fn trivial_assertion() { + debug!("trivial assertion... "); + run_kernel_op(10000); + + assert_eq!(1, 1); + + debug!(" [ok]"); + run_kernel_op(10000); +} + +mod i2c_slave; +mod multi_alarm; diff --git a/boards/apollo3/redboard_artemis_atp/src/tests/multi_alarm.rs b/boards/apollo3/redboard_artemis_atp/src/tests/multi_alarm.rs new file mode 100644 index 0000000000..62e17f4511 --- /dev/null +++ b/boards/apollo3/redboard_artemis_atp/src/tests/multi_alarm.rs @@ -0,0 +1,92 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Test the behavior of a single alarm. +//! To add this test, include the line +//! ``` +//! multi_alarm_test::run_alarm(alarm_mux); +//! ``` +//! to the OpenTitan boot sequence, where `alarm_mux` is a +//! `capsules_core::virtualizers::virtual_alarm::MuxAlarm`. The test sets up 3 +//! different virtualized alarms of random durations and prints +//! out when they fire. The durations are uniformly random with +//! one caveat, that 1 in 11 is of duration 0; this is to test +//! that alarms whose expiration was in the past due to the +//! latency of software work correctly. + +use crate::tests::run_kernel_op; +use crate::ALARM; +use apollo3::stimer::STimer; +use capsules_core::test::random_alarm::TestRandomAlarm; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use kernel::debug; +use kernel::hil::time::Alarm; +use kernel::static_init; + +static mut TESTS: Option< + [&'static TestRandomAlarm<'static, VirtualMuxAlarm<'static, STimer<'static>>>; 3], +> = None; + +#[test_case] +pub fn run_multi_alarm() { + debug!("start multi alarm test..."); + unsafe { + TESTS = Some(static_init_multi_alarm_test(ALARM.unwrap())); + TESTS.unwrap()[0].run(); + TESTS.unwrap()[1].run(); + TESTS.unwrap()[2].run(); + } + + run_kernel_op(10000); + + unsafe { + assert!(TESTS.unwrap()[0].counter.get() > 15); + assert!(TESTS.unwrap()[1].counter.get() > 30); + assert!(TESTS.unwrap()[2].counter.get() > 80); + } + + debug!(" [ok]"); + run_kernel_op(10000); +} + +unsafe fn static_init_multi_alarm_test( + mux: &'static MuxAlarm<'static, STimer<'static>>, +) -> [&'static TestRandomAlarm<'static, VirtualMuxAlarm<'static, STimer<'static>>>; 3] { + let virtual_alarm1 = static_init!( + VirtualMuxAlarm<'static, STimer<'static>>, + VirtualMuxAlarm::new(mux) + ); + virtual_alarm1.setup(); + + let test1 = static_init!( + TestRandomAlarm<'static, VirtualMuxAlarm<'static, STimer<'static>>>, + TestRandomAlarm::new(virtual_alarm1, 19, 'A', false) + ); + virtual_alarm1.set_alarm_client(test1); + + let virtual_alarm2 = static_init!( + VirtualMuxAlarm<'static, STimer<'static>>, + VirtualMuxAlarm::new(mux) + ); + virtual_alarm2.setup(); + + let test2 = static_init!( + TestRandomAlarm<'static, VirtualMuxAlarm<'static, STimer<'static>>>, + TestRandomAlarm::new(virtual_alarm2, 37, 'B', false) + ); + virtual_alarm2.set_alarm_client(test2); + + let virtual_alarm3 = static_init!( + VirtualMuxAlarm<'static, STimer<'static>>, + VirtualMuxAlarm::new(mux) + ); + virtual_alarm3.setup(); + + let test3 = static_init!( + TestRandomAlarm<'static, VirtualMuxAlarm<'static, STimer<'static>>>, + TestRandomAlarm::new(virtual_alarm3, 89, 'C', false) + ); + virtual_alarm3.set_alarm_client(test3); + [&*test1, &*test2, &*test3] +} diff --git a/boards/apollo3/redboard_artemis_nano/.cargo/config.toml b/boards/apollo3/redboard_artemis_nano/.cargo/config.toml new file mode 100644 index 0000000000..e06b76bd9b --- /dev/null +++ b/boards/apollo3/redboard_artemis_nano/.cargo/config.toml @@ -0,0 +1,6 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[target.'cfg(target_arch = "arm")'] +runner = "./run.sh" diff --git a/boards/apollo3/redboard_artemis_nano/Cargo.toml b/boards/apollo3/redboard_artemis_nano/Cargo.toml new file mode 100644 index 0000000000..f7acdf24d9 --- /dev/null +++ b/boards/apollo3/redboard_artemis_nano/Cargo.toml @@ -0,0 +1,23 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[package] +name = "redboard_artemis_nano" +version.workspace = true +authors.workspace = true +edition.workspace = true +build = "../../build.rs" + +[dependencies] +components = { path = "../../components" } +cortexm4 = { path = "../../../arch/cortex-m4" } +kernel = { path = "../../../kernel" } +apollo3 = { path = "../../../chips/apollo3" } + +capsules-core = { path = "../../../capsules/core" } +capsules-extra = { path = "../../../capsules/extra" } +capsules-system = { path = "../../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/apollo3/redboard_artemis_nano/Makefile b/boards/apollo3/redboard_artemis_nano/Makefile new file mode 100644 index 0000000000..fd16b7d3c9 --- /dev/null +++ b/boards/apollo3/redboard_artemis_nano/Makefile @@ -0,0 +1,52 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +# Makefile for building the tock kernel for the RedBoard Artemis Nano platform +# +TARGET=thumbv7em-none-eabi +PLATFORM=redboard_artemis_nano + +include ../../Makefile.common + +ifndef PORT + PORT="/dev/ttyUSB0" +endif + +# Default target for installing the kernel. +.PHONY: install +install: flash + +.PHONY: flash +flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin + python ../ambiq/ambiq_bin2board.py --bin $< \ + --load-address-blob 0xE000 \ + -b 115200 -port $(PORT) -r 2 -v \ + --magic-num 0xCB --version 0x0 \ + --load-address-wired 0xC000 \ + -i 6 --options 0x1 + +.PHONY: flash-debug +flash-debug: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).bin + python ../ambiq/ambiq_bin2board.py --bin $< \ + --load-address-blob 0xE000 \ + -b 115200 -port $(PORT) -r 2 -v \ + --magic-num 0xCB --version 0x0 \ + --load-address-wired 0xC000 \ + -i 6 --options 0x1 + +.PHONY: flash-app +flash-app: + python ../ambiq/ambiq_bin2board.py --bin $(APP) \ + --load-address-blob 0x42000 \ + -b 115200 -port $(PORT) -r 2 -v \ + --magic-num 0xCB --version 0x0 \ + --load-address-wired 0x40000 \ + -i 6 --options 0x1 + +.PHONY: test +test: + mkdir -p $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/deps/ + $(Q)cp layout.ld $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/deps/ + $(Q)cp ../../kernel_layout.ld $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/ + $(Q)RUSTFLAGS="$(RUSTC_FLAGS_TOCK)" OBJCOPY=${OBJCOPY} PORT=$(PORT) $(CARGO) test $(CARGO_FLAGS_TOCK_NO_BUILD_STD) $(NO_RUN) --bin $(PLATFORM) --release diff --git a/boards/apollo3/redboard_artemis_nano/README.md b/boards/apollo3/redboard_artemis_nano/README.md new file mode 100644 index 0000000000..f42f51321e --- /dev/null +++ b/boards/apollo3/redboard_artemis_nano/README.md @@ -0,0 +1,67 @@ +SparkFun RedBoard Artemis Nano +============================== + +## Board features + + - 17 GPIO - all interrupt capable + - 8 ADC channels with 14-bit precision + - 17 PWM channels + - 2 UARTs + - 4 I2C buses + - 2 SPI buses + - PDM Digital Microphone + - Qwiic Connector + +For more details [visit the SparkFun +website](https://www.sparkfun.com/products/15443). + +## Flashing the kernel + +The kernel can be programmed using the Ambiq python scrips. `cd` into `boards/apollo3/sparkfun_redboard_artemis_nano` +directory and run: + +```shell +$ make flash + +(or) + +$ make flash-debug +``` + +This will flash Tock onto the board via the /dev/ttyUSB0 port. If you would like to use a different port you can specify it from the `PORT` variable. + +```bash +$ PORT=/dev/ttyUSB2 make flash +``` + +This will flash Tock over the SparkFun Variable Loader (SVL) using the Ambiq loader. +The SVL can always be re-flashed if you want to. + + +## Debugging the board + +The RedBoard Artemis Nano exposes JTAG via the small headers in the middle of +the board. See the [SparkFun hookup guide](https://learn.sparkfun.com/tutorials/hookup-guide-for-the-sparkfun-redboard-artemis-nano/all) for a picture of this. + +SparkFun sell accessories you can use to connecting to this. It appears +something like the J-Link BASE will work, but that hasn't been tested by Tock. + +Instead, Tock has tested debugging with the [Black Magic Probe](https://black-magic.org/). +The Black Magic Probe (BMP) is an easy to use, mostly plug and play, JTAG/SWD debugger +for embedded microcontrollers. + +In order to debug with the BMP, first connect the 2x5 SWD cable to the RedBoard +and the BMP. The ribbon on the RedBoard should face towards the USB +connection and on the BMP away from the USB connection. + +Then power on both boards. + +Fire up an ARM GDB instance and attach to the BMP with: + +``` +target extended-remote /dev/ttyACM0 +monitor swdp_scan +attach 1 +``` + +You can then use GDB to debug the RedBoard diff --git a/boards/apollo3/redboard_artemis_nano/layout.ld b/boards/apollo3/redboard_artemis_nano/layout.ld new file mode 100644 index 0000000000..a2e4bbdd4d --- /dev/null +++ b/boards/apollo3/redboard_artemis_nano/layout.ld @@ -0,0 +1,18 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + +/* We have to reduce all flash lengths by 0x2000 as that is the offset + * we use when writing data to flash with ambiq_bin2board.py. + */ + +MEMORY +{ + rom (rx) : ORIGIN = 0x0000C000, LENGTH = 0x034000 - 0x2000 + prog (rx) : ORIGIN = 0x00040000, LENGTH = 0x100000 - 0x040000 - 0x2000 + ram (rwx) : ORIGIN = 0x10000000, LENGTH = 0x60000 +} + +PAGE_SIZE = 8K; + +INCLUDE ../../kernel_layout.ld diff --git a/boards/apollo3/redboard_artemis_nano/run.sh b/boards/apollo3/redboard_artemis_nano/run.sh new file mode 100755 index 0000000000..8a55087c0f --- /dev/null +++ b/boards/apollo3/redboard_artemis_nano/run.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +set -ex + +${OBJCOPY} --output-target=binary ${OBJCOPY_FLAGS} ${1} redboard-artemis-nano-tests.bin +python ../ambiq/ambiq_bin2board.py --bin redboard-artemis-nano-tests.bin --load-address-blob 0x40000 -b 115200 -port ${PORT} -r 2 -v --magic-num 0xCB --version 0x0 --load-address-wired 0xc000 -i 6 --options 0x1 + +# If we connect too quickly the UART doesn't work, so add a small delay +sleep 1 +screen ${PORT} 115200 diff --git a/boards/apollo3/redboard_artemis_nano/src/io.rs b/boards/apollo3/redboard_artemis_nano/src/io.rs new file mode 100644 index 0000000000..63ecf035f3 --- /dev/null +++ b/boards/apollo3/redboard_artemis_nano/src/io.rs @@ -0,0 +1,61 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use core::fmt::Write; +use core::panic::PanicInfo; +use core::ptr::addr_of; +use core::ptr::addr_of_mut; + +use crate::CHIP; +use crate::PROCESSES; +use crate::PROCESS_PRINTER; +use kernel::debug; +use kernel::debug::IoWrite; +use kernel::hil::led; + +/// Writer is used by kernel::debug to panic message to the serial port. +pub struct Writer {} + +/// Global static for debug writer +pub static mut WRITER: Writer = Writer {}; + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> ::core::fmt::Result { + self.write(s.as_bytes()); + Ok(()) + } +} + +impl IoWrite for Writer { + fn write(&mut self, buf: &[u8]) -> usize { + let uart = apollo3::uart::Uart::new_uart_0(); // Aliases memory for uart0. Okay bc we are panicking. + uart.transmit_sync(buf); + buf.len() + } +} + +/// Panic handler. +#[no_mangle] +#[panic_handler] +pub unsafe fn panic_fmt(info: &PanicInfo) -> ! { + // just create a new pin reference here instead of using global + let led_pin = &mut apollo3::gpio::GpioPin::new( + kernel::utilities::StaticRef::new( + apollo3::gpio::GPIO_BASE_RAW as *const apollo3::gpio::GpioRegisters, + ), + apollo3::gpio::Pin::Pin19, + ); + let led = &mut led::LedLow::new(led_pin); + let writer = &mut *addr_of_mut!(WRITER); + + debug::panic( + &mut [led], + writer, + info, + &cortexm4::support::nop, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), + ) +} diff --git a/boards/apollo3/redboard_artemis_nano/src/main.rs b/boards/apollo3/redboard_artemis_nano/src/main.rs new file mode 100644 index 0000000000..ef24ea93a9 --- /dev/null +++ b/boards/apollo3/redboard_artemis_nano/src/main.rs @@ -0,0 +1,495 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Board file for SparkFun Redboard Artemis Nano +//! +//! - + +#![no_std] +// Disable this attribute when documenting, as a workaround for +// https://github.com/rust-lang/rust/issues/62184. +#![cfg_attr(not(doc), no_main)] +#![deny(missing_docs)] +#![feature(custom_test_frameworks)] +#![test_runner(test_runner)] +#![reexport_test_harness_main = "test_main"] + +use core::ptr::addr_of; +use core::ptr::addr_of_mut; + +use apollo3::chip::Apollo3DefaultPeripherals; +use capsules_core::virtualizers::virtual_alarm::MuxAlarm; +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; +use components::bme280::Bme280Component; +use components::ccs811::Ccs811Component; +use kernel::capabilities; +use kernel::component::Component; +use kernel::hil::i2c::I2CMaster; +use kernel::hil::led::LedHigh; +use kernel::hil::time::Counter; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::scheduler::round_robin::RoundRobinSched; +use kernel::{create_capability, debug, static_init}; + +/// Support routines for debugging I/O. +pub mod io; + +#[cfg(test)] +mod tests; + +// Number of concurrent processes this platform supports. +const NUM_PROCS: usize = 4; + +// Actual memory for holding the active process structures. +static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = [None; 4]; + +// Static reference to chip for panic dumps. +static mut CHIP: Option<&'static apollo3::chip::Apollo3> = None; +// Static reference to process printer for panic dumps. +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; + +// How should the kernel respond when a process faults. +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; + +// Test access to the peripherals +#[cfg(test)] +static mut PERIPHERALS: Option<&'static Apollo3DefaultPeripherals> = None; +// Test access to board +#[cfg(test)] +static mut BOARD: Option<&'static kernel::Kernel> = None; +// Test access to platform +#[cfg(test)] +static mut PLATFORM: Option<&'static RedboardArtemisNano> = None; +// Test access to main loop capability +#[cfg(test)] +static mut MAIN_CAP: Option<&dyn kernel::capabilities::MainLoopCapability> = None; +// Test access to alarm +static mut ALARM: Option<&'static MuxAlarm<'static, apollo3::stimer::STimer<'static>>> = None; +// Test access to sensors +static mut BME280: Option< + &'static capsules_extra::bme280::Bme280< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, apollo3::iom::Iom<'static>>, + >, +> = None; +static mut CCS811: Option<&'static capsules_extra::ccs811::Ccs811<'static>> = None; + +/// Dummy buffer that causes the linker to reserve enough space for the stack. +#[no_mangle] +#[link_section = ".stack_buffer"] +pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; + +type BME280Sensor = components::bme280::Bme280ComponentType< + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, apollo3::iom::Iom<'static>>, +>; +type TemperatureDriver = components::temperature::TemperatureComponentType; +type HumidityDriver = components::humidity::HumidityComponentType; + +/// A structure representing this platform that holds references to all +/// capsules for this platform. +struct RedboardArtemisNano { + alarm: &'static capsules_core::alarm::AlarmDriver< + 'static, + VirtualMuxAlarm<'static, apollo3::stimer::STimer<'static>>, + >, + led: &'static capsules_core::led::LedDriver< + 'static, + LedHigh<'static, apollo3::gpio::GpioPin<'static>>, + 1, + >, + gpio: &'static capsules_core::gpio::GPIO<'static, apollo3::gpio::GpioPin<'static>>, + console: &'static capsules_core::console::Console<'static>, + i2c_master: + &'static capsules_core::i2c_master::I2CMasterDriver<'static, apollo3::iom::Iom<'static>>, + spi_controller: &'static capsules_core::spi_controller::Spi< + 'static, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice< + 'static, + apollo3::iom::Iom<'static>, + >, + >, + ble_radio: &'static capsules_extra::ble_advertising_driver::BLE< + 'static, + apollo3::ble::Ble<'static>, + VirtualMuxAlarm<'static, apollo3::stimer::STimer<'static>>, + >, + temperature: &'static TemperatureDriver, + humidity: &'static HumidityDriver, + air_quality: &'static capsules_extra::air_quality::AirQualitySensor<'static>, + scheduler: &'static RoundRobinSched<'static>, + systick: cortexm4::systick::SysTick, +} + +/// Mapping of integer syscalls to objects that implement syscalls. +impl SyscallDriverLookup for RedboardArtemisNano { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::i2c_master::DRIVER_NUM => f(Some(self.i2c_master)), + capsules_core::spi_controller::DRIVER_NUM => f(Some(self.spi_controller)), + capsules_extra::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temperature)), + capsules_extra::humidity::DRIVER_NUM => f(Some(self.humidity)), + capsules_extra::air_quality::DRIVER_NUM => f(Some(self.air_quality)), + _ => f(None), + } + } +} + +impl KernelResources> for RedboardArtemisNano { + type SyscallDriverLookup = Self; + type SyscallFilter = (); + type ProcessFault = (); + type Scheduler = RoundRobinSched<'static>; + type SchedulerTimer = cortexm4::systick::SysTick; + type WatchDog = (); + type ContextSwitchCallback = (); + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + &() + } + fn process_fault(&self) -> &Self::ProcessFault { + &() + } + fn scheduler(&self) -> &Self::Scheduler { + self.scheduler + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + &self.systick + } + fn watchdog(&self) -> &Self::WatchDog { + &() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + &() + } +} + +// Ensure that `setup()` is never inlined +// This helps reduce the stack frame, see https://github.com/tock/tock/issues/3518 +#[inline(never)] +unsafe fn setup() -> ( + &'static kernel::Kernel, + &'static RedboardArtemisNano, + &'static apollo3::chip::Apollo3, + &'static Apollo3DefaultPeripherals, +) { + let peripherals = static_init!(Apollo3DefaultPeripherals, Apollo3DefaultPeripherals::new()); + + // No need to statically allocate mcu/pwr/clk_ctrl because they are only used in main! + let mcu_ctrl = apollo3::mcuctrl::McuCtrl::new(); + let pwr_ctrl = apollo3::pwrctrl::PwrCtrl::new(); + let clkgen = apollo3::clkgen::ClkGen::new(); + + clkgen.set_clock_frequency(apollo3::clkgen::ClockFrequency::Freq48MHz); + + // initialize capabilities + let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability); + let memory_allocation_cap = create_capability!(capabilities::MemoryAllocationCapability); + + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); + + // Power up components + pwr_ctrl.enable_uart0(); + pwr_ctrl.enable_iom0(); + pwr_ctrl.enable_iom2(); + pwr_ctrl.enable_ios(); + + // Enable PinCfg + peripherals + .gpio_port + .enable_uart(&peripherals.gpio_port[48], &peripherals.gpio_port[49]); + // Enable SDA and SCL for I2C2 (exposed via Qwiic) + peripherals + .gpio_port + .enable_i2c(&peripherals.gpio_port[25], &peripherals.gpio_port[27]); + // Enable Main SPI + peripherals.gpio_port.enable_spi( + &peripherals.gpio_port[5], + &peripherals.gpio_port[7], + &peripherals.gpio_port[6], + ); + // Enable I2C slave device + peripherals + .gpio_port + .enable_i2c_slave(&peripherals.gpio_port[1], &peripherals.gpio_port[0]); + + // Configure kernel debug gpios as early as possible + kernel::debug::assign_gpios( + Some(&peripherals.gpio_port[19]), // Blue LED + None, + None, + ); + + // Create a shared UART channel for the console and for kernel debug. + let uart_mux = components::console::UartMuxComponent::new(&peripherals.uart0, 115200) + .finalize(components::uart_mux_component_static!()); + + // Setup the console. + let console = components::console::ConsoleComponent::new( + board_kernel, + capsules_core::console::DRIVER_NUM, + uart_mux, + ) + .finalize(components::console_component_static!()); + // Create the debugger object that handles calls to `debug!()`. + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); + + // LEDs + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( + LedHigh<'static, apollo3::gpio::GpioPin>, + LedHigh::new(&peripherals.gpio_port[19]), + )); + + // GPIOs + // These are also ADC channels, but let's expose them as GPIOs + let gpio = components::gpio::GpioComponent::new( + board_kernel, + capsules_core::gpio::DRIVER_NUM, + components::gpio_component_helper!( + apollo3::gpio::GpioPin, + 0 => &peripherals.gpio_port[13], // A0 + 1 => &peripherals.gpio_port[33], // A1 + 2 => &peripherals.gpio_port[11], // A2 + 3 => &peripherals.gpio_port[29], // A3 + 5 => &peripherals.gpio_port[31] // A5 + ), + ) + .finalize(components::gpio_component_static!(apollo3::gpio::GpioPin)); + + // Create a shared virtualisation mux layer on top of a single hardware + // alarm. + let _ = peripherals.stimer.start(); + let mux_alarm = components::alarm::AlarmMuxComponent::new(&peripherals.stimer).finalize( + components::alarm_mux_component_static!(apollo3::stimer::STimer), + ); + let alarm = components::alarm::AlarmDriverComponent::new( + board_kernel, + capsules_core::alarm::DRIVER_NUM, + mux_alarm, + ) + .finalize(components::alarm_component_static!(apollo3::stimer::STimer)); + ALARM = Some(mux_alarm); + + // Create a process printer for panic. + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); + PROCESS_PRINTER = Some(process_printer); + + // Init the I2C device attached via Qwiic + let i2c_master_buffer = static_init!( + [u8; capsules_core::i2c_master::BUFFER_LENGTH], + [0; capsules_core::i2c_master::BUFFER_LENGTH] + ); + let i2c_master = static_init!( + capsules_core::i2c_master::I2CMasterDriver<'static, apollo3::iom::Iom<'static>>, + capsules_core::i2c_master::I2CMasterDriver::new( + &peripherals.iom2, + i2c_master_buffer, + board_kernel.create_grant( + capsules_core::i2c_master::DRIVER_NUM, + &memory_allocation_cap + ) + ) + ); + + peripherals.iom2.set_master_client(i2c_master); + peripherals.iom2.enable(); + + let mux_i2c = components::i2c::I2CMuxComponent::new(&peripherals.iom2, None) + .finalize(components::i2c_mux_component_static!(apollo3::iom::Iom)); + + let bme280 = Bme280Component::new(mux_i2c, 0x77) + .finalize(components::bme280_component_static!(apollo3::iom::Iom)); + let temperature = components::temperature::TemperatureComponent::new( + board_kernel, + capsules_extra::temperature::DRIVER_NUM, + bme280, + ) + .finalize(components::temperature_component_static!(BME280Sensor)); + let humidity = components::humidity::HumidityComponent::new( + board_kernel, + capsules_extra::humidity::DRIVER_NUM, + bme280, + ) + .finalize(components::humidity_component_static!(BME280Sensor)); + BME280 = Some(bme280); + + let ccs811 = Ccs811Component::new(mux_i2c, 0x5B) + .finalize(components::ccs811_component_static!(apollo3::iom::Iom)); + let air_quality = components::air_quality::AirQualityComponent::new( + board_kernel, + capsules_extra::temperature::DRIVER_NUM, + ccs811, + ) + .finalize(components::air_quality_component_static!()); + CCS811 = Some(ccs811); + + // Init the SPI controller + let mux_spi = components::spi::SpiMuxComponent::new(&peripherals.iom0).finalize( + components::spi_mux_component_static!(apollo3::iom::Iom<'static>), + ); + + // The IOM0 expects an auto chip select on pin D11 or D15, neither of + // which are broken out on the board. So the CS control must be manual + let spi_controller = components::spi::SpiSyscallComponent::new( + board_kernel, + mux_spi, + &peripherals.gpio_port[35], // A14 + capsules_core::spi_controller::DRIVER_NUM, + ) + .finalize(components::spi_syscall_component_static!( + apollo3::iom::Iom<'static> + )); + + // Setup BLE + mcu_ctrl.enable_ble(); + clkgen.enable_ble(); + pwr_ctrl.enable_ble(); + peripherals.ble.setup_clocks(); + mcu_ctrl.reset_ble(); + peripherals.ble.power_up(); + peripherals.ble.ble_initialise(); + + let ble_radio = components::ble::BLEComponent::new( + board_kernel, + capsules_extra::ble_advertising_driver::DRIVER_NUM, + &peripherals.ble, + mux_alarm, + ) + .finalize(components::ble_component_static!( + apollo3::stimer::STimer, + apollo3::ble::Ble, + )); + + mcu_ctrl.print_chip_revision(); + + debug!("Initialization complete. Entering main loop"); + + // These symbols are defined in the linker script. + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + } + + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); + + let systick = cortexm4::systick::SysTick::new_with_calibration(48_000_000); + + let artemis_nano = static_init!( + RedboardArtemisNano, + RedboardArtemisNano { + alarm, + led, + gpio, + console, + i2c_master, + spi_controller, + ble_radio, + temperature, + humidity, + air_quality, + scheduler, + systick, + } + ); + + let chip = static_init!( + apollo3::chip::Apollo3, + apollo3::chip::Apollo3::new(peripherals) + ); + CHIP = Some(chip); + + kernel::process::load_processes( + board_kernel, + chip, + core::slice::from_raw_parts( + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, + ), + core::slice::from_raw_parts_mut( + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, + ), + &mut *addr_of_mut!(PROCESSES), + &FAULT_RESPONSE, + &process_mgmt_cap, + ) + .unwrap_or_else(|err| { + debug!("Error loading processes!"); + debug!("{:?}", err); + }); + + (board_kernel, artemis_nano, chip, peripherals) +} + +/// Main function. +/// +/// This function is called from the arch crate after some very basic RISC-V +/// setup and RAM initialization. +#[no_mangle] +pub unsafe fn main() { + apollo3::init(); + + #[cfg(test)] + test_main(); + + #[cfg(not(test))] + { + let (board_kernel, esp32_c3_board, chip, _peripherals) = setup(); + + let main_loop_cap = create_capability!(capabilities::MainLoopCapability); + + board_kernel.kernel_loop( + esp32_c3_board, + chip, + None::<&kernel::ipc::IPC<{ NUM_PROCS as u8 }>>, + &main_loop_cap, + ); + } +} + +#[cfg(test)] +use kernel::platform::watchdog::WatchDog; + +#[cfg(test)] +fn test_runner(tests: &[&dyn Fn()]) { + unsafe { + let (board_kernel, esp32_c3_board, _chip, peripherals) = setup(); + + BOARD = Some(board_kernel); + PLATFORM = Some(&esp32_c3_board); + PERIPHERALS = Some(peripherals); + MAIN_CAP = Some(&create_capability!(capabilities::MainLoopCapability)); + + PLATFORM.map(|p| { + p.watchdog().setup(); + }); + + for test in tests { + test(); + } + } + + loop {} +} diff --git a/boards/apollo3/redboard_artemis_nano/src/tests/environmental_sensors.rs b/boards/apollo3/redboard_artemis_nano/src/tests/environmental_sensors.rs new file mode 100644 index 0000000000..f85ecc7d34 --- /dev/null +++ b/boards/apollo3/redboard_artemis_nano/src/tests/environmental_sensors.rs @@ -0,0 +1,198 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Test that we can get temperature, humidity and pressure from the BME280 +//! chip. +//! This test requires the "SparkFun Environmental Combo Breakout" board +//! connected via the Qwiic connector +//! (https://www.sparkfun.com/products/14348). + +use crate::tests::run_kernel_op; +use crate::{BME280, CCS811}; +use core::cell::Cell; +use kernel::debug; +use kernel::hil::sensors::{ + AirQualityClient, AirQualityDriver, HumidityClient, HumidityDriver, TemperatureClient, + TemperatureDriver, +}; +use kernel::ErrorCode; + +struct SensorTestCallback { + temperature_done: Cell, + humidity_done: Cell, + co2_done: Cell, + tvoc_done: Cell, + calibration_temp: Cell>, + calibration_humidity: Cell>, +} + +unsafe impl Sync for SensorTestCallback {} + +impl<'a> SensorTestCallback { + const fn new() -> Self { + SensorTestCallback { + temperature_done: Cell::new(false), + humidity_done: Cell::new(false), + co2_done: Cell::new(false), + tvoc_done: Cell::new(false), + calibration_temp: Cell::new(None), + calibration_humidity: Cell::new(None), + } + } + + fn reset(&self) { + self.temperature_done.set(false); + self.humidity_done.set(false); + self.co2_done.set(false); + self.tvoc_done.set(false); + } +} + +impl<'a> TemperatureClient for SensorTestCallback { + fn callback(&self, result: Result) { + self.temperature_done.set(true); + self.calibration_temp.set(Some(result.unwrap())); + + debug!("Temperature: {}", result.unwrap()); + } +} + +impl<'a> HumidityClient for SensorTestCallback { + fn callback(&self, value: usize) { + self.humidity_done.set(true); + self.calibration_humidity.set(Some(value as u32)); + + debug!("Humidity: {}", value); + } +} + +impl<'a> AirQualityClient for SensorTestCallback { + fn environment_specified(&self, result: Result<(), ErrorCode>) { + result.unwrap(); + } + + fn co2_data_available(&self, value: Result) { + self.co2_done.set(true); + + debug!("CO2: {} ppm", value.unwrap()); + } + + fn tvoc_data_available(&self, value: Result) { + self.tvoc_done.set(true); + + debug!("TVOC: {} ppb", value.unwrap()); + } +} + +static CALLBACK: SensorTestCallback = SensorTestCallback::new(); + +#[test_case] +fn run_bme280_temperature() { + debug!("check run BME280 Temperature... "); + run_kernel_op(100); + + let bme280 = unsafe { BME280.unwrap() }; + + // Make sure the device is ready for us. + // The setup can take a little bit of time + run_kernel_op(800000); + + TemperatureDriver::set_client(bme280, &CALLBACK); + CALLBACK.reset(); + + bme280.read_temperature().unwrap(); + + run_kernel_op(50000); + assert_eq!(CALLBACK.temperature_done.get(), true); + + debug!(" [ok]"); + run_kernel_op(100); +} + +#[test_case] +fn run_bme280_humidity() { + debug!("check run BME280 Humidity... "); + run_kernel_op(100); + + let bme280 = unsafe { BME280.unwrap() }; + + // Make sure the debice is ready for us. + // The setup can take a little bit of time + run_kernel_op(800000); + + HumidityDriver::set_client(bme280, &CALLBACK); + CALLBACK.reset(); + + bme280.read_humidity().unwrap(); + + run_kernel_op(50000); + assert_eq!(CALLBACK.humidity_done.get(), true); + + debug!(" [ok]"); + run_kernel_op(100); +} + +#[test_case] +fn run_ccs811_co2() { + debug!("check run CCS811 CO2... "); + run_kernel_op(100); + + let ccs811 = unsafe { CCS811.unwrap() }; + + // Make sure the device is ready for us. + // The setup can take a little bit of time + run_kernel_op(800000); + + AirQualityDriver::set_client(ccs811, &CALLBACK); + CALLBACK.reset(); + + ccs811 + .specify_environment( + CALLBACK.calibration_temp.get(), + CALLBACK.calibration_humidity.get(), + ) + .unwrap(); + + run_kernel_op(7000); + + ccs811.read_co2().unwrap(); + + run_kernel_op(7000); + assert_eq!(CALLBACK.co2_done.get(), true); + + debug!(" [ok]"); + run_kernel_op(100); +} + +#[test_case] +fn run_ccs811_tvoc() { + debug!("check run CCS811 TVOC... "); + run_kernel_op(100); + + let ccs811 = unsafe { CCS811.unwrap() }; + + // Make sure the device is ready for us. + // The setup can take a little bit of time + run_kernel_op(800000); + + AirQualityDriver::set_client(ccs811, &CALLBACK); + CALLBACK.reset(); + + ccs811 + .specify_environment( + CALLBACK.calibration_temp.get(), + CALLBACK.calibration_humidity.get(), + ) + .unwrap(); + + run_kernel_op(7000); + + ccs811.read_tvoc().unwrap(); + + run_kernel_op(7000); + assert_eq!(CALLBACK.tvoc_done.get(), true); + + debug!(" [ok]"); + run_kernel_op(100); +} diff --git a/boards/apollo3/redboard_artemis_nano/src/tests/i2c_slave.rs b/boards/apollo3/redboard_artemis_nano/src/tests/i2c_slave.rs new file mode 100644 index 0000000000..b26551f397 --- /dev/null +++ b/boards/apollo3/redboard_artemis_nano/src/tests/i2c_slave.rs @@ -0,0 +1,121 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::tests::run_kernel_op; +use crate::PERIPHERALS; +use core::cell::Cell; +use kernel::debug; +use kernel::hil::i2c::I2CHwSlaveClient; +use kernel::hil::i2c::I2CSlave; +use kernel::hil::i2c::SlaveTransmissionType; +use kernel::static_init; +use kernel::utilities::cells::TakeCell; + +struct I2CSlaveCallback { + master_write_done: Cell, + send_data: TakeCell<'static, [u8]>, + master_read_done: Cell, + received_data: TakeCell<'static, [u8]>, +} + +impl<'a> I2CSlaveCallback { + fn new(send_data: &'static mut [u8], received_data: &'static mut [u8]) -> Self { + I2CSlaveCallback { + master_write_done: Cell::new(false), + send_data: TakeCell::new(send_data), + master_read_done: Cell::new(false), + received_data: TakeCell::new(received_data), + } + } + + fn reset(&self) { + self.master_write_done.set(false); + self.master_read_done.set(false); + } +} + +impl<'a> I2CHwSlaveClient for I2CSlaveCallback { + fn command_complete( + &self, + buffer: &'static mut [u8], + length: usize, + transmission_type: SlaveTransmissionType, + ) { + match transmission_type { + SlaveTransmissionType::Write => { + self.master_write_done.set(true); + debug!("Was Sent: {buffer:x?}"); + } + SlaveTransmissionType::Read => { + self.master_read_done.set(true); + } + } + } + + fn read_expected(&self) { + unimplemented!() + } + + fn write_expected(&self) { + unimplemented!() + } +} + +unsafe fn static_init_test_cb() -> &'static I2CSlaveCallback { + let received_data = static_init!([u8; 8], [0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7]); + let send_data = static_init!([u8; 8], [0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7]); + + static_init!( + I2CSlaveCallback, + I2CSlaveCallback::new(send_data, received_data) + ) +} + +#[test_case] +fn i2c_slave_receive() { + let perf = unsafe { PERIPHERALS.unwrap() }; + let i2c_slave = &perf.ios; + let cb = unsafe { static_init_test_cb() }; + let received_data = cb.received_data.take().unwrap(); + let send_data = cb.send_data.take().unwrap(); + + debug!("[I2C] Setup ios to receive... "); + run_kernel_op(100); + + i2c_slave.set_slave_client(cb); + cb.reset(); + + debug!(" [I2C] Enable... "); + i2c_slave.enable(); + run_kernel_op(100); + + debug!(" [I2C] Set address... "); + assert_eq!(i2c_slave.set_address(0x41), Ok(())); + run_kernel_op(100); + + debug!(" [I2C] read_send... "); + i2c_slave.read_send(send_data, send_data.len()).unwrap(); + run_kernel_op(100); + + debug!(" [I2C] Starting listen... "); + i2c_slave.listen(); + run_kernel_op(100); + + debug!(" [I2C] Run... "); + run_kernel_op(5000); + + debug!(" [I2C] write_receive... "); + i2c_slave + .write_receive(received_data, received_data.len()) + .unwrap(); + run_kernel_op(5000_00); + + // If there is an I2C master device you can uncomment this to + // ensure we recieve the data. + // assert_eq!(cb.master_write_done.get(), true); + + run_kernel_op(100); + debug!(" [ok]"); + run_kernel_op(100); +} diff --git a/boards/apollo3/redboard_artemis_nano/src/tests/mod.rs b/boards/apollo3/redboard_artemis_nano/src/tests/mod.rs new file mode 100644 index 0000000000..0bd1e71a4c --- /dev/null +++ b/boards/apollo3/redboard_artemis_nano/src/tests/mod.rs @@ -0,0 +1,40 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::BOARD; +use crate::CHIP; +use crate::MAIN_CAP; +use crate::NUM_PROCS; +use crate::PLATFORM; +use kernel::debug; + +fn run_kernel_op(loops: usize) { + unsafe { + for _i in 0..loops { + BOARD.unwrap().kernel_loop_operation( + PLATFORM.unwrap(), + CHIP.unwrap(), + None::<&kernel::ipc::IPC<{ NUM_PROCS as u8 }>>, + true, + MAIN_CAP.unwrap(), + ); + } + } +} + +#[test_case] +fn trivial_assertion() { + debug!("trivial assertion... "); + run_kernel_op(10000); + + assert_eq!(1, 1); + + debug!(" [ok]"); + run_kernel_op(10000); +} + +mod environmental_sensors; +mod i2c_slave; +mod multi_alarm; +mod spi_controller; diff --git a/boards/apollo3/redboard_artemis_nano/src/tests/multi_alarm.rs b/boards/apollo3/redboard_artemis_nano/src/tests/multi_alarm.rs new file mode 100644 index 0000000000..62e17f4511 --- /dev/null +++ b/boards/apollo3/redboard_artemis_nano/src/tests/multi_alarm.rs @@ -0,0 +1,92 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Test the behavior of a single alarm. +//! To add this test, include the line +//! ``` +//! multi_alarm_test::run_alarm(alarm_mux); +//! ``` +//! to the OpenTitan boot sequence, where `alarm_mux` is a +//! `capsules_core::virtualizers::virtual_alarm::MuxAlarm`. The test sets up 3 +//! different virtualized alarms of random durations and prints +//! out when they fire. The durations are uniformly random with +//! one caveat, that 1 in 11 is of duration 0; this is to test +//! that alarms whose expiration was in the past due to the +//! latency of software work correctly. + +use crate::tests::run_kernel_op; +use crate::ALARM; +use apollo3::stimer::STimer; +use capsules_core::test::random_alarm::TestRandomAlarm; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use kernel::debug; +use kernel::hil::time::Alarm; +use kernel::static_init; + +static mut TESTS: Option< + [&'static TestRandomAlarm<'static, VirtualMuxAlarm<'static, STimer<'static>>>; 3], +> = None; + +#[test_case] +pub fn run_multi_alarm() { + debug!("start multi alarm test..."); + unsafe { + TESTS = Some(static_init_multi_alarm_test(ALARM.unwrap())); + TESTS.unwrap()[0].run(); + TESTS.unwrap()[1].run(); + TESTS.unwrap()[2].run(); + } + + run_kernel_op(10000); + + unsafe { + assert!(TESTS.unwrap()[0].counter.get() > 15); + assert!(TESTS.unwrap()[1].counter.get() > 30); + assert!(TESTS.unwrap()[2].counter.get() > 80); + } + + debug!(" [ok]"); + run_kernel_op(10000); +} + +unsafe fn static_init_multi_alarm_test( + mux: &'static MuxAlarm<'static, STimer<'static>>, +) -> [&'static TestRandomAlarm<'static, VirtualMuxAlarm<'static, STimer<'static>>>; 3] { + let virtual_alarm1 = static_init!( + VirtualMuxAlarm<'static, STimer<'static>>, + VirtualMuxAlarm::new(mux) + ); + virtual_alarm1.setup(); + + let test1 = static_init!( + TestRandomAlarm<'static, VirtualMuxAlarm<'static, STimer<'static>>>, + TestRandomAlarm::new(virtual_alarm1, 19, 'A', false) + ); + virtual_alarm1.set_alarm_client(test1); + + let virtual_alarm2 = static_init!( + VirtualMuxAlarm<'static, STimer<'static>>, + VirtualMuxAlarm::new(mux) + ); + virtual_alarm2.setup(); + + let test2 = static_init!( + TestRandomAlarm<'static, VirtualMuxAlarm<'static, STimer<'static>>>, + TestRandomAlarm::new(virtual_alarm2, 37, 'B', false) + ); + virtual_alarm2.set_alarm_client(test2); + + let virtual_alarm3 = static_init!( + VirtualMuxAlarm<'static, STimer<'static>>, + VirtualMuxAlarm::new(mux) + ); + virtual_alarm3.setup(); + + let test3 = static_init!( + TestRandomAlarm<'static, VirtualMuxAlarm<'static, STimer<'static>>>, + TestRandomAlarm::new(virtual_alarm3, 89, 'C', false) + ); + virtual_alarm3.set_alarm_client(test3); + [&*test1, &*test2, &*test3] +} diff --git a/boards/apollo3/redboard_artemis_nano/src/tests/spi_controller.rs b/boards/apollo3/redboard_artemis_nano/src/tests/spi_controller.rs new file mode 100644 index 0000000000..c6cd758d65 --- /dev/null +++ b/boards/apollo3/redboard_artemis_nano/src/tests/spi_controller.rs @@ -0,0 +1,231 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::tests::run_kernel_op; +use crate::PERIPHERALS; +use core::cell::Cell; +#[allow(unused_imports)] +use kernel::hil::spi::{ClockPhase, ClockPolarity}; +use kernel::hil::spi::{SpiMaster, SpiMasterClient}; +use kernel::static_init; +use kernel::utilities::cells::TakeCell; +use kernel::{debug, ErrorCode}; + +struct SpiHostCallback { + transfer_done: Cell, + tx_len: Cell, + tx_data: TakeCell<'static, [u8]>, + rx_data: TakeCell<'static, [u8]>, +} + +impl<'a> SpiHostCallback { + fn new(tx_data: &'static mut [u8], rx_data: &'static mut [u8]) -> Self { + SpiHostCallback { + transfer_done: Cell::new(false), + tx_len: Cell::new(0), + tx_data: TakeCell::new(tx_data), + rx_data: TakeCell::new(rx_data), + } + } + + fn reset(&self) { + self.transfer_done.set(false); + self.tx_len.set(0); + } +} + +impl<'a> SpiMasterClient for SpiHostCallback { + fn read_write_done( + &self, + tx_data: &'static mut [u8], + rx_done: Option<&'static mut [u8]>, + tx_len: usize, + rc: Result<(), ErrorCode>, + ) { + // Transfer Complete + assert_eq!(rc, Ok(())); + assert_eq!(tx_len, self.tx_len.get()); + + // Capture Buffers + match rx_done { + Some(rx_buf) => { + self.rx_data.replace(rx_buf); + } + None => { + panic!("RX Buffer Lost"); + } + } + + self.tx_data.replace(tx_data); + + if self.tx_len.get() == tx_len { + self.transfer_done.set(true); + } + } +} + +unsafe fn static_init_test_cb() -> &'static SpiHostCallback { + let rx_data = static_init!([u8; 32], [0; 32]); + + let tx_data = static_init!( + [u8; 32], + [ + 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, + 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, + 0x58, 0x57, 0x40, + ] + ); + + static_init!(SpiHostCallback, SpiHostCallback::new(tx_data, rx_data)) +} + +unsafe fn static_init_test_partial_cb() -> &'static SpiHostCallback { + let rx_data = static_init!([u8; 513], [0; 513]); + // Buffer Size to exceed TXFIFO (Force partial transfers) + let tx_data = static_init!( + [u8; 513], + [ + 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, + 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, + 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, + 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, + 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, + 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, + 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, + 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, + 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, + 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, + 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, + 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, + 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, + 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, + 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, + 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, + 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, + 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, + 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, + 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, + 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, + 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, + 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, + 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, + 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, + 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, + 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, + 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, + 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, + 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, + 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, + 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, + 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, + 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, + 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, + 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0x40, + ] + ); + + static_init!(SpiHostCallback, SpiHostCallback::new(tx_data, rx_data)) +} + +/// Tests transferring a data set that exceeds the TXFIFO (256) +/// The driver must do 3 transfers (256, 256, 1) to transfer the full 513 byte +/// dataset. This tests partial transfers and continued offset write outs. +#[test_case] +fn spi_host_transfer_partial() { + let perf = unsafe { PERIPHERALS.unwrap() }; + let spi_host = &perf.iom0; + + let cb = unsafe { static_init_test_partial_cb() }; + + debug!("[SPI] Setup iom0 partial_transfer... "); + run_kernel_op(100); + + spi_host.set_client(cb); + cb.reset(); + + let tx = cb.tx_data.take().unwrap(); + let rx = cb.rx_data.take().unwrap(); + cb.tx_len.set(tx.len()); + + // Set iom0 Configs + spi_host + .specify_chip_select( + &perf.gpio_port[35], // A14 + ) + .ok(); + spi_host.set_rate(100000).ok(); + spi_host.set_polarity(ClockPolarity::IdleLow).ok(); + spi_host.set_phase(ClockPhase::SampleLeading).ok(); + + assert_eq!( + spi_host.read_write_bytes(tx, Some(rx), cb.tx_len.get()), + Ok(()) + ); + run_kernel_op(5000); + + assert_eq!(cb.transfer_done.get(), true); + + run_kernel_op(100); + debug!(" [ok]"); + run_kernel_op(100); +} + +/// Tests two single transfers that do not exceed the TXFIFO +/// The second test, is to ensure that the driver is left in a clean state +/// after a transfer (reset internal offsets and counts etc...) +#[test_case] +fn spi_host_transfer_single() { + let perf = unsafe { PERIPHERALS.unwrap() }; + let spi_host = &perf.iom0; + + let cb = unsafe { static_init_test_cb() }; + + debug!("[SPI] Setup iom0 transfers... "); + run_kernel_op(100); + spi_host.set_client(cb); + cb.reset(); + + let tx = cb.tx_data.take().unwrap(); + let rx = cb.rx_data.take().unwrap(); + cb.tx_len.set(tx.len()); + + // Set iom0 Configs + // spi_host.specify_chip_select(0).ok(); + spi_host.set_rate(100000).ok(); + spi_host.set_polarity(ClockPolarity::IdleLow).ok(); + spi_host.set_phase(ClockPhase::SampleLeading).ok(); + + assert_eq!( + spi_host.read_write_bytes(tx, Some(rx), cb.tx_len.get()), + Ok(()) + ); + run_kernel_op(5000); + + assert_eq!(cb.transfer_done.get(), true); + + run_kernel_op(100); + debug!(" [ok]"); + run_kernel_op(100); + + debug!("[SPI] Setup iom0 transfer...x2 "); + run_kernel_op(100); + + cb.reset(); + + let tx2 = cb.tx_data.take().unwrap(); + let rx2 = cb.rx_data.take().unwrap(); + cb.tx_len.set(tx2.len()); + + assert_eq!( + spi_host.read_write_bytes(tx2, Some(rx2), cb.tx_len.get()), + Ok(()) + ); + run_kernel_op(5000); + + assert_eq!(cb.transfer_done.get(), true); + + run_kernel_op(100); + debug!(" [ok]"); + run_kernel_op(100); +} diff --git a/boards/arty_e21/Cargo.toml b/boards/arty_e21/Cargo.toml index 0704a78a94..8a6f7eeb5f 100644 --- a/boards/arty_e21/Cargo.toml +++ b/boards/arty_e21/Cargo.toml @@ -1,14 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "arty_e21" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] components = { path = "../components" } rv32i = { path = "../../arch/rv32i" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } arty_e21_chip = { path = "../../chips/arty_e21_chip" } sifive = { path = "../../chips/sifive" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/arty_e21/Makefile b/boards/arty_e21/Makefile index 9d2d4ed3c7..0c10493cd8 100644 --- a/boards/arty_e21/Makefile +++ b/boards/arty_e21/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the HiFive1 platform TARGET=riscv32imac-unknown-none-elf diff --git a/boards/arty_e21/build.rs b/boards/arty_e21/build.rs deleted file mode 100644 index 3051054fc6..0000000000 --- a/boards/arty_e21/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/arty_e21/layout.ld b/boards/arty_e21/layout.ld index 4166f3c0b7..2a1f31fa67 100644 --- a/boards/arty_e21/layout.ld +++ b/boards/arty_e21/layout.ld @@ -1,3 +1,7 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + MEMORY { rom (rx) : ORIGIN = 0x40400000, LENGTH = 0x00030000 @@ -5,6 +9,4 @@ MEMORY ram (rwx) : ORIGIN = 0x80000000, LENGTH = 64K } -MPU_MIN_ALIGN = 8K; - INCLUDE ../kernel_layout.ld diff --git a/boards/arty_e21/openocd/arty-openocd-digilent.cfg b/boards/arty_e21/openocd/arty-openocd-digilent.cfg index a616ce4f1d..2035f41146 100644 --- a/boards/arty_e21/openocd/arty-openocd-digilent.cfg +++ b/boards/arty_e21/openocd/arty-openocd-digilent.cfg @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + # # Digilent Arty with Xilinx Artix-7 FPGA # diff --git a/boards/arty_e21/src/io.rs b/boards/arty_e21/src/io.rs index f21a925aaa..64a258b244 100644 --- a/boards/arty_e21/src/io.rs +++ b/boards/arty_e21/src/io.rs @@ -1,13 +1,16 @@ -use arty_e21_chip; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; +use core::ptr::addr_of; +use core::ptr::addr_of_mut; use core::str; use kernel::debug; use kernel::debug::IoWrite; use kernel::hil::gpio; use kernel::hil::led; -use rv32i; -use sifive; use crate::CHIP; use crate::PROCESSES; @@ -25,8 +28,9 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { sifive::uart::Uart::new(arty_e21_chip::uart::UART0_BASE, 32_000_000).transmit_sync(buf); + buf.len() } } @@ -34,7 +38,7 @@ impl IoWrite for Writer { #[cfg(not(test))] #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { // turn off the non panic leds, just in case let led_green = &sifive::gpio::GpioPin::new( arty_e21_chip::gpio::GPIO0_BASE, @@ -62,14 +66,14 @@ pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { ); let led_red = &mut led::LedHigh::new(led_red_pin); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led_red], writer, pi, &rv32i::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/arty_e21/src/main.rs b/boards/arty_e21/src/main.rs index 7db3f46022..23ad988784 100644 --- a/boards/arty_e21/src/main.rs +++ b/boards/arty_e21/src/main.rs @@ -1,16 +1,21 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Board file for the SiFive E21 Bitstream running on the Arty FPGA #![no_std] // Disable this attribute when documenting, as a workaround for // https://github.com/rust-lang/rust/issues/62184. #![cfg_attr(not(doc), no_main)] -#![feature(const_fn_trait_bound)] + +use core::ptr::{addr_of, addr_of_mut}; use arty_e21_chip::chip::ArtyExxDefaultPeripherals; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; + use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::scheduler::priority::PrioritySched; @@ -27,7 +32,8 @@ pub mod io; const NUM_PROCS: usize = 4; // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; // Actual memory for holding the active process structures. static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = @@ -35,7 +41,8 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] // Reference to the chip for panic dumps. static mut CHIP: Option<&'static arty_e21_chip::chip::ArtyExx> = None; -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; /// Dummy buffer that causes the linker to reserve enough space for the stack. #[no_mangle] @@ -45,18 +52,18 @@ pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; /// A structure representing this platform that holds references to all /// capsules for this platform. struct ArtyE21 { - console: &'static capsules::console::Console<'static>, - gpio: &'static capsules::gpio::GPIO<'static, arty_e21_chip::gpio::GpioPin<'static>>, - alarm: &'static capsules::alarm::AlarmDriver< + console: &'static capsules_core::console::Console<'static>, + gpio: &'static capsules_core::gpio::GPIO<'static, arty_e21_chip::gpio::GpioPin<'static>>, + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, - VirtualMuxAlarm<'static, sifive::clint::Clint<'static>>, + VirtualMuxAlarm<'static, arty_e21_chip::chip::ArtyExxClint<'static>>, >, - led: &'static capsules::led::LedDriver< + led: &'static capsules_core::led::LedDriver< 'static, hil::led::LedHigh<'static, arty_e21_chip::gpio::GpioPin<'static>>, 3, >, - button: &'static capsules::button::Button<'static, arty_e21_chip::gpio::GpioPin<'static>>, + button: &'static capsules_core::button::Button<'static, arty_e21_chip::gpio::GpioPin<'static>>, // ipc: kernel::ipc::IPC, scheduler: &'static PrioritySched, } @@ -68,12 +75,12 @@ impl SyscallDriverLookup for ArtyE21 { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), // kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), _ => f(None), @@ -93,7 +100,7 @@ impl KernelResources &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -115,13 +122,17 @@ impl KernelResources ( + &'static kernel::Kernel, + ArtyE21, + &'static arty_e21_chip::chip::ArtyExx<'static, ArtyExxDefaultPeripherals<'static>>, +) { let peripherals = static_init!(ArtyExxDefaultPeripherals, ArtyExxDefaultPeripherals::new()); + peripherals.init(); let chip = static_init!( arty_e21_chip::chip::ArtyExx, @@ -131,17 +142,8 @@ pub unsafe fn main() { chip.initialize(); let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability); - let main_loop_cap = create_capability!(capabilities::MainLoopCapability); - - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 2], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); // Configure kernel debug gpios as early as possible kernel::debug::assign_gpios( @@ -150,29 +152,25 @@ pub unsafe fn main() { Some(&peripherals.gpio_port[8]), ); - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); // Create a shared UART channel for the console and for kernel debug. - let uart_mux = components::console::UartMuxComponent::new( - &peripherals.uart0, - 115200, - dynamic_deferred_caller, - ) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(&peripherals.uart0, 115200) + .finalize(components::uart_mux_component_static!()); let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create a shared virtualization mux layer on top of a single hardware // alarm. let mux_alarm = static_init!( - MuxAlarm<'static, sifive::clint::Clint>, + MuxAlarm<'static, arty_e21_chip::chip::ArtyExxClint>, MuxAlarm::new(&peripherals.machinetimer) ); hil::time::Alarm::set_alarm_client(&peripherals.machinetimer, mux_alarm); @@ -180,25 +178,27 @@ pub unsafe fn main() { // Alarm let alarm = components::alarm::AlarmDriverComponent::new( board_kernel, - capsules::alarm::DRIVER_NUM, + capsules_core::alarm::DRIVER_NUM, mux_alarm, ) - .finalize(components::alarm_component_helper!(sifive::clint::Clint)); + .finalize(components::alarm_component_static!( + arty_e21_chip::chip::ArtyExxClint + )); // TEST for timer // // let virtual_alarm_test = static_init!( - // VirtualMuxAlarm<'static, sifive::clint::Clint>, + // VirtualMuxAlarm<'static, arty_e21_chip::chip::ArtyExxClint>, // VirtualMuxAlarm::new(mux_alarm) // ); // let timertest = static_init!( - // timer_test::TimerTest<'static, VirtualMuxAlarm<'static, sifive::clint::Clint>>, + // timer_test::TimerTest<'static, VirtualMuxAlarm<'static, arty_e21_chip::chip::ArtyExxClint>>, // timer_test::TimerTest::new(virtual_alarm_test) // ); // virtual_alarm_test.set_client(timertest); // LEDs - let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( hil::led::LedHigh<'static, arty_e21_chip::gpio::GpioPin>, hil::led::LedHigh::new(&peripherals.gpio_port[2]), // Red hil::led::LedHigh::new(&peripherals.gpio_port[1]), // Green @@ -208,7 +208,7 @@ pub unsafe fn main() { // BUTTONs let button = components::button::ButtonComponent::new( board_kernel, - capsules::button::DRIVER_NUM, + capsules_core::button::DRIVER_NUM, components::button_component_helper!( arty_e21_chip::gpio::GpioPin, ( @@ -218,14 +218,14 @@ pub unsafe fn main() { ) ), ) - .finalize(components::button_component_buf!( + .finalize(components::button_component_static!( arty_e21_chip::gpio::GpioPin )); // set GPIO driver controlling remaining GPIO pins let gpio = components::gpio::GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( arty_e21_chip::gpio::GpioPin, 0 => &peripherals.gpio_port[7], @@ -233,26 +233,28 @@ pub unsafe fn main() { 2 => &peripherals.gpio_port[6] ), ) - .finalize(components::gpio_component_buf!( + .finalize(components::gpio_component_static!( arty_e21_chip::gpio::GpioPin )); chip.enable_all_interrupts(); - let scheduler = components::sched::priority::PriorityComponent::new(board_kernel).finalize(()); + let scheduler = components::sched::priority::PriorityComponent::new(board_kernel) + .finalize(components::priority_component_static!()); let artye21 = ArtyE21 { - console: console, - gpio: gpio, - alarm: alarm, - led: led, - button: button, + console, + gpio, + alarm, + led, + button, // ipc: kernel::ipc::IPC::new(board_kernel), scheduler, }; // Create virtual device for kernel debug. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); // arty_e21_chip::uart::UART0.initialize_gpio_pins(&peripherals.gpio_port[17], &peripherals.gpio_port[16]); @@ -261,10 +263,10 @@ pub unsafe fn main() { // Uncomment to run tests //timertest.start(); /*components::test::multi_alarm_test::MultiAlarmTestComponent::new(mux_alarm) - .finalize(components::multi_alarm_test_component_buf!(sifive::clint::Clint)) + .finalize(components::multi_alarm_test_component_buf!(arty_e21_chip::chip::ArtyExxClint)) .run();*/ - /// These symbols are defined in the linker script. + // These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -280,14 +282,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_mgmt_cap, ) @@ -296,10 +298,19 @@ pub unsafe fn main() { debug!("{:?}", err); }); + (board_kernel, artye21, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, board, chip) = start(); board_kernel.kernel_loop( - &artye21, + &board, chip, - None::<&kernel::ipc::IPC>, - &main_loop_cap, + None::<&kernel::ipc::IPC<0>>, + &main_loop_capability, ); } diff --git a/boards/arty_e21/src/timer_test.rs b/boards/arty_e21/src/timer_test.rs index 91b52a7253..bc68b8e5a2 100644 --- a/boards/arty_e21/src/timer_test.rs +++ b/boards/arty_e21/src/timer_test.rs @@ -1,4 +1,6 @@ -#![allow(dead_code)] +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. use kernel::debug; use kernel::hil::time::{self, Alarm, Ticks}; @@ -9,7 +11,7 @@ pub struct TimerTest<'a, A: Alarm<'a>> { impl<'a, A: Alarm<'a>> TimerTest<'a, A> { pub const fn new(alarm: &'a A) -> TimerTest<'a, A> { - TimerTest { alarm: alarm } + TimerTest { alarm } } pub fn start(&self) { diff --git a/boards/build.rs b/boards/build.rs new file mode 100644 index 0000000000..fa0978410d --- /dev/null +++ b/boards/build.rs @@ -0,0 +1,49 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! This build script can be used by Tock board crates to ensure that they are +//! rebuilt when there are any changes to the `layout.ld` linker script or any +//! of its `INCLUDE`s. +//! +//! Board crates can use this script from their `Cargo.toml` files: +//! +//! ```toml +//! [package] +//! # ... +//! build = "../path/to/build.rs" +//! ``` + +use std::fs; +use std::path::Path; + +const LINKER_SCRIPT: &str = "layout.ld"; + +fn main() { + if !Path::new(LINKER_SCRIPT).exists() { + panic!("Boards must provide a `layout.ld` link script file"); + } + + track_linker_script(LINKER_SCRIPT); +} + +/// Track the given linker script and all of its `INCLUDE`s so that the build +/// is rerun when any of them change. +fn track_linker_script>(path: P) { + let path = path.as_ref(); + + assert!(path.is_file(), "expected path {path:?} to be a file"); + + println!("cargo:rerun-if-changed={}", path.display()); + + // Find all the `INCLUDE ` lines in the linker script. + let link_script = fs::read_to_string(path).expect("failed to read {path:?}"); + let includes = link_script + .lines() + .filter_map(|line| line.strip_prefix("INCLUDE").map(str::trim)); + + // Recursively track included linker scripts. + for include in includes { + track_linker_script(include); + } +} diff --git a/boards/clue_nrf52840/Cargo.toml b/boards/clue_nrf52840/Cargo.toml index 7033d24526..11b6ba7262 100644 --- a/boards/clue_nrf52840/Cargo.toml +++ b/boards/clue_nrf52840/Cargo.toml @@ -1,15 +1,25 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "clue_nrf52840" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] cortexm4 = { path = "../../arch/cortex-m4" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } nrf52 = { path = "../../chips/nrf52" } nrf52840 = { path = "../../chips/nrf52840" } components = { path = "../components" } nrf52_components = { path = "../nordic/nrf52_components" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/clue_nrf52840/Makefile b/boards/clue_nrf52840/Makefile index a6fdd3cf46..ed33251eb6 100644 --- a/boards/clue_nrf52840/Makefile +++ b/boards/clue_nrf52840/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the CLUE nRF52840 board. TOCK_ARCH=cortex-m4 diff --git a/boards/clue_nrf52840/README.md b/boards/clue_nrf52840/README.md index 7b68b6a557..81705d6fdc 100644 --- a/boards/clue_nrf52840/README.md +++ b/boards/clue_nrf52840/README.md @@ -65,7 +65,8 @@ If the flashing is sucessful, the LED on the back should turn on. ### Building the bootloader -This step is optional, as a prebuilt bootloader is provided as a [tock-bootloader.clue_nrf52.v1.1.0.bin](bootloader/tock-bootloader.clue_nrf52.v1.1.0.bin). +This step is optional, as a prebuilt bootloader is provided as a +[tock-bootloader.clue_nrf52.v1.1.2.bin](https://github.com/tock/tock-bootloader/releases/download/clue_nrf52840-1.1.2/tock-bootloader.clue_nrf52840.1.1.2.bin). To build the bootloader yourself, please follow the instructions in the Tock Bootloader's [documentation](https://github.com/tock/tock-bootloader/tree/master/boards) for CLUE nRF52840. diff --git a/boards/clue_nrf52840/build.rs b/boards/clue_nrf52840/build.rs deleted file mode 100644 index 3051054fc6..0000000000 --- a/boards/clue_nrf52840/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/clue_nrf52840/layout.ld b/boards/clue_nrf52840/layout.ld index 28fd2d0117..155ce70074 100644 --- a/boards/clue_nrf52840/layout.ld +++ b/boards/clue_nrf52840/layout.ld @@ -1,3 +1,7 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + MEMORY { # Make space for the UF2 bootloader (152K) @@ -15,7 +19,6 @@ MEMORY ram (rwx) : ORIGIN = 0x20006000, LENGTH = 232K } -MPU_MIN_ALIGN = 8K; PAGE_SIZE = 4K; INCLUDE ../kernel_layout.ld diff --git a/boards/clue_nrf52840/src/io.rs b/boards/clue_nrf52840/src/io.rs index c10885692e..01ddd7b4f7 100644 --- a/boards/clue_nrf52840/src/io.rs +++ b/boards/clue_nrf52840/src/io.rs @@ -1,8 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; +use core::ptr::addr_of; +use core::ptr::addr_of_mut; use kernel::ErrorCode; -use cortexm4; use kernel::debug; use kernel::debug::IoWrite; use kernel::hil::led; @@ -46,7 +51,7 @@ impl uart::TransmitClient for DummyUsbClient { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { if !self.initialized { self.initialized = true; } @@ -93,8 +98,8 @@ impl IoWrite for Writer { // mutate it. let usb = &mut cdc.controller(); STATIC_PANIC_BUF[..max].copy_from_slice(&buf[..max]); - let static_buf = &mut STATIC_PANIC_BUF; - cdc.set_transmit_client(&DUMMY); + let static_buf = &mut *addr_of_mut!(STATIC_PANIC_BUF); + cdc.set_transmit_client(&*addr_of!(DUMMY)); let _ = cdc.transmit_buffer(static_buf, max); loop { if let Some(interrupt) = cortexm4::nvic::next_pending() { @@ -105,7 +110,7 @@ impl IoWrite for Writer { n.clear_pending(); n.enable(); } - if DUMMY.fired.get() == true { + if DUMMY.fired.get() { // buffer finished transmitting, return so we can output additional // messages when requested by the panic handler. break; @@ -114,6 +119,7 @@ impl IoWrite for Writer { DUMMY.fired.set(false); }); } + buf.len() } } @@ -123,17 +129,17 @@ impl IoWrite for Writer { #[cfg(not(test))] #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { let led_kernel_pin = &nrf52840::gpio::GPIOPin::new(Pin::P1_01); let led = &mut led::LedHigh::new(led_kernel_pin); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led], writer, pi, &cortexm4::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/clue_nrf52840/src/main.rs b/boards/clue_nrf52840/src/main.rs index 829224470e..10283610a9 100644 --- a/boards/clue_nrf52840/src/main.rs +++ b/boards/clue_nrf52840/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tock kernel for the Adafruit CLUE nRF52480 Express. //! //! It is based on nRF52840 Express SoC (Cortex M4 core with a BLE + IEEE 802.15.4 transceiver). @@ -8,13 +12,14 @@ #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] -use capsules::virtual_aes_ccm::MuxAES128CCM; -use capsules::virtual_alarm::VirtualMuxAlarm; +use core::ptr::addr_of; +use core::ptr::addr_of_mut; + +use capsules_core::virtualizers::virtual_aes_ccm::MuxAES128CCM; use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; -use kernel::hil::gpio::Interrupt; +use kernel::hil::buzzer::Buzzer; use kernel::hil::i2c::I2CMaster; use kernel::hil::led::LedHigh; use kernel::hil::symmetric_encryption::AES128; @@ -22,7 +27,6 @@ use kernel::hil::time::Alarm; use kernel::hil::time::Counter; use kernel::hil::usb::Client; use kernel::platform::chip::Chip; -use kernel::platform::mpu::MPU; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::scheduler::round_robin::RoundRobinSched; #[allow(unused_imports)] @@ -99,8 +103,8 @@ pub mod io; // State for loading and holding applications. // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::StopWithDebugFaultPolicy = - kernel::process::StopWithDebugFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::StopWithDebugFaultPolicy = + capsules_system::process_policies::StopWithDebugFaultPolicy {}; // Number of concurrent processes this platform supports. const NUM_PROCS: usize = 8; @@ -109,12 +113,13 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] [None; NUM_PROCS]; static mut CHIP: Option<&'static nrf52840::chip::NRF52> = None; -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; static mut CDC_REF_FOR_PANIC: Option< - &'static capsules::usb::cdc::CdcAcm< + &'static capsules_extra::usb::cdc::CdcAcm< 'static, nrf52::usbd::Usbd, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc>, >, > = None; static mut NRF52_POWER: Option<&'static nrf52840::power::Power> = None; @@ -136,37 +141,63 @@ fn baud_rate_reset_bootloader_enter() { } } +type SHT3xSensor = components::sht3x::SHT3xComponentType< + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, nrf52840::i2c::TWI<'static>>, +>; +type TemperatureDriver = components::temperature::TemperatureComponentType; +type HumidityDriver = components::humidity::HumidityComponentType; +type RngDriver = components::rng::RngComponentType>; + +type Ieee802154Driver = components::ieee802154::Ieee802154ComponentType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; + /// Supported drivers by the platform pub struct Platform { - ble_radio: &'static capsules::ble_advertising_driver::BLE< + ble_radio: &'static capsules_extra::ble_advertising_driver::BLE< 'static, nrf52::ble_radio::Radio<'static>, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52::rtc::Rtc<'static>, + >, >, - ieee802154_radio: &'static capsules::ieee802154::RadioDriver<'static>, - console: &'static capsules::console::Console<'static>, - proximity: &'static capsules::proximity::ProximitySensor<'static>, - gpio: &'static capsules::gpio::GPIO<'static, nrf52::gpio::GPIOPin<'static>>, - led: &'static capsules::led::LedDriver< + ieee802154_radio: &'static Ieee802154Driver, + console: &'static capsules_core::console::Console<'static>, + proximity: &'static capsules_extra::proximity::ProximitySensor<'static>, + gpio: &'static capsules_core::gpio::GPIO<'static, nrf52::gpio::GPIOPin<'static>>, + led: &'static capsules_core::led::LedDriver< 'static, LedHigh<'static, nrf52::gpio::GPIOPin<'static>>, 2, >, - button: &'static capsules::button::Button<'static, nrf52::gpio::GPIOPin<'static>>, - screen: &'static capsules::screen::Screen<'static>, - rng: &'static capsules::rng::RngDriver<'static>, - ipc: kernel::ipc::IPC, - alarm: &'static capsules::alarm::AlarmDriver< + button: &'static capsules_core::button::Button<'static, nrf52::gpio::GPIOPin<'static>>, + screen: &'static capsules_extra::screen::Screen<'static>, + rng: &'static RngDriver, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52::rtc::Rtc<'static>, + >, >, - buzzer: &'static capsules::buzzer_driver::Buzzer< + buzzer: &'static capsules_extra::buzzer_driver::Buzzer< 'static, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, + capsules_extra::buzzer_pwm::PwmBuzzer< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52840::rtc::Rtc<'static>, + >, + capsules_core::virtualizers::virtual_pwm::PwmPinUser<'static, nrf52840::pwm::Pwm>, + >, >, - adc: &'static capsules::adc::AdcVirtualized<'static>, - temperature: &'static capsules::temperature::TemperatureSensor<'static>, - humidity: &'static capsules::humidity::HumiditySensor<'static>, + adc: &'static capsules_core::adc::AdcVirtualized<'static>, + temperature: &'static TemperatureDriver, + humidity: &'static HumidityDriver, scheduler: &'static RoundRobinSched<'static>, systick: cortexm4::systick::SysTick, } @@ -177,21 +208,21 @@ impl SyscallDriverLookup for Platform { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::proximity::DRIVER_NUM => f(Some(self.proximity)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::button::DRIVER_NUM => f(Some(self.button)), - capsules::adc::DRIVER_NUM => f(Some(self.adc)), - capsules::screen::DRIVER_NUM => f(Some(self.screen)), - capsules::rng::DRIVER_NUM => f(Some(self.rng)), - capsules::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), - capsules::ieee802154::DRIVER_NUM => f(Some(self.ieee802154_radio)), - capsules::buzzer_driver::DRIVER_NUM => f(Some(self.buzzer)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_extra::proximity::DRIVER_NUM => f(Some(self.proximity)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_extra::screen::DRIVER_NUM => f(Some(self.screen)), + capsules_core::rng::DRIVER_NUM => f(Some(self.rng)), + capsules_extra::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), + capsules_extra::ieee802154::DRIVER_NUM => f(Some(self.ieee802154_radio)), + capsules_extra::buzzer_driver::DRIVER_NUM => f(Some(self.buzzer)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), - capsules::temperature::DRIVER_NUM => f(Some(self.temperature)), - capsules::humidity::DRIVER_NUM => f(Some(self.humidity)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temperature)), + capsules_extra::humidity::DRIVER_NUM => f(Some(self.humidity)), _ => f(None), } } @@ -209,7 +240,7 @@ impl KernelResources &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -235,23 +266,23 @@ impl KernelResources &'static mut Nrf52840DefaultPeripherals<'static> { +unsafe fn start() -> ( + &'static kernel::Kernel, + Platform, + &'static nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>, +) { + nrf52840::init(); + + let ieee802154_ack_buf = static_init!( + [u8; nrf52840::ieee802154_radio::ACK_BUF_SIZE], + [0; nrf52840::ieee802154_radio::ACK_BUF_SIZE] + ); // Initialize chip peripheral drivers let nrf52840_peripherals = static_init!( Nrf52840DefaultPeripherals, - Nrf52840DefaultPeripherals::new() + Nrf52840DefaultPeripherals::new(ieee802154_ack_buf) ); - nrf52840_peripherals -} - -/// Main function called after RAM initialized. -#[no_mangle] -pub unsafe fn main() { - nrf52840::init(); - - let nrf52840_peripherals = get_peripherals(); - // set up circular peripheral dependencies nrf52840_peripherals.init(); @@ -261,7 +292,7 @@ pub unsafe fn main() { // bootloader. NRF52_POWER = Some(&base_peripherals.pwr_clk); - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); //-------------------------------------------------------------------------- // CAPABILITIES @@ -271,7 +302,6 @@ pub unsafe fn main() { // functions. let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); //-------------------------------------------------------------------------- @@ -293,7 +323,7 @@ pub unsafe fn main() { let gpio = components::gpio::GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( nrf52840::gpio::GPIOPin, // uncomment the following to use pins D0, D1, D2, D3 and D4 as gpio @@ -321,13 +351,13 @@ pub unsafe fn main() { 16 => &nrf52840_peripherals.gpio_port[GPIO_D16] ), ) - .finalize(components::gpio_component_buf!(nrf52840::gpio::GPIOPin)); + .finalize(components::gpio_component_static!(nrf52840::gpio::GPIOPin)); //-------------------------------------------------------------------------- // LEDs //-------------------------------------------------------------------------- - let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( LedHigh<'static, nrf52840::gpio::GPIOPin>, LedHigh::new(&nrf52840_peripherals.gpio_port[LED_RED_PIN]), LedHigh::new(&nrf52840_peripherals.gpio_port[LED_WHITE_PIN]) @@ -338,7 +368,7 @@ pub unsafe fn main() { //-------------------------------------------------------------------------- let button = components::button::ButtonComponent::new( board_kernel, - capsules::button::DRIVER_NUM, + capsules_core::button::DRIVER_NUM, components::button_component_helper!( nrf52840::gpio::GPIOPin, ( @@ -353,19 +383,9 @@ pub unsafe fn main() { ) // Right ), ) - .finalize(components::button_component_buf!(nrf52840::gpio::GPIOPin)); - - //-------------------------------------------------------------------------- - // Deferred Call (Dynamic) Setup - //-------------------------------------------------------------------------- - - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 5], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + .finalize(components::button_component_static!( + nrf52840::gpio::GPIOPin + )); //-------------------------------------------------------------------------- // ALARM & TIMER @@ -375,25 +395,25 @@ pub unsafe fn main() { let _ = rtc.start(); let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) - .finalize(components::alarm_mux_component_helper!(nrf52::rtc::Rtc)); + .finalize(components::alarm_mux_component_static!(nrf52::rtc::Rtc)); let alarm = components::alarm::AlarmDriverComponent::new( board_kernel, - capsules::alarm::DRIVER_NUM, + capsules_core::alarm::DRIVER_NUM, mux_alarm, ) - .finalize(components::alarm_component_helper!(nrf52::rtc::Rtc)); + .finalize(components::alarm_component_static!(nrf52::rtc::Rtc)); //-------------------------------------------------------------------------- // PWM & BUZZER //-------------------------------------------------------------------------- let mux_pwm = static_init!( - capsules::virtual_pwm::MuxPwm<'static, nrf52840::pwm::Pwm>, - capsules::virtual_pwm::MuxPwm::new(&base_peripherals.pwm0) + capsules_core::virtualizers::virtual_pwm::MuxPwm<'static, nrf52840::pwm::Pwm>, + capsules_core::virtualizers::virtual_pwm::MuxPwm::new(&base_peripherals.pwm0) ); let virtual_pwm_buzzer = static_init!( - capsules::virtual_pwm::PwmPinUser<'static, nrf52840::pwm::Pwm>, - capsules::virtual_pwm::PwmPinUser::new( + capsules_core::virtualizers::virtual_pwm::PwmPinUser<'static, nrf52840::pwm::Pwm>, + capsules_core::virtualizers::virtual_pwm::PwmPinUser::new( mux_pwm, nrf52840::pinmux::Pinmux::new(SPEAKER_PIN as u32) ) @@ -401,27 +421,52 @@ pub unsafe fn main() { virtual_pwm_buzzer.add_to_mux(); let virtual_alarm_buzzer = static_init!( - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52840::rtc::Rtc>, - capsules::virtual_alarm::VirtualMuxAlarm::new(mux_alarm) + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, nrf52840::rtc::Rtc>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm::new(mux_alarm) ); virtual_alarm_buzzer.setup(); - let buzzer = static_init!( - capsules::buzzer_driver::Buzzer< + let pwm_buzzer = static_init!( + capsules_extra::buzzer_pwm::PwmBuzzer< 'static, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52840::rtc::Rtc>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52840::rtc::Rtc, + >, + capsules_core::virtualizers::virtual_pwm::PwmPinUser<'static, nrf52840::pwm::Pwm>, >, - capsules::buzzer_driver::Buzzer::new( + capsules_extra::buzzer_pwm::PwmBuzzer::new( virtual_pwm_buzzer, virtual_alarm_buzzer, - capsules::buzzer_driver::DEFAULT_MAX_BUZZ_TIME_MS, + capsules_extra::buzzer_pwm::DEFAULT_MAX_BUZZ_TIME_MS, + ) + ); + + let buzzer = static_init!( + capsules_extra::buzzer_driver::Buzzer< + 'static, + capsules_extra::buzzer_pwm::PwmBuzzer< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52840::rtc::Rtc, + >, + capsules_core::virtualizers::virtual_pwm::PwmPinUser<'static, nrf52840::pwm::Pwm>, + >, + >, + capsules_extra::buzzer_driver::Buzzer::new( + pwm_buzzer, + capsules_extra::buzzer_driver::DEFAULT_MAX_BUZZ_TIME_MS, board_kernel.create_grant( - capsules::buzzer_driver::DRIVER_NUM, + capsules_extra::buzzer_driver::DRIVER_NUM, &memory_allocation_capability ) ) ); - virtual_alarm_buzzer.set_alarm_client(buzzer); + + pwm_buzzer.set_client(buzzer); + + virtual_alarm_buzzer.set_alarm_client(pwm_buzzer); //-------------------------------------------------------------------------- // UART & CONSOLE & DEBUG @@ -446,33 +491,33 @@ pub unsafe fn main() { let cdc = components::cdc::CdcAcmComponent::new( &nrf52840_peripherals.usbd, - capsules::usb::cdc::MAX_CTRL_PACKET_SIZE_NRF52840, + capsules_extra::usb::cdc::MAX_CTRL_PACKET_SIZE_NRF52840, 0x239a, 0x8071, strings, mux_alarm, - dynamic_deferred_caller, Some(&baud_rate_reset_bootloader_enter), ) - .finalize(components::usb_cdc_acm_component_helper!( + .finalize(components::cdc_acm_component_static!( nrf52::usbd::Usbd, nrf52::rtc::Rtc )); CDC_REF_FOR_PANIC = Some(cdc); //for use by panic handler // Create a shared UART channel for the console and for kernel debug. - let uart_mux = components::console::UartMuxComponent::new(cdc, 115200, dynamic_deferred_caller) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(cdc, 115200) + .finalize(components::uart_mux_component_static!()); // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); //-------------------------------------------------------------------------- // RANDOM NUMBERS @@ -480,10 +525,10 @@ pub unsafe fn main() { let rng = components::rng::RngComponent::new( board_kernel, - capsules::rng::DRIVER_NUM, + capsules_core::rng::DRIVER_NUM, &base_peripherals.trng, ) - .finalize(()); + .finalize(components::rng_component_static!(nrf52840::trng::Trng)); //-------------------------------------------------------------------------- // ADC @@ -491,53 +536,53 @@ pub unsafe fn main() { base_peripherals.adc.calibrate(); let adc_mux = components::adc::AdcMuxComponent::new(&base_peripherals.adc) - .finalize(components::adc_mux_component_helper!(nrf52840::adc::Adc)); + .finalize(components::adc_mux_component_static!(nrf52840::adc::Adc)); let adc_syscall = - components::adc::AdcVirtualComponent::new(board_kernel, capsules::adc::DRIVER_NUM) + components::adc::AdcVirtualComponent::new(board_kernel, capsules_core::adc::DRIVER_NUM) .finalize(components::adc_syscall_component_helper!( // A0 components::adc::AdcComponent::new( - &adc_mux, + adc_mux, nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput7) ) - .finalize(components::adc_component_helper!(nrf52840::adc::Adc)), + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), // A1 components::adc::AdcComponent::new( - &adc_mux, + adc_mux, nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput5) ) - .finalize(components::adc_component_helper!(nrf52840::adc::Adc)), + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), // A2 components::adc::AdcComponent::new( - &adc_mux, + adc_mux, nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput2) ) - .finalize(components::adc_component_helper!(nrf52840::adc::Adc)), + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), // A3 components::adc::AdcComponent::new( - &adc_mux, + adc_mux, nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput3) ) - .finalize(components::adc_component_helper!(nrf52840::adc::Adc)), + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), // A4 components::adc::AdcComponent::new( - &adc_mux, + adc_mux, nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput1) ) - .finalize(components::adc_component_helper!(nrf52840::adc::Adc)), + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), // A5 components::adc::AdcComponent::new( - &adc_mux, + adc_mux, nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput4) ) - .finalize(components::adc_component_helper!(nrf52840::adc::Adc)), + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), // A6 components::adc::AdcComponent::new( - &adc_mux, + adc_mux, nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput0) ) - .finalize(components::adc_component_helper!(nrf52840::adc::Adc)), + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), )); //-------------------------------------------------------------------------- @@ -545,68 +590,59 @@ pub unsafe fn main() { //-------------------------------------------------------------------------- let sensors_i2c_bus = static_init!( - capsules::virtual_i2c::MuxI2C<'static>, - capsules::virtual_i2c::MuxI2C::new(&base_peripherals.twi1, None, dynamic_deferred_caller) + capsules_core::virtualizers::virtual_i2c::MuxI2C<'static, nrf52840::i2c::TWI>, + capsules_core::virtualizers::virtual_i2c::MuxI2C::new(&base_peripherals.twi1, None,) ); + kernel::deferred_call::DeferredCallClient::register(sensors_i2c_bus); base_peripherals.twi1.configure( nrf52840::pinmux::Pinmux::new(I2C_SCL_PIN as u32), nrf52840::pinmux::Pinmux::new(I2C_SDA_PIN as u32), ); base_peripherals.twi1.set_master_client(sensors_i2c_bus); - let apds9960_i2c = static_init!( - capsules::virtual_i2c::I2CDevice, - capsules::virtual_i2c::I2CDevice::new(sensors_i2c_bus, 0x39) - ); - - let apds9960 = static_init!( - capsules::apds9960::APDS9960<'static>, - capsules::apds9960::APDS9960::new( - apds9960_i2c, - &nrf52840_peripherals.gpio_port[APDS9960_PIN], - &mut capsules::apds9960::BUFFER - ) - ); - apds9960_i2c.set_client(apds9960); - nrf52840_peripherals.gpio_port[APDS9960_PIN].set_client(apds9960); - - let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - - let proximity = static_init!( - capsules::proximity::ProximitySensor<'static>, - capsules::proximity::ProximitySensor::new( - apds9960, - board_kernel.create_grant(capsules::proximity::DRIVER_NUM, &grant_cap) - ) - ); - - kernel::hil::sensors::ProximityDriver::set_client(apds9960, proximity); + let apds9960 = components::apds9960::Apds9960Component::new( + sensors_i2c_bus, + 0x39, + &nrf52840_peripherals.gpio_port[APDS9960_PIN], + ) + .finalize(components::apds9960_component_static!(nrf52840::i2c::TWI)); + let proximity = components::proximity::ProximityComponent::new( + apds9960, + board_kernel, + capsules_extra::proximity::DRIVER_NUM, + ) + .finalize(components::proximity_component_static!()); - let sht3x = components::sht3x::SHT3xComponent::new(sensors_i2c_bus, mux_alarm).finalize( - components::sht3x_component_helper!(nrf52::rtc::Rtc<'static>, capsules::sht3x::BASE_ADDR), - ); + let sht3x = components::sht3x::SHT3xComponent::new( + sensors_i2c_bus, + capsules_extra::sht3x::BASE_ADDR, + mux_alarm, + ) + .finalize(components::sht3x_component_static!( + nrf52::rtc::Rtc<'static>, + nrf52840::i2c::TWI + )); let temperature = components::temperature::TemperatureComponent::new( board_kernel, - capsules::temperature::DRIVER_NUM, + capsules_extra::temperature::DRIVER_NUM, sht3x, ) - .finalize(()); + .finalize(components::temperature_component_static!(SHT3xSensor)); let humidity = components::humidity::HumidityComponent::new( board_kernel, - capsules::humidity::DRIVER_NUM, + capsules_extra::humidity::DRIVER_NUM, sht3x, ) - .finalize(()); + .finalize(components::humidity_component_static!(SHT3xSensor)); //-------------------------------------------------------------------------- // TFT //-------------------------------------------------------------------------- - let spi_mux = - components::spi::SpiMuxComponent::new(&base_peripherals.spim0, dynamic_deferred_caller) - .finalize(components::spi_mux_component_helper!(nrf52840::spi::SPIM)); + let spi_mux = components::spi::SpiMuxComponent::new(&base_peripherals.spim0) + .finalize(components::spi_mux_component_static!(nrf52840::spi::SPIM)); base_peripherals.spim0.configure( nrf52840::pinmux::Pinmux::new(ST7789H2_MOSI as u32), @@ -615,92 +651,88 @@ pub unsafe fn main() { ); let bus = components::bus::SpiMasterBusComponent::new( + spi_mux, + &nrf52840_peripherals.gpio_port[ST7789H2_CS], 20_000_000, kernel::hil::spi::ClockPhase::SampleLeading, kernel::hil::spi::ClockPolarity::IdleLow, ) - .finalize(components::spi_bus_component_helper!( - // spi type - nrf52840::spi::SPIM, - // chip select - &nrf52840_peripherals.gpio_port[ST7789H2_CS], - // spi mux - spi_mux - )); + .finalize(components::spi_bus_component_static!(nrf52840::spi::SPIM)); - let tft = components::st77xx::ST77XXComponent::new(mux_alarm).finalize( - components::st77xx_component_helper!( - // screen - &capsules::st77xx::ST7789H2, - // bus type - capsules::bus::SpiMasterBus< + let tft = components::st77xx::ST77XXComponent::new( + mux_alarm, + bus, + Some(&nrf52840_peripherals.gpio_port[ST7789H2_DC]), + Some(&nrf52840_peripherals.gpio_port[ST7789H2_RESET]), + &capsules_extra::st77xx::ST7789H2, + ) + .finalize(components::st77xx_component_static!( + // bus type + capsules_extra::bus::SpiMasterBus< + 'static, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice< 'static, - VirtualSpiMasterDevice<'static, nrf52840::spi::SPIM>, + nrf52840::spi::SPIM, >, - // bus - &bus, - // timer type - nrf52840::rtc::Rtc, - // pin type - nrf52::gpio::GPIOPin<'static>, - // dc - Some(&nrf52840_peripherals.gpio_port[ST7789H2_DC]), - // reset - Some(&nrf52840_peripherals.gpio_port[ST7789H2_RESET]) - ), - ); + >, + // timer type + nrf52840::rtc::Rtc, + // pin type + nrf52::gpio::GPIOPin<'static> + )); let _ = tft.init(); let screen = components::screen::ScreenComponent::new( board_kernel, - capsules::screen::DRIVER_NUM, + capsules_extra::screen::DRIVER_NUM, tft, Some(tft), ) - .finalize(components::screen_buffer_size!(57600)); + .finalize(components::screen_component_static!(57600)); //-------------------------------------------------------------------------- // WIRELESS //-------------------------------------------------------------------------- - let ble_radio = nrf52_components::BLEComponent::new( + let ble_radio = components::ble::BLEComponent::new( board_kernel, - capsules::ble_advertising_driver::DRIVER_NUM, + capsules_extra::ble_advertising_driver::DRIVER_NUM, &base_peripherals.ble_radio, mux_alarm, ) - .finalize(()); + .finalize(components::ble_component_static!( + nrf52840::rtc::Rtc, + nrf52840::ble_radio::Radio + )); let aes_mux = static_init!( MuxAES128CCM<'static, nrf52840::aes::AesECB>, - MuxAES128CCM::new(&base_peripherals.ecb, dynamic_deferred_caller) + MuxAES128CCM::new(&base_peripherals.ecb,) ); + kernel::deferred_call::DeferredCallClient::register(aes_mux); base_peripherals.ecb.set_client(aes_mux); - aes_mux.initialize_callback_handle( - dynamic_deferred_caller.register(aes_mux).unwrap(), // Unwrap fail = no deferred call slot available for ccm mux - ); - let serial_num = nrf52840::ficr::FICR_INSTANCE.address(); + let device_id = nrf52840::ficr::FICR_INSTANCE.id(); - let serial_num_bottom_16 = u16::from_le_bytes([serial_num[0], serial_num[1]]); + let device_id_bottom_16 = u16::from_le_bytes([device_id[0], device_id[1]]); let (ieee802154_radio, _mux_mac) = components::ieee802154::Ieee802154Component::new( board_kernel, - capsules::ieee802154::DRIVER_NUM, - &base_peripherals.ieee802154_radio, + capsules_extra::ieee802154::DRIVER_NUM, + &nrf52840_peripherals.ieee802154_radio, aes_mux, PAN_ID, - serial_num_bottom_16, - dynamic_deferred_caller, + device_id_bottom_16, + device_id, ) - .finalize(components::ieee802154_component_helper!( + .finalize(components::ieee802154_component_static!( nrf52840::ieee802154_radio::Radio, nrf52840::aes::AesECB<'static> )); - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); let pconsole = components::process_console::ProcessConsoleComponent::new( @@ -708,8 +740,9 @@ pub unsafe fn main() { uart_mux, mux_alarm, process_printer, + Some(cortexm4::support::reset), ) - .finalize(components::process_console_component_helper!( + .finalize(components::process_console_component_static!( nrf52840::rtc::Rtc )); let _ = pconsole.start(); @@ -722,29 +755,29 @@ pub unsafe fn main() { // approach than this. nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); let platform = Platform { - ble_radio: ble_radio, - ieee802154_radio: ieee802154_radio, - console: console, - proximity: proximity, - led: led, - gpio: gpio, + ble_radio, + ieee802154_radio, + console, + proximity, + led, + gpio, adc: adc_syscall, - screen: screen, - button: button, - rng: rng, - buzzer: buzzer, - alarm: alarm, + screen, + button, + rng, + buzzer, + alarm, ipc: kernel::ipc::IPC::new( board_kernel, kernel::ipc::DRIVER_NUM, &memory_allocation_capability, ), - temperature: temperature, - humidity: humidity, + temperature, + humidity, scheduler, systick: cortexm4::systick::SysTick::new_with_calibration(64000000), }; @@ -768,7 +801,7 @@ pub unsafe fn main() { // PROCESSES AND MAIN LOOP //-------------------------------------------------------------------------- - /// These symbols are defined in the linker script. + // These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -784,14 +817,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_management_capability, ) @@ -800,5 +833,14 @@ pub unsafe fn main() { debug!("{:?}", err); }); - board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); + (board_kernel, platform, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, board, chip) = start(); + board_kernel.kernel_loop(&board, chip, Some(&board.ipc), &main_loop_capability); } diff --git a/boards/components/Cargo.toml b/boards/components/Cargo.toml index 7a75e89b76..efcf91af5a 100644 --- a/boards/components/Cargo.toml +++ b/boards/components/Cargo.toml @@ -1,9 +1,19 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "components" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/components/src/adc.rs b/boards/components/src/adc.rs index 208e493a9f..6f31caa59d 100644 --- a/boards/components/src/adc.rs +++ b/boards/components/src/adc.rs @@ -1,38 +1,35 @@ -use capsules::adc::AdcVirtualized; -use capsules::virtual_adc::{AdcDevice, MuxAdc}; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Components for using ADC capsules. + +use capsules_core::adc::AdcDedicated; +use capsules_core::adc::AdcVirtualized; +use capsules_core::virtualizers::virtual_adc::{AdcDevice, MuxAdc}; use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; use kernel::hil::adc; -use kernel::static_init_half; #[macro_export] -macro_rules! adc_mux_component_helper { +macro_rules! adc_mux_component_static { ($A:ty $(,)?) => {{ - use capsules::virtual_adc::MuxAdc; - use core::mem::MaybeUninit; - static mut BUF: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF + kernel::static_buf!(capsules_core::virtualizers::virtual_adc::MuxAdc<'static, $A>) };}; } #[macro_export] -macro_rules! adc_component_helper { +macro_rules! adc_component_static { ($A:ty $(,)?) => {{ - use capsules::virtual_adc::AdcDevice; - use core::mem::MaybeUninit; - static mut BUF: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF + kernel::static_buf!(capsules_core::virtualizers::virtual_adc::AdcDevice<'static, $A>) };}; } #[macro_export] macro_rules! adc_syscall_component_helper { ($($P:expr),+ $(,)?) => {{ - use capsules::adc::AdcVirtualized; - use core::mem::MaybeUninit; - use kernel::hil; use kernel::count_expressions; use kernel::static_init; const NUM_DRIVERS: usize = count_expressions!($($P),+); @@ -43,38 +40,39 @@ macro_rules! adc_syscall_component_helper { $($P,)* ] ); - static mut BUF: MaybeUninit> = - MaybeUninit::uninit(); - (&mut BUF, drivers) + let adc_virtualized = kernel::static_buf!(capsules_core::adc::AdcVirtualized<'static>); + (adc_virtualized, drivers) };}; } -pub struct AdcMuxComponent { - adc: &'static A, +#[macro_export] +macro_rules! adc_dedicated_component_static { + ($A:ty $(,)?) => {{ + let adc = kernel::static_buf!(capsules_core::adc::AdcDedicated<'static, $A>); + let buffer1 = kernel::static_buf!([u16; capsules_core::adc::BUF_LEN]); + let buffer2 = kernel::static_buf!([u16; capsules_core::adc::BUF_LEN]); + let buffer3 = kernel::static_buf!([u16; capsules_core::adc::BUF_LEN]); + + (adc, buffer1, buffer2, buffer3) + };}; } -pub struct AdcComponent { - adc_mux: &'static MuxAdc<'static, A>, - channel: A::Channel, +pub struct AdcMuxComponent> { + adc: &'static A, } -impl AdcMuxComponent { +impl> AdcMuxComponent { pub fn new(adc: &'static A) -> Self { - AdcMuxComponent { adc: adc } + AdcMuxComponent { adc } } } -pub struct AdcVirtualComponent { - board_kernel: &'static kernel::Kernel, - driver_num: usize, -} - -impl Component for AdcMuxComponent { +impl> Component for AdcMuxComponent { type StaticInput = &'static mut MaybeUninit>; type Output = &'static MuxAdc<'static, A>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let adc_mux = static_init_half!(static_buffer, MuxAdc<'static, A>, MuxAdc::new(self.adc)); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let adc_mux = static_buffer.write(MuxAdc::new(self.adc)); self.adc.set_client(adc_mux); @@ -82,25 +80,26 @@ impl Component for AdcMuxComponent { } } -impl AdcComponent { +pub struct AdcComponent> { + adc_mux: &'static MuxAdc<'static, A>, + channel: A::Channel, +} + +impl> AdcComponent { pub fn new(mux: &'static MuxAdc<'static, A>, channel: A::Channel) -> Self { AdcComponent { adc_mux: mux, - channel: channel, + channel, } } } -impl Component for AdcComponent { +impl> Component for AdcComponent { type StaticInput = &'static mut MaybeUninit>; type Output = &'static AdcDevice<'static, A>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let adc_device = static_init_half!( - static_buffer, - AdcDevice<'static, A>, - AdcDevice::new(self.adc_mux, self.channel) - ); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let adc_device = static_buffer.write(AdcDevice::new(self.adc_mux, self.channel)); adc_device.add_to_mux(); @@ -108,11 +107,16 @@ impl Component for AdcComponent { } } +pub struct AdcVirtualComponent { + board_kernel: &'static kernel::Kernel, + driver_num: usize, +} + impl AdcVirtualComponent { pub fn new(board_kernel: &'static kernel::Kernel, driver_num: usize) -> AdcVirtualComponent { AdcVirtualComponent { - board_kernel: board_kernel, - driver_num: driver_num, + board_kernel, + driver_num, } } } @@ -120,19 +124,20 @@ impl AdcVirtualComponent { impl Component for AdcVirtualComponent { type StaticInput = ( &'static mut MaybeUninit>, - &'static [&'static dyn kernel::hil::adc::AdcChannel], + &'static [&'static dyn kernel::hil::adc::AdcChannel<'static>], ); - type Output = &'static capsules::adc::AdcVirtualized<'static>; + type Output = &'static capsules_core::adc::AdcVirtualized<'static>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); let grant_adc = self.board_kernel.create_grant(self.driver_num, &grant_cap); - let adc = static_init_half!( - static_buffer.0, - capsules::adc::AdcVirtualized<'static>, - capsules::adc::AdcVirtualized::new(static_buffer.1, grant_adc) - ); + let adc = static_buffer + .0 + .write(capsules_core::adc::AdcVirtualized::new( + static_buffer.1, + grant_adc, + )); for driver in static_buffer.1 { kernel::hil::adc::AdcChannel::set_client(*driver, adc); @@ -141,3 +146,65 @@ impl Component for AdcVirtualComponent { adc } } + +pub type AdcDedicatedComponentType = capsules_core::adc::AdcDedicated<'static, A>; + +pub struct AdcDedicatedComponent< + A: kernel::hil::adc::Adc<'static> + kernel::hil::adc::AdcHighSpeed<'static> + 'static, +> { + adc: &'static A, + channels: &'static [A::Channel], + board_kernel: &'static kernel::Kernel, + driver_num: usize, +} + +impl + kernel::hil::adc::AdcHighSpeed<'static> + 'static> + AdcDedicatedComponent +{ + pub fn new( + adc: &'static A, + channels: &'static [A::Channel], + board_kernel: &'static kernel::Kernel, + driver_num: usize, + ) -> AdcDedicatedComponent { + AdcDedicatedComponent { + adc, + channels, + board_kernel, + driver_num, + } + } +} + +impl + kernel::hil::adc::AdcHighSpeed<'static> + 'static> + Component for AdcDedicatedComponent +{ + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u16; capsules_core::adc::BUF_LEN]>, + &'static mut MaybeUninit<[u16; capsules_core::adc::BUF_LEN]>, + &'static mut MaybeUninit<[u16; capsules_core::adc::BUF_LEN]>, + ); + type Output = &'static AdcDedicated<'static, A>; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + + let buffer1 = s.1.write([0; capsules_core::adc::BUF_LEN]); + let buffer2 = s.2.write([0; capsules_core::adc::BUF_LEN]); + let buffer3 = s.3.write([0; capsules_core::adc::BUF_LEN]); + + let adc = s.0.write(AdcDedicated::new( + self.adc, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + self.channels, + buffer1, + buffer2, + buffer3, + )); + self.adc.set_client(adc); + self.adc.set_highspeed_client(adc); + + adc + } +} diff --git a/boards/components/src/adc_microphone.rs b/boards/components/src/adc_microphone.rs index ccec3b58ca..60c0fbb8d4 100644 --- a/boards/components/src/adc_microphone.rs +++ b/boards/components/src/adc_microphone.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for ADC Microphone //! //! Usage @@ -5,94 +9,91 @@ //! //! //! ```rust -//! let adc_microphone = components::adc_microphone::AdcMicrophoneComponent::new().finalize( -//! components::adc_microphone_component_helper!( -//! // adc -//! nrf52833::adc::Adc, -//! // adc channel -//! nrf52833::adc::AdcChannelSetup::setup( -//! nrf52833::adc::AdcChannel::AnalogInput3, -//! nrf52833::adc::AdcChannelGain::Gain4, -//! nrf52833::adc::AdcChannelResistor::Bypass, -//! nrf52833::adc::AdcChannelResistor::Pulldown, -//! nrf52833::adc::AdcChannelSamplingTime::us3 -//! ), -//! // adc mux -//! adc_mux, -//! // buffer size -//! 50, -//! // gpio -//! nrf52833::gpio::GPIOPin, -//! // optional gpio pin -//! Some(&base_peripherals.gpio_port[LED_MICROPHONE_PIN]) -//! ), -//! ); +//! let adc_microphone = components::adc_microphone::AdcMicrophoneComponent::new( +//! adc_mux, +//! nrf52833::adc::AdcChannelSetup::setup( +//! nrf52833::adc::AdcChannel::AnalogInput3, +//! nrf52833::adc::AdcChannelGain::Gain4, +//! nrf52833::adc::AdcChannelResistor::Bypass, +//! nrf52833::adc::AdcChannelResistor::Pulldown, +//! nrf52833::adc::AdcChannelSamplingTime::us3, +//! ), +//! Some(&nrf52833_peripherals.gpio_port[LED_MICROPHONE_PIN]), +//! ) +//! .finalize(components::adc_microphone_component_static!( +//! // adc +//! nrf52833::adc::Adc, +//! // buffer size +//! 50, +//! // gpio +//! nrf52833::gpio::GPIOPin +//! )); //! ``` -use capsules::adc_microphone::AdcMicrophone; -use capsules::virtual_adc::AdcDevice; -use core::marker::PhantomData; +use capsules_core::virtualizers::virtual_adc::AdcDevice; +use capsules_extra::adc_microphone::AdcMicrophone; use core::mem::MaybeUninit; use kernel::component::Component; use kernel::hil::adc::{self, AdcChannel}; use kernel::hil::gpio; -use kernel::static_init_half; #[macro_export] -macro_rules! adc_microphone_component_helper { - ($A:ty, $channel:expr, $adc_mux:expr, $LEN:literal, $P: ty, $pin: expr $(,)?) => {{ - use capsules::adc_microphone::AdcMicrophone; - use capsules::virtual_adc::AdcDevice; - use core::mem::MaybeUninit; - use kernel::hil::adc::Adc; - use kernel::hil::gpio::Pin; - - static mut BUFFER: [u16; $LEN] = [0; $LEN]; +macro_rules! adc_microphone_component_static { + ($A:ty, $LEN:literal, $P: ty $(,)?) => {{ + let adc_device = components::adc_component_static!($A); + let buffer = kernel::static_buf!([u16; $LEN]); + let adc_microphone = + kernel::static_buf!(capsules_extra::adc_microphone::AdcMicrophone<'static, $P>); - let mut adc_microphone_adc: &'static capsules::virtual_adc::AdcDevice<'static, $A> = - components::adc::AdcComponent::new($adc_mux, $channel) - .finalize(components::adc_component_helper!($A)); - static mut adc_microphone: MaybeUninit> = MaybeUninit::uninit(); - ( - &mut adc_microphone_adc, - $pin, - &mut BUFFER, - &mut adc_microphone, - ) + (adc_device, buffer, adc_microphone) };}; } -pub struct AdcMicrophoneComponent { - _adc: PhantomData, - _pin: PhantomData

, +pub struct AdcMicrophoneComponent< + A: 'static + adc::Adc<'static>, + P: 'static + gpio::Pin, + const BUF_LEN: usize, +> { + adc_mux: &'static capsules_core::virtualizers::virtual_adc::MuxAdc<'static, A>, + adc_channel: A::Channel, + pin: Option<&'static P>, } -impl AdcMicrophoneComponent { - pub fn new() -> AdcMicrophoneComponent { +impl, P: 'static + gpio::Pin, const BUF_LEN: usize> + AdcMicrophoneComponent +{ + pub fn new( + adc_mux: &'static capsules_core::virtualizers::virtual_adc::MuxAdc<'static, A>, + adc_channel: A::Channel, + pin: Option<&'static P>, + ) -> AdcMicrophoneComponent { AdcMicrophoneComponent { - _adc: PhantomData, - _pin: PhantomData, + adc_mux, + adc_channel, + pin, } } } -impl Component for AdcMicrophoneComponent { +impl, P: 'static + gpio::Pin, const BUF_LEN: usize> Component + for AdcMicrophoneComponent +{ type StaticInput = ( - &'static AdcDevice<'static, A>, - Option<&'static P>, - &'static mut [u16], + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u16; BUF_LEN]>, &'static mut MaybeUninit>, ); type Output = &'static AdcMicrophone<'static, P>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let adc_microphone = static_init_half!( - static_buffer.3, - AdcMicrophone<'static, P>, - AdcMicrophone::new(static_buffer.0, static_buffer.1, static_buffer.2) - ); + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let adc_device = + crate::adc::AdcComponent::new(self.adc_mux, self.adc_channel).finalize(s.0); + + let buffer = s.1.write([0; BUF_LEN]); + + let adc_microphone = s.2.write(AdcMicrophone::new(adc_device, self.pin, buffer)); - static_buffer.0.set_client(adc_microphone); + adc_device.set_client(adc_microphone); adc_microphone } diff --git a/boards/components/src/aes.rs b/boards/components/src/aes.rs new file mode 100644 index 0000000000..1627cd70fd --- /dev/null +++ b/boards/components/src/aes.rs @@ -0,0 +1,160 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Components for various AES utilities. +//! +//! Usage +//! ----- +//! ```rust +//! let aes_driver_device = components::aes::AesVirtualComponent::new(aes_mux).finalize( +//! components::aes_virtual_component_static!(nrf52840::aes::AesECB<'static>), +//! ); +//! +//! let aes = components::aes::AesDriverComponent::new( +//! board_kernel, +//! capsules_extra::symmetric_encryption::aes::DRIVER_NUM, +//! aes_driver_device, +//! ) +//! .finalize(components::aes_driver_component_static!( +//! capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM< +//! 'static, +//! nrf52840::aes::AesECB<'static>, +//! > +//! )); +//! ``` + +use core::mem::MaybeUninit; +use kernel::capabilities; +use kernel::component::Component; +use kernel::create_capability; +use kernel::hil; +use kernel::hil::symmetric_encryption::{ + AES128Ctr, AES128, AES128CBC, AES128CCM, AES128ECB, AES128GCM, +}; + +const CRYPT_SIZE: usize = 7 * hil::symmetric_encryption::AES128_BLOCK_SIZE; + +#[macro_export] +macro_rules! aes_virtual_component_static { + ($A:ty $(,)?) => {{ + const CRYPT_SIZE: usize = 7 * kernel::hil::symmetric_encryption::AES128_BLOCK_SIZE; + let virtual_aes = kernel::static_buf!( + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, $A> + ); + let crypt_buf = kernel::static_buf!([u8; CRYPT_SIZE]); + + (virtual_aes, crypt_buf) + };}; +} + +#[macro_export] +macro_rules! aes_driver_component_static { + ($A:ty $(,)?) => {{ + const CRYPT_SIZE: usize = 7 * kernel::hil::symmetric_encryption::AES128_BLOCK_SIZE; + let aes_src_buffer = kernel::static_buf!([u8; 16]); + let aes_dst_buffer = kernel::static_buf!([u8; CRYPT_SIZE]); + let aes_driver = + kernel::static_buf!(capsules_extra::symmetric_encryption::aes::AesDriver<'static, $A>); + + (aes_driver, aes_src_buffer, aes_dst_buffer) + };}; +} + +pub struct AesVirtualComponent + AES128Ctr + AES128CBC + AES128ECB> { + aes_mux: &'static capsules_core::virtualizers::virtual_aes_ccm::MuxAES128CCM<'static, A>, +} + +impl + AES128Ctr + AES128CBC + AES128ECB> AesVirtualComponent { + pub fn new( + aes_mux: &'static capsules_core::virtualizers::virtual_aes_ccm::MuxAES128CCM<'static, A>, + ) -> Self { + Self { aes_mux } + } +} + +impl + AES128Ctr + AES128CBC + AES128ECB> Component + for AesVirtualComponent +{ + type StaticInput = ( + &'static mut MaybeUninit< + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, A>, + >, + &'static mut MaybeUninit<[u8; CRYPT_SIZE]>, + ); + type Output = + &'static capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, A>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let crypt_buf = static_buffer.1.write([0; CRYPT_SIZE]); + let aes_ccm = static_buffer.0.write( + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM::new( + self.aes_mux, + crypt_buf, + ), + ); + aes_ccm.setup(); + + aes_ccm + } +} + +pub struct AesDriverComponent + AES128CCM<'static> + 'static> { + board_kernel: &'static kernel::Kernel, + driver_num: usize, + aes: &'static A, +} + +impl + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'static>> + AesDriverComponent +{ + pub fn new( + board_kernel: &'static kernel::Kernel, + driver_num: usize, + aes: &'static A, + ) -> AesDriverComponent { + AesDriverComponent { + board_kernel, + driver_num, + aes, + } + } +} + +impl< + A: AES128<'static> + + AES128Ctr + + AES128CBC + + AES128ECB + + AES128CCM<'static> + + AES128GCM<'static>, + > Component for AesDriverComponent +{ + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; 16]>, + &'static mut MaybeUninit<[u8; CRYPT_SIZE]>, + ); + type Output = &'static capsules_extra::symmetric_encryption::aes::AesDriver<'static, A>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + let aes_src_buf = static_buffer.1.write([0; 16]); + let aes_dst_buf = static_buffer.2.write([0; CRYPT_SIZE]); + + let aes_driver = + static_buffer + .0 + .write(capsules_extra::symmetric_encryption::aes::AesDriver::new( + self.aes, + aes_src_buf, + aes_dst_buf, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); + + hil::symmetric_encryption::AES128CCM::set_client(self.aes, aes_driver); + hil::symmetric_encryption::AES128::set_client(self.aes, aes_driver); + + aes_driver + } +} diff --git a/boards/components/src/air_quality.rs b/boards/components/src/air_quality.rs new file mode 100644 index 0000000000..e946493668 --- /dev/null +++ b/boards/components/src/air_quality.rs @@ -0,0 +1,63 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Component for any air quality sensor. +//! +//! Usage +//! ----- +//! ```rust +//! let temp = AirQualityComponent::new(board_kernel, nrf52::temperature::TEMP) +//! .finalize(air_quality_component_static!()); +//! ``` + +use capsules_extra::air_quality::AirQualitySensor; +use core::mem::MaybeUninit; +use kernel::capabilities; +use kernel::component::Component; +use kernel::create_capability; +use kernel::hil; + +#[macro_export] +macro_rules! air_quality_component_static { + () => {{ + kernel::static_buf!(capsules_extra::air_quality::AirQualitySensor<'static>) + };}; +} + +pub struct AirQualityComponent> { + board_kernel: &'static kernel::Kernel, + driver_num: usize, + temp_sensor: &'static T, +} + +impl> AirQualityComponent { + pub fn new( + board_kernel: &'static kernel::Kernel, + driver_num: usize, + temp_sensor: &'static T, + ) -> AirQualityComponent { + AirQualityComponent { + board_kernel, + driver_num, + temp_sensor, + } + } +} + +impl> Component for AirQualityComponent { + type StaticInput = &'static mut MaybeUninit>; + type Output = &'static AirQualitySensor<'static>; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + + let air_quality = s.write(AirQualitySensor::new( + self.temp_sensor, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); + + hil::sensors::AirQualityDriver::set_client(self.temp_sensor, air_quality); + air_quality + } +} diff --git a/boards/components/src/alarm.rs b/boards/components/src/alarm.rs index 9a113ebc25..e224cd82f5 100644 --- a/boards/components/src/alarm.rs +++ b/boards/components/src/alarm.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for hardware timer Alarms. //! //! This provides two components, `AlarmMuxComponent`, which provides a @@ -9,10 +13,10 @@ //! ```rust //! let ast = &sam4l::ast::AST; //! let mux_alarm = components::alarm::AlarmMuxComponent::new(ast) -//! .finalize(components::alarm_mux_component_helper!(sam4l::ast::Ast)); +//! .finalize(components::alarm_mux_component_static!(sam4l::ast::Ast)); //! ast.configure(mux_alarm); //! let alarm = components::alarm::AlarmDriverComponent::new(board_kernel, mux_alarm) -//! .finalize(components::alarm_component_helper!(sam4l::ast::Ast)); +//! .finalize(components::alarm_component_static!(sam4l::ast::Ast)); //! ``` // Author: Philip Levis @@ -20,39 +24,44 @@ use core::mem::MaybeUninit; -use capsules::alarm::AlarmDriver; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::alarm::AlarmDriver; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; use kernel::hil::time::{self, Alarm}; -use kernel::static_init_half; // Setup static space for the objects. #[macro_export] -macro_rules! alarm_mux_component_helper { +macro_rules! alarm_mux_component_static { ($A:ty $(,)?) => {{ - use capsules::virtual_alarm::MuxAlarm; - use core::mem::MaybeUninit; - static mut BUF: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF + kernel::static_buf!(capsules_core::virtualizers::virtual_alarm::MuxAlarm<'static, $A>) };}; } // Setup static space for the objects. #[macro_export] -macro_rules! alarm_component_helper { +macro_rules! alarm_component_static { ($A:ty $(,)?) => {{ - use capsules::alarm::AlarmDriver; - use capsules::virtual_alarm::VirtualMuxAlarm; - use core::mem::MaybeUninit; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF2: MaybeUninit>> = - MaybeUninit::uninit(); - (&mut BUF1, &mut BUF2) + let mux_alarm = kernel::static_buf!( + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A> + ); + let alarm_driver = kernel::static_buf!( + capsules_core::alarm::AlarmDriver< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A>, + > + ); + + (mux_alarm, alarm_driver) };}; } +pub type AlarmDriverComponentType = capsules_core::alarm::AlarmDriver< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, A>, +>; + pub struct AlarmMuxComponent> { alarm: &'static A, } @@ -67,12 +76,8 @@ impl> Component for AlarmMuxComponent { type StaticInput = &'static mut MaybeUninit>; type Output = &'static MuxAlarm<'static, A>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let mux_alarm = static_init_half!( - static_buffer, - MuxAlarm<'static, A>, - MuxAlarm::new(self.alarm) - ); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let mux_alarm = static_buffer.write(MuxAlarm::new(self.alarm)); self.alarm.set_alarm_client(mux_alarm); mux_alarm @@ -92,8 +97,8 @@ impl> AlarmDriverComponent { mux: &'static MuxAlarm<'static, A>, ) -> AlarmDriverComponent { AlarmDriverComponent { - board_kernel: board_kernel, - driver_num: driver_num, + board_kernel, + driver_num, alarm_mux: mux, } } @@ -106,24 +111,16 @@ impl> Component for AlarmDriverComponent { ); type Output = &'static AlarmDriver<'static, VirtualMuxAlarm<'static, A>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let virtual_alarm1 = static_init_half!( - static_buffer.0, - VirtualMuxAlarm<'static, A>, - VirtualMuxAlarm::new(self.alarm_mux) - ); + let virtual_alarm1 = static_buffer.0.write(VirtualMuxAlarm::new(self.alarm_mux)); virtual_alarm1.setup(); - let alarm = static_init_half!( - static_buffer.1, - AlarmDriver<'static, VirtualMuxAlarm<'static, A>>, - AlarmDriver::new( - virtual_alarm1, - self.board_kernel.create_grant(self.driver_num, &grant_cap) - ) - ); + let alarm = static_buffer.1.write(AlarmDriver::new( + virtual_alarm1, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); virtual_alarm1.set_alarm_client(alarm); alarm diff --git a/boards/components/src/analog_comparator.rs b/boards/components/src/analog_comparator.rs index 7c1bfae093..edfb4d8290 100644 --- a/boards/components/src/analog_comparator.rs +++ b/boards/components/src/analog_comparator.rs @@ -1,14 +1,18 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for initializing an Analog Comparator. //! -//! This provides one Component, AcComponent, which implements -//! a userspace syscall interface to a passed analog comparator driver. +//! This provides one Component, AcComponent, which implements a userspace +//! syscall interface to a passed analog comparator driver. //! //! Usage //! ----- //! ```rust -//! let analog_comparator = components::analog_comparator::AcComponent::new( +//! let analog_comparator = components::analog_comparator::AnalogComparatorComponent::new( //! &sam4l::acifc::ACIFC, -//! components::acomp_component_helper!( +//! components::analog_comparator_component_helper!( //! ::Channel, //! &sam4l::acifc::CHANNEL_AC0, //! &sam4l::acifc::CHANNEL_AC1, @@ -16,21 +20,17 @@ //! &sam4l::acifc::CHANNEL_AC3 //! ), //! ) -//! .finalize(components::acomp_component_buf!(sam4l::acifc::Acifc)); +//! .finalize(components::analog_comparator_component_static!(sam4l::acifc::Acifc)); //! ``` +use capsules_extra::analog_comparator::AnalogComparator; use core::mem::MaybeUninit; - -use kernel; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; -use kernel::static_init_half; - -use capsules::analog_comparator; #[macro_export] -macro_rules! acomp_component_helper { +macro_rules! analog_comparator_component_helper { ($Channel:ty, $($P:expr),+ $(,)?) => {{ use kernel::count_expressions; use kernel::static_init; @@ -46,23 +46,24 @@ macro_rules! acomp_component_helper { } #[macro_export] -macro_rules! acomp_component_buf { - ($Comp:ty $(,)?) => {{ - use capsules::analog_comparator::AnalogComparator; - use core::mem::MaybeUninit; - static mut BUF: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF +macro_rules! analog_comparator_component_static { + ($AC:ty $(,)?) => {{ + kernel::static_buf!(capsules_extra::analog_comparator::AnalogComparator<'static, $AC>) };}; } -pub struct AcComponent> { +pub struct AnalogComparatorComponent< + AC: 'static + kernel::hil::analog_comparator::AnalogComparator<'static>, +> { comp: &'static AC, ac_channels: &'static [&'static AC::Channel], board_kernel: &'static kernel::Kernel, driver_num: usize, } -impl> AcComponent { +impl> + AnalogComparatorComponent +{ pub fn new( comp: &'static AC, ac_channels: &'static [&'static AC::Channel], @@ -79,20 +80,17 @@ impl> Ac } impl> Component - for AcComponent + for AnalogComparatorComponent { - type StaticInput = &'static mut MaybeUninit>; - type Output = &'static analog_comparator::AnalogComparator<'static, AC>; + type StaticInput = &'static mut MaybeUninit>; + type Output = &'static AnalogComparator<'static, AC>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); let grant_ac = self.board_kernel.create_grant(self.driver_num, &grant_cap); - let analog_comparator = static_init_half!( - static_buffer, - analog_comparator::AnalogComparator<'static, AC>, - analog_comparator::AnalogComparator::new(self.comp, self.ac_channels, grant_ac) - ); + let analog_comparator = + static_buffer.write(AnalogComparator::new(self.comp, self.ac_channels, grant_ac)); self.comp.set_client(analog_comparator); analog_comparator diff --git a/boards/components/src/apds9960.rs b/boards/components/src/apds9960.rs new file mode 100644 index 0000000000..9e82606974 --- /dev/null +++ b/boards/components/src/apds9960.rs @@ -0,0 +1,71 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Component for APDS9960 proximity sensor. + +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_extra::apds9960::APDS9960; +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::hil::gpio; +use kernel::hil::i2c; + +#[macro_export] +macro_rules! apds9960_component_static { + ($I:ty $(,)?) => {{ + let i2c_device = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>); + let apds9960 = kernel::static_buf!( + capsules_extra::apds9960::APDS9960< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>, + > + ); + let buffer = kernel::static_buf!([u8; capsules_extra::apds9960::BUF_LEN]); + + (i2c_device, apds9960, buffer) + };}; +} + +pub struct Apds9960Component> { + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, + interrupt_pin: &'static dyn gpio::InterruptPin<'static>, +} + +impl> Apds9960Component { + pub fn new( + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, + interrupt_pin: &'static dyn gpio::InterruptPin<'static>, + ) -> Self { + Apds9960Component { + i2c_mux, + i2c_address, + interrupt_pin, + } + } +} + +impl> Component for Apds9960Component { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit>>, + &'static mut MaybeUninit<[u8; capsules_extra::apds9960::BUF_LEN]>, + ); + type Output = &'static APDS9960<'static, I2CDevice<'static, I>>; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let apds9960_i2c = s.0.write(I2CDevice::new(self.i2c_mux, self.i2c_address)); + + let buffer = s.2.write([0; capsules_extra::apds9960::BUF_LEN]); + + let apds9960 = + s.1.write(APDS9960::new(apds9960_i2c, self.interrupt_pin, buffer)); + apds9960_i2c.set_client(apds9960); + self.interrupt_pin.set_client(apds9960); + + apds9960 + } +} diff --git a/boards/components/src/app_flash_driver.rs b/boards/components/src/app_flash_driver.rs index 61d1543d57..60a805294d 100644 --- a/boards/components/src/app_flash_driver.rs +++ b/boards/components/src/app_flash_driver.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for any App Flash Driver. //! //! Usage @@ -5,45 +9,37 @@ //! ```rust //! let app_flash = //! components::app_flash_driver::AppFlashComponent::new(board_kernel, &base_peripherals.nvmc) -//! .finalize(components::app_flash_component_helper!( +//! .finalize(components::app_flash_component_static!( //! nrf52833::nvmc::Nvmc, //! 512 //! )); //! ``` -use capsules::app_flash_driver::AppFlash; -use capsules::nonvolatile_to_pages::NonvolatileToPages; +use capsules_extra::app_flash_driver::AppFlash; +use capsules_extra::nonvolatile_to_pages::NonvolatileToPages; use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; use kernel::hil; use kernel::hil::nonvolatile_storage::NonvolatileStorage; -use kernel::static_init_half; #[macro_export] -macro_rules! app_flash_component_helper { +macro_rules! app_flash_component_static { ($F:ty, $buffer_size: literal) => {{ - static mut BUFFER: [u8; $buffer_size] = [0; $buffer_size]; - use capsules::app_flash_driver::AppFlash; - use capsules::nonvolatile_to_pages::NonvolatileToPages; - use core::mem::MaybeUninit; - use kernel::hil; - static mut page_buffer: MaybeUninit<<$F as hil::flash::Flash>::Page> = - MaybeUninit::uninit(); - static mut nv_to_page: MaybeUninit> = MaybeUninit::uninit(); - static mut app_flash: MaybeUninit> = MaybeUninit::uninit(); - ( - &mut BUFFER, - &mut page_buffer, - &mut nv_to_page, - &mut app_flash, - ) + let buffer = kernel::static_buf!([u8; $buffer_size]); + let page_buffer = kernel::static_buf!(<$F as kernel::hil::flash::Flash>::Page); + let nv_to_page = kernel::static_buf!( + capsules_extra::nonvolatile_to_pages::NonvolatileToPages<'static, $F> + ); + let app_flash = kernel::static_buf!(capsules_extra::app_flash_driver::AppFlash<'static>); + (buffer, page_buffer, nv_to_page, app_flash) };}; } pub struct AppFlashComponent< F: 'static + hil::flash::Flash + hil::flash::HasClient<'static, NonvolatileToPages<'static, F>>, + const BUF_LEN: usize, > { board_kernel: &'static kernel::Kernel, driver_num: usize, @@ -54,13 +50,14 @@ impl< F: 'static + hil::flash::Flash + hil::flash::HasClient<'static, NonvolatileToPages<'static, F>>, - > AppFlashComponent + const BUF_LEN: usize, + > AppFlashComponent { pub fn new( board_kernel: &'static kernel::Kernel, driver_num: usize, storage: &'static F, - ) -> AppFlashComponent { + ) -> AppFlashComponent { AppFlashComponent { board_kernel, driver_num, @@ -73,41 +70,38 @@ impl< F: 'static + hil::flash::Flash + hil::flash::HasClient<'static, NonvolatileToPages<'static, F>>, - > Component for AppFlashComponent + const BUF_LEN: usize, + > Component for AppFlashComponent { type StaticInput = ( - &'static mut [u8], + &'static mut MaybeUninit<[u8; BUF_LEN]>, &'static mut MaybeUninit<::Page>, &'static mut MaybeUninit>, &'static mut MaybeUninit>, ); type Output = &'static AppFlash<'static>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let flash_pagebuffer = static_init_half!( - static_buffer.1, - ::Page, - ::Page::default() - ); + let buffer = static_buffer.0.write([0; BUF_LEN]); - let nv_to_page = static_init_half!( - static_buffer.2, - NonvolatileToPages<'static, F>, - NonvolatileToPages::new(self.storage, flash_pagebuffer) - ); + let flash_pagebuffer = static_buffer + .1 + .write(::Page::default()); + + let nv_to_page = static_buffer + .2 + .write(NonvolatileToPages::new(self.storage, flash_pagebuffer)); self.storage.set_client(nv_to_page); - let app_flash = static_init_half!( - static_buffer.3, - capsules::app_flash_driver::AppFlash<'static>, - capsules::app_flash_driver::AppFlash::new( + let app_flash = static_buffer + .3 + .write(capsules_extra::app_flash_driver::AppFlash::new( nv_to_page, self.board_kernel.create_grant(self.driver_num, &grant_cap), - static_buffer.0 - ) - ); + buffer, + )); nv_to_page.set_client(app_flash); diff --git a/boards/components/src/appid/assigner_name.rs b/boards/components/src/appid/assigner_name.rs new file mode 100644 index 0000000000..4059b43b11 --- /dev/null +++ b/boards/components/src/appid/assigner_name.rs @@ -0,0 +1,48 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Components for AppID assigners based on names. + +use core::mem::MaybeUninit; +use kernel::component::Component; + +#[macro_export] +macro_rules! appid_assigner_names_component_static { + () => {{ + kernel::static_buf!( + capsules_system::process_checker::basic::AppIdAssignerNames u32> + ) + };}; +} + +pub struct AppIdAssignerNamesComponent {} + +impl AppIdAssignerNamesComponent { + pub fn new() -> Self { + Self {} + } +} + +impl Component for AppIdAssignerNamesComponent { + type StaticInput = &'static mut MaybeUninit< + capsules_system::process_checker::basic::AppIdAssignerNames< + 'static, + fn(&'static str) -> u32, + >, + >; + + type Output = &'static capsules_system::process_checker::basic::AppIdAssignerNames< + 'static, + fn(&'static str) -> u32, + >; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + s.write( + capsules_system::process_checker::basic::AppIdAssignerNames::new( + &((|s| kernel::utilities::helpers::crc32_posix(s.as_bytes())) + as fn(&'static str) -> u32), + ), + ) + } +} diff --git a/boards/components/src/appid/assigner_tbf.rs b/boards/components/src/appid/assigner_tbf.rs new file mode 100644 index 0000000000..be98673601 --- /dev/null +++ b/boards/components/src/appid/assigner_tbf.rs @@ -0,0 +1,34 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Components for AppID assigners based on TBF headers. + +use core::mem::MaybeUninit; +use kernel::component::Component; + +#[macro_export] +macro_rules! appid_assigner_tbf_header_component_static { + () => {{ + kernel::static_buf!(capsules_system::process_checker::tbf::AppIdAssignerTbfHeader) + };}; +} + +pub struct AppIdAssignerTbfHeaderComponent {} + +impl AppIdAssignerTbfHeaderComponent { + pub fn new() -> Self { + Self {} + } +} + +impl Component for AppIdAssignerTbfHeaderComponent { + type StaticInput = + &'static mut MaybeUninit; + + type Output = &'static capsules_system::process_checker::tbf::AppIdAssignerTbfHeader; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + s.write(capsules_system::process_checker::tbf::AppIdAssignerTbfHeader {}) + } +} diff --git a/boards/components/src/appid/checker.rs b/boards/components/src/appid/checker.rs new file mode 100644 index 0000000000..1c57686695 --- /dev/null +++ b/boards/components/src/appid/checker.rs @@ -0,0 +1,40 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Component for creating a process checking machine. + +use core::mem::MaybeUninit; +use kernel::component::Component; + +#[macro_export] +macro_rules! process_checker_machine_component_static { + () => {{ + kernel::static_buf!(kernel::process::ProcessCheckerMachine) + };}; +} + +pub type ProcessCheckerMachineComponentType = kernel::process::ProcessCheckerMachine; + +pub struct ProcessCheckerMachineComponent { + policy: &'static dyn kernel::process_checker::AppCredentialsPolicy<'static>, +} + +impl ProcessCheckerMachineComponent { + pub fn new(policy: &'static dyn kernel::process_checker::AppCredentialsPolicy) -> Self { + Self { policy } + } +} + +impl Component for ProcessCheckerMachineComponent { + type StaticInput = &'static mut MaybeUninit; + + type Output = &'static kernel::process::ProcessCheckerMachine; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let checker = s.write(kernel::process::ProcessCheckerMachine::new(self.policy)); + + self.policy.set_client(checker); + checker + } +} diff --git a/boards/components/src/appid/checker_null.rs b/boards/components/src/appid/checker_null.rs new file mode 100644 index 0000000000..721c9e34f5 --- /dev/null +++ b/boards/components/src/appid/checker_null.rs @@ -0,0 +1,36 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Component for creating a NULL process checking machine that approves all +//! processes. + +use core::mem::MaybeUninit; +use kernel::component::Component; + +#[macro_export] +macro_rules! app_checker_null_component_static { + () => {{ + kernel::static_buf!(capsules_system::process_checker::basic::AppCheckerNull) + };}; +} + +pub type AppCheckerNullComponentType = capsules_system::process_checker::basic::AppCheckerNull; + +pub struct AppCheckerNullComponent {} + +impl AppCheckerNullComponent { + pub fn new() -> Self { + Self {} + } +} + +impl Component for AppCheckerNullComponent { + type StaticInput = + &'static mut MaybeUninit; + type Output = &'static capsules_system::process_checker::basic::AppCheckerNull; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + s.write(capsules_system::process_checker::basic::AppCheckerNull::new()) + } +} diff --git a/boards/components/src/appid/checker_sha.rs b/boards/components/src/appid/checker_sha.rs new file mode 100644 index 0000000000..b9bab6c5de --- /dev/null +++ b/boards/components/src/appid/checker_sha.rs @@ -0,0 +1,59 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Components for SHA-based credential checkers. + +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::hil::digest; + +#[macro_export] +macro_rules! app_checker_sha256_component_static { + () => {{ + let buffer = kernel::static_buf!([u8; 32]); + let checker = + kernel::static_buf!(capsules_system::process_checker::basic::AppCheckerSha256); + + (checker, buffer) + };}; +} + +pub type AppCheckerSha256ComponentType = capsules_system::process_checker::basic::AppCheckerSha256; + +pub struct AppCheckerSha256Component> { + sha: &'static S, +} + +impl> AppCheckerSha256Component { + pub fn new(sha: &'static S) -> Self { + Self { sha } + } +} + +impl< + S: kernel::hil::digest::Sha256 + + 'static + + digest::Digest<'static, 32> + + kernel::hil::digest::DigestDataVerify<'static, 32>, + > Component for AppCheckerSha256Component +{ + type StaticInput = ( + &'static mut MaybeUninit, + &'static mut MaybeUninit<[u8; 32]>, + ); + + type Output = &'static capsules_system::process_checker::basic::AppCheckerSha256; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let buffer = s.1.write([0; 32]); + + let checker = s.0.write( + capsules_system::process_checker::basic::AppCheckerSha256::new(self.sha, buffer), + ); + + digest::Digest::set_client(self.sha, checker); + + checker + } +} diff --git a/boards/components/src/appid/mod.rs b/boards/components/src/appid/mod.rs new file mode 100644 index 0000000000..6bd2004270 --- /dev/null +++ b/boards/components/src/appid/mod.rs @@ -0,0 +1,9 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +pub mod assigner_name; +pub mod assigner_tbf; +pub mod checker; +pub mod checker_null; +pub mod checker_sha; diff --git a/boards/components/src/ble.rs b/boards/components/src/ble.rs new file mode 100644 index 0000000000..7a98983570 --- /dev/null +++ b/boards/components/src/ble.rs @@ -0,0 +1,116 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Component for creating a ble_advertising_driver. +//! +//! Usage +//! ----- +//! ```rust +//! let ble_radio = BLEComponent::new(board_kernel, &nrf52::ble_radio::RADIO, mux_alarm).finalize(); +//! ``` + +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; +use core::mem::MaybeUninit; +use kernel::capabilities; +use kernel::component::Component; +use kernel::create_capability; +use kernel::hil::ble_advertising::BleConfig; +use kernel::hil::time::Alarm; + +#[macro_export] +macro_rules! ble_component_static { + ($A:ty, $B:ty $(,)?) => {{ + let alarm = kernel::static_buf!( + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A> + ); + let ble = kernel::static_buf!( + capsules_extra::ble_advertising_driver::BLE< + 'static, + $B, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A>, + > + ); + let buffer = + kernel::static_buf!([u8; capsules_extra::ble_advertising_driver::PACKET_LENGTH]); + (alarm, ble, buffer) + }}; +} + +pub struct BLEComponent< + A: kernel::hil::time::Alarm<'static> + 'static, + B: kernel::hil::ble_advertising::BleAdvertisementDriver<'static> + BleConfig + 'static, +> { + board_kernel: &'static kernel::Kernel, + driver_num: usize, + radio: &'static B, + mux_alarm: &'static capsules_core::virtualizers::virtual_alarm::MuxAlarm<'static, A>, +} + +impl< + A: kernel::hil::time::Alarm<'static> + 'static, + B: kernel::hil::ble_advertising::BleAdvertisementDriver<'static> + BleConfig + 'static, + > BLEComponent +{ + pub fn new( + board_kernel: &'static kernel::Kernel, + driver_num: usize, + radio: &'static B, + mux_alarm: &'static capsules_core::virtualizers::virtual_alarm::MuxAlarm<'static, A>, + ) -> Self { + Self { + board_kernel, + driver_num, + radio, + mux_alarm, + } + } +} + +impl< + A: kernel::hil::time::Alarm<'static> + 'static, + B: kernel::hil::ble_advertising::BleAdvertisementDriver<'static> + BleConfig + 'static, + > Component for BLEComponent +{ + type StaticInput = ( + &'static mut MaybeUninit< + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, A>, + >, + &'static mut MaybeUninit< + capsules_extra::ble_advertising_driver::BLE<'static, B, VirtualMuxAlarm<'static, A>>, + >, + &'static mut MaybeUninit<[u8; capsules_extra::ble_advertising_driver::PACKET_LENGTH]>, + ); + type Output = &'static capsules_extra::ble_advertising_driver::BLE< + 'static, + B, + VirtualMuxAlarm<'static, A>, + >; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + + let ble_radio_virtual_alarm = s.0.write( + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm::new(self.mux_alarm), + ); + ble_radio_virtual_alarm.setup(); + let buffer = + s.2.write([0; capsules_extra::ble_advertising_driver::PACKET_LENGTH]); + + let ble_radio = s.1.write(capsules_extra::ble_advertising_driver::BLE::new( + self.radio, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + buffer, + ble_radio_virtual_alarm, + )); + kernel::hil::ble_advertising::BleAdvertisementDriver::set_receive_client( + self.radio, ble_radio, + ); + kernel::hil::ble_advertising::BleAdvertisementDriver::set_transmit_client( + self.radio, ble_radio, + ); + ble_radio_virtual_alarm.set_alarm_client(ble_radio); + + ble_radio + } +} diff --git a/boards/components/src/bme280.rs b/boards/components/src/bme280.rs new file mode 100644 index 0000000000..1c527409dd --- /dev/null +++ b/boards/components/src/bme280.rs @@ -0,0 +1,84 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Components for the BME280 Humidity, Pressure and Temperature Sensor. +//! +//! Usage +//! ----- +//! ```rust +//! let bme280 = +//! Bme280Component::new(mux_i2c, 0x77).finalize(components::bme280_component_static!()); +//! let temperature = components::temperature::TemperatureComponent::new( +//! board_kernel, +//! capsules_extra::temperature::DRIVER_NUM, +//! bme280, +//! ) +//! .finalize(components::temperature_component_static!()); +//! let humidity = components::humidity::HumidityComponent::new( +//! board_kernel, +//! capsules_extra::humidity::DRIVER_NUM, +//! bme280, +//! ) +//! .finalize(components::humidity_component_static!()); +//! ``` + +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_extra::bme280::Bme280; +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::hil::i2c; + +// Setup static space for the objects. +#[macro_export] +macro_rules! bme280_component_static { + ($I:ty $(,)?) => {{ + let i2c_device = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>); + let i2c_buffer = kernel::static_buf!([u8; 26]); + let bme280 = kernel::static_buf!( + capsules_extra::bme280::Bme280< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>, + > + ); + + (i2c_device, i2c_buffer, bme280) + };}; +} + +pub type Bme280ComponentType = capsules_extra::bme280::Bme280<'static, I>; + +pub struct Bme280Component> { + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, +} + +impl> Bme280Component { + pub fn new(i2c: &'static MuxI2C<'static, I>, i2c_address: u8) -> Self { + Bme280Component { + i2c_mux: i2c, + i2c_address, + } + } +} + +impl> Component for Bme280Component { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; 26]>, + &'static mut MaybeUninit>>, + ); + type Output = &'static Bme280<'static, I2CDevice<'static, I>>; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let bme280_i2c = s.0.write(I2CDevice::new(self.i2c_mux, self.i2c_address)); + let i2c_buffer = s.1.write([0; 26]); + + let bme280 = s.2.write(Bme280::new(bme280_i2c, i2c_buffer)); + + bme280_i2c.set_client(bme280); + bme280.startup(); + bme280 + } +} diff --git a/boards/components/src/bmm150.rs b/boards/components/src/bmm150.rs new file mode 100644 index 0000000000..39b2beee4f --- /dev/null +++ b/boards/components/src/bmm150.rs @@ -0,0 +1,73 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Component for the BMM150 Magnetometer Sensor. +//! +//! +//! Usage +//! ----- +//! ```rust +//! let BMM150 = BMM150Component::new(mux_i2c, 0x10).finalize( +//! components::bmm150_component_static!(nrf5240::i2c::TWI)); +//! let ninedof = components::ninedof::NineDofComponent::new(board_kernel) +//! .finalize(components::ninedof_component_static!(BMM150)); +//! ``` + +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_extra::bmm150::BMM150; +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::hil::i2c; + +// Setup static space for the objects. +#[macro_export] +macro_rules! bmm150_component_static { + ($I:ty $(,)?) => {{ + let i2c_device = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>); + let buffer = kernel::static_buf!([u8; 8]); + let bmm150 = kernel::static_buf!( + capsules_extra::bmm150::BMM150< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>, + > + ); + + (i2c_device, buffer, bmm150) + };}; +} + +pub struct BMM150Component> { + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, +} + +impl> BMM150Component { + pub fn new(i2c: &'static MuxI2C<'static, I>, i2c_address: u8) -> Self { + BMM150Component { + i2c_mux: i2c, + i2c_address, + } + } +} + +impl> Component for BMM150Component { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; 8]>, + &'static mut MaybeUninit>>, + ); + type Output = &'static BMM150<'static, I2CDevice<'static, I>>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let bmm150_i2c = static_buffer + .0 + .write(I2CDevice::new(self.i2c_mux, self.i2c_address)); + let buffer = static_buffer.1.write([0; 8]); + let bmm150 = static_buffer.2.write(BMM150::new(buffer, bmm150_i2c)); + + bmm150_i2c.set_client(bmm150); + bmm150 + } +} diff --git a/boards/components/src/bmp280.rs b/boards/components/src/bmp280.rs new file mode 100644 index 0000000000..bbea16bd95 --- /dev/null +++ b/boards/components/src/bmp280.rs @@ -0,0 +1,107 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Component for the BMP280 Temperature and Pressure Sensor. +//! +//! Based off the SHT3x code. +//! +//! I2C Interface +//! +//! Usage +//! ----- +//! +//! With the default i2c address +//! ```rust +//! let bmp280 = components::bmp280::Bmp280Component::new(sensors_i2c_bus, mux_alarm).finalize( +//! components::bmp280_component_static!(nrf52::rtc::Rtc<'static>), +//! ); +//! bmp280.begin_reset(); +//! ``` +//! +//! With a specified i2c address +//! ```rust +//! let bmp280 = components::bmp280::Bmp280Component::new(sensors_i2c_bus, mux_alarm).finalize( +//! components::bmp280_component_static!(nrf52::rtc::Rtc<'static>, capsules_extra::bmp280::BASE_ADDR), +//! ); +//! bmp280.begin_reset(); +//! ``` + +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_extra::bmp280::Bmp280; +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::hil::i2c; +use kernel::hil::time::Alarm; + +#[macro_export] +macro_rules! bmp280_component_static { + ($A:ty, $I:ty $(,)?) => {{ + let i2c_device = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>); + let alarm = kernel::static_buf!( + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A> + ); + let buffer = kernel::static_buf!([u8; capsules_extra::bmp280::BUFFER_SIZE]); + let bmp280 = kernel::static_buf!( + capsules_extra::bmp280::Bmp280< + 'static, + VirtualMuxAlarm<'static, $A>, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>, + > + ); + + (i2c_device, alarm, buffer, bmp280) + };}; +} + +pub type Bmp280ComponentType = capsules_extra::bmp280::Bmp280<'static, A, I>; + +pub struct Bmp280Component, I: 'static + i2c::I2CMaster<'static>> { + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, + alarm_mux: &'static MuxAlarm<'static, A>, +} + +impl, I: 'static + i2c::I2CMaster<'static>> Bmp280Component { + pub fn new( + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, + alarm_mux: &'static MuxAlarm<'static, A>, + ) -> Bmp280Component { + Bmp280Component { + i2c_mux, + i2c_address, + alarm_mux, + } + } +} + +impl, I: 'static + i2c::I2CMaster<'static>> Component + for Bmp280Component +{ + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; capsules_extra::bmp280::BUFFER_SIZE]>, + &'static mut MaybeUninit< + Bmp280<'static, VirtualMuxAlarm<'static, A>, I2CDevice<'static, I>>, + >, + ); + type Output = &'static Bmp280<'static, VirtualMuxAlarm<'static, A>, I2CDevice<'static, I>>; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let bmp280_i2c = s.0.write(I2CDevice::new(self.i2c_mux, self.i2c_address)); + let bmp280_alarm = s.1.write(VirtualMuxAlarm::new(self.alarm_mux)); + bmp280_alarm.setup(); + + let buffer = s.2.write([0; capsules_extra::bmp280::BUFFER_SIZE]); + + let bmp280 = s.3.write(Bmp280::new(bmp280_i2c, buffer, bmp280_alarm)); + bmp280_i2c.set_client(bmp280); + bmp280_alarm.set_alarm_client(bmp280); + + bmp280 + } +} diff --git a/boards/components/src/bus.rs b/boards/components/src/bus.rs index d9652f7f6a..6be6b7dd7f 100644 --- a/boards/components/src/bus.rs +++ b/boards/components/src/bus.rs @@ -1,121 +1,122 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Bus Components for Intel8080 Parallel Interface, I2C, SPI //! //! Example //! //! Intel 8080 Parallel Interface //! -//! let bus = components::bus::Bus8080BusComponent::new().finalize( -//! components::bus8080_bus_component_helper!( -//! // bus type -//! stm32f412g::fsmc::Fsmc, -//! // bus -//! &stm32f412g::fsmc::FSMC -//! ), +//! let bus = components::bus::Bus8080BusComponent::new(&stm32f412g::fsmc::FSMC).finalize( +//! components::bus8080_bus_component_static!(stm32f412g::fsmc::Fsmc) //! ); //! //! SPI //! //! ```rust -//! let bus = components::bus::SpiMasterBusComponent::new().finalize( -//! components::spi_bus_component_helper!( -//! // spi type -//! nrf52840::spi::SPIM, -//! // chip select -//! &nrf52840::gpio::PORT[GPIO_D4], -//! // spi mux -//! spi_mux -//! ), +//! let bus = components::bus::SpiMasterBusComponent::new(spi_mux, +//! chip_select, +//! baud_rate, +//! clock_phase, +//! clock_polarity).finalize( +//! components::spi_bus_component_static!(nrf52840::spi::SPIM) //! ); //! ``` -use capsules::bus::{Bus8080Bus, I2CMasterBus, SpiMasterBus}; -use capsules::virtual_i2c::{I2CDevice, MuxI2C}; -use capsules::virtual_spi::VirtualSpiMasterDevice; -use core::marker::PhantomData; + +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_core::virtualizers::virtual_spi::MuxSpiMaster; +use capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice; +use capsules_extra::bus::{Bus8080Bus, I2CMasterBus, SpiMasterBus}; use core::mem::MaybeUninit; use kernel::component::Component; use kernel::hil::bus8080; +use kernel::hil::i2c; use kernel::hil::spi::{self, ClockPhase, ClockPolarity, SpiMasterDevice}; -use kernel::static_init_half; // Setup static space for the objects. #[macro_export] -macro_rules! bus8080_bus_component_helper { - ($B:ty, $bus8080:expr $(,)?) => {{ - use capsules::bus::Bus8080Bus; - use core::mem::{size_of, MaybeUninit}; - static mut bus: MaybeUninit> = MaybeUninit::uninit(); - ($bus8080, &mut bus) +macro_rules! bus8080_bus_component_static { + ($B:ty $(,)?) => {{ + kernel::static_buf!(capsules_extra::bus::Bus8080Bus<'static, $B>) };}; } #[macro_export] -macro_rules! spi_bus_component_helper { - ($S:ty, $select:expr, $spi_mux:expr $(,)?) => {{ - use capsules::bus::SpiMasterBus; - use capsules::virtual_spi::VirtualSpiMasterDevice; - use core::mem::{size_of, MaybeUninit}; - let bus_spi: &'static VirtualSpiMasterDevice<'static, $S> = - components::spi::SpiComponent::new($spi_mux, $select) - .finalize(components::spi_component_helper!($S)); - static mut ADDRESS_BUFFER: [u8; size_of::()] = [0; size_of::()]; - static mut bus: MaybeUninit>> = - MaybeUninit::uninit(); - (&bus_spi, &mut bus, &mut ADDRESS_BUFFER) +macro_rules! spi_bus_component_static { + ($S:ty $(,)?) => {{ + let spi = kernel::static_buf!( + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice<'static, $S> + ); + let address_buffer = kernel::static_buf!([u8; core::mem::size_of::()]); + let bus = kernel::static_buf!( + capsules_extra::bus::SpiMasterBus< + 'static, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice<'static, $S>, + > + ); + + (spi, bus, address_buffer) };}; } #[macro_export] -macro_rules! i2c_master_bus_component_helper { - () => {{ - use capsules::bus::I2CMasterBus; - use core::mem::{size_of, MaybeUninit}; - static mut ADDRESS_BUFFER: [u8; 1] = [0; 1]; - static mut bus: MaybeUninit> = MaybeUninit::uninit(); - (&mut bus, &mut ADDRESS_BUFFER) +macro_rules! i2c_master_bus_component_static { + ($D:ty $(,)?) => {{ + let address_buffer = kernel::static_buf!([u8; 1]); + let bus = kernel::static_buf!(capsules_extra::bus::I2CMasterBus<'static, $D>); + let i2c_device = kernel::static_buf!( + capsules_core::virtualizers::virtual_i2c::I2CDevice< + 'static, + capsules_extra::bus::I2CMasterBus<'static, $D>, + > + ); + + (bus, i2c_device, address_buffer) };}; } pub struct Bus8080BusComponent> { - _bus: PhantomData, + bus: &'static B, } impl> Bus8080BusComponent { - pub fn new() -> Bus8080BusComponent { - Bus8080BusComponent { _bus: PhantomData } + pub fn new(bus: &'static B) -> Bus8080BusComponent { + Bus8080BusComponent { bus } } } impl> Component for Bus8080BusComponent { - type StaticInput = (&'static B, &'static mut MaybeUninit>); + type StaticInput = &'static mut MaybeUninit>; type Output = &'static Bus8080Bus<'static, B>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let bus = static_init_half!( - static_buffer.1, - Bus8080Bus<'static, B>, - Bus8080Bus::new(static_buffer.0) - ); - static_buffer.0.set_client(bus); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let bus = static_buffer.write(Bus8080Bus::new(self.bus)); + self.bus.set_client(bus); bus } } -pub struct SpiMasterBusComponent { - _select: PhantomData, +pub struct SpiMasterBusComponent> { + spi_mux: &'static MuxSpiMaster<'static, S>, + chip_select: S::ChipSelect, baud_rate: u32, clock_phase: ClockPhase, clock_polarity: ClockPolarity, } -impl SpiMasterBusComponent { +impl> SpiMasterBusComponent { pub fn new( + spi_mux: &'static MuxSpiMaster<'static, S>, + chip_select: S::ChipSelect, baud_rate: u32, clock_phase: ClockPhase, clock_polarity: ClockPolarity, ) -> SpiMasterBusComponent { SpiMasterBusComponent { - _select: PhantomData, + spi_mux, + chip_select, baud_rate, clock_phase, clock_polarity, @@ -123,65 +124,62 @@ impl SpiMasterBusComponent { } } -impl Component for SpiMasterBusComponent { +impl> Component for SpiMasterBusComponent { type StaticInput = ( - &'static VirtualSpiMasterDevice<'static, S>, + &'static mut MaybeUninit>, &'static mut MaybeUninit>>, - &'static mut [u8], + &'static mut MaybeUninit<[u8; core::mem::size_of::()]>, ); type Output = &'static SpiMasterBus<'static, VirtualSpiMasterDevice<'static, S>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let spi_device = static_buffer + .0 + .write(VirtualSpiMasterDevice::new(self.spi_mux, self.chip_select)); + spi_device.setup(); + if let Err(error) = - static_buffer - .0 - .configure(self.clock_polarity, self.clock_phase, self.baud_rate) + spi_device.configure(self.clock_polarity, self.clock_phase, self.baud_rate) { panic!("Failed to setup SPI Bus ({:?})", error); } - let bus = static_init_half!( - static_buffer.1, - SpiMasterBus<'static, VirtualSpiMasterDevice<'static, S>>, - SpiMasterBus::new(static_buffer.0, static_buffer.2) - ); - static_buffer.0.set_client(bus); + + let buffer = static_buffer.2.write([0; core::mem::size_of::()]); + + let bus = static_buffer.1.write(SpiMasterBus::new(spi_device, buffer)); + spi_device.set_client(bus); bus } } -pub struct I2CMasterBusComponent { - i2c_mux: &'static MuxI2C<'static>, +pub struct I2CMasterBusComponent> { + i2c_mux: &'static MuxI2C<'static, I>, address: u8, } -impl I2CMasterBusComponent { - pub fn new(i2c_mux: &'static MuxI2C<'static>, address: u8) -> I2CMasterBusComponent { - I2CMasterBusComponent { - i2c_mux: i2c_mux, - address: address, - } +impl> I2CMasterBusComponent { + pub fn new(i2c_mux: &'static MuxI2C<'static, I>, address: u8) -> I2CMasterBusComponent { + I2CMasterBusComponent { i2c_mux, address } } } -impl Component for I2CMasterBusComponent { +impl> Component for I2CMasterBusComponent { type StaticInput = ( - &'static mut MaybeUninit>>, - &'static mut [u8], + &'static mut MaybeUninit>>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; 1]>, ); - type Output = &'static I2CMasterBus<'static, I2CDevice<'static>>; + type Output = &'static I2CMasterBus<'static, I2CDevice<'static, I>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let bus_i2c: &'static I2CDevice<'static> = - crate::i2c::I2CComponent::new(self.i2c_mux, self.address) - .finalize(crate::i2c_component_helper!()); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let i2c_device = static_buffer + .1 + .write(I2CDevice::new(self.i2c_mux, self.address)); + let buffer = static_buffer.2.write([0; 1]); - let bus = static_init_half!( - static_buffer.0, - I2CMasterBus<'static, I2CDevice<'static>>, - I2CMasterBus::new(bus_i2c, static_buffer.1) - ); - bus_i2c.set_client(bus); + let bus = static_buffer.0.write(I2CMasterBus::new(i2c_device, buffer)); + i2c_device.set_client(bus); bus } diff --git a/boards/components/src/button.rs b/boards/components/src/button.rs index c202dec966..2d1dfb9887 100644 --- a/boards/components/src/button.rs +++ b/boards/components/src/button.rs @@ -1,13 +1,16 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for Buttons. //! //! Usage //! ----- //! -//! The `button_component_helper!` macro takes 'static references to GPIO -//! pins. When GPIO instances are owned values, the -//! `button_component_helper_owned!` can be used, indicating that the passed -//! values are owned values. This macro will perform static allocation of the -//! passed in GPIO pins internally. +//! The `button_component_helper!` macro takes 'static references to GPIO pins. +//! When GPIO instances are owned values, the `button_component_helper_owned!` +//! can be used, indicating that the passed values are owned values. This macro +//! will perform static allocation of the passed in GPIO pins internally. //! //! ```rust //! let button = components::button::ButtonComponent::new( @@ -21,22 +24,21 @@ //! ) //! ), //! ) -//! .finalize(button_component_buf!(sam4l::gpio::GPIOPin)); +//! .finalize(button_component_static!(sam4l::gpio::GPIOPin)); //! ``` //! -//! Typically, `ActivationMode::ActiveLow` will be associated with `FloatingState::PullUp` -//! whereas `ActivationMode::ActiveHigh` will be paired with `FloatingState::PullDown`. -//! `FloatingState::None` will be used when the board provides external pull-up/pull-down -//! resistors. +//! Typically, `ActivationMode::ActiveLow` will be associated with +//! `FloatingState::PullUp` whereas `ActivationMode::ActiveHigh` will be paired +//! with `FloatingState::PullDown`. `FloatingState::None` will be used when the +//! board provides external pull-up/pull-down resistors. -use capsules::button::Button; +use capsules_core::button::Button; use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; use kernel::hil::gpio; use kernel::hil::gpio::InterruptWithValue; -use kernel::static_init_half; #[macro_export] macro_rules! button_component_helper_owned { @@ -76,15 +78,14 @@ macro_rules! button_component_helper { } #[macro_export] -macro_rules! button_component_buf { +macro_rules! button_component_static { ($Pin:ty $(,)?) => {{ - use capsules::button::Button; - use core::mem::MaybeUninit; - static mut BUF: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF + kernel::static_buf!(capsules_core::button::Button<'static, $Pin>) };}; } +pub type ButtonComponentType = capsules_core::button::Button<'static, IP>; + pub struct ButtonComponent> { board_kernel: &'static kernel::Kernel, driver_num: usize, @@ -106,7 +107,7 @@ impl> ButtonComponent { )], ) -> Self { Self { - board_kernel: board_kernel, + board_kernel, driver_num, button_pins, } @@ -117,16 +118,12 @@ impl> Component for ButtonComponent>; type Output = &'static Button<'static, IP>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let button = static_init_half!( - static_buffer, - capsules::button::Button<'static, IP>, - capsules::button::Button::new( - self.button_pins, - self.board_kernel.create_grant(self.driver_num, &grant_cap) - ) - ); + let button = static_buffer.write(capsules_core::button::Button::new( + self.button_pins, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); for (pin, _, _) in self.button_pins.iter() { pin.set_client(button); } diff --git a/boards/components/src/can.rs b/boards/components/src/can.rs new file mode 100644 index 0000000000..691ac4f3a4 --- /dev/null +++ b/boards/components/src/can.rs @@ -0,0 +1,91 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022 +// Copyright OxidOS Automotive SRL 2022 +// +// Author: Teona Severin + +//! Component for CAN syscall interface. +//! +//! This provides one Component, `CanComponent`, which implements a +//! userspace syscall interface to the Can peripheral. +//! +//! Usage +//! ----- +//! ```rust +//! let can = components::can::CanComponent::new( +//! board_kernel, +//! capsules_extra::can::DRIVER_NUM, +//! &peripherals.can1 +//! ).finalize(components::can_component_static!( +//! stm32f429zi::can::Can<'static> +//! )); +//! ``` +//! + +use capsules_extra::can::CanCapsule; +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::hil::can; +use kernel::{capabilities, create_capability}; + +#[macro_export] +macro_rules! can_component_static { + ($C:ty $(,)?) => {{ + use capsules_extra::can::CanCapsule; + use core::mem::MaybeUninit; + use kernel::hil::can; + use kernel::static_buf; + + let CAN_TX_BUF = static_buf!([u8; can::STANDARD_CAN_PACKET_SIZE]); + let CAN_RX_BUF = static_buf!([u8; can::STANDARD_CAN_PACKET_SIZE]); + let can = static_buf!(capsules_extra::can::CanCapsule<'static, $C>); + (can, CAN_TX_BUF, CAN_RX_BUF) + };}; +} + +pub struct CanComponent { + board_kernel: &'static kernel::Kernel, + driver_num: usize, + can: &'static A, +} + +impl CanComponent { + pub fn new( + board_kernel: &'static kernel::Kernel, + driver_num: usize, + can: &'static A, + ) -> CanComponent { + CanComponent { + board_kernel, + driver_num, + can, + } + } +} + +impl Component for CanComponent { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; can::STANDARD_CAN_PACKET_SIZE]>, + &'static mut MaybeUninit<[u8; can::STANDARD_CAN_PACKET_SIZE]>, + ); + type Output = &'static CanCapsule<'static, A>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + let grant_can = self.board_kernel.create_grant(self.driver_num, &grant_cap); + + let can = static_buffer.0.write(capsules_extra::can::CanCapsule::new( + self.can, + grant_can, + static_buffer.1.write([0; can::STANDARD_CAN_PACKET_SIZE]), + static_buffer.2.write([0; can::STANDARD_CAN_PACKET_SIZE]), + )); + can::Controller::set_client(self.can, Some(can)); + can::Transmit::set_client(self.can, Some(can)); + can::Receive::set_client(self.can, Some(can)); + + can + } +} diff --git a/boards/components/src/ccs811.rs b/boards/components/src/ccs811.rs new file mode 100644 index 0000000000..c794b43ba6 --- /dev/null +++ b/boards/components/src/ccs811.rs @@ -0,0 +1,79 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Components for the BME280 Humidity, Pressure and Temperature Sensor. +//! +//! Usage +//! ----- +//! ```rust +//! let ccs811 = +//! Ccs811Component::new(mux_i2c, 0x77).finalize(components::ccs811_component_static!()); +//! let temperature = components::temperature::TemperatureComponent::new( +//! board_kernel, +//! capsules_extra::temperature::DRIVER_NUM, +//! ccs811, +//! ) +//! .finalize(()); +//! let humidity = components::humidity::HumidityComponent::new( +//! board_kernel, +//! capsules_extra::humidity::DRIVER_NUM, +//! ccs811, +//! ) +//! .finalize(()); +//! ``` + +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_extra::ccs811::Ccs811; +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::hil::i2c; + +// Setup static space for the objects. +#[macro_export] +macro_rules! ccs811_component_static { + ($I:ty $(,)?) => {{ + let i2c_device = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>); + let buffer = kernel::static_buf!([u8; 6]); + let ccs811 = kernel::static_buf!(capsules_extra::ccs811::Ccs811<'static>); + + (i2c_device, buffer, ccs811) + };}; +} + +pub struct Ccs811Component> { + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, +} + +impl> Ccs811Component { + pub fn new(i2c: &'static MuxI2C<'static, I>, i2c_address: u8) -> Self { + Ccs811Component { + i2c_mux: i2c, + i2c_address, + } + } +} + +impl> Component for Ccs811Component { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; 6]>, + &'static mut MaybeUninit>, + ); + type Output = &'static Ccs811<'static>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let ccs811_i2c = static_buffer + .0 + .write(I2CDevice::new(self.i2c_mux, self.i2c_address)); + let buffer = static_buffer.1.write([0; 6]); + let ccs811 = static_buffer.2.write(Ccs811::new(ccs811_i2c, buffer)); + kernel::deferred_call::DeferredCallClient::register(ccs811); + + ccs811_i2c.set_client(ccs811); + ccs811.startup(); + ccs811 + } +} diff --git a/boards/components/src/cdc.rs b/boards/components/src/cdc.rs index db8e4eb4fd..1d1698408a 100644 --- a/boards/components/src/cdc.rs +++ b/boards/components/src/cdc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for CDC-ACM over USB support. //! //! This provides a component for using the CDC-ACM driver. This allows for @@ -13,33 +17,36 @@ //! ]; //! let cdc_acm = components::cdc::CdcAcmComponent::new( //! &nrf52::usbd::USBD, -//! capsules::usb::usbc_client::MAX_CTRL_PACKET_SIZE_NRF52840, +//! capsules_extra::usb::usbc_client::MAX_CTRL_PACKET_SIZE_NRF52840, //! 0x2341, //! 0x005a, //! STRINGS) -//! .finalize(components::usb_cdc_acm_component_helper!(nrf52::usbd::Usbd)); +//! .finalize(components::cdc_acm_component_static!(nrf52::usbd::Usbd)); //! ``` use core::mem::MaybeUninit; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; use kernel::component::Component; -use kernel::dynamic_deferred_call::DynamicDeferredCall; use kernel::hil; use kernel::hil::time::Alarm; -use kernel::static_init_half; // Setup static space for the objects. #[macro_export] -macro_rules! usb_cdc_acm_component_helper { +macro_rules! cdc_acm_component_static { ($U:ty, $A:ty $(,)?) => {{ - use capsules::virtual_alarm::VirtualMuxAlarm; - use core::mem::MaybeUninit; - static mut BUF0: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF1: MaybeUninit< - capsules::usb::cdc::CdcAcm<'static, $U, VirtualMuxAlarm<'static, $A>>, - > = MaybeUninit::uninit(); - (&mut BUF0, &mut BUF1) + let alarm = kernel::static_buf!( + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A> + ); + let cdc = kernel::static_buf!( + capsules_extra::usb::cdc::CdcAcm< + 'static, + $U, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A>, + > + ); + + (alarm, cdc) };}; } @@ -53,7 +60,6 @@ pub struct CdcAcmComponent< product_id: u16, strings: &'static [&'static str; 3], alarm_mux: &'static MuxAlarm<'static, A>, - deferred_caller: &'static DynamicDeferredCall, host_initiated_function: Option<&'static (dyn Fn() + 'static)>, } @@ -67,7 +73,6 @@ impl, A: 'static + Alarm<'static>> product_id: u16, strings: &'static [&'static str; 3], alarm_mux: &'static MuxAlarm<'static, A>, - deferred_caller: &'static DynamicDeferredCall, host_initiated_function: Option<&'static (dyn Fn() + 'static)>, ) -> Self { Self { @@ -77,7 +82,6 @@ impl, A: 'static + Alarm<'static>> product_id, strings, alarm_mux, - deferred_caller, host_initiated_function, } } @@ -89,37 +93,27 @@ impl, A: 'static + Alarm<'static>> type StaticInput = ( &'static mut MaybeUninit>, &'static mut MaybeUninit< - capsules::usb::cdc::CdcAcm<'static, U, VirtualMuxAlarm<'static, A>>, + capsules_extra::usb::cdc::CdcAcm<'static, U, VirtualMuxAlarm<'static, A>>, >, ); - type Output = &'static capsules::usb::cdc::CdcAcm<'static, U, VirtualMuxAlarm<'static, A>>; + type Output = + &'static capsules_extra::usb::cdc::CdcAcm<'static, U, VirtualMuxAlarm<'static, A>>; - unsafe fn finalize(self, s: Self::StaticInput) -> Self::Output { - let cdc_alarm = static_init_half!( - s.0, - VirtualMuxAlarm<'static, A>, - VirtualMuxAlarm::new(self.alarm_mux) - ); + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let cdc_alarm = s.0.write(VirtualMuxAlarm::new(self.alarm_mux)); cdc_alarm.setup(); - let cdc = static_init_half!( - s.1, - capsules::usb::cdc::CdcAcm<'static, U, VirtualMuxAlarm<'static, A>>, - capsules::usb::cdc::CdcAcm::new( - self.usb, - self.max_ctrl_packet_size, - self.vendor_id, - self.product_id, - self.strings, - cdc_alarm, - self.deferred_caller, - self.host_initiated_function, - ) - ); + let cdc = s.1.write(capsules_extra::usb::cdc::CdcAcm::new( + self.usb, + self.max_ctrl_packet_size, + self.vendor_id, + self.product_id, + self.strings, + cdc_alarm, + self.host_initiated_function, + )); + kernel::deferred_call::DeferredCallClient::register(cdc); self.usb.set_client(cdc); - cdc.initialize_callback_handle( - self.deferred_caller.register(cdc).unwrap(), // Unwrap fail = no deferred call slot available for USB-CDC - ); cdc_alarm.set_alarm_client(cdc); cdc diff --git a/boards/components/src/console.rs b/boards/components/src/console.rs index ab259e21be..97d20fa376 100644 --- a/boards/components/src/console.rs +++ b/boards/components/src/console.rs @@ -1,70 +1,96 @@ -//! Components for Console, the generic serial interface, and for multiplexed access -//! to UART. +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Components for Console and ConsoleOrdered. These are two +//! alternative implementations of the serial console system call +//! interface. Console allows prints of arbitrary length but does not +//! have ordering or atomicity guarantees. ConsoleOrdered, in +//! contrast, has limits on the maximum lengths of prints but provides +//! a temporal ordering and ensures a print is atomic at least up to +//! particular length (typically 200 bytes). Console is useful when +//! userspace is printing large messages. ConsoleOrdered is useful +//! when you are debugging and there are inter-related messages from +//! the kernel and userspace, whose ordering is important to maintain. //! //! -//! This provides two Components, `ConsoleComponent`, which implements a buffered -//! read/write console over a serial port, and `UartMuxComponent`, which provides -//! multiplexed access to hardware UART. As an example, the serial port used for -//! console on Imix is typically USART3 (the DEBUG USB connector). +//! This provides three Components, `ConsoleComponent` and +//! `ConsoleOrderedComponent`, which implement a buffered read/write +//! console over a serial port, and `UartMuxComponent`, which provides +//! multiplexed access to hardware UART. As an example, the serial +//! port used for console on Imix is typically USART3 (the DEBUG USB +//! connector). //! //! Usage //! ----- //! ```rust //! let uart_mux = UartMuxComponent::new(&sam4l::usart::USART3, //! 115200, -//! deferred_caller).finalize(()); -//! let console = ConsoleComponent::new(board_kernel, uart_mux).finalize(()); +//! deferred_caller).finalize(components::uart_mux_component_static!()); +//! let console = ConsoleComponent::new(board_kernel, uart_mux) +//! .finalize(console_component_static!()); //! ``` // Author: Philip Levis -// Last modified: 1/08/2020 +// Last modified: 1/08/2023 + +use capsules_core::console; +use capsules_core::console_ordered::ConsoleOrdered; -use capsules::console; -use capsules::virtual_uart::{MuxUart, UartDevice}; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_uart::{MuxUart, UartDevice}; +use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; -use kernel::dynamic_deferred_call::DynamicDeferredCall; use kernel::hil; +use kernel::hil::time::{self, Alarm}; use kernel::hil::uart; -use kernel::static_init; -pub struct UartMuxComponent { +use capsules_core::console::DEFAULT_BUF_SIZE; + +#[macro_export] +macro_rules! uart_mux_component_static { + // Common logic for both branches + ($rx_buffer_len: expr) => {{ + use capsules_core::virtualizers::virtual_uart::MuxUart; + use kernel::static_buf; + let uart_mux = static_buf!(MuxUart<'static>); + let rx_buf = static_buf!([u8; $rx_buffer_len]); + (uart_mux, rx_buf) + }}; + () => { + $crate::uart_mux_component_static!(capsules_core::virtualizers::virtual_uart::RX_BUF_LEN); + }; + ($rx_buffer_len: literal) => { + $crate::uart_mux_component_static!($rx_buffer_len); + }; +} + +pub struct UartMuxComponent { uart: &'static dyn uart::Uart<'static>, baud_rate: u32, - deferred_caller: &'static DynamicDeferredCall, } -impl UartMuxComponent { +impl UartMuxComponent { pub fn new( uart: &'static dyn uart::Uart<'static>, baud_rate: u32, - deferred_caller: &'static DynamicDeferredCall, - ) -> UartMuxComponent { - UartMuxComponent { - uart, - baud_rate, - deferred_caller, - } + ) -> UartMuxComponent { + UartMuxComponent { uart, baud_rate } } } -impl Component for UartMuxComponent { - type StaticInput = (); +impl Component for UartMuxComponent { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; RX_BUF_LEN]>, + ); type Output = &'static MuxUart<'static>; - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { - let uart_mux = static_init!( - MuxUart<'static>, - MuxUart::new( - self.uart, - &mut capsules::virtual_uart::RX_BUF, - self.baud_rate, - self.deferred_caller, - ) - ); - uart_mux.initialize_callback_handle( - self.deferred_caller.register(uart_mux).unwrap(), // Unwrap fail = no deferred call slot available for uart mux - ); + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let rx_buf = s.1.write([0; RX_BUF_LEN]); + let uart_mux = s.0.write(MuxUart::new(self.uart, rx_buf, self.baud_rate)); + kernel::deferred_call::DeferredCallClient::register(uart_mux); uart_mux.initialize(); hil::uart::Transmit::set_transmit_client(self.uart, uart_mux); @@ -74,49 +100,157 @@ impl Component for UartMuxComponent { } } -pub struct ConsoleComponent { +#[macro_export] +macro_rules! console_component_static { + // Common logic for both branches + ($rx_buffer_len: expr, $tx_buffer_len: expr) => {{ + use capsules_core::console::{Console, DEFAULT_BUF_SIZE}; + use capsules_core::virtualizers::virtual_uart::UartDevice; + use kernel::static_buf; + let read_buf = static_buf!([u8; $rx_buffer_len]); + let write_buf = static_buf!([u8; $tx_buffer_len]); + // Create virtual device for console. + let console_uart = static_buf!(UartDevice); + let console = static_buf!(Console<'static>); + (write_buf, read_buf, console_uart, console) + }}; + () => { + $crate::console_component_static!(DEFAULT_BUF_SIZE, DEFAULT_BUF_SIZE); + }; + ($rx_buffer_len: literal, $tx_buffer_len: literal) => { + $crate::console_component_static!($rx_buffer_len, $tx_buffer_len); + }; +} + +pub struct ConsoleComponent { board_kernel: &'static kernel::Kernel, driver_num: usize, uart_mux: &'static MuxUart<'static>, } -impl ConsoleComponent { +impl ConsoleComponent { pub fn new( board_kernel: &'static kernel::Kernel, driver_num: usize, uart_mux: &'static MuxUart, - ) -> ConsoleComponent { + ) -> ConsoleComponent { ConsoleComponent { - board_kernel: board_kernel, - driver_num: driver_num, - uart_mux: uart_mux, + board_kernel, + driver_num, + uart_mux, } } } -impl Component for ConsoleComponent { - type StaticInput = (); +impl Component + for ConsoleComponent +{ + type StaticInput = ( + &'static mut MaybeUninit<[u8; TX_BUF_LEN]>, + &'static mut MaybeUninit<[u8; RX_BUF_LEN]>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit>, + ); type Output = &'static console::Console<'static>; - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { + fn finalize(self, s: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - // Create virtual device for console. - let console_uart = static_init!(UartDevice, UartDevice::new(self.uart_mux, true)); + let write_buffer = s.0.write([0; TX_BUF_LEN]); + + let read_buffer = s.1.write([0; RX_BUF_LEN]); + + let console_uart = s.2.write(UartDevice::new(self.uart_mux, true)); console_uart.setup(); - let console = static_init!( - console::Console<'static>, - console::Console::new( - console_uart, - &mut console::WRITE_BUF, - &mut console::READ_BUF, - self.board_kernel.create_grant(self.driver_num, &grant_cap) - ) - ); + let console = s.3.write(console::Console::new( + console_uart, + write_buffer, + read_buffer, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); hil::uart::Transmit::set_transmit_client(console_uart, console); hil::uart::Receive::set_receive_client(console_uart, console); console } } +#[macro_export] +macro_rules! console_ordered_component_static { + ($A:ty $(,)?) => {{ + let mux_alarm = kernel::static_buf!(VirtualMuxAlarm<'static, $A>); + let read_buf = static_buf!([u8; capsules_core::console::DEFAULT_BUF_SIZE]); + let console_uart = + kernel::static_buf!(capsules_core::virtualizers::virtual_uart::UartDevice); + let console = kernel::static_buf!(ConsoleOrdered<'static, VirtualMuxAlarm<'static, $A>>); + (mux_alarm, read_buf, console_uart, console) + };}; +} + +pub struct ConsoleOrderedComponent> { + board_kernel: &'static kernel::Kernel, + driver_num: usize, + uart_mux: &'static MuxUart<'static>, + alarm_mux: &'static MuxAlarm<'static, A>, + atomic_size: usize, + retry_timer: u32, + write_timer: u32, +} + +impl> ConsoleOrderedComponent { + pub fn new( + board_kernel: &'static kernel::Kernel, + driver_num: usize, + uart_mux: &'static MuxUart<'static>, + alarm_mux: &'static MuxAlarm<'static, A>, + atomic_size: usize, + retry_timer: u32, + write_timer: u32, + ) -> ConsoleOrderedComponent { + ConsoleOrderedComponent { + board_kernel, + driver_num, + uart_mux, + alarm_mux, + atomic_size, + retry_timer, + write_timer, + } + } +} + +impl> Component for ConsoleOrderedComponent { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; DEFAULT_BUF_SIZE]>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit>>, + ); + type Output = &'static ConsoleOrdered<'static, VirtualMuxAlarm<'static, A>>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + + let virtual_alarm1 = static_buffer.0.write(VirtualMuxAlarm::new(self.alarm_mux)); + virtual_alarm1.setup(); + + let read_buffer = static_buffer.1.write([0; DEFAULT_BUF_SIZE]); + + let console_uart = static_buffer.2.write(UartDevice::new(self.uart_mux, true)); + console_uart.setup(); + + let console = static_buffer.3.write(ConsoleOrdered::new( + console_uart, + virtual_alarm1, + read_buffer, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + self.atomic_size, + self.retry_timer, + self.write_timer, + )); + + virtual_alarm1.set_alarm_client(console); + hil::uart::Receive::set_receive_client(console_uart, console); + console + } +} diff --git a/boards/components/src/crc.rs b/boards/components/src/crc.rs index 6321665d5a..e1b382aa9f 100644 --- a/boards/components/src/crc.rs +++ b/boards/components/src/crc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for Crc syscall interface. //! //! This provides one Component, `CrcComponent`, which implements a @@ -7,30 +11,28 @@ //! ----- //! ```rust //! let crc = components::crc::CrcComponent::new(board_kernel, &sam4l::crccu::CrcCU) -//! .finalize(components::crc_component_helper!(sam4l::crccu::Crccu)); +//! .finalize(components::crc_component_static!(sam4l::crccu::Crccu)); //! ``` // Author: Philip Levis // Author: Leon Schuermann // Last modified: 6/2/2021 +use capsules_extra::crc::CrcDriver; use core::mem::MaybeUninit; - -use capsules::crc; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; use kernel::hil::crc::Crc; -use kernel::{static_init, static_init_half}; // Setup static space for the objects. #[macro_export] -macro_rules! crc_component_helper { +macro_rules! crc_component_static { ($C:ty $(,)?) => {{ - use capsules::crc; - use core::mem::MaybeUninit; - static mut BUF: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF + let buffer = kernel::static_buf!([u8; capsules_extra::crc::DEFAULT_CRC_BUF_LENGTH]); + let crc = kernel::static_buf!(capsules_extra::crc::CrcDriver<'static, $C>); + + (crc, buffer) };}; } @@ -47,33 +49,31 @@ impl> CrcComponent { crc: &'static C, ) -> CrcComponent { CrcComponent { - board_kernel: board_kernel, - driver_num: driver_num, - crc: crc, + board_kernel, + driver_num, + crc, } } } impl> Component for CrcComponent { - type StaticInput = &'static mut MaybeUninit>; - type Output = &'static crc::CrcDriver<'static, C>; + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; capsules_extra::crc::DEFAULT_CRC_BUF_LENGTH]>, + ); + type Output = &'static CrcDriver<'static, C>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let crc_buf = static_init!( - [u8; crc::DEFAULT_CRC_BUF_LENGTH], - [0; crc::DEFAULT_CRC_BUF_LENGTH] - ); + let crc_buf = static_buffer + .1 + .write([0; capsules_extra::crc::DEFAULT_CRC_BUF_LENGTH]); - let crc = static_init_half!( - static_buffer, - crc::CrcDriver<'static, C>, - crc::CrcDriver::new( - self.crc, - crc_buf, - self.board_kernel.create_grant(self.driver_num, &grant_cap) - ) - ); + let crc = static_buffer.0.write(CrcDriver::new( + self.crc, + crc_buf, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); self.crc.set_client(crc); diff --git a/boards/components/src/ctap.rs b/boards/components/src/ctap.rs index 87b4ded9ff..f048674a32 100644 --- a/boards/components/src/ctap.rs +++ b/boards/components/src/ctap.rs @@ -1,7 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for CTAP HID over USB support. //! //! This provides a component for using the CTAP driver. This allows for -//! Client to Authenticator Protool Authentication +//! Client to Authenticator Protocol Authentication. //! //! Usage //! ----- @@ -11,8 +15,6 @@ //! "FIDO Key", // Product //! "Serial No. 5", // Serial number //! ]; -//! let ctap_send_buffer = static_init!([u8; 64], [0; 64]); -//! let ctap_recv_buffer = static_init!([u8; 64], [0; 64]); //! //! let (ctap, ctap_driver) = components::ctap::CtapComponent::new( //! &earlgrey::usbdev::USB, @@ -23,7 +25,7 @@ //! ctap_send_buffer, //! ctap_recv_buffer, //! ) -//! .finalize(components::usb_ctap_component_helper!(lowrisc::usbdev::Usb)); +//! .finalize(components::ctap_component_static!(lowrisc::usbdev::Usb)); //! //! ctap.enable(); //! ctap.attach(); @@ -34,19 +36,22 @@ use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; use kernel::hil; -use kernel::static_init_half; // Setup static space for the objects. #[macro_export] -macro_rules! usb_ctap_component_helper { +macro_rules! ctap_component_static { ($U:ty $(,)?) => {{ - use core::mem::MaybeUninit; - static mut BUF1: MaybeUninit> = - MaybeUninit::uninit(); - static mut BUF2: MaybeUninit< - capsules::ctap::CtapDriver<'static, capsules::usb::ctap::CtapHid<'static, $U>>, - > = MaybeUninit::uninit(); - (&mut BUF1, &mut BUF2) + let hid = kernel::static_buf!(capsules_extra::usb::ctap::CtapHid<'static, $U>); + let driver = kernel::static_buf!( + capsules_extra::usb_hid_driver::UsbHidDriver< + 'static, + capsules_extra::usb::usb_hid_driver::UsbHidDriver<'static, $U>, + > + ); + let send_buffer = kernel::static_buf!([u8; 64]); + let recv_buffer = kernel::static_buf!([u8; 64]); + + (hid, driver, send_buffer, recv_buffer) };}; } @@ -57,8 +62,6 @@ pub struct CtapComponent> { vendor_id: u16, product_id: u16, strings: &'static [&'static str; 3], - send_buffer: &'static mut [u8; 64], - recv_buffer: &'static mut [u8; 64], } impl> CtapComponent { @@ -69,8 +72,6 @@ impl> CtapComponent { vendor_id: u16, product_id: u16, strings: &'static [&'static str; 3], - send_buffer: &'static mut [u8; 64], - recv_buffer: &'static mut [u8; 64], ) -> CtapComponent { CtapComponent { board_kernel, @@ -79,49 +80,50 @@ impl> CtapComponent { vendor_id, product_id, strings, - send_buffer, - recv_buffer, } } } impl> Component for CtapComponent { type StaticInput = ( - &'static mut MaybeUninit>, + &'static mut MaybeUninit>, &'static mut MaybeUninit< - capsules::ctap::CtapDriver<'static, capsules::usb::ctap::CtapHid<'static, U>>, + capsules_extra::usb_hid_driver::UsbHidDriver< + 'static, + capsules_extra::usb::ctap::CtapHid<'static, U>, + >, >, + &'static mut MaybeUninit<[u8; 64]>, + &'static mut MaybeUninit<[u8; 64]>, ); type Output = ( - &'static capsules::usb::ctap::CtapHid<'static, U>, - &'static capsules::ctap::CtapDriver<'static, capsules::usb::ctap::CtapHid<'static, U>>, + &'static capsules_extra::usb::ctap::CtapHid<'static, U>, + &'static capsules_extra::usb_hid_driver::UsbHidDriver< + 'static, + capsules_extra::usb::ctap::CtapHid<'static, U>, + >, ); - unsafe fn finalize(self, s: Self::StaticInput) -> Self::Output { - let ctap = static_init_half!( - s.0, - capsules::usb::ctap::CtapHid<'static, U>, - capsules::usb::ctap::CtapHid::new( - self.usb, - self.vendor_id, - self.product_id, - self.strings - ) - ); + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let ctap = s.0.write(capsules_extra::usb::ctap::CtapHid::new( + self.usb, + self.vendor_id, + self.product_id, + self.strings, + )); self.usb.set_client(ctap); let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let ctap_driver = static_init_half!( - s.1, - capsules::ctap::CtapDriver<'static, capsules::usb::ctap::CtapHid<'static, U>>, - capsules::ctap::CtapDriver::new( - Some(ctap), - self.send_buffer, - self.recv_buffer, - self.board_kernel.create_grant(self.driver_num, &grant_cap), - ) - ); + let send_buffer = s.2.write([0; 64]); + let recv_buffer = s.3.write([0; 64]); + + let ctap_driver = s.1.write(capsules_extra::usb_hid_driver::UsbHidDriver::new( + Some(ctap), + send_buffer, + recv_buffer, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); ctap.set_client(ctap_driver); diff --git a/boards/components/src/dac.rs b/boards/components/src/dac.rs new file mode 100644 index 0000000000..2f152fcc78 --- /dev/null +++ b/boards/components/src/dac.rs @@ -0,0 +1,43 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Component for Digital to Analog Converters (DAC). +//! +//! Usage +//! ----- +//! ```rust +//! let dac = components::dac::DacComponent::new(&peripherals.dac) +//! .finalize(components::dac_component_static!()); +//! ``` + +use capsules_extra::dac::Dac; +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::hil; + +#[macro_export] +macro_rules! dac_component_static { + () => {{ + kernel::static_buf!(capsules_extra::dac::Dac<'static>) + };}; +} + +pub struct DacComponent { + dac: &'static dyn hil::dac::DacChannel, +} + +impl DacComponent { + pub fn new(dac: &'static dyn hil::dac::DacChannel) -> Self { + Self { dac } + } +} + +impl Component for DacComponent { + type StaticInput = &'static mut MaybeUninit>; + type Output = &'static Dac<'static>; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + s.write(Dac::new(self.dac)) + } +} diff --git a/boards/components/src/date_time.rs b/boards/components/src/date_time.rs new file mode 100644 index 0000000000..ce2277ac16 --- /dev/null +++ b/boards/components/src/date_time.rs @@ -0,0 +1,73 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Component for Date and Time initialisation. +//! +//! Authors: Irina Bradu +//! Remus Rughinis +//! +//! Usage +//! ----- +//! +//! '''rust +//! let date_time = components::date_time::DateTimeComponent::new( +//! board_kernel, +//! capsules_extra::date_time::DRIVER_NUM, +//! &peripherals.rtc, +//! ) +//! .finalize(rtc_component_static!(stm32f429zi::rtc::Rtc<'static>)); +//! ''' + +use core::mem::MaybeUninit; + +use capsules_extra::date_time::DateTimeCapsule; +use kernel::capabilities; +use kernel::component::Component; +use kernel::create_capability; +use kernel::hil::date_time; + +#[macro_export] +macro_rules! date_time_component_static { + ($R:ty $(,)?) => {{ + let rtc = kernel::static_buf!(capsules_extra::date_time::DateTimeCapsule<'static, $R>); + (rtc) + };}; +} + +pub struct DateTimeComponent> { + board_kernel: &'static kernel::Kernel, + driver_num: usize, + rtc: &'static D, +} + +impl> DateTimeComponent { + pub fn new( + board_kernel: &'static kernel::Kernel, + driver_num: usize, + rtc: &'static D, + ) -> DateTimeComponent { + DateTimeComponent { + board_kernel, + driver_num, + rtc, + } + } +} + +impl + kernel::deferred_call::DeferredCallClient> + Component for DateTimeComponent +{ + type StaticInput = &'static mut MaybeUninit>; + + type Output = &'static DateTimeCapsule<'static, D>; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let grant_dt = create_capability!(capabilities::MemoryAllocationCapability); + let grant_date_time = self.board_kernel.create_grant(self.driver_num, &grant_dt); + + let date_time = s.write(DateTimeCapsule::new(self.rtc, grant_date_time)); + date_time::DateTime::set_client(self.rtc, date_time); + date_time + } +} diff --git a/boards/components/src/debug_queue.rs b/boards/components/src/debug_queue.rs index ff574eca5f..9e6df24e43 100644 --- a/boards/components/src/debug_queue.rs +++ b/boards/components/src/debug_queue.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for DebugQueue, the implementation for `debug_enqueue!` and //! `debug_flush_queue!`. //! @@ -9,41 +13,53 @@ //! Usage //! ----- //! ```rust -//! let buf = static_init!([u8; 1024], [0; 1024]); -//! DebugQueueComponent::new(buf).finalize(()); +//! DebugQueueComponent::new().finalize(components::debug_queue_component_static!()); //! ``` // Author: Guillaume Endignoux // Last modified: 05 Mar 2020 +use core::mem::MaybeUninit; use kernel::collections::ring_buffer::RingBuffer; use kernel::component::Component; -use kernel::static_init; -pub struct DebugQueueComponent { - buffer: &'static mut [u8], +#[macro_export] +macro_rules! debug_queue_component_static { + () => {{ + let ring = kernel::static_buf!(kernel::collections::ring_buffer::RingBuffer<'static, u8>); + let queue = kernel::static_buf!(kernel::debug::DebugQueue); + let wrapper = kernel::static_buf!(kernel::debug::DebugQueueWrapper); + let buffer = kernel::static_buf!([u8; 1024]); + + (ring, queue, wrapper, buffer) + };}; } +pub struct DebugQueueComponent {} + impl DebugQueueComponent { - pub fn new(buffer: &'static mut [u8]) -> Self { - Self { buffer } + pub fn new() -> Self { + Self {} } } impl Component for DebugQueueComponent { - type StaticInput = (); + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit, + &'static mut MaybeUninit, + &'static mut MaybeUninit<[u8; 1024]>, + ); type Output = (); - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { - let ring_buffer = static_init!(RingBuffer<'static, u8>, RingBuffer::new(self.buffer)); - let debug_queue = static_init!( - kernel::debug::DebugQueue, - kernel::debug::DebugQueue::new(ring_buffer) - ); - let debug_queue_wrapper = static_init!( - kernel::debug::DebugQueueWrapper, - kernel::debug::DebugQueueWrapper::new(debug_queue) - ); - kernel::debug::set_debug_queue(debug_queue_wrapper); + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let buffer = s.3.write([0; 1024]); + let ring_buffer = s.0.write(RingBuffer::new(buffer)); + let debug_queue = s.1.write(kernel::debug::DebugQueue::new(ring_buffer)); + let debug_queue_wrapper = + s.2.write(kernel::debug::DebugQueueWrapper::new(debug_queue)); + unsafe { + kernel::debug::set_debug_queue(debug_queue_wrapper); + } } } diff --git a/boards/components/src/debug_writer.rs b/boards/components/src/debug_writer.rs index 5665ff0ab3..e4ce2b863e 100644 --- a/boards/components/src/debug_writer.rs +++ b/boards/components/src/debug_writer.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for DebugWriter, the implementation for `debug!()`. //! //! This provides components for attaching the kernel debug output (for panic!, @@ -7,7 +11,7 @@ //! Usage //! ----- //! ```rust -//! DebugWriterComponent::new(uart_mux).finalize(()); +//! DebugWriterComponent::new(uart_mux).finalize(components::debug_writer_component_static!()); //! //! components::debug_writer::DebugWriterNoMuxComponent::new( //! &nrf52::uart::UARTE0, @@ -18,103 +22,160 @@ // Author: Brad Campbell // Last modified: 11/07/2019 -use capsules::virtual_uart::{MuxUart, UartDevice}; +use capsules_core::virtualizers::virtual_uart::{MuxUart, UartDevice}; +use core::mem::MaybeUninit; use kernel::capabilities; use kernel::collections::ring_buffer::RingBuffer; use kernel::component::Component; use kernel::hil; use kernel::hil::uart; -use kernel::static_init; // The sum of the output_buf and internal_buf is set to a multiple of 1024 bytes in order to avoid excessive // padding between kernel memory and application memory (which often needs to be aligned to at // least a 1 KiB boundary). This is not _semantically_ critical, but helps keep buffers on 1 KiB // boundaries in some cases. Of course, these definitions are only advisory, and individual boards // can choose to pass in their own buffers with different lengths. -const DEBUG_BUFFER_KBYTE: usize = 1; +pub const DEFAULT_DEBUG_BUFFER_KBYTE: usize = 2; // Bytes [0, DEBUG_BUFFER_SPLIT) are used for output_buf while bytes -// [DEBUG_BUFFER_SPLIT, DEBUG_BUFFER_KBYTE * 1024) are used for internal_buf. +// [DEBUG_BUFFER_SPLIT, DEFAULT_DEBUG_BUFFER_KBYTE * 1024) are used for internal_buf. const DEBUG_BUFFER_SPLIT: usize = 64; -pub struct DebugWriterComponent { +/// The optional argument to this macro allows boards to specify the size of the in-RAM +/// buffer used for storing debug messages. Increase this value to be able to send more debug +/// messages in quick succession. +#[macro_export] +macro_rules! debug_writer_component_static { + ($BUF_SIZE_KB:expr) => {{ + let uart = kernel::static_buf!(capsules_core::virtualizers::virtual_uart::UartDevice); + let ring = kernel::static_buf!(kernel::collections::ring_buffer::RingBuffer<'static, u8>); + let buffer = kernel::static_buf!([u8; 1024 * $BUF_SIZE_KB]); + let debug = kernel::static_buf!(kernel::debug::DebugWriter); + let debug_wrapper = kernel::static_buf!(kernel::debug::DebugWriterWrapper); + + (uart, ring, buffer, debug, debug_wrapper) + };}; + () => {{ + $crate::debug_writer_component_static!($crate::debug_writer::DEFAULT_DEBUG_BUFFER_KBYTE) + };}; +} + +/// The optional argument to this macro allows boards to specify the size of the in-RAM +/// buffer used for storing debug messages. Increase this value to be able to send more debug +/// messages in quick succession. +#[macro_export] +macro_rules! debug_writer_no_mux_component_static { + ($BUF_SIZE_KB:expr) => {{ + let ring = kernel::static_buf!(kernel::collections::ring_buffer::RingBuffer<'static, u8>); + let buffer = kernel::static_buf!([u8; 1024 * $BUF_SIZE_KB]); + let debug = kernel::static_buf!(kernel::debug::DebugWriter); + let debug_wrapper = kernel::static_buf!(kernel::debug::DebugWriterWrapper); + + (ring, buffer, debug, debug_wrapper) + };}; + () => {{ + use $crate::debug_writer::DEFAULT_DEBUG_BUFFER_KBYTE; + $crate::debug_writer_no_mux_component_static!(DEFAULT_DEBUG_BUFFER_KBYTE); + };}; +} + +pub struct DebugWriterComponent { uart_mux: &'static MuxUart<'static>, + marker: core::marker::PhantomData<[u8; BUF_SIZE_BYTES]>, } -impl DebugWriterComponent { - pub fn new(uart_mux: &'static MuxUart) -> DebugWriterComponent { - DebugWriterComponent { uart_mux: uart_mux } +impl DebugWriterComponent { + pub fn new(uart_mux: &'static MuxUart) -> Self { + Self { + uart_mux, + marker: core::marker::PhantomData, + } } } pub struct Capability; unsafe impl capabilities::ProcessManagementCapability for Capability {} -impl Component for DebugWriterComponent { - type StaticInput = (); +impl Component for DebugWriterComponent { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; BUF_SIZE_BYTES]>, + &'static mut MaybeUninit, + &'static mut MaybeUninit, + ); type Output = (); - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { - let buf = static_init!( - [u8; 1024 * DEBUG_BUFFER_KBYTE], - [0; 1024 * DEBUG_BUFFER_KBYTE] - ); + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let buf = s.2.write([0; BUF_SIZE_BYTES]); + let (output_buf, internal_buf) = buf.split_at_mut(DEBUG_BUFFER_SPLIT); // Create virtual device for kernel debug. - let debugger_uart = static_init!(UartDevice, UartDevice::new(self.uart_mux, false)); + let debugger_uart = s.0.write(UartDevice::new(self.uart_mux, false)); debugger_uart.setup(); - let ring_buffer = static_init!(RingBuffer<'static, u8>, RingBuffer::new(internal_buf)); - let debugger = static_init!( - kernel::debug::DebugWriter, - kernel::debug::DebugWriter::new(debugger_uart, output_buf, ring_buffer) - ); + let ring_buffer = s.1.write(RingBuffer::new(internal_buf)); + let debugger = s.3.write(kernel::debug::DebugWriter::new( + debugger_uart, + output_buf, + ring_buffer, + )); hil::uart::Transmit::set_transmit_client(debugger_uart, debugger); - let debug_wrapper = static_init!( - kernel::debug::DebugWriterWrapper, - kernel::debug::DebugWriterWrapper::new(debugger) - ); - kernel::debug::set_debug_writer_wrapper(debug_wrapper); + let debug_wrapper = s.4.write(kernel::debug::DebugWriterWrapper::new(debugger)); + unsafe { + kernel::debug::set_debug_writer_wrapper(debug_wrapper); + } } } -pub struct DebugWriterNoMuxComponent + uart::Transmit<'static> + 'static> { +pub struct DebugWriterNoMuxComponent< + U: uart::Uart<'static> + uart::Transmit<'static> + 'static, + const BUF_SIZE_BYTES: usize, +> { uart: &'static U, + marker: core::marker::PhantomData<[u8; BUF_SIZE_BYTES]>, } -impl + uart::Transmit<'static> + 'static> DebugWriterNoMuxComponent { +impl + uart::Transmit<'static> + 'static, const BUF_SIZE_BYTES: usize> + DebugWriterNoMuxComponent +{ pub fn new(uart: &'static U) -> Self { - Self { uart } + Self { + uart, + marker: core::marker::PhantomData, + } } } -impl + uart::Transmit<'static> + 'static> Component - for DebugWriterNoMuxComponent +impl + uart::Transmit<'static> + 'static, const BUF_SIZE_BYTES: usize> + Component for DebugWriterNoMuxComponent { - type StaticInput = (); + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; BUF_SIZE_BYTES]>, + &'static mut MaybeUninit, + &'static mut MaybeUninit, + ); type Output = (); - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { - let buf = static_init!( - [u8; 1024 * DEBUG_BUFFER_KBYTE], - [0; 1024 * DEBUG_BUFFER_KBYTE] - ); + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let buf = s.1.write([0; BUF_SIZE_BYTES]); let (output_buf, internal_buf) = buf.split_at_mut(DEBUG_BUFFER_SPLIT); // Create virtual device for kernel debug. - let ring_buffer = static_init!(RingBuffer<'static, u8>, RingBuffer::new(internal_buf)); - let debugger = static_init!( - kernel::debug::DebugWriter, - kernel::debug::DebugWriter::new(self.uart, output_buf, ring_buffer) - ); + let ring_buffer = s.0.write(RingBuffer::new(internal_buf)); + let debugger = s.2.write(kernel::debug::DebugWriter::new( + self.uart, + output_buf, + ring_buffer, + )); hil::uart::Transmit::set_transmit_client(self.uart, debugger); - let debug_wrapper = static_init!( - kernel::debug::DebugWriterWrapper, - kernel::debug::DebugWriterWrapper::new(debugger) - ); - kernel::debug::set_debug_writer_wrapper(debug_wrapper); + let debug_wrapper = s.3.write(kernel::debug::DebugWriterWrapper::new(debugger)); + unsafe { + kernel::debug::set_debug_writer_wrapper(debug_wrapper); + } let _ = self.uart.configure(uart::Parameters { baud_rate: 115200, diff --git a/boards/components/src/digest.rs b/boards/components/src/digest.rs deleted file mode 100644 index 6556ce5846..0000000000 --- a/boards/components/src/digest.rs +++ /dev/null @@ -1,129 +0,0 @@ -//! Components for collections of Digests. -//! -//! Usage -//! ----- -//! ```rust -//! let digest_data_buffer = static_init!([u8; 64], [0; 64]); -//! let digest_dest_buffer = static_init!([u8; 32], [0; 32]); -//! -//! let mux_digest = components::digest::DigestMuxComponent::new(&earlgrey::digest::Digest).finalize( -//! components::digest_mux_component_helper!(lowrisc::digest::Digest, [u8; 32]), -//! ); -//! -//! let digest = components::digest::DigestComponent::new( -//! board_kernel, -//! &mux_digest, -//! digest_data_buffer, -//! digest_dest_buffer, -//! ) -//! .finalize(components::digest_component_helper!( -//! lowrisc::digest::Digest, -//! [u8; 32] -//! )); -//! ``` - -use capsules; -use capsules::virtual_digest::MuxDigest; -use capsules::virtual_digest::VirtualMuxDigest; -use core::mem::MaybeUninit; -use kernel::component::Component; -use kernel::hil::digest; -use kernel::static_init_half; - -// Setup static space for the objects. -#[macro_export] -macro_rules! digest_mux_component_helper { - ($A:ty, $L:expr $(,)?) => {{ - use capsules::virtual_digest::MuxDigest; - use capsules::virtual_digest::VirtualMuxDigest; - use core::mem::MaybeUninit; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF1 - };}; -} - -pub struct DigestMuxComponent, const L: usize> { - digest: &'static A, -} - -impl, const L: usize> DigestMuxComponent { - pub fn new(digest: &'static A) -> DigestMuxComponent { - DigestMuxComponent { digest } - } -} - -impl< - A: 'static - + digest::Digest<'static, L> - + digest::HMACSha256 - + digest::HMACSha384 - + digest::HMACSha512 - + digest::Sha256 - + digest::Sha384 - + digest::Sha512, - const L: usize, - > Component for DigestMuxComponent -{ - type StaticInput = &'static mut MaybeUninit>; - type Output = &'static MuxDigest<'static, A, L>; - - unsafe fn finalize(self, s: Self::StaticInput) -> Self::Output { - let mux_digest = - static_init_half!(s, MuxDigest<'static, A, L>, MuxDigest::new(self.digest)); - - mux_digest - } -} - -// Setup static space for the objects. -#[macro_export] -macro_rules! digest_component_helper { - ($A:ty, $L:expr $(,)?) => {{ - use capsules::virtual_digest::MuxDigest; - use capsules::virtual_digest::VirtualMuxDigest; - use core::mem::MaybeUninit; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF1 - };}; -} - -pub struct DigestComponent, const L: usize> { - mux_digest: &'static MuxDigest<'static, A, L>, - key_buffer: &'static mut [u8], -} - -impl, const L: usize> DigestComponent { - pub fn new( - mux_digest: &'static MuxDigest<'static, A, L>, - key_buffer: &'static mut [u8], - ) -> DigestComponent { - DigestComponent { - mux_digest, - key_buffer, - } - } -} - -impl< - A: kernel::hil::digest::HMACSha256 - + digest::HMACSha384 - + digest::HMACSha512 - + 'static - + digest::Digest<'static, L>, - const L: usize, - > Component for DigestComponent -{ - type StaticInput = &'static mut MaybeUninit>; - - type Output = &'static VirtualMuxDigest<'static, A, L>; - - unsafe fn finalize(self, s: Self::StaticInput) -> Self::Output { - let virtual_digest_user = static_init_half!( - s, - VirtualMuxDigest<'static, A, L>, - VirtualMuxDigest::new(self.mux_digest, self.key_buffer) - ); - - virtual_digest_user - } -} diff --git a/boards/components/src/eui64.rs b/boards/components/src/eui64.rs new file mode 100644 index 0000000000..14d624d9d0 --- /dev/null +++ b/boards/components/src/eui64.rs @@ -0,0 +1,44 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Component for EUI-64 (Extended Unique Identifier). +//! +//! Usage +//! ----- +//! ```rust +//!let eui64 = components::eui64::Eui64Component::new(u64::from_le_bytes(device_id)) +//! .finalize(components::eui64_component_static!()); +//! ``` + +use capsules_extra::eui64::Eui64; +use core::mem::MaybeUninit; +use kernel::component::Component; + +#[macro_export] +macro_rules! eui64_component_static { + () => {{ + kernel::static_buf!(capsules_extra::eui64::Eui64) + };}; +} + +pub type Eui64ComponentType = capsules_extra::eui64::Eui64; + +pub struct Eui64Component { + eui64: u64, +} + +impl Eui64Component { + pub fn new(eui64: u64) -> Self { + Self { eui64 } + } +} + +impl Component for Eui64Component { + type StaticInput = &'static mut MaybeUninit; + type Output = &'static Eui64; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + s.write(Eui64::new(self.eui64)) + } +} diff --git a/boards/components/src/flash.rs b/boards/components/src/flash.rs index 5a69da58f0..672c725e2b 100644 --- a/boards/components/src/flash.rs +++ b/boards/components/src/flash.rs @@ -1,43 +1,41 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for Flash //! -//! Provides `FlashMux` and `FlashUser` (virtual flash) +//! Provides `FlashMux` and `FlashUser` (virtual flash). +//! //! Usage //! ----- //! ```rust //! let mux_flash = components::flash::FlashMuxComponent::new(&base_peripherals.nvmc).finalize( -//! components::flash_mux_component_helper!(nrf52833::nvmc::Nvmc), +//! components::flash_mux_component_static!(nrf52833::nvmc::Nvmc), //! ); //! //! let virtual_app_flash = components::flash::FlashUserComponent::new(mux_flash).finalize( -//! components::flash_user_component_helper!(nrf52833::nvmc::Nvmc), +//! components::flash_user_component_static!(nrf52833::nvmc::Nvmc), //! ); //! ``` -use capsules::virtual_flash::FlashUser; -use capsules::virtual_flash::MuxFlash; +use capsules_core::virtualizers::virtual_flash::FlashUser; +use capsules_core::virtualizers::virtual_flash::MuxFlash; use core::mem::MaybeUninit; use kernel::component::Component; use kernel::hil::flash::{Flash, HasClient}; -use kernel::static_init_half; // Setup static space for the objects. #[macro_export] -macro_rules! flash_user_component_helper { +macro_rules! flash_user_component_static { ($F:ty) => {{ - use capsules::virtual_flash::FlashUser; - use core::mem::MaybeUninit; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF1 + kernel::static_buf!(capsules_core::virtualizers::virtual_flash::FlashUser<'static, $F>) };}; } #[macro_export] -macro_rules! flash_mux_component_helper { +macro_rules! flash_mux_component_static { ($F:ty) => {{ - use capsules::virtual_flash::MuxFlash; - use core::mem::MaybeUninit; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF1 + kernel::static_buf!(capsules_core::virtualizers::virtual_flash::MuxFlash<'static, $F>) };}; } @@ -57,8 +55,8 @@ impl>> Component type StaticInput = &'static mut MaybeUninit>; type Output = &'static MuxFlash<'static, F>; - unsafe fn finalize(self, s: Self::StaticInput) -> Self::Output { - let mux_flash = static_init_half!(s, MuxFlash<'static, F>, MuxFlash::new(self.flash)); + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let mux_flash = s.write(MuxFlash::new(self.flash)); HasClient::set_client(self.flash, mux_flash); mux_flash @@ -81,10 +79,7 @@ impl>> Component type StaticInput = &'static mut MaybeUninit>; type Output = &'static FlashUser<'static, F>; - unsafe fn finalize(self, s: Self::StaticInput) -> Self::Output { - let virtual_flash = - static_init_half!(s, FlashUser<'static, F>, FlashUser::new(self.mux_flash)); - - virtual_flash + fn finalize(self, s: Self::StaticInput) -> Self::Output { + s.write(FlashUser::new(self.mux_flash)) } } diff --git a/boards/components/src/fm25cl.rs b/boards/components/src/fm25cl.rs new file mode 100644 index 0000000000..6f0b72eb95 --- /dev/null +++ b/boards/components/src/fm25cl.rs @@ -0,0 +1,85 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Components for the FM25CL FRAM chip. +//! +//! Uses a SPI Interface. +//! +//! Usage +//! ----- +//! ```rust +//! let fm25cl = components::fm25cl::Fm25clComponent::new(spi_mux, stm32f429zi::gpio::PinId::PE03) +//! .finalize(components::fm25cl_component_static!(stm32f429zi::spi::Spi)); +//! ``` + +use capsules_core::virtualizers::virtual_spi::{MuxSpiMaster, VirtualSpiMasterDevice}; +use capsules_extra::fm25cl::FM25CL; +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::hil::spi; +use kernel::hil::spi::SpiMasterDevice; + +#[macro_export] +macro_rules! fm25cl_component_static { + ($S:ty $(,)?) => {{ + let txbuffer = kernel::static_buf!([u8; capsules_extra::fm25cl::BUF_LEN]); + let rxbuffer = kernel::static_buf!([u8; capsules_extra::fm25cl::BUF_LEN]); + + let spi = kernel::static_buf!( + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice<'static, $S> + ); + let fm25cl = kernel::static_buf!( + capsules_extra::fm25cl::FM25CL< + 'static, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice<'static, $S>, + > + ); + + (spi, fm25cl, txbuffer, rxbuffer) + };}; +} + +pub struct Fm25clComponent> { + spi_mux: &'static MuxSpiMaster<'static, S>, + chip_select: S::ChipSelect, +} + +impl> Fm25clComponent { + pub fn new( + spi_mux: &'static MuxSpiMaster<'static, S>, + chip_select: S::ChipSelect, + ) -> Fm25clComponent { + Fm25clComponent { + spi_mux, + chip_select, + } + } +} + +impl> Component for Fm25clComponent { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit>>, + &'static mut MaybeUninit<[u8; capsules_extra::fm25cl::BUF_LEN]>, + &'static mut MaybeUninit<[u8; capsules_extra::fm25cl::BUF_LEN]>, + ); + type Output = &'static FM25CL<'static, VirtualSpiMasterDevice<'static, S>>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let spi_device = static_buffer + .0 + .write(VirtualSpiMasterDevice::new(self.spi_mux, self.chip_select)); + spi_device.setup(); + + let txbuffer = static_buffer.2.write([0; capsules_extra::fm25cl::BUF_LEN]); + let rxbuffer = static_buffer.3.write([0; capsules_extra::fm25cl::BUF_LEN]); + + let fm25cl = static_buffer + .1 + .write(FM25CL::new(spi_device, txbuffer, rxbuffer)); + spi_device.set_client(fm25cl); + + fm25cl + } +} diff --git a/boards/components/src/ft6x06.rs b/boards/components/src/ft6x06.rs index 8b189d6380..da5a55d750 100644 --- a/boards/components/src/ft6x06.rs +++ b/boards/components/src/ft6x06.rs @@ -1,70 +1,92 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for the Ft6x06 Touch Panel. //! //! Usage //! ----- //! ```rust -//! let ft6x06 = components::ft6x06::Ft6x06Component::new() -//! .finalize(components::ft6x06_i2c_component_helper!(mux_i2c)); +//! let ft6x06 = components::ft6x06::Ft6x06Component::new( +//! i2c_mux, +//! 0x38, +//! base_peripherals.gpio_ports.get_pin(stm32f412g::gpio::PinId::PG05).unwrap() +//! ) +//! .finalize(components::ft6x06_component_static!(mux_i2c)); //! ``` -use capsules::ft6x06::Ft6x06; -use capsules::virtual_i2c::I2CDevice; + +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_extra::ft6x06::Ft6x06; +use capsules_extra::ft6x06::NO_TOUCH; use core::mem::MaybeUninit; use kernel::component::Component; use kernel::hil::gpio; -use kernel::hil::touch; -use kernel::static_init_half; +use kernel::hil::i2c; // Setup static space for the objects. #[macro_export] -macro_rules! ft6x06_i2c_component_helper { - ($i2c_mux:expr $(,)?) => {{ - use capsules::ft6x06::Ft6x06; - use capsules::ft6x06::NO_TOUCH; - use capsules::virtual_i2c::I2CDevice; - use core::mem::MaybeUninit; - use kernel::hil::touch::TouchEvent; - // Buffer to use for I2C messages - static mut BUFFER: [u8; 17] = [0; 17]; - pub static mut EVENTS_BUFFER: [TouchEvent; 2] = [NO_TOUCH, NO_TOUCH]; - let i2c = components::i2c::I2CComponent::new($i2c_mux, 0x38) - .finalize(components::i2c_component_helper!()); - static mut ft6x06: MaybeUninit> = MaybeUninit::uninit(); - (&i2c, &mut ft6x06, &mut BUFFER, &mut EVENTS_BUFFER) +macro_rules! ft6x06_component_static { + ($I:ty $(,)?) => {{ + let i2c_device = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>); + let buffer = kernel::static_buf!([u8; 17]); + let events_buffer = kernel::static_buf!([kernel::hil::touch::TouchEvent; 2]); + let ft6x06 = kernel::static_buf!( + capsules_extra::ft6x06::Ft6x06< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>, + > + ); + + (i2c_device, ft6x06, buffer, events_buffer) };}; } -pub struct Ft6x06Component { - interupt_pin: &'static dyn gpio::InterruptPin<'static>, +pub struct Ft6x06Component> { + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, + interrupt_pin: &'static dyn gpio::InterruptPin<'static>, } -impl Ft6x06Component { - pub fn new(pin: &'static dyn gpio::InterruptPin) -> Ft6x06Component { - Ft6x06Component { interupt_pin: pin } +impl> Ft6x06Component { + pub fn new( + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, + pin: &'static dyn gpio::InterruptPin, + ) -> Ft6x06Component { + Ft6x06Component { + i2c_mux, + i2c_address, + interrupt_pin: pin, + } } } -impl Component for Ft6x06Component { +impl> Component for Ft6x06Component { type StaticInput = ( - &'static I2CDevice<'static>, - &'static mut MaybeUninit>, - &'static mut [u8], - &'static mut [touch::TouchEvent; 2], + &'static mut MaybeUninit>, + &'static mut MaybeUninit>>, + &'static mut MaybeUninit<[u8; 17]>, + &'static mut MaybeUninit<[kernel::hil::touch::TouchEvent; 2]>, ); - type Output = &'static Ft6x06<'static>; + type Output = &'static Ft6x06<'static, I2CDevice<'static, I>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let ft6x06 = static_init_half!( - static_buffer.1, - Ft6x06<'static>, - Ft6x06::new( - static_buffer.0, - self.interupt_pin, - static_buffer.2, - static_buffer.3, - ) - ); - static_buffer.0.set_client(ft6x06); - self.interupt_pin.set_client(ft6x06); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let ft6x06_i2c = static_buffer + .0 + .write(I2CDevice::new(self.i2c_mux, self.i2c_address)); + + let buffer = static_buffer.2.write([0; 17]); + let events_buffer = static_buffer.3.write([NO_TOUCH, NO_TOUCH]); + + let ft6x06 = static_buffer.1.write(Ft6x06::new( + ft6x06_i2c, + self.interrupt_pin, + buffer, + events_buffer, + )); + ft6x06_i2c.set_client(ft6x06); + self.interrupt_pin.set_client(ft6x06); ft6x06 } diff --git a/boards/components/src/fxos8700.rs b/boards/components/src/fxos8700.rs index 39448b31e0..e2d2a620b7 100644 --- a/boards/components/src/fxos8700.rs +++ b/boards/components/src/fxos8700.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for the FXOS8700cq //! //! I2C Interface @@ -6,54 +10,74 @@ //! ----- //! ```rust //! let fxos8700 = components::fxos8700::Fxos8700Component::new(mux_i2c, PinId::AdB1_00.get_pin().as_ref().unwrap()) -//! .finalize(()); +//! .finalize(components::fxos8700_component_static!()); //! //! let ninedof = components::ninedof::NineDofComponent::new(board_kernel) -//! .finalize(components::ninedof_component_helper!(fxos8700)); +//! .finalize(components::ninedof_component_static!(fxos8700)); //! ``` // Based on the component written for sam4l by: // Author: Philip Levis // Last modified: 6/03/2020 -use capsules::fxos8700cq; -use capsules::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_extra::fxos8700cq::Fxos8700cq; use kernel::component::Component; +use core::mem::MaybeUninit; use kernel::hil; use kernel::hil::gpio; -use kernel::static_init; +use kernel::hil::i2c; + +#[macro_export] +macro_rules! fxos8700_component_static { + ($I:ty $(,)?) => {{ + let i2c_device = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>); + let buffer = kernel::static_buf!([u8; capsules_extra::fxos8700cq::BUF_LEN]); + let fxo = kernel::static_buf!(capsules_extra::fxos8700cq::Fxos8700cq<'static>); + + (i2c_device, buffer, fxo) + };}; +} -pub struct Fxos8700Component { - i2c_mux: &'static MuxI2C<'static>, +pub struct Fxos8700Component> { + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, gpio: &'static dyn gpio::InterruptPin<'static>, } -impl Fxos8700Component { - pub fn new<'a>( - i2c: &'static MuxI2C<'static>, +impl> Fxos8700Component { + pub fn new( + i2c: &'static MuxI2C<'static, I>, + i2c_address: u8, gpio: &'static dyn hil::gpio::InterruptPin<'static>, - ) -> Fxos8700Component { + ) -> Fxos8700Component { Fxos8700Component { i2c_mux: i2c, - gpio: gpio, + i2c_address, + gpio, } } } -impl Component for Fxos8700Component { - type StaticInput = (); - type Output = &'static fxos8700cq::Fxos8700cq<'static>; +impl> Component for Fxos8700Component { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; capsules_extra::fxos8700cq::BUF_LEN]>, + &'static mut MaybeUninit>, + ); + type Output = &'static Fxos8700cq<'static>; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let fxos8700_i2c = s.0.write(I2CDevice::new(self.i2c_mux, self.i2c_address)); + let buffer = s.1.write([0; capsules_extra::fxos8700cq::BUF_LEN]); + let fxos8700 = s.2.write(Fxos8700cq::new(fxos8700_i2c, self.gpio, buffer)); - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { - let fxos8700_i2c = static_init!(I2CDevice, I2CDevice::new(self.i2c_mux, 0x1f)); - let fxos8700 = static_init!( - fxos8700cq::Fxos8700cq<'static>, - fxos8700cq::Fxos8700cq::new(fxos8700_i2c, self.gpio, &mut fxos8700cq::BUF) - ); fxos8700_i2c.set_client(fxos8700); self.gpio.set_client(fxos8700); + fxos8700 } } diff --git a/boards/components/src/gpio.rs b/boards/components/src/gpio.rs index 842354e870..af726a9f5a 100644 --- a/boards/components/src/gpio.rs +++ b/boards/components/src/gpio.rs @@ -1,14 +1,16 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for GPIO pins. //! -//! //! Usage //! ----- //! -//! The `gpio_component_helper!` macro takes 'static references to -//! GPIO pins. When GPIO instances are owned values, the -//! `gpio_component_helper_owned!` can be used, indicating that the -//! passed values are owned values. This macro will perform static -//! allocation of the passed in GPIO pins internally. +//! The `gpio_component_helper!` macro takes 'static references to GPIO pins. +//! When GPIO instances are owned values, the `gpio_component_helper_owned!` can +//! be used, indicating that the passed values are owned values. This macro will +//! perform static allocation of the passed in GPIO pins internally. //! //! ```rust //! let gpio = components::gpio::GpioComponent::new( @@ -43,17 +45,16 @@ //! 22 => &nrf52840::gpio::PORT[Pin::P1_04], //! 23 => &nrf52840::gpio::PORT[Pin::P1_02] //! ), -//! ).finalize(components::gpio_component_buf!(nrf52840::gpio::GPIOPin)); +//! ).finalize(components::gpio_component_static!(nrf52840::gpio::GPIOPin)); //! ``` -use capsules::gpio::GPIO; +use capsules_core::gpio::GPIO; use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; use kernel::hil::gpio; use kernel::hil::gpio::InterruptWithValue; -use kernel::static_init_half; #[macro_export] macro_rules! gpio_component_helper_max_pin { @@ -112,15 +113,14 @@ macro_rules! gpio_component_helper { } #[macro_export] -macro_rules! gpio_component_buf { +macro_rules! gpio_component_static { ($Pin:ty $(,)?) => {{ - use capsules::gpio::GPIO; - use core::mem::MaybeUninit; - static mut BUF: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF + kernel::static_buf!(capsules_core::gpio::GPIO<'static, $Pin>) };}; } +pub type GpioComponentType = GPIO<'static, IP>; + pub struct GpioComponent> { board_kernel: &'static kernel::Kernel, driver_num: usize, @@ -134,7 +134,7 @@ impl> GpioComponent { gpio_pins: &'static [Option<&'static gpio::InterruptValueWrapper<'static, IP>>], ) -> Self { Self { - board_kernel: board_kernel, + board_kernel, driver_num, gpio_pins, } @@ -145,16 +145,12 @@ impl> Component for GpioComponent type StaticInput = &'static mut MaybeUninit>; type Output = &'static GPIO<'static, IP>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let gpio = static_init_half!( - static_buffer, - GPIO<'static, IP>, - GPIO::new( - self.gpio_pins, - self.board_kernel.create_grant(self.driver_num, &grant_cap) - ) - ); + let gpio = static_buffer.write(GPIO::new( + self.gpio_pins, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); for maybe_pin in self.gpio_pins.iter() { if let Some(pin) = maybe_pin { pin.set_client(gpio); diff --git a/boards/components/src/hd44780.rs b/boards/components/src/hd44780.rs index 16d1ef4893..43b5c1f855 100644 --- a/boards/components/src/hd44780.rs +++ b/boards/components/src/hd44780.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for the HD447880 LCD controller. //! //! Usage @@ -5,51 +9,53 @@ //! ```rust //! let height: u8 = 2; //! let width: u8 = 16; -//! let lcd = components::hd44780::HD44780Component::new(mux_alarm, width, height).finalize( -//! components::hd44780_component_helper!( +//! let lcd = components::hd44780::HD44780Component::new(mux_alarm, +//! width, +//! height, +//! // rs pin +//! gpio_ports.pins[5][13].as_ref().unwrap(), +//! // en pin +//! gpio_ports.pins[4][11].as_ref().unwrap(), +//! // data 4 pin +//! gpio_ports.pins[5][14].as_ref().unwrap(), +//! // data 5 pin +//! gpio_ports.pins[4][13].as_ref().unwrap(), +//! // data 6 pin +//! gpio_ports.pins[5][15].as_ref().unwrap(), +//! // data 7 pin +//! gpio_ports.pins[6][14].as_ref().unwrap()) +//! .finalize( +//! components::hd44780_component_static!( //! stm32f429zi::tim2::Tim2, -//! // rs pin -//! gpio_ports.pins[5][13].as_ref().unwrap(), -//! // en pin -//! gpio_ports.pins[4][11].as_ref().unwrap(), -//! // data 4 pin -//! gpio_ports.pins[5][14].as_ref().unwrap(), -//! // data 5 pin -//! gpio_ports.pins[4][13].as_ref().unwrap(), -//! // data 6 pin -//! gpio_ports.pins[5][15].as_ref().unwrap(), -//! // data 7 pin -//! gpio_ports.pins[6][14].as_ref().unwrap(), +//! +//! //! ) //! ); //! ``` -use capsules::hd44780::HD44780; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; + +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_extra::hd44780::HD44780; use core::mem::MaybeUninit; use kernel::component::Component; use kernel::hil::time; use kernel::hil::time::Alarm; -use kernel::static_init_half; // Setup static space for the objects. #[macro_export] -macro_rules! hd44780_component_helper { - ($A:ty, $rs:expr, $en:expr, $data_4_pin:expr, $data_5_pin:expr, $data_6_pin:expr, $data_7_pin:expr $(,)?) => {{ - use capsules::hd44780::HD44780; - use core::mem::MaybeUninit; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF2: MaybeUninit>> = - MaybeUninit::uninit(); - ( - &mut BUF1, - &mut BUF2, - $rs, - $en, - $data_4_pin, - $data_5_pin, - $data_6_pin, - $data_7_pin, - ) +macro_rules! hd44780_component_static { + ($A:ty $(,)?) => {{ + let alarm = kernel::static_buf!( + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A> + ); + let hd44780 = kernel::static_buf!( + capsules_extra::hd44780::HD44780< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A>, + > + ); + let buffer = kernel::static_buf!([u8; capsules_extra::hd44780::BUF_LEN]); + + (alarm, hd44780, buffer) };}; } @@ -57,6 +63,12 @@ pub struct HD44780Component> { alarm_mux: &'static MuxAlarm<'static, A>, width: u8, height: u8, + rs: &'static dyn kernel::hil::gpio::Pin, + en: &'static dyn kernel::hil::gpio::Pin, + data_4_pin: &'static dyn kernel::hil::gpio::Pin, + data_5_pin: &'static dyn kernel::hil::gpio::Pin, + data_6_pin: &'static dyn kernel::hil::gpio::Pin, + data_7_pin: &'static dyn kernel::hil::gpio::Pin, } impl> HD44780Component { @@ -64,11 +76,23 @@ impl> HD44780Component { alarm_mux: &'static MuxAlarm<'static, A>, width: u8, height: u8, + rs: &'static dyn kernel::hil::gpio::Pin, + en: &'static dyn kernel::hil::gpio::Pin, + data_4_pin: &'static dyn kernel::hil::gpio::Pin, + data_5_pin: &'static dyn kernel::hil::gpio::Pin, + data_6_pin: &'static dyn kernel::hil::gpio::Pin, + data_7_pin: &'static dyn kernel::hil::gpio::Pin, ) -> HD44780Component { HD44780Component { - alarm_mux: alarm_mux, - width: width, - height: height, + alarm_mux, + width, + height, + rs, + en, + data_4_pin, + data_5_pin, + data_6_pin, + data_7_pin, } } } @@ -77,39 +101,28 @@ impl> Component for HD44780Component { type StaticInput = ( &'static mut MaybeUninit>, &'static mut MaybeUninit>>, - &'static dyn kernel::hil::gpio::Pin, - &'static dyn kernel::hil::gpio::Pin, - &'static dyn kernel::hil::gpio::Pin, - &'static dyn kernel::hil::gpio::Pin, - &'static dyn kernel::hil::gpio::Pin, - &'static dyn kernel::hil::gpio::Pin, + &'static mut MaybeUninit<[u8; capsules_extra::hd44780::BUF_LEN]>, ); type Output = &'static HD44780<'static, VirtualMuxAlarm<'static, A>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let lcd_alarm = static_init_half!( - static_buffer.0, - VirtualMuxAlarm<'static, A>, - VirtualMuxAlarm::new(self.alarm_mux) - ); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let lcd_alarm = static_buffer.0.write(VirtualMuxAlarm::new(self.alarm_mux)); lcd_alarm.setup(); - let hd44780 = static_init_half!( - static_buffer.1, - capsules::hd44780::HD44780<'static, VirtualMuxAlarm<'static, A>>, - capsules::hd44780::HD44780::new( - static_buffer.2, - static_buffer.3, - static_buffer.4, - static_buffer.5, - static_buffer.6, - static_buffer.7, - &mut capsules::hd44780::ROW_OFFSETS, - lcd_alarm, - self.width, - self.height, - ) - ); + let buffer = static_buffer.2.write([0; capsules_extra::hd44780::BUF_LEN]); + + let hd44780 = static_buffer.1.write(capsules_extra::hd44780::HD44780::new( + self.rs, + self.en, + self.data_4_pin, + self.data_5_pin, + self.data_6_pin, + self.data_7_pin, + buffer, + lcd_alarm, + self.width, + self.height, + )); lcd_alarm.set_alarm_client(hd44780); hd44780 diff --git a/boards/components/src/hmac.rs b/boards/components/src/hmac.rs index c2662c4141..bdd25557c9 100644 --- a/boards/components/src/hmac.rs +++ b/boards/components/src/hmac.rs @@ -1,159 +1,155 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for collections of HMACs. //! //! Usage //! ----- //! ```rust -//! let hmac_data_buffer = static_init!([u8; 64], [0; 64]); -//! let hmac_dest_buffer = static_init!([u8; 32], [0; 32]); -//! -//! let mux_hmac = components::hmac::HmacMuxComponent::new(&earlgrey::hmac::HMAC).finalize( -//! components::hmac_mux_component_helper!(lowrisc::hmac::Hmac, [u8; 32]), -//! ); -//! //! let hmac = components::hmac::HmacComponent::new( //! board_kernel, -//! &mux_hmac, -//! hmac_data_buffer, -//! hmac_dest_buffer, +//! chip.hmac, //! ) -//! .finalize(components::hmac_component_helper!( +//! .finalize(components::hmac_component_static!( //! lowrisc::hmac::Hmac, -//! [u8; 32] +//! 32 //! )); //! ``` -use capsules; -use capsules::hmac::HmacDriver; -use capsules::virtual_hmac::MuxHmac; -use capsules::virtual_hmac::VirtualMuxHmac; +use capsules_extra::hmac::HmacDriver; use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; use kernel::hil::digest; -use kernel::static_init_half; -// Setup static space for the objects. #[macro_export] -macro_rules! hmac_mux_component_helper { +macro_rules! hmac_component_static { ($A:ty, $L:expr $(,)?) => {{ - use capsules::virtual_hmac::MuxHmac; - use capsules::virtual_hmac::VirtualMuxHmac; - use core::mem::MaybeUninit; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF1 - };}; -} - -pub struct HmacMuxComponent, const L: usize> { - hmac: &'static A, -} - -impl, const L: usize> HmacMuxComponent { - pub fn new(hmac: &'static A) -> HmacMuxComponent { - HmacMuxComponent { hmac } - } -} + let hmac = kernel::static_buf!(capsules_extra::hmac::HmacDriver<'static, $A, $L>); -impl< - A: 'static - + digest::Digest<'static, L> - + digest::HMACSha256 - + digest::HMACSha384 - + digest::HMACSha512, - const L: usize, - > Component for HmacMuxComponent -{ - type StaticInput = &'static mut MaybeUninit>; - type Output = &'static MuxHmac<'static, A, L>; + let data_buffer = kernel::static_buf!([u8; 64]); + let dest_buffer = kernel::static_buf!([u8; $L]); - unsafe fn finalize(self, s: Self::StaticInput) -> Self::Output { - let mux_hmac = static_init_half!(s, MuxHmac<'static, A, L>, MuxHmac::new(self.hmac)); - - mux_hmac - } -} - -// Setup static space for the objects. -#[macro_export] -macro_rules! hmac_component_helper { - ($A:ty, $L:expr $(,)?) => {{ - use capsules::hmac::HmacDriver; - use capsules::virtual_hmac::MuxHmac; - use capsules::virtual_hmac::VirtualMuxHmac; - use core::mem::MaybeUninit; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF2: MaybeUninit, $L>> = - MaybeUninit::uninit(); - (&mut BUF1, &mut BUF2) + (hmac, data_buffer, dest_buffer) };}; } +pub type HmacComponentType = capsules_extra::hmac::HmacDriver<'static, H, L>; + pub struct HmacComponent, const L: usize> { board_kernel: &'static kernel::Kernel, driver_num: usize, - mux_hmac: &'static MuxHmac<'static, A, L>, - key_buffer: &'static mut [u8], - data_buffer: &'static mut [u8], - dest_buffer: &'static mut [u8; L], + hmac: &'static A, } impl, const L: usize> HmacComponent { pub fn new( board_kernel: &'static kernel::Kernel, driver_num: usize, - mux_hmac: &'static MuxHmac<'static, A, L>, - key_buffer: &'static mut [u8], - data_buffer: &'static mut [u8], - dest_buffer: &'static mut [u8; L], + hmac: &'static A, ) -> HmacComponent { HmacComponent { board_kernel, driver_num, - mux_hmac, - key_buffer, - data_buffer, - dest_buffer, + hmac, } } } impl< - A: kernel::hil::digest::HMACSha256 - + digest::HMACSha384 - + digest::HMACSha512 + A: kernel::hil::digest::HmacSha256 + + digest::HmacSha384 + + digest::HmacSha512 + 'static + digest::Digest<'static, L>, const L: usize, > Component for HmacComponent { type StaticInput = ( - &'static mut MaybeUninit>, - &'static mut MaybeUninit, L>>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; 64]>, + &'static mut MaybeUninit<[u8; L]>, ); + type Output = &'static HmacDriver<'static, A, L>; - type Output = &'static HmacDriver<'static, VirtualMuxHmac<'static, A, L>, L>; - - unsafe fn finalize(self, s: Self::StaticInput) -> Self::Output { + fn finalize(self, s: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let virtual_hmac_user = static_init_half!( - s.0, - VirtualMuxHmac<'static, A, L>, - VirtualMuxHmac::new(self.mux_hmac, self.key_buffer) - ); - - let hmac = static_init_half!( - s.1, - capsules::hmac::HmacDriver<'static, VirtualMuxHmac<'static, A, L>, L>, - capsules::hmac::HmacDriver::new( - virtual_hmac_user, - self.data_buffer, - self.dest_buffer, - self.board_kernel.create_grant(self.driver_num, &grant_cap), - ) - ); + let data_buffer = s.1.write([0; 64]); + let dest_buffer = s.2.write([0; L]); + + let hmac = s.0.write(capsules_extra::hmac::HmacDriver::new( + self.hmac, + data_buffer, + dest_buffer, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); + + self.hmac.set_client(hmac); hmac } } + +#[macro_export] +macro_rules! hmac_sha256_software_component_static { + ($S:ty $(,)?) => {{ + let hmac_sha256 = + kernel::static_buf!(capsules_extra::hmac_sha256::HmacSha256Software<'static, $S>); + + let data_buffer = kernel::static_buf!([u8; 64]); + let verify_buffer = kernel::static_buf!([u8; 32]); + + (hmac_sha256, data_buffer, verify_buffer) + };}; +} + +pub type HmacSha256SoftwareComponentType = + capsules_extra::hmac_sha256::HmacSha256Software<'static, S>; + +pub struct HmacSha256SoftwareComponent< + S: digest::Sha256 + digest::DigestDataHash<'static, 32> + digest::Digest<'static, 32> + 'static, +> { + sha_256: &'static S, +} + +impl + digest::Digest<'static, 32>> + HmacSha256SoftwareComponent +{ + pub fn new(sha_256: &'static S) -> HmacSha256SoftwareComponent { + HmacSha256SoftwareComponent { sha_256 } + } +} + +impl< + S: digest::Sha256 + + digest::DigestDataHash<'static, 32> + + digest::Digest<'static, 32> + + 'static, + > Component for HmacSha256SoftwareComponent +{ + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; 64]>, + &'static mut MaybeUninit<[u8; 32]>, + ); + type Output = &'static capsules_extra::hmac_sha256::HmacSha256Software<'static, S>; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let data_buffer = s.1.write([0; 64]); + let verify_buffer = s.2.write([0; 32]); + + let hmac_sha256_sw = + s.0.write(capsules_extra::hmac_sha256::HmacSha256Software::new( + self.sha_256, + data_buffer, + verify_buffer, + )); + + kernel::hil::digest::Digest::set_client(self.sha_256, hmac_sha256_sw); + + hmac_sha256_sw + } +} diff --git a/boards/components/src/hs3003.rs b/boards/components/src/hs3003.rs new file mode 100644 index 0000000000..19a16689d0 --- /dev/null +++ b/boards/components/src/hs3003.rs @@ -0,0 +1,74 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Components for the HS3003 Temperature/Humidity Sensor. +//! +//! Usage +//! ----- +//! ```rust +//! let hs3003 = Hs3003Component::new(mux_i2c, mux_alarm, 0x44).finalize( +//! components::hs3003_component_static!(sam4l::ast::Ast)); +//! let temperature = components::temperature::TemperatureComponent::new(board_kernel, hs3003).finalize(()); +//! let humidity = components::humidity::HumidityComponent::new(board_kernel, hs3003).finalize(()); +//! ``` + +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_extra::hs3003::Hs3003; +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::hil::i2c; + +// Setup static space for the objects. +#[macro_export] +macro_rules! hs3003_component_static { + ($I:ty $(,)?) => {{ + let i2c_device = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>); + let buffer = kernel::static_buf!([u8; 5]); + let hs3003 = kernel::static_buf!( + capsules_extra::hs3003::Hs3003< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>, + > + ); + + (i2c_device, buffer, hs3003) + };}; +} + +pub type Hs3003ComponentType = Hs3003<'static, I>; + +pub struct Hs3003Component> { + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, +} + +impl> Hs3003Component { + pub fn new(i2c: &'static MuxI2C<'static, I>, i2c_address: u8) -> Self { + Hs3003Component { + i2c_mux: i2c, + i2c_address, + } + } +} + +impl> Component for Hs3003Component { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; 5]>, + &'static mut MaybeUninit>>, + ); + type Output = &'static Hs3003<'static, I2CDevice<'static, I>>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let hs3003_i2c = static_buffer + .0 + .write(I2CDevice::new(self.i2c_mux, self.i2c_address)); + let buffer = static_buffer.1.write([0; 5]); + let hs3003 = static_buffer.2.write(Hs3003::new(hs3003_i2c, buffer)); + + hs3003_i2c.set_client(hs3003); + hs3003 + } +} diff --git a/boards/components/src/hts221.rs b/boards/components/src/hts221.rs index a015674a9a..c97248fb35 100644 --- a/boards/components/src/hts221.rs +++ b/boards/components/src/hts221.rs @@ -1,59 +1,72 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for the HTS221 Temperature/Humidity Sensor. //! //! Usage //! ----- //! ```rust //! let hts221 = Hts221Component::new(mux_i2c, mux_alarm, 0x5f).finalize( -//! components::hts221!(sam4l::ast::Ast)); +//! components::hts221_component_static!(sam4l::ast::Ast)); //! let temperature = components::temperature::TemperatureComponent::new(board_kernel, hts221).finalize(()); //! let humidity = components::humidity::HumidityComponent::new(board_kernel, hts221).finalize(()); //! ``` +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_extra::hts221::Hts221; use core::mem::MaybeUninit; - -use capsules::hts221::Hts221; -use capsules::virtual_i2c::{I2CDevice, MuxI2C}; use kernel::component::Component; -use kernel::{static_init, static_init_half}; +use kernel::hil::i2c; // Setup static space for the objects. #[macro_export] -macro_rules! hts221_component_helper { - () => {{ - use capsules::hts221::Hts221; - use core::mem::MaybeUninit; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF1 +macro_rules! hts221_component_static { + ($I:ty $(,)?) => {{ + let i2c_device = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>); + let buffer = kernel::static_buf!([u8; 17]); + let hts221 = kernel::static_buf!( + capsules_extra::hts221::Hts221< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>, + > + ); + + (i2c_device, buffer, hts221) };}; } -pub struct Hts221Component { - i2c_mux: &'static MuxI2C<'static>, +pub type Hts221ComponentType = capsules_extra::hts221::Hts221<'static, I>; + +pub struct Hts221Component> { + i2c_mux: &'static MuxI2C<'static, I>, i2c_address: u8, } -impl Hts221Component { - pub fn new(i2c: &'static MuxI2C<'static>, i2c_address: u8) -> Self { +impl> Hts221Component { + pub fn new(i2c: &'static MuxI2C<'static, I>, i2c_address: u8) -> Self { Hts221Component { i2c_mux: i2c, - i2c_address: i2c_address, + i2c_address, } } } -static mut I2C_BUF: [u8; 17] = [0; 17]; - -impl Component for Hts221Component { - type StaticInput = &'static mut MaybeUninit>; - type Output = &'static Hts221<'static>; +impl> Component for Hts221Component { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; 17]>, + &'static mut MaybeUninit>>, + ); + type Output = &'static Hts221<'static, I2CDevice<'static, I>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let hts221_i2c = static_init!(I2CDevice, I2CDevice::new(self.i2c_mux, self.i2c_address)); - let hts221 = static_init_half!( - static_buffer, - Hts221<'static>, - Hts221::new(hts221_i2c, &mut I2C_BUF) - ); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let hts221_i2c = static_buffer + .0 + .write(I2CDevice::new(self.i2c_mux, self.i2c_address)); + let buffer = static_buffer.1.write([0; 17]); + let hts221 = static_buffer.2.write(Hts221::new(hts221_i2c, buffer)); hts221_i2c.set_client(hts221); hts221 diff --git a/boards/components/src/humidity.rs b/boards/components/src/humidity.rs index 4f79648ec3..cddfe62633 100644 --- a/boards/components/src/humidity.rs +++ b/boards/components/src/humidity.rs @@ -1,54 +1,65 @@ -//! Component for any Temperature sensor. +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Component for any humidity sensor. //! //! Usage //! ----- //! ```rust -//! let humidity = HumidityComponent::new(board_kernel, nrf52::humidity::TEMP).finalize(()); +//! let humidity = HumidityComponent::new(board_kernel, nrf52::humidity::TEMP) +//! .finalize(components::humidity_component_static!()); //! ``` -use capsules::humidity::HumiditySensor; +use capsules_extra::humidity::HumiditySensor; +use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; use kernel::hil; -use kernel::static_init; + +#[macro_export] +macro_rules! humidity_component_static { + ($H: ty $(,)?) => {{ + kernel::static_buf!(capsules_extra::humidity::HumiditySensor<'static, $H>) + };}; +} + +pub type HumidityComponentType = capsules_extra::humidity::HumiditySensor<'static, H>; pub struct HumidityComponent> { board_kernel: &'static kernel::Kernel, driver_num: usize, - temp_sensor: &'static T, + sensor: &'static T, } impl> HumidityComponent { pub fn new( board_kernel: &'static kernel::Kernel, driver_num: usize, - temp_sensor: &'static T, + sensor: &'static T, ) -> HumidityComponent { HumidityComponent { board_kernel, driver_num, - temp_sensor, + sensor, } } } impl> Component for HumidityComponent { - type StaticInput = (); - type Output = &'static HumiditySensor<'static>; + type StaticInput = &'static mut MaybeUninit>; + type Output = &'static HumiditySensor<'static, T>; - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { + fn finalize(self, s: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let humidity = static_init!( - HumiditySensor<'static>, - HumiditySensor::new( - self.temp_sensor, - self.board_kernel.create_grant(self.driver_num, &grant_cap) - ) - ); + let humidity = s.write(HumiditySensor::new( + self.sensor, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); - hil::sensors::HumidityDriver::set_client(self.temp_sensor, humidity); + hil::sensors::HumidityDriver::set_client(self.sensor, humidity); humidity } } diff --git a/boards/components/src/i2c.rs b/boards/components/src/i2c.rs index 3912548769..0d3c82c96b 100644 --- a/boards/components/src/i2c.rs +++ b/boards/components/src/i2c.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for I2C. //! //! This provides two components. @@ -9,79 +13,84 @@ //! Usage //! ----- //! ```rust -//! let mux_i2c = components::i2c::I2CMuxComponent::new(&stm32f3xx::i2c::I2C1).finalize(components::i2c_mux_component_helper!()); -//! let client_i2c = components::i2c::I2CComponent::new(mux_i2c, 0x19).finalize(components::i2c_component_helper!()); +//! let mux_i2c = components::i2c::I2CMuxComponent::new(&stm32f3xx::i2c::I2C1, None, dynamic_deferred_caller) +//! .finalize(components::i2c_mux_component_static!()); +//! let client_i2c = components::i2c::I2CComponent::new(mux_i2c, 0x19) +//! .finalize(components::i2c_component_static!()); //! ``` // Author: Alexandru Radovici -use capsules::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; use core::mem::MaybeUninit; +use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::DynamicDeferredCall; -use kernel::hil::i2c; -use kernel::static_init_half; +use kernel::create_capability; +use kernel::hil::i2c::{self, NoSMBus}; // Setup static space for the objects. #[macro_export] -macro_rules! i2c_mux_component_helper { - () => {{ - use capsules::virtual_i2c::MuxI2C; - use core::mem::MaybeUninit; - static mut BUF: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF +macro_rules! i2c_mux_component_static { + ($I:ty $(,)?) => {{ + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::MuxI2C<'static, $I>) + };}; + ($I:ty, $S:ty $(,)?) => {{ + kernel::static_buf!(capsules::virtual_i2c::MuxI2C<'static, $I, $S>) };}; } #[macro_export] -macro_rules! i2c_component_helper { - () => {{ - use capsules::virtual_i2c::I2CDevice; - use core::mem::MaybeUninit; - static mut BUF: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF - }}; +macro_rules! i2c_component_static { + ($I:ty $(,)?) => {{ + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>) + };}; } -pub struct I2CMuxComponent { - i2c: &'static dyn i2c::I2CMaster, - smbus: Option<&'static dyn i2c::SMBusMaster>, - deferred_caller: &'static DynamicDeferredCall, +#[macro_export] +macro_rules! i2c_master_slave_component_static { + ($I:ty $(,)?) => {{ + let i2c_master_buffer = kernel::static_buf!([u8; 32]); + let i2c_slave_buffer1 = kernel::static_buf!([u8; 32]); + let i2c_slave_buffer2 = kernel::static_buf!([u8; 32]); + + let driver = kernel::static_buf!( + capsules_core::i2c_master_slave_driver::I2CMasterSlaveDriver<'static, $I> + ); + + ( + driver, + i2c_master_buffer, + i2c_slave_buffer1, + i2c_slave_buffer2, + ) + };}; } -pub struct I2CComponent { - i2c_mux: &'static MuxI2C<'static>, - address: u8, +pub struct I2CMuxComponent< + I: 'static + i2c::I2CMaster<'static>, + S: 'static + i2c::SMBusMaster<'static> = NoSMBus, +> { + i2c: &'static I, + smbus: Option<&'static S>, } -impl I2CMuxComponent { - pub fn new( - i2c: &'static dyn i2c::I2CMaster, - smbus: Option<&'static dyn i2c::SMBusMaster>, - deferred_caller: &'static DynamicDeferredCall, - ) -> Self { - I2CMuxComponent { - i2c, - smbus, - deferred_caller, - } +impl, S: 'static + i2c::SMBusMaster<'static>> + I2CMuxComponent +{ + pub fn new(i2c: &'static I, smbus: Option<&'static S>) -> Self { + I2CMuxComponent { i2c, smbus } } } -impl Component for I2CMuxComponent { - type StaticInput = &'static mut MaybeUninit>; - type Output = &'static MuxI2C<'static>; +impl, S: 'static + i2c::SMBusMaster<'static>> Component + for I2CMuxComponent +{ + type StaticInput = &'static mut MaybeUninit>; + type Output = &'static MuxI2C<'static, I, S>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let mux_i2c = static_init_half!( - static_buffer, - MuxI2C<'static>, - MuxI2C::new(self.i2c, self.smbus, self.deferred_caller) - ); - - mux_i2c.initialize_callback_handle( - self.deferred_caller.register(mux_i2c).unwrap(), // Unwrap fail = no deferred call slot available for I2C mux - ); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let mux_i2c = static_buffer.write(MuxI2C::new(self.i2c, self.smbus)); + kernel::deferred_call::DeferredCallClient::register(mux_i2c); self.i2c.set_master_client(mux_i2c); @@ -89,26 +98,78 @@ impl Component for I2CMuxComponent { } } -impl I2CComponent { - pub fn new(mux: &'static MuxI2C<'static>, address: u8) -> Self { +pub struct I2CComponent> { + i2c_mux: &'static MuxI2C<'static, I>, + address: u8, +} + +impl> I2CComponent { + pub fn new(mux: &'static MuxI2C<'static, I>, address: u8) -> Self { I2CComponent { i2c_mux: mux, - address: address, + address, } } } -impl Component for I2CComponent { - type StaticInput = &'static mut MaybeUninit>; - type Output = &'static I2CDevice<'static>; +impl> Component for I2CComponent { + type StaticInput = &'static mut MaybeUninit>; + type Output = &'static I2CDevice<'static, I>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let i2c_device = static_init_half!( - static_buffer, - I2CDevice<'static>, - I2CDevice::new(self.i2c_mux, self.address) - ); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let i2c_device = static_buffer.write(I2CDevice::new(self.i2c_mux, self.address)); i2c_device } } + +pub struct I2CMasterSlaveDriverComponent> { + board_kernel: &'static kernel::Kernel, + driver_num: usize, + i2c: &'static I, +} + +impl> I2CMasterSlaveDriverComponent { + pub fn new(board_kernel: &'static kernel::Kernel, driver_num: usize, i2c: &'static I) -> Self { + I2CMasterSlaveDriverComponent { + board_kernel, + driver_num, + i2c, + } + } +} + +impl> Component for I2CMasterSlaveDriverComponent { + type StaticInput = ( + &'static mut MaybeUninit< + capsules_core::i2c_master_slave_driver::I2CMasterSlaveDriver<'static, I>, + >, + &'static mut MaybeUninit<[u8; 32]>, + &'static mut MaybeUninit<[u8; 32]>, + &'static mut MaybeUninit<[u8; 32]>, + ); + type Output = &'static capsules_core::i2c_master_slave_driver::I2CMasterSlaveDriver<'static, I>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + + let i2c_master_buffer = static_buffer.1.write([0; 32]); + let i2c_slave_buffer1 = static_buffer.2.write([0; 32]); + let i2c_slave_buffer2 = static_buffer.3.write([0; 32]); + + let i2c_master_slave_driver = static_buffer.0.write( + capsules_core::i2c_master_slave_driver::I2CMasterSlaveDriver::new( + self.i2c, + i2c_master_buffer, + i2c_slave_buffer1, + i2c_slave_buffer2, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + ), + ); + + self.i2c.set_master_client(i2c_master_slave_driver); + self.i2c.set_slave_client(i2c_master_slave_driver); + + i2c_master_slave_driver + } +} diff --git a/boards/components/src/ieee802154.rs b/boards/components/src/ieee802154.rs index eceac675c8..5af833777b 100644 --- a/boards/components/src/ieee802154.rs +++ b/boards/components/src/ieee802154.rs @@ -1,74 +1,189 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for IEEE 802.15.4 radio syscall interface. //! //! This provides one Component, `Ieee802154Component`, which implements a -//! userspace syscall interface to a full 802.15.4 stack with a -//! always-on MAC implementation, as well as multiplexed access to that MAC implementation. +//! userspace syscall interface to a full 802.15.4 stack with a always-on MAC +//! implementation, as well as multiplexed access to that MAC implementation. //! //! Usage //! ----- //! ```rust +//! let aes_mux = components::ieee802154::MuxAes128ccmComponent::new( +//! &base_peripherals.ecb, +//! ) +//! .finalize(components::mux_aes128ccm_component_static!( +//! nrf52840::aes::AesECB +//! )); +//! //! let (radio, mux_mac) = components::ieee802154::Ieee802154Component::new( //! board_kernel, +//! capsules_extra::ieee802154::DRIVER_NUM, //! &nrf52::ieee802154_radio::RADIO, -//! &nrf52::aes::AESECB, +//! aes_mux, //! PAN_ID, //! SRC_MAC, //! deferred_caller, //! ) -//! .finalize(components::ieee802154_component_helper!( +//! .finalize(components::ieee802154_component_static!( //! nrf52::ieee802154_radio::Radio, //! nrf52::aes::AesECB<'static> //! )); //! ``` -use capsules; -use capsules::ieee802154::device::MacDevice; -use capsules::ieee802154::mac::{AwakeMac, Mac}; +use capsules_core::virtualizers::virtual_aes_ccm::MuxAES128CCM; +use capsules_extra::ieee802154::device::MacDevice; +use capsules_extra::ieee802154::mac::{AwakeMac, Mac}; use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::DynamicDeferredCall; -use kernel::hil::radio; +use kernel::create_capability; +use kernel::hil::radio::{self, MAX_BUF_SIZE}; use kernel::hil::symmetric_encryption::{self, AES128Ctr, AES128, AES128CBC, AES128CCM, AES128ECB}; -use kernel::{create_capability, static_init, static_init_half}; + +// This buffer is used as an intermediate buffer for AES CCM encryption. An +// upper bound on the required size is `3 * BLOCK_SIZE + radio::MAX_BUF_SIZE`. +pub const CRYPT_SIZE: usize = 3 * symmetric_encryption::AES128_BLOCK_SIZE + radio::MAX_BUF_SIZE; + +#[macro_export] +macro_rules! mux_aes128ccm_component_static { + ($A:ty $(,)?) => {{ + kernel::static_buf!(capsules_core::virtualizers::virtual_aes_ccm::MuxAES128CCM<'static, $A>) + };}; +} + +pub type MuxAes128ccmComponentType = MuxAES128CCM<'static, A>; + +pub struct MuxAes128ccmComponent + AES128Ctr + AES128CBC + AES128ECB> { + aes: &'static A, +} + +impl + AES128Ctr + AES128CBC + AES128ECB> MuxAes128ccmComponent { + pub fn new(aes: &'static A) -> Self { + Self { aes } + } +} + +impl + AES128Ctr + AES128CBC + AES128ECB> Component + for MuxAes128ccmComponent +{ + type StaticInput = &'static mut MaybeUninit>; + type Output = &'static MuxAES128CCM<'static, A>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let aes_mux = static_buffer.write(MuxAES128CCM::new(self.aes)); + kernel::deferred_call::DeferredCallClient::register(aes_mux); + self.aes.set_client(aes_mux); + + aes_mux + } +} // Setup static space for the objects. #[macro_export] -macro_rules! ieee802154_component_helper { +macro_rules! ieee802154_component_static { ($R:ty, $A:ty $(,)?) => {{ - use capsules::ieee802154::mac::AwakeMac; - use core::mem::MaybeUninit; - use kernel::hil::symmetric_encryption::{AES128Ctr, AES128, AES128CBC, AES128CCM}; - - static mut BUF1: MaybeUninit> = - MaybeUninit::uninit(); - static mut BUF2: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF3: MaybeUninit< - capsules::ieee802154::framer::Framer< + let virtual_aes = kernel::static_buf!( + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, $A> + ); + let awake_mac = kernel::static_buf!(capsules_extra::ieee802154::mac::AwakeMac<'static, $R>); + let framer = kernel::static_buf!( + capsules_extra::ieee802154::framer::Framer< 'static, - AwakeMac<'static, $R>, - capsules::virtual_aes_ccm::VirtualAES128CCM<'static, $A>, - >, - > = MaybeUninit::uninit(); - (&mut BUF1, &mut BUF2, &mut BUF3) + capsules_extra::ieee802154::mac::AwakeMac<'static, $R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, $A>, + > + ); + + let mux_mac = kernel::static_buf!( + capsules_extra::ieee802154::virtual_mac::MuxMac< + 'static, + capsules_extra::ieee802154::framer::Framer< + 'static, + capsules_extra::ieee802154::mac::AwakeMac<'static, $R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, $A>, + >, + > + ); + let mac_user = kernel::static_buf!( + capsules_extra::ieee802154::virtual_mac::MacUser< + 'static, + capsules_extra::ieee802154::framer::Framer< + 'static, + capsules_extra::ieee802154::mac::AwakeMac<'static, $R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, $A>, + >, + > + ); + let radio_driver = kernel::static_buf!( + capsules_extra::ieee802154::RadioDriver< + 'static, + capsules_extra::ieee802154::virtual_mac::MacUser< + 'static, + capsules_extra::ieee802154::framer::Framer< + 'static, + capsules_extra::ieee802154::mac::AwakeMac<'static, $R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, $A>, + >, + >, + > + ); + + let radio_buf = kernel::static_buf!([u8; kernel::hil::radio::MAX_BUF_SIZE]); + let radio_rx_buf = kernel::static_buf!([u8; kernel::hil::radio::MAX_BUF_SIZE]); + let crypt_buf = kernel::static_buf!([u8; components::ieee802154::CRYPT_SIZE]); + let radio_rx_crypt_buf = kernel::static_buf!([u8; kernel::hil::radio::MAX_BUF_SIZE]); + + ( + virtual_aes, + awake_mac, + framer, + mux_mac, + mac_user, + radio_driver, + radio_buf, + radio_rx_buf, + crypt_buf, + radio_rx_crypt_buf, + ) };}; } +pub type Ieee802154ComponentType = capsules_extra::ieee802154::RadioDriver< + 'static, + capsules_extra::ieee802154::virtual_mac::MacUser< + 'static, + capsules_extra::ieee802154::framer::Framer< + 'static, + capsules_extra::ieee802154::mac::AwakeMac<'static, R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, A>, + >, + >, +>; + +pub type Ieee802154ComponentMacDeviceType = capsules_extra::ieee802154::framer::Framer< + 'static, + capsules_extra::ieee802154::mac::AwakeMac<'static, R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, A>, +>; + pub struct Ieee802154Component< - R: 'static + kernel::hil::radio::Radio, + R: 'static + kernel::hil::radio::Radio<'static>, A: 'static + AES128<'static> + AES128Ctr + AES128CBC + AES128ECB, > { board_kernel: &'static kernel::Kernel, driver_num: usize, radio: &'static R, - aes_mux: &'static capsules::virtual_aes_ccm::MuxAES128CCM<'static, A>, - pan_id: capsules::net::ieee802154::PanID, + aes_mux: &'static MuxAES128CCM<'static, A>, + pan_id: capsules_extra::net::ieee802154::PanID, short_addr: u16, - deferred_caller: &'static DynamicDeferredCall, + long_addr: [u8; 8], } impl< - R: 'static + kernel::hil::radio::Radio, + R: 'static + kernel::hil::radio::Radio<'static>, A: 'static + AES128<'static> + AES128Ctr + AES128CBC + AES128ECB, > Ieee802154Component { @@ -76,10 +191,10 @@ impl< board_kernel: &'static kernel::Kernel, driver_num: usize, radio: &'static R, - aes_mux: &'static capsules::virtual_aes_ccm::MuxAES128CCM<'static, A>, - pan_id: capsules::net::ieee802154::PanID, + aes_mux: &'static MuxAES128CCM<'static, A>, + pan_id: capsules_extra::net::ieee802154::PanID, short_addr: u16, - deferred_caller: &'static DynamicDeferredCall, + long_addr: [u8; 8], ) -> Self { Self { board_kernel, @@ -88,98 +203,146 @@ impl< aes_mux, pan_id, short_addr, - deferred_caller, + long_addr, } } } -static mut RADIO_BUF: [u8; radio::MAX_BUF_SIZE] = [0x00; radio::MAX_BUF_SIZE]; - -// The buffer packets are received into. -static mut RADIO_RX_BUF: [u8; radio::MAX_BUF_SIZE] = [0x00; radio::MAX_BUF_SIZE]; - -// This buffer is used as an intermediate buffer for AES CCM encryption -// An upper bound on the required size is 3 * BLOCK_SIZE + radio::MAX_BUF_SIZE -const CRYPT_SIZE: usize = 3 * symmetric_encryption::AES128_BLOCK_SIZE + radio::MAX_BUF_SIZE; -static mut CRYPT_BUF: [u8; CRYPT_SIZE] = [0x00; CRYPT_SIZE]; - impl< - R: 'static + kernel::hil::radio::Radio, + R: 'static + kernel::hil::radio::Radio<'static>, A: 'static + AES128<'static> + AES128Ctr + AES128CBC + AES128ECB, > Component for Ieee802154Component { type StaticInput = ( - &'static mut MaybeUninit>, - &'static mut MaybeUninit>, &'static mut MaybeUninit< - capsules::ieee802154::framer::Framer< + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, A>, + >, + &'static mut MaybeUninit>, + &'static mut MaybeUninit< + capsules_extra::ieee802154::framer::Framer< 'static, AwakeMac<'static, R>, - capsules::virtual_aes_ccm::VirtualAES128CCM<'static, A>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, A>, >, >, + &'static mut MaybeUninit< + capsules_extra::ieee802154::virtual_mac::MuxMac< + 'static, + capsules_extra::ieee802154::framer::Framer< + 'static, + AwakeMac<'static, R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, A>, + >, + >, + >, + &'static mut MaybeUninit< + capsules_extra::ieee802154::virtual_mac::MacUser< + 'static, + capsules_extra::ieee802154::framer::Framer< + 'static, + AwakeMac<'static, R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, A>, + >, + >, + >, + &'static mut MaybeUninit< + capsules_extra::ieee802154::RadioDriver< + 'static, + capsules_extra::ieee802154::virtual_mac::MacUser< + 'static, + capsules_extra::ieee802154::framer::Framer< + 'static, + AwakeMac<'static, R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, A>, + >, + >, + >, + >, + &'static mut MaybeUninit<[u8; radio::MAX_BUF_SIZE]>, + &'static mut MaybeUninit<[u8; radio::MAX_BUF_SIZE]>, + &'static mut MaybeUninit<[u8; CRYPT_SIZE]>, + &'static mut MaybeUninit<[u8; radio::MAX_BUF_SIZE]>, ); type Output = ( - &'static capsules::ieee802154::RadioDriver<'static>, - &'static capsules::ieee802154::virtual_mac::MuxMac<'static>, + &'static capsules_extra::ieee802154::RadioDriver< + 'static, + capsules_extra::ieee802154::virtual_mac::MacUser< + 'static, + capsules_extra::ieee802154::framer::Framer< + 'static, + AwakeMac<'static, R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, A>, + >, + >, + >, + &'static capsules_extra::ieee802154::virtual_mac::MuxMac< + 'static, + capsules_extra::ieee802154::framer::Framer< + 'static, + AwakeMac<'static, R>, + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, A>, + >, + >, ); - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let aes_ccm = static_init_half!( - static_buffer.0, - capsules::virtual_aes_ccm::VirtualAES128CCM<'static, A>, - capsules::virtual_aes_ccm::VirtualAES128CCM::new(self.aes_mux, &mut CRYPT_BUF) + let crypt_buf = static_buffer.8.write([0; CRYPT_SIZE]); + let aes_ccm = static_buffer.0.write( + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM::new( + self.aes_mux, + crypt_buf, + ), ); - aes_ccm.setup(); - // Keeps the radio on permanently; pass-through layer - let awake_mac = static_init_half!( - static_buffer.1, - AwakeMac<'static, R>, - AwakeMac::new(self.radio) - ); + // Keeps the radio on permanently; pass-through layer. + let radio_rx_buf = static_buffer.7.write([0; radio::MAX_BUF_SIZE]); + let awake_mac = static_buffer.1.write(AwakeMac::new(self.radio)); self.radio.set_transmit_client(awake_mac); - self.radio.set_receive_client(awake_mac, &mut RADIO_RX_BUF); + self.radio.set_receive_client(awake_mac); + self.radio.set_receive_buffer(radio_rx_buf); - let mac_device = static_init_half!( - static_buffer.2, - capsules::ieee802154::framer::Framer< - 'static, - AwakeMac<'static, R>, - capsules::virtual_aes_ccm::VirtualAES128CCM<'static, A>, - >, - capsules::ieee802154::framer::Framer::new(awake_mac, aes_ccm) - ); + let radio_rx_crypt_buf = static_buffer.9.write([0; MAX_BUF_SIZE]); + + let mac_device = static_buffer + .2 + .write(capsules_extra::ieee802154::framer::Framer::new( + awake_mac, + aes_ccm, + kernel::utilities::leasable_buffer::SubSliceMut::new(radio_rx_crypt_buf), + )); AES128CCM::set_client(aes_ccm, mac_device); awake_mac.set_transmit_client(mac_device); awake_mac.set_receive_client(mac_device); awake_mac.set_config_client(mac_device); - let mux_mac = static_init!( - capsules::ieee802154::virtual_mac::MuxMac<'static>, - capsules::ieee802154::virtual_mac::MuxMac::new(mac_device) - ); + let mux_mac = static_buffer + .3 + .write(capsules_extra::ieee802154::virtual_mac::MuxMac::new( + mac_device, + )); mac_device.set_transmit_client(mux_mac); mac_device.set_receive_client(mux_mac); - let userspace_mac = static_init!( - capsules::ieee802154::virtual_mac::MacUser<'static>, - capsules::ieee802154::virtual_mac::MacUser::new(mux_mac) - ); + let userspace_mac = + static_buffer + .4 + .write(capsules_extra::ieee802154::virtual_mac::MacUser::new( + mux_mac, + )); mux_mac.add_user(userspace_mac); - let radio_driver = static_init!( - capsules::ieee802154::RadioDriver<'static>, - capsules::ieee802154::RadioDriver::new( + let radio_buffer = static_buffer.6.write([0; radio::MAX_BUF_SIZE]); + let radio_driver = static_buffer + .5 + .write(capsules_extra::ieee802154::RadioDriver::new( userspace_mac, self.board_kernel.create_grant(self.driver_num, &grant_cap), - &mut RADIO_BUF, - self.deferred_caller, - ) - ); + radio_buffer, + )); + kernel::deferred_call::DeferredCallClient::register(radio_driver); mac_device.set_key_procedure(radio_driver); mac_device.set_device_procedure(radio_driver); @@ -187,10 +350,77 @@ impl< userspace_mac.set_receive_client(radio_driver); userspace_mac.set_pan(self.pan_id); userspace_mac.set_address(self.short_addr); - radio_driver.initialize_callback_handle( - self.deferred_caller.register(radio_driver).unwrap(), // Unwrap fail = no deferred call slot available for ieee802154 driver - ); + userspace_mac.set_address_long(self.long_addr); (radio_driver, mux_mac) } } + +// IEEE 802.15.4 RAW DRIVER + +// Setup static space for the objects. +#[macro_export] +macro_rules! ieee802154_raw_component_static { + ($R:ty $(,)?) => {{ + let radio_driver = + kernel::static_buf!(capsules_extra::ieee802154::phy_driver::RadioDriver<$R>); + let tx_buffer = kernel::static_buf!([u8; kernel::hil::radio::MAX_BUF_SIZE]); + let rx_buffer = kernel::static_buf!([u8; kernel::hil::radio::MAX_BUF_SIZE]); + + (radio_driver, tx_buffer, rx_buffer) + };}; +} + +pub type Ieee802154RawComponentType = + capsules_extra::ieee802154::phy_driver::RadioDriver<'static, R>; + +pub struct Ieee802154RawComponent> { + board_kernel: &'static kernel::Kernel, + driver_num: usize, + radio: &'static R, +} + +impl> Ieee802154RawComponent { + pub fn new( + board_kernel: &'static kernel::Kernel, + driver_num: usize, + radio: &'static R, + ) -> Self { + Self { + board_kernel, + driver_num, + radio, + } + } +} + +impl> Component for Ieee802154RawComponent { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; radio::MAX_BUF_SIZE]>, + &'static mut MaybeUninit<[u8; radio::MAX_BUF_SIZE]>, + ); + type Output = &'static capsules_extra::ieee802154::phy_driver::RadioDriver<'static, R>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + + let tx_buffer = static_buffer.1.write([0; MAX_BUF_SIZE]); + let radio_rx_buf = static_buffer.2.write([0; radio::MAX_BUF_SIZE]); + + let radio_driver = + static_buffer + .0 + .write(capsules_extra::ieee802154::phy_driver::RadioDriver::new( + self.radio, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + tx_buffer, + )); + + self.radio.set_transmit_client(radio_driver); + self.radio.set_receive_client(radio_driver); + self.radio.set_receive_buffer(radio_rx_buf); + + radio_driver + } +} diff --git a/boards/components/src/isl29035.rs b/boards/components/src/isl29035.rs index e46338c644..437ee8562c 100644 --- a/boards/components/src/isl29035.rs +++ b/boards/components/src/isl29035.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for the ISL29035 sensor. //! //! This provides two Components, Isl29035Component, which provides @@ -12,48 +16,68 @@ //! Usage //! ----- //! ```rust -//! let isl = Isl29035Component::new(mux_i2c, mux_alarm) -//! .finalize(isl29035_component_helper!(sam4l::ast::Ast)); -//! let ambient_light = AmbientLightComponent::new(board_kernel, sensors_i2c, mux_alarm) -//! .finalize(isl29035_component_helper!(sam4l::ast::Ast)); +//! let isl29035 = Isl29035Component::new(mux_i2c, mux_alarm) +//! .finalize(components::isl29035_component_static!(sam4l::ast::Ast)); +//! let ambient_light = +//! AmbientLightComponent::new(board_kernel, capsules_extra::ambient_light::DRIVER_NUM, isl29035) +//! .finalize(components::ambient_light_component_static!()); //! ``` // Author: Philip Levis // Last modified: 6/20/2018 +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_extra::ambient_light::AmbientLight; +use capsules_extra::isl29035::Isl29035; use core::mem::MaybeUninit; - -use capsules::ambient_light::AmbientLight; -use capsules::isl29035::Isl29035; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; -use capsules::virtual_i2c::{I2CDevice, MuxI2C}; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; use kernel::hil; +use kernel::hil::i2c; use kernel::hil::time::{self, Alarm}; -use kernel::{static_init, static_init_half}; // Setup static space for the objects. #[macro_export] -macro_rules! isl29035_component_helper { - ($A:ty $(,)?) => {{ - use capsules::isl29035::Isl29035; - use core::mem::MaybeUninit; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF2: MaybeUninit>> = - MaybeUninit::uninit(); - (&mut BUF1, &mut BUF2) +macro_rules! isl29035_component_static { + ($A:ty, $I:ty $(,)?) => {{ + let alarm = kernel::static_buf!( + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A> + ); + let i2c_device = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>); + let i2c_buffer = kernel::static_buf!([u8; capsules_extra::isl29035::BUF_LEN]); + let isl29035 = kernel::static_buf!( + capsules_extra::isl29035::Isl29035< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A>, + > + ); + + (alarm, i2c_device, i2c_buffer, isl29035) + };}; +} + +#[macro_export] +macro_rules! ambient_light_component_static { + () => {{ + kernel::static_buf!(capsules_extra::ambient_light::AmbientLight<'static>) };}; } -pub struct Isl29035Component> { - i2c_mux: &'static MuxI2C<'static>, +pub struct Isl29035Component< + A: 'static + time::Alarm<'static>, + I: 'static + i2c::I2CMaster<'static>, +> { + i2c_mux: &'static MuxI2C<'static, I>, alarm_mux: &'static MuxAlarm<'static, A>, } -impl> Isl29035Component { - pub fn new(i2c: &'static MuxI2C<'static>, alarm: &'static MuxAlarm<'static, A>) -> Self { +impl, I: 'static + i2c::I2CMaster<'static>> + Isl29035Component +{ + pub fn new(i2c: &'static MuxI2C<'static, I>, alarm: &'static MuxAlarm<'static, A>) -> Self { Isl29035Component { i2c_mux: i2c, alarm_mux: alarm, @@ -61,98 +85,68 @@ impl> Isl29035Component { } } -// This should really be an option, such that you can create either -// an Isl29035 component or an AmbientLight component, but not both, -// such that trying to take the buffer out of an empty option leads to -// a panic explaining why. Right now it's possible for a board to make -// both components, which will conflict on the buffer. -pal - -static mut I2C_BUF: [u8; 3] = [0; 3]; - -impl> Component for Isl29035Component { +impl, I: 'static + i2c::I2CMaster<'static>> Component + for Isl29035Component +{ type StaticInput = ( &'static mut MaybeUninit>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; capsules_extra::isl29035::BUF_LEN]>, &'static mut MaybeUninit>>, ); - type Output = &'static Isl29035<'static, VirtualMuxAlarm<'static, A>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let isl29035_i2c = static_init!(I2CDevice, I2CDevice::new(self.i2c_mux, 0x44)); - let isl29035_virtual_alarm = static_init_half!( - static_buffer.0, - VirtualMuxAlarm<'static, A>, - VirtualMuxAlarm::new(self.alarm_mux) - ); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let isl29035_i2c = static_buffer.1.write(I2CDevice::new(self.i2c_mux, 0x44)); + let isl29035_i2c_buffer = static_buffer + .2 + .write([0; capsules_extra::isl29035::BUF_LEN]); + let isl29035_virtual_alarm = static_buffer.0.write(VirtualMuxAlarm::new(self.alarm_mux)); isl29035_virtual_alarm.setup(); - let isl29035 = static_init_half!( - static_buffer.1, - Isl29035<'static, VirtualMuxAlarm<'static, A>>, - Isl29035::new(isl29035_i2c, isl29035_virtual_alarm, &mut I2C_BUF) - ); + let isl29035 = static_buffer.3.write(Isl29035::new( + isl29035_i2c, + isl29035_virtual_alarm, + isl29035_i2c_buffer, + )); isl29035_i2c.set_client(isl29035); isl29035_virtual_alarm.set_alarm_client(isl29035); isl29035 } } -pub struct AmbientLightComponent> { +pub struct AmbientLightComponent> { board_kernel: &'static kernel::Kernel, driver_num: usize, - i2c_mux: &'static MuxI2C<'static>, - alarm_mux: &'static MuxAlarm<'static, A>, + light_sensor: &'static L, } -impl> AmbientLightComponent { +impl> AmbientLightComponent { pub fn new( board_kernel: &'static kernel::Kernel, driver_num: usize, - i2c: &'static MuxI2C<'static>, - alarm: &'static MuxAlarm<'static, A>, + light_sensor: &'static L, ) -> Self { AmbientLightComponent { - board_kernel: board_kernel, - driver_num: driver_num, - i2c_mux: i2c, - alarm_mux: alarm, + board_kernel, + driver_num, + light_sensor, } } } -impl> Component for AmbientLightComponent { - type StaticInput = ( - &'static mut MaybeUninit>, - &'static mut MaybeUninit>>, - ); +impl> Component for AmbientLightComponent { + type StaticInput = &'static mut MaybeUninit>; type Output = &'static AmbientLight<'static>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let isl29035_i2c = static_init!(I2CDevice, I2CDevice::new(self.i2c_mux, 0x44)); - let isl29035_virtual_alarm = static_init_half!( - static_buffer.0, - VirtualMuxAlarm<'static, A>, - VirtualMuxAlarm::new(self.alarm_mux) - ); - isl29035_virtual_alarm.setup(); - - let isl29035 = static_init_half!( - static_buffer.1, - Isl29035<'static, VirtualMuxAlarm<'static, A>>, - Isl29035::new(isl29035_i2c, isl29035_virtual_alarm, &mut I2C_BUF) - ); - isl29035_i2c.set_client(isl29035); - isl29035_virtual_alarm.set_alarm_client(isl29035); - let ambient_light = static_init!( - AmbientLight<'static>, - AmbientLight::new( - isl29035, - self.board_kernel.create_grant(self.driver_num, &grant_cap) - ) - ); - hil::sensors::AmbientLight::set_client(isl29035, ambient_light); + let ambient_light = static_buffer.write(AmbientLight::new( + self.light_sensor, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); + hil::sensors::AmbientLight::set_client(self.light_sensor, ambient_light); ambient_light } } diff --git a/boards/components/src/keyboard_hid.rs b/boards/components/src/keyboard_hid.rs new file mode 100644 index 0000000000..3b2883146a --- /dev/null +++ b/boards/components/src/keyboard_hid.rs @@ -0,0 +1,140 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Component for USB HID keyboard support. +//! +//! Usage +//! ----- +//! +//! ``` +//! let strings = static_init!( +//! [&str; 3], +//! [ +//! "Nordic Semiconductor", // Manufacturer +//! "nRF52840dk - TockOS", // Product +//! "serial0001", // Serial number +//! ] +//! ); +//! +//! let (keyboard_hid, keyboard_hid_driver) = components::keyboard_hid::KeyboardHidComponent::new( +//! board_kernel, +//! capsules_core::driver::KeyboardHid, +//! &nrf52840_peripherals.usbd, +//! 0x1915, // Nordic Semiconductor +//! 0x503a, +//! strings, +//! ) +//! .finalize(components::keyboard_hid_component_static!( +//! nrf52840::usbd::Usbd +//! )); +//! +//! keyboard_hid.enable(); +//! keyboard_hid.attach(); +//! ``` + +use core::mem::MaybeUninit; +use kernel::capabilities; +use kernel::component::Component; +use kernel::create_capability; +use kernel::hil; + +// Setup static space for the objects. +#[macro_export] +macro_rules! keyboard_hid_component_static { + ($U:ty $(,)?) => {{ + let hid = kernel::static_buf!(capsules_extra::usb::keyboard_hid::KeyboardHid<'static, $U>); + let driver = kernel::static_buf!( + capsules_extra::usb_hid_driver::UsbHidDriver< + 'static, + capsules_extra::usb::keyboard_hid::KeyboardHid<'static, $U>, + > + ); + let send_buffer = kernel::static_buf!([u8; 64]); + let recv_buffer = kernel::static_buf!([u8; 64]); + + (hid, driver, send_buffer, recv_buffer) + };}; +} + +pub type KeyboardHidComponentType = capsules_extra::usb_hid_driver::UsbHidDriver< + 'static, + capsules_extra::usb::keyboard_hid::KeyboardHid<'static, U>, +>; + +pub struct KeyboardHidComponent> { + board_kernel: &'static kernel::Kernel, + driver_num: usize, + usb: &'static U, + vendor_id: u16, + product_id: u16, + strings: &'static [&'static str; 3], +} + +impl> KeyboardHidComponent { + pub fn new( + board_kernel: &'static kernel::Kernel, + driver_num: usize, + usb: &'static U, + vendor_id: u16, + product_id: u16, + strings: &'static [&'static str; 3], + ) -> KeyboardHidComponent { + KeyboardHidComponent { + board_kernel, + driver_num, + usb, + vendor_id, + product_id, + strings, + } + } +} + +impl> Component for KeyboardHidComponent { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit< + capsules_extra::usb_hid_driver::UsbHidDriver< + 'static, + capsules_extra::usb::keyboard_hid::KeyboardHid<'static, U>, + >, + >, + &'static mut MaybeUninit<[u8; 64]>, + &'static mut MaybeUninit<[u8; 64]>, + ); + type Output = ( + &'static capsules_extra::usb::keyboard_hid::KeyboardHid<'static, U>, + &'static capsules_extra::usb_hid_driver::UsbHidDriver< + 'static, + capsules_extra::usb::keyboard_hid::KeyboardHid<'static, U>, + >, + ); + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let keyboard_hid = + s.0.write(capsules_extra::usb::keyboard_hid::KeyboardHid::new( + self.usb, + self.vendor_id, + self.product_id, + self.strings, + )); + self.usb.set_client(keyboard_hid); + + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + + let send_buffer = s.2.write([0; 64]); + let recv_buffer = s.3.write([0; 64]); + + let usb_hid_driver = s.1.write(capsules_extra::usb_hid_driver::UsbHidDriver::new( + Some(keyboard_hid), + send_buffer, + recv_buffer, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); + + keyboard_hid.set_client(usb_hid_driver); + + (keyboard_hid, usb_hid_driver) + } +} diff --git a/boards/components/src/kv.rs b/boards/components/src/kv.rs new file mode 100644 index 0000000000..ad69d7240e --- /dev/null +++ b/boards/components/src/kv.rs @@ -0,0 +1,250 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Components for KV stack capsules. + +use capsules_extra::kv_driver::KVStoreDriver; +use capsules_extra::kv_store_permissions::KVStorePermissions; +use capsules_extra::tickv::{KVSystem, KeyType}; +use capsules_extra::tickv_kv_store::TicKVKVStore; +use capsules_extra::virtual_kv::{MuxKVPermissions, VirtualKVPermissions}; +use core::mem::MaybeUninit; +use kernel::capabilities; +use kernel::component::Component; +use kernel::create_capability; +use kernel::hil; + +/////////////////////// +// KV Userspace Driver +/////////////////////// + +#[macro_export] +macro_rules! kv_driver_component_static { + ($V:ty $(,)?) => {{ + let kv = kernel::static_buf!(capsules_extra::kv_driver::KVStoreDriver<'static, $V>); + let key_buffer = kernel::static_buf!([u8; 64]); + let value_buffer = kernel::static_buf!([u8; 256]); + + (kv, key_buffer, value_buffer) + };}; +} + +pub type KVDriverComponentType = capsules_extra::kv_driver::KVStoreDriver<'static, V>; + +pub struct KVDriverComponent + 'static> { + kv: &'static V, + board_kernel: &'static kernel::Kernel, + driver_num: usize, +} + +impl> KVDriverComponent { + pub fn new(kv: &'static V, board_kernel: &'static kernel::Kernel, driver_num: usize) -> Self { + Self { + kv, + board_kernel, + driver_num, + } + } +} + +impl> Component for KVDriverComponent { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; 64]>, + &'static mut MaybeUninit<[u8; 256]>, + ); + type Output = &'static KVStoreDriver<'static, V>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + + let key_buffer = static_buffer.1.write([0; 64]); + let value_buffer = static_buffer.2.write([0; 256]); + + let driver = static_buffer.0.write(KVStoreDriver::new( + self.kv, + key_buffer, + value_buffer, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); + self.kv.set_client(driver); + driver + } +} + +////////////// +// KV Mux +////////////// + +#[macro_export] +macro_rules! kv_permissions_mux_component_static { + ($V:ty $(,)?) => {{ + let mux = kernel::static_buf!(capsules_extra::virtual_kv::MuxKVPermissions<'static, $V>); + + mux + };}; +} + +pub type KVPermissionsMuxComponentType = + capsules_extra::kv_store_permissions::KVStorePermissions<'static, V>; + +pub struct KVPermissionsMuxComponent + 'static> { + kv: &'static V, +} + +impl> KVPermissionsMuxComponent { + pub fn new(kv: &'static V) -> KVPermissionsMuxComponent { + Self { kv } + } +} + +impl + 'static> Component for KVPermissionsMuxComponent { + type StaticInput = &'static mut MaybeUninit>; + type Output = &'static MuxKVPermissions<'static, V>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let mux = static_buffer.write(MuxKVPermissions::new(self.kv)); + self.kv.set_client(mux); + mux + } +} + +///////////////////// +// Virtual KV Object +///////////////////// + +#[macro_export] +macro_rules! virtual_kv_permissions_component_static { + ($V:ty $(,)?) => {{ + let virtual_kv = + kernel::static_buf!(capsules_extra::virtual_kv::VirtualKVPermissions<'static, $V>); + + virtual_kv + };}; +} + +pub type VirtualKVPermissionsComponentType = + capsules_extra::virtual_kv::VirtualKVPermissions<'static, V>; + +pub struct VirtualKVPermissionsComponent + 'static> { + mux_kv: &'static MuxKVPermissions<'static, V>, +} + +impl> VirtualKVPermissionsComponent { + pub fn new(mux_kv: &'static MuxKVPermissions<'static, V>) -> VirtualKVPermissionsComponent { + Self { mux_kv } + } +} + +impl + 'static> Component for VirtualKVPermissionsComponent { + type StaticInput = &'static mut MaybeUninit>; + type Output = &'static VirtualKVPermissions<'static, V>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let virtual_kv = static_buffer.write(VirtualKVPermissions::new(self.mux_kv)); + virtual_kv.setup(); + virtual_kv + } +} + +///////////////////// +// KV Store Permissions +///////////////////// + +#[macro_export] +macro_rules! kv_store_permissions_component_static { + ($V:ty $(,)?) => {{ + let buffer = kernel::static_buf!([u8; capsules_extra::kv_store_permissions::HEADER_LENGTH]); + let kv_store = kernel::static_buf!( + capsules_extra::kv_store_permissions::KVStorePermissions<'static, $V> + ); + + (kv_store, buffer) + };}; +} + +pub type KVStorePermissionsComponentType = + capsules_extra::kv_store_permissions::KVStorePermissions<'static, V>; + +pub struct KVStorePermissionsComponent + 'static> { + kv: &'static V, +} + +impl + 'static> KVStorePermissionsComponent { + pub fn new(kv: &'static V) -> Self { + Self { kv } + } +} + +impl + 'static> Component for KVStorePermissionsComponent { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; capsules_extra::kv_store_permissions::HEADER_LENGTH]>, + ); + type Output = &'static KVStorePermissions<'static, V>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let buffer = static_buffer + .1 + .write([0; capsules_extra::kv_store_permissions::HEADER_LENGTH]); + + let kv_store_permissions = static_buffer + .0 + .write(KVStorePermissions::new(self.kv, buffer)); + + self.kv.set_client(kv_store_permissions); + + kv_store_permissions + } +} + +///////////////////// +// TicKV KV Store +///////////////////// + +#[macro_export] +macro_rules! tickv_kv_store_component_static { + ($K:ty, $T:ty $(,)?) => {{ + let key = kernel::static_buf!($T); + let kv_store = + kernel::static_buf!(capsules_extra::tickv_kv_store::TicKVKVStore<'static, $K, $T>); + + (kv_store, key) + };}; +} + +pub type TicKVKVStoreComponentType = + capsules_extra::tickv_kv_store::TicKVKVStore<'static, K, T>; + +pub struct TicKVKVStoreComponent, T: 'static + KeyType> { + kv_system: &'static K, +} + +impl, T: 'static + KeyType> TicKVKVStoreComponent { + pub fn new(kv_system: &'static K) -> Self { + Self { kv_system } + } +} + +impl, T: 'static + KeyType + Default> Component + for TicKVKVStoreComponent +{ + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit, + ); + type Output = &'static TicKVKVStore<'static, K, T>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let key_buf = static_buffer.1.write(T::default()); + + let kv_store = static_buffer + .0 + .write(TicKVKVStore::new(self.kv_system, key_buf)); + + self.kv_system.set_client(kv_store); + + kv_store + } +} diff --git a/boards/components/src/l3gd20.rs b/boards/components/src/l3gd20.rs index ff0befcae3..49180dedd9 100644 --- a/boards/components/src/l3gd20.rs +++ b/boards/components/src/l3gd20.rs @@ -1,84 +1,102 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for the L3GD20 sensor. //! -//! SPI Interface +//! Uses a SPI Interface. //! //! Usage //! ----- //! ```rust -//! let l3gd20 = components::l3gd20::L3gd20SpiComponent::new().finalize( -//! components::l3gd20_spi_component_helper!( -//! // spi type -//! stm32f429zi::spi::Spi, -//! // chip select -//! stm32f429zi::gpio::PinId::PE03, -//! // spi mux -//! spi_mux -//! ) -//! ); +//! let l3gd20 = components::l3gd20::L3gd20Component::new(spi_mux, stm32f429zi::gpio::PinId::PE03).finalize( +//! components::l3gd20_component_static!(stm32f429zi::spi::Spi)); //! ``` -use capsules::l3gd20::L3gd20Spi; -use capsules::virtual_spi::VirtualSpiMasterDevice; -use core::marker::PhantomData; + +use capsules_core::virtualizers::virtual_spi::{MuxSpiMaster, VirtualSpiMasterDevice}; +use capsules_extra::l3gd20::L3gd20Spi; use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; +use kernel::create_capability; use kernel::hil::spi; use kernel::hil::spi::SpiMasterDevice; -use kernel::{create_capability, static_init_half}; // Setup static space for the objects. #[macro_export] -macro_rules! l3gd20_spi_component_helper { - ($A:ty, $select:expr, $spi_mux:expr $(,)?) => {{ - use capsules::l3gd20::L3gd20Spi; - use capsules::virtual_spi::VirtualSpiMasterDevice; - use core::mem::MaybeUninit; - let mut l3gd20_spi: &'static capsules::virtual_spi::VirtualSpiMasterDevice<'static, $A> = - components::spi::SpiComponent::new($spi_mux, $select) - .finalize(components::spi_component_helper!($A)); - static mut l3gd20spi: MaybeUninit> = MaybeUninit::uninit(); - (&mut l3gd20_spi, &mut l3gd20spi) +macro_rules! l3gd20_component_static { + ($S:ty $(,)?) => {{ + let txbuffer = kernel::static_buf!([u8; capsules_extra::l3gd20::TX_BUF_LEN]); + let rxbuffer = kernel::static_buf!([u8; capsules_extra::l3gd20::RX_BUF_LEN]); + + let spi = kernel::static_buf!( + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice<'static, $S> + ); + let l3gd20spi = kernel::static_buf!( + capsules_extra::l3gd20::L3gd20Spi< + 'static, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice<'static, $S>, + > + ); + + (spi, l3gd20spi, txbuffer, rxbuffer) };}; } -pub struct L3gd20SpiComponent { - _select: PhantomData, +pub type L3gd20ComponentType = capsules_extra::l3gd20::L3gd20Spi<'static, S>; + +pub struct L3gd20Component> { + spi_mux: &'static MuxSpiMaster<'static, S>, + chip_select: S::ChipSelect, board_kernel: &'static kernel::Kernel, driver_num: usize, } -impl L3gd20SpiComponent { - pub fn new(board_kernel: &'static kernel::Kernel, driver_num: usize) -> L3gd20SpiComponent { - L3gd20SpiComponent { - _select: PhantomData, - board_kernel: board_kernel, +impl> L3gd20Component { + pub fn new( + spi_mux: &'static MuxSpiMaster<'static, S>, + chip_select: S::ChipSelect, + board_kernel: &'static kernel::Kernel, + driver_num: usize, + ) -> L3gd20Component { + L3gd20Component { + spi_mux, + chip_select, + board_kernel, driver_num, } } } -impl Component for L3gd20SpiComponent { +impl> Component for L3gd20Component { type StaticInput = ( - &'static VirtualSpiMasterDevice<'static, S>, - &'static mut MaybeUninit>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit>>, + &'static mut MaybeUninit<[u8; capsules_extra::l3gd20::TX_BUF_LEN]>, + &'static mut MaybeUninit<[u8; capsules_extra::l3gd20::RX_BUF_LEN]>, ); - type Output = &'static L3gd20Spi<'static>; + type Output = &'static L3gd20Spi<'static, VirtualSpiMasterDevice<'static, S>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); let grant = self.board_kernel.create_grant(self.driver_num, &grant_cap); - let l3gd20 = static_init_half!( - static_buffer.1, - L3gd20Spi<'static>, - L3gd20Spi::new( - static_buffer.0, - &mut capsules::l3gd20::TXBUFFER, - &mut capsules::l3gd20::RXBUFFER, - grant - ) - ); - static_buffer.0.set_client(l3gd20); + let spi_device = static_buffer + .0 + .write(VirtualSpiMasterDevice::new(self.spi_mux, self.chip_select)); + spi_device.setup(); + + let txbuffer = static_buffer + .2 + .write([0; capsules_extra::l3gd20::TX_BUF_LEN]); + let rxbuffer = static_buffer + .3 + .write([0; capsules_extra::l3gd20::RX_BUF_LEN]); + + let l3gd20 = static_buffer + .1 + .write(L3gd20Spi::new(spi_device, txbuffer, rxbuffer, grant)); + spi_device.set_client(l3gd20); // TODO verify SPI return value let _ = l3gd20.configure(); diff --git a/boards/components/src/led.rs b/boards/components/src/led.rs index 26732394e7..7b971e5e95 100644 --- a/boards/components/src/led.rs +++ b/boards/components/src/led.rs @@ -1,29 +1,29 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for collections of LEDs. //! //! Usage //! ----- //! ```rust -//! let led = components::led::LedsComponent::new(components::led_component_helper!( +//! let led = components::led::LedsComponent::new().finalize(components::led_component_static!( //! kernel::hil::led::LedLow<'static, sam4l::gpio::GPIOPin>, //! LedLow::new(&sam4l::gpio::PORT[LED_RED_PIN]), //! LedLow::new(&sam4l::gpio::PORT[LED_GREEN_PIN]), //! LedLow::new(&sam4l::gpio::PORT[LED_BLUE_PIN]), -//! )) -//! .finalize(led_component_buf!(kernel::hil::led::LedLow<'static, sam4l::gpio::GPIOPin>)); +//! )); //! ``` -use capsules::led::LedDriver; +use capsules_core::led::LedDriver; use core::marker::PhantomData; use core::mem::MaybeUninit; use kernel::component::Component; use kernel::hil::led::Led; -use kernel::static_init_half; #[macro_export] -macro_rules! led_component_helper { +macro_rules! led_component_static { ($Led:ty, $($L:expr),+ $(,)?) => {{ - use capsules::led::LedDriver; - use core::mem::MaybeUninit; use kernel::count_expressions; use kernel::static_init; const NUM_LEDS: usize = count_expressions!($($L),+); @@ -39,11 +39,13 @@ macro_rules! led_component_helper { ] ); - static mut BUF: MaybeUninit> = MaybeUninit::uninit(); - (&mut BUF, arr) + let led = kernel::static_buf!( capsules_core::led::LedDriver<'static, $Led, NUM_LEDS>); + (led, arr) };}; } +pub type LedsComponentType = LedDriver<'static, L, NUMLEDS>; + pub struct LedsComponent { _phantom: PhantomData, } @@ -63,11 +65,7 @@ impl Component for LedsComponent; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - static_init_half!( - static_buffer.0, - LedDriver<'static, L, NUM_LEDS>, - LedDriver::new(static_buffer.1) - ) + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + static_buffer.0.write(LedDriver::new(static_buffer.1)) } } diff --git a/boards/components/src/led_matrix.rs b/boards/components/src/led_matrix.rs index b33dcd1d6e..2e35d609e7 100644 --- a/boards/components/src/led_matrix.rs +++ b/boards/components/src/led_matrix.rs @@ -1,30 +1,39 @@ -//! Components for martices of LEDs. +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Component for matrices of LEDs. //! //! Usage //! ----- //! ```rust -//! let led = components::led_matrix_component_helper!( -//! nrf52833::gpio::GPIOPin, -//! nrf52::rtc::Rtc<'static>, +//! let led_matrix = components::led_matrix::LedMatrixComponent::new( //! mux_alarm, -//! @fps => 60, -//! @cols => kernel::hil::gpio::ActivationMode::ActiveLow, -//! &base_peripherals.gpio_port[LED_MATRIX_COLS[0]], -//! &base_peripherals.gpio_port[LED_MATRIX_COLS[1]], -//! &base_peripherals.gpio_port[LED_MATRIX_COLS[2]], -//! &base_peripherals.gpio_port[LED_MATRIX_COLS[3]], -//! &base_peripherals.gpio_port[LED_MATRIX_COLS[4]], -//! @rows => kernel::hil::gpio::ActivationMode::ActiveHigh, -//! &base_peripherals.gpio_port[LED_MATRIX_ROWS[0]], -//! &base_peripherals.gpio_port[LED_MATRIX_ROWS[1]], -//! &base_peripherals.gpio_port[LED_MATRIX_ROWS[2]], -//! &base_peripherals.gpio_port[LED_MATRIX_ROWS[3]], -//! &base_peripherals.gpio_port[LED_MATRIX_ROWS[4]] -//! +//! components::led_line_component_static!( +//! nrf52833::gpio::GPIOPin, +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_COLS[0]], +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_COLS[1]], +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_COLS[2]], +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_COLS[3]], +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_COLS[4]], +//! ), +//! components::led_line_component_static!( +//! nrf52833::gpio::GPIOPin, +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_ROWS[0]], +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_ROWS[1]], +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_ROWS[2]], +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_ROWS[3]], +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_ROWS[4]], +//! ), +//! kernel::hil::gpio::ActivationMode::ActiveLow, +//! kernel::hil::gpio::ActivationMode::ActiveHigh, +//! 60, //! ) -//! .finalize(components::led_matrix_component_buf!( +//! .finalize(components::led_matrix_component_static!( //! nrf52833::gpio::GPIOPin, -//! nrf52::rtc::Rtc<'static> +//! nrf52::rtc::Rtc<'static>, +//! 5, +//! 5 //! )); //! ``` //! @@ -34,7 +43,7 @@ //! ```rust //! let led = components::led_matrix_led!( //! nrf52::gpio::GPIOPin<'static>, -//! capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, +//! capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, //! led, //! 1, //! 2 @@ -47,7 +56,7 @@ //! ```rust //! let leds = components::led_matrix_leds!( //! nrf52::gpio::GPIOPin<'static>, -//! capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, +//! capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, //! led, //! (0, 0), //! (1, 0), @@ -60,28 +69,34 @@ //! ``` //! -use capsules::led_matrix::LedMatrixDriver; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_extra::led_matrix::LedMatrixDriver; use core::mem::MaybeUninit; use kernel::component::Component; use kernel::hil::gpio::{ActivationMode, Pin}; use kernel::hil::time::Alarm; -use kernel::static_init_half; #[macro_export] -macro_rules! led_matrix_component_helper { - ($Pin:ty, $A: ty, $alarm_mux: expr, @fps => $refresh_rate: expr, @cols => $col_active:expr, $($C:expr),+, @rows => $row_active:expr, $($R:expr),+ $(,)?) => {{ - use kernel::count_expressions; +macro_rules! led_matrix_component_static { + ($Pin:ty, $A: ty, $num_cols: literal, $num_rows: literal $(,)?) => {{ + let buffer = kernel::static_buf!([u8; $num_cols * $num_rows / 8 + 1]); + let alarm = kernel::static_buf!( + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A> + ); + let led = kernel::static_buf!( + capsules_extra::led_matrix::LedMatrixDriver< + 'static, + $Pin, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A>, + > + ); - const NUM_COLS: usize = count_expressions!($($C),+); - const NUM_ROWS: usize = count_expressions!($($R),+); - static mut BUFFER: [u8; NUM_COLS*NUM_ROWS / 8 + 1] = [0; NUM_COLS*NUM_ROWS / 8 + 1]; - components::led_matrix::LedMatrixComponent::new ($alarm_mux, $crate::led_line_component_helper!($Pin, $($C,)+), $crate::led_line_component_helper!($Pin, $($R,)+), $col_active, $row_active, $refresh_rate, &mut BUFFER) + (alarm, led, buffer) };}; } #[macro_export] -macro_rules! led_line_component_helper { +macro_rules! led_line_component_static { ($Pin:ty, $($L:expr),+ $(,)?) => {{ use kernel::count_expressions; use kernel::static_init; @@ -101,24 +116,10 @@ macro_rules! led_line_component_helper { };}; } -#[macro_export] -macro_rules! led_matrix_component_buf { - ($Pin:ty, $A: ty $(,)?) => {{ - use capsules::led_matrix::LedMatrixDriver; - use capsules::virtual_alarm::VirtualMuxAlarm; - use core::mem::MaybeUninit; - - static mut alarm: MaybeUninit> = MaybeUninit::uninit(); - static mut led: MaybeUninit>> = - MaybeUninit::uninit(); - (&mut alarm, &mut led) - };}; -} - #[macro_export] macro_rules! led_matrix_led { ($Pin:ty, $A: ty, $led_matrix: expr, $col: expr, $row: expr) => {{ - use capsules::led_matrix::LedMatrixLed; + use capsules_extra::led_matrix::LedMatrixLed; static_init!( LedMatrixLed<'static, $Pin, $A>, LedMatrixLed::new($led_matrix, $col, $row) @@ -129,7 +130,7 @@ macro_rules! led_matrix_led { #[macro_export] macro_rules! led_matrix_leds { ($Pin:ty, $A: ty, $led_matrix: expr, $(($col: expr, $row: expr)),+) => {{ - use capsules::led_matrix::LedMatrixLed; + use capsules_extra::led_matrix::LedMatrixLed; use kernel::count_expressions; const NUM_LEDS: usize = count_expressions!($(($col, $row)),+); @@ -143,25 +144,36 @@ macro_rules! led_matrix_leds { };}; } -pub struct LedMatrixComponent> { - col: &'static [&'static L], - row: &'static [&'static L], +pub struct LedMatrixComponent< + L: 'static + Pin, + A: 'static + Alarm<'static>, + const NUM_COLS: usize, + const NUM_ROWS: usize, + const NUM_LED_BITS: usize, +> { + alarm_mux: &'static MuxAlarm<'static, A>, + col: &'static [&'static L; NUM_COLS], + row: &'static [&'static L; NUM_ROWS], col_active: ActivationMode, row_active: ActivationMode, refresh_rate: usize, - buffer: &'static mut [u8], - alarm_mux: &'static MuxAlarm<'static, A>, } -impl> LedMatrixComponent { +impl< + L: 'static + Pin, + A: 'static + Alarm<'static>, + const NUM_COLS: usize, + const NUM_ROWS: usize, + const NUM_LED_BITS: usize, + > LedMatrixComponent +{ pub fn new( alarm_mux: &'static MuxAlarm<'static, A>, - col: &'static [&'static L], - row: &'static [&'static L], + col: &'static [&'static L; NUM_COLS], + row: &'static [&'static L; NUM_ROWS], col_active: ActivationMode, row_active: ActivationMode, refresh_rate: usize, - buffer: &'static mut [u8], ) -> Self { Self { alarm_mux, @@ -170,39 +182,40 @@ impl> LedMatrixComponent { col_active, row_active, refresh_rate, - buffer, } } } -impl> Component for LedMatrixComponent { +impl< + L: 'static + Pin, + A: 'static + Alarm<'static>, + const NUM_COLS: usize, + const NUM_ROWS: usize, + const NUM_LED_BITS: usize, + > Component for LedMatrixComponent +{ type StaticInput = ( &'static mut MaybeUninit>, &'static mut MaybeUninit>>, + &'static mut MaybeUninit<[u8; NUM_LED_BITS]>, ); type Output = &'static LedMatrixDriver<'static, L, VirtualMuxAlarm<'static, A>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let led_alarm = static_init_half!( - static_buffer.0, - VirtualMuxAlarm<'static, A>, - VirtualMuxAlarm::new(self.alarm_mux) - ); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let led_alarm = static_buffer.0.write(VirtualMuxAlarm::new(self.alarm_mux)); led_alarm.setup(); - let led_matrix = static_init_half!( - static_buffer.1, - LedMatrixDriver<'static, L, VirtualMuxAlarm<'static, A>>, - LedMatrixDriver::new( - self.col, - self.row, - self.buffer, - led_alarm, - self.col_active, - self.row_active, - self.refresh_rate - ) - ); + let buffer = static_buffer.2.write([0; NUM_LED_BITS]); + + let led_matrix = static_buffer.1.write(LedMatrixDriver::new( + self.col, + self.row, + buffer, + led_alarm, + self.col_active, + self.row_active, + self.refresh_rate, + )); led_alarm.set_alarm_client(led_matrix); diff --git a/boards/components/src/lib.rs b/boards/components/src/lib.rs index 09b421a266..7f0e298c11 100644 --- a/boards/components/src/lib.rs +++ b/boards/components/src/lib.rs @@ -1,61 +1,96 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + #![no_std] pub mod adc; pub mod adc_microphone; +pub mod aes; +pub mod air_quality; pub mod alarm; pub mod analog_comparator; +pub mod apds9960; pub mod app_flash_driver; +pub mod appid; +pub mod ble; +pub mod bme280; +pub mod bmm150; +pub mod bmp280; pub mod bus; pub mod button; +pub mod can; +pub mod ccs811; pub mod cdc; pub mod console; pub mod crc; pub mod ctap; +pub mod dac; +pub mod date_time; pub mod debug_queue; pub mod debug_writer; -pub mod digest; +pub mod eui64; pub mod flash; +pub mod fm25cl; pub mod ft6x06; pub mod fxos8700; pub mod gpio; pub mod hd44780; pub mod hmac; +pub mod hs3003; pub mod hts221; pub mod humidity; pub mod i2c; pub mod ieee802154; pub mod isl29035; +pub mod keyboard_hid; +pub mod kv; pub mod l3gd20; pub mod led; pub mod led_matrix; pub mod lldb; +pub mod loader; +pub mod lpm013m126; +pub mod lps22hb; +pub mod lps25hb; pub mod lsm303agr; pub mod lsm303dlhc; pub mod lsm6dsox; +pub mod ltc294x; pub mod mlx90614; pub mod mx25r6435f; pub mod ninedof; pub mod nonvolatile_storage; pub mod nrf51822; pub mod panic_button; +pub mod pressure; pub mod process_console; pub mod process_printer; +pub mod proximity; +pub mod pwm; +pub mod rf233; pub mod rng; pub mod sched; pub mod screen; pub mod segger_rtt; +pub mod sh1106; pub mod sha; pub mod sht3x; +pub mod sht4x; pub mod si7021; +pub mod siphash; pub mod sound_pressure; pub mod spi; +pub mod ssd1306; pub mod st77xx; pub mod temperature; pub mod temperature_rp2040; pub mod temperature_stm; pub mod test; pub mod text_screen; +pub mod thread_network; pub mod tickv; pub mod touch; pub mod udp_driver; pub mod udp_mux; +pub mod usb; diff --git a/boards/components/src/lldb.rs b/boards/components/src/lldb.rs index 2e6e8d8589..382296037f 100644 --- a/boards/components/src/lldb.rs +++ b/boards/components/src/lldb.rs @@ -1,26 +1,49 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for LowLevelDebug //! -//! This provides one Component, LowLevelDebugComponent, which provides the LowLevelDebug -//! driver---a driver that can prints messages to the serial port relying on only `command`s from -//! userspace. It is particularly useful for board or runtime bringup when more complex operations -//! (allow and subscribe) may still not be working. +//! This provides one Component, LowLevelDebugComponent, which provides the +//! LowLevelDebug driver---a driver that can prints messages to the serial port +//! relying on only `command`s from userspace. It is particularly useful for +//! board or runtime bringup when more complex operations (allow and subscribe) +//! may still not be working. //! //! Usage //! ----- //! ```rust -//! let lldb = LowLevelDebugComponent::new(board_kernel, uart_mux).finalize(()); +//! let lldb = LowLevelDebugComponent::new(board_kernel, uart_mux) +//! .finalize(components::low_level_debug_component_static!()); //! ``` // Author: Amit Levy // Last modified: 12/04/2019 -use capsules::low_level_debug; -use capsules::virtual_uart::{MuxUart, UartDevice}; +use capsules_core::low_level_debug::LowLevelDebug; +use capsules_core::virtualizers::virtual_uart::{MuxUart, UartDevice}; +use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; use kernel::hil; -use kernel::static_init; + +#[macro_export] +macro_rules! low_level_debug_component_static { + () => {{ + let uart = + kernel::static_buf!(capsules_core::virtualizers::virtual_uart::UartDevice<'static>); + let buffer = kernel::static_buf!([u8; capsules_core::low_level_debug::BUF_LEN]); + let lldb = kernel::static_buf!( + capsules_core::low_level_debug::LowLevelDebug< + 'static, + capsules_core::virtualizers::virtual_uart::UartDevice<'static>, + > + ); + + (uart, buffer, lldb) + };}; +} pub struct LowLevelDebugComponent { board_kernel: &'static kernel::Kernel, @@ -35,34 +58,34 @@ impl LowLevelDebugComponent { uart_mux: &'static MuxUart, ) -> LowLevelDebugComponent { LowLevelDebugComponent { - board_kernel: board_kernel, - driver_num: driver_num, - uart_mux: uart_mux, + board_kernel, + driver_num, + uart_mux, } } } impl Component for LowLevelDebugComponent { - type StaticInput = (); - type Output = &'static low_level_debug::LowLevelDebug<'static, UartDevice<'static>>; + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; capsules_core::low_level_debug::BUF_LEN]>, + &'static mut MaybeUninit>>, + ); + type Output = &'static LowLevelDebug<'static, UartDevice<'static>>; - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { + fn finalize(self, s: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - // Create virtual device for console. - let lldb_uart = static_init!(UartDevice, UartDevice::new(self.uart_mux, true)); + let lldb_uart = s.0.write(UartDevice::new(self.uart_mux, true)); lldb_uart.setup(); - static mut MYBUF: [u8; low_level_debug::BUF_LEN] = [0; low_level_debug::BUF_LEN]; + let buffer = s.1.write([0; capsules_core::low_level_debug::BUF_LEN]); - let lldb = static_init!( - low_level_debug::LowLevelDebug<'static, UartDevice<'static>>, - low_level_debug::LowLevelDebug::new( - &mut MYBUF, - lldb_uart, - self.board_kernel.create_grant(self.driver_num, &grant_cap) - ) - ); + let lldb = s.2.write(LowLevelDebug::new( + buffer, + lldb_uart, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); hil::uart::Transmit::set_transmit_client(lldb_uart, lldb); lldb diff --git a/boards/components/src/loader/mod.rs b/boards/components/src/loader/mod.rs new file mode 100644 index 0000000000..478c9d71fc --- /dev/null +++ b/boards/components/src/loader/mod.rs @@ -0,0 +1,5 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +pub mod sequential; diff --git a/boards/components/src/loader/sequential.rs b/boards/components/src/loader/sequential.rs new file mode 100644 index 0000000000..635512ea56 --- /dev/null +++ b/boards/components/src/loader/sequential.rs @@ -0,0 +1,115 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Component for creating a sequential process loader. +//! +//! `ProcessLoaderSequentialComponent` uses the standard Tock assumptions about +//! where processes are stored in flash and what RAM is allocated for process +//! use. + +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::deferred_call::DeferredCallClient; +use kernel::platform::chip::Chip; +use kernel::process::ProcessLoadingAsync; + +#[macro_export] +macro_rules! process_loader_sequential_component_static { + ($C:ty, $NUMPROCS:expr $(,)?) => {{ + let loader = kernel::static_buf!(kernel::process::SequentialProcessLoaderMachine< + $C, + >); + let process_binary_array = kernel::static_buf!( + [Option; $NUMPROCS] + ); + + (loader, process_binary_array) + };}; +} + +pub type ProcessLoaderSequentialComponentType = + kernel::process::SequentialProcessLoaderMachine<'static, C>; + +pub struct ProcessLoaderSequentialComponent { + checker: &'static kernel::process::ProcessCheckerMachine, + processes: &'static mut [Option<&'static dyn kernel::process::Process>], + kernel: &'static kernel::Kernel, + chip: &'static C, + fault_policy: &'static dyn kernel::process::ProcessFaultPolicy, + appid_policy: &'static dyn kernel::process_checker::AppIdPolicy, +} + +impl ProcessLoaderSequentialComponent { + pub fn new( + checker: &'static kernel::process::ProcessCheckerMachine, + processes: &'static mut [Option<&'static dyn kernel::process::Process>], + kernel: &'static kernel::Kernel, + chip: &'static C, + fault_policy: &'static dyn kernel::process::ProcessFaultPolicy, + appid_policy: &'static dyn kernel::process_checker::AppIdPolicy, + ) -> Self { + Self { + checker, + processes, + kernel, + chip, + fault_policy, + appid_policy, + } + } +} + +impl Component for ProcessLoaderSequentialComponent { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[Option; NUM_PROCS]>, + ); + + type Output = &'static kernel::process::SequentialProcessLoaderMachine<'static, C>; + + fn finalize(mut self, s: Self::StaticInput) -> Self::Output { + let proc_manage_cap = + kernel::create_capability!(kernel::capabilities::ProcessManagementCapability); + + const ARRAY_REPEAT_VALUE: Option = None; + let process_binary_array = s.1.write([ARRAY_REPEAT_VALUE; NUM_PROCS]); + + // These symbols are defined in the standard Tock linker script. + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + } + + let loader = unsafe { + s.0.write(kernel::process::SequentialProcessLoaderMachine::new( + self.checker, + *core::ptr::addr_of_mut!(self.processes), + process_binary_array, + self.kernel, + self.chip, + core::slice::from_raw_parts( + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, + ), + core::slice::from_raw_parts_mut( + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, + ), + self.fault_policy, + self.appid_policy, + &proc_manage_cap, + )) + }; + self.checker.set_client(loader); + loader.register(); + loader.start(); + loader + } +} diff --git a/boards/components/src/lpm013m126.rs b/boards/components/src/lpm013m126.rs new file mode 100644 index 0000000000..e384d49ea8 --- /dev/null +++ b/boards/components/src/lpm013m126.rs @@ -0,0 +1,210 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Component for the Japan Display LPM013M126 display. +//! +//! Usage +//! ----- +//! +//! ```rust +//! // Optional +//! let spi_device = static_init!( +//! VirtualSpiMasterDevice<'static, nrf52840::spi::SPIM>, +//! VirtualSpiMasterDevice::new( +//! mux_spi, +//! &nrf52840_peripherals.gpio_port[Pin::P0_05], // CS pin +//! ), +//! ); +//! let display +//! = components::lpm013m126::Lpm013m126Component::new( +//! disp_pin, +//! extcomin_pin, +//! alarm_mux, +//! ) +//! .finalize( +//! components::lpm013m126_component_static!( +//! nrf52840::rtc::Rtc<'static>, +//! nrf52840::gpio::GPIOPin, +//! VirtualSpiMasterDevice<'static, nrf52840::spi::SPIM>, +//! spi_mux, +//! cs_pin, +//! ) +//! ); +//! display.initialize().unwrap(); +//! // wait for `ScreenClient::screen_is_ready` callback +//! ``` + +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_spi::{MuxSpiMaster, VirtualSpiMasterDevice}; +use capsules_extra::lpm013m126::Lpm013m126; +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::hil::gpio; +use kernel::hil::spi::{SpiMaster, SpiMasterDevice}; +use kernel::hil::time::Alarm; + +/// CS is active high +pub struct Inverted<'a, P: gpio::Pin>(pub &'a P); + +impl<'a, P: gpio::Pin> gpio::Configure for Inverted<'a, P> { + fn configuration(&self) -> gpio::Configuration { + self.0.configuration() + } + fn make_output(&self) -> gpio::Configuration { + self.0.make_output() + } + fn disable_output(&self) -> gpio::Configuration { + self.0.disable_output() + } + fn make_input(&self) -> gpio::Configuration { + self.0.make_input() + } + fn disable_input(&self) -> gpio::Configuration { + self.0.disable_input() + } + fn deactivate_to_low_power(&self) { + self.0.deactivate_to_low_power() + } + fn set_floating_state(&self, _: gpio::FloatingState) { + unimplemented!() // not sure what it looks like with inversion + } + fn floating_state(&self) -> gpio::FloatingState { + unimplemented!() // not sure what it looks like with inversion + } +} + +impl<'a, P: gpio::Pin> gpio::Output for Inverted<'a, P> { + fn set(&self) { + self.0.clear() + } + fn clear(&self) { + self.0.set() + } + fn toggle(&self) -> bool { + self.0.toggle() + } +} + +impl<'a, P: gpio::Pin> gpio::Input for Inverted<'a, P> { + fn read(&self) -> bool { + !self.0.read() + } +} + +/// Setup static space for the driver and its requirements. +#[macro_export] +macro_rules! lpm013m126_component_static { + ($A:ty, $P:ty, $S:ty $(,)?) => {{ + let alarm = kernel::static_buf!( + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A> + ); + let buffer = kernel::static_buf!([u8; capsules_extra::lpm013m126::BUF_LEN]); + let chip_select = kernel::static_buf!(components::lpm013m126::Inverted<'static, $P>); + let spi_device = kernel::static_buf!( + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice<'static, $S> + ); + let lpm013m126 = kernel::static_buf!( + capsules_extra::lpm013m126::Lpm013m126< + 'static, + VirtualMuxAlarm<'static, $A>, + $P, + VirtualSpiMasterDevice<'static, $S>, + > + ); + + (alarm, buffer, chip_select, spi_device, lpm013m126) + }}; +} + +pub struct Lpm013m126Component +where + A: 'static + Alarm<'static>, + P: 'static + gpio::Pin, + S: 'static + SpiMaster<'static>, +{ + spi: &'static MuxSpiMaster<'static, S>, + chip_select: &'static P, + disp: &'static P, + extcomin: &'static P, + alarm_mux: &'static MuxAlarm<'static, A>, +} + +impl Lpm013m126Component +where + A: 'static + Alarm<'static>, + P: 'static + gpio::Pin, + S: 'static + SpiMaster<'static>, +{ + pub fn new( + spi: &'static MuxSpiMaster<'static, S>, + + chip_select: &'static P, + disp: &'static P, + extcomin: &'static P, + alarm_mux: &'static MuxAlarm<'static, A>, + ) -> Self { + Self { + spi, + chip_select, + disp, + extcomin, + alarm_mux, + } + } +} + +impl Component for Lpm013m126Component +where + A: 'static + Alarm<'static>, + P: 'static + gpio::Pin, + S: 'static + SpiMaster<'static, ChipSelect = &'static mut Inverted<'static, P>>, +{ + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; capsules_extra::lpm013m126::BUF_LEN]>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit< + Lpm013m126<'static, VirtualMuxAlarm<'static, A>, P, VirtualSpiMasterDevice<'static, S>>, + >, + ); + type Output = &'static Lpm013m126< + 'static, + VirtualMuxAlarm<'static, A>, + P, + VirtualSpiMasterDevice<'static, S>, + >; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let lpm013m126_alarm = s.0.write(VirtualMuxAlarm::new(self.alarm_mux)); + lpm013m126_alarm.setup(); + + let buffer = s.1.write([0; capsules_extra::lpm013m126::BUF_LEN]); + + let chip_select = s.2.write(Inverted(self.chip_select)); + + let spi_device = + s.3.write(VirtualSpiMasterDevice::new(self.spi, chip_select)); + spi_device.setup(); + + let lpm013m126 = s.4.write( + Lpm013m126::new( + spi_device, + self.extcomin, + self.disp, + lpm013m126_alarm, + buffer, + ) + .unwrap(), + ); + spi_device.set_client(lpm013m126); + lpm013m126_alarm.set_alarm_client(lpm013m126); + // Because this capsule uses multiple deferred calls internally, this + // takes care of registering the deferred calls as well. Thus there is + // no need to explicitly call + // `kernel::deferred_call::DeferredCallClient::register`. + lpm013m126.setup().unwrap(); + lpm013m126 + } +} diff --git a/boards/components/src/lps22hb.rs b/boards/components/src/lps22hb.rs new file mode 100644 index 0000000000..d894c2c6b8 --- /dev/null +++ b/boards/components/src/lps22hb.rs @@ -0,0 +1,62 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Component for LPS22HB pressure sensor. + +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_extra::lps22hb::Lps22hb; +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::hil::i2c; + +#[macro_export] +macro_rules! lps22hb_component_static { + ($I:ty $(,)?) => {{ + let i2c_device = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>); + let lps22hb = kernel::static_buf!( + capsules_extra::lps22hb::Lps22hb< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>, + > + ); + let buffer = kernel::static_buf!([u8; 4]); + + (i2c_device, lps22hb, buffer) + };}; +} + +pub struct Lps22hbComponent> { + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, +} + +impl> Lps22hbComponent { + pub fn new(i2c_mux: &'static MuxI2C<'static, I>, i2c_address: u8) -> Self { + Lps22hbComponent { + i2c_mux, + i2c_address, + } + } +} + +impl> Component for Lps22hbComponent { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit>>, + &'static mut MaybeUninit<[u8; 4]>, + ); + type Output = &'static Lps22hb<'static, I2CDevice<'static, I>>; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let lps22hb_i2c = s.0.write(I2CDevice::new(self.i2c_mux, self.i2c_address)); + + let buffer = s.2.write([0; 4]); + + let lps22hb = s.1.write(Lps22hb::new(lps22hb_i2c, buffer)); + lps22hb_i2c.set_client(lps22hb); + + lps22hb + } +} diff --git a/boards/components/src/lps25hb.rs b/boards/components/src/lps25hb.rs new file mode 100644 index 0000000000..a1cf46e71c --- /dev/null +++ b/boards/components/src/lps25hb.rs @@ -0,0 +1,82 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Component for LPS25HB pressure sensor. + +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_extra::lps25hb::LPS25HB; +use core::mem::MaybeUninit; +use kernel::capabilities; +use kernel::component::Component; +use kernel::create_capability; +use kernel::hil::gpio; +use kernel::hil::i2c; + +#[macro_export] +macro_rules! lps25hb_component_static { + ($I:ty $(,)?) => {{ + let i2c_device = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<'static>); + let lps25hb = kernel::static_buf!( + capsules_extra::lps25hb::LPS25HB< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>, + > + ); + let buffer = kernel::static_buf!([u8; capsules_extra::lps25hb::BUF_LEN]); + + (i2c_device, lps25hb, buffer) + };}; +} + +pub struct Lps25hbComponent> { + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, + interrupt_pin: &'static dyn gpio::InterruptPin<'static>, + board_kernel: &'static kernel::Kernel, + driver_num: usize, +} + +impl> Lps25hbComponent { + pub fn new( + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, + interrupt_pin: &'static dyn gpio::InterruptPin<'static>, + board_kernel: &'static kernel::Kernel, + driver_num: usize, + ) -> Self { + Lps25hbComponent { + i2c_mux, + i2c_address, + interrupt_pin, + board_kernel, + driver_num, + } + } +} + +impl> Component for Lps25hbComponent { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit>>, + &'static mut MaybeUninit<[u8; capsules_extra::lps25hb::BUF_LEN]>, + ); + type Output = &'static LPS25HB<'static, I2CDevice<'static, I>>; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + let grant = self.board_kernel.create_grant(self.driver_num, &grant_cap); + + let lps25hb_i2c = s.0.write(I2CDevice::new(self.i2c_mux, self.i2c_address)); + + let buffer = s.2.write([0; capsules_extra::lps25hb::BUF_LEN]); + + let lps25hb = + s.1.write(LPS25HB::new(lps25hb_i2c, self.interrupt_pin, buffer, grant)); + lps25hb_i2c.set_client(lps25hb); + self.interrupt_pin.set_client(lps25hb); + + lps25hb + } +} diff --git a/boards/components/src/lsm303agr.rs b/boards/components/src/lsm303agr.rs index 9af85511bc..b8ba5b7169 100644 --- a/boards/components/src/lsm303agr.rs +++ b/boards/components/src/lsm303agr.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for the LSM303DLHC sensor. //! //! I2C Interface @@ -5,8 +9,8 @@ //! Usage //! ----- //! ```rust -//! let lsm303agr = components::lsm303agr::Lsm303agrI2CComponent::new() -//! .finalize(components::lsm303agr_i2c_component_helper!(mux_i2c)); +//! let lsm303agr = components::lsm303agr::Lsm303agrI2CComponent::new(mux_i2c, None, None, board_kernel, DRIVER_NUM) +//! .finalize(components::lsm303agr_component_static!()); //! //! lsm303agr.configure( //! lsm303agr::Lsm303dlhcAccelDataRate::DataRate25Hz, @@ -18,79 +22,94 @@ //! lsm303agr::Lsm303dlhcRange::Range4_7G, //! ); //! ``` -use capsules::lsm303agr::Lsm303agrI2C; -use capsules::virtual_i2c::I2CDevice; + +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_extra::lsm303agr::Lsm303agrI2C; +use capsules_extra::lsm303xx; use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; -use kernel::{create_capability, static_init_half}; +use kernel::create_capability; +use kernel::hil::i2c; // Setup static space for the objects. #[macro_export] -macro_rules! lsm303agr_i2c_component_helper { - ($i2c_mux:expr, $accelerometer_address:expr, $magnetometer_address:expr $(,)?) => {{ - use capsules::lsm303agr::Lsm303agrI2C; - use capsules::virtual_i2c::I2CDevice; - use core::mem::MaybeUninit; - - static mut BUFFER: [u8; 8] = [0; 8]; - +macro_rules! lsm303agr_component_static { + ($I:ty $(,)?) => {{ + let buffer = kernel::static_buf!([u8; 8]); let accelerometer_i2c = - components::i2c::I2CComponent::new($i2c_mux, $accelerometer_address) - .finalize(components::i2c_component_helper!()); - let magnetometer_i2c = components::i2c::I2CComponent::new($i2c_mux, $magnetometer_address) - .finalize(components::i2c_component_helper!()); - static mut lsm303agr: MaybeUninit> = MaybeUninit::uninit(); - ( - &accelerometer_i2c, - &magnetometer_i2c, - &mut BUFFER, - &mut lsm303agr, - ) - }}; + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>); + let magnetometer_i2c = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>); + let lsm303agr = kernel::static_buf!( + capsules_extra::lsm303agr::Lsm303agrI2C< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>, + > + ); - ($i2c_mux:expr $(,)?) => {{ - $crate::lsm303agr_i2c_component_helper!( - $i2c_mux, - capsules::lsm303xx::ACCELEROMETER_BASE_ADDRESS, - capsules::lsm303xx::MAGNETOMETER_BASE_ADDRESS - ) - }}; + (accelerometer_i2c, magnetometer_i2c, buffer, lsm303agr) + };}; } -pub struct Lsm303agrI2CComponent { +pub struct Lsm303agrI2CComponent> { + i2c_mux: &'static MuxI2C<'static, I>, + accelerometer_i2c_address: u8, + magnetometer_i2c_address: u8, board_kernel: &'static kernel::Kernel, driver_num: usize, } -impl Lsm303agrI2CComponent { - pub fn new(board_kernel: &'static kernel::Kernel, driver_num: usize) -> Lsm303agrI2CComponent { +impl> Lsm303agrI2CComponent { + pub fn new( + i2c_mux: &'static MuxI2C<'static, I>, + accelerometer_i2c_address: Option, + magnetometer_i2c_address: Option, + board_kernel: &'static kernel::Kernel, + driver_num: usize, + ) -> Lsm303agrI2CComponent { Lsm303agrI2CComponent { - board_kernel: board_kernel, + i2c_mux, + accelerometer_i2c_address: accelerometer_i2c_address + .unwrap_or(lsm303xx::ACCELEROMETER_BASE_ADDRESS), + magnetometer_i2c_address: magnetometer_i2c_address + .unwrap_or(lsm303xx::MAGNETOMETER_BASE_ADDRESS), + board_kernel, driver_num, } } } -impl Component for Lsm303agrI2CComponent { +impl> Component for Lsm303agrI2CComponent { type StaticInput = ( - &'static I2CDevice<'static>, - &'static I2CDevice<'static>, - &'static mut [u8], - &'static mut MaybeUninit>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; 8]>, + &'static mut MaybeUninit>>, ); - type Output = &'static Lsm303agrI2C<'static>; + type Output = &'static Lsm303agrI2C<'static, I2CDevice<'static, I>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + + let buffer = static_buffer.2.write([0; 8]); + + let accelerometer_i2c = static_buffer + .0 + .write(I2CDevice::new(self.i2c_mux, self.accelerometer_i2c_address)); + let magnetometer_i2c = static_buffer + .1 + .write(I2CDevice::new(self.i2c_mux, self.magnetometer_i2c_address)); + let grant = self.board_kernel.create_grant(self.driver_num, &grant_cap); - let lsm303agr = static_init_half!( - static_buffer.3, - Lsm303agrI2C<'static>, - Lsm303agrI2C::new(static_buffer.0, static_buffer.1, static_buffer.2, grant) - ); - static_buffer.0.set_client(lsm303agr); - static_buffer.1.set_client(lsm303agr); + let lsm303agr = static_buffer.3.write(Lsm303agrI2C::new( + accelerometer_i2c, + magnetometer_i2c, + buffer, + grant, + )); + accelerometer_i2c.set_client(lsm303agr); + magnetometer_i2c.set_client(lsm303agr); lsm303agr } diff --git a/boards/components/src/lsm303dlhc.rs b/boards/components/src/lsm303dlhc.rs index 9ca01e4cb6..07f64be62a 100644 --- a/boards/components/src/lsm303dlhc.rs +++ b/boards/components/src/lsm303dlhc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for the LSM303DLHC sensor. //! //! I2C Interface @@ -5,8 +9,8 @@ //! Usage //! ----- //! ```rust -//! let lsm303dlhc = components::lsm303dlhc::Lsm303dlhcI2CComponent::new() -//! .finalize(components::lsm303dlhc_i2c_component_helper!(mux_i2c)); +//! let lsm303dlhc = components::lsm303dlhc::Lsm303dlhcI2CComponent::new(i2c_mux, board_kernel, driver_num) +//! .finalize(components::lsm303dlhc_component_static!()); //! //! lsm303dlhc.configure( //! lsm303dlhc::Lsm303dlhcAccelDataRate::DataRate25Hz, @@ -18,80 +22,91 @@ //! lsm303dlhc::Lsm303dlhcRange::Range4_7G, //! ); //! ``` -use capsules::lsm303dlhc::Lsm303dlhcI2C; -use capsules::virtual_i2c::I2CDevice; +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_extra::lsm303dlhc::Lsm303dlhcI2C; +use capsules_extra::lsm303xx; use core::mem::MaybeUninit; use kernel::component::Component; -use kernel::static_init_half; +use kernel::hil::i2c; // Setup static space for the objects. #[macro_export] -macro_rules! lsm303dlhc_i2c_component_helper { - ($i2c_mux:expr $(,)?) => {{ - use capsules::lsm303dlhc::Lsm303dlhcI2C; - use capsules::virtual_i2c::I2CDevice; - use core::mem::MaybeUninit; - - static mut BUFFER: [u8; 8] = [0; 8]; +macro_rules! lsm303dlhc_component_static { + ($I:ty $(,)?) => {{ + let buffer = kernel::static_buf!([u8; 8]); + let accelerometer_i2c = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>); + let magnetometer_i2c = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>); + let lsm303dlhc = kernel::static_buf!( + capsules_extra::lsm303dlhc::Lsm303dlhcI2C< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<$I>, + > + ); - let accelerometer_i2c = components::i2c::I2CComponent::new( - $i2c_mux, - capsules::lsm303xx::ACCELEROMETER_BASE_ADDRESS, - ) - .finalize(components::i2c_component_helper!()); - let magnetometer_i2c = components::i2c::I2CComponent::new( - $i2c_mux, - capsules::lsm303xx::MAGNETOMETER_BASE_ADDRESS, - ) - .finalize(components::i2c_component_helper!()); - static mut lsm303dlhc: MaybeUninit> = MaybeUninit::uninit(); - ( - &accelerometer_i2c, - &magnetometer_i2c, - &mut BUFFER, - &mut lsm303dlhc, - ) + (accelerometer_i2c, magnetometer_i2c, buffer, lsm303dlhc) };}; } -pub struct Lsm303dlhcI2CComponent { +pub struct Lsm303dlhcI2CComponent> { + i2c_mux: &'static MuxI2C<'static, I>, + accelerometer_i2c_address: u8, + magnetometer_i2c_address: u8, board_kernel: &'static kernel::Kernel, driver_num: usize, } -impl Lsm303dlhcI2CComponent { - pub fn new(board_kernel: &'static kernel::Kernel, driver_num: usize) -> Lsm303dlhcI2CComponent { +impl> Lsm303dlhcI2CComponent { + pub fn new( + i2c_mux: &'static MuxI2C<'static, I>, + accelerometer_i2c_address: Option, + magnetometer_i2c_address: Option, + board_kernel: &'static kernel::Kernel, + driver_num: usize, + ) -> Lsm303dlhcI2CComponent { Lsm303dlhcI2CComponent { + i2c_mux, + accelerometer_i2c_address: accelerometer_i2c_address + .unwrap_or(lsm303xx::ACCELEROMETER_BASE_ADDRESS), + magnetometer_i2c_address: magnetometer_i2c_address + .unwrap_or(lsm303xx::MAGNETOMETER_BASE_ADDRESS), board_kernel, driver_num, } } } -impl Component for Lsm303dlhcI2CComponent { +impl> Component for Lsm303dlhcI2CComponent { type StaticInput = ( - &'static I2CDevice<'static>, - &'static I2CDevice<'static>, - &'static mut [u8], - &'static mut MaybeUninit>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; 8]>, + &'static mut MaybeUninit>>, ); - type Output = &'static Lsm303dlhcI2C<'static>; + type Output = &'static Lsm303dlhcI2C<'static, I2CDevice<'static, I>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let grant_cap = kernel::create_capability!(kernel::capabilities::MemoryAllocationCapability); - let lsm303dlhc = static_init_half!( - static_buffer.3, - Lsm303dlhcI2C<'static>, - Lsm303dlhcI2C::new( - static_buffer.0, - static_buffer.1, - static_buffer.2, - self.board_kernel.create_grant(self.driver_num, &grant_cap), - ) - ); - static_buffer.0.set_client(lsm303dlhc); - static_buffer.1.set_client(lsm303dlhc); + + let buffer = static_buffer.2.write([0; 8]); + + let accelerometer_i2c = static_buffer + .0 + .write(I2CDevice::new(self.i2c_mux, self.accelerometer_i2c_address)); + let magnetometer_i2c = static_buffer + .1 + .write(I2CDevice::new(self.i2c_mux, self.magnetometer_i2c_address)); + + let lsm303dlhc = static_buffer.3.write(Lsm303dlhcI2C::new( + accelerometer_i2c, + magnetometer_i2c, + buffer, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); + accelerometer_i2c.set_client(lsm303dlhc); + magnetometer_i2c.set_client(lsm303dlhc); lsm303dlhc } diff --git a/boards/components/src/lsm6dsox.rs b/boards/components/src/lsm6dsox.rs index 4199f359a5..fc1344d116 100644 --- a/boards/components/src/lsm6dsox.rs +++ b/boards/components/src/lsm6dsox.rs @@ -1,86 +1,103 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for the LSM6DSOXTR Sensor //! //! Usage //! ------ //! //! ```rust +//! let lsm6dsoxtr = components::lsm6dsox::Lsm6dsoxtrI2CComponent::new( +//! mux_i2c, +//! capsules_extra::lsm6dsoxtr::ACCELEROMETER_BASE_ADDRESS, +//! board_kernel, +//! capsules_extra::lsm6dsoxtr::DRIVER_NUM, +//! ) +//! .finalize(components::lsm6ds_i2c_component_static!()); +//! //! let _ = lsm6dsoxtr //! .configure( -//! capsules::lsm6ds_definitions::LSM6DSOXGyroDataRate::LSM6DSOX_GYRO_RATE_12_5_HZ, -//! capsules::lsm6ds_definitions::LSM6DSOXAccelDataRate::LSM6DSOX_ACCEL_RATE_12_5_HZ, -//! capsules::lsm6ds_definitions::LSM6DSOXAccelRange::LSM6DSOX_ACCEL_RANGE_2_G, -//! capsules::lsm6ds_definitions::LSM6DSOXTRGyroRange::LSM6DSOX_GYRO_RANGE_250_DPS, +//! capsules_extra::lsm6ds_definitions::LSM6DSOXGyroDataRate::LSM6DSOX_GYRO_RATE_12_5_HZ, +//! capsules_extra::lsm6ds_definitions::LSM6DSOXAccelDataRate::LSM6DSOX_ACCEL_RATE_12_5_HZ, +//! capsules_extra::lsm6ds_definitions::LSM6DSOXAccelRange::LSM6DSOX_ACCEL_RANGE_2_G, +//! capsules_extra::lsm6ds_definitions::LSM6DSOXTRGyroRange::LSM6DSOX_GYRO_RANGE_250_DPS, //! true, //! ) //! .map_err(|e| panic!("ERROR Failed LSM6DSOXTR sensor configuration ({:?})", e)); //! ``` //! Author: Cristiana Andrei -use capsules::lsm6dsoxtr::Lsm6dsoxtrI2C; -use capsules::virtual_i2c::I2CDevice; +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_extra::lsm6dsoxtr::Lsm6dsoxtrI2C; use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; -use kernel::{create_capability, static_init_half}; +use kernel::create_capability; +use kernel::hil::i2c; // Setup static space for the objects. #[macro_export] -macro_rules! lsm6ds_i2c_component_helper { - ($i2c_mux:expr, $accelerometer_address:expr $(,)?) => {{ - use capsules::lsm6dsoxtr::Lsm6dsoxtrI2C; - use capsules::virtual_i2c::I2CDevice; - use core::mem::MaybeUninit; - - static mut BUFFER: [u8; 8] = [0; 8]; - - let accelerometer_i2c = - components::i2c::I2CComponent::new($i2c_mux, $accelerometer_address) - .finalize(components::i2c_component_helper!()); - - static mut lsm6dsoxtr: MaybeUninit> = MaybeUninit::uninit(); - (&accelerometer_i2c, &mut BUFFER, &mut lsm6dsoxtr) - }}; +macro_rules! lsm6ds_i2c_component_static { + ($I:ty $(,)?) => {{ + let buffer = kernel::static_buf!([u8; 8]); + let i2c_device = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>); + let lsm6dsoxtr = kernel::static_buf!( + capsules_extra::lsm6dsoxtr::Lsm6dsoxtrI2C< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>, + > + ); - ($i2c_mux:expr $(,)?) => {{ - $crate::lsm6ds_i2c_component_helper!( - $i2c_mux, - capsules::lsm6dsoxtr::ACCELEROMETER_BASE_ADDRESS - ) - }}; + (i2c_device, buffer, lsm6dsoxtr) + };}; } -pub struct Lsm6dsoxtrI2CComponent { +pub struct Lsm6dsoxtrI2CComponent> { + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, board_kernel: &'static kernel::Kernel, driver_num: usize, } -impl Lsm6dsoxtrI2CComponent { - pub fn new(board_kernel: &'static kernel::Kernel, driver_num: usize) -> Lsm6dsoxtrI2CComponent { +impl> Lsm6dsoxtrI2CComponent { + pub fn new( + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, + board_kernel: &'static kernel::Kernel, + driver_num: usize, + ) -> Lsm6dsoxtrI2CComponent { Lsm6dsoxtrI2CComponent { - board_kernel: board_kernel, + i2c_mux, + i2c_address, + board_kernel, driver_num, } } } -impl Component for Lsm6dsoxtrI2CComponent { +impl> Component for Lsm6dsoxtrI2CComponent { type StaticInput = ( - &'static I2CDevice<'static>, - &'static mut [u8], - &'static mut MaybeUninit>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; 8]>, + &'static mut MaybeUninit>>, ); - type Output = &'static Lsm6dsoxtrI2C<'static>; + type Output = &'static Lsm6dsoxtrI2C<'static, I2CDevice<'static, I>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); let grant = self.board_kernel.create_grant(self.driver_num, &grant_cap); - let lsm6dsox = static_init_half!( - static_buffer.2, - Lsm6dsoxtrI2C<'static>, - Lsm6dsoxtrI2C::new(static_buffer.0, static_buffer.1, grant) - ); - static_buffer.0.set_client(lsm6dsox); + let lsm6dsox_i2c = static_buffer + .0 + .write(I2CDevice::new(self.i2c_mux, self.i2c_address)); + let buffer = static_buffer.1.write([0; 8]); + + let lsm6dsox = static_buffer + .2 + .write(Lsm6dsoxtrI2C::new(lsm6dsox_i2c, buffer, grant)); + lsm6dsox_i2c.set_client(lsm6dsox); lsm6dsox } diff --git a/boards/components/src/ltc294x.rs b/boards/components/src/ltc294x.rs new file mode 100644 index 0000000000..069eea5e7a --- /dev/null +++ b/boards/components/src/ltc294x.rs @@ -0,0 +1,128 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Component for LPS25HB pressure sensor. +//! +//! Usage +//! ----- +//! +//! ```rust +//! let ltc294x = components::Ltc294xComponent::new(i2c_mux, 0x64, None) +//! .finalize(components::ltc294x_component_static!()); +//! let ltc294x_driver = components::Ltc294xDriverComponent::new(ltc294x, board_kernel, DRIVER_NUM) +//! .finalize(components::ltc294x_driver_component_static!()); +//! ``` + +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_extra::ltc294x::LTC294XDriver; +use capsules_extra::ltc294x::LTC294X; +use core::mem::MaybeUninit; +use kernel::capabilities; +use kernel::component::Component; +use kernel::create_capability; +use kernel::hil::gpio; +use kernel::hil::i2c; + +#[macro_export] +macro_rules! ltc294x_component_static { + ($I:ty $(,)?) => {{ + let i2c_device = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>); + let ltc294x = kernel::static_buf!( + capsules_extra::ltc294x::LTC294X< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>, + > + ); + let buffer = kernel::static_buf!([u8; capsules_extra::ltc294x::BUF_LEN]); + + (i2c_device, ltc294x, buffer) + };}; +} + +#[macro_export] +macro_rules! ltc294x_driver_component_static { + () => {{ + kernel::static_buf!(capsules_extra::ltc294x::LTC294XDriver<'static>) + };}; +} + +pub struct Ltc294xComponent> { + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, + interrupt_pin: Option<&'static dyn gpio::InterruptPin<'static>>, +} + +impl> Ltc294xComponent { + pub fn new( + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, + interrupt_pin: Option<&'static dyn gpio::InterruptPin<'static>>, + ) -> Self { + Ltc294xComponent { + i2c_mux, + i2c_address, + interrupt_pin, + } + } +} + +impl> Component for Ltc294xComponent { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit>>, + &'static mut MaybeUninit<[u8; capsules_extra::ltc294x::BUF_LEN]>, + ); + type Output = &'static LTC294X<'static, I2CDevice<'static, I>>; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let ltc294x_i2c = s.0.write(I2CDevice::new(self.i2c_mux, self.i2c_address)); + + let buffer = s.2.write([0; capsules_extra::ltc294x::BUF_LEN]); + + let ltc294x = + s.1.write(LTC294X::new(ltc294x_i2c, self.interrupt_pin, buffer)); + ltc294x_i2c.set_client(ltc294x); + self.interrupt_pin.map(|pin| { + pin.set_client(ltc294x); + }); + + ltc294x + } +} + +pub struct Ltc294xDriverComponent> { + ltc294x: &'static LTC294X<'static, I2CDevice<'static, I>>, + board_kernel: &'static kernel::Kernel, + driver_num: usize, +} + +impl> Ltc294xDriverComponent { + pub fn new( + ltc294x: &'static LTC294X<'static, I2CDevice<'static, I>>, + board_kernel: &'static kernel::Kernel, + driver_num: usize, + ) -> Self { + Ltc294xDriverComponent { + ltc294x, + board_kernel, + driver_num, + } + } +} + +impl> Component for Ltc294xDriverComponent { + type StaticInput = &'static mut MaybeUninit>>; + type Output = &'static LTC294XDriver<'static, I2CDevice<'static, I>>; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + let grant = self.board_kernel.create_grant(self.driver_num, &grant_cap); + + let ltc294x_driver = s.write(LTC294XDriver::new(self.ltc294x, grant)); + self.ltc294x.set_client(ltc294x_driver); + + ltc294x_driver + } +} diff --git a/boards/components/src/mlx90614.rs b/boards/components/src/mlx90614.rs index 66e3b4feb1..b883819715 100644 --- a/boards/components/src/mlx90614.rs +++ b/boards/components/src/mlx90614.rs @@ -1,82 +1,92 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for the MLX90614 IR Temperature Sensor. //! //! Usage //! ----- //! ```rust -//! let mlx90614 = components::mlx90614::Mlx90614I2CComponent::new(mux_i2c, i2c_addr, -//! board_kernel) -//! .finalize(components::mlx90614_i2c_component_helper!()); +//! let mlx90614 = components::mlx90614::Mlx90614I2CComponent::new(mux_i2c, i2c_addr, +//! board_kernel) +//! .finalize(components::mlx90614_component_static!()); //! -//! let temp = static_init!( -//! capsules::temperature::TemperatureSensor<'static>, -//! capsules::temperature::TemperatureSensor::new(mlx90614, -//! grant_temperature)); -//! kernel::hil::sensors::TemperatureDriver::set_client(mlx90614, temp); +//! let temp = static_init!( +//! capsules_extra::temperature::TemperatureSensor<'static>, +//! capsules_extra::temperature::TemperatureSensor::new(mlx90614, +//! grant_temperature)); +//! kernel::hil::sensors::TemperatureDriver::set_client(mlx90614, temp); //! ``` -use capsules::mlx90614::Mlx90614SMBus; -use capsules::virtual_i2c::{MuxI2C, SMBusDevice}; +use capsules_core::virtualizers::virtual_i2c::{MuxI2C, SMBusDevice}; +use capsules_extra::mlx90614::Mlx90614SMBus; use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; -use kernel::{static_init, static_init_half}; +use kernel::hil::i2c::{self, NoSMBus}; // Setup static space for the objects. #[macro_export] -macro_rules! mlx90614_component_helper { +macro_rules! mlx90614_component_static { () => {{ - use capsules::mlx90614::Mlx90614SMBus; - use core::mem::MaybeUninit; - static mut BUF: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF + let i2c_device = kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::SMBusDevice); + let buffer = kernel::static_buf!([u8; 14]); + let mlx90614 = kernel::static_buf!(capsules_extra::mlx90614::Mlx90614SMBus<'static>); + + (i2c_device, buffer, mlx90614) };}; } -pub struct Mlx90614SMBusComponent { - i2c_mux: &'static MuxI2C<'static>, +pub struct Mlx90614SMBusComponent< + I: 'static + i2c::I2CMaster<'static>, + S: 'static + i2c::SMBusMaster<'static> = NoSMBus, +> { + i2c_mux: &'static MuxI2C<'static, I, S>, i2c_address: u8, board_kernel: &'static kernel::Kernel, driver_num: usize, } -impl Mlx90614SMBusComponent { +impl, S: 'static + i2c::SMBusMaster<'static>> + Mlx90614SMBusComponent +{ pub fn new( - i2c: &'static MuxI2C<'static>, + i2c: &'static MuxI2C<'static, I, S>, i2c_address: u8, board_kernel: &'static kernel::Kernel, driver_num: usize, ) -> Self { Mlx90614SMBusComponent { i2c_mux: i2c, - i2c_address: i2c_address, + i2c_address, board_kernel, driver_num, } } } -static mut I2C_BUF: [u8; 14] = [0; 14]; - -impl Component for Mlx90614SMBusComponent { - type StaticInput = &'static mut MaybeUninit>; - type Output = &'static Mlx90614SMBus<'static>; +impl, S: 'static + i2c::SMBusMaster<'static>> Component + for Mlx90614SMBusComponent +{ + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; 14]>, + &'static mut MaybeUninit>>, + ); + type Output = &'static Mlx90614SMBus<'static, SMBusDevice<'static, I, S>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let mlx90614_smbus = static_init!( - SMBusDevice, - SMBusDevice::new(self.i2c_mux, self.i2c_address) - ); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let mlx90614_smbus = static_buffer + .0 + .write(SMBusDevice::new(self.i2c_mux, self.i2c_address)); + let buffer = static_buffer.1.write([0; 14]); let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let mlx90614 = static_init_half!( - static_buffer, - Mlx90614SMBus<'static>, - Mlx90614SMBus::new( - mlx90614_smbus, - &mut I2C_BUF, - self.board_kernel.create_grant(self.driver_num, &grant_cap) - ) - ); + let mlx90614 = static_buffer.2.write(Mlx90614SMBus::new( + mlx90614_smbus, + buffer, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); mlx90614_smbus.set_client(mlx90614); mlx90614 diff --git a/boards/components/src/mx25r6435f.rs b/boards/components/src/mx25r6435f.rs index d87eea4e74..fc52d8d848 100644 --- a/boards/components/src/mx25r6435f.rs +++ b/boards/components/src/mx25r6435f.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for the MX25R6435F flash chip. //! //! Usage @@ -10,65 +14,76 @@ //! mux_alarm, //! mux_spi, //! ) -//! .finalize(components::mx25r6435f_component_helper!( +//! .finalize(components::mx25r6435f_component_static!( //! nrf52::spi::SPIM, //! nrf52::gpio::GPIOPin, //! nrf52::rtc::Rtc //! )); //! ``` -use capsules::mx25r6435f::MX25R6435F; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; -use capsules::virtual_spi::{MuxSpiMaster, VirtualSpiMasterDevice}; + +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_spi::{MuxSpiMaster, VirtualSpiMasterDevice}; +use capsules_extra::mx25r6435f::MX25R6435F; use core::mem::MaybeUninit; use kernel::component::Component; use kernel::hil; use kernel::hil::spi::SpiMasterDevice; use kernel::hil::time::Alarm; -use kernel::static_init_half; // Setup static space for the objects. #[macro_export] -macro_rules! mx25r6435f_component_helper { +macro_rules! mx25r6435f_component_static { ($S:ty, $P:ty, $A:ty $(,)?) => {{ - use capsules::mx25r6435f::MX25R6435F; - use capsules::virtual_alarm::VirtualMuxAlarm; - use capsules::virtual_spi::VirtualSpiMasterDevice; - use core::mem::MaybeUninit; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF2: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF3: MaybeUninit< - MX25R6435F< + let spi_device = kernel::static_buf!( + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice<'static, $S> + ); + let alarm = kernel::static_buf!( + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A> + ); + let mx25r6435f = kernel::static_buf!( + capsules_extra::mx25r6435f::MX25R6435F< 'static, - VirtualSpiMasterDevice<'static, $S>, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice<'static, $S>, $P, - VirtualMuxAlarm<'static, $A>, - >, - > = MaybeUninit::uninit(); - (&mut BUF1, &mut BUF2, &mut BUF3) + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A>, + > + ); + + let tx_buf = kernel::static_buf!([u8; capsules_extra::mx25r6435f::TX_BUF_LEN]); + let rx_buf = kernel::static_buf!([u8; capsules_extra::mx25r6435f::RX_BUF_LEN]); + + (spi_device, alarm, mx25r6435f, tx_buf, rx_buf) };}; } +pub type Mx25r6435fComponentType = capsules_extra::mx25r6435f::MX25R6435F< + 'static, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice<'static, S>, + P, + VirtualMuxAlarm<'static, A>, +>; + pub struct Mx25r6435fComponent< - S: 'static + hil::spi::SpiMaster, + S: 'static + hil::spi::SpiMaster<'static>, P: 'static + hil::gpio::Pin, A: 'static + hil::time::Alarm<'static>, > { - write_protect_pin: &'static P, - hold_pin: &'static P, + write_protect_pin: Option<&'static P>, + hold_pin: Option<&'static P>, chip_select: S::ChipSelect, mux_alarm: &'static MuxAlarm<'static, A>, mux_spi: &'static MuxSpiMaster<'static, S>, } impl< - S: 'static + hil::spi::SpiMaster, + S: 'static + hil::spi::SpiMaster<'static>, P: 'static + hil::gpio::Pin, A: 'static + hil::time::Alarm<'static>, > Mx25r6435fComponent { pub fn new( - write_protect_pin: &'static P, - hold_pin: &'static P, + write_protect_pin: Option<&'static P>, + hold_pin: Option<&'static P>, chip_select: S::ChipSelect, mux_alarm: &'static MuxAlarm<'static, A>, mux_spi: &'static MuxSpiMaster<'static, S>, @@ -84,7 +99,7 @@ impl< } impl< - S: 'static + hil::spi::SpiMaster, + S: 'static + hil::spi::SpiMaster<'static>, P: 'static + hil::gpio::Pin, A: 'static + hil::time::Alarm<'static>, > Component for Mx25r6435fComponent @@ -95,6 +110,8 @@ impl< &'static mut MaybeUninit< MX25R6435F<'static, VirtualSpiMasterDevice<'static, S>, P, VirtualMuxAlarm<'static, A>>, >, + &'static mut MaybeUninit<[u8; capsules_extra::mx25r6435f::TX_BUF_LEN]>, + &'static mut MaybeUninit<[u8; capsules_extra::mx25r6435f::RX_BUF_LEN]>, ); type Output = &'static MX25R6435F< 'static, @@ -103,37 +120,31 @@ impl< VirtualMuxAlarm<'static, A>, >; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let mx25r6435f_spi = static_init_half!( - static_buffer.0, - VirtualSpiMasterDevice<'static, S>, - VirtualSpiMasterDevice::new(self.mux_spi, self.chip_select) - ); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let mx25r6435f_spi = static_buffer + .0 + .write(VirtualSpiMasterDevice::new(self.mux_spi, self.chip_select)); // Create an alarm for this chip. - let mx25r6435f_virtual_alarm = static_init_half!( - static_buffer.1, - VirtualMuxAlarm<'static, A>, - VirtualMuxAlarm::new(self.mux_alarm) - ); + let mx25r6435f_virtual_alarm = static_buffer.1.write(VirtualMuxAlarm::new(self.mux_alarm)); mx25r6435f_virtual_alarm.setup(); - let mx25r6435f = static_init_half!( - static_buffer.2, - capsules::mx25r6435f::MX25R6435F< - 'static, - capsules::virtual_spi::VirtualSpiMasterDevice<'static, S>, - P, - VirtualMuxAlarm<'static, A>, - >, - capsules::mx25r6435f::MX25R6435F::new( + let tx_buf = static_buffer + .3 + .write([0; capsules_extra::mx25r6435f::TX_BUF_LEN]); + let rx_buf = static_buffer + .4 + .write([0; capsules_extra::mx25r6435f::RX_BUF_LEN]); + + let mx25r6435f = static_buffer + .2 + .write(capsules_extra::mx25r6435f::MX25R6435F::new( mx25r6435f_spi, mx25r6435f_virtual_alarm, - &mut capsules::mx25r6435f::TXBUFFER, - &mut capsules::mx25r6435f::RXBUFFER, - Some(self.write_protect_pin), - Some(self.hold_pin) - ) - ); + tx_buf, + rx_buf, + self.write_protect_pin, + self.hold_pin, + )); mx25r6435f_spi.setup(); mx25r6435f_spi.set_client(mx25r6435f); mx25r6435f_virtual_alarm.set_alarm_client(mx25r6435f); diff --git a/boards/components/src/ninedof.rs b/boards/components/src/ninedof.rs index 697f7331dc..3d2f5dee05 100644 --- a/boards/components/src/ninedof.rs +++ b/boards/components/src/ninedof.rs @@ -1,40 +1,38 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for 9DOF //! //! Usage //! ----- -//! NineDof //! //! ```rust //! let ninedof = components::ninedof::NineDofComponent::new(board_kernel) -//! .finalize(components::ninedof_component_helper!(driver1, driver2, ...)); +//! .finalize(components::ninedof_component_static!(driver1, driver2, ...)); //! ``` -use capsules::ninedof::NineDof; +use capsules_extra::ninedof::NineDof; use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; -use kernel::static_init_half; #[macro_export] -macro_rules! ninedof_component_helper { +macro_rules! ninedof_component_static { ($($P:expr),+ $(,)?) => {{ - use capsules::ninedof::NineDof; - use core::mem::MaybeUninit; - use kernel::hil; use kernel::count_expressions; - use kernel::static_init; + const NUM_DRIVERS: usize = count_expressions!($($P),+); - let drivers = static_init!( + let drivers = kernel::static_init!( [&'static dyn kernel::hil::sensors::NineDof; NUM_DRIVERS], [ $($P,)* ] ); - static mut BUF: MaybeUninit> = - MaybeUninit::uninit(); - (&mut BUF, drivers) + let ninedof = kernel::static_buf!(capsules_extra::ninedof::NineDof<'static>); + (ninedof, drivers) };}; } @@ -46,8 +44,8 @@ pub struct NineDofComponent { impl NineDofComponent { pub fn new(board_kernel: &'static kernel::Kernel, driver_num: usize) -> NineDofComponent { NineDofComponent { - board_kernel: board_kernel, - driver_num: driver_num, + board_kernel, + driver_num, } } } @@ -57,17 +55,16 @@ impl Component for NineDofComponent { &'static mut MaybeUninit>, &'static [&'static dyn kernel::hil::sensors::NineDof<'static>], ); - type Output = &'static capsules::ninedof::NineDof<'static>; + type Output = &'static capsules_extra::ninedof::NineDof<'static>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); let grant_ninedof = self.board_kernel.create_grant(self.driver_num, &grant_cap); - let ninedof = static_init_half!( - static_buffer.0, - capsules::ninedof::NineDof<'static>, - capsules::ninedof::NineDof::new(static_buffer.1, grant_ninedof) - ); + let ninedof = static_buffer.0.write(capsules_extra::ninedof::NineDof::new( + static_buffer.1, + grant_ninedof, + )); for driver in static_buffer.1 { kernel::hil::sensors::NineDof::set_client(*driver, ninedof); diff --git a/boards/components/src/nonvolatile_storage.rs b/boards/components/src/nonvolatile_storage.rs index 323a72a9ad..dbba7c9b58 100644 --- a/boards/components/src/nonvolatile_storage.rs +++ b/boards/components/src/nonvolatile_storage.rs @@ -1,7 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for non-volatile storage Drivers. //! //! This provides one component, NonvolatileStorageComponent, which provides -//! a system call inteface to non-volatile storage. +//! a system call interface to non-volatile storage. //! //! Usage //! ----- @@ -11,36 +15,41 @@ //! &sam4l::flashcalw::FLASH_CONTROLLER, //! 0x60000, //! 0x20000, -//! &_sstorage as *const u8 as usize, -//! &_estorage as *const u8 as usize, +//! core::ptr::addr_of!(_sstorage) as usize, +//! core::ptr::addr_of!(_estorage) as usize, //! ) -//! .finalize(components::nv_storage_component_helper!( +//! .finalize(components::nonvolatile_storage_component_static!( //! sam4l::flashcalw::FLASHCALW //! )); //! ``` -use capsules::nonvolatile_storage_driver::NonvolatileStorage; -use capsules::nonvolatile_to_pages::NonvolatileToPages; +use capsules_extra::nonvolatile_storage_driver::NonvolatileStorage; +use capsules_extra::nonvolatile_to_pages::NonvolatileToPages; use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; use kernel::hil; -use kernel::{static_init, static_init_half}; // Setup static space for the objects. #[macro_export] -macro_rules! nv_storage_component_helper { +macro_rules! nonvolatile_storage_component_static { ($F:ty $(,)?) => {{ - use capsules::nonvolatile_to_pages::NonvolatileToPages; - use core::mem::MaybeUninit; - use kernel::hil; - static mut BUF1: MaybeUninit<<$F as hil::flash::Flash>::Page> = MaybeUninit::uninit(); - static mut BUF2: MaybeUninit> = MaybeUninit::uninit(); - (&mut BUF1, &mut BUF2) + let page = kernel::static_buf!(<$F as kernel::hil::flash::Flash>::Page); + let ntp = kernel::static_buf!( + capsules_extra::nonvolatile_to_pages::NonvolatileToPages<'static, $F> + ); + let ns = kernel::static_buf!( + capsules_extra::nonvolatile_storage_driver::NonvolatileStorage<'static> + ); + let buffer = kernel::static_buf!([u8; capsules_extra::nonvolatile_storage_driver::BUF_LEN]); + + (page, ntp, ns, buffer) };}; } +pub type NonvolatileStorageComponentType = NonvolatileStorage<'static>; + pub struct NonvolatileStorageComponent< F: 'static + hil::flash::Flash + hil::flash::HasClient<'static, NonvolatileToPages<'static, F>>, > { @@ -89,37 +98,36 @@ impl< type StaticInput = ( &'static mut MaybeUninit<::Page>, &'static mut MaybeUninit>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; capsules_extra::nonvolatile_storage_driver::BUF_LEN]>, ); type Output = &'static NonvolatileStorage<'static>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let flash_pagebuffer = static_init_half!( - static_buffer.0, - ::Page, - ::Page::default() - ); + let buffer = static_buffer + .3 + .write([0; capsules_extra::nonvolatile_storage_driver::BUF_LEN]); - let nv_to_page = static_init_half!( - static_buffer.1, - NonvolatileToPages<'static, F>, - NonvolatileToPages::new(self.flash, flash_pagebuffer) - ); + let flash_pagebuffer = static_buffer + .0 + .write(::Page::default()); + + let nv_to_page = static_buffer + .1 + .write(NonvolatileToPages::new(self.flash, flash_pagebuffer)); hil::flash::HasClient::set_client(self.flash, nv_to_page); - let nonvolatile_storage = static_init!( - NonvolatileStorage<'static>, - NonvolatileStorage::new( - nv_to_page, - self.board_kernel.create_grant(self.driver_num, &grant_cap), - self.userspace_start, // Start address for userspace accessible region - self.userspace_length, // Length of userspace accessible region - self.kernel_start, // Start address of kernel region - self.kernel_length, // Length of kernel region - &mut capsules::nonvolatile_storage_driver::BUFFER - ) - ); + let nonvolatile_storage = static_buffer.2.write(NonvolatileStorage::new( + nv_to_page, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + self.userspace_start, // Start address for userspace accessible region + self.userspace_length, // Length of userspace accessible region + self.kernel_start, // Start address of kernel region + self.kernel_length, // Length of kernel region + buffer, + )); hil::nonvolatile_storage::NonvolatileStorage::set_client(nv_to_page, nonvolatile_storage); nonvolatile_storage } diff --git a/boards/components/src/nrf51822.rs b/boards/components/src/nrf51822.rs index c7fdcef7ff..ff77a0000c 100644 --- a/boards/components/src/nrf51822.rs +++ b/boards/components/src/nrf51822.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for communicating with the nRF51822 (BLE). //! //! This provides one Component, Nrf51822Component, which implements @@ -7,18 +11,34 @@ //! ----- //! ```rust //! let nrf_serialization = Nrf51822Component::new(&sam4l::usart::USART3, -//! &sam4l::gpio::PA[17]).finalize(()); +//! &sam4l::gpio::PA[17]) +//! .finalize(components::nrf51822_component_static!()); //! ``` // Author: Philip Levis // Last modified: 6/20/2018 -use capsules::nrf51822_serialization; +use capsules_extra::nrf51822_serialization::Nrf51822Serialization; +use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; use kernel::hil; -use kernel::static_init; + +#[macro_export] +macro_rules! nrf51822_component_static { + () => {{ + let nrf = kernel::static_buf!( + capsules_extra::nrf51822_serialization::Nrf51822Serialization<'static> + ); + let write_buffer = + kernel::static_buf!([u8; capsules_extra::nrf51822_serialization::WRITE_BUF_LEN]); + let read_buffer = + kernel::static_buf!([u8; capsules_extra::nrf51822_serialization::READ_BUF_LEN]); + + (nrf, write_buffer, read_buffer) + };}; +} pub struct Nrf51822Component< U: 'static + hil::uart::UartAdvanced<'static>, @@ -40,10 +60,10 @@ impl, G: 'static + hil::gpio::Pin> reset_pin: &'static G, ) -> Nrf51822Component { Nrf51822Component { - board_kernel: board_kernel, - driver_num: driver_num, - uart: uart, - reset_pin: reset_pin, + board_kernel, + driver_num, + uart, + reset_pin, } } } @@ -51,22 +71,28 @@ impl, G: 'static + hil::gpio::Pin> impl, G: 'static + hil::gpio::Pin> Component for Nrf51822Component { - type StaticInput = (); - type Output = &'static nrf51822_serialization::Nrf51822Serialization<'static>; + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; capsules_extra::nrf51822_serialization::WRITE_BUF_LEN]>, + &'static mut MaybeUninit<[u8; capsules_extra::nrf51822_serialization::READ_BUF_LEN]>, + ); + type Output = &'static Nrf51822Serialization<'static>; - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { + fn finalize(self, s: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let nrf_serialization = static_init!( - nrf51822_serialization::Nrf51822Serialization<'static>, - nrf51822_serialization::Nrf51822Serialization::new( - self.uart, - self.board_kernel.create_grant(self.driver_num, &grant_cap), - self.reset_pin, - &mut nrf51822_serialization::WRITE_BUF, - &mut nrf51822_serialization::READ_BUF - ) - ); + let write_buffer = + s.1.write([0; capsules_extra::nrf51822_serialization::WRITE_BUF_LEN]); + let read_buffer = + s.2.write([0; capsules_extra::nrf51822_serialization::READ_BUF_LEN]); + + let nrf_serialization = s.0.write(Nrf51822Serialization::new( + self.uart, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + self.reset_pin, + write_buffer, + read_buffer, + )); hil::uart::Transmit::set_transmit_client(self.uart, nrf_serialization); hil::uart::Receive::set_receive_client(self.uart, nrf_serialization); nrf_serialization diff --git a/boards/components/src/panic_button.rs b/boards/components/src/panic_button.rs index 246d0d369e..d7384a5595 100644 --- a/boards/components/src/panic_button.rs +++ b/boards/components/src/panic_button.rs @@ -1,8 +1,15 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component to cause a button press to trigger a kernel panic. //! -//! This can be useful especially when developping or debugging console +//! This can be useful especially when developing or debugging console //! capsules. //! +//! Note: the process console has support for triggering a panic, which may be +//! more convenient depending on the board. +//! //! Usage //! ----- //! @@ -12,22 +19,18 @@ //! kernel::hil::gpio::ActivationMode::ActiveLow, //! kernel::hil::gpio::FloatingState::PullUp //! ) -//! .finalize(panic_button_component_buf!(sam4l::gpio::GPIOPin)); +//! .finalize(components::panic_button_component_static!(sam4l::gpio::GPIOPin)); //! ``` -use capsules::panic_button::PanicButton; +use capsules_extra::panic_button::PanicButton; use core::mem::MaybeUninit; use kernel::component::Component; use kernel::hil::gpio; -use kernel::static_init_half; #[macro_export] -macro_rules! panic_button_component_buf { +macro_rules! panic_button_component_static { ($Pin:ty $(,)?) => {{ - use capsules::button::PanicButton; - use core::mem::MaybeUninit; - static mut BUF: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF + kernel::static_buf!(capsules_core::button::PanicButton<'static, $Pin>) };}; } @@ -55,12 +58,9 @@ impl> Component for PanicButtonCompone type StaticInput = &'static mut MaybeUninit>; type Output = (); - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let panic_button = static_init_half!( - static_buffer, - PanicButton<'static, IP>, - PanicButton::new(self.pin, self.mode, self.floating_state) - ); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let panic_button = + static_buffer.write(PanicButton::new(self.pin, self.mode, self.floating_state)); self.pin.set_client(panic_button); } } diff --git a/boards/components/src/pressure.rs b/boards/components/src/pressure.rs new file mode 100644 index 0000000000..c53af6a942 --- /dev/null +++ b/boards/components/src/pressure.rs @@ -0,0 +1,64 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Component for any barometer sensor. +//! +//! Usage +//! ----- +//! ```rust +//! let pressure = PressureComponent::new(board_kernel, nrf52::pressure::PRES) +//! .finalize(components::pressure_component_static!()); +//! ``` + +use capsules_extra::pressure::PressureSensor; +use core::mem::MaybeUninit; +use kernel::capabilities; +use kernel::component::Component; +use kernel::create_capability; +use kernel::hil; + +#[macro_export] +macro_rules! pressure_component_static { + ($T:ty $(,)?) => {{ + kernel::static_buf!(capsules_extra::pressure::PressureSensor<'static, $T>) + };}; +} + +pub struct PressureComponent> { + board_kernel: &'static kernel::Kernel, + driver_num: usize, + pressure_sensor: &'static T, +} + +impl> PressureComponent { + pub fn new( + board_kernel: &'static kernel::Kernel, + driver_num: usize, + pressure_sensor: &'static T, + ) -> PressureComponent { + PressureComponent { + board_kernel, + driver_num, + pressure_sensor, + } + } +} + +impl> Component for PressureComponent { + type StaticInput = &'static mut MaybeUninit>; + type Output = &'static PressureSensor<'static, T>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + + let pressure = static_buffer.write(PressureSensor::new( + self.pressure_sensor, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); + + hil::sensors::PressureDriver::set_client(self.pressure_sensor, pressure); + + pressure + } +} diff --git a/boards/components/src/process_console.rs b/boards/components/src/process_console.rs index e17460a19f..34a7fd2171 100644 --- a/boards/components/src/process_console.rs +++ b/boards/components/src/process_console.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for ProcessConsole, the command console. //! //! This provides one Component, ProcessConsoleComponent, which implements a @@ -7,69 +11,90 @@ //! Usage //! ----- //! ```rust -//! let pconsole = ProcessConsoleComponent::new(board_kernel, uart_mux).finalize(()); +//! let pconsole = ProcessConsoleComponent::new(board_kernel, uart_mux, alarm_mux, process_printer, Some(reset_function)) +//! .finalize(process_console_component_static!()); //! ``` // Author: Philip Levis // Last modified: 6/20/2018 -use core::marker::PhantomData; +use capsules_core::process_console::{self, ProcessConsole}; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_uart::{MuxUart, UartDevice}; use core::mem::MaybeUninit; - -use capsules::process_console::{self, ProcessConsole}; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; -use capsules::virtual_uart::{MuxUart, UartDevice}; use kernel::capabilities; use kernel::component::Component; use kernel::hil; use kernel::hil::time::Alarm; use kernel::process::ProcessPrinter; -use kernel::{static_init, static_init_half}; #[macro_export] -macro_rules! process_console_component_helper { - ($A: ty) => {{ - use capsules::process_console::ProcessConsole; - use capsules::virtual_alarm::VirtualMuxAlarm; - use components::process_console::Capability; - use core::mem::MaybeUninit; - - static mut BUFFER: MaybeUninit, Capability>> = - MaybeUninit::uninit(); +macro_rules! process_console_component_static { + ($A: ty, $COMMAND_HISTORY_LEN: expr $(,)?) => {{ + let alarm = kernel::static_buf!(capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A>); + let uart = kernel::static_buf!(capsules_core::virtualizers::virtual_uart::UartDevice); + let pconsole = kernel::static_buf!( + capsules_core::process_console::ProcessConsole< + $COMMAND_HISTORY_LEN, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A>, + components::process_console::Capability, + > + ); - static mut ALARM: MaybeUninit> = MaybeUninit::uninit(); + let write_buffer = kernel::static_buf!([u8; capsules_core::process_console::WRITE_BUF_LEN]); + let read_buffer = kernel::static_buf!([u8; capsules_core::process_console::READ_BUF_LEN]); + let queue_buffer = kernel::static_buf!([u8; capsules_core::process_console::QUEUE_BUF_LEN]); + let command_buffer = kernel::static_buf!([u8; capsules_core::process_console::COMMAND_BUF_LEN]); + let command_history_buffer = kernel::static_buf!( + [capsules_core::process_console::Command; $COMMAND_HISTORY_LEN] + ); - (&mut BUFFER, &mut ALARM) - }}; + ( + alarm, + uart, + write_buffer, + read_buffer, + queue_buffer, + command_buffer, + command_history_buffer, + pconsole, + ) + };}; + ($A: ty $(,)?) => {{ + $crate::process_console_component_static!($A, { capsules_core::process_console::DEFAULT_COMMAND_HISTORY_LEN }) + };}; } -pub struct ProcessConsoleComponent> { +pub struct ProcessConsoleComponent> { board_kernel: &'static kernel::Kernel, uart_mux: &'static MuxUart<'static>, alarm_mux: &'static MuxAlarm<'static, A>, - _alarm: PhantomData, process_printer: &'static dyn ProcessPrinter, + reset_function: Option !>, } -impl> ProcessConsoleComponent { +impl> + ProcessConsoleComponent +{ pub fn new( board_kernel: &'static kernel::Kernel, uart_mux: &'static MuxUart, alarm_mux: &'static MuxAlarm<'static, A>, process_printer: &'static dyn ProcessPrinter, - ) -> ProcessConsoleComponent { + reset_function: Option !>, + ) -> ProcessConsoleComponent { ProcessConsoleComponent { - board_kernel: board_kernel, - uart_mux: uart_mux, - alarm_mux: alarm_mux, - _alarm: PhantomData, + board_kernel, + uart_mux, + alarm_mux, process_printer, + reset_function, } } } -/// These constants are defined in the linker script for where the -/// kernel is placed in memory on chip. +// These constants are defined in the linker script for where the +// kernel is placed in memory on chip. extern "C" { static _estack: u8; static _sstack: u8; @@ -85,56 +110,84 @@ extern "C" { pub struct Capability; unsafe impl capabilities::ProcessManagementCapability for Capability {} -impl> Component for ProcessConsoleComponent { +impl> Component + for ProcessConsoleComponent +{ type StaticInput = ( - &'static mut MaybeUninit, Capability>>, &'static mut MaybeUninit>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; capsules_core::process_console::WRITE_BUF_LEN]>, + &'static mut MaybeUninit<[u8; capsules_core::process_console::READ_BUF_LEN]>, + &'static mut MaybeUninit<[u8; capsules_core::process_console::QUEUE_BUF_LEN]>, + &'static mut MaybeUninit<[u8; capsules_core::process_console::COMMAND_BUF_LEN]>, + &'static mut MaybeUninit<[capsules_core::process_console::Command; COMMAND_HISTORY_LEN]>, + &'static mut MaybeUninit< + ProcessConsole<'static, COMMAND_HISTORY_LEN, VirtualMuxAlarm<'static, A>, Capability>, + >, ); - type Output = - &'static process_console::ProcessConsole<'static, VirtualMuxAlarm<'static, A>, Capability>; - - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + type Output = &'static process_console::ProcessConsole< + 'static, + COMMAND_HISTORY_LEN, + VirtualMuxAlarm<'static, A>, + Capability, + >; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { // Create virtual device for console. - let console_uart = static_init!(UartDevice, UartDevice::new(self.uart_mux, true)); + let console_uart = static_buffer.1.write(UartDevice::new(self.uart_mux, true)); console_uart.setup(); // Get addresses of where the kernel is placed to enable additional // debugging in process console. - let kernel_addresses = process_console::KernelAddresses { - stack_start: &_sstack as *const u8, - stack_end: &_estack as *const u8, - text_start: &_stext as *const u8, - text_end: &_etext as *const u8, - read_only_data_start: &_srodata as *const u8, - relocations_start: &_srelocate as *const u8, - relocations_end: &_erelocate as *const u8, - bss_start: &_szero as *const u8, - bss_end: &_ezero as *const u8, + // SAFETY: These statics are defined by the linker script, and we are merely creating + // pointers to them. + let kernel_addresses = unsafe { + process_console::KernelAddresses { + stack_start: core::ptr::addr_of!(_sstack), + stack_end: core::ptr::addr_of!(_estack), + text_start: core::ptr::addr_of!(_stext), + text_end: core::ptr::addr_of!(_etext), + read_only_data_start: core::ptr::addr_of!(_srodata), + relocations_start: core::ptr::addr_of!(_srelocate), + relocations_end: core::ptr::addr_of!(_erelocate), + bss_start: core::ptr::addr_of!(_szero), + bss_end: core::ptr::addr_of!(_ezero), + } }; - let console_alarm = static_init_half!( - static_buffer.1, - VirtualMuxAlarm<'static, A>, - VirtualMuxAlarm::new(self.alarm_mux) - ); + let console_alarm = static_buffer.0.write(VirtualMuxAlarm::new(self.alarm_mux)); console_alarm.setup(); - let console = static_init_half!( - static_buffer.0, - ProcessConsole<'static, VirtualMuxAlarm<'static, A>, Capability>, - ProcessConsole::new( - console_uart, - console_alarm, - self.process_printer, - &mut process_console::WRITE_BUF, - &mut process_console::READ_BUF, - &mut process_console::QUEUE_BUF, - &mut process_console::COMMAND_BUF, - self.board_kernel, - kernel_addresses, - Capability, - ) - ); + let write_buffer = static_buffer + .2 + .write([0; capsules_core::process_console::WRITE_BUF_LEN]); + let read_buffer = static_buffer + .3 + .write([0; capsules_core::process_console::READ_BUF_LEN]); + let queue_buffer = static_buffer + .4 + .write([0; capsules_core::process_console::QUEUE_BUF_LEN]); + let command_buffer = static_buffer + .5 + .write([0; capsules_core::process_console::COMMAND_BUF_LEN]); + let command_history_buffer = static_buffer + .6 + .write([capsules_core::process_console::Command::default(); COMMAND_HISTORY_LEN]); + + let console = static_buffer.7.write(ProcessConsole::new( + console_uart, + console_alarm, + self.process_printer, + write_buffer, + read_buffer, + queue_buffer, + command_buffer, + command_history_buffer, + self.board_kernel, + kernel_addresses, + self.reset_function, + Capability, + )); hil::uart::Transmit::set_transmit_client(console_uart, console); hil::uart::Receive::set_receive_client(console_uart, console); console_alarm.set_alarm_client(console); diff --git a/boards/components/src/process_printer.rs b/boards/components/src/process_printer.rs index 03a71de9e2..049edb7420 100644 --- a/boards/components/src/process_printer.rs +++ b/boards/components/src/process_printer.rs @@ -1,13 +1,25 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for process printers. //! //! Usage //! ----- //! ```rust -//! let process_printer = ProcessPrinterTextComponent::new().finalize(()); +//! let process_printer = ProcessPrinterTextComponent::new() +//! .finalize(components::process_printer_component_static!()); //! ``` +use core::mem::MaybeUninit; use kernel::component::Component; -use kernel::static_init; + +#[macro_export] +macro_rules! process_printer_text_component_static { + () => {{ + kernel::static_buf!(capsules_system::process_printer::ProcessPrinterText) + };}; +} pub struct ProcessPrinterTextComponent {} @@ -18,13 +30,11 @@ impl ProcessPrinterTextComponent { } impl Component for ProcessPrinterTextComponent { - type StaticInput = (); - type Output = &'static kernel::process::ProcessPrinterText; + type StaticInput = + &'static mut MaybeUninit; + type Output = &'static capsules_system::process_printer::ProcessPrinterText; - unsafe fn finalize(self, _static_buffer: Self::StaticInput) -> Self::Output { - static_init!( - kernel::process::ProcessPrinterText, - kernel::process::ProcessPrinterText::new() - ) + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + static_buffer.write(capsules_system::process_printer::ProcessPrinterText::new()) } } diff --git a/boards/components/src/proximity.rs b/boards/components/src/proximity.rs new file mode 100644 index 0000000000..18f8d93606 --- /dev/null +++ b/boards/components/src/proximity.rs @@ -0,0 +1,61 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Component for any proximity sensor. +//! +//! Usage +//! ----- +//! ```rust +//! let proximity = ProximityComponent::new(apds9960, board_kernel, capsules_extra::proximity::DRIVER_NUM) +//! .finalize(components::proximity_component_static!()); +//! ``` + +use capsules_extra::proximity::ProximitySensor; +use core::mem::MaybeUninit; +use kernel::capabilities; +use kernel::component::Component; +use kernel::create_capability; +use kernel::hil; + +#[macro_export] +macro_rules! proximity_component_static { + () => {{ + kernel::static_buf!(capsules_extra::proximity::ProximitySensor<'static>) + };}; +} + +pub struct ProximityComponent + 'static> { + sensor: &'static P, + board_kernel: &'static kernel::Kernel, + driver_num: usize, +} + +impl> ProximityComponent

{ + pub fn new( + sensor: &'static P, + board_kernel: &'static kernel::Kernel, + driver_num: usize, + ) -> ProximityComponent

{ + ProximityComponent { + sensor, + board_kernel, + driver_num, + } + } +} + +impl> Component for ProximityComponent

{ + type StaticInput = &'static mut MaybeUninit>; + type Output = &'static ProximitySensor<'static>; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + let grant = self.board_kernel.create_grant(self.driver_num, &grant_cap); + + let proximity = s.write(ProximitySensor::new(self.sensor, grant)); + + hil::sensors::ProximityDriver::set_client(self.sensor, proximity); + proximity + } +} diff --git a/boards/components/src/pwm.rs b/boards/components/src/pwm.rs new file mode 100644 index 0000000000..b055d65b3b --- /dev/null +++ b/boards/components/src/pwm.rs @@ -0,0 +1,129 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Components for using PWM. + +use capsules_core::virtualizers::virtual_pwm::{MuxPwm, PwmPinUser}; +use capsules_extra::pwm::Pwm; +use core::mem::MaybeUninit; +use kernel::capabilities; +use kernel::component::Component; +use kernel::create_capability; +use kernel::hil::pwm; + +#[macro_export] +macro_rules! pwm_mux_component_static { + ($A:ty $(,)?) => {{ + kernel::static_buf!(capsules_core::virtualizers::virtual_pwm::MuxPwm<'static, $A>) + };}; +} + +#[macro_export] +macro_rules! pwm_pin_user_component_static { + ($A:ty $(,)?) => {{ + kernel::static_buf!(capsules_core::virtualizers::virtual_pwm::PwmPinUser<'static, $A>) + };}; +} + +#[macro_export] +macro_rules! pwm_driver_component_helper { + ($($P:expr),+ $(,)?) => {{ + use kernel::count_expressions; + use kernel::static_init; + const NUM_DRIVERS: usize = count_expressions!($($P),+); + + let drivers = static_init!( + [&'static dyn kernel::hil::pwm::PwmPin; NUM_DRIVERS], + [ + $($P,)* + ] + ); + let pwm = kernel::static_buf!(capsules_extra::pwm::Pwm<'static, NUM_DRIVERS>); + (pwm, drivers) + };}; +} + +pub struct PwmMuxComponent { + pwm: &'static P, +} + +impl PwmMuxComponent

{ + pub fn new(pwm: &'static P) -> Self { + PwmMuxComponent { pwm } + } +} + +impl Component for PwmMuxComponent

{ + type StaticInput = &'static mut MaybeUninit>; + type Output = &'static MuxPwm<'static, P>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let pwm_mux = static_buffer.write(MuxPwm::new(self.pwm)); + + pwm_mux + } +} + +pub struct PwmPinUserComponent { + pwm_mux: &'static MuxPwm<'static, P>, + channel: P::Pin, +} + +impl PwmPinUserComponent

{ + pub fn new(mux: &'static MuxPwm<'static, P>, channel: P::Pin) -> Self { + PwmPinUserComponent { + pwm_mux: mux, + channel, + } + } +} + +impl Component for PwmPinUserComponent

{ + type StaticInput = &'static mut MaybeUninit>; + type Output = &'static PwmPinUser<'static, P>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let pwm_pin = static_buffer.write(PwmPinUser::new(self.pwm_mux, self.channel)); + + pwm_pin.add_to_mux(); + + pwm_pin + } +} + +pub struct PwmDriverComponent { + board_kernel: &'static kernel::Kernel, + driver_num: usize, +} + +impl PwmDriverComponent { + pub fn new( + board_kernel: &'static kernel::Kernel, + driver_num: usize, + ) -> PwmDriverComponent { + PwmDriverComponent { + board_kernel, + driver_num, + } + } +} + +impl Component for PwmDriverComponent { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static [&'static dyn kernel::hil::pwm::PwmPin; NUM_PINS], + ); + type Output = &'static capsules_extra::pwm::Pwm<'static, NUM_PINS>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + let grant_adc = self.board_kernel.create_grant(self.driver_num, &grant_cap); + + let pwm = static_buffer + .0 + .write(capsules_extra::pwm::Pwm::new(static_buffer.1, grant_adc)); + + pwm + } +} diff --git a/boards/components/src/rf233.rs b/boards/components/src/rf233.rs new file mode 100644 index 0000000000..178c3c4109 --- /dev/null +++ b/boards/components/src/rf233.rs @@ -0,0 +1,119 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Component for communicating with an RF233 chip (802.15.4) connected via SPI. +//! +//! This provides one Component, RF233Component, which provides basic +//! packet-level interfaces for communicating with 802.15.4. +//! +//! Usage +//! ----- +//! ```rust +//! let rf233 = components::rf233::RF233Component::new( +//! rf233_spi, +//! &peripherals.pa[09], // reset +//! &peripherals.pa[10], // sleep +//! &peripherals.pa[08], // irq +//! &peripherals.pa[08], +//! RADIO_CHANNEL, +//! ) +//! .finalize(components::rf233_component_static!(sam4l::spi::SpiHw)); +//! ``` + +use capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice; +use capsules_extra::rf233::RF233; +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::hil::radio::RadioConfig; +use kernel::hil::spi::{SpiMaster, SpiMasterDevice}; +use kernel::hil::{self, radio}; + +// Setup static space for the objects. +#[macro_export] +macro_rules! rf233_component_static { + ($S:ty $(,)?) => {{ + let spi_device = kernel::static_buf!( + capsules_extra::rf233::RF233< + 'static, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice<'static, $S>, + > + ); + // The RF233 radio stack requires four buffers for its SPI operations: + // + // 1. buf: a packet-sized buffer for SPI operations, which is + // used as the read buffer when it writes a packet passed to it and the write + // buffer when it reads a packet into a buffer passed to it. + // 2. rx_buf: buffer to receive packets into + // 3 + 4: two small buffers for performing registers + // operations (one read, one write). + let rf233_buf = kernel::static_buf!([u8; kernel::hil::radio::MAX_BUF_SIZE]); + let rf233_reg_write = + kernel::static_buf!([u8; capsules_extra::rf233::SPI_REGISTER_TRANSACTION_LENGTH]); + let rf233_reg_read = + kernel::static_buf!([u8; capsules_extra::rf233::SPI_REGISTER_TRANSACTION_LENGTH]); + + (spi_device, rf233_buf, rf233_reg_write, rf233_reg_read) + };}; +} + +pub struct RF233Component + 'static> { + spi: &'static VirtualSpiMasterDevice<'static, S>, + reset: &'static dyn hil::gpio::Pin, + sleep: &'static dyn hil::gpio::Pin, + irq: &'static dyn hil::gpio::InterruptPin<'static>, + ctl: &'static dyn hil::gpio::InterruptPin<'static>, + channel: radio::RadioChannel, +} + +impl + 'static> RF233Component { + pub fn new( + spi: &'static VirtualSpiMasterDevice<'static, S>, + reset: &'static dyn hil::gpio::Pin, + sleep: &'static dyn hil::gpio::Pin, + irq: &'static dyn hil::gpio::InterruptPin<'static>, + ctl: &'static dyn hil::gpio::InterruptPin<'static>, + channel: radio::RadioChannel, + ) -> Self { + Self { + spi, + reset, + sleep, + irq, + ctl, + channel, + } + } +} + +impl + 'static> Component for RF233Component { + type StaticInput = ( + &'static mut MaybeUninit>>, + &'static mut MaybeUninit<[u8; hil::radio::MAX_BUF_SIZE]>, + &'static mut MaybeUninit<[u8; capsules_extra::rf233::SPI_REGISTER_TRANSACTION_LENGTH]>, + &'static mut MaybeUninit<[u8; capsules_extra::rf233::SPI_REGISTER_TRANSACTION_LENGTH]>, + ); + type Output = &'static RF233<'static, VirtualSpiMasterDevice<'static, S>>; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let rf233_buf = s.1.write([0; hil::radio::MAX_BUF_SIZE]); + let rf233_reg_write = + s.2.write([0; capsules_extra::rf233::SPI_REGISTER_TRANSACTION_LENGTH]); + let rf233_reg_read = + s.3.write([0; capsules_extra::rf233::SPI_REGISTER_TRANSACTION_LENGTH]); + let rf233 = s.0.write(RF233::new( + self.spi, + rf233_buf, + rf233_reg_write, + rf233_reg_read, + self.reset, + self.sleep, + self.irq, + self.channel, + )); + self.ctl.set_client(rf233); + self.spi.set_client(rf233); + let _ = rf233.initialize(); + rf233 + } +} diff --git a/boards/components/src/rng.rs b/boards/components/src/rng.rs index a6cad45110..a828ffa5e5 100644 --- a/boards/components/src/rng.rs +++ b/boards/components/src/rng.rs @@ -1,63 +1,87 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for random number generator using `Entropy32ToRandom`. //! -//! This provides one Component, RngComponent, which implements a -//! userspace syscall interface to the RNG peripheral (TRNG). +//! This provides one Component, RngComponent, which implements a userspace +//! syscall interface to the RNG peripheral (TRNG). //! //! Usage //! ----- //! ```rust -//! let rng = components::rng::RngComponent::new(board_kernel, &sam4l::trng::TRNG).finalize(()); +//! let rng = components::rng::RngComponent::new(board_kernel, &sam4l::trng::TRNG) +//! .finalize(rng_component_static!()); //! ``` // Author: Hudson Ayers // Last modified: 07/12/2019 -use capsules::rng; +use capsules_core::rng; +use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; use kernel::hil::entropy::Entropy32; use kernel::hil::rng::Rng; -use kernel::static_init; -pub struct RngComponent { +#[macro_export] +macro_rules! rng_component_static { + ($E: ty $(,)?) => {{ + let etr = kernel::static_buf!(capsules_core::rng::Entropy32ToRandom<'static, $E>); + let rng = kernel::static_buf!( + capsules_core::rng::RngDriver< + 'static, + capsules_core::rng::Entropy32ToRandom<'static, $E>, + > + ); + + (etr, rng) + };}; +} + +pub type RngComponentType = + rng::RngDriver<'static, capsules_core::rng::Entropy32ToRandom<'static, E>>; + +pub struct RngComponent + 'static> { board_kernel: &'static kernel::Kernel, driver_num: usize, - trng: &'static dyn Entropy32<'static>, + trng: &'static E, } -impl RngComponent { - pub fn new( - board_kernel: &'static kernel::Kernel, - driver_num: usize, - trng: &'static dyn Entropy32<'static>, - ) -> RngComponent { - RngComponent { - board_kernel: board_kernel, - driver_num: driver_num, - trng: trng, +impl> RngComponent { + pub fn new(board_kernel: &'static kernel::Kernel, driver_num: usize, trng: &'static E) -> Self { + Self { + board_kernel, + driver_num, + trng, } } } -impl Component for RngComponent { - type StaticInput = (); - type Output = &'static rng::RngDriver<'static>; +impl> Component for RngComponent { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit< + capsules_core::rng::RngDriver< + 'static, + capsules_core::rng::Entropy32ToRandom<'static, E>, + >, + >, + ); + type Output = + &'static rng::RngDriver<'static, capsules_core::rng::Entropy32ToRandom<'static, E>>; - unsafe fn finalize(self, _static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let entropy_to_random = static_init!( - rng::Entropy32ToRandom<'static>, - rng::Entropy32ToRandom::new(self.trng) - ); - let rng = static_init!( - rng::RngDriver<'static>, - rng::RngDriver::new( - entropy_to_random, - self.board_kernel.create_grant(self.driver_num, &grant_cap) - ) - ); + let entropy_to_random = static_buffer + .0 + .write(rng::Entropy32ToRandom::new(self.trng)); + let rng = static_buffer.1.write(rng::RngDriver::new( + entropy_to_random, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); self.trng.set_client(entropy_to_random); entropy_to_random.set_client(rng); diff --git a/boards/components/src/sched/cooperative.rs b/boards/components/src/sched/cooperative.rs index be01f2189f..1c438550dd 100644 --- a/boards/components/src/sched/cooperative.rs +++ b/boards/components/src/sched/cooperative.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for a cooperative scheduler. //! //! This provides one Component, CooperativeComponent. @@ -6,7 +10,7 @@ //! ----- //! ```rust //! let scheduler = components::cooperative::CooperativeComponent::new(&PROCESSES) -//! .finalize(components::coop_component_helper!(NUM_PROCS)); +//! .finalize(components::cooperative_component_static!(NUM_PROCS)); //! ``` // Author: Hudson Ayers @@ -15,43 +19,47 @@ use core::mem::MaybeUninit; use kernel::component::Component; use kernel::process::Process; use kernel::scheduler::cooperative::{CoopProcessNode, CooperativeSched}; -use kernel::{static_init, static_init_half}; #[macro_export] -macro_rules! coop_component_helper { +macro_rules! cooperative_component_static { ($N:expr $(,)?) => {{ - use core::mem::MaybeUninit; - use kernel::scheduler::cooperative::CoopProcessNode; - use kernel::static_buf; - const UNINIT: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF: [MaybeUninit>; $N] = [UNINIT; $N]; - &mut BUF + let coop_sched = + kernel::static_buf!(kernel::scheduler::cooperative::CooperativeSched<'static>); + let coop_nodes = kernel::static_buf!( + [core::mem::MaybeUninit>; $N] + ); + + (coop_sched, coop_nodes) };}; } -pub struct CooperativeComponent { +pub struct CooperativeComponent { processes: &'static [Option<&'static dyn Process>], } -impl CooperativeComponent { - pub fn new(processes: &'static [Option<&'static dyn Process>]) -> CooperativeComponent { +impl CooperativeComponent { + pub fn new( + processes: &'static [Option<&'static dyn Process>], + ) -> CooperativeComponent { CooperativeComponent { processes } } } -impl Component for CooperativeComponent { - type StaticInput = &'static mut [MaybeUninit>]; +impl Component for CooperativeComponent { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[MaybeUninit>; NUM_PROCS]>, + ); type Output = &'static mut CooperativeSched<'static>; - unsafe fn finalize(self, proc_nodes: Self::StaticInput) -> Self::Output { - let scheduler = static_init!(CooperativeSched<'static>, CooperativeSched::new()); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let scheduler = static_buffer.0.write(CooperativeSched::new()); + + const UNINIT: MaybeUninit> = MaybeUninit::uninit(); + let nodes = static_buffer.1.write([UNINIT; NUM_PROCS]); - for (i, node) in proc_nodes.iter_mut().enumerate() { - let init_node = static_init_half!( - node, - CoopProcessNode<'static>, - CoopProcessNode::new(&self.processes[i]) - ); + for (i, node) in nodes.iter_mut().enumerate() { + let init_node = node.write(CoopProcessNode::new(&self.processes[i])); scheduler.processes.push_head(init_node); } scheduler diff --git a/boards/components/src/sched/mlfq.rs b/boards/components/src/sched/mlfq.rs index aba4a70363..4210367171 100644 --- a/boards/components/src/sched/mlfq.rs +++ b/boards/components/src/sched/mlfq.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for a multi-level feedback queue scheduler. //! //! This provides one Component, MLFQComponent. @@ -7,38 +11,42 @@ use core::mem::MaybeUninit; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; use kernel::component::Component; use kernel::hil::time; use kernel::process::Process; use kernel::scheduler::mlfq::{MLFQProcessNode, MLFQSched}; -use kernel::static_init_half; #[macro_export] -macro_rules! mlfq_component_helper { +macro_rules! mlfq_component_static { ($A:ty, $N:expr $(,)?) => {{ - use core::mem::MaybeUninit; - use kernel::scheduler::mlfq::{MLFQProcessNode, MLFQSched}; - use kernel::static_init; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF2: MaybeUninit>> = - MaybeUninit::uninit(); - const UNINIT: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF3: [MaybeUninit>; $N] = [UNINIT; $N]; - (&mut BUF1, &mut BUF2, &mut BUF3) + let alarm = kernel::static_buf!( + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A> + ); + let mlfq_sched = kernel::static_buf!( + kernel::scheduler::mlfq::MLFQSched< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A>, + > + ); + let mlfq_node = kernel::static_buf!( + [core::mem::MaybeUninit>; $N] + ); + + (alarm, mlfq_sched, mlfq_node) };}; } -pub struct MLFQComponent> { +pub struct MLFQComponent, const NUM_PROCS: usize> { alarm_mux: &'static MuxAlarm<'static, A>, processes: &'static [Option<&'static dyn Process>], } -impl> MLFQComponent { +impl, const NUM_PROCS: usize> MLFQComponent { pub fn new( alarm_mux: &'static MuxAlarm<'static, A>, processes: &'static [Option<&'static dyn Process>], - ) -> MLFQComponent { + ) -> MLFQComponent { MLFQComponent { alarm_mux, processes, @@ -46,34 +54,27 @@ impl> MLFQComponent { } } -impl> Component for MLFQComponent { +impl, const NUM_PROCS: usize> Component + for MLFQComponent +{ type StaticInput = ( &'static mut MaybeUninit>, &'static mut MaybeUninit>>, - &'static mut [MaybeUninit>], + &'static mut MaybeUninit<[MaybeUninit>; NUM_PROCS]>, ); type Output = &'static mut MLFQSched<'static, VirtualMuxAlarm<'static, A>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let (alarm_buf, sched_buf, proc_nodes) = static_buffer; - let scheduler_alarm = static_init_half!( - alarm_buf, - VirtualMuxAlarm<'static, A>, - VirtualMuxAlarm::new(self.alarm_mux) - ); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let scheduler_alarm = static_buffer.0.write(VirtualMuxAlarm::new(self.alarm_mux)); scheduler_alarm.setup(); - let scheduler = static_init_half!( - sched_buf, - MLFQSched<'static, VirtualMuxAlarm<'static, A>>, - MLFQSched::new(scheduler_alarm) - ); - for (i, node) in proc_nodes.iter_mut().enumerate() { - let init_node = static_init_half!( - node, - MLFQProcessNode<'static>, - MLFQProcessNode::new(&self.processes[i]) - ); + let scheduler = static_buffer.1.write(MLFQSched::new(scheduler_alarm)); + + const UNINIT: MaybeUninit> = MaybeUninit::uninit(); + let nodes = static_buffer.2.write([UNINIT; NUM_PROCS]); + + for (i, node) in nodes.iter_mut().enumerate() { + let init_node = node.write(MLFQProcessNode::new(&self.processes[i])); scheduler.processes[0].push_head(init_node); } scheduler diff --git a/boards/components/src/sched/mod.rs b/boards/components/src/sched/mod.rs index 88d306530a..199ac75995 100644 --- a/boards/components/src/sched/mod.rs +++ b/boards/components/src/sched/mod.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + pub mod cooperative; pub mod mlfq; pub mod priority; diff --git a/boards/components/src/sched/priority.rs b/boards/components/src/sched/priority.rs index 9399ff7093..731d5768e3 100644 --- a/boards/components/src/sched/priority.rs +++ b/boards/components/src/sched/priority.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for a priority scheduler. //! //! This provides one Component, PriorityComponent. @@ -6,12 +10,20 @@ //! ----- //! ```rust //! let scheduler = -//! components::priority::PriorityComponent::new(board_kernel).finalize(()); +//! components::priority::PriorityComponent::new(board_kernel) +//! .finalize(components::priority_component_static!()); //! ``` +use core::mem::MaybeUninit; use kernel::component::Component; use kernel::scheduler::priority::PrioritySched; -use kernel::static_init; + +#[macro_export] +macro_rules! priority_component_static { + () => {{ + kernel::static_buf!(kernel::scheduler::priority::PrioritySched) + };}; +} pub struct PriorityComponent { board_kernel: &'static kernel::Kernel, @@ -24,11 +36,10 @@ impl PriorityComponent { } impl Component for PriorityComponent { - type StaticInput = (); + type StaticInput = &'static mut MaybeUninit; type Output = &'static mut PrioritySched; - unsafe fn finalize(self, _static_buffer: Self::StaticInput) -> Self::Output { - let scheduler = static_init!(PrioritySched, PrioritySched::new(self.board_kernel)); - scheduler + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + static_buffer.write(PrioritySched::new(self.board_kernel)) } } diff --git a/boards/components/src/sched/round_robin.rs b/boards/components/src/sched/round_robin.rs index c29412c7d0..a3c358da22 100644 --- a/boards/components/src/sched/round_robin.rs +++ b/boards/components/src/sched/round_robin.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for a round robin scheduler. //! //! This provides one Component, RoundRobinComponent. @@ -6,7 +10,7 @@ //! ----- //! ```rust //! let scheduler = components::round_robin::RoundRobinComponent::new(&PROCESSES) -//! .finalize(components::rr_component_helper!(NUM_PROCS)); +//! .finalize(components::round_robin_component_static!(NUM_PROCS)); //! ``` // Author: Hudson Ayers @@ -16,43 +20,48 @@ use core::mem::MaybeUninit; use kernel::component::Component; use kernel::process::Process; use kernel::scheduler::round_robin::{RoundRobinProcessNode, RoundRobinSched}; -use kernel::{static_init, static_init_half}; #[macro_export] -macro_rules! rr_component_helper { +macro_rules! round_robin_component_static { ($N:expr $(,)?) => {{ - use core::mem::MaybeUninit; - use kernel::scheduler::round_robin::RoundRobinProcessNode; - use kernel::static_buf; - const UNINIT: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF: [MaybeUninit>; $N] = [UNINIT; $N]; - &mut BUF + let rr_sched = + kernel::static_buf!(kernel::scheduler::round_robin::RoundRobinSched<'static>); + let rr_nodes = kernel::static_buf!( + [core::mem::MaybeUninit>; + $N] + ); + + (rr_sched, rr_nodes) };}; } -pub struct RoundRobinComponent { +pub struct RoundRobinComponent { processes: &'static [Option<&'static dyn Process>], } -impl RoundRobinComponent { - pub fn new(processes: &'static [Option<&'static dyn Process>]) -> RoundRobinComponent { +impl RoundRobinComponent { + pub fn new( + processes: &'static [Option<&'static dyn Process>], + ) -> RoundRobinComponent { RoundRobinComponent { processes } } } -impl Component for RoundRobinComponent { - type StaticInput = &'static mut [MaybeUninit>]; +impl Component for RoundRobinComponent { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[MaybeUninit>; NUM_PROCS]>, + ); type Output = &'static mut RoundRobinSched<'static>; - unsafe fn finalize(self, buf: Self::StaticInput) -> Self::Output { - let scheduler = static_init!(RoundRobinSched<'static>, RoundRobinSched::new()); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let scheduler = static_buffer.0.write(RoundRobinSched::new()); + + const UNINIT: MaybeUninit> = MaybeUninit::uninit(); + let nodes = static_buffer.1.write([UNINIT; NUM_PROCS]); - for (i, node) in buf.iter_mut().enumerate() { - let init_node = static_init_half!( - node, - RoundRobinProcessNode<'static>, - RoundRobinProcessNode::new(&self.processes[i]) - ); + for (i, node) in nodes.iter_mut().enumerate() { + let init_node = node.write(RoundRobinProcessNode::new(&self.processes[i])); scheduler.processes.push_head(init_node); } scheduler diff --git a/boards/components/src/screen.rs b/boards/components/src/screen.rs index 31aea7494d..00e90d76bd 100644 --- a/boards/components/src/screen.rs +++ b/boards/components/src/screen.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for the Screen. //! //! Buffer Size @@ -16,74 +20,152 @@ //! ```rust //! let screen = //! components::screen::ScreenComponent::new(board_kernel, tft, None) -//! .finalize(components::screen_buffer_size!(40960)); +//! .finalize(components::screen_component_static!(40960)); //! ``` //! //! // Screen with Setup //! ```rust //! let screen = //! components::screen::ScreenComponent::new(board_kernel, tft, Some(tft)) -//! .finalize(components::screen_buffer_size!(40960)); +//! .finalize(components::screen_component_static!(40960)); //! ``` + +use capsules_extra::screen::Screen; +use capsules_extra::screen_shared::ScreenShared; +use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; -use kernel::static_init; +use kernel::hil; #[macro_export] -macro_rules! screen_buffer_size { +macro_rules! screen_component_static { ($s:literal $(,)?) => {{ - static mut BUFFER: [u8; $s] = [0; $s]; - (&mut BUFFER) - }}; + let buffer = kernel::static_buf!([u8; $s]); + let screen = kernel::static_buf!(capsules_extra::screen::Screen); + + (buffer, screen) + };}; } -pub struct ScreenComponent { +pub type ScreenComponentType = capsules_extra::screen::Screen<'static>; + +pub struct ScreenComponent { board_kernel: &'static kernel::Kernel, driver_num: usize, - screen: &'static dyn kernel::hil::screen::Screen, - screen_setup: Option<&'static dyn kernel::hil::screen::ScreenSetup>, + screen: &'static dyn kernel::hil::screen::Screen<'static>, + screen_setup: Option<&'static dyn kernel::hil::screen::ScreenSetup<'static>>, } -impl ScreenComponent { +impl ScreenComponent { pub fn new( board_kernel: &'static kernel::Kernel, driver_num: usize, screen: &'static dyn kernel::hil::screen::Screen, screen_setup: Option<&'static dyn kernel::hil::screen::ScreenSetup>, - ) -> ScreenComponent { + ) -> ScreenComponent { ScreenComponent { - board_kernel: board_kernel, - driver_num: driver_num, - screen: screen, - screen_setup: screen_setup, + board_kernel, + driver_num, + screen, + screen_setup, } } } -impl Component for ScreenComponent { - type StaticInput = &'static mut [u8]; - type Output = &'static capsules::screen::Screen<'static>; +impl Component for ScreenComponent { + type StaticInput = ( + &'static mut MaybeUninit<[u8; SCREEN_BUF_LEN]>, + &'static mut MaybeUninit>, + ); + type Output = &'static Screen<'static>; - unsafe fn finalize(self, static_input: Self::StaticInput) -> Self::Output { + fn finalize(self, static_input: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); let grant_screen = self.board_kernel.create_grant(self.driver_num, &grant_cap); - let screen = static_init!( - capsules::screen::Screen, - capsules::screen::Screen::new( - self.screen, - self.screen_setup, - static_input, - grant_screen - ) - ); - - kernel::hil::screen::Screen::set_client(self.screen, Some(screen)); + let buffer = static_input.0.write([0; SCREEN_BUF_LEN]); + + let screen = static_input.1.write(Screen::new( + self.screen, + self.screen_setup, + buffer, + grant_screen, + )); + + kernel::hil::screen::Screen::set_client(self.screen, screen); if let Some(screen_setup) = self.screen_setup { - kernel::hil::screen::ScreenSetup::set_client(screen_setup, Some(screen)); + kernel::hil::screen::ScreenSetup::set_client(screen_setup, screen); } screen } } + +#[macro_export] +macro_rules! screen_shared_component_static { + ($s:literal, $S:ty $(,)?) => {{ + let buffer = kernel::static_buf!([u8; $s]); + let screen = kernel::static_buf!(capsules_extra::screen_shared::ScreenShared<$S>); + + (buffer, screen) + };}; +} + +pub type ScreenSharedComponentType = capsules_extra::screen_shared::ScreenShared<'static, S>; + +pub struct ScreenSharedComponent< + const SCREEN_BUF_LEN: usize, + S: hil::screen::Screen<'static> + 'static, +> { + board_kernel: &'static kernel::Kernel, + driver_num: usize, + screen: &'static S, + apps_regions: &'static [capsules_extra::screen_shared::AppScreenRegion], +} + +impl> + ScreenSharedComponent +{ + pub fn new( + board_kernel: &'static kernel::Kernel, + driver_num: usize, + screen: &'static S, + apps_regions: &'static [capsules_extra::screen_shared::AppScreenRegion], + ) -> ScreenSharedComponent { + ScreenSharedComponent { + board_kernel, + driver_num, + screen, + apps_regions, + } + } +} + +impl> Component + for ScreenSharedComponent +{ + type StaticInput = ( + &'static mut MaybeUninit<[u8; SCREEN_BUF_LEN]>, + &'static mut MaybeUninit>, + ); + type Output = &'static ScreenShared<'static, S>; + + fn finalize(self, static_input: Self::StaticInput) -> Self::Output { + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + let grant_screen = self.board_kernel.create_grant(self.driver_num, &grant_cap); + + let buffer = static_input.0.write([0; SCREEN_BUF_LEN]); + + let screen = static_input.1.write(ScreenShared::new( + self.screen, + grant_screen, + buffer, + self.apps_regions, + )); + + kernel::hil::screen::Screen::set_client(self.screen, screen); + + screen + } +} diff --git a/boards/components/src/segger_rtt.rs b/boards/components/src/segger_rtt.rs index ca39edefd2..80bb53959b 100644 --- a/boards/components/src/segger_rtt.rs +++ b/boards/components/src/segger_rtt.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for SeggerRttMemory. //! //! This provides two `Component`s: @@ -8,34 +12,49 @@ //! Usage //! ----- //! ```rust -//! let rtt_memory = components::segger_rtt::SeggerRttMemoryComponent::new().finalize(()); +//! let rtt_memory = components::segger_rtt::SeggerRttMemoryComponent::new() +//! .finalize(components::segger_rtt_memory_component_static!()); //! let rtt = components::segger_rtt::SeggerRttComponent::new(mux_alarm, rtt_memory) -//! .finalize(components::segger_rtt_component_helper!(nrf52832::rtc::Rtc)); +//! .finalize(components::segger_rtt_component_static!(nrf52832::rtc::Rtc)); //! ``` // Author: Guillaume Endignoux // Last modified: 07/02/2020 -use capsules::segger_rtt::{ - SeggerRtt, SeggerRttMemory, DEFAULT_DOWN_BUFFER_LENGTH, DEFAULT_UP_BUFFER_LENGTH, -}; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_extra::segger_rtt::{SeggerRtt, SeggerRttMemory}; use core::mem::MaybeUninit; use kernel::component::Component; use kernel::hil::time::{self, Alarm}; -use kernel::{static_init, static_init_half}; // Setup static space for the objects. #[macro_export] -macro_rules! segger_rtt_component_helper { +macro_rules! segger_rtt_memory_component_static { + () => {{ + let rtt_memory = kernel::static_buf!(capsules_extra::segger_rtt::SeggerRttMemory); + let up_buffer = + kernel::static_buf!([u8; capsules_extra::segger_rtt::DEFAULT_UP_BUFFER_LENGTH]); + let down_buffer = + kernel::static_buf!([u8; capsules_extra::segger_rtt::DEFAULT_DOWN_BUFFER_LENGTH]); + + (rtt_memory, up_buffer, down_buffer) + };}; +} + +#[macro_export] +macro_rules! segger_rtt_component_static { ($A:ty $(,)?) => {{ - use capsules::segger_rtt::SeggerRtt; - use capsules::virtual_alarm::VirtualMuxAlarm; - use core::mem::MaybeUninit; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF2: MaybeUninit>> = - MaybeUninit::uninit(); - (&mut BUF1, &mut BUF2) + let alarm = kernel::static_buf!( + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A> + ); + let rtt = kernel::static_buf!( + capsules_extra::segger_rtt::SeggerRtt< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A>, + > + ); + + (alarm, rtt) };}; } @@ -47,7 +66,7 @@ pub struct SeggerRttMemoryRefs<'a> { impl<'a> SeggerRttMemoryRefs<'a> { pub unsafe fn get_rtt_memory_ptr(&mut self) -> *mut SeggerRttMemory<'a> { - self.rtt_memory as *mut _ + core::ptr::from_mut(self.rtt_memory) } } @@ -60,33 +79,30 @@ impl SeggerRttMemoryComponent { } impl Component for SeggerRttMemoryComponent { - type StaticInput = (); + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; capsules_extra::segger_rtt::DEFAULT_UP_BUFFER_LENGTH]>, + &'static mut MaybeUninit<[u8; capsules_extra::segger_rtt::DEFAULT_DOWN_BUFFER_LENGTH]>, + ); type Output = SeggerRttMemoryRefs<'static>; - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { + fn finalize(self, s: Self::StaticInput) -> Self::Output { let name = b"Terminal\0"; let up_buffer_name = name; let down_buffer_name = name; - let up_buffer = static_init!( - [u8; DEFAULT_UP_BUFFER_LENGTH], - [0; DEFAULT_UP_BUFFER_LENGTH] - ); - let down_buffer = static_init!( - [u8; DEFAULT_DOWN_BUFFER_LENGTH], - [0; DEFAULT_DOWN_BUFFER_LENGTH] - ); - - let rtt_memory = static_init!( - SeggerRttMemory, - SeggerRttMemory::new_raw( - up_buffer_name, - up_buffer.as_ptr(), - up_buffer.len(), - down_buffer_name, - down_buffer.as_ptr(), - down_buffer.len() - ) - ); + let up_buffer = + s.1.write([0; capsules_extra::segger_rtt::DEFAULT_UP_BUFFER_LENGTH]); + let down_buffer = + s.2.write([0; capsules_extra::segger_rtt::DEFAULT_DOWN_BUFFER_LENGTH]); + + let rtt_memory = s.0.write(SeggerRttMemory::new_raw( + up_buffer_name, + up_buffer.as_ptr(), + up_buffer.len(), + down_buffer_name, + down_buffer.as_ptr(), + down_buffer.len(), + )); SeggerRttMemoryRefs { rtt_memory, up_buffer, @@ -117,27 +133,20 @@ impl> Component for SeggerRttComponent { &'static mut MaybeUninit>, &'static mut MaybeUninit>>, ); - type Output = &'static capsules::segger_rtt::SeggerRtt<'static, VirtualMuxAlarm<'static, A>>; + type Output = + &'static capsules_extra::segger_rtt::SeggerRtt<'static, VirtualMuxAlarm<'static, A>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let virtual_alarm_rtt = static_init_half!( - static_buffer.0, - VirtualMuxAlarm<'static, A>, - VirtualMuxAlarm::new(self.mux_alarm) - ); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let virtual_alarm_rtt = static_buffer.0.write(VirtualMuxAlarm::new(self.mux_alarm)); virtual_alarm_rtt.setup(); // RTT communication channel - let rtt = static_init_half!( - static_buffer.1, - SeggerRtt<'static, VirtualMuxAlarm<'static, A>>, - SeggerRtt::new( - virtual_alarm_rtt, - self.rtt_memory_refs.rtt_memory, - self.rtt_memory_refs.up_buffer, - self.rtt_memory_refs.down_buffer - ) - ); + let rtt = static_buffer.1.write(SeggerRtt::new( + virtual_alarm_rtt, + self.rtt_memory_refs.rtt_memory, + self.rtt_memory_refs.up_buffer, + self.rtt_memory_refs.down_buffer, + )); virtual_alarm_rtt.set_alarm_client(rtt); diff --git a/boards/components/src/sh1106.rs b/boards/components/src/sh1106.rs new file mode 100644 index 0000000000..f7a5f6e677 --- /dev/null +++ b/boards/components/src/sh1106.rs @@ -0,0 +1,89 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Components for the SH1106 OLED screen. +//! +//! Usage +//! ----- +//! ```rust +//! +//! let oled_i2c = components::i2c::I2CComponent::new(i2c_bus, 0x3c) +//! .finalize(components::i2c_component_static!(nrf52840::i2c::TWI)); +//! +//! let sh1106 = components::sh1106::Sh1106Component::new(oled_i2c, true) +//! .finalize(components::sh1106_component_static!(nrf52840::i2c::TWI)); +//! ``` + +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::hil; + +// Setup static space for the objects. +#[macro_export] +macro_rules! sh1106_component_static { + ($I: ty $(,)?) => {{ + let buffer = kernel::static_buf!([u8; capsules_extra::sh1106::BUFFER_SIZE]); + let sh1106 = kernel::static_buf!( + capsules_extra::sh1106::Sh1106< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>, + > + ); + + (buffer, sh1106) + };}; +} + +pub type Sh1106ComponentType = capsules_extra::sh1106::Sh1106< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, I>, +>; + +pub struct Sh1106Component + 'static> { + i2c_device: &'static capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, I>, + use_charge_pump: bool, +} + +impl + 'static> Sh1106Component { + pub fn new( + i2c_device: &'static capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, I>, + use_charge_pump: bool, + ) -> Sh1106Component { + Sh1106Component { + i2c_device, + use_charge_pump, + } + } +} + +impl + 'static> Component for Sh1106Component { + type StaticInput = ( + &'static mut MaybeUninit<[u8; capsules_extra::sh1106::BUFFER_SIZE]>, + &'static mut MaybeUninit< + capsules_extra::sh1106::Sh1106< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, I>, + >, + >, + ); + type Output = &'static capsules_extra::sh1106::Sh1106< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, I>, + >; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let buffer = static_buffer + .0 + .write([0; capsules_extra::sh1106::BUFFER_SIZE]); + + let sh1106 = static_buffer.1.write(capsules_extra::sh1106::Sh1106::new( + self.i2c_device, + buffer, + self.use_charge_pump, + )); + self.i2c_device.set_client(sh1106); + + sh1106 + } +} diff --git a/boards/components/src/sha.rs b/boards/components/src/sha.rs index f15b26048a..9f74d0b3cc 100644 --- a/boards/components/src/sha.rs +++ b/boards/components/src/sha.rs @@ -1,112 +1,64 @@ -//! Components for collections of SHAa. +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Components for collections of SHA. //! //! Usage //! ----- //! ```rust -//! let sha_data_buffer = static_init!([u8; 64], [0; 64]); -//! let sha_dest_buffer = static_init!([u8; 32], [0; 32]); -//! -//! let mux_sha = components::sha::ShaMuxComponent::new(&earlgrey::sha::HMAC).finalize( -//! components::sha_mux_component_helper!(lowrisc::sha::Sha, [u8; 32]), -//! ); -//! //! let sha = components::sha::ShaComponent::new( //! board_kernel, -//! &mux_sha, -//! sha_data_buffer, -//! sha_dest_buffer, +//! chip.sha, //! ) -//! .finalize(components::sha_component_helper!( +//! .finalize(components::sha_component_static!( //! lowrisc::sha::Sha, //! 32, //! )); //! ``` -use capsules; -use capsules::sha::ShaDriver; -use capsules::virtual_sha::MuxSha; -use capsules::virtual_sha::VirtualMuxSha; +use capsules_extra::sha::ShaDriver; use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; use kernel::hil::digest; -use kernel::static_init_half; // Setup static space for the objects. #[macro_export] -macro_rules! sha_mux_component_helper { - ($A:ty, $L:expr $(,)?) => {{ - use capsules::virtual_sha::MuxSha; - use capsules::virtual_sha::VirtualMuxSha; - use core::mem::MaybeUninit; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF1 - };}; -} - -pub struct ShaMuxComponent, const L: usize> { - sha: &'static A, -} - -impl, const L: usize> ShaMuxComponent { - pub fn new(sha: &'static A) -> ShaMuxComponent { - ShaMuxComponent { sha } - } -} - -impl< - A: 'static + digest::Digest<'static, L> + digest::Sha256 + digest::Sha384 + digest::Sha512, - const L: usize, - > Component for ShaMuxComponent -{ - type StaticInput = &'static mut MaybeUninit>; - type Output = &'static MuxSha<'static, A, L>; +macro_rules! sha_component_static { + ($A:ty, $L:expr$(,)?) => {{ + let sha_driver = kernel::static_buf!( + capsules_extra::sha::ShaDriver< + 'static, + capsules_core::virtualizers::virtual_sha::VirtualMuxSha<'static, $A, $L>, + $L, + > + ); - unsafe fn finalize(self, s: Self::StaticInput) -> Self::Output { - let mux_sha = static_init_half!(s, MuxSha<'static, A, L>, MuxSha::new(self.sha)); + let data_buffer = kernel::static_buf!([u8; 64]); + let dest_buffer = kernel::static_buf!([u8; $L]); - mux_sha - } -} - -// Setup static space for the objects. -#[macro_export] -macro_rules! sha_component_helper { - ($A:ty, $L:expr$(,)?) => {{ - use capsules::sha::ShaDriver; - use capsules::virtual_sha::MuxSha; - use capsules::virtual_sha::VirtualMuxSha; - use core::mem::MaybeUninit; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF2: MaybeUninit, $L>> = - MaybeUninit::uninit(); - (&mut BUF1, &mut BUF2) + (sha_driver, data_buffer, dest_buffer) };}; } pub struct ShaComponent, const L: usize> { board_kernel: &'static kernel::Kernel, driver_num: usize, - mux_sha: &'static MuxSha<'static, A, L>, - data_buffer: &'static mut [u8], - dest_buffer: &'static mut [u8; L], + sha: &'static A, } impl, const L: usize> ShaComponent { pub fn new( board_kernel: &'static kernel::Kernel, driver_num: usize, - mux_sha: &'static MuxSha<'static, A, L>, - data_buffer: &'static mut [u8], - dest_buffer: &'static mut [u8; L], + sha: &'static A, ) -> ShaComponent { ShaComponent { board_kernel, driver_num, - mux_sha, - data_buffer, - dest_buffer, + sha, } } } @@ -121,32 +73,57 @@ impl< > Component for ShaComponent { type StaticInput = ( - &'static mut MaybeUninit>, - &'static mut MaybeUninit, L>>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; 64]>, + &'static mut MaybeUninit<[u8; L]>, ); - type Output = &'static ShaDriver<'static, VirtualMuxSha<'static, A, L>, L>; + type Output = &'static ShaDriver<'static, A, L>; - unsafe fn finalize(self, s: Self::StaticInput) -> Self::Output { + fn finalize(self, s: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let virtual_sha_user = static_init_half!( - s.0, - VirtualMuxSha<'static, A, L>, - VirtualMuxSha::new(self.mux_sha) - ); + let data_buffer = s.1.write([0; 64]); + let dest_buffer = s.2.write([0; L]); - let sha = static_init_half!( - s.1, - capsules::sha::ShaDriver<'static, VirtualMuxSha<'static, A, L>, L>, - capsules::sha::ShaDriver::new( - virtual_sha_user, - self.data_buffer, - self.dest_buffer, - self.board_kernel.create_grant(self.driver_num, &grant_cap), - ) - ); + let sha = s.0.write(capsules_extra::sha::ShaDriver::new( + self.sha, + data_buffer, + dest_buffer, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); + + self.sha.set_client(sha); sha } } + +#[macro_export] +macro_rules! sha_software_256_component_static { + ($(,)?) => {{ + kernel::static_buf!(capsules_extra::sha256::Sha256Software<'static>) + };}; +} + +pub struct ShaSoftware256Component {} + +impl ShaSoftware256Component { + pub fn new() -> ShaSoftware256Component { + ShaSoftware256Component {} + } +} + +impl Component for ShaSoftware256Component { + type StaticInput = &'static mut MaybeUninit>; + + type Output = &'static capsules_extra::sha256::Sha256Software<'static>; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let sha_256_sw = s.write(capsules_extra::sha256::Sha256Software::new()); + + kernel::deferred_call::DeferredCallClient::register(sha_256_sw); + + sha_256_sw + } +} diff --git a/boards/components/src/sht3x.rs b/boards/components/src/sht3x.rs index 8e0dbb4891..b21aa4d4a7 100644 --- a/boards/components/src/sht3x.rs +++ b/boards/components/src/sht3x.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for the SHT3x sensor. //! //! I2C Interface @@ -5,94 +9,91 @@ //! Usage //! ----- //! -//! With the default i2c address -//! ```rust -//! let sht3x = components::sht3x::SHT3xComponent::new(sensors_i2c_bus, mux_alarm).finalize( -//! components::sht3x_component_helper!(nrf52::rtc::Rtc<'static>), -//! ); -//! sht3x.reset(); -//! ``` -//! -//! With a specified i2c address //! ```rust -//! let sht3x = components::sht3x::SHT3xComponent::new(sensors_i2c_bus, mux_alarm).finalize( -//! components::sht3x_component_helper!(nrf52::rtc::Rtc<'static>, capsules::sht3x::BASE_ADDR), +//! let sht3x = components::sht3x::SHT3xComponent::new(sensors_i2c_bus, capsules_extra::sht3x::BASE_ADDR, mux_alarm).finalize( +//! components::sht3x_component_static!(nrf52::rtc::Rtc<'static>), //! ); //! sht3x.reset(); //! ``` -use capsules::sht3x::SHT3x; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; -use capsules::virtual_i2c::MuxI2C; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_extra::sht3x::SHT3x; use core::mem::MaybeUninit; use kernel::component::Component; +use kernel::hil::i2c; use kernel::hil::time::Alarm; -use kernel::static_init_half; - // Setup static space for the objects. #[macro_export] -macro_rules! sht3x_component_helper { - ($A:ty) => {{ - use capsules::sht3x; - $crate::sht3x_component_helper!($A, sht3x::BASE_ADDR) - }}; - - // used for specifically stating the i2c address - // as some boards (like nrf52) require a shift - ($A:ty, $address: expr) => {{ - use capsules::sht3x::SHT3x; - use capsules::virtual_i2c::I2CDevice; - use core::mem::MaybeUninit; - - static mut BUFFER: [u8; 6] = [0; 6]; +macro_rules! sht3x_component_static { + ($A:ty, $I:ty $(,)?) => {{ + let buffer = kernel::static_buf!([u8; 6]); + let i2c_device = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>); + let sht3x_alarm = kernel::static_buf!( + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A> + ); + let sht3x = kernel::static_buf!( + capsules_extra::sht3x::SHT3x< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A>, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>, + > + ); - static mut sht3x: MaybeUninit>> = - MaybeUninit::uninit(); - static mut sht3x_alarm: MaybeUninit> = MaybeUninit::uninit(); - (&mut sht3x_alarm, &mut BUFFER, &mut sht3x, $address) - }}; + (sht3x_alarm, i2c_device, sht3x, buffer) + };}; } -pub struct SHT3xComponent> { - i2c_mux: &'static MuxI2C<'static>, +pub type SHT3xComponentType = capsules_extra::sht3x::SHT3x<'static, A, I>; + +pub struct SHT3xComponent, I: 'static + i2c::I2CMaster<'static>> { + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, alarm_mux: &'static MuxAlarm<'static, A>, } -impl> SHT3xComponent { +impl, I: 'static + i2c::I2CMaster<'static>> SHT3xComponent { pub fn new( - i2c_mux: &'static MuxI2C<'static>, + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, alarm_mux: &'static MuxAlarm<'static, A>, - ) -> SHT3xComponent { - SHT3xComponent { i2c_mux, alarm_mux } + ) -> SHT3xComponent { + SHT3xComponent { + i2c_mux, + i2c_address, + alarm_mux, + } } } -impl> Component for SHT3xComponent { +impl, I: 'static + i2c::I2CMaster<'static>> Component + for SHT3xComponent +{ type StaticInput = ( &'static mut MaybeUninit>, - &'static mut [u8], - &'static mut MaybeUninit>>, - u8, + &'static mut MaybeUninit>, + &'static mut MaybeUninit< + SHT3x<'static, VirtualMuxAlarm<'static, A>, I2CDevice<'static, I>>, + >, + &'static mut MaybeUninit<[u8; 6]>, ); - type Output = &'static SHT3x<'static, VirtualMuxAlarm<'static, A>>; + type Output = &'static SHT3x<'static, VirtualMuxAlarm<'static, A>, I2CDevice<'static, I>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let sht3x_i2c = crate::i2c::I2CComponent::new(self.i2c_mux, static_buffer.3) - .finalize(crate::i2c_component_helper!()); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let sht3x_i2c = static_buffer + .1 + .write(I2CDevice::new(self.i2c_mux, self.i2c_address)); - let sht3x_alarm = static_init_half!( - static_buffer.0, - VirtualMuxAlarm<'static, A>, - VirtualMuxAlarm::new(self.alarm_mux) - ); + let buffer = static_buffer.3.write([0; 6]); + + let sht3x_alarm = static_buffer.0.write(VirtualMuxAlarm::new(self.alarm_mux)); sht3x_alarm.setup(); - let sht3x = static_init_half!( - static_buffer.2, - SHT3x<'static, VirtualMuxAlarm<'static, A>>, - SHT3x::new(sht3x_i2c, static_buffer.1, sht3x_alarm) - ); + let sht3x = static_buffer + .2 + .write(SHT3x::new(sht3x_i2c, buffer, sht3x_alarm)); sht3x_i2c.set_client(sht3x); sht3x_alarm.set_alarm_client(sht3x); diff --git a/boards/components/src/sht4x.rs b/boards/components/src/sht4x.rs new file mode 100644 index 0000000000..939577d3d6 --- /dev/null +++ b/boards/components/src/sht4x.rs @@ -0,0 +1,102 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Component for the SHT4x sensor. +//! +//! I2C Interface +//! +//! Usage +//! ----- +//! +//! ```rust +//! let sht4x = components::sht4x::SHT4xComponent::new(sensors_i2c_bus, capsules_extra::sht4x::BASE_ADDR, mux_alarm).finalize( +//! components::sht4x_component_static!(nrf52::rtc::Rtc<'static>), +//! ); +//! sht4x.reset(); +//! ``` + +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_extra::sht4x::SHT4x; +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::hil::i2c; +use kernel::hil::time::Alarm; + +// Setup static space for the objects. +#[macro_export] +macro_rules! sht4x_component_static { + ($A:ty, $I:ty $(,)?) => {{ + let buffer = kernel::static_buf!([u8; 6]); + let i2c_device = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>); + let sht4x_alarm = kernel::static_buf!( + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A> + ); + let sht4x = kernel::static_buf!( + capsules_extra::sht4x::SHT4x< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A>, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>, + > + ); + + (sht4x_alarm, i2c_device, sht4x, buffer) + };}; +} + +pub type SHT4xComponentType = capsules_extra::sht4x::SHT4x<'static, A, I>; + +pub struct SHT4xComponent, I: 'static + i2c::I2CMaster<'static>> { + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, + alarm_mux: &'static MuxAlarm<'static, A>, +} + +impl, I: 'static + i2c::I2CMaster<'static>> SHT4xComponent { + pub fn new( + i2c_mux: &'static MuxI2C<'static, I>, + i2c_address: u8, + alarm_mux: &'static MuxAlarm<'static, A>, + ) -> SHT4xComponent { + SHT4xComponent { + i2c_mux, + i2c_address, + alarm_mux, + } + } +} + +impl, I: 'static + i2c::I2CMaster<'static>> Component + for SHT4xComponent +{ + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit< + SHT4x<'static, VirtualMuxAlarm<'static, A>, I2CDevice<'static, I>>, + >, + &'static mut MaybeUninit<[u8; 6]>, + ); + type Output = &'static SHT4x<'static, VirtualMuxAlarm<'static, A>, I2CDevice<'static, I>>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let sht4x_i2c = static_buffer + .1 + .write(I2CDevice::new(self.i2c_mux, self.i2c_address)); + + let buffer = static_buffer.3.write([0; 6]); + + let sht4x_alarm = static_buffer.0.write(VirtualMuxAlarm::new(self.alarm_mux)); + sht4x_alarm.setup(); + + let sht4x = static_buffer + .2 + .write(SHT4x::new(sht4x_i2c, buffer, sht4x_alarm)); + sht4x_i2c.set_client(sht4x); + sht4x_alarm.set_alarm_client(sht4x); + + sht4x + } +} diff --git a/boards/components/src/si7021.rs b/boards/components/src/si7021.rs index 9c73ca305b..0703114f8c 100644 --- a/boards/components/src/si7021.rs +++ b/boards/components/src/si7021.rs @@ -1,16 +1,17 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for the SI7021 Temperature/Humidity Sensor. //! -//! This provides two Components, SI7021Component, which provides -//! access to the SI7021 over I2C, and HumidityComponent, -//! which provides a humidity system call driver. SI7021Component is -//! a parameter to HumidityComponent. +//! This provides the SI7021Component which provides access to the SI7021 over +//! I2C. //! //! Usage //! ----- //! ```rust //! let si7021 = SI7021Component::new(mux_i2c, mux_alarm, 0x40).finalize( -//! components::si7021_component_helper!(sam4l::ast::Ast)); -//! let humidity = HumidityComponent::new(board_kernel, si7021).finalize(()); +//! components::si7021_component_static!(sam4l::ast::Ast)); //! ``` // Author: Philip Levis @@ -18,116 +19,89 @@ use core::mem::MaybeUninit; -use capsules::humidity::HumiditySensor; -use capsules::si7021::SI7021; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; -use capsules::virtual_i2c::{I2CDevice, MuxI2C}; -use kernel::capabilities; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_i2c::{I2CDevice, MuxI2C}; +use capsules_extra::si7021::SI7021; use kernel::component::Component; -use kernel::create_capability; -use kernel::hil; +use kernel::hil::i2c; use kernel::hil::time::{self, Alarm}; -use kernel::{static_init, static_init_half}; // Setup static space for the objects. #[macro_export] -macro_rules! si7021_component_helper { - ($A:ty $(,)?) => {{ - use capsules::si7021::SI7021; - use core::mem::MaybeUninit; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF2: MaybeUninit>> = - MaybeUninit::uninit(); - (&mut BUF1, &mut BUF2) +macro_rules! si7021_component_static { + ($A:ty, $I:ty $(,)? ) => {{ + let alarm = kernel::static_buf!( + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A> + ); + let i2c_device = + kernel::static_buf!(capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>); + let si7021 = kernel::static_buf!( + capsules_extra::si7021::SI7021< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A>, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>, + > + ); + let buffer = kernel::static_buf!([u8; 14]); + + (alarm, i2c_device, si7021, buffer) };}; } -pub struct SI7021Component> { - i2c_mux: &'static MuxI2C<'static>, +pub type SI7021ComponentType = capsules_extra::si7021::SI7021<'static, A, I>; + +pub struct SI7021Component, I: 'static + i2c::I2CMaster<'static>> +{ + i2c_mux: &'static MuxI2C<'static, I>, alarm_mux: &'static MuxAlarm<'static, A>, i2c_address: u8, } -impl> SI7021Component { +impl, I: 'static + i2c::I2CMaster<'static>> + SI7021Component +{ pub fn new( - i2c: &'static MuxI2C<'static>, + i2c: &'static MuxI2C<'static, I>, alarm: &'static MuxAlarm<'static, A>, i2c_address: u8, ) -> Self { SI7021Component { i2c_mux: i2c, alarm_mux: alarm, - i2c_address: i2c_address, + i2c_address, } } } -static mut I2C_BUF: [u8; 14] = [0; 14]; - -impl> Component for SI7021Component { +impl, I: 'static + i2c::I2CMaster<'static>> Component + for SI7021Component +{ type StaticInput = ( &'static mut MaybeUninit>, - &'static mut MaybeUninit>>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit< + SI7021<'static, VirtualMuxAlarm<'static, A>, I2CDevice<'static, I>>, + >, + &'static mut MaybeUninit<[u8; 14]>, ); - type Output = &'static SI7021<'static, VirtualMuxAlarm<'static, A>>; + type Output = &'static SI7021<'static, VirtualMuxAlarm<'static, A>, I2CDevice<'static, I>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let si7021_i2c = static_init!(I2CDevice, I2CDevice::new(self.i2c_mux, self.i2c_address)); - let si7021_alarm = static_init_half!( - static_buffer.0, - VirtualMuxAlarm<'static, A>, - VirtualMuxAlarm::new(self.alarm_mux) - ); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let si7021_i2c = static_buffer + .1 + .write(I2CDevice::new(self.i2c_mux, self.i2c_address)); + + let si7021_alarm = static_buffer.0.write(VirtualMuxAlarm::new(self.alarm_mux)); si7021_alarm.setup(); - let si7021 = static_init_half!( - static_buffer.1, - SI7021<'static, VirtualMuxAlarm<'static, A>>, - SI7021::new(si7021_i2c, si7021_alarm, &mut I2C_BUF) - ); + let buffer = static_buffer.3.write([0; 14]); + + let si7021 = static_buffer + .2 + .write(SI7021::new(si7021_i2c, si7021_alarm, buffer)); si7021_i2c.set_client(si7021); si7021_alarm.set_alarm_client(si7021); si7021 } } - -pub struct HumidityComponent> { - board_kernel: &'static kernel::Kernel, - driver_num: usize, - si7021: &'static SI7021<'static, VirtualMuxAlarm<'static, A>>, -} - -impl> HumidityComponent { - pub fn new( - board_kernel: &'static kernel::Kernel, - driver_num: usize, - si: &'static SI7021<'static, VirtualMuxAlarm<'static, A>>, - ) -> HumidityComponent { - HumidityComponent { - board_kernel, - driver_num, - si7021: si, - } - } -} - -impl> Component for HumidityComponent { - type StaticInput = (); - type Output = &'static HumiditySensor<'static>; - - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { - let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - - let hum = static_init!( - HumiditySensor<'static>, - HumiditySensor::new( - self.si7021, - self.board_kernel.create_grant(self.driver_num, &grant_cap) - ) - ); - - hil::sensors::HumidityDriver::set_client(self.si7021, hum); - hum - } -} diff --git a/boards/components/src/siphash.rs b/boards/components/src/siphash.rs new file mode 100644 index 0000000000..b0b60c2f41 --- /dev/null +++ b/boards/components/src/siphash.rs @@ -0,0 +1,38 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Components for SipHash hasher. + +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::deferred_call::DeferredCallClient; + +// Setup static space for the objects. +#[macro_export] +macro_rules! siphasher24_component_static { + ($(,)?) => {{ + kernel::static_buf!(capsules_extra::sip_hash::SipHasher24) + };}; +} + +pub type Siphasher24ComponentType = capsules_extra::sip_hash::SipHasher24<'static>; + +pub struct Siphasher24Component {} + +impl Siphasher24Component { + pub fn new() -> Siphasher24Component { + Siphasher24Component {} + } +} + +impl Component for Siphasher24Component { + type StaticInput = &'static mut MaybeUninit>; + type Output = &'static capsules_extra::sip_hash::SipHasher24<'static>; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let sip_hash = s.write(capsules_extra::sip_hash::SipHasher24::new()); + sip_hash.register(); + sip_hash + } +} diff --git a/boards/components/src/sound_pressure.rs b/boards/components/src/sound_pressure.rs index e7abe37356..252fa18af7 100644 --- a/boards/components/src/sound_pressure.rs +++ b/boards/components/src/sound_pressure.rs @@ -1,17 +1,29 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for any Sound Pressure sensor. //! //! Usage //! ----- //! ```rust -//! let sound_pressure = SoundPressureComponent::new(board_kernel, adc_microphone).finalize(()); +//! let sound_pressure = SoundPressureComponent::new(board_kernel, adc_microphone) +//! .finalize(sound_pressure_component_static!()); //! ``` -use capsules::sound_pressure::SoundPressureSensor; +use capsules_extra::sound_pressure::SoundPressureSensor; +use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; use kernel::hil; -use kernel::static_init; + +#[macro_export] +macro_rules! sound_pressure_component_static { + () => {{ + kernel::static_buf!(capsules_extra::sound_pressure::SoundPressureSensor<'static>) + };}; +} pub struct SoundPressureComponent> { board_kernel: &'static kernel::Kernel, @@ -34,19 +46,16 @@ impl> SoundPressureComponent> Component for SoundPressureComponent { - type StaticInput = (); + type StaticInput = &'static mut MaybeUninit>; type Output = &'static SoundPressureSensor<'static>; - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { + fn finalize(self, s: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let sound_pressure = static_init!( - capsules::sound_pressure::SoundPressureSensor<'static>, - capsules::sound_pressure::SoundPressureSensor::new( - self.sound_sensor, - self.board_kernel.create_grant(self.driver_num, &grant_cap) - ) - ); + let sound_pressure = s.write(SoundPressureSensor::new( + self.sound_sensor, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); hil::sensors::SoundPressure::set_client(self.sound_sensor, sound_pressure); sound_pressure diff --git a/boards/components/src/spi.rs b/boards/components/src/spi.rs index 9ea8ae91b3..b824c05454 100644 --- a/boards/components/src/spi.rs +++ b/boards/components/src/spi.rs @@ -1,13 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for SPI. //! -//! This provides three components. +//! This provides four components. //! //! 1. `SpiMuxComponent` provides a virtualization layer for a SPI controller. -//! //! 2. `SpiSyscallComponent` provides a controller system call interface to SPI. -//! //! 3. `SpiPSyscallComponent` provides a peripheral system call interface to SPI. -//! //! 4. `SpiComponent` provides a virtualized client to the SPI bus. //! //! `SpiSyscallComponent` is used for processes, while `SpiComponent` is used @@ -17,11 +18,11 @@ //! ----- //! ```rust //! let mux_spi = components::spi::SpiMuxComponent::new(&sam4l::spi::SPI).finalize( -//! components::spi_mux_component_helper!(sam4l::spi::SpiHw)); +//! components::spi_mux_component_static!(sam4l::spi::SpiHw)); //! let spi_syscalls = SpiSyscallComponent::new(mux_spi, 3).finalize( -//! components::spi_syscalls_component_helper!(sam4l::spi::SpiHw)); +//! components::spi_syscalls_component_static!(sam4l::spi::SpiHw)); //! let rf233_spi = SpiComponent::new(mux_spi, 3).finalize( -//! components::spi_component_helper!(sam4l::spi::SpiHw)); +//! components::spi_component_static!(sam4l::spi::SpiHw)); //! ``` // Author: Philip Levis @@ -29,120 +30,119 @@ use core::mem::MaybeUninit; -use capsules::spi_controller::{Spi, DEFAULT_READ_BUF_LENGTH, DEFAULT_WRITE_BUF_LENGTH}; -use capsules::spi_peripheral::SpiPeripheral; -use capsules::virtual_spi; -use capsules::virtual_spi::{MuxSpiMaster, VirtualSpiMasterDevice}; +use capsules_core::spi_controller::{Spi, DEFAULT_READ_BUF_LENGTH, DEFAULT_WRITE_BUF_LENGTH}; +use capsules_core::spi_peripheral::SpiPeripheral; +use capsules_core::virtualizers::virtual_spi; +use capsules_core::virtualizers::virtual_spi::{MuxSpiMaster, VirtualSpiMasterDevice}; use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::DynamicDeferredCall; +use kernel::create_capability; use kernel::hil::spi; use kernel::hil::spi::{SpiMasterDevice, SpiSlaveDevice}; -use kernel::{create_capability, static_init, static_init_half}; // Setup static space for the objects. #[macro_export] -macro_rules! spi_mux_component_helper { +macro_rules! spi_mux_component_static { ($S:ty $(,)?) => {{ - use capsules::virtual_spi::MuxSpiMaster; - use core::mem::MaybeUninit; - static mut BUF: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF + kernel::static_buf!(capsules_core::virtualizers::virtual_spi::MuxSpiMaster<'static, $S>) };}; } #[macro_export] -macro_rules! spi_syscall_component_helper { +macro_rules! spi_syscall_component_static { ($S:ty $(,)?) => {{ - use capsules::spi_controller::Spi; - use capsules::virtual_spi::VirtualSpiMasterDevice; - use core::mem::MaybeUninit; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF2: MaybeUninit>> = - MaybeUninit::uninit(); - (&mut BUF1, &mut BUF2) + let virtual_spi = kernel::static_buf!( + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice<'static, $S> + ); + let spi = kernel::static_buf!( + capsules_core::spi_controller::Spi< + 'static, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice<'static, $S>, + > + ); + + let spi_read_buf = + kernel::static_buf!([u8; capsules_core::spi_controller::DEFAULT_READ_BUF_LENGTH]); + let spi_write_buf = + kernel::static_buf!([u8; capsules_core::spi_controller::DEFAULT_WRITE_BUF_LENGTH]); + + (virtual_spi, spi, spi_read_buf, spi_write_buf) };}; } #[macro_export] -macro_rules! spi_syscallp_component_helper { +macro_rules! spi_syscallp_component_static { ($S:ty $(,)?) => {{ - use capsules::spi_peripheral::SpiPeripheral; - use capsules::virtual_spi::SpiSlaveDevice; - use core::mem::MaybeUninit; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF2: MaybeUninit>> = - MaybeUninit::uninit(); - (&mut BUF1, &mut BUF2) + let spi_slave = kernel::static_buf!( + capsules_core::virtualizers::virtual_spi::SpiSlaveDevice<'static, $S> + ); + let spi_peripheral = kernel::static_buf!( + capsules_core::spi_peripheral::SpiPeripheral< + 'static, + capsules_core::virtualizers::virtual_spi::SpiSlaveDevice<'static, $S>, + > + ); + + let spi_read_buf = + kernel::static_buf!([u8; capsules_core::spi_controller::DEFAULT_READ_BUF_LENGTH]); + let spi_write_buf = + kernel::static_buf!([u8; capsules_core::spi_controller::DEFAULT_WRITE_BUF_LENGTH]); + + (spi_slave, spi_peripheral, spi_read_buf, spi_write_buf) };}; } #[macro_export] -macro_rules! spi_component_helper { +macro_rules! spi_component_static { ($S:ty $(,)?) => {{ - use capsules::virtual_spi::VirtualSpiMasterDevice; - use core::mem::MaybeUninit; - static mut BUF: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF + kernel::static_buf!( + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice<'static, $S> + ) };}; } #[macro_export] -macro_rules! spi_peripheral_component_helper { +macro_rules! spi_peripheral_component_static { ($S:ty $(,)?) => {{ - use capsules::spi_peripheral::SpiPeripheral; - use core::mem::MaybeUninit; - static mut BUF: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF + kernel::static_buf!(capsules_core::spi_peripheral::SpiPeripheral<'static, $S>) };}; } -pub struct SpiMuxComponent { +pub struct SpiMuxComponent> { spi: &'static S, - deferred_caller: &'static DynamicDeferredCall, } -pub struct SpiSyscallComponent { +pub struct SpiSyscallComponent> { board_kernel: &'static kernel::Kernel, spi_mux: &'static MuxSpiMaster<'static, S>, chip_select: S::ChipSelect, driver_num: usize, } -pub struct SpiSyscallPComponent { +pub struct SpiSyscallPComponent> { board_kernel: &'static kernel::Kernel, spi_slave: &'static S, driver_num: usize, } -pub struct SpiComponent { +pub struct SpiComponent> { spi_mux: &'static MuxSpiMaster<'static, S>, chip_select: S::ChipSelect, } -impl SpiMuxComponent { - pub fn new(spi: &'static S, deferred_caller: &'static DynamicDeferredCall) -> Self { - SpiMuxComponent { - spi: spi, - deferred_caller: deferred_caller, - } +impl> SpiMuxComponent { + pub fn new(spi: &'static S) -> Self { + Self { spi } } } -impl Component for SpiMuxComponent { +impl> Component for SpiMuxComponent { type StaticInput = &'static mut MaybeUninit>; type Output = &'static MuxSpiMaster<'static, S>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let mux_spi = static_init_half!( - static_buffer, - MuxSpiMaster<'static, S>, - MuxSpiMaster::new(self.spi, self.deferred_caller) - ); - - mux_spi.initialize_callback_handle( - self.deferred_caller.register(mux_spi).unwrap(), // Unwrap fail = no deferred call slot available for SPI mux - ); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let mux_spi = static_buffer.write(MuxSpiMaster::new(self.spi)); + kernel::deferred_call::DeferredCallClient::register(mux_spi); self.spi.set_client(mux_spi); @@ -154,7 +154,7 @@ impl Component for SpiMuxComponent { } } -impl SpiSyscallComponent { +impl> SpiSyscallComponent { pub fn new( board_kernel: &'static kernel::Kernel, mux: &'static MuxSpiMaster<'static, S>, @@ -162,46 +162,37 @@ impl SpiSyscallComponent { driver_num: usize, ) -> Self { SpiSyscallComponent { - board_kernel: board_kernel, + board_kernel, spi_mux: mux, - chip_select: chip_select, + chip_select, driver_num, } } } -impl Component for SpiSyscallComponent { +impl> Component for SpiSyscallComponent { type StaticInput = ( &'static mut MaybeUninit>, &'static mut MaybeUninit>>, + &'static mut MaybeUninit<[u8; DEFAULT_READ_BUF_LENGTH]>, + &'static mut MaybeUninit<[u8; DEFAULT_WRITE_BUF_LENGTH]>, ); type Output = &'static Spi<'static, VirtualSpiMasterDevice<'static, S>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let syscall_spi_device = static_init_half!( - static_buffer.0, - VirtualSpiMasterDevice<'static, S>, - VirtualSpiMasterDevice::new(self.spi_mux, self.chip_select) - ); - - let spi_syscalls = static_init_half!( - static_buffer.1, - Spi<'static, VirtualSpiMasterDevice<'static, S>>, - Spi::new( - syscall_spi_device, - self.board_kernel.create_grant(self.driver_num, &grant_cap) - ) - ); + let syscall_spi_device = static_buffer + .0 + .write(VirtualSpiMasterDevice::new(self.spi_mux, self.chip_select)); - let spi_read_buf = - static_init!([u8; DEFAULT_READ_BUF_LENGTH], [0; DEFAULT_READ_BUF_LENGTH]); + let spi_syscalls = static_buffer.1.write(Spi::new( + syscall_spi_device, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); - let spi_write_buf = static_init!( - [u8; DEFAULT_WRITE_BUF_LENGTH], - [0; DEFAULT_WRITE_BUF_LENGTH] - ); + let spi_read_buf = static_buffer.2.write([0; DEFAULT_READ_BUF_LENGTH]); + let spi_write_buf = static_buffer.3.write([0; DEFAULT_WRITE_BUF_LENGTH]); spi_syscalls.config_buffers(spi_read_buf, spi_write_buf); syscall_spi_device.setup(); @@ -210,7 +201,7 @@ impl Component for SpiSyscallComponent { } } -impl SpiSyscallPComponent { +impl> SpiSyscallPComponent { pub fn new( board_kernel: &'static kernel::Kernel, slave: &'static S, @@ -224,38 +215,29 @@ impl SpiSyscallPComponent { } } -impl Component for SpiSyscallPComponent { +impl> Component for SpiSyscallPComponent { type StaticInput = ( &'static mut MaybeUninit>, &'static mut MaybeUninit>>, + &'static mut MaybeUninit<[u8; DEFAULT_READ_BUF_LENGTH]>, + &'static mut MaybeUninit<[u8; DEFAULT_WRITE_BUF_LENGTH]>, ); type Output = &'static SpiPeripheral<'static, virtual_spi::SpiSlaveDevice<'static, S>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let syscallp_spi_device = static_init_half!( - static_buffer.0, - virtual_spi::SpiSlaveDevice<'static, S>, - virtual_spi::SpiSlaveDevice::new(self.spi_slave) - ); + let syscallp_spi_device = static_buffer + .0 + .write(virtual_spi::SpiSlaveDevice::new(self.spi_slave)); - let spi_syscallsp = static_init_half!( - static_buffer.1, - SpiPeripheral<'static, virtual_spi::SpiSlaveDevice<'static, S>>, - SpiPeripheral::new( - syscallp_spi_device, - self.board_kernel.create_grant(self.driver_num, &grant_cap) - ) - ); + let spi_syscallsp = static_buffer.1.write(SpiPeripheral::new( + syscallp_spi_device, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); - let spi_read_buf = - static_init!([u8; DEFAULT_READ_BUF_LENGTH], [0; DEFAULT_READ_BUF_LENGTH]); - - let spi_write_buf = static_init!( - [u8; DEFAULT_WRITE_BUF_LENGTH], - [0; DEFAULT_WRITE_BUF_LENGTH] - ); + let spi_read_buf = static_buffer.2.write([0; DEFAULT_READ_BUF_LENGTH]); + let spi_write_buf = static_buffer.3.write([0; DEFAULT_WRITE_BUF_LENGTH]); spi_syscallsp.config_buffers(spi_read_buf, spi_write_buf); syscallp_spi_device.set_client(spi_syscallsp); @@ -264,37 +246,34 @@ impl Component for SpiSyscallPComponent { } } -impl SpiComponent { +impl> SpiComponent { pub fn new(mux: &'static MuxSpiMaster<'static, S>, chip_select: S::ChipSelect) -> Self { SpiComponent { spi_mux: mux, - chip_select: chip_select, + chip_select, } } } -impl Component for SpiComponent { +impl> Component for SpiComponent { type StaticInput = &'static mut MaybeUninit>; type Output = &'static VirtualSpiMasterDevice<'static, S>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let spi_device = static_init_half!( - static_buffer, - VirtualSpiMasterDevice<'static, S>, - VirtualSpiMasterDevice::new(self.spi_mux, self.chip_select) - ); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let spi_device = + static_buffer.write(VirtualSpiMasterDevice::new(self.spi_mux, self.chip_select)); spi_device.setup(); spi_device } } -pub struct SpiPeripheralComponent { +pub struct SpiPeripheralComponent> { board_kernel: &'static kernel::Kernel, device: &'static S, driver_num: usize, } -impl SpiPeripheralComponent { +impl> SpiPeripheralComponent { pub fn new( board_kernel: &'static kernel::Kernel, device: &'static S, @@ -308,23 +287,19 @@ impl SpiPeripheralComponent { } } -impl Component +impl + kernel::hil::spi::SpiSlaveDevice<'static>> Component for SpiPeripheralComponent { type StaticInput = &'static mut MaybeUninit>; type Output = &'static SpiPeripheral<'static, S>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let spi_device = static_init_half!( - static_buffer, - SpiPeripheral<'static, S>, - SpiPeripheral::new( - self.device, - self.board_kernel.create_grant(self.driver_num, &grant_cap) - ) - ); + let spi_device = static_buffer.write(SpiPeripheral::new( + self.device, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); spi_device } diff --git a/boards/components/src/ssd1306.rs b/boards/components/src/ssd1306.rs new file mode 100644 index 0000000000..1669c385c0 --- /dev/null +++ b/boards/components/src/ssd1306.rs @@ -0,0 +1,89 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Components for the SSD1306 OLED screen. +//! +//! Usage +//! ----- +//! ```rust +//! +//! let ssd1306_i2c = components::i2c::I2CComponent::new(i2c_bus, 0x3c) +//! .finalize(components::i2c_component_static!(nrf52840::i2c::TWI)); +//! +//! let ssd1306 = components::ssd1306::Ssd1306Component::new(ssd1306_i2c, true) +//! .finalize(components::ssd1306_component_static!(nrf52840::i2c::TWI)); +//! ``` + +use core::mem::MaybeUninit; +use kernel::component::Component; +use kernel::hil; + +// Setup static space for the objects. +#[macro_export] +macro_rules! ssd1306_component_static { + ($I: ty $(,)?) => {{ + let buffer = kernel::static_buf!([u8; capsules_extra::ssd1306::BUFFER_SIZE]); + let ssd1306 = kernel::static_buf!( + capsules_extra::ssd1306::Ssd1306< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, $I>, + > + ); + + (buffer, ssd1306) + };}; +} + +pub type Ssd1306ComponentType = capsules_extra::ssd1306::Ssd1306< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, I>, +>; + +pub struct Ssd1306Component + 'static> { + i2c_device: &'static capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, I>, + use_charge_pump: bool, +} + +impl + 'static> Ssd1306Component { + pub fn new( + i2c_device: &'static capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, I>, + use_charge_pump: bool, + ) -> Ssd1306Component { + Ssd1306Component { + i2c_device, + use_charge_pump, + } + } +} + +impl + 'static> Component for Ssd1306Component { + type StaticInput = ( + &'static mut MaybeUninit<[u8; capsules_extra::ssd1306::BUFFER_SIZE]>, + &'static mut MaybeUninit< + capsules_extra::ssd1306::Ssd1306< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, I>, + >, + >, + ); + type Output = &'static capsules_extra::ssd1306::Ssd1306< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, I>, + >; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let buffer = static_buffer + .0 + .write([0; capsules_extra::ssd1306::BUFFER_SIZE]); + + let ssd1306 = static_buffer.1.write(capsules_extra::ssd1306::Ssd1306::new( + self.i2c_device, + buffer, + self.use_charge_pump, + )); + self.i2c_device.set_client(ssd1306); + + ssd1306 + } +} diff --git a/boards/components/src/st77xx.rs b/boards/components/src/st77xx.rs index c4aa04433e..b2d73b7bc6 100644 --- a/boards/components/src/st77xx.rs +++ b/boards/components/src/st77xx.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for the ST77XX screen. //! //! Usage @@ -5,7 +9,7 @@ //! ```rust //! //! let bus = components::bus::SpiMasterBusComponent::new().finalize( -//! components::spi_bus_component_helper!( +//! components::spi_bus_component_static!( //! // spi type //! nrf52840::spi::SPIM, //! // chip select @@ -15,65 +19,54 @@ //! ), //! ); //! -//! let tft = components::st77xx::ST77XXComponent::new(mux_alarm).finalize( -//! components::st77xx_component_helper!( -//! // screen -//! &capsules::st77xx::ST7735, +//! let tft = components::st77xx::ST77XXComponent::new(mux_alarm, +//! bus, +//! Some(&nrf52840::gpio::PORT[GPIO_D3]), +//! Some(&nrf52840::gpio::PORT[GPIO_D2]), +//! &capsules_extra::st77xx::ST7735).finalize( +//! components::st77xx_component_static!( //! // bus type -//! capsules::bus::SpiMasterBus< +//! capsules_extra::bus::SpiMasterBus< //! 'static, //! VirtualSpiMasterDevice<'static, nrf52840::spi::SPIM>, //! >, -//! // bus -//! &bus, //! // timer type //! nrf52840::rtc::Rtc, //! // pin type //! nrf52::gpio::GPIOPin<'static>, -//! // dc -//! Some(&nrf52840::gpio::PORT[GPIO_D3]), -//! // reset -//! Some(&nrf52840::gpio::PORT[GPIO_D2]) //! ), //! ); //! ``` -use capsules::bus; -use capsules::st77xx::{ST77XXScreen, ST77XX}; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; -use core::marker::PhantomData; + +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_extra::bus; +use capsules_extra::st77xx::{ST77XXScreen, ST77XX}; use core::mem::MaybeUninit; use kernel::component::Component; use kernel::hil::gpio; use kernel::hil::time::{self, Alarm}; -use kernel::static_init_half; // Setup static space for the objects. #[macro_export] -macro_rules! st77xx_component_helper { - ($screen:expr, $B: ty, $bus:expr, $A:ty, $P:ty, $dc:expr, $reset:expr $(,)?) => {{ - use capsules::bus::Bus; - use capsules::st77xx::{SendCommand, BUFFER_SIZE, SEQUENCE_BUFFER_SIZE, ST77XX}; - use capsules::virtual_alarm::VirtualMuxAlarm; - use capsules::virtual_spi::VirtualSpiMasterDevice; - use core::mem::MaybeUninit; - use kernel::hil::spi::{self, SpiMasterDevice}; - let st77xx_bus: &$B = $bus; - static mut BUFFER: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE]; - static mut SEQUENCE_BUFFER: [SendCommand; SEQUENCE_BUFFER_SIZE] = - [SendCommand::Nop; SEQUENCE_BUFFER_SIZE]; - static mut st77xx_alarm: MaybeUninit> = MaybeUninit::uninit(); - static mut st77xx: MaybeUninit, $B, $P>> = - MaybeUninit::uninit(); - ( - st77xx_bus, - &mut st77xx_alarm, - $dc, - $reset, - &mut st77xx, - $screen, - &mut BUFFER, - &mut SEQUENCE_BUFFER, - ) +macro_rules! st77xx_component_static { + ($B: ty, $A:ty, $P:ty $(,)?) => {{ + let buffer = kernel::static_buf!([u8; capsules_extra::st77xx::BUFFER_SIZE]); + let sequence_buffer = kernel::static_buf!( + [capsules_extra::st77xx::SendCommand; capsules_extra::st77xx::SEQUENCE_BUFFER_SIZE] + ); + let st77xx_alarm = kernel::static_buf!( + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A> + ); + let st77xx = kernel::static_buf!( + capsules_extra::st77xx::ST77XX< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A>, + $B, + $P, + > + ); + + (st77xx_alarm, st77xx, buffer, sequence_buffer) };}; } @@ -83,18 +76,28 @@ pub struct ST77XXComponent< P: 'static + gpio::Pin, > { alarm_mux: &'static MuxAlarm<'static, A>, - _bus: PhantomData, - _pin: PhantomData

, + bus: &'static B, + dc: Option<&'static P>, + reset: Option<&'static P>, + screen: &'static ST77XXScreen, } impl, B: 'static + bus::Bus<'static>, P: 'static + gpio::Pin> ST77XXComponent { - pub fn new(alarm_mux: &'static MuxAlarm<'static, A>) -> ST77XXComponent { + pub fn new( + alarm_mux: &'static MuxAlarm<'static, A>, + bus: &'static B, + dc: Option<&'static P>, + reset: Option<&'static P>, + screen: &'static ST77XXScreen, + ) -> ST77XXComponent { ST77XXComponent { - alarm_mux: alarm_mux, - _bus: PhantomData, - _pin: PhantomData, + alarm_mux, + bus, + dc, + reset, + screen, } } } @@ -103,39 +106,37 @@ impl, B: 'static + bus::Bus<'static>, P: 'stat Component for ST77XXComponent { type StaticInput = ( - &'static B, &'static mut MaybeUninit>, - Option<&'static P>, - Option<&'static P>, &'static mut MaybeUninit, B, P>>, - &'static ST77XXScreen, - &'static mut [u8], - &'static mut [capsules::st77xx::SendCommand], + &'static mut MaybeUninit<[u8; capsules_extra::st77xx::BUFFER_SIZE]>, + &'static mut MaybeUninit< + [capsules_extra::st77xx::SendCommand; capsules_extra::st77xx::SEQUENCE_BUFFER_SIZE], + >, ); type Output = &'static ST77XX<'static, VirtualMuxAlarm<'static, A>, B, P>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let st77xx_alarm = static_init_half!( - static_buffer.1, - VirtualMuxAlarm<'static, A>, - VirtualMuxAlarm::new(self.alarm_mux) - ); + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let st77xx_alarm = static_buffer.0.write(VirtualMuxAlarm::new(self.alarm_mux)); st77xx_alarm.setup(); - let st77xx = static_init_half!( - static_buffer.4, - ST77XX<'static, VirtualMuxAlarm<'static, A>, B, P>, - ST77XX::new( - static_buffer.0, - st77xx_alarm, - static_buffer.2, - static_buffer.3, - static_buffer.6, - static_buffer.7, - static_buffer.5 - ) + let buffer = static_buffer + .2 + .write([0; capsules_extra::st77xx::BUFFER_SIZE]); + let sequence_buffer = static_buffer.3.write( + [capsules_extra::st77xx::SendCommand::Nop; + capsules_extra::st77xx::SEQUENCE_BUFFER_SIZE], ); - static_buffer.0.set_client(st77xx); + + let st77xx = static_buffer.1.write(ST77XX::new( + self.bus, + st77xx_alarm, + self.dc, + self.reset, + buffer, + sequence_buffer, + self.screen, + )); + self.bus.set_client(st77xx); st77xx_alarm.set_alarm_client(st77xx); st77xx diff --git a/boards/components/src/temperature.rs b/boards/components/src/temperature.rs index a0c0de258c..6923bbe64b 100644 --- a/boards/components/src/temperature.rs +++ b/boards/components/src/temperature.rs @@ -1,17 +1,31 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for any Temperature sensor. //! //! Usage //! ----- //! ```rust -//! let temp = TemperatureComponent::new(board_kernel, nrf52::temperature::TEMP).finalize(()); +//! let temp = TemperatureComponent::new(board_kernel, nrf52::temperature::TEMP) +//! .finalize(components::temperature_component_static!()); //! ``` -use capsules::temperature::TemperatureSensor; +use capsules_extra::temperature::TemperatureSensor; +use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; use kernel::hil; -use kernel::static_init; + +#[macro_export] +macro_rules! temperature_component_static { + ($T:ty $(,)?) => {{ + kernel::static_buf!(capsules_extra::temperature::TemperatureSensor<'static, $T>) + };}; +} + +pub type TemperatureComponentType = capsules_extra::temperature::TemperatureSensor<'static, T>; pub struct TemperatureComponent> { board_kernel: &'static kernel::Kernel, @@ -34,19 +48,16 @@ impl> TemperatureComponent } impl> Component for TemperatureComponent { - type StaticInput = (); - type Output = &'static TemperatureSensor<'static>; + type StaticInput = &'static mut MaybeUninit>; + type Output = &'static TemperatureSensor<'static, T>; - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { + fn finalize(self, s: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let temp = static_init!( - TemperatureSensor<'static>, - TemperatureSensor::new( - self.temp_sensor, - self.board_kernel.create_grant(self.driver_num, &grant_cap) - ) - ); + let temp = s.write(TemperatureSensor::new( + self.temp_sensor, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); hil::sensors::TemperatureDriver::set_client(self.temp_sensor, temp); temp diff --git a/boards/components/src/temperature_rp2040.rs b/boards/components/src/temperature_rp2040.rs index 034d9826b1..a8136f1798 100644 --- a/boards/components/src/temperature_rp2040.rs +++ b/boards/components/src/temperature_rp2040.rs @@ -1,59 +1,73 @@ -use capsules::temperature_rp2040::TemperatureRp2040; -use capsules::virtual_adc::AdcDevice; -use core::marker::PhantomData; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Component for the RaspberryPI 2040 built-in temperature sensor. + +use capsules_core::virtualizers::virtual_adc::AdcDevice; +use capsules_extra::temperature_rp2040::TemperatureRp2040; use core::mem::MaybeUninit; use kernel::component::Component; use kernel::hil::adc; use kernel::hil::adc::AdcChannel; -use kernel::static_init_half; #[macro_export] -macro_rules! temperaturerp2040_adc_component_helper { - ($A:ty, $channel:expr, $adc_mux:expr $(,)?) => {{ - use capsules::temperature_rp2040::TemperatureRp2040; - use capsules::virtual_adc::AdcDevice; - use core::mem::MaybeUninit; - use kernel::hil::adc::Adc; - let mut temperature_adc: &'static capsules::virtual_adc::AdcDevice<'static, $A> = - components::adc::AdcComponent::new($adc_mux, $channel) - .finalize(components::adc_component_helper!($A)); - static mut temperature: MaybeUninit> = MaybeUninit::uninit(); - (&mut temperature_adc, &mut temperature) +macro_rules! temperature_rp2040_adc_component_static { + ($A:ty $(,)?) => {{ + let adc_device = components::adc_component_static!($A); + let temperature_rp2040 = kernel::static_buf!( + capsules_extra::temperature_rp2040::TemperatureRp2040< + 'static, + capsules_core::virtualizers::virtual_adc::AdcDevice<'static, $A>, + > + ); + + (adc_device, temperature_rp2040) };}; } -pub struct TemperatureRp2040Component { - _select: PhantomData, +pub type TemperatureRp2040ComponentType = + capsules_extra::temperature_rp2040::TemperatureRp2040<'static, A>; + +pub struct TemperatureRp2040Component> { + adc_mux: &'static capsules_core::virtualizers::virtual_adc::MuxAdc<'static, A>, + adc_channel: A::Channel, slope: f32, v_27: f32, } -impl TemperatureRp2040Component { - pub fn new(slope: f32, v_27: f32) -> TemperatureRp2040Component { +impl> TemperatureRp2040Component { + pub fn new( + adc_mux: &'static capsules_core::virtualizers::virtual_adc::MuxAdc<'static, A>, + adc_channel: A::Channel, + slope: f32, + v_27: f32, + ) -> TemperatureRp2040Component { TemperatureRp2040Component { - _select: PhantomData, - slope: slope, - v_27: v_27, + adc_mux, + adc_channel, + slope, + v_27, } } } -impl Component for TemperatureRp2040Component { +impl> Component for TemperatureRp2040Component { type StaticInput = ( - &'static AdcDevice<'static, A>, - &'static mut MaybeUninit>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit>>, ); - type Output = &'static TemperatureRp2040<'static>; + type Output = &'static TemperatureRp2040<'static, AdcDevice<'static, A>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let temperature_stm = static_init_half!( - static_buffer.1, - TemperatureRp2040<'static>, - TemperatureRp2040::new(static_buffer.0, self.slope, self.v_27) - ); + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let adc_device = + crate::adc::AdcComponent::new(self.adc_mux, self.adc_channel).finalize(s.0); + + let temperature_rp2040 = + s.1.write(TemperatureRp2040::new(adc_device, self.slope, self.v_27)); - static_buffer.0.set_client(temperature_stm); + adc_device.set_client(temperature_rp2040); - temperature_stm + temperature_rp2040 } } diff --git a/boards/components/src/temperature_stm.rs b/boards/components/src/temperature_stm.rs index cbc232e5a3..14bbe50ece 100644 --- a/boards/components/src/temperature_stm.rs +++ b/boards/components/src/temperature_stm.rs @@ -1,58 +1,72 @@ -use capsules::temperature_stm::TemperatureSTM; -use capsules::virtual_adc::AdcDevice; -use core::marker::PhantomData; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Component for the built-in STM temperature sensor. + +use capsules_core::virtualizers::virtual_adc::AdcDevice; +use capsules_extra::temperature_stm::TemperatureSTM; use core::mem::MaybeUninit; use kernel::component::Component; use kernel::hil::adc; use kernel::hil::adc::AdcChannel; -use kernel::static_init_half; #[macro_export] -macro_rules! temperaturestm_adc_component_helper { - ($A:ty, $channel:expr, $adc_mux:expr $(,)?) => {{ - use capsules::temperature_stm::TemperatureSTM; - use capsules::virtual_adc::AdcDevice; - use core::mem::MaybeUninit; - use kernel::hil::adc::Adc; - let mut temperature_stm_adc: &'static capsules::virtual_adc::AdcDevice<'static, $A> = - components::adc::AdcComponent::new($adc_mux, $channel) - .finalize(components::adc_component_helper!($A)); - static mut temperature_stm: MaybeUninit> = MaybeUninit::uninit(); - (&mut temperature_stm_adc, &mut temperature_stm) +macro_rules! temperature_stm_adc_component_static { + ($A:ty $(,)?) => {{ + let adc_device = components::adc_component_static!($A); + let temperature_stm = kernel::static_buf!( + capsules_extra::temperature_stm::TemperatureSTM< + 'static, + capsules_core::virtualizers::virtual_adc::AdcDevice<'static, $A>, + > + ); + + (adc_device, temperature_stm) };}; } -pub struct TemperatureSTMComponent { - _select: PhantomData, +pub type TemperatureSTMComponentType = + capsules_extra::temperature_stm::TemperatureSTM<'static, A>; + +pub struct TemperatureSTMComponent> { + adc_mux: &'static capsules_core::virtualizers::virtual_adc::MuxAdc<'static, A>, + adc_channel: A::Channel, slope: f32, v_25: f32, } -impl TemperatureSTMComponent { - pub fn new(slope: f32, v_25: f32) -> TemperatureSTMComponent { +impl> TemperatureSTMComponent { + pub fn new( + adc_mux: &'static capsules_core::virtualizers::virtual_adc::MuxAdc<'static, A>, + adc_channel: A::Channel, + slope: f32, + v_25: f32, + ) -> TemperatureSTMComponent { TemperatureSTMComponent { - _select: PhantomData, - slope: slope, - v_25: v_25, + adc_mux, + adc_channel, + slope, + v_25, } } } -impl Component for TemperatureSTMComponent { +impl> Component for TemperatureSTMComponent { type StaticInput = ( - &'static AdcDevice<'static, A>, - &'static mut MaybeUninit>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit>>, ); - type Output = &'static TemperatureSTM<'static>; + type Output = &'static TemperatureSTM<'static, AdcDevice<'static, A>>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let temperature_stm = static_init_half!( - static_buffer.1, - TemperatureSTM<'static>, - TemperatureSTM::new(static_buffer.0, self.slope, self.v_25) - ); + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let adc_device = + crate::adc::AdcComponent::new(self.adc_mux, self.adc_channel).finalize(s.0); + + let temperature_stm = + s.1.write(TemperatureSTM::new(adc_device, self.slope, self.v_25)); - static_buffer.0.set_client(temperature_stm); + adc_device.set_client(temperature_stm); temperature_stm } diff --git a/boards/components/src/test/mod.rs b/boards/components/src/test/mod.rs index 83ee39f00b..f6a012b011 100644 --- a/boards/components/src/test/mod.rs +++ b/boards/components/src/test/mod.rs @@ -1 +1,5 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + pub mod multi_alarm_test; diff --git a/boards/components/src/test/multi_alarm_test.rs b/boards/components/src/test/multi_alarm_test.rs index 861e719fcb..431ac80f59 100644 --- a/boards/components/src/test/multi_alarm_test.rs +++ b/boards/components/src/test/multi_alarm_test.rs @@ -1,31 +1,28 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::mem::MaybeUninit; -use capsules::test::random_alarm::TestRandomAlarm; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::test::random_alarm::TestRandomAlarm; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; use kernel::component::Component; use kernel::hil::time::{self, Alarm}; -use kernel::static_init_half; #[macro_export] macro_rules! multi_alarm_test_component_buf { ($A:ty $(,)?) => {{ - use capsules::test::random_alarm::TestRandomAlarm; - use capsules::virtual_alarm::VirtualMuxAlarm; - use core::mem::MaybeUninit; - static mut BUF00: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF01: MaybeUninit>> = - MaybeUninit::uninit(); - static mut BUF10: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF11: MaybeUninit>> = - MaybeUninit::uninit(); - static mut BUF20: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF21: MaybeUninit>> = - MaybeUninit::uninit(); - ( - (&mut BUF00, &mut BUF01), - (&mut BUF10, &mut BUF11), - (&mut BUF20, &mut BUF21), - ) + use capsules_core::test::random_alarm::TestRandomAlarm; + use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; + + let buf00 = kernel::static_buf!(VirtualMuxAlarm<'static, $A>); + let buf01 = kernel::static_buf!(TestRandomAlarm<'static, VirtualMuxAlarm<'static, $A>>); + let buf10 = kernel::static_buf!(VirtualMuxAlarm<'static, $A>); + let buf11 = kernel::static_buf!(TestRandomAlarm<'static, VirtualMuxAlarm<'static, $A>>); + let buf20 = kernel::static_buf!(VirtualMuxAlarm<'static, $A>); + let buf21 = kernel::static_buf!(TestRandomAlarm<'static, VirtualMuxAlarm<'static, $A>>); + + ((buf00, buf01)(buf10, buf11)(buf20, buf21)) };}; } @@ -56,49 +53,31 @@ impl> Component for MultiAlarmTestComponent ); type Output = MultiAlarmTestRunner; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let (buf0, buf1, buf2) = static_buffer; - let virtual_alarm0 = static_init_half!( - buf0.0, - VirtualMuxAlarm<'static, A>, - VirtualMuxAlarm::new(self.mux) - ); + let virtual_alarm0 = buf0.0.write(VirtualMuxAlarm::new(self.mux)); virtual_alarm0.setup(); - let test0 = static_init_half!( - buf0.1, - TestRandomAlarm<'static, VirtualMuxAlarm<'static, A>>, - TestRandomAlarm::new(virtual_alarm0, 19, 'A', true) - ); + let test0 = buf0 + .1 + .write(TestRandomAlarm::new(virtual_alarm0, 19, 'A', true)); virtual_alarm0.set_alarm_client(test0); - let virtual_alarm1 = static_init_half!( - buf1.0, - VirtualMuxAlarm<'static, A>, - VirtualMuxAlarm::new(self.mux) - ); + let virtual_alarm1 = buf1.0.write(VirtualMuxAlarm::new(self.mux)); virtual_alarm1.setup(); - let test1 = static_init_half!( - buf1.1, - TestRandomAlarm<'static, VirtualMuxAlarm<'static, A>>, - TestRandomAlarm::new(virtual_alarm1, 37, 'B', true) - ); + let test1 = buf1 + .1 + .write(TestRandomAlarm::new(virtual_alarm1, 37, 'B', true)); virtual_alarm1.set_alarm_client(test1); - let virtual_alarm2 = static_init_half!( - buf2.0, - VirtualMuxAlarm<'static, A>, - VirtualMuxAlarm::new(self.mux) - ); + let virtual_alarm2 = buf2.0.write(VirtualMuxAlarm::new(self.mux)); virtual_alarm2.setup(); - let test2 = static_init_half!( - buf2.1, - TestRandomAlarm<'static, VirtualMuxAlarm<'static, A>>, - TestRandomAlarm::new(virtual_alarm2, 89, 'C', true) - ); + let test2 = buf2 + .1 + .write(TestRandomAlarm::new(virtual_alarm2, 89, 'C', true)); virtual_alarm2.set_alarm_client(test2); MultiAlarmTestRunner { diff --git a/boards/components/src/text_screen.rs b/boards/components/src/text_screen.rs index 701caa9297..616d70ba87 100644 --- a/boards/components/src/text_screen.rs +++ b/boards/components/src/text_screen.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for the Text Screen. //! //! Buffer Size @@ -15,58 +19,63 @@ //! ```rust //! let text_screen = //! components::text_screen::TextScreenComponent::new(board_kernel, tft) -//! .finalize(components::screen_buffer_size!(40960)); +//! .finalize(components::text_screen_component_static!(40960)); //! ``` //! + +use capsules_extra::text_screen::TextScreen; +use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; -use kernel::static_init; #[macro_export] -macro_rules! text_screen_buffer_size { - ($s:literal) => {{ - static mut BUFFER: [u8; $s] = [0; $s]; - (&mut BUFFER) - }}; +macro_rules! text_screen_component_static { + ($s:literal $(,)?) => {{ + let buffer = kernel::static_buf!([u8; $s]); + let screen = kernel::static_buf!(capsules_extra::text_screen::TextScreen); + + (buffer, screen) + };}; } -pub struct TextScreenComponent { +pub struct TextScreenComponent { board_kernel: &'static kernel::Kernel, driver_num: usize, text_screen: &'static dyn kernel::hil::text_screen::TextScreen<'static>, } -impl TextScreenComponent { +impl TextScreenComponent { pub fn new( board_kernel: &'static kernel::Kernel, driver_num: usize, text_screen: &'static dyn kernel::hil::text_screen::TextScreen<'static>, - ) -> TextScreenComponent { + ) -> TextScreenComponent { TextScreenComponent { - board_kernel: board_kernel, - driver_num: driver_num, - text_screen: text_screen, + board_kernel, + driver_num, + text_screen, } } } -impl Component for TextScreenComponent { - type StaticInput = &'static mut [u8]; - type Output = &'static capsules::text_screen::TextScreen<'static>; +impl Component for TextScreenComponent { + type StaticInput = ( + &'static mut MaybeUninit<[u8; SCREEN_BUF_LEN]>, + &'static mut MaybeUninit>, + ); + type Output = &'static TextScreen<'static>; - unsafe fn finalize(self, static_input: Self::StaticInput) -> Self::Output { + fn finalize(self, static_input: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); let grant_text_screen = self.board_kernel.create_grant(self.driver_num, &grant_cap); - let text_screen = static_init!( - capsules::text_screen::TextScreen, - capsules::text_screen::TextScreen::new( - self.text_screen, - static_input, - grant_text_screen - ) - ); + let buffer = static_input.0.write([0; SCREEN_BUF_LEN]); + + let text_screen = + static_input + .1 + .write(TextScreen::new(self.text_screen, buffer, grant_text_screen)); kernel::hil::text_screen::TextScreen::set_client(self.text_screen, Some(text_screen)); diff --git a/boards/components/src/thread_network.rs b/boards/components/src/thread_network.rs new file mode 100644 index 0000000000..cabb59b075 --- /dev/null +++ b/boards/components/src/thread_network.rs @@ -0,0 +1,275 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Component to initialize the Thread Network. +//! +//! This provides one Component, ThreadNetworkComponent. This component initializes +//! a Thread Network controller for maintaining and managing a Thread network. +//! +//! Usage +//! ----- +//! ```rust +//! let thread_driver = components::thread_network::ThreadNetworkComponent::new( +//! board_kernel, +//! capsules_extra::net::thread::driver::DRIVER_NUM, +//! udp_send_mux, +//! udp_recv_mux, +//! udp_port_table, +//! aes_mux, +//! device_id, +//! mux_alarm, +//! ) +//! .finalize(components::thread_network_component_static!( +//! nrf52840::rtc::Rtc, +//! nrf52840::aes::AesECB<'static> +//! )); +//! ``` + +use capsules_core::virtualizers::virtual_aes_ccm::MuxAES128CCM; +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; +use capsules_extra::net::ipv6::ipv6_send::IP6SendStruct; +use capsules_extra::net::network_capabilities::{ + AddrRange, NetworkCapability, PortRange, UdpVisibilityCapability, +}; +use kernel::hil::symmetric_encryption::{self, AES128Ctr, AES128, AES128CBC, AES128CCM, AES128ECB}; + +use capsules_core::virtualizers::virtual_alarm::MuxAlarm; +use capsules_extra::net::thread::thread_utils::THREAD_PORT_NUMBER; +use capsules_extra::net::udp::udp_port_table::UdpPortManager; +use capsules_extra::net::udp::udp_recv::MuxUdpReceiver; +use capsules_extra::net::udp::udp_recv::UDPReceiver; +use capsules_extra::net::udp::udp_send::{MuxUdpSender, UDPSendStruct, UDPSender}; +use core::mem::MaybeUninit; +use kernel::capabilities; +use kernel::capabilities::NetworkCapabilityCreationCapability; +use kernel::component::Component; +use kernel::create_capability; +use kernel::hil::radio; +use kernel::hil::time::Alarm; + +const MAX_PAYLOAD_LEN: usize = super::udp_mux::MAX_PAYLOAD_LEN; +pub const CRYPT_SIZE: usize = 3 * symmetric_encryption::AES128_BLOCK_SIZE + radio::MAX_BUF_SIZE; + +// Setup static space for the objects. +#[macro_export] +macro_rules! thread_network_component_static { + ($A:ty, $B:ty $(,)?) => {{ + use components::udp_mux::MAX_PAYLOAD_LEN; + + let udp_send = kernel::static_buf!( + capsules_extra::net::udp::udp_send::UDPSendStruct< + 'static, + capsules_extra::net::ipv6::ipv6_send::IP6SendStruct< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A>, + >, + > + ); + let udp_vis_cap = + kernel::static_buf!(capsules_extra::net::network_capabilities::UdpVisibilityCapability); + let net_cap = + kernel::static_buf!(capsules_extra::net::network_capabilities::NetworkCapability); + let thread_network_driver = kernel::static_buf!( + capsules_extra::net::thread::driver::ThreadNetworkDriver< + 'static, + VirtualMuxAlarm<'static, $A>, + > + ); + let send_buffer = kernel::static_buf!([u8; MAX_PAYLOAD_LEN]); + let recv_buffer = kernel::static_buf!([u8; MAX_PAYLOAD_LEN]); + let udp_recv = + kernel::static_buf!(capsules_extra::net::udp::udp_recv::UDPReceiver<'static>); + let crypt_buf = kernel::static_buf!([u8; components::ieee802154::CRYPT_SIZE]); + let crypt = kernel::static_buf!( + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, $B>, + ); + let alarm = kernel::static_buf!(VirtualMuxAlarm<'static, $A>); + + ( + udp_send, + udp_vis_cap, + net_cap, + thread_network_driver, + send_buffer, + recv_buffer, + udp_recv, + crypt_buf, + crypt, + alarm, + ) + };}; +} +pub struct ThreadNetworkComponent< + A: Alarm<'static> + 'static, + B: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + 'static, +> { + board_kernel: &'static kernel::Kernel, + driver_num: usize, + udp_send_mux: + &'static MuxUdpSender<'static, IP6SendStruct<'static, VirtualMuxAlarm<'static, A>>>, + udp_recv_mux: &'static MuxUdpReceiver<'static>, + port_table: &'static UdpPortManager, + aes_mux: &'static MuxAES128CCM<'static, B>, + serial_num: [u8; 8], + alarm_mux: &'static MuxAlarm<'static, A>, +} + +impl< + A: Alarm<'static> + 'static, + B: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + 'static, + > ThreadNetworkComponent +{ + pub fn new( + board_kernel: &'static kernel::Kernel, + driver_num: usize, + udp_send_mux: &'static MuxUdpSender< + 'static, + IP6SendStruct<'static, VirtualMuxAlarm<'static, A>>, + >, + udp_recv_mux: &'static MuxUdpReceiver<'static>, + port_table: &'static UdpPortManager, + aes_mux: &'static MuxAES128CCM<'static, B>, + serial_num: [u8; 8], + alarm_mux: &'static MuxAlarm<'static, A>, + ) -> Self { + Self { + board_kernel, + driver_num, + udp_send_mux, + udp_recv_mux, + port_table, + aes_mux, + serial_num, + alarm_mux, + } + } +} + +impl< + A: Alarm<'static> + 'static, + B: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + 'static, + > Component for ThreadNetworkComponent +{ + type StaticInput = ( + &'static mut MaybeUninit< + UDPSendStruct< + 'static, + capsules_extra::net::ipv6::ipv6_send::IP6SendStruct< + 'static, + VirtualMuxAlarm<'static, A>, + >, + >, + >, + &'static mut MaybeUninit< + capsules_extra::net::network_capabilities::UdpVisibilityCapability, + >, + &'static mut MaybeUninit, + &'static mut MaybeUninit< + capsules_extra::net::thread::driver::ThreadNetworkDriver< + 'static, + VirtualMuxAlarm<'static, A>, + >, + >, + &'static mut MaybeUninit<[u8; MAX_PAYLOAD_LEN]>, + &'static mut MaybeUninit<[u8; MAX_PAYLOAD_LEN]>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; CRYPT_SIZE]>, + &'static mut MaybeUninit< + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM<'static, B>, + >, + &'static mut MaybeUninit>, + ); + type Output = &'static capsules_extra::net::thread::driver::ThreadNetworkDriver< + 'static, + VirtualMuxAlarm<'static, A>, + >; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let thread_virtual_alarm: &mut VirtualMuxAlarm<'_, A> = + s.9.write(VirtualMuxAlarm::new(self.alarm_mux)); + thread_virtual_alarm.setup(); + + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + + // AES-128CCM setup + let crypt_buf = s.7.write([0; CRYPT_SIZE]); + let aes_ccm = s.8.write( + capsules_core::virtualizers::virtual_aes_ccm::VirtualAES128CCM::new( + self.aes_mux, + crypt_buf, + ), + ); + aes_ccm.setup(); + + let create_cap = create_capability!(NetworkCapabilityCreationCapability); + let udp_vis = s.1.write(UdpVisibilityCapability::new(&create_cap)); + let udp_send = s.0.write(UDPSendStruct::new(self.udp_send_mux, udp_vis)); + + // Can't use create_capability bc need capability to have a static lifetime + // so that Thread driver can use it as needed + struct DriverCap; + unsafe impl capabilities::UdpDriverCapability for DriverCap {} + static DRIVER_CAP: DriverCap = DriverCap; + + let net_cap = s.2.write(NetworkCapability::new( + AddrRange::Any, + PortRange::Any, + PortRange::Any, + &create_cap, + )); + + let send_buffer = s.4.write([0; MAX_PAYLOAD_LEN]); + let recv_buffer = s.5.write([0; MAX_PAYLOAD_LEN]); + + let thread_network_driver = s.3.write( + capsules_extra::net::thread::driver::ThreadNetworkDriver::new( + udp_send, + aes_ccm, + thread_virtual_alarm, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + self.serial_num, + MAX_PAYLOAD_LEN, + self.port_table, + kernel::utilities::leasable_buffer::SubSliceMut::new(send_buffer), + kernel::utilities::leasable_buffer::SubSliceMut::new(recv_buffer), + &DRIVER_CAP, + net_cap, + ), + ); + + thread_virtual_alarm.set_alarm_client(thread_network_driver); + + udp_send.set_client(thread_network_driver); + AES128CCM::set_client(aes_ccm, thread_network_driver); + + let udp_driver_rcvr = s.6.write(UDPReceiver::new()); + udp_driver_rcvr.set_client(thread_network_driver); + + // TODO: Thread requires port 19788 for sending/receiving MLE messages. + // The below implementation binds Thread to the required port and updates + // the UDP receiving/sending objects. There is a chance that creating a socket + // fails due to the max number of sockets being exceeded or failing to bind + // the requested port. In either case, the current implementation panics here + // as it is impossible to create a Thread network without port 19788 (used for MLE). + // Future implementations may wish to change this behavior. + self.port_table + .create_socket() + .map(|socket| { + self.port_table + .bind(socket, THREAD_PORT_NUMBER, net_cap) + .map_or_else( + |_| (), + |(tx_bind, rx_bind)| { + udp_driver_rcvr.set_binding(rx_bind); + udp_send.set_binding(tx_bind); + }, + ) + }) + .unwrap(); + + self.udp_recv_mux.add_client(udp_driver_rcvr); + + thread_network_driver + } +} diff --git a/boards/components/src/tickv.rs b/boards/components/src/tickv.rs index b15bb511a7..7f8c1dde79 100644 --- a/boards/components/src/tickv.rs +++ b/boards/components/src/tickv.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for TicKV KV System Driver. //! //! This provides one component, TicKVComponent, which provides @@ -16,70 +20,102 @@ //! ); //! //! let mux_flash = components::tickv::FlashMuxComponent::new(&peripherals.flash_ctrl).finalize( -//! components::flash_user_component_helper!(lowrisc::flash_ctrl::FlashCtrl), +//! components::flash_user_component_static!(lowrisc::flash_ctrl::FlashCtrl), +//! ); +//! +//! // SipHash +//! let sip_hash = static_init!( +//! capsules_extra::sip_hash::SipHasher24, +//! capsules_extra::sip_hash::SipHasher24::new() //! ); +//! sip_hash.register(); //! -//! let kvstore = components::tickv::TicKVComponent::new( +//! let tickv = components::tickv::TicKVComponent::new( +//! sip_hash, //! &mux_flash, //! 0x20040000 / lowrisc::flash_ctrl::PAGE_SIZE, //! 0x40000, //! flash_ctrl_read_buf, //! page_buffer, //! ) -//! .finalize(components::tickv_component_helper!( -//! lowrisc::flash_ctrl::FlashCtrl +//! .finalize(components::tickv_component_static!( +//! lowrisc::flash_ctrl::FlashCtrl, +//! capsules_extra::sip_hash::SipHasher24 //! )); //! hil::flash::HasClient::set_client(&peripherals.flash_ctrl, mux_flash); //! ``` -use capsules::tickv::TicKVStore; -use capsules::virtual_flash::FlashUser; -use capsules::virtual_flash::MuxFlash; +use capsules_core::virtualizers::virtual_flash::FlashUser; +use capsules_core::virtualizers::virtual_flash::MuxFlash; +use capsules_extra::tickv::TicKVSystem; use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; use kernel::hil; use kernel::hil::flash::HasClient; -use kernel::static_init_half; +use kernel::hil::hasher::Hasher; // Setup static space for the objects. #[macro_export] -macro_rules! tickv_component_helper { - ($F:ty) => {{ - use capsules::tickv::TicKVStore; - use capsules::virtual_flash::FlashUser; - use core::mem::MaybeUninit; - use kernel::hil; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF2: MaybeUninit>> = - MaybeUninit::uninit(); - (&mut BUF1, &mut BUF2) +macro_rules! tickv_component_static { + ($F:ty, $H:ty, $PAGE_SIZE:expr $(,)?) => {{ + let flash = + kernel::static_buf!(capsules_core::virtualizers::virtual_flash::FlashUser<'static, $F>); + let tickv = kernel::static_buf!( + capsules_extra::tickv::TicKVSystem< + 'static, + capsules_core::virtualizers::virtual_flash::FlashUser<'static, $F>, + $H, + $PAGE_SIZE, + > + ); + + (flash, tickv) + };}; +} + +#[macro_export] +macro_rules! tickv_dedicated_flash_component_static { + ($F:ty, $H:ty, $PAGE_SIZE:expr $(,)?) => {{ + let tickfs_read_buffer = kernel::static_buf!([u8; $PAGE_SIZE]); + let tickv = + kernel::static_buf!(capsules_extra::tickv::TicKVSystem<'static, $F, $H, $PAGE_SIZE>); + + (tickv, tickfs_read_buffer) };}; } pub struct TicKVComponent< F: 'static + hil::flash::Flash + hil::flash::HasClient<'static, MuxFlash<'static, F>>, + H: 'static + Hasher<'static, 8>, + const PAGE_SIZE: usize, > { mux_flash: &'static MuxFlash<'static, F>, + hasher: &'static H, region_offset: usize, flash_size: usize, - tickfs_read_buf: &'static mut [u8; 64], + tickfs_read_buf: &'static mut [u8; PAGE_SIZE], flash_read_buffer: &'static mut F::Page, } -impl>> - TicKVComponent +impl< + F: 'static + hil::flash::Flash + hil::flash::HasClient<'static, MuxFlash<'static, F>>, + H: Hasher<'static, 8>, + const PAGE_SIZE: usize, + > TicKVComponent { pub fn new( + hasher: &'static H, mux_flash: &'static MuxFlash<'static, F>, region_offset: usize, flash_size: usize, - tickfs_read_buf: &'static mut [u8; 64], + tickfs_read_buf: &'static mut [u8; PAGE_SIZE], flash_read_buffer: &'static mut F::Page, ) -> Self { Self { mux_flash, + hasher, region_offset, flash_size, tickfs_read_buf, @@ -88,37 +124,109 @@ impl>> - Component for TicKVComponent +impl< + F: 'static + hil::flash::Flash + hil::flash::HasClient<'static, MuxFlash<'static, F>>, + H: 'static + Hasher<'static, 8>, + const PAGE_SIZE: usize, + > Component for TicKVComponent { type StaticInput = ( &'static mut MaybeUninit>, - &'static mut MaybeUninit>>, + &'static mut MaybeUninit, H, PAGE_SIZE>>, ); - type Output = &'static TicKVStore<'static, FlashUser<'static, F>>; + type Output = &'static TicKVSystem<'static, FlashUser<'static, F>, H, PAGE_SIZE>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { let _grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let virtual_flash = static_init_half!( - static_buffer.0, - FlashUser<'static, F>, - FlashUser::new(self.mux_flash) - ); + let virtual_flash = static_buffer.0.write(FlashUser::new(self.mux_flash)); - let driver = static_init_half!( - static_buffer.1, - TicKVStore<'static, FlashUser<'static, F>>, - TicKVStore::new( - virtual_flash, - self.tickfs_read_buf, - self.flash_read_buffer, - self.region_offset, - self.flash_size, - ) - ); + let driver = static_buffer.1.write(TicKVSystem::new( + virtual_flash, + self.hasher, + self.tickfs_read_buf, + self.flash_read_buffer, + self.region_offset, + self.flash_size, + )); virtual_flash.set_client(driver); - driver.initalise(); + driver.initialise(); driver } } + +pub type TicKVDedicatedFlashComponentType = + capsules_extra::tickv::TicKVSystem<'static, F, H, PAGE>; + +pub struct TicKVDedicatedFlashComponent< + F: 'static + + hil::flash::Flash + + hil::flash::HasClient<'static, TicKVSystem<'static, F, H, PAGE_SIZE>>, + H: 'static + Hasher<'static, 8>, + const PAGE_SIZE: usize, +> { + flash: &'static F, + hasher: &'static H, + region_offset: usize, + flash_size: usize, + flash_read_buffer: &'static mut F::Page, +} + +impl< + F: 'static + + hil::flash::Flash + + hil::flash::HasClient<'static, TicKVSystem<'static, F, H, PAGE_SIZE>>, + H: Hasher<'static, 8>, + const PAGE_SIZE: usize, + > TicKVDedicatedFlashComponent +{ + pub fn new( + hasher: &'static H, + flash: &'static F, + region_offset: usize, + flash_size: usize, + flash_read_buffer: &'static mut F::Page, + ) -> Self { + Self { + flash, + hasher, + region_offset, + flash_size, + flash_read_buffer, + } + } +} + +impl< + F: 'static + + hil::flash::Flash + + hil::flash::HasClient<'static, TicKVSystem<'static, F, H, PAGE_SIZE>>, + H: 'static + Hasher<'static, 8>, + const PAGE_SIZE: usize, + > Component for TicKVDedicatedFlashComponent +{ + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; PAGE_SIZE]>, + ); + type Output = &'static TicKVSystem<'static, F, H, PAGE_SIZE>; + + fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let _grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + + let tickfs_read_buf = static_buffer.1.write([0; PAGE_SIZE]); + + let tickv = static_buffer.0.write(TicKVSystem::new( + self.flash, + self.hasher, + tickfs_read_buf, + self.flash_read_buffer, + self.region_offset, + self.flash_size, + )); + self.flash.set_client(tickv); + self.hasher.set_client(tickv); + tickv.initialise(); + tickv + } +} diff --git a/boards/components/src/touch.rs b/boards/components/src/touch.rs index ff45cb30f6..48955b9386 100644 --- a/boards/components/src/touch.rs +++ b/boards/components/src/touch.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for the Touch Panel. //! //! Usage @@ -9,12 +13,12 @@ //! // Just Touch //! let touch = //! components::touch::TouchComponent::new(board_kernel, ts, None, Some(screen)) -//! .finalize(()); +//! .finalize(components::touch_component_static!()); //! //! // With Gesture //! let touch = //! components::touch::TouchComponent::new(board_kernel, ts, Some(ts), Some(screen)) -//! .finalize(()); +//! .finalize(components::touch_component_static!()); //! ``` //! //! Multi Touch @@ -23,24 +27,32 @@ //! // Just Multi Touch //! let touch = //! components::touch::MultiTouchComponent::new(board_kernel, ts, None, Some(screen)) -//! .finalize(()); +//! .finalize(components::touch_component_static!()); //! //! // With Gesture //! let touch = //! components::touch::MultiTouchComponent::new(board_kernel, ts, Some(ts), Some(screen)) -//! .finalize(()); +//! .finalize(components::touch_component_static!()); //! ``` +use capsules_extra::touch::Touch; +use core::mem::MaybeUninit; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; -use kernel::static_init; + +#[macro_export] +macro_rules! touch_component_static { + () => {{ + kernel::static_buf!(capsules_extra::touch::Touch<'static>) + };}; +} pub struct TouchComponent { board_kernel: &'static kernel::Kernel, driver_num: usize, touch: &'static dyn kernel::hil::touch::Touch<'static>, gesture: Option<&'static dyn kernel::hil::touch::Gesture<'static>>, - screen: Option<&'static dyn kernel::hil::screen::Screen>, + screen: Option<&'static dyn kernel::hil::screen::Screen<'static>>, } impl TouchComponent { @@ -49,30 +61,32 @@ impl TouchComponent { driver_num: usize, touch: &'static dyn kernel::hil::touch::Touch<'static>, gesture: Option<&'static dyn kernel::hil::touch::Gesture<'static>>, - screen: Option<&'static dyn kernel::hil::screen::Screen>, + screen: Option<&'static dyn kernel::hil::screen::Screen<'static>>, ) -> TouchComponent { TouchComponent { - board_kernel: board_kernel, - driver_num: driver_num, - touch: touch, - gesture: gesture, - screen: screen, + board_kernel, + driver_num, + touch, + gesture, + screen, } } } impl Component for TouchComponent { - type StaticInput = (); - type Output = &'static capsules::touch::Touch<'static>; + type StaticInput = &'static mut MaybeUninit>; + type Output = &'static capsules_extra::touch::Touch<'static>; - unsafe fn finalize(self, _static_input: Self::StaticInput) -> Self::Output { + fn finalize(self, static_input: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); let grant_touch = self.board_kernel.create_grant(self.driver_num, &grant_cap); - let touch = static_init!( - capsules::touch::Touch, - capsules::touch::Touch::new(Some(self.touch), None, self.screen, grant_touch) - ); + let touch = static_input.write(capsules_extra::touch::Touch::new( + Some(self.touch), + None, + self.screen, + grant_touch, + )); kernel::hil::touch::Touch::set_client(self.touch, touch); if let Some(gesture) = self.gesture { @@ -88,7 +102,7 @@ pub struct MultiTouchComponent { driver_num: usize, multi_touch: &'static dyn kernel::hil::touch::MultiTouch<'static>, gesture: Option<&'static dyn kernel::hil::touch::Gesture<'static>>, - screen: Option<&'static dyn kernel::hil::screen::Screen>, + screen: Option<&'static dyn kernel::hil::screen::Screen<'static>>, } impl MultiTouchComponent { @@ -100,27 +114,29 @@ impl MultiTouchComponent { screen: Option<&'static dyn kernel::hil::screen::Screen>, ) -> MultiTouchComponent { MultiTouchComponent { - board_kernel: board_kernel, - driver_num: driver_num, - multi_touch: multi_touch, - gesture: gesture, - screen: screen, + board_kernel, + driver_num, + multi_touch, + gesture, + screen, } } } impl Component for MultiTouchComponent { - type StaticInput = (); - type Output = &'static capsules::touch::Touch<'static>; + type StaticInput = &'static mut MaybeUninit>; + type Output = &'static capsules_extra::touch::Touch<'static>; - unsafe fn finalize(self, _static_input: Self::StaticInput) -> Self::Output { + fn finalize(self, static_input: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); let grant_touch = self.board_kernel.create_grant(self.driver_num, &grant_cap); - let touch = static_init!( - capsules::touch::Touch, - capsules::touch::Touch::new(None, Some(self.multi_touch), self.screen, grant_touch) - ); + let touch = static_input.write(capsules_extra::touch::Touch::new( + None, + Some(self.multi_touch), + self.screen, + grant_touch, + )); kernel::hil::touch::MultiTouch::set_client(self.multi_touch, touch); if let Some(gesture) = self.gesture { diff --git a/boards/components/src/udp_driver.rs b/boards/components/src/udp_driver.rs index 1c0fd8c59c..438e2e9f05 100644 --- a/boards/components/src/udp_driver.rs +++ b/boards/components/src/udp_driver.rs @@ -1,7 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component to initialize the userland UDP driver. //! -//! This provides one Component, UDPDriverComponent. This component initializes a userspace -//! UDP driver that allows apps to use the UDP stack. +//! This provides one Component, UDPDriverComponent. This component initializes +//! a userspace UDP driver that allows apps to use the UDP stack. //! //! Usage //! ----- @@ -14,48 +18,53 @@ //! local_ip_ifaces, //! PAYLOAD_LEN, //! ) -//! .finalize(); +//! .finalize(components::udp_driver_component_static!()); //! ``` -use capsules; -use capsules::net::ipv6::ip_utils::IPAddr; -use capsules::net::ipv6::ipv6_send::IP6SendStruct; -use capsules::net::network_capabilities::{ +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; +use capsules_extra::net::ipv6::ip_utils::IPAddr; +use capsules_extra::net::ipv6::ipv6_send::IP6SendStruct; +use capsules_extra::net::network_capabilities::{ AddrRange, NetworkCapability, PortRange, UdpVisibilityCapability, }; -use capsules::net::udp::udp_port_table::UdpPortManager; -use capsules::net::udp::udp_recv::MuxUdpReceiver; -use capsules::net::udp::udp_recv::UDPReceiver; -use capsules::net::udp::udp_send::{MuxUdpSender, UDPSendStruct, UDPSender}; -use capsules::virtual_alarm::VirtualMuxAlarm; +use capsules_extra::net::udp::udp_port_table::UdpPortManager; +use capsules_extra::net::udp::udp_recv::MuxUdpReceiver; +use capsules_extra::net::udp::udp_recv::UDPReceiver; +use capsules_extra::net::udp::udp_send::{MuxUdpSender, UDPSendStruct, UDPSender}; use core::mem::MaybeUninit; -use kernel; use kernel::capabilities; use kernel::capabilities::NetworkCapabilityCreationCapability; use kernel::component::Component; +use kernel::create_capability; use kernel::hil::time::Alarm; -use kernel::{create_capability, static_init, static_init_half}; const MAX_PAYLOAD_LEN: usize = super::udp_mux::MAX_PAYLOAD_LEN; -static mut DRIVER_BUF: [u8; MAX_PAYLOAD_LEN] = [0; MAX_PAYLOAD_LEN]; - // Setup static space for the objects. #[macro_export] -macro_rules! udp_driver_component_helper { +macro_rules! udp_driver_component_static { ($A:ty $(,)?) => {{ - use capsules::net::udp::udp_send::UDPSendStruct; - use core::mem::MaybeUninit; - static mut BUF0: MaybeUninit< - UDPSendStruct< + use components::udp_mux::MAX_PAYLOAD_LEN; + + let udp_send = kernel::static_buf!( + capsules_extra::net::udp::udp_send::UDPSendStruct< 'static, - capsules::net::ipv6::ipv6_send::IP6SendStruct< + capsules_extra::net::ipv6::ipv6_send::IP6SendStruct< 'static, - VirtualMuxAlarm<'static, $A>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, $A>, >, - >, - > = MaybeUninit::uninit(); - (&mut BUF0,) + > + ); + let udp_vis_cap = + kernel::static_buf!(capsules_extra::net::network_capabilities::UdpVisibilityCapability); + let net_cap = + kernel::static_buf!(capsules_extra::net::network_capabilities::NetworkCapability); + let udp_driver = kernel::static_buf!(capsules_extra::net::udp::UDPDriver<'static>); + let buffer = kernel::static_buf!([u8; MAX_PAYLOAD_LEN]); + let udp_recv = + kernel::static_buf!(capsules_extra::net::udp::udp_recv::UDPReceiver<'static>); + + (udp_send, udp_vis_cap, net_cap, udp_driver, buffer, udp_recv) };}; } @@ -97,28 +106,28 @@ impl> Component for UDPDriverComponent { &'static mut MaybeUninit< UDPSendStruct< 'static, - capsules::net::ipv6::ipv6_send::IP6SendStruct<'static, VirtualMuxAlarm<'static, A>>, + capsules_extra::net::ipv6::ipv6_send::IP6SendStruct< + 'static, + VirtualMuxAlarm<'static, A>, + >, >, >, + &'static mut MaybeUninit< + capsules_extra::net::network_capabilities::UdpVisibilityCapability, + >, + &'static mut MaybeUninit, + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[u8; MAX_PAYLOAD_LEN]>, + &'static mut MaybeUninit>, ); - type Output = &'static capsules::net::udp::UDPDriver<'static>; + type Output = &'static capsules_extra::net::udp::UDPDriver<'static>; - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + fn finalize(self, s: Self::StaticInput) -> Self::Output { let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); // TODO: change initialization below let create_cap = create_capability!(NetworkCapabilityCreationCapability); - let udp_vis = static_init!( - UdpVisibilityCapability, - UdpVisibilityCapability::new(&create_cap) - ); - let udp_send = static_init_half!( - static_buffer.0, - UDPSendStruct< - 'static, - capsules::net::ipv6::ipv6_send::IP6SendStruct<'static, VirtualMuxAlarm<'static, A>>, - >, - UDPSendStruct::new(self.udp_send_mux, udp_vis) - ); + let udp_vis = s.1.write(UdpVisibilityCapability::new(&create_cap)); + let udp_send = s.0.write(UDPSendStruct::new(self.udp_send_mux, udp_vis)); // Can't use create_capability bc need capability to have a static lifetime // so that UDP driver can use it as needed @@ -126,28 +135,29 @@ impl> Component for UDPDriverComponent { unsafe impl capabilities::UdpDriverCapability for DriverCap {} static DRIVER_CAP: DriverCap = DriverCap; - let net_cap = static_init!( - NetworkCapability, - NetworkCapability::new(AddrRange::Any, PortRange::Any, PortRange::Any, &create_cap) - ); + let net_cap = s.2.write(NetworkCapability::new( + AddrRange::Any, + PortRange::Any, + PortRange::Any, + &create_cap, + )); - let udp_driver = static_init!( - capsules::net::udp::UDPDriver<'static>, - capsules::net::udp::UDPDriver::new( - udp_send, - self.board_kernel.create_grant(self.driver_num, &grant_cap), - self.interface_list, - MAX_PAYLOAD_LEN, - self.port_table, - kernel::utilities::leasable_buffer::LeasableBuffer::new(&mut DRIVER_BUF), - &DRIVER_CAP, - net_cap, - ) - ); + let buffer = s.4.write([0; MAX_PAYLOAD_LEN]); + + let udp_driver = s.3.write(capsules_extra::net::udp::UDPDriver::new( + udp_send, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + self.interface_list, + MAX_PAYLOAD_LEN, + self.port_table, + kernel::utilities::leasable_buffer::SubSliceMut::new(buffer), + &DRIVER_CAP, + net_cap, + )); udp_send.set_client(udp_driver); self.port_table.set_user_ports(udp_driver, &DRIVER_CAP); - let udp_driver_rcvr = static_init!(UDPReceiver<'static>, UDPReceiver::new()); + let udp_driver_rcvr = s.5.write(UDPReceiver::new()); self.udp_recv_mux.set_driver(udp_driver); self.udp_recv_mux.add_client(udp_driver_rcvr); udp_driver diff --git a/boards/components/src/udp_mux.rs b/boards/components/src/udp_mux.rs index d173a94384..021c97264a 100644 --- a/boards/components/src/udp_mux.rs +++ b/boards/components/src/udp_mux.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component to initialize the udp/6lowpan interface. //! //! This provides one Component, UDPMuxComponent. This component @@ -17,35 +21,35 @@ //! mux_alarm, //! MAX_PAYLOAD_LEN, //! ) -//! .finalize(); +//! .finalize(components::udp_mux_component_static!()); //! ``` // Author: Hudson Ayers // Last Modified: 5/21/2019 -use capsules; -use capsules::ieee802154::device::MacDevice; -use capsules::net::ieee802154::MacAddress; -use capsules::net::ipv6::ip_utils::IPAddr; -use capsules::net::ipv6::ipv6_recv::IP6Receiver; -use capsules::net::ipv6::ipv6_send::IP6SendStruct; -use capsules::net::ipv6::ipv6_send::IP6Sender; -use capsules::net::ipv6::{IP6Packet, IPPayload, TransportHeader}; -use capsules::net::network_capabilities::{IpVisibilityCapability, UdpVisibilityCapability}; -use capsules::net::sixlowpan::{sixlowpan_compression, sixlowpan_state}; -use capsules::net::udp::udp_port_table::{SocketBindingEntry, UdpPortManager, MAX_NUM_BOUND_PORTS}; -use capsules::net::udp::udp_recv::MuxUdpReceiver; -use capsules::net::udp::udp_send::MuxUdpSender; -use capsules::net::udp::UDPHeader; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_extra::ieee802154::device::MacDevice; +use capsules_extra::net::ieee802154::MacAddress; +use capsules_extra::net::ipv6::ip_utils::IPAddr; +use capsules_extra::net::ipv6::ipv6_recv::IP6Receiver; +use capsules_extra::net::ipv6::ipv6_recv::IP6RecvStruct; +use capsules_extra::net::ipv6::ipv6_send::IP6SendStruct; +use capsules_extra::net::ipv6::ipv6_send::IP6Sender; +use capsules_extra::net::ipv6::{IP6Packet, IPPayload, TransportHeader}; +use capsules_extra::net::network_capabilities::{IpVisibilityCapability, UdpVisibilityCapability}; +use capsules_extra::net::sixlowpan::{sixlowpan_compression, sixlowpan_state}; +use capsules_extra::net::udp::udp_port_table::{ + SocketBindingEntry, UdpPortManager, MAX_NUM_BOUND_PORTS, +}; +use capsules_extra::net::udp::udp_recv::MuxUdpReceiver; +use capsules_extra::net::udp::udp_send::MuxUdpSender; +use capsules_extra::net::udp::UDPHeader; use core::mem::MaybeUninit; -use kernel; use kernel::capabilities; use kernel::component::Component; use kernel::create_capability; use kernel::hil::radio; use kernel::hil::time::Alarm; -use kernel::{static_init, static_init_half}; // The UDP stack requires several packet buffers: // @@ -56,64 +60,102 @@ use kernel::{static_init, static_init_half}; // Additionally, every capsule using the stack needs an additional buffer to craft packets for // tx which can then be passed to the MuxUdpSender for tx. -static mut RADIO_BUF: [u8; radio::MAX_BUF_SIZE] = [0x00; radio::MAX_BUF_SIZE]; -static mut SIXLOWPAN_RX_BUF: [u8; 1280] = [0x00; 1280]; - pub const MAX_PAYLOAD_LEN: usize = 200; //The max size UDP message that can be sent by userspace apps or capsules -static mut UDP_DGRAM: [u8; MAX_PAYLOAD_LEN] = [0; MAX_PAYLOAD_LEN]; - -// Rather than require a data structure with 65535 slots (number of UDP ports), we -// use a structure that can hold up to 16 port bindings. Any given capsule can bind -// at most one port. When a capsule obtains a socket, it is assigned a slot in this table. -// MAX_NUM_BOUND_PORTS represents the total number of capsules that can bind to different -// ports simultaneously within the Tock kernel. -// Each slot in the table tracks one socket that has been given to a capsule. If no -// slots in the table are free, no slots remain to be given out. If a socket is used to bind to -// a port, the port that is bound is saved in the slot to ensure that subsequent bindings do -// not also attempt to bind that port number. -static mut USED_KERNEL_PORTS: [Option; MAX_NUM_BOUND_PORTS] = - [None; MAX_NUM_BOUND_PORTS]; // Setup static space for the objects. #[macro_export] -macro_rules! udp_mux_component_helper { - ($A:ty $(,)?) => {{ - use capsules; - use capsules::net::sixlowpan::{sixlowpan_compression, sixlowpan_state}; - use capsules::net::udp::udp_send::MuxUdpSender; - use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +macro_rules! udp_mux_component_static { + ($A:ty, $M:ty $(,)?) => {{ + use capsules_core; + use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; + use capsules_extra::net::sixlowpan::{sixlowpan_compression, sixlowpan_state}; + use capsules_extra::net::udp::udp_send::MuxUdpSender; + use components::udp_mux::MAX_PAYLOAD_LEN; use core::mem::MaybeUninit; - static mut BUF0: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF1: MaybeUninit> = - MaybeUninit::uninit(); - static mut BUF2: MaybeUninit< + + let alarm = kernel::static_buf!(VirtualMuxAlarm<'static, $A>); + let mac_user = + kernel::static_buf!(capsules_extra::ieee802154::virtual_mac::MacUser<'static, $M>); + let sixlowpan = kernel::static_buf!( sixlowpan_state::Sixlowpan< 'static, VirtualMuxAlarm<'static, $A>, sixlowpan_compression::Context, - >, - > = MaybeUninit::uninit(); - static mut BUF3: MaybeUninit> = MaybeUninit::uninit(); - static mut BUF4: MaybeUninit< - capsules::net::ipv6::ipv6_send::IP6SendStruct<'static, VirtualMuxAlarm<'static, $A>>, - > = MaybeUninit::uninit(); - static mut BUF5: MaybeUninit< + > + ); + let rx_state = kernel::static_buf!(sixlowpan_state::RxState<'static>); + let ip6_send = kernel::static_buf!( + capsules_extra::net::ipv6::ipv6_send::IP6SendStruct< + 'static, + VirtualMuxAlarm<'static, $A>, + > + ); + let mux_udp_send = kernel::static_buf!( MuxUdpSender< 'static, - capsules::net::ipv6::ipv6_send::IP6SendStruct< + capsules_extra::net::ipv6::ipv6_send::IP6SendStruct< 'static, VirtualMuxAlarm<'static, $A>, >, - >, - > = MaybeUninit::uninit(); + > + ); + let mux_udp_recv = + kernel::static_buf!(capsules_extra::net::udp::udp_recv::MuxUdpReceiver<'static>); + let udp_port_manager = + kernel::static_buf!(capsules_extra::net::udp::udp_port_table::UdpPortManager); + + let ip6_packet = kernel::static_buf!(capsules_extra::net::ipv6::IP6Packet<'static>); + let ip6_receive = + kernel::static_buf!(capsules_extra::net::ipv6::ipv6_recv::IP6RecvStruct<'static>); + + // Rather than require a data structure with 65535 slots (number of UDP ports), + // we use a structure that can hold up to 16 port bindings. Any given capsule + // can bind at most one port. When a capsule obtains a socket, it is assigned a + // slot in this table. MAX_NUM_BOUND_PORTS represents the total number of + // capsules that can bind to different ports simultaneously within the Tock + // kernel. + // + // Each slot in the table tracks one socket that has been given to a capsule. If + // no slots in the table are free, no slots remain to be given out. If a socket + // is used to bind to a port, the port that is bound is saved in the slot to + // ensure that subsequent bindings do not also attempt to bind that port number. + let used_ports = kernel::static_buf!( + [Option; + capsules_extra::net::udp::udp_port_table::MAX_NUM_BOUND_PORTS] + ); + + let radio_buf = kernel::static_buf!([u8; kernel::hil::radio::MAX_BUF_SIZE]); + let sixlowpan_rx = kernel::static_buf!([u8; 1280]); + let udp_dgram = kernel::static_buf!([u8; MAX_PAYLOAD_LEN]); + + let udp_vis_cap = + kernel::static_buf!(capsules_extra::net::network_capabilities::UdpVisibilityCapability); + let ip_vis_cap = + kernel::static_buf!(capsules_extra::net::network_capabilities::IpVisibilityCapability); + ( - &mut BUF0, &mut BUF1, &mut BUF2, &mut BUF3, &mut BUF4, &mut BUF5, + alarm, + mac_user, + sixlowpan, + rx_state, + ip6_send, + mux_udp_send, + mux_udp_recv, + udp_port_manager, + ip6_packet, + ip6_receive, + used_ports, + radio_buf, + sixlowpan_rx, + udp_dgram, + udp_vis_cap, + ip_vis_cap, ) };}; } -pub struct UDPMuxComponent + 'static> { - mux_mac: &'static capsules::ieee802154::virtual_mac::MuxMac<'static>, +pub struct UDPMuxComponent + 'static, M: MacDevice<'static> + 'static> { + mux_mac: &'static capsules_extra::ieee802154::virtual_mac::MuxMac<'static, M>, ctx_pfix_len: u8, ctx_pfix: [u8; 16], dst_mac_addr: MacAddress, @@ -122,9 +164,9 @@ pub struct UDPMuxComponent + 'static> { alarm_mux: &'static MuxAlarm<'static, A>, } -impl + 'static> UDPMuxComponent { +impl + 'static, M: MacDevice<'static>> UDPMuxComponent { pub fn new( - mux_mac: &'static capsules::ieee802154::virtual_mac::MuxMac<'static>, + mux_mac: &'static capsules_extra::ieee802154::virtual_mac::MuxMac<'static, M>, ctx_pfix_len: u8, ctx_pfix: [u8; 16], dst_mac_addr: MacAddress, @@ -144,10 +186,10 @@ impl + 'static> UDPMuxComponent { } } -impl + 'static> Component for UDPMuxComponent { +impl + 'static, M: MacDevice<'static>> Component for UDPMuxComponent { type StaticInput = ( &'static mut MaybeUninit>, - &'static mut MaybeUninit>, + &'static mut MaybeUninit>, &'static mut MaybeUninit< sixlowpan_state::Sixlowpan< 'static, @@ -157,14 +199,30 @@ impl + 'static> Component for UDPMuxComponent { >, &'static mut MaybeUninit>, &'static mut MaybeUninit< - capsules::net::ipv6::ipv6_send::IP6SendStruct<'static, VirtualMuxAlarm<'static, A>>, + capsules_extra::net::ipv6::ipv6_send::IP6SendStruct< + 'static, + VirtualMuxAlarm<'static, A>, + >, >, &'static mut MaybeUninit< MuxUdpSender< 'static, - capsules::net::ipv6::ipv6_send::IP6SendStruct<'static, VirtualMuxAlarm<'static, A>>, + capsules_extra::net::ipv6::ipv6_send::IP6SendStruct< + 'static, + VirtualMuxAlarm<'static, A>, + >, >, >, + &'static mut MaybeUninit>, + &'static mut MaybeUninit, + &'static mut MaybeUninit>, + &'static mut MaybeUninit>, + &'static mut MaybeUninit<[Option; MAX_NUM_BOUND_PORTS]>, + &'static mut MaybeUninit<[u8; radio::MAX_BUF_SIZE]>, + &'static mut MaybeUninit<[u8; 1280]>, + &'static mut MaybeUninit<[u8; MAX_PAYLOAD_LEN]>, + &'static mut MaybeUninit, + &'static mut MaybeUninit, ); type Output = ( &'static MuxUdpSender<'static, IP6SendStruct<'static, VirtualMuxAlarm<'static, A>>>, @@ -172,117 +230,90 @@ impl + 'static> Component for UDPMuxComponent { &'static UdpPortManager, ); - unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { - let ipsender_virtual_alarm = static_init_half!( - static_buffer.0, - VirtualMuxAlarm<'static, A>, - VirtualMuxAlarm::new(self.alarm_mux) - ); + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let ipsender_virtual_alarm = s.0.write(VirtualMuxAlarm::new(self.alarm_mux)); ipsender_virtual_alarm.setup(); - let udp_mac = static_init_half!( - static_buffer.1, - capsules::ieee802154::virtual_mac::MacUser<'static>, - capsules::ieee802154::virtual_mac::MacUser::new(self.mux_mac) - ); + let udp_mac = + s.1.write(capsules_extra::ieee802154::virtual_mac::MacUser::new( + self.mux_mac, + )); self.mux_mac.add_user(udp_mac); let create_cap = create_capability!(capabilities::NetworkCapabilityCreationCapability); - let udp_vis = static_init!( - UdpVisibilityCapability, - UdpVisibilityCapability::new(&create_cap) - ); - let ip_vis = static_init!( - IpVisibilityCapability, - IpVisibilityCapability::new(&create_cap) - ); + let udp_vis = s.14.write(UdpVisibilityCapability::new(&create_cap)); + let ip_vis = s.15.write(IpVisibilityCapability::new(&create_cap)); - let sixlowpan = static_init_half!( - static_buffer.2, - sixlowpan_state::Sixlowpan< - 'static, - VirtualMuxAlarm<'static, A>, - sixlowpan_compression::Context, - >, - sixlowpan_state::Sixlowpan::new( - sixlowpan_compression::Context { - prefix: self.ctx_pfix, - prefix_len: self.ctx_pfix_len, - id: 0, - compress: false, - }, - ipsender_virtual_alarm, // OK to reuse bc only used to get time, not set alarms - ) - ); + let sixlowpan = s.2.write(sixlowpan_state::Sixlowpan::new( + sixlowpan_compression::Context { + prefix: self.ctx_pfix, + prefix_len: self.ctx_pfix_len, + id: 0, + compress: false, + }, + ipsender_virtual_alarm, // OK to reuse bc only used to get time, not set alarms + )); + let sixlowpan_rx_buffer = s.12.write([0; 1280]); let sixlowpan_state = sixlowpan as &dyn sixlowpan_state::SixlowpanState; let sixlowpan_tx = sixlowpan_state::TxState::new(sixlowpan_state); - let default_rx_state = static_init_half!( - static_buffer.3, - sixlowpan_state::RxState<'static>, - sixlowpan_state::RxState::new(&mut SIXLOWPAN_RX_BUF) - ); + let default_rx_state = + s.3.write(sixlowpan_state::RxState::new(sixlowpan_rx_buffer)); sixlowpan_state.add_rx_state(default_rx_state); udp_mac.set_receive_client(sixlowpan); + let udp_dgram_buffer = s.13.write([0; MAX_PAYLOAD_LEN]); let tr_hdr = TransportHeader::UDP(UDPHeader::new()); let ip_pyld: IPPayload = IPPayload { header: tr_hdr, - payload: &mut UDP_DGRAM, + payload: udp_dgram_buffer, }; - let ip6_dg = static_init!(IP6Packet<'static>, IP6Packet::new(ip_pyld)); + let ip6_dg = s.8.write(IP6Packet::new(ip_pyld)); + + let radio_buf = s.11.write([0; radio::MAX_BUF_SIZE]); - // In current design, all udp senders share same IP sender, and the IP sender - // holds the destination mac address. This means all UDP senders must send to - // the same mac address...this works fine under the assumption - // of all packets being routed via a single gateway router, but doesn't work - // if multiple senders want to send to different addresses on a local network. - // This will be fixed once we have an ipv6_nd cache mapping IP addresses to dst macs - let ip_send = static_init_half!( - static_buffer.4, - capsules::net::ipv6::ipv6_send::IP6SendStruct<'static, VirtualMuxAlarm<'static, A>>, - capsules::net::ipv6::ipv6_send::IP6SendStruct::new( + // In current design, all udp senders share same IP sender, and the IP + // sender holds the destination mac address. This means all UDP senders + // must send to the same mac address...this works fine under the + // assumption of all packets being routed via a single gateway router, + // but doesn't work if multiple senders want to send to different + // addresses on a local network. This will be fixed once we have an + // ipv6_nd cache mapping IP addresses to dst macs + let ip_send = + s.4.write(capsules_extra::net::ipv6::ipv6_send::IP6SendStruct::new( ip6_dg, ipsender_virtual_alarm, - &mut RADIO_BUF, + radio_buf, sixlowpan_tx, udp_mac, self.dst_mac_addr, self.src_mac_addr, ip_vis, - ) - ); + )); ipsender_virtual_alarm.set_alarm_client(ip_send); - // Initially, set src IP of the sender to be the first IP in the Interface - // list. Userland apps can change this if they so choose. - // Notably, the src addr is the same regardless of if messages are sent from - // userland or capsules. + // Initially, set src IP of the sender to be the first IP in the + // Interface list. Userland apps can change this if they so choose. + // Notably, the src addr is the same regardless of if messages are sent + // from userland or capsules. ip_send.set_addr(self.interface_list[0]); udp_mac.set_transmit_client(ip_send); - let ip_receive = static_init!( - capsules::net::ipv6::ipv6_recv::IP6RecvStruct<'static>, - capsules::net::ipv6::ipv6_recv::IP6RecvStruct::new() - ); + let ip_receive = + s.9.write(capsules_extra::net::ipv6::ipv6_recv::IP6RecvStruct::new()); sixlowpan_state.set_rx_client(ip_receive); - let udp_recv_mux = static_init!(MuxUdpReceiver<'static>, MuxUdpReceiver::new()); + let udp_recv_mux = s.6.write(MuxUdpReceiver::new()); ip_receive.set_client(udp_recv_mux); - let udp_send_mux = static_init_half!( - static_buffer.5, - MuxUdpSender< - 'static, - capsules::net::ipv6::ipv6_send::IP6SendStruct<'static, VirtualMuxAlarm<'static, A>>, - >, - MuxUdpSender::new(ip_send) - ); + let udp_send_mux = s.5.write(MuxUdpSender::new(ip_send)); ip_send.set_client(udp_send_mux); + let kernel_ports = s.10.write([None; MAX_NUM_BOUND_PORTS]); let create_table_cap = create_capability!(capabilities::CreatePortTableCapability); - let udp_port_table = static_init!( - UdpPortManager, - UdpPortManager::new(&create_table_cap, &mut USED_KERNEL_PORTS, udp_vis) - ); + let udp_port_table = s.7.write(UdpPortManager::new( + &create_table_cap, + kernel_ports, + udp_vis, + )); (udp_send_mux, udp_recv_mux, udp_port_table) } diff --git a/boards/components/src/usb.rs b/boards/components/src/usb.rs new file mode 100644 index 0000000000..fa36d0c86c --- /dev/null +++ b/boards/components/src/usb.rs @@ -0,0 +1,91 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Generic component for initializing a USB device given a USBController. +//! +//! This provides one Component, UsbComponent, which implements +//! A userspace syscall interface to a USB peripheral. +//! +//! Usage +//! ----- +//! ```rust +//! let usb_driver = components::usb::UsbComponent::new( +//! board_kernel, +//! capsules_extra::usb::usb_user::DRIVER_NUM, +//! &peripherals.usbc, +//! ) +//! .finalize(components::usb_component_static!(sam4l::usbc::Usbc)); +//! ``` + +use core::mem::MaybeUninit; +use kernel::capabilities; +use kernel::component::Component; +use kernel::create_capability; +use kernel::hil::usb::UsbController; + +#[macro_export] +macro_rules! usb_component_static { + ($U:ty $(,)?) => {{ + let usb_client = kernel::static_buf!(capsules_extra::usb::usbc_client::Client<'static, $U>); + let usb_driver = kernel::static_buf!( + capsules_extra::usb::usb_user::UsbSyscallDriver< + 'static, + capsules_extra::usb::usbc_client::Client<'static, $U>, + > + ); + (usb_client, usb_driver) + }}; +} + +pub struct UsbComponent + 'static> { + board_kernel: &'static kernel::Kernel, + driver_num: usize, + usbc: &'static U, +} + +impl + 'static> UsbComponent { + pub fn new(board_kernel: &'static kernel::Kernel, driver_num: usize, usbc: &'static U) -> Self { + Self { + board_kernel, + driver_num, + usbc, + } + } +} + +impl + 'static> Component for UsbComponent { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit< + capsules_extra::usb::usb_user::UsbSyscallDriver< + 'static, + capsules_extra::usb::usbc_client::Client<'static, U>, + >, + >, + ); + type Output = &'static capsules_extra::usb::usb_user::UsbSyscallDriver< + 'static, + capsules_extra::usb::usbc_client::Client<'static, U>, + >; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + + // Configure the USB controller + let usb_client = s.0.write(capsules_extra::usb::usbc_client::Client::new( + self.usbc, + capsules_extra::usb::usbc_client::MAX_CTRL_PACKET_SIZE_SAM4L, + )); + self.usbc.set_client(usb_client); + + // Configure the USB userspace driver + let usb_driver = + s.1.write(capsules_extra::usb::usb_user::UsbSyscallDriver::new( + usb_client, + self.board_kernel.create_grant(self.driver_num, &grant_cap), + )); + + usb_driver + } +} diff --git a/boards/configurations/README.md b/boards/configurations/README.md new file mode 100644 index 0000000000..84deb3594d --- /dev/null +++ b/boards/configurations/README.md @@ -0,0 +1,36 @@ +Configuration Boards +==================== + +This folder contains Tock kernel configurations for boards designed for very +specific purposes. These are not expected to be general purpose boards (the root +boards/ directory stores those), but instead these boards are configured to +expose specific functionality, likely for testing. + +Many functions in Tock are configurable, and each board is free to select the +preferred configuration based on hardware features or intended use cases. This +often means, however, that there are many kernel configurations that _no_ boards +use, making it difficult to test those configurations. + +For example, checking process credentials can use different credential policies. +A configuration board can be configured with a specific credential checker, even +if no root-level board wants to use that configuration. + +Directory Structure and Naming +------------------------------ + +Boards in this `boards/configurations` directory should be organized by the root +board type. The name must be `-test-[configuration]`. For example: + +``` +boards/configurations/ + nrf52840dk/ + nrf52840k-test-[configuration1] + nrf52840k-test-[configuration2] + imix/ + imix-test-[configuration1] + imix-test-[configuration2] +``` + +Each specific board configuration for each root board should have a descriptive +name. For example, if a configuration of the nrf52840dk is designed for running +kernel tests, the board might be called `nrf52840dk/nrf52840dk-test-kernel`. diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/Cargo.toml b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/Cargo.toml new file mode 100644 index 0000000000..a04f02b5a9 --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/Cargo.toml @@ -0,0 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +[package] +name = "nrf52840dk-test-appid-sha256" +version.workspace = true +authors.workspace = true +build = "../../../build.rs" +edition.workspace = true + +[dependencies] +components = { path = "../../../components" } +cortexm4 = { path = "../../../../arch/cortex-m4" } +kernel = { path = "../../../../kernel" } +nrf52840 = { path = "../../../../chips/nrf52840" } +nrf52_components = { path = "../../../nordic/nrf52_components" } + +capsules-core = { path = "../../../../capsules/core" } +capsules-extra = { path = "../../../../capsules/extra" } +capsules-system = { path = "../../../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/Makefile b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/Makefile new file mode 100644 index 0000000000..99e0a57161 --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/Makefile @@ -0,0 +1,9 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +TARGET=thumbv7em-none-eabi +PLATFORM=nrf52840dk-test-appid-sha256 + +include ../../../Makefile.common +include ../nrf52840dk.mk diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/README.md b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/README.md new file mode 100644 index 0000000000..8a1f90a480 --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/README.md @@ -0,0 +1,4 @@ +nRF52840-DK SHA256 AppID Test Board +=================================== + +This is a minimal kernel for testing with SHA256 credential checking. diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/layout.ld b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/layout.ld new file mode 100644 index 0000000000..417a58fe7d --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/layout.ld @@ -0,0 +1,6 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + +INCLUDE ../../../nordic/nrf52840_chip_layout.ld +INCLUDE ../../../kernel_layout.ld diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/src/io.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/src/io.rs new file mode 100644 index 0000000000..8fd2af1ea2 --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/src/io.rs @@ -0,0 +1,17 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +use core::panic::PanicInfo; +use nrf52840::gpio::Pin; + +#[cfg(not(test))] +#[no_mangle] +#[panic_handler] +/// Panic handler +pub unsafe fn panic_fmt(_pi: &PanicInfo) -> ! { + // The nRF52840DK LEDs (see back of board) + let led_kernel_pin = &nrf52840::gpio::GPIOPin::new(Pin::P0_13); + let led = &mut kernel::hil::led::LedLow::new(led_kernel_pin); + kernel::debug::panic_blink_forever(&mut [led]) +} diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/src/main.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/src/main.rs new file mode 100644 index 0000000000..7afb19eaba --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-sha256/src/main.rs @@ -0,0 +1,309 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Tock kernel for the Nordic Semiconductor nRF52840 development kit (DK). + +#![no_std] +// Disable this attribute when documenting, as a workaround for +// https://github.com/rust-lang/rust/issues/62184. +#![cfg_attr(not(doc), no_main)] +#![deny(missing_docs)] + +use core::ptr::{addr_of, addr_of_mut}; + +use kernel::component::Component; +use kernel::hil::led::LedLow; +use kernel::hil::time::Counter; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::scheduler::round_robin::RoundRobinSched; +use kernel::{capabilities, create_capability, static_init}; +use nrf52840::gpio::Pin; +use nrf52840::interrupt_service::Nrf52840DefaultPeripherals; +use nrf52_components::{UartChannel, UartPins}; + +// The nRF52840DK LEDs (see back of board) +const LED1_PIN: Pin = Pin::P0_13; +const LED2_PIN: Pin = Pin::P0_14; +const LED3_PIN: Pin = Pin::P0_15; +const LED4_PIN: Pin = Pin::P0_16; + +const BUTTON_RST_PIN: Pin = Pin::P0_18; + +const UART_RTS: Option = Some(Pin::P0_05); +const UART_TXD: Pin = Pin::P0_06; +const UART_CTS: Option = Some(Pin::P0_07); +const UART_RXD: Pin = Pin::P0_08; + +/// Debug Writer +pub mod io; + +// State for loading and holding applications. +// How should the kernel respond when a process faults. +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; + +// Number of concurrent processes this platform supports. +const NUM_PROCS: usize = 8; + +static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = + [None; NUM_PROCS]; + +static mut CHIP: Option<&'static nrf52840::chip::NRF52> = None; + +/// Dummy buffer that causes the linker to reserve enough space for the stack. +#[no_mangle] +#[link_section = ".stack_buffer"] +pub static mut STACK_MEMORY: [u8; 0x2000] = [0; 0x2000]; + +//------------------------------------------------------------------------------ +// SYSCALL DRIVER TYPE DEFINITIONS +//------------------------------------------------------------------------------ + +type AlarmDriver = components::alarm::AlarmDriverComponentType>; + +/// Supported drivers by the platform +pub struct Platform { + console: &'static capsules_core::console::Console<'static>, + led: &'static capsules_core::led::LedDriver< + 'static, + kernel::hil::led::LedLow<'static, nrf52840::gpio::GPIOPin<'static>>, + 4, + >, + alarm: &'static AlarmDriver, + scheduler: &'static RoundRobinSched<'static>, + systick: cortexm4::systick::SysTick, +} + +impl SyscallDriverLookup for Platform { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + _ => f(None), + } + } +} + +/// This is in a separate, inline(never) function so that its stack frame is +/// removed when this function returns. Otherwise, the stack space used for +/// these static_inits is wasted. +#[inline(never)] +unsafe fn create_peripherals() -> &'static mut Nrf52840DefaultPeripherals<'static> { + let ieee802154_ack_buf = static_init!( + [u8; nrf52840::ieee802154_radio::ACK_BUF_SIZE], + [0; nrf52840::ieee802154_radio::ACK_BUF_SIZE] + ); + // Initialize chip peripheral drivers + let nrf52840_peripherals = static_init!( + Nrf52840DefaultPeripherals, + Nrf52840DefaultPeripherals::new(ieee802154_ack_buf) + ); + + nrf52840_peripherals +} + +impl KernelResources>> + for Platform +{ + type SyscallDriverLookup = Self; + type SyscallFilter = (); + type ProcessFault = (); + type Scheduler = RoundRobinSched<'static>; + type SchedulerTimer = cortexm4::systick::SysTick; + type WatchDog = (); + type ContextSwitchCallback = (); + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + &() + } + fn process_fault(&self) -> &Self::ProcessFault { + &() + } + fn scheduler(&self) -> &Self::Scheduler { + self.scheduler + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + &self.systick + } + fn watchdog(&self) -> &Self::WatchDog { + &() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + &() + } +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + //-------------------------------------------------------------------------- + // INITIAL SETUP + //-------------------------------------------------------------------------- + + // Apply errata fixes and enable interrupts. + nrf52840::init(); + + // Set up peripheral drivers. Called in separate function to reduce stack + // usage. + let nrf52840_peripherals = create_peripherals(); + + // Set up circular peripheral dependencies. + nrf52840_peripherals.init(); + let base_peripherals = &nrf52840_peripherals.nrf52; + + // Choose the channel for serial output. This board can be configured to use + // either the Segger RTT channel or via UART with traditional TX/RX GPIO + // pins. + let uart_channel = UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD)); + + // Setup space to store the core kernel data structure. + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); + + // Create (and save for panic debugging) a chip object to setup low-level + // resources (e.g. MPU, systick). + let chip = static_init!( + nrf52840::chip::NRF52, + nrf52840::chip::NRF52::new(nrf52840_peripherals) + ); + CHIP = Some(chip); + + // Do nRF configuration and setup. This is shared code with other nRF-based + // platforms. + nrf52_components::startup::NrfStartupComponent::new( + false, + BUTTON_RST_PIN, + nrf52840::uicr::Regulator0Output::DEFAULT, + &base_peripherals.nvmc, + ) + .finalize(()); + + //-------------------------------------------------------------------------- + // CAPABILITIES + //-------------------------------------------------------------------------- + + // Create capabilities that the board needs to call certain protected kernel + // functions. + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + //-------------------------------------------------------------------------- + // LEDs + //-------------------------------------------------------------------------- + + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( + LedLow<'static, nrf52840::gpio::GPIOPin>, + LedLow::new(&nrf52840_peripherals.gpio_port[LED1_PIN]), + LedLow::new(&nrf52840_peripherals.gpio_port[LED2_PIN]), + LedLow::new(&nrf52840_peripherals.gpio_port[LED3_PIN]), + LedLow::new(&nrf52840_peripherals.gpio_port[LED4_PIN]), + )); + + //-------------------------------------------------------------------------- + // TIMER + //-------------------------------------------------------------------------- + + let rtc = &base_peripherals.rtc; + let _ = rtc.start(); + let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) + .finalize(components::alarm_mux_component_static!(nrf52840::rtc::Rtc)); + let alarm = components::alarm::AlarmDriverComponent::new( + board_kernel, + capsules_core::alarm::DRIVER_NUM, + mux_alarm, + ) + .finalize(components::alarm_component_static!(nrf52840::rtc::Rtc)); + + //-------------------------------------------------------------------------- + // UART & CONSOLE & DEBUG + //-------------------------------------------------------------------------- + + let uart_channel = nrf52_components::UartChannelComponent::new( + uart_channel, + mux_alarm, + &base_peripherals.uarte0, + ) + .finalize(nrf52_components::uart_channel_component_static!( + nrf52840::rtc::Rtc + )); + + // Virtualize the UART channel for the console and for kernel debug. + let uart_mux = components::console::UartMuxComponent::new(uart_channel, 115200) + .finalize(components::uart_mux_component_static!()); + + // Setup the serial console for userspace. + let console = components::console::ConsoleComponent::new( + board_kernel, + capsules_core::console::DRIVER_NUM, + uart_mux, + ) + .finalize(components::console_component_static!()); + + //-------------------------------------------------------------------------- + // NRF CLOCK SETUP + //-------------------------------------------------------------------------- + + nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); + + //-------------------------------------------------------------------------- + // Credential Checking + //-------------------------------------------------------------------------- + + // Create the software-based SHA engine. + let sha = components::sha::ShaSoftware256Component::new() + .finalize(components::sha_software_256_component_static!()); + + // Create the credential checker. + let checking_policy = components::appid::checker_sha::AppCheckerSha256Component::new(sha) + .finalize(components::app_checker_sha256_component_static!()); + + // Create the AppID assigner. + let assigner = components::appid::assigner_name::AppIdAssignerNamesComponent::new() + .finalize(components::appid_assigner_names_component_static!()); + + // Create the process checking machine. + let checker = components::appid::checker::ProcessCheckerMachineComponent::new(checking_policy) + .finalize(components::process_checker_machine_component_static!()); + + // Create and start the asynchronous process loader. + let _loader = components::loader::sequential::ProcessLoaderSequentialComponent::new( + checker, + &mut *addr_of_mut!(PROCESSES), + board_kernel, + chip, + &FAULT_RESPONSE, + assigner, + ) + .finalize(components::process_loader_sequential_component_static!( + nrf52840::chip::NRF52, + NUM_PROCS + )); + + //-------------------------------------------------------------------------- + // PLATFORM SETUP, SCHEDULER, AND START KERNEL LOOP + //-------------------------------------------------------------------------- + + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); + + let platform = Platform { + console, + led, + alarm, + scheduler, + systick: cortexm4::systick::SysTick::new_with_calibration(64000000), + }; + + board_kernel.kernel_loop( + &platform, + chip, + None::<&kernel::ipc::IPC<0>>, + &main_loop_capability, + ); +} diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/Cargo.toml b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/Cargo.toml new file mode 100644 index 0000000000..7e5251d3e0 --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/Cargo.toml @@ -0,0 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +[package] +name = "nrf52840dk-test-appid-tbf" +version.workspace = true +authors.workspace = true +build = "../../../build.rs" +edition.workspace = true + +[dependencies] +components = { path = "../../../components" } +cortexm4 = { path = "../../../../arch/cortex-m4" } +kernel = { path = "../../../../kernel" } +nrf52840 = { path = "../../../../chips/nrf52840" } +nrf52_components = { path = "../../../nordic/nrf52_components" } + +capsules-core = { path = "../../../../capsules/core" } +capsules-extra = { path = "../../../../capsules/extra" } +capsules-system = { path = "../../../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/Makefile b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/Makefile new file mode 100644 index 0000000000..6008df0d70 --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/Makefile @@ -0,0 +1,9 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +TARGET=thumbv7em-none-eabi +PLATFORM=nrf52840dk-test-appid-tbf + +include ../../../Makefile.common +include ../nrf52840dk.mk diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/README.md b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/README.md new file mode 100644 index 0000000000..768cc142d8 --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/README.md @@ -0,0 +1,4 @@ +nRF52840-DK TBF AppID Test Board +================================ + +This is a minimal kernel for testing with setting `ShortId`s from TBF headers. diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/layout.ld b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/layout.ld new file mode 100644 index 0000000000..417a58fe7d --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/layout.ld @@ -0,0 +1,6 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + +INCLUDE ../../../nordic/nrf52840_chip_layout.ld +INCLUDE ../../../kernel_layout.ld diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/src/io.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/src/io.rs new file mode 100644 index 0000000000..8fd2af1ea2 --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/src/io.rs @@ -0,0 +1,17 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +use core::panic::PanicInfo; +use nrf52840::gpio::Pin; + +#[cfg(not(test))] +#[no_mangle] +#[panic_handler] +/// Panic handler +pub unsafe fn panic_fmt(_pi: &PanicInfo) -> ! { + // The nRF52840DK LEDs (see back of board) + let led_kernel_pin = &nrf52840::gpio::GPIOPin::new(Pin::P0_13); + let led = &mut kernel::hil::led::LedLow::new(led_kernel_pin); + kernel::debug::panic_blink_forever(&mut [led]) +} diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/src/main.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/src/main.rs new file mode 100644 index 0000000000..eb4de8875a --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-appid-tbf/src/main.rs @@ -0,0 +1,350 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Tock kernel for the Nordic Semiconductor nRF52840 development kit (DK). + +#![no_std] +// Disable this attribute when documenting, as a workaround for +// https://github.com/rust-lang/rust/issues/62184. +#![cfg_attr(not(doc), no_main)] +#![deny(missing_docs)] + +use core::ptr::{addr_of, addr_of_mut}; + +use kernel::component::Component; +use kernel::hil::led::LedLow; +use kernel::hil::time::Counter; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::process::ProcessLoadingAsync; +use kernel::scheduler::round_robin::RoundRobinSched; +use kernel::{capabilities, create_capability, static_init}; +use nrf52840::gpio::Pin; +use nrf52840::interrupt_service::Nrf52840DefaultPeripherals; +use nrf52_components::{UartChannel, UartPins}; + +// The nRF52840DK LEDs (see back of board) +const LED1_PIN: Pin = Pin::P0_13; +const LED2_PIN: Pin = Pin::P0_14; +const LED3_PIN: Pin = Pin::P0_15; +const LED4_PIN: Pin = Pin::P0_16; + +const BUTTON_RST_PIN: Pin = Pin::P0_18; + +const UART_RTS: Option = Some(Pin::P0_05); +const UART_TXD: Pin = Pin::P0_06; +const UART_CTS: Option = Some(Pin::P0_07); +const UART_RXD: Pin = Pin::P0_08; + +/// Debug Writer +pub mod io; + +// State for loading and holding applications. +// How should the kernel respond when a process faults. +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; + +// Number of concurrent processes this platform supports. +const NUM_PROCS: usize = 8; + +static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = + [None; NUM_PROCS]; + +static mut CHIP: Option<&'static nrf52840::chip::NRF52> = None; + +/// Dummy buffer that causes the linker to reserve enough space for the stack. +#[no_mangle] +#[link_section = ".stack_buffer"] +pub static mut STACK_MEMORY: [u8; 0x2000] = [0; 0x2000]; + +//------------------------------------------------------------------------------ +// SYSCALL DRIVER TYPE DEFINITIONS +//------------------------------------------------------------------------------ + +type AlarmDriver = components::alarm::AlarmDriverComponentType>; + +/// Supported drivers by the platform +pub struct Platform { + console: &'static capsules_core::console::Console<'static>, + led: &'static capsules_core::led::LedDriver< + 'static, + kernel::hil::led::LedLow<'static, nrf52840::gpio::GPIOPin<'static>>, + 4, + >, + alarm: &'static AlarmDriver, + scheduler: &'static RoundRobinSched<'static>, + systick: cortexm4::systick::SysTick, +} + +impl SyscallDriverLookup for Platform { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + _ => f(None), + } + } +} + +/// This is in a separate, inline(never) function so that its stack frame is +/// removed when this function returns. Otherwise, the stack space used for +/// these static_inits is wasted. +#[inline(never)] +unsafe fn create_peripherals() -> &'static mut Nrf52840DefaultPeripherals<'static> { + let ieee802154_ack_buf = static_init!( + [u8; nrf52840::ieee802154_radio::ACK_BUF_SIZE], + [0; nrf52840::ieee802154_radio::ACK_BUF_SIZE] + ); + // Initialize chip peripheral drivers + let nrf52840_peripherals = static_init!( + Nrf52840DefaultPeripherals, + Nrf52840DefaultPeripherals::new(ieee802154_ack_buf) + ); + + nrf52840_peripherals +} + +impl KernelResources>> + for Platform +{ + type SyscallDriverLookup = Self; + type SyscallFilter = (); + type ProcessFault = (); + type Scheduler = RoundRobinSched<'static>; + type SchedulerTimer = cortexm4::systick::SysTick; + type WatchDog = (); + type ContextSwitchCallback = (); + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + &() + } + fn process_fault(&self) -> &Self::ProcessFault { + &() + } + fn scheduler(&self) -> &Self::Scheduler { + self.scheduler + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + &self.systick + } + fn watchdog(&self) -> &Self::WatchDog { + &() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + &() + } +} + +impl kernel::process::ProcessLoadingAsyncClient for Platform { + fn process_loaded(&self, _result: Result<(), kernel::process::ProcessLoadError>) {} + + fn process_loading_finished(&self) { + kernel::debug!("Processes Loaded:"); + + unsafe { + for (i, proc) in PROCESSES.iter().enumerate() { + proc.map(|p| { + kernel::debug!("[{}] {}", i, p.get_process_name()); + kernel::debug!(" ShortId: {}", p.short_app_id()); + }); + } + } + } +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + //-------------------------------------------------------------------------- + // INITIAL SETUP + //-------------------------------------------------------------------------- + + // Apply errata fixes and enable interrupts. + nrf52840::init(); + + // Set up peripheral drivers. Called in separate function to reduce stack + // usage. + let nrf52840_peripherals = create_peripherals(); + + // Set up circular peripheral dependencies. + nrf52840_peripherals.init(); + let base_peripherals = &nrf52840_peripherals.nrf52; + + // Choose the channel for serial output. This board can be configured to use + // either the Segger RTT channel or via UART with traditional TX/RX GPIO + // pins. + let uart_channel = UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD)); + + // Setup space to store the core kernel data structure. + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); + + // Create (and save for panic debugging) a chip object to setup low-level + // resources (e.g. MPU, systick). + let chip = static_init!( + nrf52840::chip::NRF52, + nrf52840::chip::NRF52::new(nrf52840_peripherals) + ); + CHIP = Some(chip); + + // Do nRF configuration and setup. This is shared code with other nRF-based + // platforms. + nrf52_components::startup::NrfStartupComponent::new( + false, + BUTTON_RST_PIN, + nrf52840::uicr::Regulator0Output::DEFAULT, + &base_peripherals.nvmc, + ) + .finalize(()); + + //-------------------------------------------------------------------------- + // CAPABILITIES + //-------------------------------------------------------------------------- + + // Create capabilities that the board needs to call certain protected kernel + // functions. + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + //-------------------------------------------------------------------------- + // LEDs + //-------------------------------------------------------------------------- + + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( + LedLow<'static, nrf52840::gpio::GPIOPin>, + LedLow::new(&nrf52840_peripherals.gpio_port[LED1_PIN]), + LedLow::new(&nrf52840_peripherals.gpio_port[LED2_PIN]), + LedLow::new(&nrf52840_peripherals.gpio_port[LED3_PIN]), + LedLow::new(&nrf52840_peripherals.gpio_port[LED4_PIN]), + )); + + //-------------------------------------------------------------------------- + // TIMER + //-------------------------------------------------------------------------- + + let rtc = &base_peripherals.rtc; + let _ = rtc.start(); + let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) + .finalize(components::alarm_mux_component_static!(nrf52840::rtc::Rtc)); + let alarm = components::alarm::AlarmDriverComponent::new( + board_kernel, + capsules_core::alarm::DRIVER_NUM, + mux_alarm, + ) + .finalize(components::alarm_component_static!(nrf52840::rtc::Rtc)); + + //-------------------------------------------------------------------------- + // UART & CONSOLE & DEBUG + //-------------------------------------------------------------------------- + + let uart_channel = nrf52_components::UartChannelComponent::new( + uart_channel, + mux_alarm, + &base_peripherals.uarte0, + ) + .finalize(nrf52_components::uart_channel_component_static!( + nrf52840::rtc::Rtc + )); + + // Virtualize the UART channel for the console and for kernel debug. + let uart_mux = components::console::UartMuxComponent::new(uart_channel, 115200) + .finalize(components::uart_mux_component_static!()); + + // Setup the serial console for userspace. + let console = components::console::ConsoleComponent::new( + board_kernel, + capsules_core::console::DRIVER_NUM, + uart_mux, + ) + .finalize(components::console_component_static!()); + + // Tool for displaying information about processes. + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); + + // Create the process console, an interactive terminal for managing + // processes. + let pconsole = components::process_console::ProcessConsoleComponent::new( + board_kernel, + uart_mux, + mux_alarm, + process_printer, + Some(cortexm4::support::reset), + ) + .finalize(components::process_console_component_static!( + nrf52840::rtc::Rtc<'static> + )); + + // Create the debugger object that handles calls to `debug!()`. + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); + + //-------------------------------------------------------------------------- + // NRF CLOCK SETUP + //-------------------------------------------------------------------------- + + nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); + + //-------------------------------------------------------------------------- + // Credential Checking + //-------------------------------------------------------------------------- + + // Create the credential checker. + let checking_policy = components::appid::checker_null::AppCheckerNullComponent::new() + .finalize(components::app_checker_null_component_static!()); + + // Create the AppID assigner. + let assigner = components::appid::assigner_tbf::AppIdAssignerTbfHeaderComponent::new() + .finalize(components::appid_assigner_tbf_header_component_static!()); + + // Create the process checking machine. + let checker = components::appid::checker::ProcessCheckerMachineComponent::new(checking_policy) + .finalize(components::process_checker_machine_component_static!()); + + // Create and start the asynchronous process loader. + let loader = components::loader::sequential::ProcessLoaderSequentialComponent::new( + checker, + &mut *addr_of_mut!(PROCESSES), + board_kernel, + chip, + &FAULT_RESPONSE, + assigner, + ) + .finalize(components::process_loader_sequential_component_static!( + nrf52840::chip::NRF52, + NUM_PROCS + )); + + //-------------------------------------------------------------------------- + // PLATFORM SETUP, SCHEDULER, AND START KERNEL LOOP + //-------------------------------------------------------------------------- + + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); + + let platform = static_init!( + Platform, + Platform { + console, + led, + alarm, + scheduler, + systick: cortexm4::systick::SysTick::new_with_calibration(64000000), + } + ); + loader.set_client(platform); + + let _ = pconsole.start(); + + board_kernel.kernel_loop( + platform, + chip, + None::<&kernel::ipc::IPC<0>>, + &main_loop_capability, + ); +} diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/Cargo.toml b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/Cargo.toml new file mode 100644 index 0000000000..702256da67 --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/Cargo.toml @@ -0,0 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +[package] +name = "nrf52840dk-test-kernel" +version.workspace = true +authors.workspace = true +build = "../../../build.rs" +edition.workspace = true + +[dependencies] +components = { path = "../../../components" } +cortexm4 = { path = "../../../../arch/cortex-m4" } +kernel = { path = "../../../../kernel" } +nrf52840 = { path = "../../../../chips/nrf52840" } +nrf52_components = { path = "../../../nordic/nrf52_components" } + +capsules-core = { path = "../../../../capsules/core" } +capsules-extra = { path = "../../../../capsules/extra" } +capsules-system = { path = "../../../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/Makefile b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/Makefile new file mode 100644 index 0000000000..c97b7a8ed8 --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/Makefile @@ -0,0 +1,9 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +TARGET=thumbv7em-none-eabi +PLATFORM=nrf52840dk-test-kernel + +include ../../../Makefile.common +include ../nrf52840dk.mk diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/README.md b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/README.md new file mode 100644 index 0000000000..76b0858d04 --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/README.md @@ -0,0 +1,4 @@ +nRF52840-DK Kernel Tests Test Board +=================================== + +This is a minimal kernel for running kernel tests. diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/layout.ld b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/layout.ld new file mode 100644 index 0000000000..417a58fe7d --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/layout.ld @@ -0,0 +1,6 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + +INCLUDE ../../../nordic/nrf52840_chip_layout.ld +INCLUDE ../../../kernel_layout.ld diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/io.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/io.rs new file mode 100644 index 0000000000..229a830248 --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/io.rs @@ -0,0 +1,117 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use core::fmt::Write; +use core::panic::PanicInfo; +use cortexm4; +use kernel::debug; +use kernel::debug::IoWrite; +use kernel::hil::led; +use kernel::hil::uart; +use kernel::hil::uart::Configure; +use nrf52840::gpio::Pin; +use nrf52840::uart::{Uarte, UARTE0_BASE}; + +use crate::CHIP; +use crate::PROCESSES; +use crate::PROCESS_PRINTER; + +enum Writer { + WriterUart(/* initialized */ bool), + WriterRtt(&'static capsules_extra::segger_rtt::SeggerRttMemory<'static>), +} + +static mut WRITER: Writer = Writer::WriterUart(false); + +// Wait a fixed number of cycles to avoid missing characters over the RTT console +fn wait() { + for _ in 0..1000 { + cortexm4::support::nop(); + } +} + +/// Set the RTT memory buffer used to output panic messages. +pub unsafe fn set_rtt_memory( + rtt_memory: &'static capsules_extra::segger_rtt::SeggerRttMemory<'static>, +) { + WRITER = Writer::WriterRtt(rtt_memory); +} + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> ::core::fmt::Result { + self.write(s.as_bytes()); + Ok(()) + } +} + +impl IoWrite for Writer { + fn write(&mut self, buf: &[u8]) -> usize { + match self { + Writer::WriterUart(ref mut initialized) => { + // Here, we create a second instance of the Uarte struct. + // This is okay because we only call this during a panic, and + // we will never actually process the interrupts + let uart = Uarte::new(UARTE0_BASE); + if !*initialized { + *initialized = true; + let _ = uart.configure(uart::Parameters { + baud_rate: 115200, + stop_bits: uart::StopBits::One, + parity: uart::Parity::None, + hw_flow_control: false, + width: uart::Width::Eight, + }); + } + for &c in buf { + unsafe { + uart.send_byte(c); + } + while !uart.tx_ready() {} + } + } + Writer::WriterRtt(rtt_memory) => { + let up_buffer = unsafe { &*rtt_memory.get_up_buffer_ptr() }; + let buffer_len = up_buffer.length.get(); + let buffer = unsafe { + core::slice::from_raw_parts_mut( + up_buffer.buffer.get() as *mut u8, + buffer_len as usize, + ) + }; + + let mut write_position = up_buffer.write_position.get(); + + for &c in buf { + buffer[write_position as usize] = c; + write_position = (write_position + 1) % buffer_len; + up_buffer.write_position.set(write_position); + wait(); + } + } + }; + buf.len() + } +} + +#[cfg(not(test))] +#[no_mangle] +#[panic_handler] +/// Panic handler +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { + // The nRF52840DK LEDs (see back of board) + + use core::ptr::{addr_of, addr_of_mut}; + let led_kernel_pin = &nrf52840::gpio::GPIOPin::new(Pin::P0_13); + let led = &mut led::LedLow::new(led_kernel_pin); + let writer = &mut *addr_of_mut!(WRITER); + debug::panic( + &mut [led], + writer, + pi, + &cortexm4::support::nop, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), + ) +} diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/main.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/main.rs new file mode 100644 index 0000000000..d4267e76c8 --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/main.rs @@ -0,0 +1,274 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Tock kernel for the Nordic Semiconductor nRF52840 development kit (DK). + +#![no_std] +// Disable this attribute when documenting, as a workaround for +// https://github.com/rust-lang/rust/issues/62184. +#![cfg_attr(not(doc), no_main)] +#![deny(missing_docs)] + +use capsules_core::test::capsule_test::{CapsuleTestClient, CapsuleTestError}; +use core::cell::Cell; +use core::ptr::addr_of; +use kernel::component::Component; +use kernel::hil::time::Counter; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::scheduler::round_robin::RoundRobinSched; +use kernel::utilities::cells::NumericCellExt; +use kernel::{capabilities, create_capability, static_init}; +use nrf52840::chip::Nrf52DefaultPeripherals; +use nrf52840::gpio::Pin; +use nrf52840::interrupt_service::Nrf52840DefaultPeripherals; +use nrf52_components::{UartChannel, UartPins}; + +mod test; + +const BUTTON_RST_PIN: Pin = Pin::P0_18; + +const UART_RTS: Option = Some(Pin::P0_05); +const UART_TXD: Pin = Pin::P0_06; +const UART_CTS: Option = Some(Pin::P0_07); +const UART_RXD: Pin = Pin::P0_08; + +/// Debug Writer +pub mod io; + +// Number of concurrent processes this platform supports. +const NUM_PROCS: usize = 0; + +static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = + [None; NUM_PROCS]; + +static mut CHIP: Option<&'static nrf52840::chip::NRF52> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; + +/// Dummy buffer that causes the linker to reserve enough space for the stack. +#[no_mangle] +#[link_section = ".stack_buffer"] +pub static mut STACK_MEMORY: [u8; 0x2000] = [0; 0x2000]; + +//------------------------------------------------------------------------------ +// SYSCALL DRIVER TYPE DEFINITIONS +//------------------------------------------------------------------------------ + +/// Supported drivers by the platform +pub struct Platform { + scheduler: &'static RoundRobinSched<'static>, + systick: cortexm4::systick::SysTick, +} + +impl SyscallDriverLookup for Platform { + fn with_driver(&self, _driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + f(None) + } +} + +//------------------------------------------------------------------------------ +// TEST LAUNCHER FOR RUNNING TESTS +//------------------------------------------------------------------------------ + +struct TestLauncher { + test_index: Cell, + peripherals: &'static Nrf52DefaultPeripherals<'static>, +} +impl TestLauncher { + fn new(peripherals: &'static Nrf52DefaultPeripherals<'static>) -> Self { + Self { + test_index: Cell::new(0), + peripherals, + } + } + + fn next(&'static self) { + let index = self.test_index.get(); + self.test_index.increment(); + match index { + 0 => unsafe { test::sha256_test::run_sha256(self) }, + 1 => unsafe { test::hmac_sha256_test::run_hmacsha256(self) }, + 2 => unsafe { test::siphash24_test::run_siphash24(self) }, + 3 => unsafe { test::aes_test::run_aes128_ctr(&self.peripherals.ecb, self) }, + 4 => unsafe { test::aes_test::run_aes128_cbc(&self.peripherals.ecb, self) }, + 5 => unsafe { test::aes_test::run_aes128_ecb(&self.peripherals.ecb, self) }, + _ => kernel::debug!("All tests finished."), + } + } +} +impl CapsuleTestClient for TestLauncher { + fn done(&'static self, _result: Result<(), CapsuleTestError>) { + self.next(); + } +} + +/// This is in a separate, inline(never) function so that its stack frame is +/// removed when this function returns. Otherwise, the stack space used for +/// these static_inits is wasted. +#[inline(never)] +unsafe fn create_peripherals() -> &'static mut Nrf52840DefaultPeripherals<'static> { + let ieee802154_ack_buf = static_init!( + [u8; nrf52840::ieee802154_radio::ACK_BUF_SIZE], + [0; nrf52840::ieee802154_radio::ACK_BUF_SIZE] + ); + // Initialize chip peripheral drivers + let nrf52840_peripherals = static_init!( + Nrf52840DefaultPeripherals, + Nrf52840DefaultPeripherals::new(ieee802154_ack_buf) + ); + + nrf52840_peripherals +} + +impl KernelResources>> + for Platform +{ + type SyscallDriverLookup = Self; + type SyscallFilter = (); + type ProcessFault = (); + type Scheduler = RoundRobinSched<'static>; + type SchedulerTimer = cortexm4::systick::SysTick; + type WatchDog = (); + type ContextSwitchCallback = (); + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + &() + } + fn process_fault(&self) -> &Self::ProcessFault { + &() + } + fn scheduler(&self) -> &Self::Scheduler { + self.scheduler + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + &self.systick + } + fn watchdog(&self) -> &Self::WatchDog { + &() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + &() + } +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + //-------------------------------------------------------------------------- + // INITIAL SETUP + //-------------------------------------------------------------------------- + + // Apply errata fixes and enable interrupts. + nrf52840::init(); + + // Set up peripheral drivers. Called in separate function to reduce stack + // usage. + let nrf52840_peripherals = create_peripherals(); + + // Set up circular peripheral dependencies. + nrf52840_peripherals.init(); + let base_peripherals = &nrf52840_peripherals.nrf52; + + // Setup space to store the core kernel data structure. + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); + + // Create (and save for panic debugging) a chip object to setup low-level + // resources (e.g. MPU, systick). + let chip = static_init!( + nrf52840::chip::NRF52, + nrf52840::chip::NRF52::new(nrf52840_peripherals) + ); + CHIP = Some(chip); + + // Do nRF configuration and setup. This is shared code with other nRF-based + // platforms. + nrf52_components::startup::NrfStartupComponent::new( + false, + BUTTON_RST_PIN, + nrf52840::uicr::Regulator0Output::DEFAULT, + &base_peripherals.nvmc, + ) + .finalize(()); + + //-------------------------------------------------------------------------- + // CAPABILITIES + //-------------------------------------------------------------------------- + + // Create capabilities that the board needs to call certain protected kernel + // functions. + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + //-------------------------------------------------------------------------- + // TIMER + //-------------------------------------------------------------------------- + + let rtc = &base_peripherals.rtc; + let _ = rtc.start(); + let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) + .finalize(components::alarm_mux_component_static!(nrf52840::rtc::Rtc)); + + //-------------------------------------------------------------------------- + // UART & CONSOLE & DEBUG + //-------------------------------------------------------------------------- + + let uart_channel = nrf52_components::UartChannelComponent::new( + UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD)), + mux_alarm, + &base_peripherals.uarte0, + ) + .finalize(nrf52_components::uart_channel_component_static!( + nrf52840::rtc::Rtc + )); + + // Virtualize the UART channel for the console and for kernel debug. + let uart_mux = components::console::UartMuxComponent::new(uart_channel, 115200) + .finalize(components::uart_mux_component_static!()); + + // Create the debugger object that handles calls to `debug!()`. + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); + + //-------------------------------------------------------------------------- + // NRF CLOCK SETUP + //-------------------------------------------------------------------------- + + nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); + + //-------------------------------------------------------------------------- + // PLATFORM AND SCHEDULER + //-------------------------------------------------------------------------- + + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); + + let platform = Platform { + scheduler, + systick: cortexm4::systick::SysTick::new_with_calibration(64000000), + }; + + let test_launcher = static_init!(TestLauncher, TestLauncher::new(base_peripherals)); + + //-------------------------------------------------------------------------- + // TESTS + //-------------------------------------------------------------------------- + + test_launcher.next(); + + //-------------------------------------------------------------------------- + // KERNEL LOOP + //-------------------------------------------------------------------------- + + board_kernel.kernel_loop( + &platform, + chip, + None::<&kernel::ipc::IPC<0>>, + &main_loop_capability, + ); +} diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/test/aes_test.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/test/aes_test.rs new file mode 100644 index 0000000000..75931ca96c --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/test/aes_test.rs @@ -0,0 +1,105 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Test that AES (either CTR or CBC mode) is working properly. +//! +//! To test CBC mode, add the following line to the imix boot sequence: +//! ``` +//! test::aes_test::run_aes128_cbc(); +//! ``` +//! You should see the following output: +//! ``` +//! aes_test passed (CBC Enc Src/Dst) +//! aes_test passed (CBC Dec Src/Dst) +//! aes_test passed (CBC Enc In-place) +//! aes_test passed (CBC Dec In-place) +//! ``` +//! To test CTR mode, add the following line to the imix boot sequence: +//! ``` +//! test::aes_test::run_aes128_ctr(); +//! ``` +//! You should see the following output: +//! ``` +//! aes_test CTR passed: (CTR Enc Ctr Src/Dst) +//! aes_test CTR passed: (CTR Dec Ctr Src/Dst) +//! ``` + +use capsules_core::test::capsule_test::{CapsuleTest, CapsuleTestClient}; +use capsules_extra::test::aes::TestAes128Cbc; +use capsules_extra::test::aes::TestAes128Ctr; +use capsules_extra::test::aes::TestAes128Ecb; +use kernel::hil::symmetric_encryption::{AES128, AES128_BLOCK_SIZE, AES128_KEY_SIZE}; +use kernel::static_init; +use nrf52840::aes::AesECB; + +pub unsafe fn run_aes128_ctr(aes: &'static AesECB, client: &'static dyn CapsuleTestClient) { + let t = static_init_test_ctr(aes, client); + aes.set_client(t); + + t.run(); +} + +pub unsafe fn run_aes128_cbc(aes: &'static AesECB, client: &'static dyn CapsuleTestClient) { + let t = static_init_test_cbc(aes, client); + aes.set_client(t); + + t.run(); +} + +pub unsafe fn run_aes128_ecb(aes: &'static AesECB, client: &'static dyn CapsuleTestClient) { + let t = static_init_test_ecb(aes, client); + aes.set_client(t); + + t.run(); +} + +unsafe fn static_init_test_ctr( + aes: &'static AesECB, + client: &'static dyn CapsuleTestClient, +) -> &'static TestAes128Ctr<'static, AesECB<'static>> { + let source = static_init!([u8; 4 * AES128_BLOCK_SIZE], [0; 4 * AES128_BLOCK_SIZE]); + let data = static_init!([u8; 6 * AES128_BLOCK_SIZE], [0; 6 * AES128_BLOCK_SIZE]); + let key = static_init!([u8; AES128_KEY_SIZE], [0; AES128_KEY_SIZE]); + let iv = static_init!([u8; AES128_BLOCK_SIZE], [0; AES128_BLOCK_SIZE]); + + let test = static_init!( + TestAes128Ctr<'static, AesECB>, + TestAes128Ctr::new(aes, key, iv, source, data, true) + ); + test.set_client(client); + test +} + +unsafe fn static_init_test_cbc( + aes: &'static AesECB, + client: &'static dyn CapsuleTestClient, +) -> &'static TestAes128Cbc<'static, AesECB<'static>> { + let source = static_init!([u8; 4 * AES128_BLOCK_SIZE], [0; 4 * AES128_BLOCK_SIZE]); + let data = static_init!([u8; 6 * AES128_BLOCK_SIZE], [0; 6 * AES128_BLOCK_SIZE]); + let key = static_init!([u8; AES128_KEY_SIZE], [0; AES128_KEY_SIZE]); + let iv = static_init!([u8; AES128_BLOCK_SIZE], [0; AES128_BLOCK_SIZE]); + + let test = static_init!( + TestAes128Cbc<'static, AesECB>, + TestAes128Cbc::new(aes, key, iv, source, data, false) + ); + test.set_client(client); + test +} + +unsafe fn static_init_test_ecb( + aes: &'static AesECB, + client: &'static dyn CapsuleTestClient, +) -> &'static TestAes128Ecb<'static, AesECB<'static>> { + let source = static_init!([u8; 4 * AES128_BLOCK_SIZE], [0; 4 * AES128_BLOCK_SIZE]); + let data = static_init!([u8; 6 * AES128_BLOCK_SIZE], [0; 6 * AES128_BLOCK_SIZE]); + let key = static_init!([u8; AES128_KEY_SIZE], [0; AES128_KEY_SIZE]); + + let test = static_init!( + TestAes128Ecb<'static, AesECB>, + TestAes128Ecb::new(aes, key, source, data, false) + ); + test.set_client(client); + test +} diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/test/hmac_sha256_test.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/test/hmac_sha256_test.rs new file mode 100644 index 0000000000..4e64823a38 --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/test/hmac_sha256_test.rs @@ -0,0 +1,60 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! This tests a software HMAC-SHA256 implementation. + +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::test::capsule_test::{CapsuleTest, CapsuleTestClient}; +use capsules_extra::hmac_sha256::HmacSha256Software; +use capsules_extra::sha256::Sha256Software; +use capsules_extra::test::hmac_sha256::TestHmacSha256; +use kernel::deferred_call::DeferredCallClient; +use kernel::static_init; + +pub unsafe fn run_hmacsha256(client: &'static dyn CapsuleTestClient) { + let t = static_init_test_hmacsha256(client); + t.run(); +} + +pub static mut DIGEST_DATA: [u8; 32] = [0; 32]; + +// Test from https://en.wikipedia.org/wiki/HMAC#Examples +pub static mut WIKI_STR: [u8; 43] = *b"The quick brown fox jumps over the lazy dog"; +pub static mut WIKI_KEY: [u8; 3] = *b"key"; +pub static mut WIKI_HMAC: [u8; 32] = [ + 0xf7, 0xbc, 0x83, 0xf4, 0x30, 0x53, 0x84, 0x24, 0xb1, 0x32, 0x98, 0xe6, 0xaa, 0x6f, 0xb1, 0x43, + 0xef, 0x4d, 0x59, 0xa1, 0x49, 0x46, 0x17, 0x59, 0x97, 0x47, 0x9d, 0xbc, 0x2d, 0x1a, 0x3c, 0xd8, +]; + +unsafe fn static_init_test_hmacsha256( + client: &'static dyn CapsuleTestClient, +) -> &'static TestHmacSha256 { + let sha256_hash_buf = static_init!([u8; 64], [0; 64]); + + let sha256 = static_init!(Sha256Software<'static>, Sha256Software::new()); + sha256.register(); + + let hmacsha256_verify_buf = static_init!([u8; 32], [0; 32]); + + let hmacsha256 = static_init!( + HmacSha256Software<'static, Sha256Software<'static>>, + HmacSha256Software::new(sha256, sha256_hash_buf, hmacsha256_verify_buf) + ); + kernel::hil::digest::Digest::set_client(sha256, hmacsha256); + + let test = static_init!( + TestHmacSha256, + TestHmacSha256::new( + hmacsha256, + &mut *addr_of_mut!(WIKI_KEY), + &mut *addr_of_mut!(WIKI_STR), + &mut *addr_of_mut!(DIGEST_DATA), + &*addr_of!(WIKI_HMAC) + ) + ); + test.set_client(client); + + test +} diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/test/mod.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/test/mod.rs new file mode 100644 index 0000000000..ef6c7b31df --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/test/mod.rs @@ -0,0 +1,8 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +pub(crate) mod aes_test; +pub(crate) mod hmac_sha256_test; +pub(crate) mod sha256_test; +pub(crate) mod siphash24_test; diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/test/sha256_test.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/test/sha256_test.rs new file mode 100644 index 0000000000..9c26b60ecd --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/test/sha256_test.rs @@ -0,0 +1,70 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! This tests a software SHA256 implementation. +//! +//! This test uses a deferred call (for callbacks). It tries to +//! hash 'hello world' and uses Digest::validate to check that the hash +//! is correct. +//! +//! The expected output is +//! Sha256Test: Verification result: Ok(true) +//! +//! This tests whether the SHA-256 hash of the string "hello hello +//! hello hello hello hello hello hello hello hello hello hello " +//! hashes correctly. This string is 12 repetitions of "hello ", so is +//! 72 bytes long. As SHA uses 64-byte/512 bit blocks, this verifies +//! that multi-block hashes work correctly. + +use core::ptr::addr_of_mut; + +use capsules_core::test::capsule_test::{CapsuleTest, CapsuleTestClient}; +use capsules_extra::sha256::Sha256Software; +use capsules_extra::test::sha256::TestSha256; +use kernel::static_init; + +pub unsafe fn run_sha256(client: &'static dyn CapsuleTestClient) { + let t = static_init_test_sha256(client); + t.run(); +} + +// // HSTRING is "hello world" and HHASH is the SHA-256 hash of this string. +// pub static mut HSTRING: [u8; 11] = *b"hello world"; + +// pub static mut HHASH: [u8; 32] = [ +// 0xB9, 0x4D, 0x27, 0xB9, 0x93, 0x4D, 0x3E, 0x08, 0xA5, 0x2E, 0x52, 0xD7, 0xDA, 0x7D, 0xAB, 0xFA, +// 0xC4, 0x84, 0xEF, 0xE3, 0x7A, 0x53, 0x80, 0xEE, 0x90, 0x88, 0xF7, 0xAC, 0xE2, 0xEF, 0xCD, 0xE9, +// ]; + +// LSTRING is 12 repetitions of "hello " (72 bytes long) and LHASH is +// the SHA-256 hash of this string. +pub static mut LSTRING: [u8; 72] = [0; 72]; +pub static mut LHASH: [u8; 32] = [ + 0x59, 0x42, 0xc3, 0x71, 0x6f, 0x02, 0x82, 0x89, 0x3f, 0xbe, 0x04, 0x9b, 0xa2, 0x0e, 0x56, 0x0e, + 0x45, 0x94, 0xd5, 0xee, 0x15, 0xcb, 0x8a, 0x1e, 0x28, 0x7c, 0x20, 0x12, 0xc2, 0xce, 0xb5, 0xa9, +]; + +unsafe fn static_init_test_sha256(client: &'static dyn CapsuleTestClient) -> &'static TestSha256 { + let sha = static_init!(Sha256Software<'static>, Sha256Software::new()); + kernel::deferred_call::DeferredCallClient::register(sha); + let bytes = b"hello "; + for i in 0..12 { + for j in 0..6 { + LSTRING[i * 6 + j] = bytes[j]; + } + } + // We expect LSTRING to hash to LHASH, so final argument is true + let test = static_init!( + TestSha256, + TestSha256::new( + sha, + &mut *addr_of_mut!(LSTRING), + &mut *addr_of_mut!(LHASH), + true + ) + ); + test.set_client(client); + + test +} diff --git a/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/test/siphash24_test.rs b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/test/siphash24_test.rs new file mode 100644 index 0000000000..7a11330e2e --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk-test-kernel/src/test/siphash24_test.rs @@ -0,0 +1,52 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! This tests a software SipHash24 implementation. To run this test, +//! add this line to the boot sequence: +//! ``` +//! test::siphash24_test::run_siphash24(); +//! ``` + +use core::ptr::addr_of_mut; + +use capsules_core::test::capsule_test::{CapsuleTest, CapsuleTestClient}; +use capsules_extra::sip_hash::SipHasher24; +use capsules_extra::test::siphash24::TestSipHash24; +use kernel::static_init; + +pub unsafe fn run_siphash24(client: &'static dyn CapsuleTestClient) { + let t = static_init_test_siphash24(client); + t.run(); +} + +pub static mut HSTRING: [u8; 15] = *b"tickv-super-key"; +pub static mut HBUF: [u8; 64] = [0; 64]; + +pub static mut HHASH: [u8; 8] = [0; 8]; +pub static mut CHASH: [u8; 8] = [0xd1, 0xdc, 0x3b, 0x92, 0xc2, 0x5a, 0x1b, 0x30]; + +unsafe fn static_init_test_siphash24( + client: &'static dyn CapsuleTestClient, +) -> &'static TestSipHash24 { + let sha = static_init!(SipHasher24<'static>, SipHasher24::new()); + kernel::deferred_call::DeferredCallClient::register(sha); + + // Copy to the 64 byte buffer because we always hash 64 bytes. + for i in 0..15 { + HBUF[i] = HSTRING[i]; + } + let test = static_init!( + TestSipHash24, + TestSipHash24::new( + sha, + &mut *addr_of_mut!(HBUF), + &mut *addr_of_mut!(HHASH), + &mut *addr_of_mut!(CHASH) + ) + ); + + test.set_client(client); + + test +} diff --git a/boards/configurations/nrf52840dk/nrf52840dk.mk b/boards/configurations/nrf52840dk/nrf52840dk.mk new file mode 100644 index 0000000000..d5a090c698 --- /dev/null +++ b/boards/configurations/nrf52840dk/nrf52840dk.mk @@ -0,0 +1,29 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +# Shared makefile for building the tock kernel for nRF test boards. + +TOCKLOADER=tockloader + +# Where in the SAM4L flash to load the kernel with `tockloader` +KERNEL_ADDRESS=0x00000 + +# Upload programs over uart with tockloader +ifdef PORT + TOCKLOADER_GENERAL_FLAGS += --port $(PORT) +endif + +# Default target for installing the kernel. +.PHONY: install +install: flash + +# Upload the kernel over JTAG +.PHONY: flash +flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin + $(TOCKLOADER) $(TOCKLOADER_GENERAL_FLAGS) flash --address $(KERNEL_ADDRESS) --board nrf52dk --jlink $< + +# Upload the kernel over JTAG using OpenOCD +.PHONY: flash-openocd +flash-openocd: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin + $(TOCKLOADER) $(TOCKLOADER_GENERAL_FLAGS) flash --address $(KERNEL_ADDRESS) --board nrf52dk --openocd $< diff --git a/boards/esp32-c3-devkitM-1/.cargo/config b/boards/esp32-c3-devkitM-1/.cargo/config deleted file mode 100644 index d2eca3121f..0000000000 --- a/boards/esp32-c3-devkitM-1/.cargo/config +++ /dev/null @@ -1,2 +0,0 @@ -[target.'cfg(target_arch = "riscv32")'] -runner = "./run.sh" diff --git a/boards/esp32-c3-devkitM-1/.cargo/config.toml b/boards/esp32-c3-devkitM-1/.cargo/config.toml new file mode 100644 index 0000000000..7efa9d922a --- /dev/null +++ b/boards/esp32-c3-devkitM-1/.cargo/config.toml @@ -0,0 +1,6 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[target.'cfg(target_arch = "riscv32")'] +runner = "./run.sh" diff --git a/boards/esp32-c3-devkitM-1/Cargo.toml b/boards/esp32-c3-devkitM-1/Cargo.toml index 3f3030a04a..7bf47d9dd9 100644 --- a/boards/esp32-c3-devkitM-1/Cargo.toml +++ b/boards/esp32-c3-devkitM-1/Cargo.toml @@ -1,14 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "esp32-c3-board" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] components = { path = "../components" } rv32i = { path = "../../arch/rv32i" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } esp32 = { path = "../../chips/esp32" } esp32-c3 = { path = "../../chips/esp32-c3" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/esp32-c3-devkitM-1/Makefile b/boards/esp32-c3-devkitM-1/Makefile index b67b32ef29..6283fcff31 100644 --- a/boards/esp32-c3-devkitM-1/Makefile +++ b/boards/esp32-c3-devkitM-1/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the ESP32-C3 platform TARGET=riscv32imc-unknown-none-elf @@ -11,7 +15,7 @@ include ../Makefile.common install: flash flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf - esptool.py --port /dev/ttyUSB0 --chip esp32c3 elf2image --use_segments --output binary.hex $^ + esptool.py --port /dev/ttyUSB0 --chip esp32c3 elf2image --use_segments --output binary.hex $^ --dont-append-digest esptool.py --port /dev/ttyUSB0 --chip esp32c3 write_flash --flash_mode dio --flash_size detect --flash_freq 80m 0x0 binary.hex flash-app: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf @@ -19,7 +23,7 @@ ifeq ($(APP),) $(error "Please specify an APP to be flashed") endif $(RISC_PREFIX)-objcopy --update-section .apps=$(APP) $^ $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.elf - esptool.py --port /dev/ttyUSB0 --chip esp32c3 elf2image --use_segments --output binary.hex $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.elf + esptool.py --port /dev/ttyUSB0 --chip esp32c3 elf2image --use_segments --output binary.hex $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.elf --dont-append-digest esptool.py --port /dev/ttyUSB0 --chip esp32c3 write_flash --flash_mode dio --flash_size detect --flash_freq 80m 0x0 binary.hex test: diff --git a/boards/esp32-c3-devkitM-1/README.md b/boards/esp32-c3-devkitM-1/README.md index f5aeb4a553..a550476483 100644 --- a/boards/esp32-c3-devkitM-1/README.md +++ b/boards/esp32-c3-devkitM-1/README.md @@ -1,19 +1,18 @@ -ESP32-C3 Board -============== +# ESP32-C3 Board ESP32-C3 is a system on a chip that integrates the following features: - * Wi-Fi (2.4 GHz band) - * Bluetooth Low Energy - * High performance 32-bit RISC-V-ish single-core processor - * Multiple peripherals - * Built-in security hardware + +- Wi-Fi (2.4 GHz band) +- Bluetooth Low Energy +- High performance 32-bit RISC-V-ish single-core processor +- Multiple peripherals +- Built-in security hardware Powered by 40 nm technology, ESP32-C3 provides a robust, highly integrated platform, which helps meet the continuous demands for efficient power usage, compact design, security, high performance, and reliability. -Setup ------ +## Setup Install the ESP tool @@ -67,8 +66,23 @@ Entering main loop. screen /dev/ttyUSB0 115200 ``` -JTAG Debugging --------------- +## Building and Flashing Applications + +Apps are built out-of-tree, for example: + +```bash +$ cd libtock-c/examples/ +$ make RISCV=1 +``` + +Then to flash an app: + +``` +$ cd tock/boards/esp32-c3-devkitM-1 +$ make flash-app APP=../../../libtock-c/examples//build/rv32imac/rv32imac.0x403B0060.0x3FCC0000.tbf +``` + +## JTAG Debugging In order to use JTAG debugging you first need to build a fork of OpenOCD @@ -83,7 +97,7 @@ make -j8 Note that the connection can be unreliable, commit 5b67ad1b15938f524f133afb8ef652c990f570eb seems to work though. -Then connect an [FDTI C232HM](https://ftdichip.com/products/c232hm-ddhsl-0-2/) +Then connect an [FTDI C232HM](https://ftdichip.com/products/c232hm-ddhsl-0-2/) cable as described here: https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-guides/jtag-debugging/configure-other-jtag.html. diff --git a/boards/esp32-c3-devkitM-1/build.rs b/boards/esp32-c3-devkitM-1/build.rs deleted file mode 100644 index 3051054fc6..0000000000 --- a/boards/esp32-c3-devkitM-1/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/esp32-c3-devkitM-1/layout.ld b/boards/esp32-c3-devkitM-1/layout.ld index b29ba93736..817a10501d 100644 --- a/boards/esp32-c3-devkitM-1/layout.ld +++ b/boards/esp32-c3-devkitM-1/layout.ld @@ -1,3 +1,7 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + MEMORY { rom (rx) : ORIGIN = 0x40380000, LENGTH = 0x30000 @@ -5,6 +9,4 @@ MEMORY prog (rx) : ORIGIN = 0x403B0000, LENGTH = 0x30000 } -MPU_MIN_ALIGN = 1K; - INCLUDE ../kernel_layout.ld diff --git a/boards/esp32-c3-devkitM-1/run.sh b/boards/esp32-c3-devkitM-1/run.sh index 8ce4276fe0..89ccd99cb6 100755 --- a/boards/esp32-c3-devkitM-1/run.sh +++ b/boards/esp32-c3-devkitM-1/run.sh @@ -1,4 +1,8 @@ #!/bin/bash +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + esptool.py --port /dev/ttyUSB0 --chip esp32c3 elf2image --use_segments --output binary.hex ${1} esptool.py --port /dev/ttyUSB0 --chip esp32c3 write_flash --flash_mode dio --flash_size detect --flash_freq 80m 0x0 binary.hex diff --git a/boards/esp32-c3-devkitM-1/src/io.rs b/boards/esp32-c3-devkitM-1/src/io.rs index 924bc859e1..74cf8db87f 100644 --- a/boards/esp32-c3-devkitM-1/src/io.rs +++ b/boards/esp32-c3-devkitM-1/src/io.rs @@ -1,5 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; +use core::ptr::addr_of; use core::str; use kernel::debug; use kernel::debug::IoWrite; @@ -20,11 +25,12 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { let uart = esp32::uart::Uart::new(esp32::uart::UART0_BASE); uart.disable_tx_interrupt(); uart.disable_rx_interrupt(); uart.transmit_sync(buf); + buf.len() } } @@ -32,12 +38,14 @@ impl IoWrite for Writer { #[cfg(not(test))] #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { - let writer = &mut WRITER; +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { + use core::ptr::addr_of_mut; + + let writer = &mut *addr_of_mut!(WRITER); debug::panic_banner(writer, pi); - debug::panic_cpu_state(&CHIP, writer); - debug::panic_process_info(&PROCESSES, &PROCESS_PRINTER, writer); + debug::panic_cpu_state(&*addr_of!(CHIP), writer); + debug::panic_process_info(&*addr_of!(PROCESSES), &*addr_of!(PROCESS_PRINTER), writer); loop { rv32i::support::nop(); @@ -47,16 +55,18 @@ pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { #[cfg(test)] #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { - let writer = &mut WRITER; +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { + use core::ptr::addr_of_mut; + + let writer = &mut *addr_of_mut!(WRITER); debug::panic_print( writer, pi, &rv32i::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ); let _ = writeln!(writer, "{}", pi); diff --git a/boards/esp32-c3-devkitM-1/src/main.rs b/boards/esp32-c3-devkitM-1/src/main.rs index 613783fb40..dbe0bf0ac8 100644 --- a/boards/esp32-c3-devkitM-1/src/main.rs +++ b/boards/esp32-c3-devkitM-1/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Board file for ESP32-C3 RISC-V development platform. //! @@ -9,11 +13,12 @@ #![test_runner(test_runner)] #![reexport_test_harness_main = "test_main"] -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; use esp32_c3::chip::Esp32C3DefaultPeripherals; use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::platform::scheduler_timer::VirtualSchedulerTimer; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::scheduler::priority::PrioritySched; @@ -36,10 +41,12 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] // Reference to the chip for panic dumps. static mut CHIP: Option<&'static esp32_c3::chip::Esp32C3> = None; // Static reference to process printer for panic dumps. -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; // Test access to the peripherals #[cfg(test)] @@ -64,17 +71,20 @@ static mut ALARM: Option<&'static MuxAlarm<'static, esp32_c3::timg::TimG<'static #[link_section = ".stack_buffer"] pub static mut STACK_MEMORY: [u8; 0x900] = [0; 0x900]; +type RngDriver = components::rng::RngComponentType>; + /// A structure representing this platform that holds references to all /// capsules for this platform. We've included an alarm and console. struct Esp32C3Board { - gpio: &'static capsules::gpio::GPIO<'static, esp32::gpio::GpioPin<'static>>, - console: &'static capsules::console::Console<'static>, - alarm: &'static capsules::alarm::AlarmDriver< + gpio: &'static capsules_core::gpio::GPIO<'static, esp32::gpio::GpioPin<'static>>, + console: &'static capsules_core::console::Console<'static>, + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, VirtualMuxAlarm<'static, esp32_c3::timg::TimG<'static>>, >, scheduler: &'static PrioritySched, scheduler_timer: &'static VirtualSchedulerTimer>, + rng: &'static RngDriver, } /// Mapping of integer syscalls to objects that implement syscalls. @@ -84,9 +94,10 @@ impl SyscallDriverLookup for Esp32C3Board { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::rng::DRIVER_NUM => f(Some(self.rng)), _ => f(None), } } @@ -98,13 +109,13 @@ impl KernelResources>; type WatchDog = (); - type ContextSwitchCallback = (); fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -116,7 +127,7 @@ impl KernelResources &Self::SchedulerTimer { - &self.scheduler_timer + self.scheduler_timer } fn watchdog(&self) -> &Self::WatchDog { &() @@ -135,13 +146,14 @@ unsafe fn setup() -> ( use esp32_c3::sysreg::{CpuFrequency, PllFrequency}; // only machine mode - rv32i::configure_trap_handler(rv32i::PermissionMode::Machine); + rv32i::configure_trap_handler(); let peripherals = static_init!(Esp32C3DefaultPeripherals, Esp32C3DefaultPeripherals::new()); peripherals.timg0.disable_wdt(); peripherals.rtc_cntl.disable_wdt(); peripherals.rtc_cntl.disable_super_wdt(); + peripherals.rtc_cntl.enable_fosc(); peripherals.sysreg.disable_timg0(); peripherals.sysreg.enable_timg0(); @@ -153,30 +165,18 @@ unsafe fn setup() -> ( let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability); let memory_allocation_cap = create_capability!(capabilities::MemoryAllocationCapability); - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); - - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 1], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); // Configure kernel debug gpios as early as possible kernel::debug::assign_gpios(None, None, None); // Create a shared UART channel for the console and for kernel debug. - let uart_mux = components::console::UartMuxComponent::new( - &peripherals.uart0, - 115200, - dynamic_deferred_caller, - ) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(&peripherals.uart0, 115200) + .finalize(components::uart_mux_component_static!()); let gpio = components::gpio::GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( esp32::gpio::GpioPin, 0 => &peripherals.gpio[0], @@ -186,10 +186,11 @@ unsafe fn setup() -> ( 4 => &peripherals.gpio[4], 5 => &peripherals.gpio[5], 6 => &peripherals.gpio[6], - 7 => &peripherals.gpio[15] + 7 => &peripherals.gpio[7], + 8 => &peripherals.gpio[15] ), ) - .finalize(components::gpio_component_buf!(esp32::gpio::GpioPin)); + .finalize(components::gpio_component_static!(esp32::gpio::GpioPin)); // Create a shared virtualization mux layer on top of a single hardware // alarm. @@ -209,10 +210,10 @@ unsafe fn setup() -> ( virtual_alarm_user.setup(); let alarm = static_init!( - capsules::alarm::AlarmDriver<'static, VirtualMuxAlarm<'static, esp32_c3::timg::TimG>>, - capsules::alarm::AlarmDriver::new( + capsules_core::alarm::AlarmDriver<'static, VirtualMuxAlarm<'static, esp32_c3::timg::TimG>>, + capsules_core::alarm::AlarmDriver::new( virtual_alarm_user, - board_kernel.create_grant(capsules::alarm::DRIVER_NUM, &memory_allocation_cap) + board_kernel.create_grant(capsules_core::alarm::DRIVER_NUM, &memory_allocation_cap) ) ); hil::time::Alarm::set_alarm_client(virtual_alarm_user, alarm); @@ -240,22 +241,23 @@ unsafe fn setup() -> ( // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); // Create process printer for panic. - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); debug!("ESP32-C3 initialisation complete."); debug!("Entering main loop."); - /// These symbols are defined in the linker script. + // These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -267,11 +269,8 @@ unsafe fn setup() -> ( static _eappmem: u8; } - let scheduler = components::sched::priority::PriorityComponent::new(board_kernel).finalize(()); - - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); - PROCESS_PRINTER = Some(process_printer); + let scheduler = components::sched::priority::PriorityComponent::new(board_kernel) + .finalize(components::priority_component_static!()); // PROCESS CONSOLE let process_console = components::process_console::ProcessConsoleComponent::new( @@ -279,20 +278,29 @@ unsafe fn setup() -> ( uart_mux, mux_alarm, process_printer, + None, ) - .finalize(components::process_console_component_helper!( + .finalize(components::process_console_component_static!( esp32_c3::timg::TimG )); let _ = process_console.start(); + let rng = components::rng::RngComponent::new( + board_kernel, + capsules_core::rng::DRIVER_NUM, + &peripherals.rng, + ) + .finalize(components::rng_component_static!(esp32_c3::rng::Rng)); + let esp32_c3_board = static_init!( Esp32C3Board, Esp32C3Board { + gpio, console, alarm, - gpio, scheduler, scheduler_timer, + rng, } ); @@ -300,14 +308,14 @@ unsafe fn setup() -> ( board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_mgmt_cap, ) @@ -316,6 +324,8 @@ unsafe fn setup() -> ( debug!("{:?}", err); }); + peripherals.init(); + (board_kernel, esp32_c3_board, chip, peripherals) } @@ -337,7 +347,7 @@ pub unsafe fn main() { board_kernel.kernel_loop( esp32_c3_board, chip, - None::<&kernel::ipc::IPC>, + None::<&kernel::ipc::IPC<0>>, &main_loop_cap, ); } @@ -354,8 +364,10 @@ fn test_runner(tests: &[&dyn Fn()]) { BOARD = Some(board_kernel); PLATFORM = Some(&esp32_c3_board); PERIPHERALS = Some(peripherals); - SCHEDULER = - Some(components::sched::priority::PriorityComponent::new(board_kernel).finalize(())); + SCHEDULER = Some( + components::sched::priority::PriorityComponent::new(board_kernel) + .finalize(components::priority_component_static!()), + ); MAIN_CAP = Some(&create_capability!(capabilities::MainLoopCapability)); PLATFORM.map(|p| { diff --git a/boards/esp32-c3-devkitM-1/src/tests/mod.rs b/boards/esp32-c3-devkitM-1/src/tests/mod.rs index 8e20fed09c..fa7bd80670 100644 --- a/boards/esp32-c3-devkitM-1/src/tests/mod.rs +++ b/boards/esp32-c3-devkitM-1/src/tests/mod.rs @@ -1,7 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use crate::BOARD; use crate::CHIP; use crate::MAIN_CAP; -use crate::NUM_PROCS; use crate::PLATFORM; use kernel::debug; @@ -11,7 +14,7 @@ fn run_kernel_op(loops: usize) { BOARD.unwrap().kernel_loop_operation( PLATFORM.unwrap(), CHIP.unwrap(), - None::<&kernel::ipc::IPC>, + None::<&kernel::ipc::IPC<0>>, true, MAIN_CAP.unwrap(), ); diff --git a/boards/esp32-c3-devkitM-1/src/tests/multi_alarm.rs b/boards/esp32-c3-devkitM-1/src/tests/multi_alarm.rs index 8862fc651b..e00750fef6 100644 --- a/boards/esp32-c3-devkitM-1/src/tests/multi_alarm.rs +++ b/boards/esp32-c3-devkitM-1/src/tests/multi_alarm.rs @@ -1,10 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test the behavior of a single alarm. //! To add this test, include the line //! ``` //! multi_alarm_test::run_alarm(alarm_mux); //! ``` //! to the OpenTitan boot sequence, where `alarm_mux` is a -//! `capsules::virtual_alarm::MuxAlarm`. The test sets up 3 +//! `capsules_core::virtualizers::virtual_alarm::MuxAlarm`. The test sets up 3 //! different virtualized alarms of random durations and prints //! out when they fire. The durations are uniformly random with //! one caveat, that 1 in 11 is of duration 0; this is to test @@ -13,8 +17,8 @@ use crate::tests::run_kernel_op; use crate::ALARM; -use capsules::test::random_alarm::TestRandomAlarm; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::test::random_alarm::TestRandomAlarm; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; use esp32_c3::timg::TimG; use kernel::debug; use kernel::hil::time::Alarm; diff --git a/boards/hail/Cargo.toml b/boards/hail/Cargo.toml index 2bb28535f7..51154beb57 100644 --- a/boards/hail/Cargo.toml +++ b/boards/hail/Cargo.toml @@ -1,13 +1,23 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "hail" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] components = { path = "../components" } cortexm4 = { path = "../../arch/cortex-m4" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } sam4l = { path = "../../chips/sam4l" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/hail/Makefile b/boards/hail/Makefile index 678029c8c3..83abd0eb7d 100644 --- a/boards/hail/Makefile +++ b/boards/hail/Makefile @@ -1,7 +1,12 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the Hail platform TARGET=thumbv7em-none-eabi PLATFORM=hail +USE_STABLE_RUST=1 include ../Makefile.common diff --git a/boards/hail/build.rs b/boards/hail/build.rs deleted file mode 100644 index 46b1df0bac..0000000000 --- a/boards/hail/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=chip_layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/hail/chip_layout.ld b/boards/hail/chip_layout.ld index c3d5b38328..14f528c9ac 100644 --- a/boards/hail/chip_layout.ld +++ b/boards/hail/chip_layout.ld @@ -1,3 +1,7 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + /* Memory Spaces Definitions, 448K flash, 64K ram */ /* Bootloader is at address 0x00000000 */ MEMORY @@ -6,5 +10,3 @@ MEMORY prog (rx) : ORIGIN = 0x00030000, LENGTH = 0x00040000 ram (rwx) : ORIGIN = 0x20000000, LENGTH = 64K } - -MPU_MIN_ALIGN = 8K; diff --git a/boards/hail/connect.cfg b/boards/hail/connect.cfg index 96f9e8b4cf..4bd80a79ae 100644 --- a/boards/hail/connect.cfg +++ b/boards/hail/connect.cfg @@ -1,3 +1,6 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. interface jlink transport select swd diff --git a/boards/hail/debug.gdb b/boards/hail/debug.gdb index ecf1e3e4c4..6228c08994 100644 --- a/boards/hail/debug.gdb +++ b/boards/hail/debug.gdb @@ -1,3 +1,6 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. target remote :3333 monitor reset diff --git a/boards/hail/jlink/gdb_session.sh b/boards/hail/jlink/gdb_session.sh index c3bb0c20a0..9a137f801e 100755 --- a/boards/hail/jlink/gdb_session.sh +++ b/boards/hail/jlink/gdb_session.sh @@ -1,2 +1,7 @@ #!/usr/bin/env sh + +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + arm-none-eabi-gdb -x hail.gdb diff --git a/boards/hail/jlink/hail.gdb b/boards/hail/jlink/hail.gdb index b05a5dd063..a136639bc9 100644 --- a/boards/hail/jlink/hail.gdb +++ b/boards/hail/jlink/hail.gdb @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + target remote localhost:2331 monitor speed 30 file ../../../target/thumbv7em-none-eabi/release/hail diff --git a/boards/hail/jlink/jlink_gdbserver.sh b/boards/hail/jlink/jlink_gdbserver.sh index 6e2134b391..a23302913b 100755 --- a/boards/hail/jlink/jlink_gdbserver.sh +++ b/boards/hail/jlink/jlink_gdbserver.sh @@ -1,2 +1,7 @@ #!/usr/bin/env sh + +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + JLinkGDBServer -device ATSAM4LC8C -speed 1200 -if swd -AutoConnect 1 -port 2331 diff --git a/boards/hail/layout.ld b/boards/hail/layout.ld index 234dcaea2c..6814c00acd 100644 --- a/boards/hail/layout.ld +++ b/boards/hail/layout.ld @@ -1,2 +1,6 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + INCLUDE ./chip_layout.ld INCLUDE ../kernel_layout.ld diff --git a/boards/hail/rust-toolchain.toml b/boards/hail/rust-toolchain.toml new file mode 100644 index 0000000000..3a49472eed --- /dev/null +++ b/boards/hail/rust-toolchain.toml @@ -0,0 +1,8 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +[toolchain] +channel = "stable" +components = ["llvm-tools", "rust-src", "rustfmt", "clippy"] +targets = ["thumbv6m-none-eabi", "thumbv7em-none-eabi", "thumbv7em-none-eabihf", "riscv32imc-unknown-none-elf", "riscv32imac-unknown-none-elf"] diff --git a/boards/hail/src/io.rs b/boards/hail/src/io.rs index 5940c03075..5ff88f8ba9 100644 --- a/boards/hail/src/io.rs +++ b/boards/hail/src/io.rs @@ -1,7 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; use core::str; -use cortexm4; use kernel::debug; use kernel::debug::IoWrite; use kernel::hil::led; @@ -25,7 +28,7 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { // Here, we create a second instance of the USART0 struct. // This is okay because we only call this during a panic, and // we will never actually process the interrupts @@ -47,6 +50,7 @@ impl IoWrite for Writer { uart.send_byte(regs_manager, c); while !uart.tx_ready(regs_manager) {} } + buf.len() } } @@ -54,8 +58,10 @@ impl IoWrite for Writer { #[cfg(not(test))] #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { // turn off the non panic leds, just in case + + use core::ptr::{addr_of, addr_of_mut}; let led_green = sam4l::gpio::GPIOPin::new(sam4l::gpio::Pin::PA14); led_green.enable_output(); led_green.set(); @@ -65,14 +71,14 @@ pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { let red_pin = sam4l::gpio::GPIOPin::new(sam4l::gpio::Pin::PA13); let led_red = &mut led::LedLow::new(&red_pin); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led_red], writer, pi, &cortexm4::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/hail/src/main.rs b/boards/hail/src/main.rs index 0e5d38ce2b..6abe00cdd7 100644 --- a/boards/hail/src/main.rs +++ b/boards/hail/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Board file for Hail development platform. //! //! - @@ -9,21 +13,17 @@ #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] -use capsules::virtual_alarm::VirtualMuxAlarm; -use capsules::virtual_i2c::{I2CDevice, MuxI2C}; -use capsules::virtual_spi::VirtualSpiMasterDevice; +use core::ptr::{addr_of, addr_of_mut}; + use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil; -use kernel::hil::i2c::I2CMaster; use kernel::hil::led::LedLow; use kernel::hil::Controller; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::scheduler::round_robin::RoundRobinSched; #[allow(unused_imports)] use kernel::{create_capability, debug, debug_gpio, static_init}; -use sam4l::adc::Channel; use sam4l::chip::Sam4lDefaultPeripherals; /// Support routines for debugging I/O. @@ -43,42 +43,57 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] [None; NUM_PROCS]; static mut CHIP: Option<&'static sam4l::chip::Sam4l> = None; -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; /// Dummy buffer that causes the linker to reserve enough space for the stack. #[no_mangle] #[link_section = ".stack_buffer"] pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; +type SI7021Sensor = components::si7021::SI7021ComponentType< + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, sam4l::i2c::I2CHw<'static>>, +>; +type TemperatureDriver = components::temperature::TemperatureComponentType; +type HumidityDriver = components::humidity::HumidityComponentType; +type RngDriver = components::rng::RngComponentType>; + /// A structure representing this platform that holds references to all /// capsules for this platform. struct Hail { - console: &'static capsules::console::Console<'static>, - gpio: &'static capsules::gpio::GPIO<'static, sam4l::gpio::GPIOPin<'static>>, - alarm: &'static capsules::alarm::AlarmDriver< + console: &'static capsules_core::console::Console<'static>, + gpio: &'static capsules_core::gpio::GPIO<'static, sam4l::gpio::GPIOPin<'static>>, + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, - VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + sam4l::ast::Ast<'static>, + >, >, - ambient_light: &'static capsules::ambient_light::AmbientLight<'static>, - temp: &'static capsules::temperature::TemperatureSensor<'static>, - ninedof: &'static capsules::ninedof::NineDof<'static>, - humidity: &'static capsules::humidity::HumiditySensor<'static>, - spi: &'static capsules::spi_controller::Spi< + ambient_light: &'static capsules_extra::ambient_light::AmbientLight<'static>, + temp: &'static TemperatureDriver, + ninedof: &'static capsules_extra::ninedof::NineDof<'static>, + humidity: &'static HumidityDriver, + spi: &'static capsules_core::spi_controller::Spi< 'static, - VirtualSpiMasterDevice<'static, sam4l::spi::SpiHw>, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice< + 'static, + sam4l::spi::SpiHw<'static>, + >, >, - nrf51822: &'static capsules::nrf51822_serialization::Nrf51822Serialization<'static>, - adc: &'static capsules::adc::AdcDedicated<'static, sam4l::adc::Adc>, - led: &'static capsules::led::LedDriver< + nrf51822: &'static capsules_extra::nrf51822_serialization::Nrf51822Serialization<'static>, + adc: &'static capsules_core::adc::AdcDedicated<'static, sam4l::adc::Adc<'static>>, + led: &'static capsules_core::led::LedDriver< 'static, LedLow<'static, sam4l::gpio::GPIOPin<'static>>, 3, >, - button: &'static capsules::button::Button<'static, sam4l::gpio::GPIOPin<'static>>, - rng: &'static capsules::rng::RngDriver<'static>, - ipc: kernel::ipc::IPC, - crc: &'static capsules::crc::CrcDriver<'static, sam4l::crccu::Crccu<'static>>, - dac: &'static capsules::dac::Dac<'static>, + button: &'static capsules_core::button::Button<'static, sam4l::gpio::GPIOPin<'static>>, + rng: &'static RngDriver, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + crc: &'static capsules_extra::crc::CrcDriver<'static, sam4l::crccu::Crccu<'static>>, + dac: &'static capsules_extra::dac::Dac<'static>, scheduler: &'static RoundRobinSched<'static>, systick: cortexm4::systick::SysTick, } @@ -90,25 +105,25 @@ impl SyscallDriverLookup for Hail { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::spi_controller::DRIVER_NUM => f(Some(self.spi)), - capsules::nrf51822_serialization::DRIVER_NUM => f(Some(self.nrf51822)), - capsules::ambient_light::DRIVER_NUM => f(Some(self.ambient_light)), - capsules::adc::DRIVER_NUM => f(Some(self.adc)), - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::button::DRIVER_NUM => f(Some(self.button)), - capsules::humidity::DRIVER_NUM => f(Some(self.humidity)), - capsules::temperature::DRIVER_NUM => f(Some(self.temp)), - capsules::ninedof::DRIVER_NUM => f(Some(self.ninedof)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::spi_controller::DRIVER_NUM => f(Some(self.spi)), + capsules_extra::nrf51822_serialization::DRIVER_NUM => f(Some(self.nrf51822)), + capsules_extra::ambient_light::DRIVER_NUM => f(Some(self.ambient_light)), + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_extra::humidity::DRIVER_NUM => f(Some(self.humidity)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temp)), + capsules_extra::ninedof::DRIVER_NUM => f(Some(self.ninedof)), - capsules::rng::DRIVER_NUM => f(Some(self.rng)), + capsules_core::rng::DRIVER_NUM => f(Some(self.rng)), - capsules::crc::DRIVER_NUM => f(Some(self.crc)), + capsules_extra::crc::DRIVER_NUM => f(Some(self.crc)), - capsules::dac::DRIVER_NUM => f(Some(self.dac)), + capsules_extra::dac::DRIVER_NUM => f(Some(self.dac)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), _ => f(None), @@ -126,7 +141,7 @@ impl KernelResources> for Hail { type ContextSwitchCallback = (); fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -213,22 +228,15 @@ unsafe fn set_pin_primary_functions(peripherals: &Sam4lDefaultPeripherals) { /// removed when this function returns. Otherwise, the stack space used for /// these static_inits is wasted. #[inline(never)] -unsafe fn get_peripherals( - pm: &'static sam4l::pm::PowerManager, -) -> &'static Sam4lDefaultPeripherals { - static_init!(Sam4lDefaultPeripherals, Sam4lDefaultPeripherals::new(pm)) -} - -/// Board's main function. -/// -/// This is called from the reset handler after memory initialization is -/// complete. -#[no_mangle] -pub unsafe fn main() { +unsafe fn start() -> ( + &'static kernel::Kernel, + Hail, + &'static sam4l::chip::Sam4l, +) { sam4l::init(); let pm = static_init!(sam4l::pm::PowerManager, sam4l::pm::PowerManager::new()); - let peripherals = get_peripherals(pm); + let peripherals = static_init!(Sam4lDefaultPeripherals, Sam4lDefaultPeripherals::new(pm)); pm.setup_system_clock( sam4l::pm::SystemClockSource::PllExternalOscillatorAt48MHz { @@ -242,20 +250,17 @@ pub unsafe fn main() { sam4l::bpm::set_ck32source(sam4l::bpm::CK32Source::RC32K); set_pin_primary_functions(peripherals); - peripherals.setup_dma(); + peripherals.setup_circular_deps(); let chip = static_init!( sam4l::chip::Sam4l, sam4l::chip::Sam4l::new(pm, peripherals) ); CHIP = Some(chip); - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); - // Create capabilities that the board needs to call certain protected kernel // functions. let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); // Configure kernel debug gpios as early as possible @@ -265,54 +270,46 @@ pub unsafe fn main() { Some(&peripherals.pa[14]), ); - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 3], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); // Initialize USART0 for Uart peripherals.usart0.set_mode(sam4l::usart::UsartMode::Uart); // Create a shared UART channel for the console and for kernel debug. - let uart_mux = components::console::UartMuxComponent::new( - &peripherals.usart0, - 115200, - dynamic_deferred_caller, - ) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(&peripherals.usart0, 115200) + .finalize(components::uart_mux_component_static!()); uart_mux.initialize(); hil::uart::Transmit::set_transmit_client(&peripherals.usart0, uart_mux); hil::uart::Receive::set_receive_client(&peripherals.usart0, uart_mux); let mux_alarm = components::alarm::AlarmMuxComponent::new(&peripherals.ast) - .finalize(components::alarm_mux_component_helper!(sam4l::ast::Ast)); + .finalize(components::alarm_mux_component_static!(sam4l::ast::Ast)); peripherals.ast.configure(mux_alarm); // Setup the console and the process inspection console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); let process_console = components::process_console::ProcessConsoleComponent::new( board_kernel, uart_mux, mux_alarm, process_printer, + Some(cortexm4::support::reset), ) - .finalize(components::process_console_component_helper!( + .finalize(components::process_console_component_static!( sam4l::ast::Ast<'static> )); - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); // Initialize USART3 for UART for the nRF serialization link. peripherals.usart3.set_mode(sam4l::usart::UsartMode::Uart); @@ -320,83 +317,77 @@ pub unsafe fn main() { // over UART to the nRF51822 radio. let nrf_serialization = components::nrf51822::Nrf51822Component::new( board_kernel, - capsules::nrf51822_serialization::DRIVER_NUM, + capsules_extra::nrf51822_serialization::DRIVER_NUM, &peripherals.usart3, &peripherals.pa[17], ) - .finalize(()); + .finalize(components::nrf51822_component_static!()); - let sensors_i2c = static_init!( - MuxI2C<'static>, - MuxI2C::new(&peripherals.i2c1, None, dynamic_deferred_caller) - ); - peripherals.i2c1.set_master_client(sensors_i2c); + let sensors_i2c = components::i2c::I2CMuxComponent::new(&peripherals.i2c1, None) + .finalize(components::i2c_mux_component_static!(sam4l::i2c::I2CHw)); // SI7021 Temperature / Humidity Sensor, address: 0x40 - let si7021 = components::si7021::SI7021Component::new(sensors_i2c, mux_alarm, 0x40) - .finalize(components::si7021_component_helper!(sam4l::ast::Ast)); + let si7021 = components::si7021::SI7021Component::new(sensors_i2c, mux_alarm, 0x40).finalize( + components::si7021_component_static!(sam4l::ast::Ast, sam4l::i2c::I2CHw), + ); let temp = components::temperature::TemperatureComponent::new( board_kernel, - capsules::temperature::DRIVER_NUM, + capsules_extra::temperature::DRIVER_NUM, si7021, ) - .finalize(()); - let humidity = components::si7021::HumidityComponent::new( + .finalize(components::temperature_component_static!(SI7021Sensor)); + let humidity = components::humidity::HumidityComponent::new( board_kernel, - capsules::humidity::DRIVER_NUM, + capsules_extra::humidity::DRIVER_NUM, si7021, ) - .finalize(()); + .finalize(components::humidity_component_static!(SI7021Sensor)); // Configure the ISL29035, device address 0x44 + let isl29035 = components::isl29035::Isl29035Component::new(sensors_i2c, mux_alarm).finalize( + components::isl29035_component_static!(sam4l::ast::Ast, sam4l::i2c::I2CHw), + ); let ambient_light = components::isl29035::AmbientLightComponent::new( board_kernel, - capsules::ambient_light::DRIVER_NUM, - sensors_i2c, - mux_alarm, + capsules_extra::ambient_light::DRIVER_NUM, + isl29035, ) - .finalize(components::isl29035_component_helper!(sam4l::ast::Ast)); + .finalize(components::ambient_light_component_static!()); // Alarm let alarm = components::alarm::AlarmDriverComponent::new( board_kernel, - capsules::alarm::DRIVER_NUM, + capsules_core::alarm::DRIVER_NUM, mux_alarm, ) - .finalize(components::alarm_component_helper!(sam4l::ast::Ast)); + .finalize(components::alarm_component_static!(sam4l::ast::Ast)); // FXOS8700CQ accelerometer, device address 0x1e - let fxos8700_i2c = static_init!(I2CDevice, I2CDevice::new(sensors_i2c, 0x1e)); - let fxos8700 = static_init!( - capsules::fxos8700cq::Fxos8700cq<'static>, - capsules::fxos8700cq::Fxos8700cq::new( - fxos8700_i2c, - &peripherals.pa[9], - &mut capsules::fxos8700cq::BUF - ) - ); - fxos8700_i2c.set_client(fxos8700); - peripherals.pa[9].set_client(fxos8700); + let fxos8700 = + components::fxos8700::Fxos8700Component::new(sensors_i2c, 0x1e, &peripherals.pa[9]) + .finalize(components::fxos8700_component_static!(sam4l::i2c::I2CHw)); - let ninedof = - components::ninedof::NineDofComponent::new(board_kernel, capsules::ninedof::DRIVER_NUM) - .finalize(components::ninedof_component_helper!(fxos8700)); + let ninedof = components::ninedof::NineDofComponent::new( + board_kernel, + capsules_extra::ninedof::DRIVER_NUM, + ) + .finalize(components::ninedof_component_static!(fxos8700)); // SPI // Set up a SPI MUX, so there can be multiple clients. - let mux_spi = components::spi::SpiMuxComponent::new(&peripherals.spi, dynamic_deferred_caller) - .finalize(components::spi_mux_component_helper!(sam4l::spi::SpiHw)); + let mux_spi = components::spi::SpiMuxComponent::new(&peripherals.spi) + .finalize(components::spi_mux_component_static!(sam4l::spi::SpiHw)); // Create the SPI system call capsule. let spi_syscalls = components::spi::SpiSyscallComponent::new( board_kernel, mux_spi, 0, - capsules::spi_controller::DRIVER_NUM, + capsules_core::spi_controller::DRIVER_NUM, ) - .finalize(components::spi_syscall_component_helper!(sam4l::spi::SpiHw)); + .finalize(components::spi_syscall_component_static!(sam4l::spi::SpiHw)); // LEDs - let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( LedLow<'static, sam4l::gpio::GPIOPin>, LedLow::new(&peripherals.pa[13]), // Red LedLow::new(&peripherals.pa[15]), // Green @@ -406,7 +397,7 @@ pub unsafe fn main() { // BUTTONs let button = components::button::ButtonComponent::new( board_kernel, - capsules::button::DRIVER_NUM, + capsules_core::button::DRIVER_NUM, components::button_component_helper!( sam4l::gpio::GPIOPin, ( @@ -416,58 +407,40 @@ pub unsafe fn main() { ) ), ) - .finalize(components::button_component_buf!(sam4l::gpio::GPIOPin)); + .finalize(components::button_component_static!(sam4l::gpio::GPIOPin)); // Setup ADC let adc_channels = static_init!( [sam4l::adc::AdcChannel; 6], [ - sam4l::adc::AdcChannel::new(Channel::AD0), // A0 - sam4l::adc::AdcChannel::new(Channel::AD1), // A1 - sam4l::adc::AdcChannel::new(Channel::AD3), // A2 - sam4l::adc::AdcChannel::new(Channel::AD4), // A3 - sam4l::adc::AdcChannel::new(Channel::AD5), // A4 - sam4l::adc::AdcChannel::new(Channel::AD6), // A5 - ] - ); - // Capsule expects references inside array bc it was built assuming model in which - // global structs are used, so this is a bit of a hack to pass it what it wants. - let ref_channels = static_init!( - [&sam4l::adc::AdcChannel; 6], - [ - &adc_channels[0], - &adc_channels[1], - &adc_channels[2], - &adc_channels[3], - &adc_channels[4], - &adc_channels[5], + sam4l::adc::AdcChannel::new(sam4l::adc::Channel::AD0), // A0 + sam4l::adc::AdcChannel::new(sam4l::adc::Channel::AD1), // A1 + sam4l::adc::AdcChannel::new(sam4l::adc::Channel::AD3), // A2 + sam4l::adc::AdcChannel::new(sam4l::adc::Channel::AD4), // A3 + sam4l::adc::AdcChannel::new(sam4l::adc::Channel::AD5), // A4 + sam4l::adc::AdcChannel::new(sam4l::adc::Channel::AD6), // A5 ] ); - let adc = static_init!( - capsules::adc::AdcDedicated<'static, sam4l::adc::Adc>, - capsules::adc::AdcDedicated::new( - &peripherals.adc, - board_kernel.create_grant(capsules::adc::DRIVER_NUM, &memory_allocation_capability), - ref_channels, - &mut capsules::adc::ADC_BUFFER1, - &mut capsules::adc::ADC_BUFFER2, - &mut capsules::adc::ADC_BUFFER3 - ) - ); - peripherals.adc.set_client(adc); + let adc = components::adc::AdcDedicatedComponent::new( + &peripherals.adc, + adc_channels, + board_kernel, + capsules_core::adc::DRIVER_NUM, + ) + .finalize(components::adc_dedicated_component_static!(sam4l::adc::Adc)); // Setup RNG let rng = components::rng::RngComponent::new( board_kernel, - capsules::rng::DRIVER_NUM, + capsules_core::rng::DRIVER_NUM, &peripherals.trng, ) - .finalize(()); + .finalize(components::rng_component_static!(sam4l::trng::Trng)); // set GPIO driver controlling remaining GPIO pins let gpio = components::gpio::GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( sam4l::gpio::GPIOPin, 0 => &peripherals.pb[14], // D0 @@ -476,21 +449,19 @@ pub unsafe fn main() { 3 => &peripherals.pb[12] // D7 ), ) - .finalize(components::gpio_component_buf!(sam4l::gpio::GPIOPin)); + .finalize(components::gpio_component_static!(sam4l::gpio::GPIOPin)); // CRC let crc = components::crc::CrcComponent::new( board_kernel, - capsules::crc::DRIVER_NUM, + capsules_extra::crc::DRIVER_NUM, &peripherals.crccu, ) - .finalize(components::crc_component_helper!(sam4l::crccu::Crccu)); + .finalize(components::crc_component_static!(sam4l::crccu::Crccu)); // DAC - let dac = static_init!( - capsules::dac::Dac<'static>, - capsules::dac::Dac::new(&peripherals.dac) - ); + let dac = components::dac::DacComponent::new(&peripherals.dac) + .finalize(components::dac_component_static!()); // // DEBUG Restart All Apps // // @@ -501,10 +472,10 @@ pub unsafe fn main() { // struct ProcessMgmtCap; // unsafe impl capabilities::ProcessManagementCapability for ProcessMgmtCap {} // let debug_process_restart = static_init!( - // capsules::debug_process_restart::DebugProcessRestart< + // capsules_core::debug_process_restart::DebugProcessRestart< // ProcessMgmtCap, // >, - // capsules::debug_process_restart::DebugProcessRestart::new( + // capsules_core::debug_process_restart::DebugProcessRestart::new( // board_kernel, // &peripherals.pa[16], // ProcessMgmtCap @@ -514,12 +485,12 @@ pub unsafe fn main() { // Configure application fault policy let fault_policy = static_init!( - kernel::process::ThresholdRestartThenPanicFaultPolicy, - kernel::process::ThresholdRestartThenPanicFaultPolicy::new(4) + capsules_system::process_policies::ThresholdRestartThenPanicFaultPolicy, + capsules_system::process_policies::ThresholdRestartThenPanicFaultPolicy::new(4) ); - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); let hail = Hail { console, @@ -556,7 +527,7 @@ pub unsafe fn main() { debug!("Initialization complete. Entering main loop."); - /// These symbols are defined in the linker script. + // These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -572,14 +543,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), fault_policy, &process_management_capability, ) @@ -588,5 +559,14 @@ pub unsafe fn main() { debug!("{:?}", err); }); - board_kernel.kernel_loop(&hail, chip, Some(&hail.ipc), &main_loop_capability); + (board_kernel, hail, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); } diff --git a/boards/hail/src/test_take_map_cell.rs b/boards/hail/src/test_take_map_cell.rs index 3edb40b426..2eb32dd469 100644 --- a/boards/hail/src/test_take_map_cell.rs +++ b/boards/hail/src/test_take_map_cell.rs @@ -1,3 +1,9 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use core::ptr::addr_of; + use kernel::debug; use kernel::utilities::cells::MapCell; @@ -5,33 +11,33 @@ pub unsafe fn test_take_map_cell() { static FOO: u32 = 1234; static mut MC_REF: MapCell<&'static u32> = MapCell::new(&FOO); - test_map_cell(&MC_REF); + test_map_cell(&*addr_of!(MC_REF)); static mut MC1: MapCell<[[u8; 256]; 1]> = MapCell::new([[125; 256]; 1]); - test_map_cell(&MC1); + test_map_cell(&*addr_of!(MC1)); static mut MC2: MapCell<[[u8; 256]; 2]> = MapCell::new([[125; 256]; 2]); - test_map_cell(&MC2); + test_map_cell(&*addr_of!(MC2)); static mut MC3: MapCell<[[u8; 256]; 3]> = MapCell::new([[125; 256]; 3]); - test_map_cell(&MC3); + test_map_cell(&*addr_of!(MC3)); static mut MC4: MapCell<[[u8; 256]; 4]> = MapCell::new([[125; 256]; 4]); - test_map_cell(&MC4); + test_map_cell(&*addr_of!(MC4)); static mut MC5: MapCell<[[u8; 256]; 5]> = MapCell::new([[125; 256]; 5]); - test_map_cell(&MC5); + test_map_cell(&*addr_of!(MC5)); static mut MC6: MapCell<[[u8; 256]; 6]> = MapCell::new([[125; 256]; 6]); - test_map_cell(&MC6); + test_map_cell(&*addr_of!(MC6)); static mut MC7: MapCell<[[u8; 256]; 7]> = MapCell::new([[125; 256]; 7]); - test_map_cell(&MC7); + test_map_cell(&*addr_of!(MC7)); } #[inline(never)] #[allow(unused_unsafe)] -unsafe fn test_map_cell<'a, A>(tc: &MapCell) { +unsafe fn test_map_cell(tc: &MapCell) { let dwt_ctl: *mut u32 = 0xE0001000 as *mut u32; let dwt_cycles: *mut u32 = 0xE0001004 as *mut u32; let demcr: *mut u32 = 0xE000EDFC as *mut u32; diff --git a/boards/hifive1/Cargo.toml b/boards/hifive1/Cargo.toml index 5994c5a8cb..b95ba670e1 100644 --- a/boards/hifive1/Cargo.toml +++ b/boards/hifive1/Cargo.toml @@ -1,14 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "hifive1" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] components = { path = "../components" } rv32i = { path = "../../arch/rv32i" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } -e310x = { path = "../../chips/e310x" } +e310_g002 = { path = "../../chips/e310_g002" } sifive = { path = "../../chips/sifive" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/hifive1/Makefile b/boards/hifive1/Makefile index 09ed959e27..39ab6518fd 100644 --- a/boards/hifive1/Makefile +++ b/boards/hifive1/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the HiFive1 platform TARGET=riscv32imac-unknown-none-elf diff --git a/boards/hifive1/build.rs b/boards/hifive1/build.rs deleted file mode 100644 index 3051054fc6..0000000000 --- a/boards/hifive1/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/hifive1/layout.ld b/boards/hifive1/layout.ld index 369238de50..4be3945554 100644 --- a/boards/hifive1/layout.ld +++ b/boards/hifive1/layout.ld @@ -1,3 +1,7 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + /* The HiFive1a board has 512 MB of flash. The first 0x400000 is reserved for * the default bootloader provided by SiFive. We also reserve room for apps to * make all of the linker files work, but don't really support them on this @@ -11,6 +15,4 @@ MEMORY ram (rwx) : ORIGIN = 0x80000000, LENGTH = 0x4000 } -MPU_MIN_ALIGN = 1K; - INCLUDE ../kernel_layout.ld diff --git a/boards/hifive1/src/io.rs b/boards/hifive1/src/io.rs index 059c3a8394..17ffbd60af 100644 --- a/boards/hifive1/src/io.rs +++ b/boards/hifive1/src/io.rs @@ -1,12 +1,16 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; +use core::ptr::addr_of; +use core::ptr::addr_of_mut; use core::str; -use e310x; use kernel::debug; use kernel::debug::IoWrite; use kernel::hil::gpio; use kernel::hil::led; -use rv32i; use crate::CHIP; use crate::PROCESSES; @@ -24,9 +28,10 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { - let uart = sifive::uart::Uart::new(e310x::uart::UART0_BASE, 16_000_000); + fn write(&mut self, buf: &[u8]) -> usize { + let uart = sifive::uart::Uart::new(e310_g002::uart::UART0_BASE, 16_000_000); uart.transmit_sync(buf); + buf.len() } } @@ -34,10 +39,10 @@ impl IoWrite for Writer { #[cfg(not(test))] #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { // turn off the non panic leds, just in case let led_green = sifive::gpio::GpioPin::new( - e310x::gpio::GPIO0_BASE, + e310_g002::gpio::GPIO0_BASE, sifive::gpio::pins::pin19, sifive::gpio::pins::pin19::SET, sifive::gpio::pins::pin19::CLEAR, @@ -46,7 +51,7 @@ pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { gpio::Output::set(&led_green); let led_blue = sifive::gpio::GpioPin::new( - e310x::gpio::GPIO0_BASE, + e310_g002::gpio::GPIO0_BASE, sifive::gpio::pins::pin21, sifive::gpio::pins::pin21::SET, sifive::gpio::pins::pin21::CLEAR, @@ -55,21 +60,21 @@ pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { gpio::Output::set(&led_blue); let led_red_pin = sifive::gpio::GpioPin::new( - e310x::gpio::GPIO0_BASE, + e310_g002::gpio::GPIO0_BASE, sifive::gpio::pins::pin22, sifive::gpio::pins::pin22::SET, sifive::gpio::pins::pin22::CLEAR, ); let led_red = &mut led::LedLow::new(&led_red_pin); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led_red], writer, pi, &rv32i::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/hifive1/src/main.rs b/boards/hifive1/src/main.rs index 3ca6293406..ede5f5481c 100644 --- a/boards/hifive1/src/main.rs +++ b/boards/hifive1/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Board file for SiFive HiFive1b RISC-V development platform. //! //! - @@ -9,17 +13,20 @@ // https://github.com/rust-lang/rust/issues/62184. #![cfg_attr(not(doc), no_main)] -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; -use e310x::chip::E310xDefaultPeripherals; +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use e310_g002::interrupt_service::E310G002DefaultPeripherals; use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil; use kernel::hil::led::LedLow; +use kernel::platform::chip::Chip; use kernel::platform::scheduler_timer::VirtualSchedulerTimer; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::scheduler::cooperative::CooperativeSched; use kernel::utilities::registers::interfaces::ReadWriteable; +use kernel::Kernel; use kernel::{create_capability, debug, static_init}; use rv32i::csr; @@ -33,12 +40,14 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] [None; NUM_PROCS]; // Reference to the chip for panic dumps. -static mut CHIP: Option<&'static e310x::chip::E310x> = None; +static mut CHIP: Option<&'static e310_g002::chip::E310x> = None; // Reference to the process printer for panic dumps. -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; /// Dummy buffer that causes the linker to reserve enough space for the stack. #[no_mangle] @@ -48,23 +57,24 @@ pub static mut STACK_MEMORY: [u8; 0x900] = [0; 0x900]; /// A structure representing this platform that holds references to all /// capsules for this platform. We've included an alarm and console. struct HiFive1 { - led: &'static capsules::led::LedDriver< + led: &'static capsules_core::led::LedDriver< 'static, LedLow<'static, sifive::gpio::GpioPin<'static>>, 3, >, - console: &'static capsules::console::Console<'static>, - lldb: &'static capsules::low_level_debug::LowLevelDebug< + console: &'static capsules_core::console::Console<'static>, + lldb: &'static capsules_core::low_level_debug::LowLevelDebug< 'static, - capsules::virtual_uart::UartDevice<'static>, + capsules_core::virtualizers::virtual_uart::UartDevice<'static>, >, - alarm: &'static capsules::alarm::AlarmDriver< + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, - VirtualMuxAlarm<'static, sifive::clint::Clint<'static>>, + VirtualMuxAlarm<'static, e310_g002::chip::E310xClint<'static>>, >, scheduler: &'static CooperativeSched<'static>, - scheduler_timer: - &'static VirtualSchedulerTimer>>, + scheduler_timer: &'static VirtualSchedulerTimer< + VirtualMuxAlarm<'static, e310_g002::chip::E310xClint<'static>>, + >, } /// Mapping of integer syscalls to objects that implement syscalls. @@ -74,27 +84,29 @@ impl SyscallDriverLookup for HiFive1 { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::low_level_debug::DRIVER_NUM => f(Some(self.lldb)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::low_level_debug::DRIVER_NUM => f(Some(self.lldb)), _ => f(None), } } } -impl KernelResources>> for HiFive1 { +impl KernelResources>> + for HiFive1 +{ type SyscallDriverLookup = Self; type SyscallFilter = (); type ProcessFault = (); type Scheduler = CooperativeSched<'static>; type SchedulerTimer = - VirtualSchedulerTimer>>; + VirtualSchedulerTimer>>; type WatchDog = (); type ContextSwitchCallback = (); fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -106,7 +118,7 @@ impl KernelResources &Self::SchedulerTimer { - &self.scheduler_timer + self.scheduler_timer } fn watchdog(&self) -> &Self::WatchDog { &() @@ -116,115 +128,171 @@ impl KernelResources(board_kernel: &'static Kernel, chip: &'static C) { + // These symbols are defined in the linker script. + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + } - let peripherals = static_init!(E310xDefaultPeripherals, E310xDefaultPeripherals::new()); + let app_flash = unsafe { + core::slice::from_raw_parts( + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, + ) + }; + + let app_memory = unsafe { + core::slice::from_raw_parts_mut( + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, + ) + }; - // initialize capabilities let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability); - let memory_allocation_cap = create_capability!(capabilities::MemoryAllocationCapability); + kernel::process::load_processes( + board_kernel, + chip, + app_flash, + app_memory, + unsafe { &mut *addr_of_mut!(PROCESSES) }, + &FAULT_RESPONSE, + &process_mgmt_cap, + ) + .unwrap_or_else(|err| { + debug!("Error loading processes!"); + debug!("{:?}", err); + }); +} - peripherals.watchdog.disable(); - peripherals.rtc.disable(); - peripherals.pwm0.disable(); - peripherals.pwm1.disable(); - peripherals.pwm2.disable(); +/// This is in a separate, inline(never) function so that its stack frame is +/// removed when this function returns. Otherwise, the stack space used for +/// these static_inits is wasted. +#[inline(never)] +unsafe fn start() -> ( + &'static kernel::Kernel, + HiFive1, + &'static e310_g002::chip::E310x<'static, E310G002DefaultPeripherals<'static>>, +) { + // only machine mode + rv32i::configure_trap_handler(); - peripherals - .prci - .set_clock_frequency(sifive::prci::ClockFrequency::Freq16Mhz); + let peripherals = static_init!( + E310G002DefaultPeripherals, + E310G002DefaultPeripherals::new(344_000_000) + ); - let main_loop_cap = create_capability!(capabilities::MainLoopCapability); + peripherals.init(); - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); + peripherals.e310x.watchdog.disable(); + peripherals.e310x.rtc.disable(); + peripherals.e310x.pwm0.disable(); + peripherals.e310x.pwm1.disable(); + peripherals.e310x.pwm2.disable(); + peripherals.e310x.uart1.disable(); - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 2], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + peripherals + .e310x + .prci + .set_clock_frequency(sifive::prci::ClockFrequency::Freq344Mhz); + + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); // Configure kernel debug gpios as early as possible kernel::debug::assign_gpios( - Some(&peripherals.gpio_port[22]), // Red + Some(&peripherals.e310x.gpio_port[22]), // Red None, None, ); // Create a shared UART channel for the console and for kernel debug. - let uart_mux = components::console::UartMuxComponent::new( - &peripherals.uart0, - 115200, - dynamic_deferred_caller, - ) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(&peripherals.e310x.uart0, 115200) + .finalize(components::uart_mux_component_static!()); // LEDs - let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( LedLow<'static, sifive::gpio::GpioPin>, - LedLow::new(&peripherals.gpio_port[22]), // Red - LedLow::new(&peripherals.gpio_port[19]), // Green - LedLow::new(&peripherals.gpio_port[21]), // Blue + LedLow::new(&peripherals.e310x.gpio_port[22]), // Red + LedLow::new(&peripherals.e310x.gpio_port[19]), // Green + LedLow::new(&peripherals.e310x.gpio_port[21]), // Blue )); - peripherals - .uart0 - .initialize_gpio_pins(&peripherals.gpio_port[17], &peripherals.gpio_port[16]); + peripherals.e310x.uart0.initialize_gpio_pins( + &peripherals.e310x.gpio_port[17], + &peripherals.e310x.gpio_port[16], + ); let hardware_timer = static_init!( - sifive::clint::Clint, - sifive::clint::Clint::new(&e310x::clint::CLINT_BASE) + e310_g002::chip::E310xClint, + e310_g002::chip::E310xClint::new(&e310_g002::clint::CLINT_BASE) ); // Create a shared virtualization mux layer on top of a single hardware // alarm. let mux_alarm = static_init!( - MuxAlarm<'static, sifive::clint::Clint>, + MuxAlarm<'static, e310_g002::chip::E310xClint>, MuxAlarm::new(hardware_timer) ); hil::time::Alarm::set_alarm_client(hardware_timer, mux_alarm); // Alarm let virtual_alarm_user = static_init!( - VirtualMuxAlarm<'static, sifive::clint::Clint>, + VirtualMuxAlarm<'static, e310_g002::chip::E310xClint>, VirtualMuxAlarm::new(mux_alarm) ); virtual_alarm_user.setup(); let systick_virtual_alarm = static_init!( - VirtualMuxAlarm<'static, sifive::clint::Clint>, + VirtualMuxAlarm<'static, e310_g002::chip::E310xClint>, VirtualMuxAlarm::new(mux_alarm) ); systick_virtual_alarm.setup(); + let memory_allocation_cap = create_capability!(capabilities::MemoryAllocationCapability); let alarm = static_init!( - capsules::alarm::AlarmDriver<'static, VirtualMuxAlarm<'static, sifive::clint::Clint>>, - capsules::alarm::AlarmDriver::new( + capsules_core::alarm::AlarmDriver< + 'static, + VirtualMuxAlarm<'static, e310_g002::chip::E310xClint>, + >, + capsules_core::alarm::AlarmDriver::new( virtual_alarm_user, - board_kernel.create_grant(capsules::alarm::DRIVER_NUM, &memory_allocation_cap) + board_kernel.create_grant(capsules_core::alarm::DRIVER_NUM, &memory_allocation_cap) ) ); hil::time::Alarm::set_alarm_client(virtual_alarm_user, alarm); let chip = static_init!( - e310x::chip::E310x, - e310x::chip::E310x::new(peripherals, hardware_timer) + e310_g002::chip::E310x, + e310_g002::chip::E310x::new(peripherals, hardware_timer) ); CHIP = Some(chip); - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); + let process_console = components::process_console::ProcessConsoleComponent::new( + board_kernel, + uart_mux, + mux_alarm, + process_printer, + None, + ) + .finalize(components::process_console_component_static!( + e310_g002::chip::E310xClint + )); + let _ = process_console.start(); + // Need to enable all interrupts for Tock Kernel chip.enable_plic_interrupts(); @@ -237,78 +305,61 @@ pub unsafe fn main() { // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + const DEBUG_BUFFER_KB: usize = 1; + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!(DEBUG_BUFFER_KB)); let lldb = components::lldb::LowLevelDebugComponent::new( board_kernel, - capsules::low_level_debug::DRIVER_NUM, + capsules_core::low_level_debug::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::low_level_debug_component_static!()); - // Need two debug!() calls to actually test with QEMU. QEMU seems to have - // a much larger UART TX buffer (or it transmits faster). + // Need two debug!() calls to actually test with QEMU. QEMU seems to have a + // much larger UART TX buffer (or it transmits faster). With a single call + // the entire message is printed to console even if the kernel loop does not run debug!("HiFive1 initialization complete."); debug!("Entering main loop."); - /// These symbols are defined in the linker script. - extern "C" { - /// Beginning of the ROM region containing app images. - static _sapps: u8; - /// End of the ROM region containing app images. - static _eapps: u8; - /// Beginning of the RAM region for app memory. - static mut _sappmem: u8; - /// End of the RAM region for app memory. - static _eappmem: u8; - } - - let scheduler = components::sched::cooperative::CooperativeComponent::new(&PROCESSES) - .finalize(components::coop_component_helper!(NUM_PROCS)); + let scheduler = + components::sched::cooperative::CooperativeComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::cooperative_component_static!(NUM_PROCS)); let scheduler_timer = static_init!( - VirtualSchedulerTimer>>, + VirtualSchedulerTimer>>, VirtualSchedulerTimer::new(systick_virtual_alarm) ); let hifive1 = HiFive1 { - console: console, - alarm: alarm, - lldb: lldb, led, + console, + lldb, + alarm, scheduler, scheduler_timer, }; - kernel::process::load_processes( - board_kernel, - chip, - core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, - ), - core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, - ), - &mut PROCESSES, - &FAULT_RESPONSE, - &process_mgmt_cap, - ) - .unwrap_or_else(|err| { - debug!("Error loading processes!"); - debug!("{:?}", err); - }); + load_processes_not_inlined(board_kernel, chip); + + (board_kernel, hifive1, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + let (board_kernel, board, chip) = start(); board_kernel.kernel_loop( - &hifive1, + &board, chip, - None::<&kernel::ipc::IPC>, - &main_loop_cap, + None::<&kernel::ipc::IPC<0>>, + &main_loop_capability, ); } diff --git a/boards/hifive_inventor/Cargo.toml b/boards/hifive_inventor/Cargo.toml new file mode 100644 index 0000000000..775a0ec4da --- /dev/null +++ b/boards/hifive_inventor/Cargo.toml @@ -0,0 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[package] +name = "hifive_inventor" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true + +[dependencies] +components = { path = "../components" } +rv32i = { path = "../../arch/rv32i" } +kernel = { path = "../../kernel" } +e310_g003 = { path = "../../chips/e310_g003" } +sifive = { path = "../../chips/sifive" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/hifive_inventor/Makefile b/boards/hifive_inventor/Makefile new file mode 100644 index 0000000000..7e473ed2b6 --- /dev/null +++ b/boards/hifive_inventor/Makefile @@ -0,0 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +# Makefile for building the tock kernel for the HiFive Inventor platform + +TARGET=riscv32imac-unknown-none-elf +PLATFORM=hifive_inventor +QEMU ?= qemu-system-riscv32 + +include ../Makefile.common + +# Default target for installing the kernel. +.PHONY: install +install: flash-jlink + +TOCKLOADER=tockloader +TOCKLOADER_JTAG_FLAGS = --jlink --board hifive1b +KERNEL_ADDRESS = 0x20010000 + +# upload kernel over JTAG +.PHONY: flash-jlink +flash-jlink: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin + $(TOCKLOADER) $(TOCKLOADER_GENERAL_FLAGS) flash --address $(KERNEL_ADDRESS) $(TOCKLOADER_JTAG_FLAGS) $< diff --git a/boards/hifive_inventor/README.md b/boards/hifive_inventor/README.md new file mode 100644 index 0000000000..7a7c770777 --- /dev/null +++ b/boards/hifive_inventor/README.md @@ -0,0 +1,42 @@ +# BBC HiFive Inventor - FE310-G003 RISC-V Board + + + +The [BBC HiFive Inventor](https://www.hifiveinventor.com/) is a +board based on the SiFive FE310-G003 chip built around the +[E31 Core](https://www.sifive.com/cores/e31). It includes the following +peripherals: + +- 6x8 RGB LED Matrix +- Light Sensor +- LSM303AGR compass and accelerometer +- Bluetooth and Wi-Fi connectivity co-processor + +**At present, the peripherals are not set up.** We are waiting for the schematic. + +## Programming + +When using `tockloader` use settings for board `hifive1b` since they are the +same (same debugger, same kernel and program memory). + +### Using J-Link + +Running `make flash-jlink` should load the kernel onto the board. It requires +you install [J-Link](https://www.segger.com/downloads/jlink#J-LinkSoftwareAndDocumentationPack). +Make sure that the `JLinkExe` executable is accessible starting from your +`PATH` variable. + +If need, use `gdb` to debug the kernel. Start a custom gdb server with +`JLinkGDBServerExe`, or use the following configuration: + +```bash +$ JLinkGDBServerCLExe -select USB -device FE310 -endian little -if JTAG -speed 1200 -noir -noLocalhostOnly +``` + +### Other tools + +I would also like to note that `openocd` support is in developement. +[A update](https://review.openocd.org/c/openocd/+/7135) for adding the flash +ISSI IS25LQ040 chip is on it's way. + +Running in QEMU has not been tested, yet. diff --git a/boards/hifive_inventor/layout.ld b/boards/hifive_inventor/layout.ld new file mode 100644 index 0000000000..dd16de43aa --- /dev/null +++ b/boards/hifive_inventor/layout.ld @@ -0,0 +1,17 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + +/* The HiFive inventor board has 512 MiB of flash and 64 KiB of RAM. + */ + +MEMORY +{ + rom (rx) : ORIGIN = 0x20010000, LENGTH = 0x30000 + prog (rx) : ORIGIN = 0x20040000, LENGTH = 512M-0x430000 + ram (rwx) : ORIGIN = 0x80000000, LENGTH = 0x10000 +} + +MPU_MIN_ALIGN = 1K; + +INCLUDE ../kernel_layout.ld diff --git a/boards/hifive_inventor/src/io.rs b/boards/hifive_inventor/src/io.rs new file mode 100644 index 0000000000..c938b38494 --- /dev/null +++ b/boards/hifive_inventor/src/io.rs @@ -0,0 +1,60 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use core::fmt::Write; +use core::panic::PanicInfo; +use core::str; +use kernel::debug; +use kernel::debug::IoWrite; +use kernel::hil::led; + +use crate::CHIP; +use crate::PROCESSES; +use crate::PROCESS_PRINTER; + +struct Writer {} + +static mut WRITER: Writer = Writer {}; + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> ::core::fmt::Result { + self.write(s.as_bytes()); + Ok(()) + } +} + +impl IoWrite for Writer { + fn write(&mut self, buf: &[u8]) -> usize { + let uart = sifive::uart::Uart::new(e310_g003::uart::UART0_BASE, 16_000_000); + uart.transmit_sync(buf); + buf.len() + } +} + +/// Panic handler. +#[cfg(not(test))] +#[no_mangle] +#[panic_handler] +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { + use core::ptr::{addr_of, addr_of_mut}; + + let led = sifive::gpio::GpioPin::new( + e310_g003::gpio::GPIO0_BASE, + sifive::gpio::pins::pin22, + sifive::gpio::pins::pin22::SET, + sifive::gpio::pins::pin22::CLEAR, + ); + let led = &mut led::LedLow::new(&led); + let writer = &mut *addr_of_mut!(WRITER); + + debug::panic( + &mut [led], + writer, + pi, + &rv32i::support::nop, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), + ) +} diff --git a/boards/hifive_inventor/src/main.rs b/boards/hifive_inventor/src/main.rs new file mode 100644 index 0000000000..5c94f783f7 --- /dev/null +++ b/boards/hifive_inventor/src/main.rs @@ -0,0 +1,325 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Board file for BBC HiFive Inventor RISC-V development platform. +//! +//! - + +#![no_std] +// Disable this attribute when documenting, as a workaround for +// https://github.com/rust-lang/rust/issues/62184. +#![cfg_attr(not(doc), no_main)] + +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use e310_g003::interrupt_service::E310G003DefaultPeripherals; +use kernel::capabilities; +use kernel::component::Component; +use kernel::hil; +use kernel::platform::scheduler_timer::VirtualSchedulerTimer; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::scheduler::cooperative::CooperativeSched; +use kernel::utilities::registers::interfaces::ReadWriteable; +use kernel::{create_capability, debug, static_init}; +use rv32i::csr; + +pub mod io; + +pub const NUM_PROCS: usize = 4; +// +// Actual memory for holding the active process structures. Need an empty list +// at least. +static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = + [None; NUM_PROCS]; + +// Reference to the chip for panic dumps. +static mut CHIP: Option<&'static e310_g003::chip::E310x> = None; +// Reference to the process printer for panic dumps. +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; + +// How should the kernel respond when a process faults. +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; + +/// Dummy buffer that causes the linker to reserve enough space for the stack. +#[no_mangle] +#[link_section = ".stack_buffer"] +pub static mut STACK_MEMORY: [u8; 0x1500] = [0; 0x1500]; + +/// A structure representing this platform that holds references to all +/// capsules for this platform. We've included an alarm and console. +struct HiFiveInventor { + console: &'static capsules_core::console::Console<'static>, + lldb: &'static capsules_core::low_level_debug::LowLevelDebug< + 'static, + capsules_core::virtualizers::virtual_uart::UartDevice<'static>, + >, + alarm: &'static capsules_core::alarm::AlarmDriver< + 'static, + VirtualMuxAlarm<'static, e310_g003::chip::E310xClint<'static>>, + >, + scheduler: &'static CooperativeSched<'static>, + scheduler_timer: &'static VirtualSchedulerTimer< + VirtualMuxAlarm<'static, e310_g003::chip::E310xClint<'static>>, + >, +} + +/// Mapping of integer syscalls to objects that implement syscalls. +impl SyscallDriverLookup for HiFiveInventor { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::low_level_debug::DRIVER_NUM => f(Some(self.lldb)), + _ => f(None), + } + } +} + +impl KernelResources>> + for HiFiveInventor +{ + type SyscallDriverLookup = Self; + type SyscallFilter = (); + type ProcessFault = (); + type Scheduler = CooperativeSched<'static>; + type SchedulerTimer = + VirtualSchedulerTimer>>; + type WatchDog = (); + type ContextSwitchCallback = (); + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + &() + } + fn process_fault(&self) -> &Self::ProcessFault { + &() + } + fn scheduler(&self) -> &Self::Scheduler { + self.scheduler + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + self.scheduler_timer + } + fn watchdog(&self) -> &Self::WatchDog { + &() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + &() + } +} + +/// This is in a separate, inline(never) function so that its stack frame is +/// removed when this function returns. Otherwise, the stack space used for +/// these static_inits is wasted. +#[inline(never)] +unsafe fn start() -> ( + &'static kernel::Kernel, + HiFiveInventor, + &'static e310_g003::chip::E310x<'static, E310G003DefaultPeripherals<'static>>, +) { + // only machine mode + rv32i::configure_trap_handler(); + + let peripherals = static_init!( + E310G003DefaultPeripherals, + E310G003DefaultPeripherals::new(16_000_000) + ); + + // Setup any recursive dependencies and register deferred calls: + peripherals.init(); + + peripherals + .e310x + .prci + .set_clock_frequency(sifive::prci::ClockFrequency::Freq16Mhz); + + peripherals.e310x.uart0.initialize_gpio_pins( + &peripherals.e310x.gpio_port[17], + &peripherals.e310x.gpio_port[16], + ); + peripherals.e310x.uart1.initialize_gpio_pins( + &peripherals.e310x.gpio_port[18], + &peripherals.e310x.gpio_port[23], + ); + + peripherals.e310x.watchdog.disable(); + peripherals.e310x.rtc.disable(); + peripherals.e310x.pwm0.disable(); + peripherals.e310x.pwm1.disable(); + peripherals.e310x.pwm2.disable(); + peripherals.e310x.uart1.disable(); + + // initialize capabilities + let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability); + let memory_allocation_cap = create_capability!(capabilities::MemoryAllocationCapability); + + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); + + // Configure kernel debug gpios as early as possible + kernel::debug::assign_gpios(None, None, None); + + // Create a shared UART channel for the console and for kernel debug. + let uart_mux = components::console::UartMuxComponent::new(&peripherals.e310x.uart0, 115200) + .finalize(components::uart_mux_component_static!()); + + let hardware_timer = static_init!( + e310_g003::chip::E310xClint, + e310_g003::chip::E310xClint::new(&e310_g003::clint::CLINT_BASE) + ); + + // Create a shared virtualization mux layer on top of a single hardware + // alarm. + let mux_alarm = static_init!( + MuxAlarm<'static, e310_g003::chip::E310xClint>, + MuxAlarm::new(hardware_timer) + ); + hil::time::Alarm::set_alarm_client(hardware_timer, mux_alarm); + + // Alarm + let virtual_alarm_user = static_init!( + VirtualMuxAlarm<'static, e310_g003::chip::E310xClint>, + VirtualMuxAlarm::new(mux_alarm) + ); + virtual_alarm_user.setup(); + + let systick_virtual_alarm = static_init!( + VirtualMuxAlarm<'static, e310_g003::chip::E310xClint>, + VirtualMuxAlarm::new(mux_alarm) + ); + systick_virtual_alarm.setup(); + + let alarm = static_init!( + capsules_core::alarm::AlarmDriver< + 'static, + VirtualMuxAlarm<'static, e310_g003::chip::E310xClint>, + >, + capsules_core::alarm::AlarmDriver::new( + virtual_alarm_user, + board_kernel.create_grant(capsules_core::alarm::DRIVER_NUM, &memory_allocation_cap) + ) + ); + hil::time::Alarm::set_alarm_client(virtual_alarm_user, alarm); + + let chip = static_init!( + e310_g003::chip::E310x, + e310_g003::chip::E310x::new(peripherals, hardware_timer) + ); + CHIP = Some(chip); + + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); + PROCESS_PRINTER = Some(process_printer); + + let process_console = components::process_console::ProcessConsoleComponent::new( + board_kernel, + uart_mux, + mux_alarm, + process_printer, + None, + ) + .finalize(components::process_console_component_static!( + e310_g003::chip::E310xClint + )); + let _ = process_console.start(); + + // Need to enable all interrupts for Tock Kernel + chip.enable_plic_interrupts(); + + // enable interrupts globally + csr::CSR + .mie + .modify(csr::mie::mie::mext::SET + csr::mie::mie::msoft::SET + csr::mie::mie::mtimer::SET); + csr::CSR.mstatus.modify(csr::mstatus::mstatus::mie::SET); + + // Setup the console. + let console = components::console::ConsoleComponent::new( + board_kernel, + capsules_core::console::DRIVER_NUM, + uart_mux, + ) + .finalize(components::console_component_static!()); + // Create the debugger object that handles calls to `debug!()`. + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); + + let lldb = components::lldb::LowLevelDebugComponent::new( + board_kernel, + capsules_core::low_level_debug::DRIVER_NUM, + uart_mux, + ) + .finalize(components::low_level_debug_component_static!()); + + debug!("HiFive1 initialization complete. Entering main loop."); + + // These symbols are defined in the linker script. + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + } + + let scheduler = + components::sched::cooperative::CooperativeComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::cooperative_component_static!(NUM_PROCS)); + + let scheduler_timer = static_init!( + VirtualSchedulerTimer>>, + VirtualSchedulerTimer::new(systick_virtual_alarm) + ); + let hifive1 = HiFiveInventor { + console, + lldb, + alarm, + scheduler, + scheduler_timer, + }; + kernel::process::load_processes( + board_kernel, + chip, + core::slice::from_raw_parts( + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, + ), + core::slice::from_raw_parts_mut( + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, + ), + &mut *addr_of_mut!(PROCESSES), + &FAULT_RESPONSE, + &process_mgmt_cap, + ) + .unwrap_or_else(|err| { + debug!("Error loading processes!"); + debug!("{:?}", err); + }); + + (board_kernel, hifive1, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, board, chip) = start(); + board_kernel.kernel_loop( + &board, + chip, + None::<&kernel::ipc::IPC<0>>, + &main_loop_capability, + ); +} diff --git a/boards/imix/Cargo.toml b/boards/imix/Cargo.toml index 8d4801b5cc..bd7558bbc1 100644 --- a/boards/imix/Cargo.toml +++ b/boards/imix/Cargo.toml @@ -1,13 +1,23 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "imix" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] components = { path = "../components" } cortexm4 = { path = "../../arch/cortex-m4" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } sam4l = { path = "../../chips/sam4l" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/imix/Makefile b/boards/imix/Makefile index 18b084d7f8..574e606fcb 100644 --- a/boards/imix/Makefile +++ b/boards/imix/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the Imix platform TARGET=thumbv7em-none-eabi diff --git a/boards/imix/README.md b/boards/imix/README.md index 20866367d3..9ad8aaad51 100644 --- a/boards/imix/README.md +++ b/boards/imix/README.md @@ -3,6 +3,14 @@ imix: Platform-Specific Instructions This board file is for imix version 2. +This board requires that applications have a SHA256 credential. +SHA256 credentials can be added to TBF objects using the `--sha256` +command line option in `elf2tab`. The simplest way to do so for +libtock-c applications is to add + +`ELF2TAB_ARGS += --sha256` at the end of the application Makefile, +after the line in which `AppMakefile.mk` is included. + ## Userspace Resource Mapping diff --git a/boards/imix/build.rs b/boards/imix/build.rs deleted file mode 100644 index 3051054fc6..0000000000 --- a/boards/imix/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/imix/chip_layout.ld b/boards/imix/chip_layout.ld index 2007c83828..195403e389 100644 --- a/boards/imix/chip_layout.ld +++ b/boards/imix/chip_layout.ld @@ -1,3 +1,7 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + /* Memory Spaces Definitions, 448K flash, 64K ram */ /* Use bootloader starting at 0x0000 */ MEMORY @@ -6,5 +10,3 @@ MEMORY prog (rx) : ORIGIN = 0x00040000, LENGTH = 0x00040000 ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00010000 } - -MPU_MIN_ALIGN = 8K; diff --git a/boards/imix/connect.cfg b/boards/imix/connect.cfg index 8dc2a3914e..4bd80a79ae 100644 --- a/boards/imix/connect.cfg +++ b/boards/imix/connect.cfg @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + interface jlink transport select swd source [find target/at91sam4lXX.cfg] diff --git a/boards/imix/debug.gdb b/boards/imix/debug.gdb index ecf1e3e4c4..6228c08994 100644 --- a/boards/imix/debug.gdb +++ b/boards/imix/debug.gdb @@ -1,3 +1,6 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. target remote :3333 monitor reset diff --git a/boards/imix/layout.ld b/boards/imix/layout.ld index 234dcaea2c..6814c00acd 100644 --- a/boards/imix/layout.ld +++ b/boards/imix/layout.ld @@ -1,2 +1,6 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + INCLUDE ./chip_layout.ld INCLUDE ../kernel_layout.ld diff --git a/boards/imix/src/alarm_test.rs b/boards/imix/src/alarm_test.rs index 7ee1e405aa..c3cfce3412 100644 --- a/boards/imix/src/alarm_test.rs +++ b/boards/imix/src/alarm_test.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test the behavior of a single alarm. //! To add this test, include the line //! ``` @@ -12,7 +16,7 @@ //! debug_gpio on pin XX so you can more precisely check //! the timings with a logic analyzer. -use capsules::test::alarm_edge_cases::TestAlarmEdgeCases; +use capsules_core::test::alarm_edge_cases::TestAlarmEdgeCases; use kernel::debug; use kernel::hil::time::Alarm; use kernel::static_init; diff --git a/boards/imix/src/imix_components/adc.rs b/boards/imix/src/imix_components/adc.rs deleted file mode 100644 index 8ea0f0203c..0000000000 --- a/boards/imix/src/imix_components/adc.rs +++ /dev/null @@ -1,90 +0,0 @@ -//! Component for the ADC on the imix board. -//! -//! This provides one Component, AdcComponent, which implements the -//! dedicated userspace syscall interface to the SAM4L ADC. It -//! provides 6 ADC channels, AD0-AD5. -//! -//! Usage -//! ----- -//! ```rust -//! let adc = AdcComponent::new().finalize(()); -//! ``` - -// Author: Philip Levis -// Last modified: 6/20/2018 - -#![allow(dead_code)] // Components are intended to be conditionally included - -use capsules::adc; -use kernel::capabilities; -use kernel::component::Component; -use kernel::create_capability; -use kernel::static_init; -use sam4l::adc::Channel; - -pub struct AdcComponent { - board_kernel: &'static kernel::Kernel, - driver_num: usize, - adc: &'static sam4l::adc::Adc, -} - -impl AdcComponent { - pub fn new( - board_kernel: &'static kernel::Kernel, - driver_num: usize, - adc: &'static sam4l::adc::Adc, - ) -> AdcComponent { - AdcComponent { - board_kernel, - driver_num, - adc, - } - } -} - -impl Component for AdcComponent { - type StaticInput = (); - type Output = &'static adc::AdcDedicated<'static, sam4l::adc::Adc>; - - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { - let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let adc_channels = static_init!( - [sam4l::adc::AdcChannel; 6], - [ - sam4l::adc::AdcChannel::new(Channel::AD1), // AD0 - sam4l::adc::AdcChannel::new(Channel::AD2), // AD1 - sam4l::adc::AdcChannel::new(Channel::AD3), // AD2 - sam4l::adc::AdcChannel::new(Channel::AD4), // AD3 - sam4l::adc::AdcChannel::new(Channel::AD5), // AD4 - sam4l::adc::AdcChannel::new(Channel::AD6), // AD5 - ] - ); - // Capsule expects references inside array bc it was built assuming model in which - // global structs are used, so this is a bit of a hack to pass it what it wants. - let ref_channels = static_init!( - [&sam4l::adc::AdcChannel; 6], - [ - &adc_channels[0], - &adc_channels[1], - &adc_channels[2], - &adc_channels[3], - &adc_channels[4], - &adc_channels[5], - ] - ); - let adc = static_init!( - adc::AdcDedicated<'static, sam4l::adc::Adc>, - adc::AdcDedicated::new( - &self.adc, - self.board_kernel.create_grant(self.driver_num, &grant_cap), - ref_channels, - &mut adc::ADC_BUFFER1, - &mut adc::ADC_BUFFER2, - &mut adc::ADC_BUFFER3 - ) - ); - self.adc.set_client(adc); - - adc - } -} diff --git a/boards/imix/src/imix_components/fxos8700.rs b/boards/imix/src/imix_components/fxos8700.rs deleted file mode 100644 index ac6588c928..0000000000 --- a/boards/imix/src/imix_components/fxos8700.rs +++ /dev/null @@ -1,109 +0,0 @@ -//! Components for the FXOS8700 on the imix board. -//! -//! This provides two Components. Fxos8700Component provides a kernel -//! implementation of the Fxos8700 over I2C, while NineDofComponent -//! provides a system call interface to the sensor. Note that only one -//! of these components should be allocated, as they use the same -//! static buffer: NineDofComponent instantiations a -//! Fxos8700Component, so if your code creates both components, then -//! there will be two Fxos8700Component instances conflicting on the -//! buffer. -//! -//! Usage -//! ----- -//! ```rust -//! let ninedof = NineDofComponent::new(mux_i2c, &sam4l::gpio::PC[13]).finalize(()); -//! let fxos8700 = Fxos8700Component::new(mux_i2c, &sam4l::gpio::PC[13]).finalize(()); -//! ``` - -// Author: Philip Levis -// Last modified: 6/20/2018 - -#![allow(dead_code)] // Components are intended to be conditionally included -#![allow(unused_imports)] // I2CDevice - -use capsules::fxos8700cq; -use capsules::ninedof::NineDof; -use capsules::virtual_i2c::{I2CDevice, MuxI2C}; -use kernel::capabilities; -use kernel::component::Component; -use kernel::create_capability; -use kernel::hil; -use kernel::static_init; - -pub struct Fxos8700Component { - i2c_mux: &'static MuxI2C<'static>, - gpio: &'static sam4l::gpio::GPIOPin<'static>, -} - -impl Fxos8700Component { - pub fn new( - i2c: &'static MuxI2C<'static>, - gpio: &'static sam4l::gpio::GPIOPin<'static>, - ) -> Fxos8700Component { - Fxos8700Component { - i2c_mux: i2c, - gpio: gpio, - } - } -} - -impl Component for Fxos8700Component { - type StaticInput = (); - type Output = &'static fxos8700cq::Fxos8700cq<'static>; - - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { - let fxos8700_i2c = static_init!(I2CDevice, I2CDevice::new(self.i2c_mux, 0x1e)); - let fxos8700 = static_init!( - fxos8700cq::Fxos8700cq<'static>, - fxos8700cq::Fxos8700cq::new(fxos8700_i2c, self.gpio, &mut fxos8700cq::BUF) - ); - fxos8700_i2c.set_client(fxos8700); - self.gpio.set_client(fxos8700); - fxos8700 - } -} - -pub struct NineDofComponent { - board_kernel: &'static kernel::Kernel, - driver_num: usize, - i2c_mux: &'static MuxI2C<'static>, - gpio: &'static sam4l::gpio::GPIOPin<'static>, -} - -impl NineDofComponent { - pub fn new( - board_kernel: &'static kernel::Kernel, - driver_num: usize, - i2c: &'static MuxI2C<'static>, - gpio: &'static sam4l::gpio::GPIOPin, - ) -> NineDofComponent { - NineDofComponent { - board_kernel: board_kernel, - driver_num: driver_num, - i2c_mux: i2c, - gpio: gpio, - } - } -} - -impl Component for NineDofComponent { - type StaticInput = (); - type Output = &'static NineDof<'static>; - - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { - let fxos8700_i2c = static_init!(I2CDevice, I2CDevice::new(self.i2c_mux, 0x1e)); - let fxos8700 = static_init!( - fxos8700cq::Fxos8700cq<'static>, - fxos8700cq::Fxos8700cq::new(fxos8700_i2c, self.gpio, &mut fxos8700cq::BUF) - ); - fxos8700_i2c.set_client(fxos8700); - self.gpio.set_client(fxos8700); - - let ninedof = - components::ninedof::NineDofComponent::new(self.board_kernel, self.driver_num) - .finalize(components::ninedof_component_helper!(fxos8700)); - - ninedof - } -} diff --git a/boards/imix/src/imix_components/mod.rs b/boards/imix/src/imix_components/mod.rs index e047f46ae5..de47e607d0 100644 --- a/boards/imix/src/imix_components/mod.rs +++ b/boards/imix/src/imix_components/mod.rs @@ -1,10 +1,5 @@ -pub mod adc; -pub mod fxos8700; -pub mod rf233; -pub mod test; -pub mod usb; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. -pub use self::adc::AdcComponent; -pub use self::fxos8700::NineDofComponent; -pub use self::rf233::RF233Component; -pub use self::usb::UsbComponent; +pub mod test; diff --git a/boards/imix/src/imix_components/rf233.rs b/boards/imix/src/imix_components/rf233.rs deleted file mode 100644 index 67c84d36b6..0000000000 --- a/boards/imix/src/imix_components/rf233.rs +++ /dev/null @@ -1,67 +0,0 @@ -//! Component for communicating with the RF233 (802.15.4) on imix boards. -//! -//! This provides one Component, RF233Component, which provides basic -//! packet-level interfaces for communicating with 802.15.4. -//! -//! Usage -//! ----- -//! ```rust -//! let rf233 = RF233Component::new(rf233_spi, -//! &sam4l::gpio::PA[09], // reset -//! &sam4l::gpio::PA[10], // sleep -//! &sam4l::gpio::PA[08], // irq -//! &sam4l::gpio::PA[08]).finalize(()); -//! ``` - -// Author: Philip Levis - -use capsules::rf233::RF233; -use capsules::virtual_spi::VirtualSpiMasterDevice; -use kernel::component::Component; -use kernel::hil; -use kernel::hil::spi::SpiMasterDevice; -use kernel::static_init; - -pub struct RF233Component { - spi: &'static VirtualSpiMasterDevice<'static, sam4l::spi::SpiHw>, - reset: &'static dyn hil::gpio::Pin, - sleep: &'static dyn hil::gpio::Pin, - irq: &'static dyn hil::gpio::InterruptPin<'static>, - ctl: &'static sam4l::gpio::GPIOPin<'static>, - channel: u8, -} - -impl RF233Component { - pub fn new( - spi: &'static VirtualSpiMasterDevice<'static, sam4l::spi::SpiHw>, - reset: &'static dyn hil::gpio::Pin, - sleep: &'static dyn hil::gpio::Pin, - irq: &'static dyn hil::gpio::InterruptPin<'static>, - ctl: &'static sam4l::gpio::GPIOPin<'static>, - channel: u8, - ) -> RF233Component { - RF233Component { - spi: spi, - reset: reset, - sleep: sleep, - irq: irq, - ctl: ctl, - channel: channel, - } - } -} - -impl Component for RF233Component { - type StaticInput = (); - type Output = &'static RF233<'static, VirtualSpiMasterDevice<'static, sam4l::spi::SpiHw>>; - - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { - let rf233: &RF233<'static, VirtualSpiMasterDevice<'static, sam4l::spi::SpiHw>> = static_init!( - RF233<'static, VirtualSpiMasterDevice<'static, sam4l::spi::SpiHw>>, - RF233::new(self.spi, self.reset, self.sleep, self.irq, self.channel) - ); - self.ctl.set_client(rf233); - self.spi.set_client(rf233); - rf233 - } -} diff --git a/boards/imix/src/imix_components/test/mock_udp.rs b/boards/imix/src/imix_components/test/mock_udp.rs index 15a16dd2b5..7ca7e2bede 100644 --- a/boards/imix/src/imix_components/test/mock_udp.rs +++ b/boards/imix/src/imix_components/test/mock_udp.rs @@ -1,24 +1,52 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component to test in kernel udp // Author: Hudson Ayers #![allow(dead_code)] // Components are intended to be conditionally included -use capsules::net::ipv6::ipv6_send::IP6SendStruct; -use capsules::net::network_capabilities::{NetworkCapability, UdpVisibilityCapability}; -use capsules::net::udp::udp_recv::{MuxUdpReceiver, UDPReceiver}; -use capsules::net::udp::udp_send::{MuxUdpSender, UDPSendStruct, UDPSender}; - -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; - -use capsules::net::udp::udp_port_table::UdpPortManager; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_extra::net::ipv6::ipv6_send::IP6SendStruct; +use capsules_extra::net::network_capabilities::{NetworkCapability, UdpVisibilityCapability}; +use capsules_extra::net::udp::udp_port_table::UdpPortManager; +use capsules_extra::net::udp::udp_recv::{MuxUdpReceiver, UDPReceiver}; +use capsules_extra::net::udp::udp_send::{MuxUdpSender, UDPSendStruct, UDPSender}; +use core::mem::MaybeUninit; use kernel::component::Component; use kernel::hil::time::Alarm; -use kernel::static_init; use kernel::utilities::cells::TakeCell; +#[macro_export] +/// Macro for constructing a mock UDP capsule for tests. +macro_rules! mock_udp_component_static { + () => {{ + use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; + use capsules_extra::net::udp::udp_recv::UDPReceiver; + use capsules_extra::net::udp::udp_send::UDPSendStruct; + let udp_send = kernel::static_buf!( + UDPSendStruct< + 'static, + capsules_extra::net::ipv6::ipv6_send::IP6SendStruct< + 'static, + VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>, + >, + > + ); + + let udp_recv = kernel::static_buf!(UDPReceiver<'static>); + let udp_alarm = kernel::static_buf!(VirtualMuxAlarm<'static, sam4l::ast::Ast>,); + + let mock_udp = kernel::static_buf!( + capsules_extra::test::udp::MockUdp<'static, VirtualMuxAlarm<'static, sam4l::ast::Ast>>, + ); + (udp_send, udp_recv, udp_alarm, mock_udp) + }}; +} + pub struct MockUDPComponent { - // TODO: consider putting bound_port_table in a TakeCell udp_send_mux: &'static MuxUdpSender< 'static, IP6SendStruct<'static, VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>>, @@ -49,62 +77,66 @@ impl MockUDPComponent { udp_vis: &'static UdpVisibilityCapability, ) -> MockUDPComponent { MockUDPComponent { - udp_send_mux: udp_send_mux, - udp_recv_mux: udp_recv_mux, - bound_port_table: bound_port_table, + udp_send_mux, + udp_recv_mux, + bound_port_table, alarm_mux: alarm, udp_payload: TakeCell::new(udp_payload), - id: id, - dst_port: dst_port, - net_cap: net_cap, - udp_vis: udp_vis, + id, + dst_port, + net_cap, + udp_vis, } } } impl Component for MockUDPComponent { - type StaticInput = (); - type Output = &'static capsules::test::udp::MockUdp< - 'static, - VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>, - >; - - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { - let udp_send = static_init!( + type StaticInput = ( + &'static mut MaybeUninit< UDPSendStruct< 'static, - capsules::net::ipv6::ipv6_send::IP6SendStruct< + capsules_extra::net::ipv6::ipv6_send::IP6SendStruct< 'static, VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>, >, >, - UDPSendStruct::new(self.udp_send_mux, self.udp_vis) - ); + >, + &'static mut MaybeUninit>, + &'static mut MaybeUninit>>, + &'static mut MaybeUninit< + capsules_extra::test::udp::MockUdp< + 'static, + VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>, + >, + >, + ); + type Output = &'static capsules_extra::test::udp::MockUdp< + 'static, + VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>, + >; - let udp_recv = static_init!(UDPReceiver<'static>, UDPReceiver::new()); + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let udp_send = + s.0.write(UDPSendStruct::new(self.udp_send_mux, self.udp_vis)); + + let udp_recv = s.1.write(UDPReceiver::new()); self.udp_recv_mux.add_client(udp_recv); - let udp_alarm = static_init!( - VirtualMuxAlarm<'static, sam4l::ast::Ast>, - VirtualMuxAlarm::new(self.alarm_mux) - ); + let udp_alarm = s.2.write(VirtualMuxAlarm::new(self.alarm_mux)); udp_alarm.setup(); - let mock_udp = static_init!( - capsules::test::udp::MockUdp<'static, VirtualMuxAlarm<'static, sam4l::ast::Ast>>, - capsules::test::udp::MockUdp::new( - self.id, - udp_alarm, - udp_send, - udp_recv, - self.bound_port_table, - kernel::utilities::leasable_buffer::LeasableBuffer::new( - self.udp_payload.take().expect("missing payload") - ), - self.dst_port, - self.net_cap, - ) - ); + let mock_udp = s.3.write(capsules_extra::test::udp::MockUdp::new( + self.id, + udp_alarm, + udp_send, + udp_recv, + self.bound_port_table, + kernel::utilities::leasable_buffer::SubSliceMut::new( + self.udp_payload.take().expect("missing payload"), + ), + self.dst_port, + self.net_cap, + )); udp_send.set_client(mock_udp); udp_recv.set_client(mock_udp); udp_alarm.set_alarm_client(mock_udp); diff --git a/boards/imix/src/imix_components/test/mock_udp2.rs b/boards/imix/src/imix_components/test/mock_udp2.rs deleted file mode 100644 index ceb7e8c613..0000000000 --- a/boards/imix/src/imix_components/test/mock_udp2.rs +++ /dev/null @@ -1,115 +0,0 @@ -//! Component to test in kernel udp. -//! -//! Duplicate of mock_udp.rs. Can't call original component twice because it uses static_init!() -//! so have to rely on duplicate files. - -// Author: Hudson Ayers - -#![allow(dead_code)] // Components are intended to be conditionally included - -use capsules::net::ipv6::ipv6_send::IP6SendStruct; -use capsules::net::network_capabilities::{NetworkCapability, UdpVisibilityCapability}; -use capsules::net::udp::udp_recv::{MuxUdpReceiver, UDPReceiver}; -use capsules::net::udp::udp_send::{MuxUdpSender, UDPSendStruct, UDPSender}; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; - -use capsules::net::udp::udp_port_table::UdpPortManager; -use kernel::component::Component; -use kernel::hil::time::Alarm; -use kernel::static_init; -use kernel::utilities::cells::TakeCell; - -pub struct MockUDPComponent2 { - // TODO: consider putting bound_port_table in a TakeCell - udp_send_mux: &'static MuxUdpSender< - 'static, - IP6SendStruct<'static, VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>>, - >, - udp_recv_mux: &'static MuxUdpReceiver<'static>, - bound_port_table: &'static UdpPortManager, - alarm_mux: &'static MuxAlarm<'static, sam4l::ast::Ast<'static>>, - udp_payload: TakeCell<'static, [u8]>, - id: u16, - dst_port: u16, - net_cap: &'static NetworkCapability, - udp_vis: &'static UdpVisibilityCapability, -} - -impl MockUDPComponent2 { - pub fn new( - udp_send_mux: &'static MuxUdpSender< - 'static, - IP6SendStruct<'static, VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>>, - >, - udp_recv_mux: &'static MuxUdpReceiver<'static>, - bound_port_table: &'static UdpPortManager, - alarm: &'static MuxAlarm<'static, sam4l::ast::Ast<'static>>, - udp_payload: &'static mut [u8], - id: u16, - dst_port: u16, - net_cap: &'static NetworkCapability, - udp_vis: &'static UdpVisibilityCapability, - ) -> MockUDPComponent2 { - MockUDPComponent2 { - udp_send_mux: udp_send_mux, - udp_recv_mux: udp_recv_mux, - bound_port_table: bound_port_table, - alarm_mux: alarm, - udp_payload: TakeCell::new(udp_payload), - id: id, - dst_port: dst_port, - net_cap: net_cap, - udp_vis: udp_vis, - } - } -} - -impl Component for MockUDPComponent2 { - type StaticInput = (); - type Output = &'static capsules::test::udp::MockUdp< - 'static, - VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>, - >; - - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { - let udp_send = static_init!( - UDPSendStruct< - 'static, - capsules::net::ipv6::ipv6_send::IP6SendStruct< - 'static, - VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>, - >, - >, - UDPSendStruct::new(self.udp_send_mux, self.udp_vis) - ); - - let udp_recv = static_init!(UDPReceiver<'static>, UDPReceiver::new()); - self.udp_recv_mux.add_client(udp_recv); - - let udp_alarm = static_init!( - VirtualMuxAlarm<'static, sam4l::ast::Ast>, - VirtualMuxAlarm::new(self.alarm_mux) - ); - udp_alarm.setup(); - - let mock_udp = static_init!( - capsules::test::udp::MockUdp<'static, VirtualMuxAlarm<'static, sam4l::ast::Ast>>, - capsules::test::udp::MockUdp::new( - self.id, - udp_alarm, - udp_send, - udp_recv, - self.bound_port_table, - kernel::utilities::leasable_buffer::LeasableBuffer::new( - self.udp_payload.take().expect("missing payload") - ), - self.dst_port, - self.net_cap, - ) - ); - udp_send.set_client(mock_udp); - udp_recv.set_client(mock_udp); - udp_alarm.set_alarm_client(mock_udp); - mock_udp - } -} diff --git a/boards/imix/src/imix_components/test/mod.rs b/boards/imix/src/imix_components/test/mod.rs index 29f3125609..e7e58361ca 100644 --- a/boards/imix/src/imix_components/test/mod.rs +++ b/boards/imix/src/imix_components/test/mod.rs @@ -1,5 +1,5 @@ -pub mod mock_udp; -pub mod mock_udp2; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. -pub use self::mock_udp::MockUDPComponent; -pub use self::mock_udp2::MockUDPComponent2; +pub mod mock_udp; diff --git a/boards/imix/src/imix_components/usb.rs b/boards/imix/src/imix_components/usb.rs deleted file mode 100644 index da5e860e27..0000000000 --- a/boards/imix/src/imix_components/usb.rs +++ /dev/null @@ -1,79 +0,0 @@ -//! Component for USB on the imix board. -//! -//! This provides one Component, UsbComponent, which implements -//! a userspace syscall interface to the USB peripheral on the SAM4L. -//! -//! Usage -//! ----- -//! ```rust -//! let usb = UsbComponent::new().finalize(()); -//! ``` - -// Author: Philip Levis -// Last modified: 6/20/2018 - -#![allow(dead_code)] // Components are intended to be conditionally included - -use kernel::capabilities; -use kernel::component::Component; -use kernel::create_capability; -use kernel::hil::usb::UsbController; -use kernel::static_init; - -pub struct UsbComponent { - board_kernel: &'static kernel::Kernel, - driver_num: usize, - usbc: &'static sam4l::usbc::Usbc<'static>, -} - -type UsbDevice = capsules::usb::usb_user::UsbSyscallDriver< - 'static, - capsules::usb::usbc_client::Client<'static, sam4l::usbc::Usbc<'static>>, ->; - -impl UsbComponent { - pub fn new( - board_kernel: &'static kernel::Kernel, - driver_num: usize, - usbc: &'static sam4l::usbc::Usbc<'static>, - ) -> UsbComponent { - UsbComponent { - board_kernel, - driver_num, - usbc, - } - } -} - -impl Component for UsbComponent { - type StaticInput = (); - type Output = &'static UsbDevice; - - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { - let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - - // Configure the USB controller - let usb_client = static_init!( - capsules::usb::usbc_client::Client<'static, sam4l::usbc::Usbc<'static>>, - capsules::usb::usbc_client::Client::new( - &self.usbc, - capsules::usb::usbc_client::MAX_CTRL_PACKET_SIZE_SAM4L - ) - ); - self.usbc.set_client(usb_client); - - // Configure the USB userspace driver - let usb_driver = static_init!( - capsules::usb::usb_user::UsbSyscallDriver< - 'static, - capsules::usb::usbc_client::Client<'static, sam4l::usbc::Usbc<'static>>, - >, - capsules::usb::usb_user::UsbSyscallDriver::new( - usb_client, - self.board_kernel.create_grant(self.driver_num, &grant_cap) - ) - ); - - usb_driver - } -} diff --git a/boards/imix/src/io.rs b/boards/imix/src/io.rs index bae8bcf2be..5ba0f2d33a 100644 --- a/boards/imix/src/io.rs +++ b/boards/imix/src/io.rs @@ -1,11 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; -use cortexm4; use kernel::debug; use kernel::debug::IoWrite; use kernel::hil::led; use kernel::hil::uart::{self, Configure}; -use sam4l; use crate::CHIP; use crate::PROCESSES; @@ -25,7 +27,7 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { // Here, we create a second instance of the USART3 struct. // This is okay because we only call this during a panic, and // we will never actually process the interrupts @@ -43,10 +45,13 @@ impl IoWrite for Writer { uart.enable_tx(regs_manager); } // XXX: I'd like to get this working the "right" way, but I'm not sure how + let mut total = 0; for &c in buf { uart.send_byte(regs_manager, c); while !uart.tx_ready(regs_manager) {} + total += 1; } + total } } @@ -54,17 +59,19 @@ impl IoWrite for Writer { #[cfg(not(test))] #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { + use core::ptr::{addr_of, addr_of_mut}; + let led_pin = sam4l::gpio::GPIOPin::new(sam4l::gpio::Pin::PC22); let led = &mut led::LedLow::new(&led_pin); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led], writer, pi, &cortexm4::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/imix/src/main.rs b/boards/imix/src/main.rs index 4704f6d9fd..f25ee7c91e 100644 --- a/boards/imix/src/main.rs +++ b/boards/imix/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Board file for Imix development platform. //! //! - @@ -10,17 +14,19 @@ #![deny(missing_docs)] mod imix_components; -use capsules::alarm::AlarmDriver; -use capsules::net::ieee802154::MacAddress; -use capsules::net::ipv6::ip_utils::IPAddr; -use capsules::virtual_aes_ccm::MuxAES128CCM; -use capsules::virtual_alarm::VirtualMuxAlarm; -use capsules::virtual_i2c::MuxI2C; -use capsules::virtual_spi::VirtualSpiMasterDevice; -//use capsules::virtual_timer::MuxTimer; +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::alarm::AlarmDriver; +use capsules_core::console_ordered::ConsoleOrdered; +use capsules_core::virtualizers::virtual_aes_ccm::MuxAES128CCM; +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; +use capsules_core::virtualizers::virtual_i2c::MuxI2C; +use capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice; +use capsules_extra::net::ieee802154::MacAddress; +use capsules_extra::net::ipv6::ip_utils::IPAddr; use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; +use kernel::deferred_call::DeferredCallClient; use kernel::hil::i2c::I2CMaster; use kernel::hil::radio; #[allow(unused_imports)] @@ -28,30 +34,27 @@ use kernel::hil::radio::{RadioConfig, RadioData}; use kernel::hil::symmetric_encryption::AES128; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::scheduler::round_robin::RoundRobinSched; + //use kernel::hil::time::Alarm; use kernel::hil::led::LedHigh; use kernel::hil::Controller; #[allow(unused_imports)] -use kernel::{create_capability, debug, debug_gpio, static_init}; +use kernel::{create_capability, debug, debug_gpio, static_buf, static_init}; use sam4l::chip::Sam4lDefaultPeripherals; -use components; use components::alarm::{AlarmDriverComponent, AlarmMuxComponent}; -use components::console::{ConsoleComponent, UartMuxComponent}; +use components::console::{ConsoleOrderedComponent, UartMuxComponent}; use components::crc::CrcComponent; use components::debug_writer::DebugWriterComponent; use components::gpio::GpioComponent; use components::isl29035::AmbientLightComponent; +use components::isl29035::Isl29035Component; use components::led::LedsComponent; use components::nrf51822::Nrf51822Component; use components::process_console::ProcessConsoleComponent; use components::rng::RngComponent; -use components::si7021::{HumidityComponent, SI7021Component}; +use components::si7021::SI7021Component; use components::spi::{SpiComponent, SpiSyscallComponent}; -use imix_components::adc::AdcComponent; -use imix_components::fxos8700::NineDofComponent; -use imix_components::rf233::RF233Component; -use imix_components::usb::UsbComponent; /// Support routines for debugging I/O. /// @@ -83,105 +86,119 @@ const NUM_PROCS: usize = 4; // have those devices talk to each other without having to modify the kernel flashed // onto each device. This makes MAC address configuration a good target for capabilities - // only allow one app per board to have control of MAC address configuration? -const RADIO_CHANNEL: u8 = 26; +const RADIO_CHANNEL: radio::RadioChannel = radio::RadioChannel::Channel26; const DST_MAC_ADDR: MacAddress = MacAddress::Short(49138); const DEFAULT_CTX_PREFIX_LEN: u8 = 8; //Length of context for 6LoWPAN compression -const DEFAULT_CTX_PREFIX: [u8; 16] = [0x0 as u8; 16]; //Context for 6LoWPAN Compression +const DEFAULT_CTX_PREFIX: [u8; 16] = [0x0_u8; 16]; //Context for 6LoWPAN Compression const PAN_ID: u16 = 0xABCD; // how should the kernel respond when a process faults -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::StopFaultPolicy = + capsules_system::process_policies::StopFaultPolicy {}; static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = [None; NUM_PROCS]; static mut CHIP: Option<&'static sam4l::chip::Sam4l> = None; -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; /// Dummy buffer that causes the linker to reserve enough space for the stack. #[no_mangle] #[link_section = ".stack_buffer"] pub static mut STACK_MEMORY: [u8; 0x2000] = [0; 0x2000]; +type SI7021Sensor = components::si7021::SI7021ComponentType< + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, sam4l::i2c::I2CHw<'static>>, +>; +type TemperatureDriver = components::temperature::TemperatureComponentType; +type HumidityDriver = components::humidity::HumidityComponentType; +type RngDriver = components::rng::RngComponentType>; + +type Rf233 = capsules_extra::rf233::RF233< + 'static, + VirtualSpiMasterDevice<'static, sam4l::spi::SpiHw<'static>>, +>; +type Ieee802154MacDevice = + components::ieee802154::Ieee802154ComponentMacDeviceType>; + struct Imix { - pconsole: &'static capsules::process_console::ProcessConsole< + pconsole: &'static capsules_core::process_console::ProcessConsole< 'static, - capsules::virtual_alarm::VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>, + { capsules_core::process_console::DEFAULT_COMMAND_HISTORY_LEN }, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + sam4l::ast::Ast<'static>, + >, components::process_console::Capability, >, - console: &'static capsules::console::Console<'static>, - gpio: &'static capsules::gpio::GPIO<'static, sam4l::gpio::GPIOPin<'static>>, + console: &'static capsules_core::console_ordered::ConsoleOrdered< + 'static, + VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>, + >, + gpio: &'static capsules_core::gpio::GPIO<'static, sam4l::gpio::GPIOPin<'static>>, alarm: &'static AlarmDriver<'static, VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>>, - temp: &'static capsules::temperature::TemperatureSensor<'static>, - humidity: &'static capsules::humidity::HumiditySensor<'static>, - ambient_light: &'static capsules::ambient_light::AmbientLight<'static>, - adc: &'static capsules::adc::AdcDedicated<'static, sam4l::adc::Adc>, - led: &'static capsules::led::LedDriver< + temp: &'static TemperatureDriver, + humidity: &'static HumidityDriver, + ambient_light: &'static capsules_extra::ambient_light::AmbientLight<'static>, + adc: &'static capsules_core::adc::AdcDedicated<'static, sam4l::adc::Adc<'static>>, + led: &'static capsules_core::led::LedDriver< 'static, LedHigh<'static, sam4l::gpio::GPIOPin<'static>>, 1, >, - button: &'static capsules::button::Button<'static, sam4l::gpio::GPIOPin<'static>>, - rng: &'static capsules::rng::RngDriver<'static>, - analog_comparator: &'static capsules::analog_comparator::AnalogComparator< + button: &'static capsules_core::button::Button<'static, sam4l::gpio::GPIOPin<'static>>, + rng: &'static RngDriver, + analog_comparator: &'static capsules_extra::analog_comparator::AnalogComparator< 'static, sam4l::acifc::Acifc<'static>, >, - spi: &'static capsules::spi_controller::Spi< + spi: &'static capsules_core::spi_controller::Spi< 'static, - VirtualSpiMasterDevice<'static, sam4l::spi::SpiHw>, + VirtualSpiMasterDevice<'static, sam4l::spi::SpiHw<'static>>, >, - ipc: kernel::ipc::IPC, - ninedof: &'static capsules::ninedof::NineDof<'static>, - udp_driver: &'static capsules::net::udp::UDPDriver<'static>, - crc: &'static capsules::crc::CrcDriver<'static, sam4l::crccu::Crccu<'static>>, - usb_driver: &'static capsules::usb::usb_user::UsbSyscallDriver< + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + ninedof: &'static capsules_extra::ninedof::NineDof<'static>, + udp_driver: &'static capsules_extra::net::udp::UDPDriver<'static>, + crc: &'static capsules_extra::crc::CrcDriver<'static, sam4l::crccu::Crccu<'static>>, + usb_driver: &'static capsules_extra::usb::usb_user::UsbSyscallDriver< 'static, - capsules::usb::usbc_client::Client<'static, sam4l::usbc::Usbc<'static>>, + capsules_extra::usb::usbc_client::Client<'static, sam4l::usbc::Usbc<'static>>, >, - nrf51822: &'static capsules::nrf51822_serialization::Nrf51822Serialization<'static>, - nonvolatile_storage: &'static capsules::nonvolatile_storage_driver::NonvolatileStorage<'static>, + nrf51822: &'static capsules_extra::nrf51822_serialization::Nrf51822Serialization<'static>, + nonvolatile_storage: + &'static capsules_extra::nonvolatile_storage_driver::NonvolatileStorage<'static>, scheduler: &'static RoundRobinSched<'static>, systick: cortexm4::systick::SysTick, } -// The RF233 radio stack requires our buffers for its SPI operations: -// -// 1. buf: a packet-sized buffer for SPI operations, which is -// used as the read buffer when it writes a packet passed to it and the write -// buffer when it reads a packet into a buffer passed to it. -// 2. rx_buf: buffer to receive packets into -// 3 + 4: two small buffers for performing registers -// operations (one read, one write). - -static mut RF233_BUF: [u8; radio::MAX_BUF_SIZE] = [0x00; radio::MAX_BUF_SIZE]; -static mut RF233_REG_WRITE: [u8; 2] = [0x00; 2]; -static mut RF233_REG_READ: [u8; 2] = [0x00; 2]; - impl SyscallDriverLookup for Imix { fn with_driver(&self, driver_num: usize, f: F) -> R where F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::spi_controller::DRIVER_NUM => f(Some(self.spi)), - capsules::adc::DRIVER_NUM => f(Some(self.adc)), - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::button::DRIVER_NUM => f(Some(self.button)), - capsules::analog_comparator::DRIVER_NUM => f(Some(self.analog_comparator)), - capsules::ambient_light::DRIVER_NUM => f(Some(self.ambient_light)), - capsules::temperature::DRIVER_NUM => f(Some(self.temp)), - capsules::humidity::DRIVER_NUM => f(Some(self.humidity)), - capsules::ninedof::DRIVER_NUM => f(Some(self.ninedof)), - capsules::crc::DRIVER_NUM => f(Some(self.crc)), - capsules::usb::usb_user::DRIVER_NUM => f(Some(self.usb_driver)), - capsules::net::udp::DRIVER_NUM => f(Some(self.udp_driver)), - capsules::nrf51822_serialization::DRIVER_NUM => f(Some(self.nrf51822)), - capsules::nonvolatile_storage_driver::DRIVER_NUM => f(Some(self.nonvolatile_storage)), - capsules::rng::DRIVER_NUM => f(Some(self.rng)), + capsules_core::console_ordered::DRIVER_NUM => f(Some(self.console)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::spi_controller::DRIVER_NUM => f(Some(self.spi)), + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_extra::analog_comparator::DRIVER_NUM => f(Some(self.analog_comparator)), + capsules_extra::ambient_light::DRIVER_NUM => f(Some(self.ambient_light)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temp)), + capsules_extra::humidity::DRIVER_NUM => f(Some(self.humidity)), + capsules_extra::ninedof::DRIVER_NUM => f(Some(self.ninedof)), + capsules_extra::crc::DRIVER_NUM => f(Some(self.crc)), + capsules_extra::usb::usb_user::DRIVER_NUM => f(Some(self.usb_driver)), + capsules_extra::net::udp::DRIVER_NUM => f(Some(self.udp_driver)), + capsules_extra::nrf51822_serialization::DRIVER_NUM => f(Some(self.nrf51822)), + capsules_extra::nonvolatile_storage_driver::DRIVER_NUM => { + f(Some(self.nonvolatile_storage)) + } + capsules_core::rng::DRIVER_NUM => f(Some(self.rng)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), _ => f(None), } @@ -198,7 +215,7 @@ impl KernelResources> for Imix { type ContextSwitchCallback = (); fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -294,20 +311,14 @@ unsafe fn set_pin_primary_functions(peripherals: &Sam4lDefaultPeripherals) { /// removed when this function returns. Otherwise, the stack space used for /// these static_inits is wasted. #[inline(never)] -unsafe fn get_peripherals( - pm: &'static sam4l::pm::PowerManager, -) -> &'static Sam4lDefaultPeripherals { - static_init!(Sam4lDefaultPeripherals, Sam4lDefaultPeripherals::new(pm)) -} - -/// Main function. -/// -/// This is called after RAM initialization is complete. -#[no_mangle] -pub unsafe fn main() { +unsafe fn start() -> ( + &'static kernel::Kernel, + Imix, + &'static sam4l::chip::Sam4l, +) { sam4l::init(); let pm = static_init!(sam4l::pm::PowerManager, sam4l::pm::PowerManager::new()); - let peripherals = get_peripherals(pm); + let peripherals = static_init!(Sam4lDefaultPeripherals, Sam4lDefaultPeripherals::new(pm)); pm.setup_system_clock( sam4l::pm::SystemClockSource::PllExternalOscillatorAt48MHz { @@ -322,7 +333,7 @@ pub unsafe fn main() { set_pin_primary_functions(peripherals); - peripherals.setup_dma(); + peripherals.setup_circular_deps(); let chip = static_init!( sam4l::chip::Sam4l, sam4l::chip::Sam4l::new(pm, peripherals) @@ -331,8 +342,6 @@ pub unsafe fn main() { // Create capabilities that the board needs to call certain protected kernel // functions. - let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability); - let main_cap = create_capability!(capabilities::MainLoopCapability); let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); power::configure_submodules( @@ -347,97 +356,125 @@ pub unsafe fn main() { }, ); - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 5], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); - - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); // # CONSOLE // Create a shared UART channel for the consoles and for kernel debug. peripherals.usart3.set_mode(sam4l::usart::UsartMode::Uart); - let uart_mux = - UartMuxComponent::new(&peripherals.usart3, 115200, dynamic_deferred_caller).finalize(()); + let uart_mux = UartMuxComponent::new(&peripherals.usart3, 115200) + .finalize(components::uart_mux_component_static!()); // # TIMER let mux_alarm = AlarmMuxComponent::new(&peripherals.ast) - .finalize(components::alarm_mux_component_helper!(sam4l::ast::Ast)); + .finalize(components::alarm_mux_component_static!(sam4l::ast::Ast)); peripherals.ast.configure(mux_alarm); - let alarm = AlarmDriverComponent::new(board_kernel, capsules::alarm::DRIVER_NUM, mux_alarm) - .finalize(components::alarm_component_helper!(sam4l::ast::Ast)); - let pconsole = ProcessConsoleComponent::new(board_kernel, uart_mux, mux_alarm, process_printer) - .finalize(components::process_console_component_helper!( - sam4l::ast::Ast - )); - let console = - ConsoleComponent::new(board_kernel, capsules::console::DRIVER_NUM, uart_mux).finalize(()); - DebugWriterComponent::new(uart_mux).finalize(()); + let alarm = + AlarmDriverComponent::new(board_kernel, capsules_core::alarm::DRIVER_NUM, mux_alarm) + .finalize(components::alarm_component_static!(sam4l::ast::Ast)); + + let pconsole = ProcessConsoleComponent::new( + board_kernel, + uart_mux, + mux_alarm, + process_printer, + Some(cortexm4::support::reset), + ) + .finalize(components::process_console_component_static!( + sam4l::ast::Ast + )); + + let console = ConsoleOrderedComponent::new( + board_kernel, + capsules_core::console_ordered::DRIVER_NUM, + uart_mux, + mux_alarm, + 200, + 5, + 5, + ) + .finalize(components::console_ordered_component_static!( + sam4l::ast::Ast + )); + DebugWriterComponent::new(uart_mux).finalize(components::debug_writer_component_static!()); // Allow processes to communicate over BLE through the nRF51822 peripherals.usart2.set_mode(sam4l::usart::UsartMode::Uart); let nrf_serialization = Nrf51822Component::new( board_kernel, - capsules::nrf51822_serialization::DRIVER_NUM, + capsules_extra::nrf51822_serialization::DRIVER_NUM, &peripherals.usart2, &peripherals.pb[07], ) - .finalize(()); + .finalize(components::nrf51822_component_static!()); // # I2C and I2C Sensors let mux_i2c = static_init!( - MuxI2C<'static>, - MuxI2C::new(&peripherals.i2c2, None, dynamic_deferred_caller) + MuxI2C<'static, sam4l::i2c::I2CHw<'static>>, + MuxI2C::new(&peripherals.i2c2, None) ); + kernel::deferred_call::DeferredCallClient::register(mux_i2c); peripherals.i2c2.set_master_client(mux_i2c); + let isl29035 = Isl29035Component::new(mux_i2c, mux_alarm).finalize( + components::isl29035_component_static!(sam4l::ast::Ast, sam4l::i2c::I2CHw<'static>), + ); let ambient_light = AmbientLightComponent::new( board_kernel, - capsules::ambient_light::DRIVER_NUM, - mux_i2c, - mux_alarm, + capsules_extra::ambient_light::DRIVER_NUM, + isl29035, ) - .finalize(components::isl29035_component_helper!(sam4l::ast::Ast)); - let si7021 = SI7021Component::new(mux_i2c, mux_alarm, 0x40) - .finalize(components::si7021_component_helper!(sam4l::ast::Ast)); + .finalize(components::ambient_light_component_static!()); + + let si7021 = SI7021Component::new(mux_i2c, mux_alarm, 0x40).finalize( + components::si7021_component_static!(sam4l::ast::Ast, sam4l::i2c::I2CHw<'static>), + ); let temp = components::temperature::TemperatureComponent::new( board_kernel, - capsules::temperature::DRIVER_NUM, + capsules_extra::temperature::DRIVER_NUM, si7021, ) - .finalize(()); - let humidity = - HumidityComponent::new(board_kernel, capsules::ninedof::DRIVER_NUM, si7021).finalize(()); - let ninedof = NineDofComponent::new( + .finalize(components::temperature_component_static!(SI7021Sensor)); + let humidity = components::humidity::HumidityComponent::new( board_kernel, - capsules::ninedof::DRIVER_NUM, - mux_i2c, - &peripherals.pc[13], + capsules_extra::humidity::DRIVER_NUM, + si7021, ) - .finalize(()); + .finalize(components::humidity_component_static!(SI7021Sensor)); + + let fxos8700 = components::fxos8700::Fxos8700Component::new(mux_i2c, 0x1e, &peripherals.pc[13]) + .finalize(components::fxos8700_component_static!( + sam4l::i2c::I2CHw<'static> + )); + + let ninedof = components::ninedof::NineDofComponent::new( + board_kernel, + capsules_extra::ninedof::DRIVER_NUM, + ) + .finalize(components::ninedof_component_static!(fxos8700)); // SPI MUX, SPI syscall driver and RF233 radio - let mux_spi = components::spi::SpiMuxComponent::new(&peripherals.spi, dynamic_deferred_caller) - .finalize(components::spi_mux_component_helper!(sam4l::spi::SpiHw)); + let mux_spi = components::spi::SpiMuxComponent::new(&peripherals.spi).finalize( + components::spi_mux_component_static!(sam4l::spi::SpiHw<'static>), + ); let spi_syscalls = SpiSyscallComponent::new( board_kernel, mux_spi, 2, - capsules::spi_controller::DRIVER_NUM, + capsules_core::spi_controller::DRIVER_NUM, ) - .finalize(components::spi_syscall_component_helper!(sam4l::spi::SpiHw)); - let rf233_spi = SpiComponent::new(mux_spi, 3) - .finalize(components::spi_component_helper!(sam4l::spi::SpiHw)); - let rf233 = RF233Component::new( + .finalize(components::spi_syscall_component_static!( + sam4l::spi::SpiHw<'static> + )); + let rf233_spi = SpiComponent::new(mux_spi, 3).finalize(components::spi_component_static!( + sam4l::spi::SpiHw<'static> + )); + let rf233 = components::rf233::RF233Component::new( rf233_spi, &peripherals.pa[09], // reset &peripherals.pa[10], // sleep @@ -445,13 +482,33 @@ pub unsafe fn main() { &peripherals.pa[08], RADIO_CHANNEL, ) - .finalize(()); + .finalize(components::rf233_component_static!( + sam4l::spi::SpiHw<'static> + )); + + // Setup ADC + let adc_channels = static_init!( + [sam4l::adc::AdcChannel; 6], + [ + sam4l::adc::AdcChannel::new(sam4l::adc::Channel::AD1), // AD0 + sam4l::adc::AdcChannel::new(sam4l::adc::Channel::AD2), // AD1 + sam4l::adc::AdcChannel::new(sam4l::adc::Channel::AD3), // AD2 + sam4l::adc::AdcChannel::new(sam4l::adc::Channel::AD4), // AD3 + sam4l::adc::AdcChannel::new(sam4l::adc::Channel::AD5), // AD4 + sam4l::adc::AdcChannel::new(sam4l::adc::Channel::AD6), // AD5 + ] + ); + let adc = components::adc::AdcDedicatedComponent::new( + &peripherals.adc, + adc_channels, + board_kernel, + capsules_core::adc::DRIVER_NUM, + ) + .finalize(components::adc_dedicated_component_static!(sam4l::adc::Adc)); - let adc = - AdcComponent::new(board_kernel, capsules::adc::DRIVER_NUM, &peripherals.adc).finalize(()); let gpio = GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( sam4l::gpio::GPIOPin, 0 => &peripherals.pc[31], @@ -463,16 +520,16 @@ pub unsafe fn main() { 6 => &peripherals.pa[20] ), ) - .finalize(components::gpio_component_buf!(sam4l::gpio::GPIOPin)); + .finalize(components::gpio_component_static!(sam4l::gpio::GPIOPin)); - let led = LedsComponent::new().finalize(components::led_component_helper!( + let led = LedsComponent::new().finalize(components::led_component_static!( LedHigh<'static, sam4l::gpio::GPIOPin>, LedHigh::new(&peripherals.pc[10]), )); let button = components::button::ButtonComponent::new( board_kernel, - capsules::button::DRIVER_NUM, + capsules_core::button::DRIVER_NUM, components::button_component_helper!( sam4l::gpio::GPIOPin, ( @@ -482,10 +539,14 @@ pub unsafe fn main() { ) ), ) - .finalize(components::button_component_buf!(sam4l::gpio::GPIOPin)); + .finalize(components::button_component_static!(sam4l::gpio::GPIOPin)); - let crc = CrcComponent::new(board_kernel, capsules::crc::DRIVER_NUM, &peripherals.crccu) - .finalize(components::crc_component_helper!(sam4l::crccu::Crccu)); + let crc = CrcComponent::new( + board_kernel, + capsules_extra::crc::DRIVER_NUM, + &peripherals.crccu, + ) + .finalize(components::crc_component_static!(sam4l::crccu::Crccu)); let ac_0 = static_init!( sam4l::acifc::AcChannel, @@ -503,9 +564,9 @@ pub unsafe fn main() { sam4l::acifc::AcChannel, sam4l::acifc::AcChannel::new(sam4l::acifc::Channel::AC0) ); - let analog_comparator = components::analog_comparator::AcComponent::new( + let analog_comparator = components::analog_comparator::AnalogComparatorComponent::new( &peripherals.acifc, - components::acomp_component_helper!( + components::analog_comparator_component_helper!( ::Channel, ac_0, ac_1, @@ -513,52 +574,58 @@ pub unsafe fn main() { ac_3 ), board_kernel, - capsules::analog_comparator::DRIVER_NUM, + capsules_extra::analog_comparator::DRIVER_NUM, ) - .finalize(components::acomp_component_buf!(sam4l::acifc::Acifc)); - let rng = - RngComponent::new(board_kernel, capsules::rng::DRIVER_NUM, &peripherals.trng).finalize(()); + .finalize(components::analog_comparator_component_static!( + sam4l::acifc::Acifc + )); + let rng = RngComponent::new( + board_kernel, + capsules_core::rng::DRIVER_NUM, + &peripherals.trng, + ) + .finalize(components::rng_component_static!(sam4l::trng::Trng)); // For now, assign the 802.15.4 MAC address on the device as // simply a 16-bit short address which represents the last 16 bits // of the serial number of the sam4l for this device. In the - // future, we could generate the MAC address by hashing the full + // future, we could generate the DEFAULT_EXT_SRC_MAC address by hashing the full // 120-bit serial number let serial_num: sam4l::serial_num::SerialNum = sam4l::serial_num::SerialNum::new(); let serial_num_bottom_16 = (serial_num.get_lower_64() & 0x0000_0000_0000_ffff) as u16; let src_mac_from_serial_num: MacAddress = MacAddress::Short(serial_num_bottom_16); + const DEFAULT_EXT_SRC_MAC: [u8; 8] = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77]; let aes_mux = static_init!( MuxAES128CCM<'static, sam4l::aes::Aes>, - MuxAES128CCM::new(&peripherals.aes, dynamic_deferred_caller) + MuxAES128CCM::new(&peripherals.aes) ); + aes_mux.register(); peripherals.aes.set_client(aes_mux); - aes_mux.initialize_callback_handle( - dynamic_deferred_caller.register(aes_mux).unwrap(), // Unwrap fail = no deferred call slot available for ccm mux - ); - // Can this initialize be pushed earlier, or into component? -pal - let _ = rf233.initialize(&mut RF233_BUF, &mut RF233_REG_WRITE, &mut RF233_REG_READ); let (_, mux_mac) = components::ieee802154::Ieee802154Component::new( board_kernel, - capsules::ieee802154::DRIVER_NUM, + capsules_extra::ieee802154::DRIVER_NUM, rf233, aes_mux, PAN_ID, serial_num_bottom_16, - dynamic_deferred_caller, + DEFAULT_EXT_SRC_MAC, ) - .finalize(components::ieee802154_component_helper!( - capsules::rf233::RF233<'static, VirtualSpiMasterDevice<'static, sam4l::spi::SpiHw>>, + .finalize(components::ieee802154_component_static!( + capsules_extra::rf233::RF233< + 'static, + VirtualSpiMasterDevice<'static, sam4l::spi::SpiHw<'static>>, + >, sam4l::aes::Aes<'static> )); - let usb_driver = UsbComponent::new( + let usb_driver = components::usb::UsbComponent::new( board_kernel, - capsules::usb::usb_user::DRIVER_NUM, + capsules_extra::usb::usb_user::DRIVER_NUM, &peripherals.usbc, ) - .finalize(()); + .finalize(components::usb_component_static!(sam4l::usbc::Usbc)); // Kernel storage region, allocated with the storage_volume! // macro in common/utils.rs @@ -570,14 +637,14 @@ pub unsafe fn main() { let nonvolatile_storage = components::nonvolatile_storage::NonvolatileStorageComponent::new( board_kernel, - capsules::nonvolatile_storage_driver::DRIVER_NUM, + capsules_extra::nonvolatile_storage_driver::DRIVER_NUM, &peripherals.flash_controller, - 0x60000, // Start address for userspace accessible region - 0x20000, // Length of userspace accessible region - &_sstorage as *const u8 as usize, //start address of kernel region - &_estorage as *const u8 as usize - &_sstorage as *const u8 as usize, // length of kernel region + 0x60000, // Start address for userspace accessible region + 0x20000, // Length of userspace accessible region + core::ptr::addr_of!(_sstorage) as usize, //start address of kernel region + core::ptr::addr_of!(_estorage) as usize - core::ptr::addr_of!(_sstorage) as usize, // length of kernel region ) - .finalize(components::nv_storage_component_helper!( + .finalize(components::nonvolatile_storage_component_static!( sam4l::flashcalw::FLASHCALW )); @@ -606,21 +673,54 @@ pub unsafe fn main() { local_ip_ifaces, mux_alarm, ) - .finalize(components::udp_mux_component_helper!(sam4l::ast::Ast)); + .finalize(components::udp_mux_component_static!( + sam4l::ast::Ast, + Ieee802154MacDevice + )); // UDP driver initialization happens here let udp_driver = components::udp_driver::UDPDriverComponent::new( board_kernel, - capsules::net::udp::driver::DRIVER_NUM, + capsules_extra::net::udp::driver::DRIVER_NUM, udp_send_mux, udp_recv_mux, udp_port_table, local_ip_ifaces, ) - .finalize(components::udp_driver_component_helper!(sam4l::ast::Ast)); + .finalize(components::udp_driver_component_static!(sam4l::ast::Ast)); + + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + // Create the software-based SHA engine. + let sha = components::sha::ShaSoftware256Component::new() + .finalize(components::sha_software_256_component_static!()); + + // Create the credential checker. + let checking_policy = components::appid::checker_sha::AppCheckerSha256Component::new(sha) + .finalize(components::app_checker_sha256_component_static!()); + + // Create the AppID assigner. + let assigner = components::appid::assigner_name::AppIdAssignerNamesComponent::new() + .finalize(components::appid_assigner_names_component_static!()); + + // Create the process checking machine. + let checker = components::appid::checker::ProcessCheckerMachineComponent::new(checking_policy) + .finalize(components::process_checker_machine_component_static!()); + + // Create and start the asynchronous process loader. + let _loader = components::loader::sequential::ProcessLoaderSequentialComponent::new( + checker, + &mut *addr_of_mut!(PROCESSES), + board_kernel, + chip, + &FAULT_RESPONSE, + assigner, + ) + .finalize(components::process_loader_sequential_component_static!( + sam4l::chip::Sam4l, + NUM_PROCS + )); let imix = Imix { pconsole, @@ -665,17 +765,15 @@ pub unsafe fn main() { // //test::virtual_uart_rx_test::run_virtual_uart_receive(uart_mux); //test::rng_test::run_entropy32(&peripherals.trng); - //test::virtual_aes_ccm_test::run(&peripherals.aes, dynamic_deferred_caller); + //test::virtual_aes_ccm_test::run(&peripherals.aes); //test::aes_test::run_aes128_ctr(&peripherals.aes); //test::aes_test::run_aes128_cbc(&peripherals.aes); //test::log_test::run( // mux_alarm, - // dynamic_deferred_caller, // &peripherals.flash_controller, //); //test::linear_log_test::run( // mux_alarm, - // dynamic_deferred_caller, // &peripherals.flash_controller, //); //test::icmp_lowpan_test::run(mux_mac, mux_alarm); @@ -702,43 +800,22 @@ pub unsafe fn main() { );*/ //virtual_alarm_timer.set_alarm_client(mux_timer); + //test::sha256_test::run_sha256(); + /*components::test::multi_alarm_test::MultiAlarmTestComponent::new(mux_alarm) .finalize(components::multi_alarm_test_component_buf!(sam4l::ast::Ast)) .run();*/ debug!("Initialization complete. Entering main loop"); - /// These symbols are defined in the linker script. - extern "C" { - /// Beginning of the ROM region containing app images. - static _sapps: u8; - /// End of the ROM region containing app images. - static _eapps: u8; - /// Beginning of the RAM region for app memory. - static mut _sappmem: u8; - /// End of the RAM region for app memory. - static _eappmem: u8; - } + (board_kernel, imix, chip) +} - kernel::process::load_processes( - board_kernel, - chip, - core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, - ), - core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, - ), - &mut PROCESSES, - &FAULT_RESPONSE, - &process_mgmt_cap, - ) - .unwrap_or_else(|err| { - debug!("Error loading processes!"); - debug!("{:?}", err); - }); +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); - board_kernel.kernel_loop(&imix, chip, Some(&imix.ipc), &main_cap); + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); } diff --git a/boards/imix/src/multi_timer_test.rs b/boards/imix/src/multi_timer_test.rs index 02ed66d674..10b99362a9 100644 --- a/boards/imix/src/multi_timer_test.rs +++ b/boards/imix/src/multi_timer_test.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test the behavior of a single alarm. //! To add this test, include the line //! ``` @@ -12,8 +16,8 @@ use kernel::debug; use kernel::hil::time::Timer; use kernel::static_init; -use capsules::test::random_timer::TestRandomTimer; -use capsules::virtual_timer::{MuxTimer, VirtualTimer}; +use capsules_core::test::random_timer::TestRandomTimer; +use capsules_core::virtualizers::virtual_timer::{MuxTimer, VirtualTimer}; use sam4l::ast::Ast; pub unsafe fn run_multi_timer(mux: &'static MuxTimer<'static, Ast<'static>>) { diff --git a/boards/imix/src/power.rs b/boards/imix/src/power.rs index bef5a3c3fd..61c945d5fd 100644 --- a/boards/imix/src/power.rs +++ b/boards/imix/src/power.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implements a helper function for enabling/disabling power on the imix //! submodules. // On imix, submodules are powered on/off via power gate ICs. The MCU has an diff --git a/boards/imix/src/test/aes_test.rs b/boards/imix/src/test/aes_test.rs index 8bae62cd59..13411daaf6 100644 --- a/boards/imix/src/test/aes_test.rs +++ b/boards/imix/src/test/aes_test.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test that AES (either CTR or CBC mode) is working properly. //! //! To test CBC mode, add the following line to the imix boot sequence: @@ -21,8 +25,8 @@ //! aes_test CTR passed: (CTR Dec Ctr Src/Dst) //! ``` -use capsules::test::aes::TestAes128Cbc; -use capsules::test::aes::TestAes128Ctr; +use capsules_extra::test::aes::TestAes128Cbc; +use capsules_extra::test::aes::TestAes128Ctr; use kernel::hil::symmetric_encryption::{AES128, AES128_BLOCK_SIZE, AES128_KEY_SIZE}; use kernel::static_init; use sam4l::aes::Aes; @@ -49,7 +53,7 @@ unsafe fn static_init_test_ctr(aes: &'static Aes) -> &'static TestAes128Ctr<'sta static_init!( TestAes128Ctr<'static, Aes>, - TestAes128Ctr::new(&aes, key, iv, source, data) + TestAes128Ctr::new(aes, key, iv, source, data, true) ) } @@ -61,6 +65,6 @@ unsafe fn static_init_test_cbc(aes: &'static Aes) -> &'static TestAes128Cbc<'sta static_init!( TestAes128Cbc<'static, Aes>, - TestAes128Cbc::new(&aes, key, iv, source, data) + TestAes128Cbc::new(aes, key, iv, source, data, true) ) } diff --git a/boards/imix/src/test/crc_test.rs b/boards/imix/src/test/crc_test.rs index d54b9529b7..31495155d9 100644 --- a/boards/imix/src/test/crc_test.rs +++ b/boards/imix/src/test/crc_test.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test that CRC is working properly. //! //! To test, add the following line to the imix boot sequence: @@ -17,7 +21,7 @@ //! taken from //! -use capsules::test::crc::TestCrc; +use capsules_extra::test::crc::TestCrc; use kernel::hil::crc::Crc; use kernel::static_init; use sam4l::crccu::Crccu; @@ -33,7 +37,7 @@ unsafe fn static_init_crc(crc: &'static Crccu) -> &'static TestCrc<'static, Crcc let data = static_init!([u8; 9], [0; 9]); for i in 0..9 { - data[i] = i as u8 + ('1' as u8); + data[i] = i as u8 + b'1'; } - static_init!(TestCrc<'static, Crccu>, TestCrc::new(&crc, data)) + static_init!(TestCrc<'static, Crccu>, TestCrc::new(crc, data)) } diff --git a/boards/imix/src/test/i2c_dummy.rs b/boards/imix/src/test/i2c_dummy.rs index 9aa22f36c6..39a4bc721c 100644 --- a/boards/imix/src/test/i2c_dummy.rs +++ b/boards/imix/src/test/i2c_dummy.rs @@ -1,6 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! A dummy I2C client use core::cell::Cell; +use core::ptr::addr_of_mut; use kernel::debug; use kernel::hil; use kernel::hil::i2c::{Error, I2CMaster}; @@ -11,11 +16,11 @@ use kernel::hil::i2c::{Error, I2CMaster}; struct ScanClient { dev_id: Cell, - i2c_master: &'static dyn I2CMaster, + i2c_master: &'static dyn I2CMaster<'static>, } impl ScanClient { - pub fn new(i2c_master: &'static dyn I2CMaster) -> Self { + pub fn new(i2c_master: &'static dyn I2CMaster<'static>) -> Self { Self { dev_id: Cell::new(1), i2c_master, @@ -31,7 +36,7 @@ impl hil::i2c::I2CHwMasterClient for ScanClient { debug!("{:#x}", dev_id); } - let dev: &dyn I2CMaster = self.i2c_master; + let dev: &dyn I2CMaster<'static> = self.i2c_master; if dev_id < 0x7F { dev_id += 1; self.dev_id.set(dev_id); @@ -46,7 +51,7 @@ impl hil::i2c::I2CHwMasterClient for ScanClient { } /// This test should be called with I2C2, specifically -pub fn i2c_scan_slaves(i2c_master: &'static mut dyn I2CMaster) { +pub fn i2c_scan_slaves(i2c_master: &'static dyn I2CMaster<'static>) { static mut DATA: [u8; 255] = [0; 255]; let dev = i2c_master; @@ -57,8 +62,12 @@ pub fn i2c_scan_slaves(i2c_master: &'static mut dyn I2CMaster) { dev.enable(); debug!("Scanning for I2C devices..."); - dev.write(i2c_client.dev_id.get(), unsafe { &mut DATA }, 2) - .unwrap(); + dev.write( + i2c_client.dev_id.get(), + unsafe { &mut *addr_of_mut!(DATA) }, + 2, + ) + .unwrap(); } // =========================================== @@ -75,11 +84,11 @@ enum AccelClientState { struct AccelClient { state: Cell, - i2c_master: &'static dyn I2CMaster, + i2c_master: &'static dyn I2CMaster<'static>, } impl AccelClient { - pub fn new(i2c_master: &'static dyn I2CMaster) -> Self { + pub fn new(i2c_master: &'static dyn I2CMaster<'static>) -> Self { Self { state: Cell::new(AccelClientState::ReadingWhoami), i2c_master, @@ -95,16 +104,16 @@ impl hil::i2c::I2CHwMasterClient for AccelClient { AccelClientState::ReadingWhoami => { debug!("WHOAMI Register 0x{:x} ({:?})", buffer[0], status); debug!("Activating Sensor..."); - buffer[0] = 0x2A as u8; // CTRL_REG1 + buffer[0] = 0x2A_u8; // CTRL_REG1 buffer[1] = 1; // Bit 1 sets `active` dev.write(0x1e, buffer, 2).unwrap(); self.state.set(AccelClientState::Activating); } AccelClientState::Activating => { debug!("Sensor Activated ({:?})", status); - buffer[0] = 0x01 as u8; // X-MSB register - // Reading 6 bytes will increment the register pointer through - // X-MSB, X-LSB, Y-MSB, Y-LSB, Z-MSB, Z-LSB + buffer[0] = 0x01_u8; // X-MSB register + // Reading 6 bytes will increment the register pointer through + // X-MSB, X-LSB, Y-MSB, Y-LSB, Z-MSB, Z-LSB dev.write_read(0x1e, buffer, 1, 6).unwrap(); self.state.set(AccelClientState::ReadingAccelData); } @@ -125,16 +134,16 @@ impl hil::i2c::I2CHwMasterClient for AccelClient { status ); - buffer[0] = 0x01 as u8; // X-MSB register - // Reading 6 bytes will increment the register pointer through - // X-MSB, X-LSB, Y-MSB, Y-LSB, Z-MSB, Z-LSB + buffer[0] = 0x01_u8; // X-MSB register + // Reading 6 bytes will increment the register pointer through + // X-MSB, X-LSB, Y-MSB, Y-LSB, Z-MSB, Z-LSB dev.write_read(0x1e, buffer, 1, 6).unwrap(); self.state.set(AccelClientState::ReadingAccelData); } AccelClientState::Deactivating => { debug!("Sensor deactivated ({:?})", status); debug!("Reading Accel's WHOAMI..."); - buffer[0] = 0x0D as u8; // 0x0D == WHOAMI register + buffer[0] = 0x0D_u8; // 0x0D == WHOAMI register dev.write_read(0x1e, buffer, 1, 1).unwrap(); self.state.set(AccelClientState::ReadingWhoami); } @@ -143,7 +152,7 @@ impl hil::i2c::I2CHwMasterClient for AccelClient { } /// This test should be called with I2C2, specifically -pub fn i2c_accel_test(i2c_master: &'static dyn I2CMaster) { +pub fn i2c_accel_test(i2c_master: &'static dyn I2CMaster<'static>) { static mut DATA: [u8; 255] = [0; 255]; let dev = i2c_master; @@ -152,9 +161,9 @@ pub fn i2c_accel_test(i2c_master: &'static dyn I2CMaster) { dev.set_master_client(i2c_client); dev.enable(); - let buf = unsafe { &mut DATA }; + let buf = unsafe { &mut *addr_of_mut!(DATA) }; debug!("Reading Accel's WHOAMI..."); - buf[0] = 0x0D as u8; // 0x0D == WHOAMI register + buf[0] = 0x0D_u8; // 0x0D == WHOAMI register dev.write_read(0x1e, buf, 1, 1).unwrap(); i2c_client.state.set(AccelClientState::ReadingWhoami); } @@ -171,11 +180,11 @@ enum LiClientState { struct LiClient { state: Cell, - i2c_master: &'static dyn I2CMaster, + i2c_master: &'static dyn I2CMaster<'static>, } impl LiClient { - pub fn new(i2c_master: &'static dyn I2CMaster) -> Self { + pub fn new(i2c_master: &'static dyn I2CMaster<'static>) -> Self { Self { state: Cell::new(LiClientState::Enabling), i2c_master, @@ -189,8 +198,8 @@ impl hil::i2c::I2CHwMasterClient for LiClient { match self.state.get() { LiClientState::Enabling => { - debug!("Reading Lumminance Registers ({:?})", status); - buffer[0] = 0x02 as u8; + debug!("Reading luminance Registers ({:?})", status); + buffer[0] = 0x02_u8; buffer[0] = 0; dev.write_read(0x44, buffer, 1, 2).unwrap(); self.state.set(LiClientState::ReadingLI); @@ -202,7 +211,7 @@ impl hil::i2c::I2CHwMasterClient for LiClient { (intensity * 100) >> 16, status ); - buffer[0] = 0x02 as u8; + buffer[0] = 0x02_u8; dev.write_read(0x44, buffer, 1, 2).unwrap(); self.state.set(LiClientState::ReadingLI); } @@ -211,7 +220,7 @@ impl hil::i2c::I2CHwMasterClient for LiClient { } /// This test should be called with I2C2, specifically -pub fn i2c_li_test(i2c_master: &'static dyn I2CMaster) { +pub fn i2c_li_test(i2c_master: &'static dyn I2CMaster<'static>) { static mut DATA: [u8; 255] = [0; 255]; let pin = sam4l::gpio::GPIOPin::new(sam4l::gpio::Pin::PA16); @@ -224,7 +233,7 @@ pub fn i2c_li_test(i2c_master: &'static dyn I2CMaster) { dev.set_master_client(i2c_client); dev.enable(); - let buf = unsafe { &mut DATA }; + let buf = unsafe { &mut *addr_of_mut!(DATA) }; debug!("Enabling LI..."); buf[0] = 0; buf[1] = 0b10100000; diff --git a/boards/imix/src/test/icmp_lowpan_test.rs b/boards/imix/src/test/icmp_lowpan_test.rs index 48da816baf..a0b1299cb1 100644 --- a/boards/imix/src/test/icmp_lowpan_test.rs +++ b/boards/imix/src/test/icmp_lowpan_test.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! `icmp_lowpan_test.rs`: Test kernel space sending of //! ICMP packets over 6LoWPAN //! @@ -10,22 +14,23 @@ //! test::icmp_lowpan_test::run(mux_mac, mux_alarm); //! ``` -use capsules::ieee802154::device::MacDevice; -use capsules::net::icmpv6::icmpv6_send::{ICMP6SendStruct, ICMP6Sender}; -use capsules::net::icmpv6::{ICMP6Header, ICMP6Type}; -use capsules::net::ieee802154::MacAddress; -use capsules::net::ipv6::ip_utils::IPAddr; -use capsules::net::ipv6::ipv6_send::{IP6SendStruct, IP6Sender}; -use capsules::net::ipv6::{IP6Packet, IPPayload, TransportHeader}; -use capsules::net::network_capabilities::{ +use capsules_extra::ieee802154::device::MacDevice; +use capsules_extra::net::icmpv6::icmpv6_send::{ICMP6SendStruct, ICMP6Sender}; +use capsules_extra::net::icmpv6::{ICMP6Header, ICMP6Type}; +use capsules_extra::net::ieee802154::MacAddress; +use capsules_extra::net::ipv6::ip_utils::IPAddr; +use capsules_extra::net::ipv6::ipv6_send::{IP6SendStruct, IP6Sender}; +use capsules_extra::net::ipv6::{IP6Packet, IPPayload, TransportHeader}; +use capsules_extra::net::network_capabilities::{ AddrRange, IpVisibilityCapability, NetworkCapability, PortRange, }; -use capsules::net::sixlowpan::sixlowpan_compression; -use capsules::net::sixlowpan::sixlowpan_state::{Sixlowpan, SixlowpanState, TxState}; +use capsules_extra::net::sixlowpan::sixlowpan_compression; +use capsules_extra::net::sixlowpan::sixlowpan_state::{Sixlowpan, SixlowpanState, TxState}; use kernel::ErrorCode; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; use core::cell::Cell; +use core::ptr::addr_of_mut; use kernel::capabilities::NetworkCapabilityCreationCapability; use kernel::create_capability; use kernel::debug; @@ -42,7 +47,7 @@ pub const DST_ADDR: IPAddr = IPAddr([ /* 6LoWPAN Constants */ const DEFAULT_CTX_PREFIX_LEN: u8 = 8; -static DEFAULT_CTX_PREFIX: [u8; 16] = [0x0 as u8; 16]; +static DEFAULT_CTX_PREFIX: [u8; 16] = [0x0_u8; 16]; static mut RX_STATE_BUF: [u8; 1280] = [0x0; 1280]; const DST_MAC_ADDR: MacAddress = MacAddress::Short(0x802); const SRC_MAC_ADDR: MacAddress = MacAddress::Short(0xf00f); @@ -52,7 +57,7 @@ pub const TEST_LOOP: bool = false; static mut ICMP_PAYLOAD: [u8; 10] = [0; 10]; -pub static mut RF233_BUF: [u8; radio::MAX_BUF_SIZE] = [0 as u8; radio::MAX_BUF_SIZE]; +pub static mut RF233_BUF: [u8; radio::MAX_BUF_SIZE] = [0_u8; radio::MAX_BUF_SIZE]; //Use a global variable option, initialize as None, then actually initialize in initialize all @@ -63,8 +68,18 @@ pub struct LowpanICMPTest<'a, A: time::Alarm<'a>> { net_cap: &'static NetworkCapability, } +type Rf233 = capsules_extra::rf233::RF233< + 'static, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice< + 'static, + sam4l::spi::SpiHw<'static>, + >, +>; +type Ieee802154MacDevice = + components::ieee802154::Ieee802154ComponentMacDeviceType>; + pub unsafe fn run( - mux_mac: &'static capsules::ieee802154::virtual_mac::MuxMac<'static>, + mux_mac: &'static capsules_extra::ieee802154::virtual_mac::MuxMac<'static, Ieee802154MacDevice>, mux_alarm: &'static MuxAlarm<'static, sam4l::ast::Ast>, ) { let create_cap = create_capability!(NetworkCapabilityCreationCapability); @@ -77,8 +92,8 @@ pub unsafe fn run( IpVisibilityCapability::new(&create_cap) ); let radio_mac = static_init!( - capsules::ieee802154::virtual_mac::MacUser<'static>, - capsules::ieee802154::virtual_mac::MacUser::new(mux_mac) + capsules_extra::ieee802154::virtual_mac::MacUser<'static, Ieee802154MacDevice>, + capsules_extra::ieee802154::virtual_mac::MacUser::new(mux_mac) ); mux_mac.add_user(radio_mac); let ipsender_virtual_alarm = static_init!( @@ -111,7 +126,7 @@ pub unsafe fn run( let ip_pyld: IPPayload = IPPayload { header: TransportHeader::ICMP(icmp_hdr), - payload: &mut ICMP_PAYLOAD, + payload: &mut *addr_of_mut!(ICMP_PAYLOAD), }; let ip6_dg = static_init!(IP6Packet<'static>, IP6Packet::new(ip_pyld)); @@ -121,7 +136,7 @@ pub unsafe fn run( IP6SendStruct::new( ip6_dg, ipsender_virtual_alarm, - &mut RF233_BUF, + &mut *addr_of_mut!(RF233_BUF), sixlowpan_tx, radio_mac, DST_MAC_ADDR, @@ -163,7 +178,7 @@ pub unsafe fn run( icmp_lowpan_test.start(); } -impl<'a, A: time::Alarm<'a>> capsules::net::icmpv6::icmpv6_send::ICMP6SendClient +impl<'a, A: time::Alarm<'a>> capsules_extra::net::icmpv6::icmpv6_send::ICMP6SendClient for LowpanICMPTest<'a, A> { fn send_done(&self, result: Result<(), ErrorCode>) { @@ -187,10 +202,10 @@ impl<'a, A: time::Alarm<'a>> LowpanICMPTest<'a, A> { net_cap: &'static NetworkCapability, ) -> LowpanICMPTest<'a, A> { LowpanICMPTest { - alarm: alarm, + alarm, test_counter: Cell::new(0), - icmp_sender: icmp_sender, - net_cap: net_cap, + icmp_sender, + net_cap, } } @@ -239,8 +254,12 @@ impl<'a, A: time::Alarm<'a>> LowpanICMPTest<'a, A> { fn send_next(&self) { let icmp_hdr = ICMP6Header::new(ICMP6Type::Type128); // Echo Request let _ = unsafe { - self.icmp_sender - .send(DST_ADDR, icmp_hdr, &mut ICMP_PAYLOAD, self.net_cap) + self.icmp_sender.send( + DST_ADDR, + icmp_hdr, + &mut *addr_of_mut!(ICMP_PAYLOAD), + self.net_cap, + ) }; } } diff --git a/boards/imix/src/test/ipv6_lowpan_test.rs b/boards/imix/src/test/ipv6_lowpan_test.rs index c8f3ddebb7..3d25082326 100644 --- a/boards/imix/src/test/ipv6_lowpan_test.rs +++ b/boards/imix/src/test/ipv6_lowpan_test.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! `ipv6_lowpan_test.rs`: 6LoWPAN Fragmentation Test Suite //! //! This implements a simple testing framework for 6LoWPAN fragmentation and @@ -28,17 +32,18 @@ //! lowpan_frag_test.start(); // If flashing the transmitting Imix //! ``` -use capsules::ieee802154::device::{MacDevice, TxClient}; -use capsules::net::ieee802154::MacAddress; -use capsules::net::ipv6::ip_utils::{ip6_nh, IPAddr}; -use capsules::net::ipv6::{IP6Header, IP6Packet, IPPayload, TransportHeader}; -use capsules::net::sixlowpan::sixlowpan_compression; -use capsules::net::sixlowpan::sixlowpan_state::{ +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_extra::ieee802154::device::{MacDevice, TxClient}; +use capsules_extra::net::ieee802154::MacAddress; +use capsules_extra::net::ipv6::ip_utils::{ip6_nh, IPAddr}; +use capsules_extra::net::ipv6::{IP6Header, IP6Packet, IPPayload, TransportHeader}; +use capsules_extra::net::sixlowpan::sixlowpan_compression; +use capsules_extra::net::sixlowpan::sixlowpan_state::{ RxState, Sixlowpan, SixlowpanRxClient, SixlowpanState, TxState, }; -use capsules::net::udp::UDPHeader; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_extra::net::udp::UDPHeader; use core::cell::Cell; +use core::ptr::addr_of_mut; use kernel::debug; use kernel::hil::radio; use kernel::hil::time::{self, Alarm, ConvertTicks}; @@ -60,11 +65,11 @@ pub const DST_MAC_ADDR: MacAddress = MacAddress::Short(57326); pub const IP6_HDR_SIZE: usize = 40; pub const UDP_HDR_SIZE: usize = 8; pub const PAYLOAD_LEN: usize = 200; -pub static mut RF233_BUF: [u8; radio::MAX_BUF_SIZE] = [0 as u8; radio::MAX_BUF_SIZE]; +pub static mut RF233_BUF: [u8; radio::MAX_BUF_SIZE] = [0_u8; radio::MAX_BUF_SIZE]; /* 6LoWPAN Constants */ const DEFAULT_CTX_PREFIX_LEN: u8 = 8; -static DEFAULT_CTX_PREFIX: [u8; 16] = [0x0 as u8; 16]; +static DEFAULT_CTX_PREFIX: [u8; 16] = [0x0_u8; 16]; static mut RX_STATE_BUF: [u8; 1280] = [0x0; 1280]; #[derive(Copy, Clone, Debug, PartialEq)] @@ -115,6 +120,16 @@ static mut UDP_DGRAM: [u8; PAYLOAD_LEN - UDP_HDR_SIZE] = [0; PAYLOAD_LEN - UDP_H static mut IP6_DG_OPT: Option = None; //END changes +type Rf233 = capsules_extra::rf233::RF233< + 'static, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice< + 'static, + sam4l::spi::SpiHw<'static>, + >, +>; +type Ieee802154MacDevice = + components::ieee802154::Ieee802154ComponentMacDeviceType>; + pub struct LowpanTest<'a, A: time::Alarm<'a>> { alarm: &'a A, sixlowpan_tx: TxState<'a>, @@ -123,18 +138,21 @@ pub struct LowpanTest<'a, A: time::Alarm<'a>> { } pub unsafe fn initialize_all( - mux_mac: &'static capsules::ieee802154::virtual_mac::MuxMac<'static>, + mux_mac: &'static capsules_extra::ieee802154::virtual_mac::MuxMac<'static, Ieee802154MacDevice>, mux_alarm: &'static MuxAlarm<'static, sam4l::ast::Ast>, ) -> &'static LowpanTest< 'static, - capsules::virtual_alarm::VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>, > { let radio_mac = static_init!( - capsules::ieee802154::virtual_mac::MacUser<'static>, - capsules::ieee802154::virtual_mac::MacUser::new(mux_mac) + capsules_extra::ieee802154::virtual_mac::MacUser<'static, Ieee802154MacDevice>, + capsules_extra::ieee802154::virtual_mac::MacUser::new(mux_mac) ); mux_mac.add_user(radio_mac); - let default_rx_state = static_init!(RxState<'static>, RxState::new(&mut RX_STATE_BUF)); + let default_rx_state = static_init!( + RxState<'static>, + RxState::new(&mut *addr_of_mut!(RX_STATE_BUF)) + ); let sixlo_alarm = static_init!( VirtualMuxAlarm, @@ -182,12 +200,7 @@ pub unsafe fn initialize_all( radio_mac.set_receive_client(sixlowpan); // Following code initializes an IP6Packet using the global UDP_DGRAM buffer as the payload - let mut udp_hdr: UDPHeader = UDPHeader { - src_port: 0, - dst_port: 0, - len: 0, - cksum: 0, - }; + let mut udp_hdr: UDPHeader = UDPHeader::new(); udp_hdr.set_src_port(12345); udp_hdr.set_dst_port(54321); udp_hdr.set_len(PAYLOAD_LEN as u16); @@ -203,7 +216,7 @@ pub unsafe fn initialize_all( let ip_pyld: IPPayload = IPPayload { header: tr_hdr, - payload: &mut UDP_DGRAM, + payload: &mut *addr_of_mut!(UDP_DGRAM), }; let mut ip6_dg: IP6Packet = IP6Packet { @@ -228,9 +241,9 @@ impl<'a, A: time::Alarm<'a>> LowpanTest<'a, A> { alarm: &'a A, ) -> LowpanTest<'a, A> { LowpanTest { - alarm: alarm, - sixlowpan_tx: sixlowpan_tx, - radio: radio, + alarm, + sixlowpan_tx, + radio, test_counter: Cell::new(0), } } @@ -409,7 +422,7 @@ impl<'a, A: time::Alarm<'a>> LowpanTest<'a, A> { } unsafe fn send_ipv6_packet(&self, _: &[u8]) { - self.send_next(&mut RF233_BUF); + self.send_next(&mut *addr_of_mut!(RF233_BUF)); } fn send_next(&self, tx_buf: &'static mut [u8]) { @@ -418,7 +431,7 @@ impl<'a, A: time::Alarm<'a>> LowpanTest<'a, A> { Some(ref ip6_packet) => { match self .sixlowpan_tx - .next_fragment(&ip6_packet, tx_buf, self.radio) + .next_fragment(ip6_packet, tx_buf, self.radio) { Ok((is_done, frame)) => { //TODO: Fix ordering so that debug output does not indicate extra frame sent @@ -472,9 +485,9 @@ impl<'a, A: time::Alarm<'a>> TxClient for LowpanTest<'a, A> { let mut i = 0; while i < 4000000 { ARRAY[i % 100] = (i % 100) as u8; - i = i + 1; + i += 1; if i % 1000000 == 0 { - i = i + 2; + i += 2; } } } @@ -789,7 +802,7 @@ fn ipv6_prepare_packet(tf: TF, hop_limit: u8, sac: SAC, dac: DAC) { ip6_header.dst_addr.0[0] = 0xff; ip6_header.dst_addr.0[1] = DST_ADDR.0[1]; ip6_header.dst_addr.0[2] = DST_ADDR.0[2]; - ip6_header.dst_addr.0[3] = 64 as u8; + ip6_header.dst_addr.0[3] = 64_u8; ip6_header.dst_addr.0[4..12].copy_from_slice(&MLP); ip6_header.dst_addr.0[12..16].copy_from_slice(&DST_ADDR.0[12..16]); } diff --git a/boards/imix/src/test/linear_log_test.rs b/boards/imix/src/test/linear_log_test.rs index 721bab559d..ae890f2b71 100644 --- a/boards/imix/src/test/linear_log_test.rs +++ b/boards/imix/src/test/linear_log_test.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tests the log storage interface in linear mode. For testing in circular mode, see //! log_test.rs. //! @@ -8,16 +12,16 @@ //! //! To run the test, add the following line to the imix boot sequence: //! ``` -//! test::linear_log_test::run(mux_alarm, dynamic_deferred_caller); +//! test::linear_log_test::run(mux_alarm); //! ``` //! and use the `USER` and `RESET` buttons to manually erase the log and reboot the imix, //! respectively. -use capsules::log; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_extra::log; use core::cell::Cell; +use core::ptr::addr_of_mut; use kernel::debug; -use kernel::dynamic_deferred_call::DynamicDeferredCall; use kernel::hil::flash; use kernel::hil::log::{LogRead, LogReadClient, LogWrite, LogWriteClient}; use kernel::hil::time::{Alarm, AlarmClient, ConvertTicks}; @@ -33,7 +37,6 @@ storage_volume!(LINEAR_TEST_LOG, 1); pub unsafe fn run( mux_alarm: &'static MuxAlarm<'static, Ast>, - deferred_caller: &'static DynamicDeferredCall, flash_controller: &'static sam4l::flashcalw::FLASHCALW, ) { // Set up flash controller. @@ -43,20 +46,10 @@ pub unsafe fn run( // Create actual log storage abstraction on top of flash. let log = static_init!( Log, - log::Log::new( - &LINEAR_TEST_LOG, - &flash_controller, - pagebuffer, - deferred_caller, - false - ) + log::Log::new(&LINEAR_TEST_LOG, flash_controller, pagebuffer, false) ); + kernel::deferred_call::DeferredCallClient::register(log); flash::HasClient::set_client(flash_controller, log); - log.initialize_callback_handle( - deferred_caller - .register(log) - .expect("no deferred call slot available for log storage"), - ); let alarm = static_init!( VirtualMuxAlarm<'static, Ast>, @@ -67,7 +60,7 @@ pub unsafe fn run( // Create and run test for log storage. let test = static_init!( LogTest>, - LogTest::new(log, &mut BUFFER, alarm, &TEST_OPS) + LogTest::new(log, &mut *addr_of_mut!(BUFFER), alarm, &TEST_OPS) ); log.set_read_client(test); log.set_append_client(test); diff --git a/boards/imix/src/test/log_test.rs b/boards/imix/src/test/log_test.rs index af3e3bad0c..f866b95f3a 100644 --- a/boards/imix/src/test/log_test.rs +++ b/boards/imix/src/test/log_test.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tests the log storage interface in circular mode. For testing in linear mode, see //! linear_log_test.rs. //! @@ -20,16 +24,16 @@ //! //! To run the test, add the following line to the imix boot sequence: //! ``` -//! test::log_test::run(mux_alarm, dynamic_deferred_caller, &peripherals.flash_controller); +//! test::log_test::run(mux_alarm, &peripherals.flash_controller); //! ``` //! and use the `USER` and `RESET` buttons to manually erase the log and reboot the imix, //! respectively. -use capsules::log; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_extra::log; use core::cell::Cell; +use core::ptr::addr_of_mut; use kernel::debug; -use kernel::dynamic_deferred_call::DynamicDeferredCall; use kernel::hil::flash; use kernel::hil::gpio::{self, Interrupt}; use kernel::hil::log::{LogRead, LogReadClient, LogWrite, LogWriteClient}; @@ -46,7 +50,6 @@ storage_volume!(TEST_LOG, 2); pub unsafe fn run( mux_alarm: &'static MuxAlarm<'static, Ast>, - deferred_caller: &'static DynamicDeferredCall, flash_controller: &'static sam4l::flashcalw::FLASHCALW, ) { // Set up flash controller. @@ -56,20 +59,10 @@ pub unsafe fn run( // Create actual log storage abstraction on top of flash. let log = static_init!( Log, - log::Log::new( - &TEST_LOG, - &flash_controller, - pagebuffer, - deferred_caller, - true - ) + log::Log::new(&TEST_LOG, flash_controller, pagebuffer, true) ); + kernel::deferred_call::DeferredCallClient::register(log); flash::HasClient::set_client(flash_controller, log); - log.initialize_callback_handle( - deferred_caller - .register(log) - .expect("no deferred call slot available for log storage"), - ); let alarm = static_init!( VirtualMuxAlarm<'static, Ast>, @@ -80,7 +73,7 @@ pub unsafe fn run( // Create and run test for log storage. let test = static_init!( LogTest>, - LogTest::new(log, &mut BUFFER, alarm, &TEST_OPS) + LogTest::new(log, &mut *addr_of_mut!(BUFFER), alarm, &TEST_OPS) ); log.set_read_client(test); log.set_append_client(test); @@ -122,7 +115,7 @@ static TEST_OPS: [TestOp; 24] = [ // Try bad seeks, should fail and not change read entry ID. TestOp::Write, TestOp::BadSeek(0), - TestOp::BadSeek(core::usize::MAX), + TestOp::BadSeek(usize::MAX), TestOp::Read, // Try bad write, nothing should change. TestOp::BadWrite, @@ -308,7 +301,7 @@ impl> LogTest { .take() .map( move |buffer| match self.log.read(buffer, buffer.len() + 1) { - Ok(_) => panic!("Read with too-large max read length succeeded unexpectedly!"), + Ok(()) => panic!("Read with too-large max read length succeeded unexpectedly!"), Err((error, original_buffer)) => { self.buffer.replace(original_buffer); assert_eq!(error, ErrorCode::INVAL); @@ -321,7 +314,7 @@ impl> LogTest { self.buffer .take() .map(move |buffer| match self.log.read(buffer, BUFFER_LEN - 1) { - Ok(_) => panic!("Read with too-small buffer succeeded unexpectedly!"), + Ok(()) => panic!("Read with too-small buffer succeeded unexpectedly!"), Err((error, original_buffer)) => { self.buffer.replace(original_buffer); if self.read_val.get() == self.write_val.get() { @@ -363,7 +356,7 @@ impl> LogTest { self.buffer .take() .map(move |buffer| match self.log.append(buffer, 0) { - Ok(_) => panic!("Appending entry of size 0 succeeded unexpectedly!"), + Ok(()) => panic!("Appending entry of size 0 succeeded unexpectedly!"), Err((error, original_buffer)) => { self.buffer.replace(original_buffer); assert_eq!(error, ErrorCode::INVAL); @@ -376,7 +369,7 @@ impl> LogTest { .take() .map( move |buffer| match self.log.append(buffer, buffer.len() + 1) { - Ok(_) => panic!("Appending with too-small buffer succeeded unexpectedly!"), + Ok(()) => panic!("Appending with too-small buffer succeeded unexpectedly!"), Err((error, original_buffer)) => { self.buffer.replace(original_buffer); assert_eq!(error, ErrorCode::INVAL); @@ -387,8 +380,11 @@ impl> LogTest { // Ensure failure if entry is too large to fit within a single flash page. unsafe { - match self.log.append(&mut DUMMY_BUFFER, DUMMY_BUFFER.len()) { - Ok(_) => panic!("Appending with too-small buffer succeeded unexpectedly!"), + match self + .log + .append(&mut *addr_of_mut!(DUMMY_BUFFER), DUMMY_BUFFER.len()) + { + Ok(()) => panic!("Appending with too-small buffer succeeded unexpectedly!"), Err((ecode, _original_buffer)) => assert_eq!(ecode, ErrorCode::SIZE), } } diff --git a/boards/imix/src/test/mod.rs b/boards/imix/src/test/mod.rs index 3998ccd40d..5e73fcf804 100644 --- a/boards/imix/src/test/mod.rs +++ b/boards/imix/src/test/mod.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + pub(crate) mod aes_test; pub(crate) mod crc_test; pub(crate) mod i2c_dummy; @@ -6,6 +10,7 @@ pub(crate) mod ipv6_lowpan_test; pub(crate) mod linear_log_test; pub(crate) mod log_test; pub(crate) mod rng_test; +pub(crate) mod sha256_test; pub(crate) mod spi_dummy; pub(crate) mod spi_loopback; pub(crate) mod udp_lowpan_test; diff --git a/boards/imix/src/test/rng_test.rs b/boards/imix/src/test/rng_test.rs index cb097d9dbc..48f93c6c48 100644 --- a/boards/imix/src/test/rng_test.rs +++ b/boards/imix/src/test/rng_test.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! This tests an underlying 32-bit entropy generator and the library //! transformations between 8-bit and 32-bit entropy. To run this test, //! add this line to the imix boot sequence: @@ -14,8 +18,8 @@ //! different on each invocation. Rigorous entropy tests are outside //! the scope of this test. -use capsules::rng; -use capsules::test::rng::TestRng; +use capsules_core::rng; +use capsules_core::test::rng::TestRng; use kernel::hil::entropy::{Entropy32, Entropy8}; use kernel::hil::rng::Rng; use kernel::static_init; @@ -27,12 +31,21 @@ pub unsafe fn run_entropy32(trng: &'static Trng) { } unsafe fn static_init_test_entropy32(trng: &'static Trng) -> &'static TestRng<'static> { - let e1 = static_init!(rng::Entropy32To8<'static>, rng::Entropy32To8::new(trng)); + let e1 = static_init!( + rng::Entropy32To8<'static, Trng>, + rng::Entropy32To8::new(trng) + ); trng.set_client(e1); - let e2 = static_init!(rng::Entropy8To32<'static>, rng::Entropy8To32::new(e1)); + let e2 = static_init!( + rng::Entropy8To32<'static, rng::Entropy32To8<'static, Trng>>, + rng::Entropy8To32::new(e1) + ); e1.set_client(e2); let er = static_init!( - rng::Entropy32ToRandom<'static>, + rng::Entropy32ToRandom< + 'static, + rng::Entropy8To32<'static, rng::Entropy32To8<'static, Trng>>, + >, rng::Entropy32ToRandom::new(e2) ); e2.set_client(er); diff --git a/boards/imix/src/test/sha256_test.rs b/boards/imix/src/test/sha256_test.rs new file mode 100644 index 0000000000..3fe59a1995 --- /dev/null +++ b/boards/imix/src/test/sha256_test.rs @@ -0,0 +1,71 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! This tests a software SHA256 implementation. To run this test, +//! add this line to the imix boot sequence: +//! ``` +//! test::sha256_test::run_sha256(); +//! ``` +//! This test uses a deferred call (for callbacks). It tries to +//! hash 'hello world' and uses Digest::validate to check that the hash +//! is correct. +//! +//! The expected output is +//! Sha256Test: Verification result: Ok(true) +//! +//! This tests whether the SHA-256 hash of the string "hello hello +//! hello hello hello hello hello hello hello hello hello hello " +//! hashes correctly. This string is 12 repetitions of "hello ", so is +//! 72 bytes long. As SHA uses 64-byte/512 bit blocks, this verifies +//! that multi-block hashes work correctly. + +use core::ptr::addr_of_mut; + +use capsules_extra::sha256::Sha256Software; +use capsules_extra::test::sha256::TestSha256; +use kernel::static_init; + +pub unsafe fn run_sha256() { + let t = static_init_test_sha256(); + t.run(); +} + +// HSTRING is "hello world" and HHASH is the SHA-256 hash of this string. +pub static mut HSTRING: [u8; 11] = *b"hello world"; + +pub static mut HHASH: [u8; 32] = [ + 0xB9, 0x4D, 0x27, 0xB9, 0x93, 0x4D, 0x3E, 0x08, 0xA5, 0x2E, 0x52, 0xD7, 0xDA, 0x7D, 0xAB, 0xFA, + 0xC4, 0x84, 0xEF, 0xE3, 0x7A, 0x53, 0x80, 0xEE, 0x90, 0x88, 0xF7, 0xAC, 0xE2, 0xEF, 0xCD, 0xE9, +]; + +// LSTRING is 12 repetitions of "hello " (72 bytes long) and LHASH is +// the SHA-256 hash of this string. +pub static mut LSTRING: [u8; 72] = [0; 72]; +pub static mut LHASH: [u8; 32] = [ + 0x59, 0x42, 0xc3, 0x71, 0x6f, 0x02, 0x82, 0x89, 0x3f, 0xbe, 0x04, 0x9b, 0xa2, 0x0e, 0x56, 0x0e, + 0x45, 0x94, 0xd5, 0xee, 0x15, 0xcb, 0x8a, 0x1e, 0x28, 0x7c, 0x20, 0x12, 0xc2, 0xce, 0xb5, 0xa9, +]; + +unsafe fn static_init_test_sha256() -> &'static TestSha256 { + let sha = static_init!(Sha256Software<'static>, Sha256Software::new()); + kernel::deferred_call::DeferredCallClient::register(sha); + let bytes = b"hello "; + for i in 0..12 { + for j in 0..6 { + LSTRING[i * 6 + j] = bytes[j]; + } + } + // We expect LSTRING to hash to LHASH, so final argument is true + let test = static_init!( + TestSha256, + TestSha256::new( + sha, + &mut *addr_of_mut!(LSTRING), + &mut *addr_of_mut!(LHASH), + true + ) + ); + + test +} diff --git a/boards/imix/src/test/spi_dummy.rs b/boards/imix/src/test/spi_dummy.rs index f1623a02a3..c7d7ae1181 100644 --- a/boards/imix/src/test/spi_dummy.rs +++ b/boards/imix/src/test/spi_dummy.rs @@ -1,5 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! A dummy SPI client to test the SPI implementation +use core::ptr::addr_of_mut; + use kernel::hil::gpio::Configure; use kernel::hil::spi::{self, SpiMaster}; use kernel::ErrorCode; @@ -7,15 +13,12 @@ use kernel::ErrorCode; #[allow(unused_variables, dead_code)] pub struct DummyCB { val: u8, - spi: &'static sam4l::spi::SpiHw, + spi: &'static sam4l::spi::SpiHw<'static>, } impl DummyCB { - pub fn new(spi: &'static sam4l::spi::SpiHw) -> Self { - Self { - val: 0x55 as u8, - spi, - } + pub fn new(spi: &'static sam4l::spi::SpiHw<'static>) -> Self { + Self { val: 0x55_u8, spi } } } @@ -36,7 +39,9 @@ impl spi::SpiMasterClient for DummyCB { unsafe { // do actual stuff // TODO verify SPI return value - let _ = self.spi.read_write_bytes(&mut A5, None, A5.len()); + let _ = self + .spi + .read_write_bytes(&mut *addr_of_mut!(A5), None, A5.len()); // FLOP = !FLOP; // let len: usize = BUF1.len(); @@ -64,7 +69,7 @@ impl spi::SpiMasterClient for DummyCB { // the board. #[inline(never)] #[allow(unused_variables, dead_code)] -pub unsafe fn spi_dummy_test(spi: &'static sam4l::spi::SpiHw) { +pub unsafe fn spi_dummy_test(spi: &'static sam4l::spi::SpiHw<'static>) { // set the LED to mark that we've programmed. let pin = sam4l::gpio::GPIOPin::new(sam4l::gpio::Pin::PC10); pin.make_output(); @@ -82,7 +87,12 @@ pub unsafe fn spi_dummy_test(spi: &'static sam4l::spi::SpiHw) { spi.set_baud_rate(200000); let len = BUF2.len(); - if spi.read_write_bytes(&mut BUF2, Some(&mut BUF1), len) != Ok(()) { + if spi.read_write_bytes( + &mut *addr_of_mut!(BUF2), + Some(&mut *addr_of_mut!(BUF1)), + len, + ) != Ok(()) + { loop { spi.write_byte(0xA5).unwrap(); } diff --git a/boards/imix/src/test/spi_loopback.rs b/boards/imix/src/test/spi_loopback.rs index 5f552107e9..71c355bdc3 100644 --- a/boards/imix/src/test/spi_loopback.rs +++ b/boards/imix/src/test/spi_loopback.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! A SPI test which read/writes and expects MOSI to //! be loopbacked to MISO. It checks that what it writes //! is what it reads. The values put in the buffer are @@ -10,9 +14,10 @@ //! with different bit rates should see different clock //! frequencies. -use capsules::virtual_spi::MuxSpiMaster; +use capsules_core::virtualizers::virtual_spi::MuxSpiMaster; use components::spi::SpiComponent; use core::cell::Cell; +use core::ptr::addr_of_mut; use kernel::component::Component; use kernel::debug; use kernel::hil::spi::{self, SpiMasterDevice}; @@ -22,11 +27,11 @@ use kernel::ErrorCode; pub struct SpiLoopback { cs: Cell, val: Cell, - spi: &'static dyn SpiMasterDevice, + spi: &'static dyn SpiMasterDevice<'static>, } impl SpiLoopback { - pub fn new(spi: &'static dyn SpiMasterDevice, cs: u8, counter: u8) -> Self { + pub fn new(spi: &'static dyn SpiMasterDevice<'static>, cs: u8, counter: u8) -> Self { Self { val: Cell::new(counter), cs: Cell::new(cs), @@ -81,14 +86,22 @@ impl spi::SpiMasterClient for SpiLoopback { #[inline(never)] #[allow(unused_variables, dead_code)] -pub unsafe fn spi_loopback_test(spi: &'static dyn SpiMasterDevice, counter: u8, speed: u32) { +pub unsafe fn spi_loopback_test( + spi: &'static dyn SpiMasterDevice<'static>, + counter: u8, + speed: u32, +) { let spicb = kernel::static_init!(SpiLoopback, SpiLoopback::new(spi, 0, counter)); spi.set_client(spicb); spi.set_rate(speed) .expect("Failed to set SPI speed in SPI loopback test."); let len = WBUF.len(); - if let Err((e, _, _)) = spi.read_write_bytes(&mut WBUF, Some(&mut RBUF), len) { + if let Err((e, _, _)) = spi.read_write_bytes( + &mut *addr_of_mut!(WBUF), + Some(&mut *addr_of_mut!(RBUF)), + len, + ) { panic!( "Could not start SPI test, error on read_write_bytes is {:?}", e @@ -100,9 +113,9 @@ pub unsafe fn spi_loopback_test(spi: &'static dyn SpiMasterDevice, counter: u8, #[allow(unused_variables, dead_code)] pub unsafe fn spi_two_loopback_test(mux: &'static MuxSpiMaster<'static, sam4l::spi::SpiHw>) { let spi_fast = - SpiComponent::new(mux, 0).finalize(components::spi_component_helper!(sam4l::spi::SpiHw)); + SpiComponent::new(mux, 0).finalize(components::spi_component_static!(sam4l::spi::SpiHw)); let spi_slow = - SpiComponent::new(mux, 1).finalize(components::spi_component_helper!(sam4l::spi::SpiHw)); + SpiComponent::new(mux, 1).finalize(components::spi_component_static!(sam4l::spi::SpiHw)); let spicb_fast = kernel::static_init!(SpiLoopback, SpiLoopback::new(spi_fast, 0, 0x80)); let spicb_slow = kernel::static_init!(SpiLoopback, SpiLoopback::new(spi_slow, 1, 0x00)); @@ -116,7 +129,11 @@ pub unsafe fn spi_two_loopback_test(mux: &'static MuxSpiMaster<'static, sam4l::s spi_slow.set_client(spicb_slow); let len = WBUF.len(); - if let Err((e, _, _)) = spi_fast.read_write_bytes(&mut WBUF, Some(&mut RBUF), len) { + if let Err((e, _, _)) = spi_fast.read_write_bytes( + &mut *addr_of_mut!(WBUF), + Some(&mut *addr_of_mut!(RBUF)), + len, + ) { panic!( "Could not start SPI test, error on read_write_bytes is {:?}", e @@ -124,7 +141,11 @@ pub unsafe fn spi_two_loopback_test(mux: &'static MuxSpiMaster<'static, sam4l::s } let len = WBUF2.len(); - if let Err((e, _, _)) = spi_slow.read_write_bytes(&mut WBUF2, Some(&mut RBUF2), len) { + if let Err((e, _, _)) = spi_slow.read_write_bytes( + &mut *addr_of_mut!(WBUF2), + Some(&mut *addr_of_mut!(RBUF2)), + len, + ) { panic!( "Could not start SPI test, error on read_write_bytes is {:?}", e diff --git a/boards/imix/src/test/spi_slave_dummy.rs b/boards/imix/src/test/spi_slave_dummy.rs index a5be5e465e..4a10741433 100644 --- a/boards/imix/src/test/spi_slave_dummy.rs +++ b/boards/imix/src/test/spi_slave_dummy.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! A dummy SPI client to test the SPI implementation use kernel::hil::gpio; @@ -5,7 +9,7 @@ use kernel::hil::gpio::Pin; use kernel::hil::spi::{self, SpiSlave}; use sam4l::spi::SPI as SPI_SLAVE; -#[allow(unused_variables,dead_code)] +#[allow(unused_variables, dead_code)] pub struct SlaveCB { val: u8, } @@ -16,11 +20,13 @@ pub static mut BUF1: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0]; pub static mut BUF2: [u8; 8] = [8, 7, 6, 5, 4, 3, 2, 1]; impl spi::SpiSlaveClient for SlaveCB { - #[allow(unused_variables,dead_code)] - fn read_write_done(&self, - write_buffer: Option<&'static mut [u8]>, - read_buffer: Option<&'static mut [u8]>, - len: usize) { + #[allow(unused_variables, dead_code)] + fn read_write_done( + &self, + write_buffer: Option<&'static mut [u8]>, + read_buffer: Option<&'static mut [u8]>, + len: usize, + ) { unsafe { SPI_SLAVE.read_write_bytes(Some(&mut BUF2), None, 8); } @@ -45,9 +51,6 @@ impl spi::SpiSlaveClient for SlaveCB { SPI_SLAVE.set_write_byte(BUF2[COUNTER]); } */ - - - } } } @@ -55,9 +58,8 @@ impl spi::SpiSlaveClient for SlaveCB { pub static mut SPISLAVECB: SlaveCB = SlaveCB { val: 0x55 as u8 }; #[inline(never)] -#[allow(unused_variables,dead_code)] +#[allow(unused_variables, dead_code)] pub unsafe fn spi_slave_dummy_test() { - // set the LED to mark that we've programmed. // TODO: This doesn't do anything? We always blink... sam4l::gpio::PC[10].make_output(); @@ -84,5 +86,5 @@ pub unsafe fn spi_slave_dummy_test() { // pin2.clear(); - // TODO: We clear this for the trigger, set it perminantly to behave as NSS + // TODO: We clear this for the trigger, set it permanently to behave as NSS } diff --git a/boards/imix/src/test/udp_lowpan_test.rs b/boards/imix/src/test/udp_lowpan_test.rs index 3303e3e216..686c2d1845 100644 --- a/boards/imix/src/test/udp_lowpan_test.rs +++ b/boards/imix/src/test/udp_lowpan_test.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! `udp_lowpan_test.rs`: Kernel test suite for the UDP/6LoWPAN stack //! //! This file tests port binding and sending and receiving messages from kernel space. @@ -116,18 +120,19 @@ //! ``` use super::super::imix_components::test::mock_udp::MockUDPComponent; -use super::super::imix_components::test::mock_udp2::MockUDPComponent2; -use capsules::net::ipv6::ip_utils::IPAddr; -use capsules::net::ipv6::ipv6_send::IP6SendStruct; -use capsules::net::network_capabilities::{ +use crate::mock_udp_component_static; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_extra::net::ipv6::ip_utils::IPAddr; +use capsules_extra::net::ipv6::ipv6_send::IP6SendStruct; +use capsules_extra::net::network_capabilities::{ AddrRange, NetworkCapability, PortRange, UdpVisibilityCapability, }; -use capsules::net::udp::udp_port_table::UdpPortManager; -use capsules::net::udp::udp_recv::MuxUdpReceiver; -use capsules::net::udp::udp_send::MuxUdpSender; -use capsules::test::udp::MockUdp; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_extra::net::udp::udp_port_table::UdpPortManager; +use capsules_extra::net::udp::udp_recv::MuxUdpReceiver; +use capsules_extra::net::udp::udp_send::MuxUdpSender; +use capsules_extra::test::udp::MockUdp; use core::cell::Cell; +use core::ptr::addr_of_mut; use kernel::capabilities::NetworkCapabilityCreationCapability; use kernel::component::Component; use kernel::create_capability; @@ -172,7 +177,7 @@ pub unsafe fn initialize_all( mux_alarm: &'static MuxAlarm<'static, sam4l::ast::Ast>, ) -> &'static LowpanTest< 'static, - capsules::virtual_alarm::VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, sam4l::ast::Ast<'static>>, > { let create_cap = create_capability!(NetworkCapabilityCreationCapability); let net_cap = static_init!( @@ -188,26 +193,26 @@ pub unsafe fn initialize_all( udp_recv_mux, port_table, mux_alarm, - &mut UDP_PAYLOAD1, + &mut *addr_of_mut!(UDP_PAYLOAD1), 1, //id 3, //dst_port net_cap, udp_vis, ) - .finalize(()); + .finalize(mock_udp_component_static!()); - let mock_udp2 = MockUDPComponent2::new( + let mock_udp2 = MockUDPComponent::new( udp_send_mux, udp_recv_mux, port_table, mux_alarm, - &mut UDP_PAYLOAD2, + &mut *addr_of_mut!(UDP_PAYLOAD2), 2, //id 4, //dst_port net_cap, udp_vis, ) - .finalize(()); + .finalize(mock_udp_component_static!()); let alarm = static_init!( VirtualMuxAlarm<'static, sam4l::ast::Ast>, @@ -233,11 +238,11 @@ impl<'a, A: time::Alarm<'a>> LowpanTest<'a, A> { mock_udp2: &'static MockUdp<'a, A>, ) -> LowpanTest<'a, A> { LowpanTest { - alarm: alarm, + alarm, test_counter: Cell::new(0), - port_table: port_table, - mock_udp1: mock_udp1, - mock_udp2: mock_udp2, + port_table, + mock_udp1, + mock_udp2, test_mode: Cell::new(TestMode::DefaultMode), } } diff --git a/boards/imix/src/test/virtual_aes_ccm_test.rs b/boards/imix/src/test/virtual_aes_ccm_test.rs index ee3cdd5b17..5ce8d2a4ec 100644 --- a/boards/imix/src/test/virtual_aes_ccm_test.rs +++ b/boards/imix/src/test/virtual_aes_ccm_test.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! notice that there will be 18 tests, 6 for each, //! and the test output will make the debug buffer full, //! please go to boards/components/src/debug_writer.rs and change @@ -34,9 +38,8 @@ //! aes_ccm_test passed: (current_test=1, encrypting=false, tag_is_valid=true) //! aes_ccm_test passed: (current_test=2, encrypting=true, tag_is_valid=true) //! aes_ccm_test passed: (current_test=2, encrypting=false, tag_is_valid=true) -use capsules::test::aes_ccm::Test; -use capsules::virtual_aes_ccm; -use kernel::dynamic_deferred_call::DynamicDeferredCall; +use capsules_core::virtualizers::virtual_aes_ccm; +use capsules_extra::test::aes_ccm::Test; use kernel::hil::symmetric_encryption::{AES128, AES128CCM, AES128_BLOCK_SIZE}; use kernel::static_init; use sam4l::aes::Aes; @@ -44,20 +47,10 @@ use sam4l::aes::Aes; type AESCCMMUX = virtual_aes_ccm::MuxAES128CCM<'static, Aes<'static>>; type AESCCMCLIENT = virtual_aes_ccm::VirtualAES128CCM<'static, Aes<'static>>; -pub unsafe fn run( - aes: &'static sam4l::aes::Aes, - dynamic_deferred_caller: &'static DynamicDeferredCall, -) { +pub unsafe fn run(aes: &'static sam4l::aes::Aes) { // mux - let ccm_mux = static_init!( - AESCCMMUX, - virtual_aes_ccm::MuxAES128CCM::new(aes, dynamic_deferred_caller) - ); - ccm_mux.initialize_callback_handle( - dynamic_deferred_caller - .register(ccm_mux) - .expect("no deferred call slot available for ccm mux"), - ); + let ccm_mux = static_init!(AESCCMMUX, virtual_aes_ccm::MuxAES128CCM::new(aes)); + kernel::deferred_call::DeferredCallClient::register(ccm_mux); aes.set_client(ccm_mux); // ---------------- ONE CLIENT --------------------- // client 1 diff --git a/boards/imix/src/test/virtual_uart_rx_test.rs b/boards/imix/src/test/virtual_uart_rx_test.rs index c4a954cd5f..9b2c501d77 100644 --- a/boards/imix/src/test/virtual_uart_rx_test.rs +++ b/boards/imix/src/test/virtual_uart_rx_test.rs @@ -1,10 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test reception on the virtualized UART by creating two readers that //! read in parallel. To add this test, include the line //! ``` //! virtual_uart_rx_test::run_virtual_uart_receive(uart_mux); //! ``` //! to the imix boot sequence, where `uart_mux` is a -//! `capsules::virtual_uart::MuxUart`. There is a 3-byte and a 7-byte +//! `capsules_core::virtualizers::virtual_uart::MuxUart`. There is a 3-byte and a 7-byte //! read running in parallel. Test that they are both working by typing //! and seeing that they both get all characters. If you repeatedly //! type 'a', for example (0x61), you should see something like: @@ -44,8 +48,10 @@ //! 61 //! ``` -use capsules::test::virtual_uart::TestVirtualUartReceive; -use capsules::virtual_uart::{MuxUart, UartDevice}; +use core::ptr::addr_of_mut; + +use capsules_core::test::virtual_uart::TestVirtualUartReceive; +use capsules_core::virtualizers::virtual_uart::{MuxUart, UartDevice}; use kernel::debug; use kernel::hil::uart::Receive; use kernel::static_init; @@ -66,7 +72,7 @@ unsafe fn static_init_test_receive_small( device.setup(); let test = static_init!( TestVirtualUartReceive, - TestVirtualUartReceive::new(device, &mut SMALL) + TestVirtualUartReceive::new(device, &mut *addr_of_mut!(SMALL)) ); device.set_receive_client(test); test @@ -80,7 +86,7 @@ unsafe fn static_init_test_receive_large( device.setup(); let test = static_init!( TestVirtualUartReceive, - TestVirtualUartReceive::new(device, &mut BUFFER) + TestVirtualUartReceive::new(device, &mut *addr_of_mut!(BUFFER)) ); device.set_receive_client(test); test diff --git a/boards/imxrt1050-evkb/Cargo.toml b/boards/imxrt1050-evkb/Cargo.toml index 950cc28820..497de8a5ab 100644 --- a/boards/imxrt1050-evkb/Cargo.toml +++ b/boards/imxrt1050-evkb/Cargo.toml @@ -1,13 +1,23 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "imxrt1050-evkb" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] components = { path = "../components" } cortexm7 = { path = "../../arch/cortex-m7" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } imxrt10xx = { path = "../../chips/imxrt10xx" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/imxrt1050-evkb/MIMXRT1052xxxxB.xml b/boards/imxrt1050-evkb/MIMXRT1052xxxxB.xml index 22a0405a0e..f77b1d9173 100644 --- a/boards/imxrt1050-evkb/MIMXRT1052xxxxB.xml +++ b/boards/imxrt1050-evkb/MIMXRT1052xxxxB.xml @@ -1,3 +1,7 @@ + + + + diff --git a/boards/imxrt1050-evkb/MIMXRT1052xxxxB_part.xml b/boards/imxrt1050-evkb/MIMXRT1052xxxxB_part.xml index 26ac221b4f..c8a4ad9ebe 100644 --- a/boards/imxrt1050-evkb/MIMXRT1052xxxxB_part.xml +++ b/boards/imxrt1050-evkb/MIMXRT1052xxxxB_part.xml @@ -1,2 +1,6 @@ + + + + MIMXRT1052xxxxB MIMXRT1050 NXP Cortex-M7 Cortex-M diff --git a/boards/imxrt1050-evkb/Makefile b/boards/imxrt1050-evkb/Makefile index ca3e04a495..6e844fd47f 100644 --- a/boards/imxrt1050-evkb/Makefile +++ b/boards/imxrt1050-evkb/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the i.MX RT1052 platform TARGET=thumbv7em-none-eabi diff --git a/boards/imxrt1050-evkb/build.rs b/boards/imxrt1050-evkb/build.rs deleted file mode 100644 index bab41af2a6..0000000000 --- a/boards/imxrt1050-evkb/build.rs +++ /dev/null @@ -1,8 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=chip_layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); - - // rebuild if `asm.s` changed - println!("cargo:rerun-if-changed=hdr.s"); // <- NEW! -} diff --git a/boards/imxrt1050-evkb/chip_layout.ld b/boards/imxrt1050-evkb/chip_layout.ld index ce6a6ded91..8918f5709e 100644 --- a/boards/imxrt1050-evkb/chip_layout.ld +++ b/boards/imxrt1050-evkb/chip_layout.ld @@ -1,3 +1,7 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + /* Memory layout for the i.MX RT 1050 EVKB * rom = 2MB (LENGTH = 0x02000000) * kernel = 256KB @@ -12,8 +16,6 @@ MEMORY ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x20000 } -MPU_MIN_ALIGN = 8K; - SECTIONS { .boot_hdr : ALIGN(4) @@ -21,4 +23,4 @@ SECTIONS { KEEP (*(.boot_hdr)) } > hdr -} \ No newline at end of file +} diff --git a/boards/imxrt1050-evkb/hdr.s b/boards/imxrt1050-evkb/hdr.s deleted file mode 100644 index 71cbe6e00c..0000000000 --- a/boards/imxrt1050-evkb/hdr.s +++ /dev/null @@ -1,2 +0,0 @@ -.section .boot_hdr, "ax" -.incbin "boot_header" diff --git a/boards/imxrt1050-evkb/layout.ld b/boards/imxrt1050-evkb/layout.ld index 234dcaea2c..6814c00acd 100644 --- a/boards/imxrt1050-evkb/layout.ld +++ b/boards/imxrt1050-evkb/layout.ld @@ -1,2 +1,6 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + INCLUDE ./chip_layout.ld INCLUDE ../kernel_layout.ld diff --git a/boards/imxrt1050-evkb/src/boot_header.rs b/boards/imxrt1050-evkb/src/boot_header.rs index 2e8b97e007..f804bfabda 100644 --- a/boards/imxrt1050-evkb/src/boot_header.rs +++ b/boards/imxrt1050-evkb/src/boot_header.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + /// Vector which contains the FCB header /// This section is extracted with objcopy from the hello world /// example in the i.MX RT 1050 EVKB SDK examples. diff --git a/boards/imxrt1050-evkb/src/io.rs b/boards/imxrt1050-evkb/src/io.rs index bfc5cafe08..7d4292dc82 100644 --- a/boards/imxrt1050-evkb/src/io.rs +++ b/boards/imxrt1050-evkb/src/io.rs @@ -1,5 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; +use core::ptr::addr_of; +use core::ptr::addr_of_mut; use kernel::debug; use kernel::debug::IoWrite; @@ -37,7 +43,7 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { let ccm = crate::imxrt1050::ccm::Ccm::new(); let uart = imxrt1050::lpuart::Lpuart::new_lpuart1(&ccm); @@ -56,25 +62,26 @@ impl IoWrite for Writer { for &c in buf { uart.send_byte(c); } + buf.len() } } /// Panic handler. #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(info: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(info: &PanicInfo) -> ! { // User Led is connected to AdB0_09 let pin = imxrt1050::gpio::Pin::from_pin_id(PinId::AdB0_09); let led = &mut led::LedLow::new(&pin); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led], writer, info, &cortexm7::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/imxrt1050-evkb/src/main.rs b/boards/imxrt1050-evkb/src/main.rs index 9157f0b7cf..660eff4d35 100644 --- a/boards/imxrt1050-evkb/src/main.rs +++ b/boards/imxrt1050-evkb/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Reference Manual for the Imxrt-1052 development board //! //! - @@ -6,12 +10,13 @@ #![no_main] #![deny(missing_docs)] -use capsules::virtual_alarm::VirtualMuxAlarm; +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; use components::gpio::GpioComponent; use kernel::capabilities; use kernel::component::Component; use kernel::debug; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil::gpio::Configure; use kernel::hil::led::LedLow; use kernel::platform::{KernelResources, SyscallDriverLookup}; @@ -49,10 +54,12 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] type Chip = imxrt1050::chip::Imxrt10xx; static mut CHIP: Option<&'static Chip> = None; -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; // Manually setting the boot header section that contains the FCB header #[used] @@ -69,20 +76,20 @@ pub static mut STACK_MEMORY: [u8; 0x2000] = [0; 0x2000]; /// A structure representing this platform that holds references to all /// capsules for this platform. struct Imxrt1050EVKB { - alarm: &'static capsules::alarm::AlarmDriver< + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, VirtualMuxAlarm<'static, imxrt1050::gpt::Gpt1<'static>>, >, - button: &'static capsules::button::Button<'static, imxrt1050::gpio::Pin<'static>>, - console: &'static capsules::console::Console<'static>, - gpio: &'static capsules::gpio::GPIO<'static, imxrt1050::gpio::Pin<'static>>, - ipc: kernel::ipc::IPC, - led: &'static capsules::led::LedDriver< + button: &'static capsules_core::button::Button<'static, imxrt1050::gpio::Pin<'static>>, + console: &'static capsules_core::console::Console<'static>, + gpio: &'static capsules_core::gpio::GPIO<'static, imxrt1050::gpio::Pin<'static>>, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + led: &'static capsules_core::led::LedDriver< 'static, LedLow<'static, imxrt1050::gpio::Pin<'static>>, 1, >, - ninedof: &'static capsules::ninedof::NineDof<'static>, + ninedof: &'static capsules_extra::ninedof::NineDof<'static>, scheduler: &'static RoundRobinSched<'static>, systick: cortexm7::systick::SysTick, @@ -95,13 +102,13 @@ impl SyscallDriverLookup for Imxrt1050EVKB { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::button::DRIVER_NUM => f(Some(self.button)), - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::ninedof::DRIVER_NUM => f(Some(self.ninedof)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_extra::ninedof::DRIVER_NUM => f(Some(self.ninedof)), _ => f(None), } } @@ -119,7 +126,7 @@ impl KernelResources &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -219,24 +226,18 @@ unsafe fn setup_peripherals(peripherals: &imxrt1050::chip::Imxrt10xxDefaultPerip /// removed when this function returns. Otherwise, the stack space used for /// these static_inits is wasted. #[inline(never)] -unsafe fn get_peripherals() -> &'static mut imxrt1050::chip::Imxrt10xxDefaultPeripherals { +unsafe fn start() -> ( + &'static kernel::Kernel, + Imxrt1050EVKB, + &'static imxrt1050::chip::Imxrt10xx, +) { + imxrt1050::init(); + let ccm = static_init!(imxrt1050::ccm::Ccm, imxrt1050::ccm::Ccm::new()); let peripherals = static_init!( imxrt1050::chip::Imxrt10xxDefaultPeripherals, imxrt1050::chip::Imxrt10xxDefaultPeripherals::new(ccm) ); - - peripherals -} - -/// Main function. -/// -/// This is called after RAM initialization is complete. -#[no_mangle] -pub unsafe fn main() { - imxrt1050::init(); - - let peripherals = get_peripherals(); peripherals.ccm.set_low_power_mode(); peripherals.lpuart1.disable_clock(); peripherals.lpuart2.disable_clock(); @@ -250,15 +251,7 @@ pub unsafe fn main() { setup_peripherals(peripherals); - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); - - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 2], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); let chip = static_init!(Chip, Chip::new(peripherals)); CHIP = Some(chip); @@ -309,35 +302,31 @@ pub unsafe fn main() { // Enable clock peripherals.lpuart1.enable_clock(); - let lpuart_mux = components::console::UartMuxComponent::new( - &peripherals.lpuart1, - 115200, - dynamic_deferred_caller, - ) - .finalize(()); + let lpuart_mux = components::console::UartMuxComponent::new(&peripherals.lpuart1, 115200) + .finalize(components::uart_mux_component_static!()); io::WRITER.set_initialized(); // Create capabilities that the board needs to call certain protected kernel // functions. let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, lpuart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(lpuart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(lpuart_mux) + .finalize(components::debug_writer_component_static!()); // LEDs // Clock to Port A is enabled in `set_pin_primary_functions() - let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( LedLow<'static, imxrt1050::gpio::Pin<'static>>, LedLow::new(peripherals.ports.pin(imxrt1050::gpio::PinId::AdB0_09)), )); @@ -345,7 +334,7 @@ pub unsafe fn main() { // BUTTONs let button = components::button::ButtonComponent::new( board_kernel, - capsules::button::DRIVER_NUM, + capsules_core::button::DRIVER_NUM, components::button_component_helper!( imxrt1050::gpio::Pin, ( @@ -355,33 +344,33 @@ pub unsafe fn main() { ) ), ) - .finalize(components::button_component_buf!(imxrt1050::gpio::Pin)); + .finalize(components::button_component_static!(imxrt1050::gpio::Pin)); // ALARM let gpt1 = &peripherals.gpt1; let mux_alarm = components::alarm::AlarmMuxComponent::new(gpt1).finalize( - components::alarm_mux_component_helper!(imxrt1050::gpt::Gpt1), + components::alarm_mux_component_static!(imxrt1050::gpt::Gpt1), ); let alarm = components::alarm::AlarmDriverComponent::new( board_kernel, - capsules::alarm::DRIVER_NUM, + capsules_core::alarm::DRIVER_NUM, mux_alarm, ) - .finalize(components::alarm_component_helper!(imxrt1050::gpt::Gpt1)); + .finalize(components::alarm_component_static!(imxrt1050::gpt::Gpt1)); // GPIO // For now we expose only two pins let gpio = GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( imxrt1050::gpio::Pin<'static>, // The User Led 0 => peripherals.ports.pin(imxrt1050::gpio::PinId::AdB0_09) ), ) - .finalize(components::gpio_component_buf!( + .finalize(components::gpio_component_static!( imxrt1050::gpio::Pin<'static> )); @@ -439,37 +428,42 @@ pub unsafe fn main() { .set_speed(imxrt1050::lpi2c::Lpi2cSpeed::Speed100k, 8); use imxrt1050::gpio::PinId; - let mux_i2c = - components::i2c::I2CMuxComponent::new(&peripherals.lpi2c1, None, dynamic_deferred_caller) - .finalize(components::i2c_mux_component_helper!()); + let mux_i2c = components::i2c::I2CMuxComponent::new(&peripherals.lpi2c1, None).finalize( + components::i2c_mux_component_static!(imxrt1050::lpi2c::Lpi2c), + ); // Fxos8700 sensor let fxos8700 = components::fxos8700::Fxos8700Component::new( mux_i2c, + 0x1f, peripherals.ports.pin(PinId::AdB1_00), ) - .finalize(()); + .finalize(components::fxos8700_component_static!( + imxrt1050::lpi2c::Lpi2c + )); // Ninedof - let ninedof = - components::ninedof::NineDofComponent::new(board_kernel, capsules::ninedof::DRIVER_NUM) - .finalize(components::ninedof_component_helper!(fxos8700)); + let ninedof = components::ninedof::NineDofComponent::new( + board_kernel, + capsules_extra::ninedof::DRIVER_NUM, + ) + .finalize(components::ninedof_component_static!(fxos8700)); - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); let imxrt1050 = Imxrt1050EVKB { - console: console, + console, ipc: kernel::ipc::IPC::new( board_kernel, kernel::ipc::DRIVER_NUM, &memory_allocation_capability, ), - led: led, - button: button, - ninedof: ninedof, - alarm: alarm, - gpio: gpio, + led, + button, + ninedof, + alarm, + gpio, scheduler, systick: cortexm7::systick::SysTick::new_with_calibration(792_000_000), @@ -483,8 +477,8 @@ pub unsafe fn main() { //-------------------------------------------------------------------------- // Process Console //--------------------------------------------------------------------------- - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); let process_console = components::process_console::ProcessConsoleComponent::new( @@ -492,8 +486,9 @@ pub unsafe fn main() { lpuart_mux, mux_alarm, process_printer, + None, ) - .finalize(components::process_console_component_helper!( + .finalize(components::process_console_component_static!( imxrt1050::gpt::Gpt1 )); let _ = process_console.start(); @@ -519,14 +514,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_management_capability, ) @@ -535,10 +530,14 @@ pub unsafe fn main() { debug!("{:?}", err); }); - board_kernel.kernel_loop( - &imxrt1050, - chip, - Some(&imxrt1050.ipc), - &main_loop_capability, - ); + (board_kernel, imxrt1050, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, board, chip) = start(); + board_kernel.kernel_loop(&board, chip, Some(&board.ipc), &main_loop_capability); } diff --git a/boards/kernel_layout.ld b/boards/kernel_layout.ld index bf6f7bbd6e..e056cd120b 100644 --- a/boards/kernel_layout.ld +++ b/boards/kernel_layout.ld @@ -1,8 +1,11 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + /* * This is the generic linker script for Tock. For most developers, it should * be sufficient to define {ROM/PROG/RAM}_{ORIGIN/LENGTH} (6 variables, the - * start and length for each), MPU_MIN_ALIGN (the minimum alignment - * granularity supported by the MPU) and PAGE_SIZE (the size of a flash page). + * start and length for each) and PAGE_SIZE (the size of a flash page). * If undefined, PAGE_SIZE uses the default value of 512 bytes. * * -------------------------------------------------------------------------- @@ -324,24 +327,99 @@ SECTIONS * * Tock uses the remainder of SRAM for application memory. * - * Currently, Tock allocates a fixed array of application memories at + * Currently, Tock allocates a fixed array of application memory at * compile-time, and that array is simply placed here. A possible * future enhancement may allow the kernel to parcel this memory space * dynamically, requiring changes to this section. */ - . = ALIGN(MPU_MIN_ALIGN); _sappmem = .; *(.app_memory) } > ram _eappmem = ORIGIN(ram) + LENGTH(ram); + + /* Place attributes at the end of the kernel ROM region. These will be used + * for host-side tools to learn about the installed kernel. + * + * Attributes are stored as TLVs, but going backwards in flash. + * + * 2 byte 2 byte 3 byte 1 byte 4 byte + * -----------+------------+--------+--------+----------+---------+---------+ Start + * | Value... | Type | Length | Reserved | Version | TOCK | of + * -----------+------------+--------+--------+----------+---------+---------+ `prog` + * <-TLVs...-> <-----TLV--------------------> <----Header--------> <-Sentl-> + * + * The TLV `Length` specifies the size of the TLV value (i.e. not including + * the TLV header). + */ + .attributes : AT (ORIGIN(rom) + LENGTH(rom) - SIZEOF(.attributes)) + { + /* TLV: Kernel Flash + * This indicates the start address of the kernel flash and the size of + * the kernel binary. + */ + LONG(ORIGIN(rom)) /* Address of start of kernel binary. */ + LONG((LOADADDR(.relocate) + (_erelocate - _srelocate)) - ORIGIN(rom)) /* Length of kernel binary. */ + SHORT(0x0102) /* Type = Kernel Flash = 0x0102 */ + SHORT(8) /* Length = 8 bytes */ + + /* TLV: App Memory + * This indicates the start address and size of RAM available for + * userspace apps. + */ + LONG(_sappmem) /* Address of start of app memory. */ + LONG(_eappmem - _sappmem) /* Length of app memory. */ + SHORT(0x0101) /* Type = App RAM Region = 0x0101 */ + SHORT(8) /* Length = 8 bytes */ + + /* Version and Reserved. Current version is 0x01. + */ + BYTE(0) /* Reserved */ + BYTE(0) /* Reserved */ + BYTE(0) /* Reserved */ + BYTE(1) /* Version = 0x01 */ + + /* Sentinel. + */ + BYTE(84) /* T */ + BYTE(79) /* O */ + BYTE(67) /* C */ + BYTE(75) /* K */ + } > rom + + /* Discard RISC-V relevant .eh_frame, we are not doing unwind on panic so it is not needed. */ /DISCARD/ : { - *(.eh_frame); + *(.eh_frame*); } } -ASSERT((_etext-_stext) + (_erelocate-_srelocate) < LENGTH(rom), " -Text plus relocations exceeds the available ROM space."); +/* LOADADDR: Return the absolute load address of the named section. This is + * normally the same as ADDR, but it may be different if the AT keyword is used + * in the section definition. */ +_sattributes = LOADADDR(.attributes); +_eattributes = LOADADDR(.attributes) + SIZEOF(.attributes); +_erom = ORIGIN(rom) + LENGTH(rom); + +/* This.. this is a dirty, not-fully-understood, hack. In some way, linking is + * a multi-pass process. i.e., if you were to ASSERT(0, "how many links") + * you'll get three assertions printed for one invocation of rust-lld. + * + * Empirically, in the first pass, _eattributes ends up at + * _erom + SIZEOF(.attributes) + * So, we need to allow that case. + * + * That's really not the dream, since we can't identify passes. We really only + * want to let the wrong location through on the first pass. Thus, the _best_ + * solution would be to extract the _erom and _eattributes symbols with readelf + * or similar and verify they're equal, but that would be yet-one-more-tool. + * Maybe something we can do in CI at some point, and remove this assert. + */ +ASSERT((_eattributes == _erom) || (_eattributes == _erom + SIZEOF(.attributes)), "Kernel attributes are not at the end of ROM.") + +/* This assert works out because even though some of the relative positions are + * off, the sizes are sane in each pass. */ +ASSERT((_etext - _stext) + (_erelocate - _srelocate) + (_eattributes - _sattributes) < LENGTH(rom), +"Text plus relocations plus attributes exceeds the available ROM space."); diff --git a/boards/litex/arty/Cargo.toml b/boards/litex/arty/Cargo.toml index 302f42e2e2..9d7e5da9ff 100644 --- a/boards/litex/arty/Cargo.toml +++ b/boards/litex/arty/Cargo.toml @@ -1,13 +1,23 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "litex_arty" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../../build.rs" +edition.workspace = true [dependencies] components = { path = "../../components" } rv32i = { path = "../../../arch/rv32i" } -capsules = { path = "../../../capsules" } kernel = { path = "../../../kernel" } litex_vexriscv = { path = "../../../chips/litex_vexriscv" } + +capsules-core = { path = "../../../capsules/core" } +capsules-extra = { path = "../../../capsules/extra" } +capsules-system = { path = "../../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/litex/arty/Makefile b/boards/litex/arty/Makefile index 501ca59f21..e761758ac4 100644 --- a/boards/litex/arty/Makefile +++ b/boards/litex/arty/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for a LiteX SoC targeting a # Digilent Arty-A7 FPGA development board diff --git a/boards/litex/arty/README.md b/boards/litex/arty/README.md index d17fc541d4..6c815e5fff 100644 --- a/boards/litex/arty/README.md +++ b/boards/litex/arty/README.md @@ -11,9 +11,9 @@ differ significantly depending on the LiteX release and configuration options used. This board definition currently targets and has been tested with - [the LiteX SoC generator, revision - c43132f81f1113](https://github.com/enjoy-digital/litex/tree/c43132f81f1113) + a6d9955c9d3065](https://github.com/enjoy-digital/litex/tree/a6d9955c9d3065) - using the companion [target - file](https://github.com/litex-hub/litex-boards/blob/f18b10d1edb4e1/litex_boards/targets/digilent_arty.py) + file](https://github.com/litex-hub/litex-boards/blob/52f9f0f1079085/litex_boards/targets/digilent_arty.py) from `litex-boards` - built around a VexRiscv-CPU with PMP, hardware multiplication and compressed instruction support (named `TockSecureIMC`) @@ -24,7 +24,12 @@ tested with --cpu-variant=tock+secure+imc --csr-data-width=32 --timer-uptime + --integrated-rom-size=0xb000 --with-ethernet + --with-buttons + --with-xadc + --with-dna + --with-spi-flash ``` The `tock+secure+imc` is a custom VexRiscv CPU variant, based on the @@ -35,13 +40,13 @@ hardware multiplication and compressed instruction support (such that it is compatible with the `rv32imc` arch). Prebuilt and tested bitstreams (including the generated VexRiscv CPU -Verilog files) can be obtained from the [Tock on LiteX companion -repository +Verilog files and a patched LiteX version to support them) can be +obtained from the [Tock on LiteX companion repository releases](https://github.com/lschuermann/tock-litex/releases/). The current board definition has been verified to work with [release -2021100501](https://github.com/lschuermann/tock-litex/releases/tag/2021100501). The -bitstream for this board is located in `digilent_arty_a7-35t.zip` or -`digilent_arty_a7-100t.zip` under `gateware/digilent_arty.bit`. +2024011101](https://github.com/lschuermann/tock-litex/releases/tag/2024011101). +The bitstream for this board is located in `digilent_arty_a7-35t.zip` +or `digilent_arty_a7-100t.zip` under `gateware/digilent_arty.bit`. Many bitstream customizations can be represented in the Tock board by simply changing the variables in @@ -59,18 +64,24 @@ and cores are supported: - [X] UART output via USB-FTDI - [X] Green onboard LEDs - [X] 100MBit/s Ethernet MAC +- [X] GPIO Interface +- [X] Buttons and Switches The following components and cores require porting: -- [ ] GPIO Interface -- [ ] Buttons and Switches - [ ] RGB LEDs +- [ ] XADC Core +- [ ] FPGA DNA Core +- [ ] SPI Flash Building the SoC / Programming the FPGA --------------------------------------- -Please refer to the [LiteX -documentation](https://github.com/enjoy-digital/litex/wiki/) for +The [Tock on LiteX companion +repository](https://github.com/lschuermann/tock-litex) contains instructions for +how to build the specific FPGA bitstream as targeted with this board +definition. Please refer to the [LiteX +documentation](https://github.com/enjoy-digital/litex/wiki/) for general instructions on how to install and use the LiteX SoC generator. Once LiteX and Xilinx Vivado is installed, building a bitstream should @@ -120,10 +131,10 @@ To boot via serial run the LiteX-included `litex_term.py` (sometimes available as `lxterm`): ``` $ ./litex/litex/tools/litex_term.py \ - --speed 10000000 \ - --serial-boot \ - --kernel $TOCK_BINARY \ - $SERIAL_PORT + --speed 1000000 \ + --serial-boot \ + --kernel $TOCK_BINARY \ + $SERIAL_PORT ``` , where `TOCK_BINARY` points to the board's binary (kernel, optionally including optionally applications), and `SERIAL_PORT` is the UART @@ -181,6 +192,39 @@ $ cp $PATH_TO_TOCK/target/riscv32i-unknown-none-elf/release/litex_arty.bin \ Make sure that the tftp server is running and the firewall is configured correctly. +### Running Applications + +The LiteX Arty board does not currently expose or use any persistent storage, +other than to hold the FPGA bistream containing the LiteX BIOS. For this reason, +Tockloader is not able to interact with this board directly. However, Tockloader +includes a flash-file support mode which supports operating on a binary file +representing the device's flash. This can be used to combine the kernel and +applications in a single binary, which can then be loaded onto the board using +the above methods. An example of this is illustrated below: + +``` +$ tockloader flash \ + --board litex_arty \ + --flash-file ./litex_arty_flash.bin \ + -a 0x0 \ + ./tock/target/riscv32imc-unknown-none-elf/release/litex_arty.bin +[INFO ] Operating on flash file "./litex_arty_flash.bin". +[INFO ] Limiting flash size to 0x8000000 bytes. +[STATUS ] Flashing binary to board... +[INFO ] Finished in 0.000 seconds +$ tockloader install \ + --board litex_arty \ + --arch rv32imc \ + --flash-file ./litex_arty_flash.bin \ + ./libtock-c/examples/c_hello/build/c_hello.tab +[INFO ] Using settings from KNOWN_BOARDS["litex_arty"] +[INFO ] Operating on flash file "./litex_arty_flash.bin". +[INFO ] Limiting flash size to 0x10000000 bytes. +[STATUS ] Installing app on the board... +[INFO ] Found sort order: +[INFO ] App "c_hello" at address 0x41000060 +[INFO ] Finished in 0.002 seconds +``` Debugging --------- diff --git a/boards/litex/arty/build.rs b/boards/litex/arty/build.rs deleted file mode 100644 index 1fdd4924f0..0000000000 --- a/boards/litex/arty/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../../kernel_layout.ld"); -} diff --git a/boards/litex/arty/layout.ld b/boards/litex/arty/layout.ld index 789090df4a..f9a3511b7c 100644 --- a/boards/litex/arty/layout.ld +++ b/boards/litex/arty/layout.ld @@ -1,12 +1,27 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + /* The entire memory is actually placed into DRAM by the bootloader */ MEMORY { rom (rx) : ORIGIN = 0x40000000, LENGTH = 0x1000000 prog (rx) : ORIGIN = 0x41000000, LENGTH = 0x1000000 - ram (rwx) : ORIGIN = 0x42000000, LENGTH = 0x50000000 - 0x42000000 + ram (rwx) : ORIGIN = 0x42000000, LENGTH = 0x1000000 } -MPU_MIN_ALIGN = 1K; +SECTIONS { + /* Export the start & end of SRAM and flash as symbols for setting up the + * PMP. Flash includes rom and prog, such that we can use a single NAPOT + * region. The .text section will be made executable by a separate PMP + * region. + */ + _sflash = ORIGIN(rom); + _eflash = ORIGIN(prog) + LENGTH(prog); + + _ssram = ORIGIN(ram); + _esram = ORIGIN(ram) + LENGTH(ram); +} INCLUDE ../../kernel_layout.ld diff --git a/boards/litex/arty/src/io.rs b/boards/litex/arty/src/io.rs index 3e6aa61e5b..b434d83086 100644 --- a/boards/litex/arty/src/io.rs +++ b/boards/litex/arty/src/io.rs @@ -1,9 +1,12 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; use core::str; use kernel::debug; use kernel::debug::IoWrite; -use rv32i; use crate::{PANIC_REFERENCES, PROCESSES}; @@ -19,10 +22,11 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { unsafe { PANIC_REFERENCES.uart.unwrap().transmit_sync(buf); } + buf.len() } } @@ -30,20 +34,22 @@ impl IoWrite for Writer { #[cfg(not(test))] #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { + use core::ptr::{addr_of, addr_of_mut}; + let panic_led = PANIC_REFERENCES .led_controller .and_then(|ctrl| ctrl.panic_led(0)); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [&mut panic_led.unwrap()], writer, pi, &rv32i::support::nop, - &PROCESSES, - &PANIC_REFERENCES.chip, - &PANIC_REFERENCES.process_printer, + &*addr_of!(PROCESSES), + &*addr_of!(PANIC_REFERENCES.chip), + &*addr_of!(PANIC_REFERENCES.process_printer), ) } diff --git a/boards/litex/arty/src/litex_generated_constants.rs b/boards/litex/arty/src/litex_generated_constants.rs index 8e933cdd61..c484fc066c 100644 --- a/boards/litex/arty/src/litex_generated_constants.rs +++ b/boards/litex/arty/src/litex_generated_constants.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + #![allow(unused)] // LiteX SoC Wishbone Register format @@ -36,15 +40,19 @@ pub const UART_INTERRUPT: usize = 0; // constants defined in `generated/csr.h` pub const CSR_BASE: usize = 0xf0000000; -pub const CSR_CTRL_BASE: usize = CSR_BASE + 0x0000; -pub const CSR_DDRPHY_BASE: usize = CSR_BASE + 0x0800; -pub const CSR_ETHMAC_BASE: usize = CSR_BASE + 0x1000; -pub const CSR_ETHPHY_BASE: usize = CSR_BASE + 0x1800; -pub const CSR_IDENTIFIER_MEM_BASE: usize = CSR_BASE + 0x2000; -pub const CSR_LEDS_BASE: usize = CSR_BASE + 0x2800; -pub const CSR_SDRAM_BASE: usize = CSR_BASE + 0x3000; -pub const CSR_TIMER0_BASE: usize = CSR_BASE + 0x3800; -pub const CSR_UART_BASE: usize = CSR_BASE + 0x4000; +pub const CSR_BUTTONS_BASE: usize = CSR_BASE + 0x0000; +pub const CSR_CTRL_BASE: usize = CSR_BASE + 0x0800; +pub const CSR_DDRPHY_BASE: usize = CSR_BASE + 0x1000; +pub const CSR_DNA_BASE: usize = CSR_BASE + 0x1800; +pub const CSR_ETHMAC_BASE: usize = CSR_BASE + 0x2000; +pub const CSR_ETHPHY_BASE: usize = CSR_BASE + 0x2800; +pub const CSR_IDENTIFIER_MEM_BASE: usize = CSR_BASE + 0x3000; +pub const CSR_LEDS_BASE: usize = CSR_BASE + 0x3800; +pub const CSR_SDRAM_BASE: usize = CSR_BASE + 0x4000; +pub const CSR_SPIFLASH_CORE_BASE: usize = CSR_BASE + 0x4800; +pub const CSR_TIMER0_BASE: usize = CSR_BASE + 0x5000; +pub const CSR_UART_BASE: usize = CSR_BASE + 0x5800; +pub const CSR_XADC_BASE: usize = CSR_BASE + 0x6000; // constants defined in `generated/mem.h` pub const MEM_ETHMAC_BASE: usize = 0x80000000; diff --git a/boards/litex/arty/src/main.rs b/boards/litex/arty/src/main.rs index 88f2abebe0..893d492f8d 100644 --- a/boards/litex/arty/src/main.rs +++ b/boards/litex/arty/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Board file for a LiteX-built VexRiscv-based SoC synthesized for a //! Digilent Arty-A7 FPGA board @@ -6,10 +10,12 @@ // https://github.com/rust-lang/rust/issues/62184. #![cfg_attr(not(doc), no_main)] -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; + use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil::time::{Alarm, Timer}; use kernel::platform::chip::InterruptService; use kernel::platform::scheduler_timer::VirtualSchedulerTimer; @@ -47,7 +53,15 @@ struct LiteXArtyInterruptablePeripherals { >, ethmac0: &'static litex_vexriscv::liteeth::LiteEth<'static, socc::SoCRegisterFmt>, } -impl InterruptService<()> for LiteXArtyInterruptablePeripherals { + +impl LiteXArtyInterruptablePeripherals { + // Resolve any recursive dependencies and set up deferred calls: + pub fn init(&'static self) { + kernel::deferred_call::DeferredCallClient::register(self.uart0); + } +} + +impl InterruptService for LiteXArtyInterruptablePeripherals { unsafe fn service_interrupt(&self, interrupt: u32) -> bool { match interrupt as usize { socc::UART_INTERRUPT => { @@ -65,10 +79,6 @@ impl InterruptService<()> for LiteXArtyInterruptablePeripherals { _ => false, } } - - unsafe fn service_deferred_call(&self, _task: ()) -> bool { - false - } } const NUM_PROCS: usize = 4; @@ -85,7 +95,7 @@ struct LiteXArtyPanicReferences { uart: Option<&'static litex_vexriscv::uart::LiteXUart<'static, socc::SoCRegisterFmt>>, led_controller: Option<&'static litex_vexriscv::led_controller::LiteXLedController>, - process_printer: Option<&'static kernel::process::ProcessPrinterText>, + process_printer: Option<&'static capsules_system::process_printer::ProcessPrinterText>, } static mut PANIC_REFERENCES: LiteXArtyPanicReferences = LiteXArtyPanicReferences { chip: None, @@ -95,7 +105,8 @@ static mut PANIC_REFERENCES: LiteXArtyPanicReferences = LiteXArtyPanicReferences }; // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; /// Dummy buffer that causes the linker to reserve enough space for the stack. #[no_mangle] @@ -105,17 +116,31 @@ pub static mut STACK_MEMORY: [u8; 0x2000] = [0; 0x2000]; /// A structure representing this platform that holds references to all /// capsules for this platform. struct LiteXArty { - led_driver: &'static capsules::led::LedDriver< + led_driver: &'static capsules_core::led::LedDriver< 'static, litex_vexriscv::led_controller::LiteXLed<'static, socc::SoCRegisterFmt>, 4, >, - console: &'static capsules::console::Console<'static>, - lldb: &'static capsules::low_level_debug::LowLevelDebug< + console: &'static capsules_core::console::Console<'static>, + pconsole: &'static capsules_core::process_console::ProcessConsole< + 'static, + { capsules_core::process_console::DEFAULT_COMMAND_HISTORY_LEN }, + VirtualMuxAlarm< + 'static, + litex_vexriscv::timer::LiteXAlarm< + 'static, + 'static, + socc::SoCRegisterFmt, + socc::ClockFrequency, + >, + >, + components::process_console::Capability, + >, + lldb: &'static capsules_core::low_level_debug::LowLevelDebug< 'static, - capsules::virtual_uart::UartDevice<'static>, + capsules_core::virtualizers::virtual_uart::UartDevice<'static>, >, - alarm: &'static capsules::alarm::AlarmDriver< + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, VirtualMuxAlarm< 'static, @@ -127,6 +152,7 @@ struct LiteXArty { >, >, >, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, scheduler: &'static CooperativeSched<'static>, scheduler_timer: &'static VirtualSchedulerTimer< VirtualMuxAlarm< @@ -148,10 +174,11 @@ impl SyscallDriverLookup for LiteXArty { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::led::DRIVER_NUM => f(Some(self.led_driver)), - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::low_level_debug::DRIVER_NUM => f(Some(self.lldb)), + capsules_core::led::DRIVER_NUM => f(Some(self.led_driver)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::low_level_debug::DRIVER_NUM => f(Some(self.lldb)), + kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), _ => f(None), } } @@ -179,7 +206,7 @@ impl KernelResources &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -191,7 +218,7 @@ impl KernelResources &Self::SchedulerTimer { - &self.scheduler_timer + self.scheduler_timer } fn watchdog(&self) -> &Self::WatchDog { &() @@ -201,31 +228,84 @@ impl KernelResources ( + &'static kernel::Kernel, + LiteXArty, + &'static litex_vexriscv::chip::LiteXVexRiscv, +) { + // These symbols are defined in the linker script. + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + /// The start of the kernel text (Included only for kernel PMP) + static _stext: u8; + /// The end of the kernel text (Included only for kernel PMP) + static _etext: u8; + /// The start of the kernel / app / storage flash (Included only for kernel PMP) + static _sflash: u8; + /// The end of the kernel / app / storage flash (Included only for kernel PMP) + static _eflash: u8; + /// The start of the kernel / app RAM (Included only for kernel PMP) + static _ssram: u8; + /// The end of the kernel / app RAM (Included only for kernel PMP) + static _esram: u8; + } + // ---------- BASIC INITIALIZATION ---------- // Basic setup of the riscv platform. - rv32i::configure_trap_handler(rv32i::PermissionMode::Machine); + rv32i::configure_trap_handler(); + + // Set up memory protection immediately after setting the trap handler, to + // ensure that much of the board initialization routine runs with PMP kernel + // memory protection. + let pmp = rv32i::pmp::kernel_protection::KernelProtectionPMP::new( + rv32i::pmp::kernel_protection::FlashRegion( + rv32i::pmp::NAPOTRegionSpec::new( + core::ptr::addr_of!(_sflash), + core::ptr::addr_of!(_eflash) as usize - core::ptr::addr_of!(_sflash) as usize, + ) + .unwrap(), + ), + rv32i::pmp::kernel_protection::RAMRegion( + rv32i::pmp::NAPOTRegionSpec::new( + core::ptr::addr_of!(_ssram), + core::ptr::addr_of!(_esram) as usize - core::ptr::addr_of!(_ssram) as usize, + ) + .unwrap(), + ), + rv32i::pmp::kernel_protection::MMIORegion( + rv32i::pmp::NAPOTRegionSpec::new( + 0xf0000000 as *const u8, // start + 0x10000000, // size + ) + .unwrap(), + ), + rv32i::pmp::kernel_protection::KernelTextRegion( + rv32i::pmp::TORRegionSpec::new( + core::ptr::addr_of!(_stext), + core::ptr::addr_of!(_etext), + ) + .unwrap(), + ), + ) + .unwrap(); // initialize capabilities let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability); let memory_allocation_cap = create_capability!(capabilities::MemoryAllocationCapability); - let main_loop_cap = create_capability!(capabilities::MainLoopCapability); - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); - - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 2], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); // ---------- LED CONTROLLER HARDWARE ---------- @@ -314,7 +394,7 @@ pub unsafe fn main() { virtual_alarm_user.setup(); let alarm = static_init!( - capsules::alarm::AlarmDriver< + capsules_core::alarm::AlarmDriver< 'static, VirtualMuxAlarm< 'static, @@ -326,9 +406,9 @@ pub unsafe fn main() { >, >, >, - capsules::alarm::AlarmDriver::new( + capsules_core::alarm::AlarmDriver::new( virtual_alarm_user, - board_kernel.create_grant(capsules::alarm::DRIVER_NUM, &memory_allocation_cap) + board_kernel.create_grant(capsules_core::alarm::DRIVER_NUM, &memory_allocation_cap) ) ); virtual_alarm_user.set_alarm_client(alarm); @@ -377,22 +457,15 @@ pub unsafe fn main() { // hardware. Change with --uart-baudrate during SoC // generation. Fixed to 1MBd. None, - dynamic_deferred_caller, ) ); - uart0.initialize( - dynamic_deferred_caller.register(uart0).unwrap(), // Unwrap fail = dynamic deferred caller out of slots - ); + uart0.initialize(); PANIC_REFERENCES.uart = Some(uart0); // Create a shared UART channel for the console and for kernel debug. - let uart_mux = components::console::UartMuxComponent::new( - uart0, - socc::UART_BAUDRATE, - dynamic_deferred_caller, - ) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(uart0, socc::UART_BAUDRATE) + .finalize(components::uart_mux_component_static!()); // ---------- ETHERNET ---------- @@ -423,7 +496,7 @@ pub unsafe fn main() { // LEDs let led_driver = - components::led::LedsComponent::new().finalize(components::led_component_helper!( + components::led::LedsComponent::new().finalize(components::led_component_static!( litex_vexriscv::led_controller::LiteXLed<'static, socc::SoCRegisterFmt>, led0.get_led(0).unwrap(), led0.get_led(1).unwrap(), @@ -436,11 +509,12 @@ pub unsafe fn main() { let interrupt_service = static_init!( LiteXArtyInterruptablePeripherals, LiteXArtyInterruptablePeripherals { - timer0, uart0, + timer0, ethmac0, } ); + interrupt_service.init(); let chip = static_init!( litex_vexriscv::chip::LiteXVexRiscv< @@ -448,14 +522,15 @@ pub unsafe fn main() { >, litex_vexriscv::chip::LiteXVexRiscv::new( "LiteX on Arty A7", - interrupt_service + interrupt_service, + pmp, ) ); PANIC_REFERENCES.chip = Some(chip); - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PANIC_REFERENCES.process_printer = Some(process_printer); @@ -468,61 +543,76 @@ pub unsafe fn main() { // Unmask all interrupt sources in the interrupt controller chip.unmask_interrupts(); + // Setup the process console. + let pconsole = components::process_console::ProcessConsoleComponent::new( + board_kernel, + uart_mux, + mux_alarm, + process_printer, + None, + ) + .finalize(components::process_console_component_static!( + litex_vexriscv::timer::LiteXAlarm< + 'static, + 'static, + socc::SoCRegisterFmt, + socc::ClockFrequency, + > + )); + // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); + // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); let lldb = components::lldb::LowLevelDebugComponent::new( board_kernel, - capsules::low_level_debug::DRIVER_NUM, + capsules_core::low_level_debug::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::low_level_debug_component_static!()); - debug!("LiteX+VexRiscv on ArtyA7: initialization complete, entering main loop."); - - /// These symbols are defined in the linker script. - extern "C" { - /// Beginning of the ROM region containing app images. - static _sapps: u8; - /// End of the ROM region containing app images. - static _eapps: u8; - /// Beginning of the RAM region for app memory. - static mut _sappmem: u8; - /// End of the RAM region for app memory. - static _eappmem: u8; - } - - let scheduler = components::sched::cooperative::CooperativeComponent::new(&PROCESSES) - .finalize(components::coop_component_helper!(NUM_PROCS)); + let scheduler = + components::sched::cooperative::CooperativeComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::cooperative_component_static!(NUM_PROCS)); let litex_arty = LiteXArty { - console: console, - alarm: alarm, - lldb: lldb, + console, + pconsole, + alarm, + lldb, led_driver, scheduler, scheduler_timer, + ipc: kernel::ipc::IPC::new( + board_kernel, + kernel::ipc::DRIVER_NUM, + &memory_allocation_cap, + ), }; + let _ = litex_arty.pconsole.start(); + debug!("LiteX+VexRiscv on ArtyA7: initialization complete, entering main loop."); + kernel::process::load_processes( board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_mgmt_cap, ) @@ -531,10 +621,14 @@ pub unsafe fn main() { debug!("{:?}", err); }); - board_kernel.kernel_loop( - &litex_arty, - chip, - None::<&kernel::ipc::IPC>, - &main_loop_cap, - ); + (board_kernel, litex_arty, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, board, chip) = start(); + board_kernel.kernel_loop(&board, chip, Some(&board.ipc), &main_loop_capability); } diff --git a/boards/litex/sim/Cargo.toml b/boards/litex/sim/Cargo.toml index 7e6998fd3c..5b100e1e2c 100644 --- a/boards/litex/sim/Cargo.toml +++ b/boards/litex/sim/Cargo.toml @@ -1,13 +1,23 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "litex_sim" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../../build.rs" +edition.workspace = true [dependencies] components = { path = "../../components" } rv32i = { path = "../../../arch/rv32i" } -capsules = { path = "../../../capsules" } kernel = { path = "../../../kernel" } litex_vexriscv = { path = "../../../chips/litex_vexriscv" } + +capsules-core = { path = "../../../capsules/core" } +capsules-extra = { path = "../../../capsules/extra" } +capsules-system = { path = "../../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/litex/sim/Makefile b/boards/litex/sim/Makefile index 662b88078c..f6f573c261 100644 --- a/boards/litex/sim/Makefile +++ b/boards/litex/sim/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for a LiteX SoC running in a # Verilated simulation diff --git a/boards/litex/sim/README.md b/boards/litex/sim/README.md index e9e63202e0..71960ba4c5 100644 --- a/boards/litex/sim/README.md +++ b/boards/litex/sim/README.md @@ -9,9 +9,9 @@ Since LiteX is a SoC builder, the individual generated SoCs can differ significantly depending on the release and configuration options used. This board definition currently targets and has been tested with - [the LiteX SoC generator, revision - c43132f81f1113](https://github.com/enjoy-digital/litex/tree/c43132f81f1113) + a6d9955c9d3065](https://github.com/enjoy-digital/litex/tree/a6d9955c9d3065) - using the included - [`litex_sim`](https://github.com/enjoy-digital/litex/blob/c43132f81f1113/litex/tools/litex_sim.py) + [`litex_sim`](https://github.com/enjoy-digital/litex/blob/a6d9955c9d3065/litex/tools/litex_sim.py) - built around a VexRiscv-CPU with PMP, hardware multiplication and compressed instruction support (named `TockSecureIMC`) - featuring a TIMER0 with 64-bit wide hardware uptime @@ -26,15 +26,15 @@ used. This board definition currently targets and has been tested with --timer-uptime --with-gpio --rom-init $PATH_TO_TOCK_BINARY - --with-gpio ``` The `tock+secure+imc` is a custom VexRiscv CPU variant, based on the build infrastructure in [pythondata-cpu-vexriscv](https://github.com/litex-hub/pythondata-cpu-vexriscv), -which is patched to introduce a CPU with physical memory protection, -hardware multiplication and compressed instruction support (such that -it is compatible with the `rv32imc` arch). +which is patched to introduce a CPU with a Physical Memory Protection +(PMP) unit with Top of Range (TOR) addressing support, hardware +multiplication and compressed instruction support (such that it is +compatible with the `rv32imc` arch). The [`tock-litex`](https://github.com/lschuermann/tock-litex) repository contains helpful instructions for how to set up the local @@ -51,7 +51,7 @@ CSR locations in memory. The companion repository [tock-litex](https://github.com/lschuermann/tock-litex) provides access to an environment with the required LiteX Python packages in their targeted versions. This board currently targets the release -[2021100501](https://github.com/lschuermann/tock-litex/releases/tag/2021100501) +[2024011101](https://github.com/lschuermann/tock-litex/releases/tag/2024011101) of `tock-litex`. @@ -86,6 +86,41 @@ If everything works you should be greeted by the Tock kernel: Verilated LiteX+VexRiscv: initialization complete, entering main loop. ``` +### Running with Applications + +By its nature, this simulated board does not feature any persistent storage. For +this reason, Tockloader is not able to interact with a running LiteX Simulator +instance directly. However, Tockloader includes a flash-file support mode which +supports operating on a binary file representing a device's flash. This can be +used to combine the kernel and applications in a single binary, which can then +be loaded into the simulation using the above method (passed to the `--rom-init` +parameter). An example of this is illustrated below: + +``` +$ tockloader flash \ + --board litex_sim \ + --flash-file ./litex_sim_flash.bin \ + -a 0x0 \ + ./tock/target/riscv32imc-unknown-none-elf/release/litex_sim.bin +[INFO ] Using settings from KNOWN_BOARDS["litex_sim"] +[INFO ] Operating on flash file "./litex_sim_flash.bin". +[INFO ] Limiting flash size to 0x100000 bytes. +[STATUS ] Flashing binary to board... +[INFO ] Finished in 0.000 seconds +$ tockloader install \ + --board litex_sim \ + --arch rv32imc \ + --flash-file ./litex_sim_flash.bin \ + ./libtock-c/examples/c_hello/build/c_hello.tab +[INFO ] Using settings from KNOWN_BOARDS["litex_sim"] +[INFO ] Operating on flash file "./litex_sim_flash.bin". +[INFO ] Limiting flash size to 0x100000 bytes. +[STATUS ] Installing app on the board... +[INFO ] Found sort order: +[INFO ] App "c_hello" at address 0x80060 +[INFO ] Finished in 0.002 seconds +``` + Debugging --------- diff --git a/boards/litex/sim/build.rs b/boards/litex/sim/build.rs deleted file mode 100644 index 1fdd4924f0..0000000000 --- a/boards/litex/sim/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../../kernel_layout.ld"); -} diff --git a/boards/litex/sim/layout.ld b/boards/litex/sim/layout.ld index e826c09ee7..0fe2382257 100644 --- a/boards/litex/sim/layout.ld +++ b/boards/litex/sim/layout.ld @@ -1,3 +1,7 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + /* RAM starts at 0x40000000, the binary is loaded into ROM at 0x0 */ MEMORY @@ -7,6 +11,17 @@ MEMORY ram (rwx) : ORIGIN = 0x40000000, LENGTH = 0x10000000 } -MPU_MIN_ALIGN = 1K; +SECTIONS { + /* Export the start & end of SRAM and flash as symbols for setting up the + * PMP. Flash includes rom and prog, such that we can use a single NAPOT + * region. The .text section will be made executable by a separate PMP + * region. + */ + _sflash = ORIGIN(rom); + _eflash = ORIGIN(prog) + LENGTH(prog); + + _ssram = ORIGIN(ram); + _esram = ORIGIN(ram) + LENGTH(ram); +} INCLUDE ../../kernel_layout.ld diff --git a/boards/litex/sim/src/io.rs b/boards/litex/sim/src/io.rs index 9bbee62d40..0ed1ee7098 100644 --- a/boards/litex/sim/src/io.rs +++ b/boards/litex/sim/src/io.rs @@ -1,9 +1,12 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; use core::str; use kernel::debug; use kernel::debug::IoWrite; -use rv32i; use crate::{PANIC_REFERENCES, PROCESSES}; @@ -19,10 +22,11 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { unsafe { PANIC_REFERENCES.uart.unwrap().transmit_sync(buf); } + buf.len() } } @@ -30,16 +34,18 @@ impl IoWrite for Writer { #[cfg(not(test))] #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { - let writer = &mut WRITER; +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { + use core::ptr::{addr_of, addr_of_mut}; + + let writer = &mut *addr_of_mut!(WRITER); debug::panic_print( writer, pi, &rv32i::support::nop, - &PROCESSES, - &PANIC_REFERENCES.chip, - &PANIC_REFERENCES.process_printer, + &*addr_of!(PROCESSES), + &*addr_of!(PANIC_REFERENCES.chip), + &*addr_of!(PANIC_REFERENCES.process_printer), ); // The system is no longer in a well-defined state; loop forever diff --git a/boards/litex/sim/src/litex_generated_constants.rs b/boards/litex/sim/src/litex_generated_constants.rs index 98033a3294..276c72a89a 100644 --- a/boards/litex/sim/src/litex_generated_constants.rs +++ b/boards/litex/sim/src/litex_generated_constants.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + #![allow(unused)] // LiteX SoC Wishbone Register format diff --git a/boards/litex/sim/src/main.rs b/boards/litex/sim/src/main.rs index 15771322eb..80bc40ae44 100644 --- a/boards/litex/sim/src/main.rs +++ b/boards/litex/sim/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Board file for a LiteX SoC running in a Verilated simulation #![no_std] @@ -5,10 +9,11 @@ // https://github.com/rust-lang/rust/issues/62184. #![cfg_attr(not(doc), no_main)] -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil::led::LedHigh; use kernel::hil::time::{Alarm, Timer}; use kernel::platform::chip::InterruptService; @@ -49,7 +54,14 @@ struct LiteXSimInterruptablePeripherals { ethmac0: &'static litex_vexriscv::liteeth::LiteEth<'static, socc::SoCRegisterFmt>, } -impl InterruptService<()> for LiteXSimInterruptablePeripherals { +impl LiteXSimInterruptablePeripherals { + // Resolve any recursive dependencies and set up deferred calls: + pub fn init(&'static self) { + kernel::deferred_call::DeferredCallClient::register(self.uart0); + } +} + +impl InterruptService for LiteXSimInterruptablePeripherals { unsafe fn service_interrupt(&self, interrupt: u32) -> bool { match interrupt as usize { socc::UART_INTERRUPT => { @@ -71,10 +83,6 @@ impl InterruptService<()> for LiteXSimInterruptablePeripherals { _ => false, } } - - unsafe fn service_deferred_call(&self, _task: ()) -> bool { - false - } } const NUM_PROCS: usize = 4; @@ -88,7 +96,7 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] struct LiteXSimPanicReferences { chip: Option<&'static litex_vexriscv::chip::LiteXVexRiscv>, uart: Option<&'static litex_vexriscv::uart::LiteXUart<'static, socc::SoCRegisterFmt>>, - process_printer: Option<&'static kernel::process::ProcessPrinterText>, + process_printer: Option<&'static capsules_system::process_printer::ProcessPrinterText>, } static mut PANIC_REFERENCES: LiteXSimPanicReferences = LiteXSimPanicReferences { chip: None, @@ -97,7 +105,8 @@ static mut PANIC_REFERENCES: LiteXSimPanicReferences = LiteXSimPanicReferences { }; // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; /// Dummy buffer that causes the linker to reserve enough space for the stack. #[no_mangle] @@ -107,15 +116,15 @@ pub static mut STACK_MEMORY: [u8; 0x2000] = [0; 0x2000]; /// A structure representing this platform that holds references to all /// capsules for this platform. struct LiteXSim { - gpio_driver: &'static capsules::gpio::GPIO< + gpio_driver: &'static capsules_core::gpio::GPIO< 'static, litex_vexriscv::gpio::LiteXGPIOPin<'static, 'static, socc::SoCRegisterFmt>, >, - button_driver: &'static capsules::button::Button< + button_driver: &'static capsules_core::button::Button< 'static, litex_vexriscv::gpio::LiteXGPIOPin<'static, 'static, socc::SoCRegisterFmt>, >, - led_driver: &'static capsules::led::LedDriver< + led_driver: &'static capsules_core::led::LedDriver< 'static, LedHigh< 'static, @@ -123,12 +132,12 @@ struct LiteXSim { >, 8, >, - console: &'static capsules::console::Console<'static>, - lldb: &'static capsules::low_level_debug::LowLevelDebug< + console: &'static capsules_core::console::Console<'static>, + lldb: &'static capsules_core::low_level_debug::LowLevelDebug< 'static, - capsules::virtual_uart::UartDevice<'static>, + capsules_core::virtualizers::virtual_uart::UartDevice<'static>, >, - alarm: &'static capsules::alarm::AlarmDriver< + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, VirtualMuxAlarm< 'static, @@ -140,7 +149,7 @@ struct LiteXSim { >, >, >, - ipc: kernel::ipc::IPC, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, scheduler: &'static CooperativeSched<'static>, scheduler_timer: &'static VirtualSchedulerTimer< VirtualMuxAlarm< @@ -162,12 +171,12 @@ impl SyscallDriverLookup for LiteXSim { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::button::DRIVER_NUM => f(Some(self.button_driver)), - capsules::led::DRIVER_NUM => f(Some(self.led_driver)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio_driver)), - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::low_level_debug::DRIVER_NUM => f(Some(self.lldb)), + capsules_core::button::DRIVER_NUM => f(Some(self.button_driver)), + capsules_core::led::DRIVER_NUM => f(Some(self.led_driver)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio_driver)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::low_level_debug::DRIVER_NUM => f(Some(self.lldb)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), _ => f(None), } @@ -196,7 +205,7 @@ impl KernelResources &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -208,7 +217,7 @@ impl KernelResources &Self::SchedulerTimer { - &self.scheduler_timer + self.scheduler_timer } fn watchdog(&self) -> &Self::WatchDog { &() @@ -218,30 +227,84 @@ impl KernelResources ( + &'static kernel::Kernel, + LiteXSim, + &'static litex_vexriscv::chip::LiteXVexRiscv, +) { + // These symbols are defined in the linker script. + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + /// The start of the kernel text (Included only for kernel PMP) + static _stext: u8; + /// The end of the kernel text (Included only for kernel PMP) + static _etext: u8; + /// The start of the kernel / app / storage flash (Included only for kernel PMP) + static _sflash: u8; + /// The end of the kernel / app / storage flash (Included only for kernel PMP) + static _eflash: u8; + /// The start of the kernel / app RAM (Included only for kernel PMP) + static _ssram: u8; + /// The end of the kernel / app RAM (Included only for kernel PMP) + static _esram: u8; + } + // ---------- BASIC INITIALIZATION ---------- + // Basic setup of the riscv platform. - rv32i::configure_trap_handler(rv32i::PermissionMode::Machine); + rv32i::configure_trap_handler(); + + // Set up memory protection immediately after setting the trap handler, to + // ensure that much of the board initialization routine runs with PMP kernel + // memory protection. + let pmp = rv32i::pmp::kernel_protection::KernelProtectionPMP::new( + rv32i::pmp::kernel_protection::FlashRegion( + rv32i::pmp::NAPOTRegionSpec::new( + core::ptr::addr_of!(_sflash), + core::ptr::addr_of!(_eflash) as usize - core::ptr::addr_of!(_sflash) as usize, + ) + .unwrap(), + ), + rv32i::pmp::kernel_protection::RAMRegion( + rv32i::pmp::NAPOTRegionSpec::new( + core::ptr::addr_of!(_ssram), + core::ptr::addr_of!(_esram) as usize - core::ptr::addr_of!(_ssram) as usize, + ) + .unwrap(), + ), + rv32i::pmp::kernel_protection::MMIORegion( + rv32i::pmp::NAPOTRegionSpec::new( + 0xf0000000 as *const u8, // start + 0x10000000, // size + ) + .unwrap(), + ), + rv32i::pmp::kernel_protection::KernelTextRegion( + rv32i::pmp::TORRegionSpec::new( + core::ptr::addr_of!(_stext), + core::ptr::addr_of!(_etext), + ) + .unwrap(), + ), + ) + .unwrap(); // initialize capabilities let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability); let memory_allocation_cap = create_capability!(capabilities::MemoryAllocationCapability); - let main_loop_cap = create_capability!(capabilities::MainLoopCapability); - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); - - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 2], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); // --------- TIMER & UPTIME CORE; ALARM INITIALIZATION ---------- @@ -310,7 +373,7 @@ pub unsafe fn main() { virtual_alarm_user.setup(); let alarm = static_init!( - capsules::alarm::AlarmDriver< + capsules_core::alarm::AlarmDriver< 'static, VirtualMuxAlarm< 'static, @@ -322,9 +385,9 @@ pub unsafe fn main() { >, >, >, - capsules::alarm::AlarmDriver::new( + capsules_core::alarm::AlarmDriver::new( virtual_alarm_user, - board_kernel.create_grant(capsules::alarm::DRIVER_NUM, &memory_allocation_cap) + board_kernel.create_grant(capsules_core::alarm::DRIVER_NUM, &memory_allocation_cap) ) ); virtual_alarm_user.set_alarm_client(alarm); @@ -370,12 +433,9 @@ pub unsafe fn main() { as *const litex_vexriscv::uart::LiteXUartRegisters, ), None, // LiteX simulator has no UART phy - dynamic_deferred_caller, ) ); - uart0.initialize( - dynamic_deferred_caller.register(uart0).unwrap(), // Unwrap fail = dynamic deferred caller out of slots - ); + uart0.initialize(); PANIC_REFERENCES.uart = Some(uart0); @@ -383,9 +443,8 @@ pub unsafe fn main() { // // The baudrate is ingnored, as no UART phy is present in the // verilated simulation. - let uart_mux = - components::console::UartMuxComponent::new(uart0, 115200, dynamic_deferred_caller) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(uart0, 115200) + .finalize(components::uart_mux_component_static!()); // ---------- ETHERNET ---------- @@ -432,7 +491,7 @@ pub unsafe fn main() { let gpio_driver = components::gpio::GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper_owned!( GPIOPin, 16 => gpio0.get_gpio_pin(16).unwrap(), @@ -453,7 +512,7 @@ pub unsafe fn main() { 31 => gpio0.get_gpio_pin(31).unwrap(), ), ) - .finalize(components::gpio_component_buf!(GPIOPin)); + .finalize(components::gpio_component_static!(GPIOPin)); // ---------- LED DRIVER ---------- @@ -472,7 +531,7 @@ pub unsafe fn main() { ); let led_driver = - components::led::LedsComponent::new().finalize(components::led_component_helper!( + components::led::LedsComponent::new().finalize(components::led_component_static!( kernel::hil::led::LedHigh, LedHigh::new(&led_gpios[0]), LedHigh::new(&led_gpios[1]), @@ -488,7 +547,7 @@ pub unsafe fn main() { let button_driver = components::button::ButtonComponent::new( board_kernel, - capsules::button::DRIVER_NUM, + capsules_core::button::DRIVER_NUM, components::button_component_helper_owned!( GPIOPin, ( @@ -533,7 +592,7 @@ pub unsafe fn main() { ), ), ) - .finalize(components::button_component_buf!(GPIOPin)); + .finalize(components::button_component_static!(GPIOPin)); // ---------- INITIALIZE CHIP, ENABLE INTERRUPTS ---------- @@ -541,11 +600,12 @@ pub unsafe fn main() { LiteXSimInterruptablePeripherals, LiteXSimInterruptablePeripherals { gpio0, - timer0, uart0, + timer0, ethmac0, } ); + interrupt_service.init(); let chip = static_init!( litex_vexriscv::chip::LiteXVexRiscv< @@ -553,14 +613,15 @@ pub unsafe fn main() { >, litex_vexriscv::chip::LiteXVexRiscv::new( "Verilated LiteX on VexRiscv", - interrupt_service + interrupt_service, + pmp, ) ); PANIC_REFERENCES.chip = Some(chip); - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PANIC_REFERENCES.process_printer = Some(process_printer); @@ -576,44 +637,34 @@ pub unsafe fn main() { // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); let lldb = components::lldb::LowLevelDebugComponent::new( board_kernel, - capsules::low_level_debug::DRIVER_NUM, + capsules_core::low_level_debug::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::low_level_debug_component_static!()); debug!("Verilated LiteX+VexRiscv: initialization complete, entering main loop."); - /// These symbols are defined in the linker script. - extern "C" { - /// Beginning of the ROM region containing app images. - static _sapps: u8; - /// End of the ROM region containing app images. - static _eapps: u8; - /// Beginning of the RAM region for app memory. - static mut _sappmem: u8; - /// End of the RAM region for app memory. - static _eappmem: u8; - } - - let scheduler = components::sched::cooperative::CooperativeComponent::new(&PROCESSES) - .finalize(components::coop_component_helper!(NUM_PROCS)); + let scheduler = + components::sched::cooperative::CooperativeComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::cooperative_component_static!(NUM_PROCS)); let litex_sim = LiteXSim { - gpio_driver: gpio_driver, - button_driver: button_driver, - led_driver: led_driver, - console: console, - alarm: alarm, - lldb: lldb, + gpio_driver, + button_driver, + led_driver, + console, + alarm, + lldb, ipc: kernel::ipc::IPC::new( board_kernel, kernel::ipc::DRIVER_NUM, @@ -627,14 +678,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_mgmt_cap, ) @@ -643,5 +694,14 @@ pub unsafe fn main() { debug!("{:?}", err); }); - board_kernel.kernel_loop(&litex_sim, chip, Some(&litex_sim.ipc), &main_loop_cap); + (board_kernel, litex_sim, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, board, chip) = start(); + board_kernel.kernel_loop(&board, chip, Some(&board.ipc), &main_loop_capability); } diff --git a/boards/makepython-nrf52840/Cargo.toml b/boards/makepython-nrf52840/Cargo.toml new file mode 100644 index 0000000000..a657aafc77 --- /dev/null +++ b/boards/makepython-nrf52840/Cargo.toml @@ -0,0 +1,25 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[package] +name = "makepython-nrf52840" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true + +[dependencies] +cortexm4 = { path = "../../arch/cortex-m4" } +kernel = { path = "../../kernel" } +nrf52 = { path = "../../chips/nrf52" } +nrf52840 = { path = "../../chips/nrf52840" } +components = { path = "../components" } +nrf52_components = { path = "../nordic/nrf52_components" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/makepython-nrf52840/Makefile b/boards/makepython-nrf52840/Makefile new file mode 100644 index 0000000000..7f1258392c --- /dev/null +++ b/boards/makepython-nrf52840/Makefile @@ -0,0 +1,30 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +# Makefile for building the tock kernel for the Arduino Nano 33 BLE board. + +TOCK_ARCH=cortex-m4 +TARGET=thumbv7em-none-eabi +PLATFORM=makepython-nrf52840 + +include ../Makefile.common + +ifdef PORT + FLAGS += --port $(PORT) +endif + +# Default target for installing the kernel. +.PHONY: install +install: program + +# Upload the kernel using tockloader and the tock bootloader +.PHONY: program +program: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin + tockloader $(FLAGS) flash --address 0x10000 $< + +.PHONY: flash-bootloader +flash-bootloader: + curl -L --output /tmp/makepython-nrf52840-bootloader_v1.1.3.bin https://github.com/tock/tock-bootloader/releases/download/v1.1.3/makepython-nrf52840-bootloader_v1.1.3.bin + tockloader flash --address 0 /tmp/makepython-nrf52840-bootloader_v1.1.3.bin + rm /tmp/makepython-nrf52840-bootloader_v1.1.3.bin diff --git a/boards/makepython-nrf52840/README.md b/boards/makepython-nrf52840/README.md new file mode 100644 index 0000000000..d38b2f8746 --- /dev/null +++ b/boards/makepython-nrf52840/README.md @@ -0,0 +1,52 @@ +MakePython nRF52840 +=================== + + + +The [MakePython nRF52840](https://www.makerfabs.com/makepython-nrf52840.html) is +a development board with the Nordic nRF52840 SoC and a 128 x 64 pixel OLED +display. + + +## Getting Started + +First, follow the [Tock Getting Started guide](../../doc/Getting_Started.md). + +The MakePython nRF52840 is designed to be programmed using an external JLink +programmer. We would like to avoid this requirement, so we use the [Tock +Bootloader](https://github.com/tock/tock-bootloader) which allows us to program +the board over the UART connection. However, we still require the programmer one +time to flash the bootloader. + +To flash the bootloader we must connect a JLink programmer. The easiest way is +to use an nRF52840dk board. + +### Connect the nRF52840dk to the MakePython-nRF52840 + +First we jumper the board as shown with the following pin mappings + +| nRF52840dk | MakePython-nRF52840 | +|------------|---------------------| +| GND | GND | +| SWD SEL | +3V3 | +| SWD CLK | SWDCLK | +| SWD IO | SWDDIO | + +Make sure _both_ the nRF52840dk board and the MakePython-nRF52840 board are +attached to your computer via two USB connections. + +Then: + +``` +make flash-bootloader +``` + +This will use JLinkExe to flash the bootloader using the nRF52840dk's onboard +jtag hardware. + +### Using the Bootloader + +The bootloader activates when the reset button is pressed twice in quick +succession. The green LED will stay on when the bootloader is active. + +Once the bootloader is installed tockloader will work as expected. diff --git a/boards/makepython-nrf52840/layout.ld b/boards/makepython-nrf52840/layout.ld new file mode 100644 index 0000000000..d5950e39c4 --- /dev/null +++ b/boards/makepython-nrf52840/layout.ld @@ -0,0 +1,14 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + +MEMORY +{ + rom (rx) : ORIGIN = 0x00010000, LENGTH = 256K + prog (rx) : ORIGIN = 0x00050000, LENGTH = 704K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 256K +} + +PAGE_SIZE = 4K; + +INCLUDE ../kernel_layout.ld diff --git a/boards/makepython-nrf52840/src/io.rs b/boards/makepython-nrf52840/src/io.rs new file mode 100644 index 0000000000..1d7349b70e --- /dev/null +++ b/boards/makepython-nrf52840/src/io.rs @@ -0,0 +1,142 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use core::fmt::Write; +use core::panic::PanicInfo; +use core::ptr::{addr_of, addr_of_mut}; + +use kernel::debug; +use kernel::debug::IoWrite; +use kernel::hil::led; +use kernel::hil::uart::{self}; +use kernel::ErrorCode; +use nrf52840::gpio::Pin; + +use crate::CHIP; +use crate::PROCESSES; +use crate::PROCESS_PRINTER; +use kernel::hil::uart::Transmit; +use kernel::utilities::cells::VolatileCell; + +struct Writer { + initialized: bool, +} + +static mut WRITER: Writer = Writer { initialized: false }; + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> ::core::fmt::Result { + self.write(s.as_bytes()); + Ok(()) + } +} + +const BUF_LEN: usize = 512; +static mut STATIC_PANIC_BUF: [u8; BUF_LEN] = [0; BUF_LEN]; + +static mut DUMMY: DummyUsbClient = DummyUsbClient { + fired: VolatileCell::new(false), +}; + +struct DummyUsbClient { + fired: VolatileCell, +} + +impl uart::TransmitClient for DummyUsbClient { + fn transmitted_buffer(&self, _: &'static mut [u8], _: usize, _: Result<(), ErrorCode>) { + self.fired.set(true); + } +} + +impl IoWrite for Writer { + fn write(&mut self, buf: &[u8]) -> usize { + if !self.initialized { + self.initialized = true; + } + // Here we mimic a synchronous UART output by calling transmit_buffer + // on the CDC stack and then spinning on USB interrupts until the transaction + // is complete. If the USB or CDC stack panicked, this may fail. It will also + // fail if the panic occurred prior to the USB connection being initialized. + // In the latter case, the LEDs should still blink in the panic pattern. + + // spin so that if any USB DMA is ongoing it will finish + // we should only need this on the first call to write() + let mut i = 0; + loop { + i += 1; + cortexm4::support::nop(); + if i > 10000 { + break; + } + } + + // copy_from_slice() requires equal length slices + // This will truncate any writes longer than BUF_LEN, but simplifies the + // code. In practice, BUF_LEN=512 always seems sufficient for the size of + // individual calls to write made by the panic handler. + let mut max = BUF_LEN; + if buf.len() < BUF_LEN { + max = buf.len(); + } + + unsafe { + // If CDC_REF_FOR_PANIC is not yet set we panicked very early, + // and not much we can do. Don't want to double fault, + // so just return. + super::CDC_REF_FOR_PANIC.map(|cdc| { + // Lots of unsafe dereferencing of global static mut objects here. + // However, this should be okay, because it all happens within + // a single thread, and: + // - This is the only place the global CDC_REF_FOR_PANIC is used, the logic is the same + // as applies for the global CHIP variable used in the panic handler. + // - We do create multiple mutable references to the STATIC_PANIC_BUF, but we never + // access the STATIC_PANIC_BUF after a slice of it is passed to transmit_buffer + // until the slice has been returned in the uart callback. + // - Similarly, only this function uses the global DUMMY variable, and we do not + // mutate it. + let usb = &mut cdc.controller(); + STATIC_PANIC_BUF[..max].copy_from_slice(&buf[..max]); + let static_buf = &mut *addr_of_mut!(STATIC_PANIC_BUF); + cdc.set_transmit_client(&*addr_of!(DUMMY)); + let _ = cdc.transmit_buffer(static_buf, max); + loop { + if let Some(interrupt) = cortexm4::nvic::next_pending() { + if interrupt == 39 { + usb.handle_interrupt(); + } + let n = cortexm4::nvic::Nvic::new(interrupt); + n.clear_pending(); + n.enable(); + } + if DUMMY.fired.get() { + // buffer finished transmitting, return so we can output additional + // messages when requested by the panic handler. + break; + } + } + DUMMY.fired.set(false); + }); + } + buf.len() + } +} + +/// We just use the standard default provided by the debug module in the kernel. +#[cfg(not(test))] +#[no_mangle] +#[panic_handler] +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { + let led_kernel_pin = &nrf52840::gpio::GPIOPin::new(Pin::P1_10); + let led = &mut led::LedLow::new(led_kernel_pin); + let writer = &mut *addr_of_mut!(WRITER); + debug::panic( + &mut [led], + writer, + pi, + &cortexm4::support::nop, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), + ) +} diff --git a/boards/makepython-nrf52840/src/main.rs b/boards/makepython-nrf52840/src/main.rs new file mode 100644 index 0000000000..d605b98b8b --- /dev/null +++ b/boards/makepython-nrf52840/src/main.rs @@ -0,0 +1,736 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Tock kernel for the MakePython nRF52840. +//! +//! It is based on nRF52840 SoC. + +#![no_std] +// Disable this attribute when documenting, as a workaround for +// https://github.com/rust-lang/rust/issues/62184. +#![cfg_attr(not(doc), no_main)] +#![deny(missing_docs)] + +use core::ptr::{addr_of, addr_of_mut}; + +use kernel::capabilities; +use kernel::component::Component; +use kernel::hil::led::LedLow; +use kernel::hil::time::Counter; +use kernel::hil::usb::Client; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::scheduler::round_robin::RoundRobinSched; +#[allow(unused_imports)] +use kernel::{create_capability, debug, debug_gpio, debug_verbose, static_init}; + +use nrf52840::gpio::Pin; +use nrf52840::interrupt_service::Nrf52840DefaultPeripherals; + +// The datasheet and website and everything say this is connected to P1.10, but +// actually looking at the hardware files (and what actually works) is that the +// LED is connected to P1.11 (as of a board I received in September 2023). +// +// https://github.com/Makerfabs/NRF52840/issues/1 +const LED_PIN: Pin = Pin::P1_11; + +const BUTTON_RST_PIN: Pin = Pin::P0_18; +const BUTTON_PIN: Pin = Pin::P1_15; + +const GPIO_D0: Pin = Pin::P0_23; +const GPIO_D1: Pin = Pin::P0_12; +const GPIO_D2: Pin = Pin::P0_09; +const GPIO_D3: Pin = Pin::P0_07; + +const _UART_TX_PIN: Pin = Pin::P0_06; +const _UART_RX_PIN: Pin = Pin::P0_08; + +/// I2C pins for all of the sensors. +const I2C_SDA_PIN: Pin = Pin::P0_26; +const I2C_SCL_PIN: Pin = Pin::P0_27; + +// Constants related to the configuration of the 15.4 network stack +/// Personal Area Network ID for the IEEE 802.15.4 radio +const PAN_ID: u16 = 0xABCD; +/// Gateway (or next hop) MAC Address +const DST_MAC_ADDR: capsules_extra::net::ieee802154::MacAddress = + capsules_extra::net::ieee802154::MacAddress::Short(49138); +const DEFAULT_CTX_PREFIX_LEN: u8 = 8; //Length of context for 6LoWPAN compression +const DEFAULT_CTX_PREFIX: [u8; 16] = [0x0_u8; 16]; //Context for 6LoWPAN Compression + +/// UART Writer for panic!()s. +pub mod io; + +// How should the kernel respond when a process faults. For this board we choose +// to stop the app and print a notice, but not immediately panic. This allows +// users to debug their apps, but avoids issues with using the USB/CDC stack +// synchronously for panic! too early after the board boots. +const FAULT_RESPONSE: capsules_system::process_policies::StopWithDebugFaultPolicy = + capsules_system::process_policies::StopWithDebugFaultPolicy {}; + +// Number of concurrent processes this platform supports. +const NUM_PROCS: usize = 8; + +// State for loading and holding applications. +static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = + [None; NUM_PROCS]; + +static mut CHIP: Option<&'static nrf52840::chip::NRF52> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; +static mut CDC_REF_FOR_PANIC: Option< + &'static capsules_extra::usb::cdc::CdcAcm< + 'static, + nrf52::usbd::Usbd, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc>, + >, +> = None; +static mut NRF52_POWER: Option<&'static nrf52840::power::Power> = None; + +/// Dummy buffer that causes the linker to reserve enough space for the stack. +#[no_mangle] +#[link_section = ".stack_buffer"] +pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; + +// Function for the CDC/USB stack to use to enter the bootloader. +fn baud_rate_reset_bootloader_enter() { + unsafe { + // 0x90 is the magic value the bootloader expects + NRF52_POWER.unwrap().set_gpregret(0x90); + cortexm4::scb::reset(); + } +} + +fn crc(s: &'static str) -> u32 { + kernel::utilities::helpers::crc32_posix(s.as_bytes()) +} + +//------------------------------------------------------------------------------ +// SYSCALL DRIVER TYPE DEFINITIONS +//------------------------------------------------------------------------------ + +type AlarmDriver = components::alarm::AlarmDriverComponentType>; + +type Screen = components::ssd1306::Ssd1306ComponentType>; +type ScreenDriver = components::screen::ScreenSharedComponentType; + +type Ieee802154MacDevice = components::ieee802154::Ieee802154ComponentMacDeviceType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; +type Ieee802154Driver = components::ieee802154::Ieee802154ComponentType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; +type RngDriver = components::rng::RngComponentType>; + +/// Supported drivers by the platform +pub struct Platform { + ble_radio: &'static capsules_extra::ble_advertising_driver::BLE< + 'static, + nrf52::ble_radio::Radio<'static>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52::rtc::Rtc<'static>, + >, + >, + ieee802154_radio: &'static Ieee802154Driver, + console: &'static capsules_core::console::Console<'static>, + pconsole: &'static capsules_core::process_console::ProcessConsole< + 'static, + { capsules_core::process_console::DEFAULT_COMMAND_HISTORY_LEN }, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52::rtc::Rtc<'static>, + >, + components::process_console::Capability, + >, + gpio: &'static capsules_core::gpio::GPIO<'static, nrf52::gpio::GPIOPin<'static>>, + led: &'static capsules_core::led::LedDriver< + 'static, + LedLow<'static, nrf52::gpio::GPIOPin<'static>>, + 1, + >, + adc: &'static capsules_core::adc::AdcVirtualized<'static>, + rng: &'static RngDriver, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + alarm: &'static AlarmDriver, + button: &'static capsules_core::button::Button<'static, nrf52840::gpio::GPIOPin<'static>>, + screen: &'static ScreenDriver, + udp_driver: &'static capsules_extra::net::udp::UDPDriver<'static>, + scheduler: &'static RoundRobinSched<'static>, + systick: cortexm4::systick::SysTick, +} + +impl SyscallDriverLookup for Platform { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_core::rng::DRIVER_NUM => f(Some(self.rng)), + capsules_extra::screen::DRIVER_NUM => f(Some(self.screen)), + capsules_extra::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), + capsules_extra::ieee802154::DRIVER_NUM => f(Some(self.ieee802154_radio)), + capsules_extra::net::udp::DRIVER_NUM => f(Some(self.udp_driver)), + kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), + _ => f(None), + } + } +} + +impl KernelResources>> + for Platform +{ + type SyscallDriverLookup = Self; + type SyscallFilter = (); + type ProcessFault = (); + type Scheduler = RoundRobinSched<'static>; + type SchedulerTimer = cortexm4::systick::SysTick; + type WatchDog = (); + type ContextSwitchCallback = (); + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + &() + } + fn process_fault(&self) -> &Self::ProcessFault { + &() + } + fn scheduler(&self) -> &Self::Scheduler { + self.scheduler + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + &self.systick + } + fn watchdog(&self) -> &Self::WatchDog { + &() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + &() + } +} + +/// This is in a separate, inline(never) function so that its stack frame is +/// removed when this function returns. Otherwise, the stack space used for +/// these static_inits is wasted. +#[inline(never)] +pub unsafe fn start() -> ( + &'static kernel::Kernel, + Platform, + &'static nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>, +) { + nrf52840::init(); + + let ieee802154_ack_buf = static_init!( + [u8; nrf52840::ieee802154_radio::ACK_BUF_SIZE], + [0; nrf52840::ieee802154_radio::ACK_BUF_SIZE] + ); + + // Initialize chip peripheral drivers + let nrf52840_peripherals = static_init!( + Nrf52840DefaultPeripherals, + Nrf52840DefaultPeripherals::new(ieee802154_ack_buf) + ); + + // set up circular peripheral dependencies + nrf52840_peripherals.init(); + let base_peripherals = &nrf52840_peripherals.nrf52; + + // Save a reference to the power module for resetting the board into the + // bootloader. + NRF52_POWER = Some(&base_peripherals.pwr_clk); + + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); + + // Do nRF configuration and setup. This is shared code with other nRF-based + // platforms. + nrf52_components::startup::NrfStartupComponent::new( + false, + BUTTON_RST_PIN, + nrf52840::uicr::Regulator0Output::DEFAULT, + &base_peripherals.nvmc, + ) + .finalize(()); + + let chip = static_init!( + nrf52840::chip::NRF52, + nrf52840::chip::NRF52::new(nrf52840_peripherals) + ); + CHIP = Some(chip); + + //-------------------------------------------------------------------------- + // CAPABILITIES + //-------------------------------------------------------------------------- + + // Create capabilities that the board needs to call certain protected kernel + // functions. + let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); + + //-------------------------------------------------------------------------- + // DEBUG GPIO + //-------------------------------------------------------------------------- + + // Configure kernel debug GPIOs as early as possible. These are used by the + // `debug_gpio!(0, toggle)` macro. We configure these early so that the + // macro is available during most of the setup code and kernel execution. + kernel::debug::assign_gpios(Some(&nrf52840_peripherals.gpio_port[LED_PIN]), None, None); + + //-------------------------------------------------------------------------- + // GPIO + //-------------------------------------------------------------------------- + + let gpio = components::gpio::GpioComponent::new( + board_kernel, + capsules_core::gpio::DRIVER_NUM, + components::gpio_component_helper!( + nrf52840::gpio::GPIOPin, + 0 => &nrf52840_peripherals.gpio_port[GPIO_D0], + 1 => &nrf52840_peripherals.gpio_port[GPIO_D1], + 2 => &nrf52840_peripherals.gpio_port[GPIO_D2], + 3 => &nrf52840_peripherals.gpio_port[GPIO_D3], + ), + ) + .finalize(components::gpio_component_static!(nrf52840::gpio::GPIOPin)); + + //-------------------------------------------------------------------------- + // LEDs + //-------------------------------------------------------------------------- + + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( + LedLow<'static, nrf52840::gpio::GPIOPin>, + LedLow::new(&nrf52840_peripherals.gpio_port[LED_PIN]), + )); + + //-------------------------------------------------------------------------- + // BUTTONS + //-------------------------------------------------------------------------- + + let button = components::button::ButtonComponent::new( + board_kernel, + capsules_core::button::DRIVER_NUM, + components::button_component_helper!( + nrf52840::gpio::GPIOPin, + ( + &nrf52840_peripherals.gpio_port[BUTTON_PIN], + kernel::hil::gpio::ActivationMode::ActiveLow, + kernel::hil::gpio::FloatingState::PullUp + ) + ), + ) + .finalize(components::button_component_static!( + nrf52840::gpio::GPIOPin + )); + + //-------------------------------------------------------------------------- + // ALARM & TIMER + //-------------------------------------------------------------------------- + + let rtc = &base_peripherals.rtc; + let _ = rtc.start(); + + let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) + .finalize(components::alarm_mux_component_static!(nrf52::rtc::Rtc)); + let alarm = components::alarm::AlarmDriverComponent::new( + board_kernel, + capsules_core::alarm::DRIVER_NUM, + mux_alarm, + ) + .finalize(components::alarm_component_static!(nrf52::rtc::Rtc)); + + //-------------------------------------------------------------------------- + // UART & CONSOLE & DEBUG + //-------------------------------------------------------------------------- + + // Setup the CDC-ACM over USB driver that we will use for UART. + // We use the Arduino Vendor ID and Product ID since the device is the same. + + // Create the strings we include in the USB descriptor. We use the hardcoded + // DEVICEADDR register on the nRF52 to set the serial number. + let serial_number_buf = static_init!([u8; 17], [0; 17]); + let serial_number_string: &'static str = + nrf52::ficr::FICR_INSTANCE.address_str(serial_number_buf); + let strings = static_init!( + [&str; 3], + [ + "MakePython", // Manufacturer + "NRF52840 - TockOS", // Product + serial_number_string, // Serial number + ] + ); + + let cdc = components::cdc::CdcAcmComponent::new( + &nrf52840_peripherals.usbd, + capsules_extra::usb::cdc::MAX_CTRL_PACKET_SIZE_NRF52840, + 0x2341, + 0x005a, + strings, + mux_alarm, + Some(&baud_rate_reset_bootloader_enter), + ) + .finalize(components::cdc_acm_component_static!( + nrf52::usbd::Usbd, + nrf52::rtc::Rtc + )); + CDC_REF_FOR_PANIC = Some(cdc); //for use by panic handler + + // Process Printer for displaying process information. + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); + PROCESS_PRINTER = Some(process_printer); + + // Create a shared UART channel for the console and for kernel debug. + let uart_mux = components::console::UartMuxComponent::new(cdc, 115200) + .finalize(components::uart_mux_component_static!()); + + let pconsole = components::process_console::ProcessConsoleComponent::new( + board_kernel, + uart_mux, + mux_alarm, + process_printer, + Some(cortexm4::support::reset), + ) + .finalize(components::process_console_component_static!( + nrf52::rtc::Rtc<'static> + )); + + // Setup the console. + let console = components::console::ConsoleComponent::new( + board_kernel, + capsules_core::console::DRIVER_NUM, + uart_mux, + ) + .finalize(components::console_component_static!()); + // Create the debugger object that handles calls to `debug!()`. + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); + + //-------------------------------------------------------------------------- + // RANDOM NUMBERS + //-------------------------------------------------------------------------- + + let rng = components::rng::RngComponent::new( + board_kernel, + capsules_core::rng::DRIVER_NUM, + &base_peripherals.trng, + ) + .finalize(components::rng_component_static!(nrf52840::trng::Trng)); + + //-------------------------------------------------------------------------- + // ADC + //-------------------------------------------------------------------------- + base_peripherals.adc.calibrate(); + + let adc_mux = components::adc::AdcMuxComponent::new(&base_peripherals.adc) + .finalize(components::adc_mux_component_static!(nrf52840::adc::Adc)); + + let adc_syscall = + components::adc::AdcVirtualComponent::new(board_kernel, capsules_core::adc::DRIVER_NUM) + .finalize(components::adc_syscall_component_helper!( + // A0 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput2) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A1 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput3) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A2 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput6) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A3 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput5) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A4 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput7) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A5 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput0) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A6 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput4) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A7 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput1) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + )); + + //-------------------------------------------------------------------------- + // SCREEN + //-------------------------------------------------------------------------- + + let i2c_bus = components::i2c::I2CMuxComponent::new(&base_peripherals.twi1, None) + .finalize(components::i2c_mux_component_static!(nrf52840::i2c::TWI)); + base_peripherals.twi1.configure( + nrf52840::pinmux::Pinmux::new(I2C_SCL_PIN as u32), + nrf52840::pinmux::Pinmux::new(I2C_SDA_PIN as u32), + ); + + // I2C address is b011110X, and on this board D/C̅ is GND. + let ssd1306_i2c = components::i2c::I2CComponent::new(i2c_bus, 0x3c) + .finalize(components::i2c_component_static!(nrf52840::i2c::TWI)); + + // Create the ssd1306 object for the actual screen driver. + let ssd1306 = components::ssd1306::Ssd1306Component::new(ssd1306_i2c, true) + .finalize(components::ssd1306_component_static!(nrf52840::i2c::TWI)); + + // Create a Driver for userspace access to the screen. + // let screen = components::screen::ScreenComponent::new( + // board_kernel, + // capsules_extra::screen::DRIVER_NUM, + // ssd1306, + // Some(ssd1306), + // ) + // .finalize(components::screen_component_static!(1032)); + + let apps_regions = static_init!( + [capsules_extra::screen_shared::AppScreenRegion; 3], + [ + capsules_extra::screen_shared::AppScreenRegion::new( + kernel::process::ShortId::Fixed(core::num::NonZeroU32::new(crc("circle")).unwrap()), + 0, // x + 0, // y + 8 * 8, // width + 8 * 8 // height + ), + capsules_extra::screen_shared::AppScreenRegion::new( + kernel::process::ShortId::Fixed(core::num::NonZeroU32::new(crc("count")).unwrap()), + 8 * 8, // x + 0, // y + 8 * 8, // width + 4 * 8 // height + ), + capsules_extra::screen_shared::AppScreenRegion::new( + kernel::process::ShortId::Fixed( + core::num::NonZeroU32::new(crc("tock-scroll")).unwrap() + ), + 8 * 8, // x + 4 * 8, // y + 8 * 8, // width + 4 * 8 // height + ) + ] + ); + + let screen = components::screen::ScreenSharedComponent::new( + board_kernel, + capsules_extra::screen::DRIVER_NUM, + ssd1306, + apps_regions, + ) + .finalize(components::screen_shared_component_static!(1032, Screen)); + + //-------------------------------------------------------------------------- + // WIRELESS + //-------------------------------------------------------------------------- + + let ble_radio = components::ble::BLEComponent::new( + board_kernel, + capsules_extra::ble_advertising_driver::DRIVER_NUM, + &base_peripherals.ble_radio, + mux_alarm, + ) + .finalize(components::ble_component_static!( + nrf52840::rtc::Rtc, + nrf52840::ble_radio::Radio + )); + + use capsules_extra::net::ieee802154::MacAddress; + + let aes_mux = components::ieee802154::MuxAes128ccmComponent::new(&base_peripherals.ecb) + .finalize(components::mux_aes128ccm_component_static!( + nrf52840::aes::AesECB + )); + + let device_id = nrf52840::ficr::FICR_INSTANCE.id(); + let device_id_bottom_16 = u16::from_le_bytes([device_id[0], device_id[1]]); + let (ieee802154_radio, mux_mac) = components::ieee802154::Ieee802154Component::new( + board_kernel, + capsules_extra::ieee802154::DRIVER_NUM, + &nrf52840_peripherals.ieee802154_radio, + aes_mux, + PAN_ID, + device_id_bottom_16, + device_id, + ) + .finalize(components::ieee802154_component_static!( + nrf52840::ieee802154_radio::Radio, + nrf52840::aes::AesECB<'static> + )); + use capsules_extra::net::ipv6::ip_utils::IPAddr; + + let local_ip_ifaces = static_init!( + [IPAddr; 3], + [ + IPAddr([ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, + ]), + IPAddr([ + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, + ]), + IPAddr::generate_from_mac(capsules_extra::net::ieee802154::MacAddress::Short( + device_id_bottom_16 + )), + ] + ); + + let (udp_send_mux, udp_recv_mux, udp_port_table) = components::udp_mux::UDPMuxComponent::new( + mux_mac, + DEFAULT_CTX_PREFIX_LEN, + DEFAULT_CTX_PREFIX, + DST_MAC_ADDR, + MacAddress::Short(device_id_bottom_16), + local_ip_ifaces, + mux_alarm, + ) + .finalize(components::udp_mux_component_static!( + nrf52840::rtc::Rtc, + Ieee802154MacDevice + )); + + // UDP driver initialization happens here + let udp_driver = components::udp_driver::UDPDriverComponent::new( + board_kernel, + capsules_extra::net::udp::DRIVER_NUM, + udp_send_mux, + udp_recv_mux, + udp_port_table, + local_ip_ifaces, + ) + .finalize(components::udp_driver_component_static!(nrf52840::rtc::Rtc)); + + //-------------------------------------------------------------------------- + // APP ID CHECKING + //-------------------------------------------------------------------------- + + // Create the software-based SHA engine. + let sha = components::sha::ShaSoftware256Component::new() + .finalize(components::sha_software_256_component_static!()); + + // Create the credential checker. + let checking_policy = components::appid::checker_sha::AppCheckerSha256Component::new(sha) + .finalize(components::app_checker_sha256_component_static!()); + + // Create the AppID assigner. + let assigner = components::appid::assigner_name::AppIdAssignerNamesComponent::new() + .finalize(components::appid_assigner_names_component_static!()); + + // Create the process checking machine. + let checker = components::appid::checker::ProcessCheckerMachineComponent::new(checking_policy) + .finalize(components::process_checker_machine_component_static!()); + + // Create and start the asynchronous process loader. + let _loader = components::loader::sequential::ProcessLoaderSequentialComponent::new( + checker, + &mut *addr_of_mut!(PROCESSES), + board_kernel, + chip, + &FAULT_RESPONSE, + assigner, + ) + .finalize(components::process_loader_sequential_component_static!( + nrf52840::chip::NRF52, + NUM_PROCS + )); + + //-------------------------------------------------------------------------- + // FINAL SETUP AND BOARD BOOT + //-------------------------------------------------------------------------- + + // Start all of the clocks. Low power operation will require a better + // approach than this. + nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); + + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); + + let platform = Platform { + ble_radio, + ieee802154_radio, + console, + pconsole, + adc: adc_syscall, + led, + button, + gpio, + rng, + screen, + alarm, + udp_driver, + ipc: kernel::ipc::IPC::new( + board_kernel, + kernel::ipc::DRIVER_NUM, + &memory_allocation_capability, + ), + scheduler, + systick: cortexm4::systick::SysTick::new_with_calibration(64000000), + }; + + // Configure the USB stack to enable a serial port over CDC-ACM. + cdc.enable(); + cdc.attach(); + + //-------------------------------------------------------------------------- + // TESTS + //-------------------------------------------------------------------------- + // test::linear_log_test::run( + // mux_alarm, + // &nrf52840_peripherals.nrf52.nvmc, + // ); + // test::log_test::run( + // mux_alarm, + // &nrf52840_peripherals.nrf52.nvmc, + // ); + + debug!("Initialization complete. Entering main loop."); + let _ = platform.pconsole.start(); + + ssd1306.init_screen(); + + //-------------------------------------------------------------------------- + // PROCESSES AND MAIN LOOP + //-------------------------------------------------------------------------- + + (board_kernel, platform, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); +} diff --git a/boards/microbit_v2/Cargo.toml b/boards/microbit_v2/Cargo.toml index a8cd5fd40b..498aaf0c04 100644 --- a/boards/microbit_v2/Cargo.toml +++ b/boards/microbit_v2/Cargo.toml @@ -1,15 +1,25 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "microbit_v2" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] cortexm4 = { path = "../../arch/cortex-m4" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } nrf52 = { path = "../../chips/nrf52" } nrf52833 = { path = "../../chips/nrf52833" } components = { path = "../components" } nrf52_components = { path = "../nordic/nrf52_components" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/microbit_v2/Makefile b/boards/microbit_v2/Makefile index e93cf36de4..9517629f75 100644 --- a/boards/microbit_v2/Makefile +++ b/boards/microbit_v2/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the BBC microbit v2 board. TOCK_ARCH=cortex-m4 @@ -34,6 +38,6 @@ flash-app: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin .PHONY: flash-bootloader flash-bootloader: curl -L --output /tmp/tock-bootloader.microbit_v2.vv1.1.1.bin https://github.com/tock/tock-bootloader/releases/download/microbit_v2-vv1.1.1/tock-bootloader.microbit_v2.vv1.1.1.bin - $(OPENOCD) $(OPENOCD_OPTIONS) -c "program /tmp/tock-bootloader.microbit_v2.vv1.1.1.bin; verify_image /tmp/tock-bootloader.microbit_v2.vv1.1.1.bin; reset; shutdown;" + $(OPENOCD) $(OPENOCD_OPTIONS) -c "program /tmp/tock-bootloader.microbit_v2.vv1.1.1.bin; verify_image /tmp/tock-bootloader.microbit_v2.vv1.1.1.bin; reset halt; shutdown;" rm /tmp/tock-bootloader.microbit_v2.vv1.1.1.bin diff --git a/boards/microbit_v2/README.md b/boards/microbit_v2/README.md index 4572e73527..090cb14ea4 100644 --- a/boards/microbit_v2/README.md +++ b/boards/microbit_v2/README.md @@ -1,9 +1,9 @@ -BBC Micro:bit v2 - nRF52833 with Bluetooth LE +BBC micro:bit v2 - nRF52833 with Bluetooth LE ================================================== -The [BBC Micro:bit v2 - nRF52833 with Bluetooth LE](https://microbit.org/new-microbit/) is a +The [BBC micro:bit v2 - nRF52833 with Bluetooth LE](https://microbit.org/new-microbit/) is a board based on the Nordic nRF52833 SoC. It includes the following sensors: @@ -131,6 +131,11 @@ $ tockloader install --openocd --board microbit_v2 --bundle-apps app.tab > $ tockloader ... --page-size 512 > ``` +## Book + +For further details and examples about how to use Tock with the BBC micro:bit, you might +want to check out the [Getting Started with Secure Embedded Systems](https://link.springer.com/book/10.1007/978-1-4842-7789-8) book. + ## Troubleshooting ### Could not find MEM-AP to control the core diff --git a/boards/microbit_v2/build.rs b/boards/microbit_v2/build.rs deleted file mode 100644 index 3051054fc6..0000000000 --- a/boards/microbit_v2/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/microbit_v2/layout.ld b/boards/microbit_v2/layout.ld index 2cd0c16ff7..b9b80d64d0 100644 --- a/boards/microbit_v2/layout.ld +++ b/boards/microbit_v2/layout.ld @@ -1,3 +1,7 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + MEMORY { # with bootloader @@ -8,7 +12,6 @@ MEMORY ram (rwx) : ORIGIN = 0x20000000, LENGTH = 128K } -MPU_MIN_ALIGN = 8K; PAGE_SIZE = 4K; INCLUDE ../kernel_layout.ld diff --git a/boards/microbit_v2/openocd.cfg b/boards/microbit_v2/openocd.cfg index 27e16f6fd7..3f4ba0e548 100644 --- a/boards/microbit_v2/openocd.cfg +++ b/boards/microbit_v2/openocd.cfg @@ -1,13 +1,16 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + source [find interface/cmsis-dap.cfg] transport select swd source [find target/nrf52.cfg] -set WORKAREASIZE 0x40000 -$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $WORKAREASIZE -work-area-backup 0 - -# catch is necessary to be backward compatible with openocd 0.10 -# this should be removed when 0.11 becomes more widely used - -catch { flash bank $_CHIPNAME.flash nrf51 0x00000000 0 1 1 $_TARGETNAME } err -catch { flash bank $_CHIPNAME.uicr nrf51 0x10001000 0 1 1 $_TARGETNAME } err +# necessary to be backward compatible with openocd 0.10 +if { [flash list] == "" } { + set WORKAREASIZE 0x40000 + $_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size $WORKAREASIZE -work-area-backup 0 + flash bank $_CHIPNAME.flash nrf51 0x00000000 0 1 1 $_TARGETNAME + flash bank $_CHIPNAME.uicr nrf51 0x10001000 0 1 1 $_TARGETNAME +} diff --git a/boards/microbit_v2/src/io.rs b/boards/microbit_v2/src/io.rs index c6b6199273..ec2e125b5c 100644 --- a/boards/microbit_v2/src/io.rs +++ b/boards/microbit_v2/src/io.rs @@ -1,14 +1,16 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; -use cortexm4; use kernel::debug; use kernel::debug::IoWrite; use kernel::hil::led; use kernel::hil::uart; -use nrf52833::gpio::{self, Pin}; - -use kernel::hil::gpio::{Configure, Input, Output}; +use nrf52833::gpio::Pin; +use nrf52833::uart::{Uarte, UARTE0_BASE}; use crate::CHIP; use crate::PROCESSES; @@ -37,8 +39,8 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { - let uart = nrf52833::uart::Uarte::new(); + fn write(&mut self, buf: &[u8]) -> usize { + let uart = Uarte::new(UARTE0_BASE); use kernel::hil::uart::Configure; @@ -59,31 +61,7 @@ impl IoWrite for Writer { while !uart.tx_ready() {} } } - } -} - -struct MatrixLed( - &'static gpio::GPIOPin<'static>, - &'static gpio::GPIOPin<'static>, -); - -impl led::Led for MatrixLed { - fn init(&self) { - self.0.make_output(); - self.1.make_output(); - self.1.clear(); - } - fn on(&self) { - self.1.set(); - } - fn off(&self) { - self.1.clear(); - } - fn toggle(&self) { - self.1.toggle(); - } - fn read(&self) -> bool { - self.1.read() + buf.len() } } @@ -93,22 +71,23 @@ impl led::Led for MatrixLed { #[cfg(not(test))] #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { // MicroBit v2 has an LED matrix, use the upper left LED // let mut led = Led (&gpio::PORT[Pin::P0_28], ); // MicroBit v2 has a microphone LED, use it for panic + + use core::ptr::{addr_of, addr_of_mut}; let led_kernel_pin = &nrf52833::gpio::GPIOPin::new(Pin::P0_20); let led = &mut led::LedLow::new(led_kernel_pin); - // MatrixLed(&gpio::PORT[Pin::P0_28], &gpio::PORT[Pin::P0_21]); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led], writer, pi, &cortexm4::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/microbit_v2/src/main.rs b/boards/microbit_v2/src/main.rs index 788100c580..e403296a65 100644 --- a/boards/microbit_v2/src/main.rs +++ b/boards/microbit_v2/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tock kernel for the Micro:bit v2. //! //! It is based on nRF52833 SoC (Cortex M4 core with a BLE). @@ -8,9 +12,10 @@ #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] +use core::ptr::{addr_of, addr_of_mut}; + use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil::time::Counter; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::scheduler::round_robin::RoundRobinSched; @@ -60,7 +65,8 @@ pub mod io; // State for loading and holding applications. // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; // Number of concurrent processes this platform supports. const NUM_PROCS: usize = 4; @@ -69,46 +75,75 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] [None; NUM_PROCS]; static mut CHIP: Option<&'static nrf52833::chip::NRF52> = None; -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; /// Dummy buffer that causes the linker to reserve enough space for the stack. #[no_mangle] #[link_section = ".stack_buffer"] -pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; +pub static mut STACK_MEMORY: [u8; 0x2000] = [0; 0x2000]; // debug mode requires more stack space // pub static mut STACK_MEMORY: [u8; 0x2000] = [0; 0x2000]; +type TemperatureDriver = + components::temperature::TemperatureComponentType>; +type RngDriver = components::rng::RngComponentType>; + /// Supported drivers by the platform pub struct MicroBit { - ble_radio: &'static capsules::ble_advertising_driver::BLE< + ble_radio: &'static capsules_extra::ble_advertising_driver::BLE< 'static, nrf52::ble_radio::Radio<'static>, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52::rtc::Rtc<'static>, + >, >, - console: &'static capsules::console::Console<'static>, - gpio: &'static capsules::gpio::GPIO<'static, nrf52::gpio::GPIOPin<'static>>, - led: &'static capsules::led_matrix::LedMatrixDriver< + console: &'static capsules_core::console::Console<'static>, + gpio: &'static capsules_core::gpio::GPIO<'static, nrf52::gpio::GPIOPin<'static>>, + led: &'static capsules_core::led::LedDriver< 'static, - nrf52::gpio::GPIOPin<'static>, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, + capsules_extra::led_matrix::LedMatrixLed< + 'static, + nrf52::gpio::GPIOPin<'static>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52::rtc::Rtc<'static>, + >, + >, + 25, >, - button: &'static capsules::button::Button<'static, nrf52::gpio::GPIOPin<'static>>, - rng: &'static capsules::rng::RngDriver<'static>, - ninedof: &'static capsules::ninedof::NineDof<'static>, - lsm303agr: &'static capsules::lsm303agr::Lsm303agrI2C<'static>, - temperature: &'static capsules::temperature::TemperatureSensor<'static>, - ipc: kernel::ipc::IPC, - adc: &'static capsules::adc::AdcVirtualized<'static>, - alarm: &'static capsules::alarm::AlarmDriver< + button: &'static capsules_core::button::Button<'static, nrf52::gpio::GPIOPin<'static>>, + rng: &'static RngDriver, + ninedof: &'static capsules_extra::ninedof::NineDof<'static>, + lsm303agr: &'static capsules_extra::lsm303agr::Lsm303agrI2C< 'static, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, nrf52833::i2c::TWI<'static>>, + >, + temperature: &'static TemperatureDriver, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + adc: &'static capsules_core::adc::AdcVirtualized<'static>, + alarm: &'static capsules_core::alarm::AlarmDriver< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52::rtc::Rtc<'static>, + >, >, - buzzer: &'static capsules::buzzer_driver::Buzzer< + buzzer_driver: &'static capsules_extra::buzzer_driver::Buzzer< 'static, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52833::rtc::Rtc<'static>>, + capsules_extra::buzzer_pwm::PwmBuzzer< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52833::rtc::Rtc<'static>, + >, + capsules_core::virtualizers::virtual_pwm::PwmPinUser<'static, nrf52833::pwm::Pwm>, + >, >, - app_flash: &'static capsules::app_flash_driver::AppFlash<'static>, - sound_pressure: &'static capsules::sound_pressure::SoundPressureSensor<'static>, + pwm: &'static capsules_extra::pwm::Pwm<'static, 1>, + app_flash: &'static capsules_extra::app_flash_driver::AppFlash<'static>, + sound_pressure: &'static capsules_extra::sound_pressure::SoundPressureSensor<'static>, scheduler: &'static RoundRobinSched<'static>, systick: cortexm4::systick::SysTick, @@ -120,20 +155,21 @@ impl SyscallDriverLookup for MicroBit { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::button::DRIVER_NUM => f(Some(self.button)), - capsules::led_matrix::DRIVER_NUM => f(Some(self.led)), - capsules::ninedof::DRIVER_NUM => f(Some(self.ninedof)), - capsules::adc::DRIVER_NUM => f(Some(self.adc)), - capsules::temperature::DRIVER_NUM => f(Some(self.temperature)), - capsules::lsm303agr::DRIVER_NUM => f(Some(self.lsm303agr)), - capsules::rng::DRIVER_NUM => f(Some(self.rng)), - capsules::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), - capsules::buzzer_driver::DRIVER_NUM => f(Some(self.buzzer)), - capsules::app_flash_driver::DRIVER_NUM => f(Some(self.app_flash)), - capsules::sound_pressure::DRIVER_NUM => f(Some(self.sound_pressure)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_extra::ninedof::DRIVER_NUM => f(Some(self.ninedof)), + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temperature)), + capsules_extra::lsm303agr::DRIVER_NUM => f(Some(self.lsm303agr)), + capsules_core::rng::DRIVER_NUM => f(Some(self.rng)), + capsules_extra::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), + capsules_extra::buzzer_driver::DRIVER_NUM => f(Some(self.buzzer_driver)), + capsules_extra::pwm::DRIVER_NUM => f(Some(self.pwm)), + capsules_extra::app_flash_driver::DRIVER_NUM => f(Some(self.app_flash)), + capsules_extra::sound_pressure::DRIVER_NUM => f(Some(self.sound_pressure)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), _ => f(None), } @@ -152,7 +188,7 @@ impl KernelResources &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -178,29 +214,24 @@ impl KernelResources &'static mut Nrf52833DefaultPeripherals<'static> { - // Initialize chip peripheral drivers +unsafe fn start() -> ( + &'static kernel::Kernel, + MicroBit, + &'static nrf52833::chip::NRF52<'static, Nrf52833DefaultPeripherals<'static>>, +) { + nrf52833::init(); + let nrf52833_peripherals = static_init!( Nrf52833DefaultPeripherals, Nrf52833DefaultPeripherals::new() ); - nrf52833_peripherals -} - -/// Main function called after RAM initialized. -#[no_mangle] -pub unsafe fn main() { - nrf52833::init(); - - let nrf52833_peripherals = get_peripherals(); - // set up circular peripheral dependencies nrf52833_peripherals.init(); let base_peripherals = &nrf52833_peripherals.nrf52; - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); //-------------------------------------------------------------------------- // CAPABILITIES @@ -210,7 +241,6 @@ pub unsafe fn main() { // functions. let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); //-------------------------------------------------------------------------- @@ -232,26 +262,27 @@ pub unsafe fn main() { let gpio = components::gpio::GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( nrf52833::gpio::GPIOPin, // Used as ADC, comment them out in the ADC section to use them as GPIO // 0 => &nrf52833_peripherals.gpio_port[GPIO_P0], // 1 => &nrf52833_peripherals.gpio_port[_GPIO_P1], // 2 => &nrf52833_peripherals.gpio_port[_GPIO_P2], - 8 => &nrf52833_peripherals.gpio_port[GPIO_P8], + // Used as PWM, comment them out in the PWM section to use them as GPIO + //8 => &nrf52833_peripherals.gpio_port[GPIO_P8], 9 => &nrf52833_peripherals.gpio_port[GPIO_P9], 16 => &nrf52833_peripherals.gpio_port[GPIO_P16], ), ) - .finalize(components::gpio_component_buf!(nrf52833::gpio::GPIOPin)); + .finalize(components::gpio_component_static!(nrf52833::gpio::GPIOPin)); //-------------------------------------------------------------------------- // Buttons //-------------------------------------------------------------------------- let button = components::button::ButtonComponent::new( board_kernel, - capsules::button::DRIVER_NUM, + capsules_core::button::DRIVER_NUM, components::button_component_helper!( nrf52833::gpio::GPIOPin, ( @@ -271,19 +302,9 @@ pub unsafe fn main() { ), // Touch Logo ), ) - .finalize(components::button_component_buf!(nrf52833::gpio::GPIOPin)); - - //-------------------------------------------------------------------------- - // Deferred Call (Dynamic) Setup - //-------------------------------------------------------------------------- - - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 3], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + .finalize(components::button_component_static!( + nrf52833::gpio::GPIOPin + )); //-------------------------------------------------------------------------- // ALARM & TIMER @@ -293,55 +314,91 @@ pub unsafe fn main() { let _ = rtc.start(); let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) - .finalize(components::alarm_mux_component_helper!(nrf52::rtc::Rtc)); + .finalize(components::alarm_mux_component_static!(nrf52::rtc::Rtc)); let alarm = components::alarm::AlarmDriverComponent::new( board_kernel, - capsules::alarm::DRIVER_NUM, + capsules_core::alarm::DRIVER_NUM, mux_alarm, ) - .finalize(components::alarm_component_helper!(nrf52::rtc::Rtc)); + .finalize(components::alarm_component_static!(nrf52::rtc::Rtc)); //-------------------------------------------------------------------------- // PWM & BUZZER //-------------------------------------------------------------------------- + use kernel::hil::buzzer::Buzzer; use kernel::hil::time::Alarm; - let mux_pwm = static_init!( - capsules::virtual_pwm::MuxPwm<'static, nrf52833::pwm::Pwm>, - capsules::virtual_pwm::MuxPwm::new(&base_peripherals.pwm0) - ); - let virtual_pwm_buzzer = static_init!( - capsules::virtual_pwm::PwmPinUser<'static, nrf52833::pwm::Pwm>, - capsules::virtual_pwm::PwmPinUser::new( - mux_pwm, - nrf52833::pinmux::Pinmux::new(SPEAKER_PIN as u32) - ) - ); - virtual_pwm_buzzer.add_to_mux(); + let mux_pwm = components::pwm::PwmMuxComponent::new(&base_peripherals.pwm0) + .finalize(components::pwm_mux_component_static!(nrf52833::pwm::Pwm)); + + let virtual_pwm_buzzer = components::pwm::PwmPinUserComponent::new( + mux_pwm, + nrf52833::pinmux::Pinmux::new(SPEAKER_PIN as u32), + ) + .finalize(components::pwm_pin_user_component_static!( + nrf52833::pwm::Pwm + )); let virtual_alarm_buzzer = static_init!( - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52833::rtc::Rtc>, - capsules::virtual_alarm::VirtualMuxAlarm::new(mux_alarm) + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, nrf52833::rtc::Rtc>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm::new(mux_alarm) ); virtual_alarm_buzzer.setup(); - let buzzer = static_init!( - capsules::buzzer_driver::Buzzer< + let pwm_buzzer = static_init!( + capsules_extra::buzzer_pwm::PwmBuzzer< 'static, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52833::rtc::Rtc>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52833::rtc::Rtc, + >, + capsules_core::virtualizers::virtual_pwm::PwmPinUser<'static, nrf52833::pwm::Pwm>, >, - capsules::buzzer_driver::Buzzer::new( + capsules_extra::buzzer_pwm::PwmBuzzer::new( virtual_pwm_buzzer, virtual_alarm_buzzer, - capsules::buzzer_driver::DEFAULT_MAX_BUZZ_TIME_MS, + capsules_extra::buzzer_pwm::DEFAULT_MAX_BUZZ_TIME_MS, + ) + ); + + let buzzer_driver = static_init!( + capsules_extra::buzzer_driver::Buzzer< + 'static, + capsules_extra::buzzer_pwm::PwmBuzzer< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52833::rtc::Rtc, + >, + capsules_core::virtualizers::virtual_pwm::PwmPinUser<'static, nrf52833::pwm::Pwm>, + >, + >, + capsules_extra::buzzer_driver::Buzzer::new( + pwm_buzzer, + capsules_extra::buzzer_driver::DEFAULT_MAX_BUZZ_TIME_MS, board_kernel.create_grant( - capsules::buzzer_driver::DRIVER_NUM, + capsules_extra::buzzer_driver::DRIVER_NUM, &memory_allocation_capability ) ) ); - virtual_alarm_buzzer.set_alarm_client(buzzer); + + pwm_buzzer.set_client(buzzer_driver); + + virtual_alarm_buzzer.set_alarm_client(pwm_buzzer); + + let virtual_pwm_driver = components::pwm::PwmPinUserComponent::new( + mux_pwm, + nrf52833::pinmux::Pinmux::new(GPIO_P8 as u32), + ) + .finalize(components::pwm_pin_user_component_static!( + nrf52833::pwm::Pwm + )); + + let pwm = + components::pwm::PwmDriverComponent::new(board_kernel, capsules_extra::pwm::DRIVER_NUM) + .finalize(components::pwm_driver_component_helper!(virtual_pwm_driver)); //-------------------------------------------------------------------------- // UART & CONSOLE & DEBUG @@ -355,22 +412,19 @@ pub unsafe fn main() { ); // Create a shared UART channel for the console and for kernel debug. - let uart_mux = components::console::UartMuxComponent::new( - &base_peripherals.uarte0, - 115200, - dynamic_deferred_caller, - ) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(&base_peripherals.uarte0, 115200) + .finalize(components::uart_mux_component_static!()); // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); //-------------------------------------------------------------------------- // RANDOM NUMBERS @@ -378,59 +432,66 @@ pub unsafe fn main() { let rng = components::rng::RngComponent::new( board_kernel, - capsules::rng::DRIVER_NUM, + capsules_core::rng::DRIVER_NUM, &base_peripherals.trng, ) - .finalize(()); + .finalize(components::rng_component_static!(nrf52833::trng::Trng)); //-------------------------------------------------------------------------- // SENSORS //-------------------------------------------------------------------------- - base_peripherals.twi0.configure( + base_peripherals.twi1.configure( nrf52833::pinmux::Pinmux::new(I2C_SCL_PIN as u32), nrf52833::pinmux::Pinmux::new(I2C_SDA_PIN as u32), ); - let sensors_i2c_bus = components::i2c::I2CMuxComponent::new( - &base_peripherals.twi0, - None, - dynamic_deferred_caller, - ) - .finalize(components::i2c_mux_component_helper!()); + let sensors_i2c_bus = components::i2c::I2CMuxComponent::new(&base_peripherals.twi1, None) + .finalize(components::i2c_mux_component_static!( + nrf52833::i2c::TWI<'static> + )); // LSM303AGR let lsm303agr = components::lsm303agr::Lsm303agrI2CComponent::new( + sensors_i2c_bus, + None, + None, board_kernel, - capsules::lsm303agr::DRIVER_NUM, + capsules_extra::lsm303agr::DRIVER_NUM, ) - .finalize(components::lsm303agr_i2c_component_helper!(sensors_i2c_bus)); + .finalize(components::lsm303agr_component_static!( + nrf52833::i2c::TWI<'static> + )); if let Err(error) = lsm303agr.configure( - capsules::lsm303xx::Lsm303AccelDataRate::DataRate25Hz, + capsules_extra::lsm303xx::Lsm303AccelDataRate::DataRate25Hz, false, - capsules::lsm303xx::Lsm303Scale::Scale2G, + capsules_extra::lsm303xx::Lsm303Scale::Scale2G, false, true, - capsules::lsm303xx::Lsm303MagnetoDataRate::DataRate3_0Hz, - capsules::lsm303xx::Lsm303Range::Range1_9G, + capsules_extra::lsm303xx::Lsm303MagnetoDataRate::DataRate3_0Hz, + capsules_extra::lsm303xx::Lsm303Range::Range1_9G, ) { debug!("Failed to configure LSM303AGR sensor ({:?})", error); } - let ninedof = - components::ninedof::NineDofComponent::new(board_kernel, capsules::ninedof::DRIVER_NUM) - .finalize(components::ninedof_component_helper!(lsm303agr)); + let ninedof = components::ninedof::NineDofComponent::new( + board_kernel, + capsules_extra::ninedof::DRIVER_NUM, + ) + .finalize(components::ninedof_component_static!(lsm303agr)); // Temperature let temperature = components::temperature::TemperatureComponent::new( board_kernel, - capsules::temperature::DRIVER_NUM, + capsules_extra::temperature::DRIVER_NUM, &base_peripherals.temp, ) - .finalize(()); + .finalize(components::temperature_component_static!( + nrf52833::temperature::Temp + )); //-------------------------------------------------------------------------- // ADC @@ -438,87 +499,84 @@ pub unsafe fn main() { base_peripherals.adc.calibrate(); let adc_mux = components::adc::AdcMuxComponent::new(&base_peripherals.adc) - .finalize(components::adc_mux_component_helper!(nrf52833::adc::Adc)); + .finalize(components::adc_mux_component_static!(nrf52833::adc::Adc)); // Comment out the following to use P0, P1 and P2 as GPIO let adc_syscall = - components::adc::AdcVirtualComponent::new(board_kernel, capsules::adc::DRIVER_NUM) + components::adc::AdcVirtualComponent::new(board_kernel, capsules_core::adc::DRIVER_NUM) .finalize(components::adc_syscall_component_helper!( // ADC Ring 0 (P0) components::adc::AdcComponent::new( - &adc_mux, + adc_mux, nrf52833::adc::AdcChannelSetup::new(nrf52833::adc::AdcChannel::AnalogInput0) ) - .finalize(components::adc_component_helper!(nrf52833::adc::Adc)), + .finalize(components::adc_component_static!(nrf52833::adc::Adc)), // ADC Ring 1 (P1) components::adc::AdcComponent::new( - &adc_mux, + adc_mux, nrf52833::adc::AdcChannelSetup::new(nrf52833::adc::AdcChannel::AnalogInput1) ) - .finalize(components::adc_component_helper!(nrf52833::adc::Adc)), + .finalize(components::adc_component_static!(nrf52833::adc::Adc)), // ADC Ring 2 (P2) components::adc::AdcComponent::new( - &adc_mux, + adc_mux, nrf52833::adc::AdcChannelSetup::new(nrf52833::adc::AdcChannel::AnalogInput2) ) - .finalize(components::adc_component_helper!(nrf52833::adc::Adc)) + .finalize(components::adc_component_static!(nrf52833::adc::Adc)) )); // Microphone - let adc_microphone = components::adc_microphone::AdcMicrophoneComponent::new().finalize( - components::adc_microphone_component_helper!( - // adc - nrf52833::adc::Adc, - // adc channel - nrf52833::adc::AdcChannelSetup::setup( - nrf52833::adc::AdcChannel::AnalogInput3, - nrf52833::adc::AdcChannelGain::Gain4, - nrf52833::adc::AdcChannelResistor::Bypass, - nrf52833::adc::AdcChannelResistor::Pulldown, - nrf52833::adc::AdcChannelSamplingTime::us3 - ), - // adc mux - adc_mux, - // buffer size - 50, - // gpio - nrf52833::gpio::GPIOPin, - // optional gpio pin - Some(&nrf52833_peripherals.gpio_port[LED_MICROPHONE_PIN]) + let adc_microphone = components::adc_microphone::AdcMicrophoneComponent::new( + adc_mux, + nrf52833::adc::AdcChannelSetup::setup( + nrf52833::adc::AdcChannel::AnalogInput3, + nrf52833::adc::AdcChannelGain::Gain4, + nrf52833::adc::AdcChannelResistor::Bypass, + nrf52833::adc::AdcChannelResistor::Pulldown, + nrf52833::adc::AdcChannelSamplingTime::us3, ), - ); + Some(&nrf52833_peripherals.gpio_port[LED_MICROPHONE_PIN]), + ) + .finalize(components::adc_microphone_component_static!( + // adc + nrf52833::adc::Adc, + // buffer size + 50, + // gpio + nrf52833::gpio::GPIOPin + )); - let _ = &nrf52833_peripherals.gpio_port[LED_MICROPHONE_PIN].set_high_drive(true); + nrf52833_peripherals.gpio_port[LED_MICROPHONE_PIN].set_high_drive(true); let sound_pressure = components::sound_pressure::SoundPressureComponent::new( board_kernel, - capsules::sound_pressure::DRIVER_NUM, + capsules_extra::sound_pressure::DRIVER_NUM, adc_microphone, ) - .finalize(()); + .finalize(components::sound_pressure_component_static!()); //-------------------------------------------------------------------------- // STORAGE //-------------------------------------------------------------------------- let mux_flash = components::flash::FlashMuxComponent::new(&base_peripherals.nvmc).finalize( - components::flash_mux_component_helper!(nrf52833::nvmc::Nvmc), + components::flash_mux_component_static!(nrf52833::nvmc::Nvmc), ); // App Flash let virtual_app_flash = components::flash::FlashUserComponent::new(mux_flash).finalize( - components::flash_user_component_helper!(nrf52833::nvmc::Nvmc), + components::flash_user_component_static!(nrf52833::nvmc::Nvmc), ); let app_flash = components::app_flash_driver::AppFlashComponent::new( board_kernel, - capsules::app_flash_driver::DRIVER_NUM, + capsules_extra::app_flash_driver::DRIVER_NUM, virtual_app_flash, ) - .finalize(components::app_flash_component_helper!( - capsules::virtual_flash::FlashUser<'static, nrf52833::nvmc::Nvmc>, + .finalize(components::app_flash_component_static!( + capsules_core::virtualizers::virtual_flash::FlashUser<'static, nrf52833::nvmc::Nvmc>, 512 )); @@ -526,47 +584,103 @@ pub unsafe fn main() { // WIRELESS //-------------------------------------------------------------------------- - let ble_radio = nrf52_components::BLEComponent::new( + let ble_radio = components::ble::BLEComponent::new( board_kernel, - capsules::ble_advertising_driver::DRIVER_NUM, + capsules_extra::ble_advertising_driver::DRIVER_NUM, &base_peripherals.ble_radio, mux_alarm, ) - .finalize(()); + .finalize(components::ble_component_static!( + nrf52833::rtc::Rtc, + nrf52833::ble_radio::Radio + )); //-------------------------------------------------------------------------- // LED Matrix //-------------------------------------------------------------------------- - let led = components::led_matrix_component_helper!( - nrf52833::gpio::GPIOPin, - nrf52::rtc::Rtc<'static>, + let led_matrix = components::led_matrix::LedMatrixComponent::new( mux_alarm, - @fps => 60, - @cols => kernel::hil::gpio::ActivationMode::ActiveLow, + components::led_line_component_static!( + nrf52833::gpio::GPIOPin, &nrf52833_peripherals.gpio_port[LED_MATRIX_COLS[0]], &nrf52833_peripherals.gpio_port[LED_MATRIX_COLS[1]], &nrf52833_peripherals.gpio_port[LED_MATRIX_COLS[2]], &nrf52833_peripherals.gpio_port[LED_MATRIX_COLS[3]], &nrf52833_peripherals.gpio_port[LED_MATRIX_COLS[4]], - @rows => kernel::hil::gpio::ActivationMode::ActiveHigh, + ), + components::led_line_component_static!( + nrf52833::gpio::GPIOPin, &nrf52833_peripherals.gpio_port[LED_MATRIX_ROWS[0]], &nrf52833_peripherals.gpio_port[LED_MATRIX_ROWS[1]], &nrf52833_peripherals.gpio_port[LED_MATRIX_ROWS[2]], &nrf52833_peripherals.gpio_port[LED_MATRIX_ROWS[3]], - &nrf52833_peripherals.gpio_port[LED_MATRIX_ROWS[4]] - + &nrf52833_peripherals.gpio_port[LED_MATRIX_ROWS[4]], + ), + kernel::hil::gpio::ActivationMode::ActiveLow, + kernel::hil::gpio::ActivationMode::ActiveHigh, + 60, ) - .finalize(components::led_matrix_component_buf!( + .finalize(components::led_matrix_component_static!( nrf52833::gpio::GPIOPin, - nrf52::rtc::Rtc<'static> + nrf52::rtc::Rtc<'static>, + 5, + 5 )); + let led = static_init!( + capsules_core::led::LedDriver< + 'static, + capsules_extra::led_matrix::LedMatrixLed< + 'static, + nrf52::gpio::GPIOPin<'static>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52::rtc::Rtc<'static>, + >, + >, + 25, + >, + capsules_core::led::LedDriver::new(components::led_matrix_leds!( + nrf52::gpio::GPIOPin<'static>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52::rtc::Rtc<'static>, + >, + led_matrix, + (0, 0), + (1, 0), + (2, 0), + (3, 0), + (4, 0), + (0, 1), + (1, 1), + (2, 1), + (3, 1), + (4, 1), + (0, 2), + (1, 2), + (2, 2), + (3, 2), + (4, 2), + (0, 3), + (1, 3), + (2, 3), + (3, 3), + (4, 3), + (0, 4), + (1, 4), + (2, 4), + (3, 4), + (4, 4) + )), + ); + //-------------------------------------------------------------------------- // Process Console //-------------------------------------------------------------------------- - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); let _process_console = components::process_console::ProcessConsoleComponent::new( @@ -574,8 +688,9 @@ pub unsafe fn main() { uart_mux, mux_alarm, process_printer, + Some(cortexm4::support::reset), ) - .finalize(components::process_console_component_helper!( + .finalize(components::process_console_component_static!( nrf52833::rtc::Rtc )); let _ = _process_console.start(); @@ -592,8 +707,8 @@ pub unsafe fn main() { while !base_peripherals.clock.low_started() {} while !base_peripherals.clock.high_started() {} - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); let microbit = MicroBit { ble_radio, @@ -605,7 +720,8 @@ pub unsafe fn main() { temperature, lsm303agr, ninedof, - buzzer, + buzzer_driver, + pwm, sound_pressure, adc: adc_syscall, alarm, @@ -632,7 +748,7 @@ pub unsafe fn main() { // PROCESSES AND MAIN LOOP //-------------------------------------------------------------------------- - /// These symbols are defined in the linker script. + // These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -648,14 +764,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_management_capability, ) @@ -664,5 +780,14 @@ pub unsafe fn main() { debug!("{:?}", err); }); - board_kernel.kernel_loop(µbit, chip, Some(µbit.ipc), &main_loop_capability); + (board_kernel, microbit, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, board, chip) = start(); + board_kernel.kernel_loop(&board, chip, Some(&board.ipc), &main_loop_capability); } diff --git a/boards/msp_exp432p401r/Cargo.toml b/boards/msp_exp432p401r/Cargo.toml index 3478d8c198..c9679a49dc 100644 --- a/boards/msp_exp432p401r/Cargo.toml +++ b/boards/msp_exp432p401r/Cargo.toml @@ -1,13 +1,23 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "msp-exp432p401r" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] components = { path = "../components" } cortexm4 = { path = "../../arch/cortex-m4" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } msp432 = { path = "../../chips/msp432" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/msp_exp432p401r/Makefile b/boards/msp_exp432p401r/Makefile index 5af0b0ffa3..21ed3bc732 100644 --- a/boards/msp_exp432p401r/Makefile +++ b/boards/msp_exp432p401r/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the MSP-EXP432P401 launchpad # TARGET=thumbv7em-none-eabi diff --git a/boards/msp_exp432p401r/build.rs b/boards/msp_exp432p401r/build.rs deleted file mode 100644 index 46b1df0bac..0000000000 --- a/boards/msp_exp432p401r/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=chip_layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/msp_exp432p401r/chip_layout.ld b/boards/msp_exp432p401r/chip_layout.ld index 03adcbd6d6..f01d2202cb 100644 --- a/boards/msp_exp432p401r/chip_layout.ld +++ b/boards/msp_exp432p401r/chip_layout.ld @@ -1,3 +1,7 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + /* Memory Space Definitions, 256K flash, 64K ram */ MEMORY { @@ -6,5 +10,4 @@ MEMORY ram (rwx) : ORIGIN = 0x20000000, LENGTH = 64K } -MPU_MIN_ALIGN = 8K; PAGE_SIZE = 4K; diff --git a/boards/msp_exp432p401r/layout.ld b/boards/msp_exp432p401r/layout.ld index 234dcaea2c..6814c00acd 100644 --- a/boards/msp_exp432p401r/layout.ld +++ b/boards/msp_exp432p401r/layout.ld @@ -1,2 +1,6 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + INCLUDE ./chip_layout.ld INCLUDE ../kernel_layout.ld diff --git a/boards/msp_exp432p401r/openocd.cfg b/boards/msp_exp432p401r/openocd.cfg index 56402978bd..0f22096b14 100644 --- a/boards/msp_exp432p401r/openocd.cfg +++ b/boards/msp_exp432p401r/openocd.cfg @@ -1,6 +1,10 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + adapter driver xds110 adapter speed 10000 source [find board/ti_msp432_launchpad.cfg] init; -reset halt; \ No newline at end of file +reset halt; diff --git a/boards/msp_exp432p401r/src/io.rs b/boards/msp_exp432p401r/src/io.rs index 60403df2d2..3ddf602508 100644 --- a/boards/msp_exp432p401r/src/io.rs +++ b/boards/msp_exp432p401r/src/io.rs @@ -1,9 +1,15 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use crate::CHIP; use crate::PROCESSES; use crate::PROCESS_PRINTER; use core::fmt::Write; use core::panic::PanicInfo; +use core::ptr::addr_of; +use core::ptr::addr_of_mut; use kernel::debug; use kernel::debug::IoWrite; use kernel::hil::led; @@ -24,20 +30,21 @@ impl Write for Uart { } impl IoWrite for Uart { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { let uart0 = msp432::uart::Uart::new(msp432::usci::USCI_A0_BASE, 0, 1, 1, 1); uart0.transmit_sync(buf); + buf.len() } } /// Panic handler #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(info: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(info: &PanicInfo) -> ! { const LED1_PIN: IntPinNr = IntPinNr::P01_0; let gpio_pin = msp432::gpio::IntPin::new(LED1_PIN); let led = &mut led::LedHigh::new(&gpio_pin); - let writer = &mut UART; + let writer = &mut *addr_of_mut!(UART); let wdt = Wdt::new(); wdt.disable(); @@ -46,8 +53,8 @@ pub unsafe extern "C" fn panic_fmt(info: &PanicInfo) -> ! { writer, info, &cortexm4::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/msp_exp432p401r/src/main.rs b/boards/msp_exp432p401r/src/main.rs index 603dea7777..46c63c8b81 100644 --- a/boards/msp_exp432p401r/src/main.rs +++ b/boards/msp_exp432p401r/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Board file for the MSP-EXP432P401R evaluation board from TI. //! //! - @@ -8,11 +12,11 @@ #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] +use core::ptr::{addr_of, addr_of_mut}; + use components::gpio::GpioComponent; use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::DynamicDeferredCall; -use kernel::dynamic_deferred_call::DynamicDeferredCallClientState; use kernel::hil::gpio::Configure; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::scheduler::round_robin::RoundRobinSched; @@ -32,10 +36,12 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] static mut CHIP: Option<&'static msp432::chip::Msp432> = None; // Static reference to process printer for panic dumps. -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; /// How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; /// Dummy buffer that causes the linker to reserve enough space for the stack. #[no_mangle] @@ -45,20 +51,23 @@ pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; /// A structure representing this platform that holds references to all /// capsules for this platform. struct MspExp432P401R { - led: &'static capsules::led::LedDriver< + led: &'static capsules_core::led::LedDriver< 'static, kernel::hil::led::LedHigh<'static, msp432::gpio::IntPin<'static>>, 3, >, - console: &'static capsules::console::Console<'static>, - button: &'static capsules::button::Button<'static, msp432::gpio::IntPin<'static>>, - gpio: &'static capsules::gpio::GPIO<'static, msp432::gpio::IntPin<'static>>, - alarm: &'static capsules::alarm::AlarmDriver< + console: &'static capsules_core::console::Console<'static>, + button: &'static capsules_core::button::Button<'static, msp432::gpio::IntPin<'static>>, + gpio: &'static capsules_core::gpio::GPIO<'static, msp432::gpio::IntPin<'static>>, + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, - capsules::virtual_alarm::VirtualMuxAlarm<'static, msp432::timer::TimerA<'static>>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + msp432::timer::TimerA<'static>, + >, >, - ipc: kernel::ipc::IPC, - adc: &'static capsules::adc::AdcDedicated<'static, msp432::adc::Adc<'static>>, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + adc: &'static capsules_core::adc::AdcDedicated<'static, msp432::adc::Adc<'static>>, wdt: &'static msp432::wdt::Wdt, scheduler: &'static RoundRobinSched<'static>, systick: cortexm4::systick::SysTick, @@ -76,7 +85,7 @@ impl KernelResources &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -91,7 +100,7 @@ impl KernelResources &Self::WatchDog { - &self.wdt + self.wdt } fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { &() @@ -105,13 +114,13 @@ impl SyscallDriverLookup for MspExp432P401R { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::button::DRIVER_NUM => f(Some(self.button)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), - capsules::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), _ => f(None), } } @@ -183,21 +192,17 @@ unsafe fn setup_adc_pins(gpio: &msp432::gpio::GpioManager) { /// removed when this function returns. Otherwise, the stack space used for /// these static_inits is wasted. #[inline(never)] -unsafe fn get_peripherals() -> &'static mut msp432::chip::Msp432DefaultPeripherals<'static> { - static_init!( - msp432::chip::Msp432DefaultPeripherals, - msp432::chip::Msp432DefaultPeripherals::new() - ) -} - -/// Main function. -/// -/// This is called after RAM initialization is complete. -#[no_mangle] -pub unsafe fn main() { +unsafe fn start() -> ( + &'static kernel::Kernel, + MspExp432P401R, + &'static msp432::chip::Msp432<'static, msp432::chip::Msp432DefaultPeripherals<'static>>, +) { startup_intilialisation(); - let peripherals = get_peripherals(); + let peripherals = static_init!( + msp432::chip::Msp432DefaultPeripherals, + msp432::chip::Msp432DefaultPeripherals::new() + ); peripherals.init(); // Setup the GPIO pins to use the HFXT (high frequency external) oscillator (48MHz) @@ -228,7 +233,7 @@ pub unsafe fn main() { peripherals.gpio.int_pins[msp432::gpio::IntPinNr::P01_2 as usize].enable_primary_function(); peripherals.gpio.int_pins[msp432::gpio::IntPinNr::P01_3 as usize].enable_primary_function(); - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); let chip = static_init!( msp432::chip::Msp432, msp432::chip::Msp432::new(peripherals) @@ -238,7 +243,7 @@ pub unsafe fn main() { // Setup buttons let button = components::button::ButtonComponent::new( board_kernel, - capsules::button::DRIVER_NUM, + capsules_core::button::DRIVER_NUM, components::button_component_helper!( msp432::gpio::IntPin, ( @@ -253,10 +258,10 @@ pub unsafe fn main() { ) ), ) - .finalize(components::button_component_buf!(msp432::gpio::IntPin)); + .finalize(components::button_component_static!(msp432::gpio::IntPin)); // Setup LEDs - let leds = components::led::LedsComponent::new().finalize(components::led_component_helper!( + let leds = components::led::LedsComponent::new().finalize(components::led_component_static!( kernel::hil::led::LedHigh<'static, msp432::gpio::IntPin>, kernel::hil::led::LedHigh::new( &peripherals.gpio.int_pins[msp432::gpio::IntPinNr::P02_0 as usize] @@ -272,7 +277,7 @@ pub unsafe fn main() { // Setup user-GPIOs let gpio = GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( msp432::gpio::IntPin<'static>, // Left outer connector, top to bottom @@ -316,101 +321,82 @@ pub unsafe fn main() { 34 => &peripherals.gpio.int_pins[msp432::gpio::IntPinNr::P03_6 as usize] ), ) - .finalize(components::gpio_component_buf!( + .finalize(components::gpio_component_static!( msp432::gpio::IntPin<'static> )); let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 1], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); // Setup UART0 - let uart_mux = components::console::UartMuxComponent::new( - &peripherals.uart0, - 115200, - dynamic_deferred_caller, - ) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(&peripherals.uart0, 115200) + .finalize(components::uart_mux_component_static!()); // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); // Setup alarm let timer0 = &peripherals.timer_a0; let mux_alarm = components::alarm::AlarmMuxComponent::new(timer0).finalize( - components::alarm_mux_component_helper!(msp432::timer::TimerA), + components::alarm_mux_component_static!(msp432::timer::TimerA), ); let alarm = components::alarm::AlarmDriverComponent::new( board_kernel, - capsules::alarm::DRIVER_NUM, + capsules_core::alarm::DRIVER_NUM, mux_alarm, ) - .finalize(components::alarm_component_helper!(msp432::timer::TimerA)); + .finalize(components::alarm_component_static!(msp432::timer::TimerA)); // Setup ADC - setup_adc_pins(&peripherals.gpio); let adc_channels = static_init!( - [&'static msp432::adc::Channel; 24], + [msp432::adc::Channel; 24], [ - &msp432::adc::Channel::Channel0, // A0 - &msp432::adc::Channel::Channel1, // A1 - &msp432::adc::Channel::Channel2, // A2 - &msp432::adc::Channel::Channel3, // A3 - &msp432::adc::Channel::Channel4, // A4 - &msp432::adc::Channel::Channel5, // A5 - &msp432::adc::Channel::Channel6, // A6 - &msp432::adc::Channel::Channel7, // A7 - &msp432::adc::Channel::Channel8, // A8 - &msp432::adc::Channel::Channel9, // A9 - &msp432::adc::Channel::Channel10, // A10 - &msp432::adc::Channel::Channel11, // A11 - &msp432::adc::Channel::Channel12, // A12 - &msp432::adc::Channel::Channel13, // A13 - &msp432::adc::Channel::Channel14, // A14 - &msp432::adc::Channel::Channel15, // A15 - &msp432::adc::Channel::Channel16, // A16 - &msp432::adc::Channel::Channel17, // A17 - &msp432::adc::Channel::Channel18, // A18 - &msp432::adc::Channel::Channel19, // A19 - &msp432::adc::Channel::Channel20, // A20 - &msp432::adc::Channel::Channel21, // A21 - &msp432::adc::Channel::Channel22, // A22 - &msp432::adc::Channel::Channel23, // A23 + msp432::adc::Channel::Channel0, // A0 + msp432::adc::Channel::Channel1, // A1 + msp432::adc::Channel::Channel2, // A2 + msp432::adc::Channel::Channel3, // A3 + msp432::adc::Channel::Channel4, // A4 + msp432::adc::Channel::Channel5, // A5 + msp432::adc::Channel::Channel6, // A6 + msp432::adc::Channel::Channel7, // A7 + msp432::adc::Channel::Channel8, // A8 + msp432::adc::Channel::Channel9, // A9 + msp432::adc::Channel::Channel10, // A10 + msp432::adc::Channel::Channel11, // A11 + msp432::adc::Channel::Channel12, // A12 + msp432::adc::Channel::Channel13, // A13 + msp432::adc::Channel::Channel14, // A14 + msp432::adc::Channel::Channel15, // A15 + msp432::adc::Channel::Channel16, // A16 + msp432::adc::Channel::Channel17, // A17 + msp432::adc::Channel::Channel18, // A18 + msp432::adc::Channel::Channel19, // A19 + msp432::adc::Channel::Channel20, // A20 + msp432::adc::Channel::Channel21, // A21 + msp432::adc::Channel::Channel22, // A22 + msp432::adc::Channel::Channel23, // A23 ] ); - - let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let grant_adc = board_kernel.create_grant(capsules::adc::DRIVER_NUM, &grant_cap); - let adc = static_init!( - capsules::adc::AdcDedicated<'static, msp432::adc::Adc>, - capsules::adc::AdcDedicated::new( - &peripherals.adc, - grant_adc, - adc_channels, - &mut capsules::adc::ADC_BUFFER1, - &mut capsules::adc::ADC_BUFFER2, - &mut capsules::adc::ADC_BUFFER3 - ) - ); - - peripherals.adc.set_client(adc); + let adc = components::adc::AdcDedicatedComponent::new( + &peripherals.adc, + adc_channels, + board_kernel, + capsules_core::adc::DRIVER_NUM, + ) + .finalize(components::adc_dedicated_component_static!( + msp432::adc::Adc + )); // Set the reference voltage for the ADC to 2.5V peripherals @@ -419,25 +405,25 @@ pub unsafe fn main() { // Enable the internal temperature sensor on ADC Channel 22 peripherals.adc_ref.enable_temp_sensor(true); - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); let msp_exp432p4014 = MspExp432P401R { led: leds, - console: console, - button: button, - gpio: gpio, - alarm: alarm, + console, + button, + gpio, + alarm, ipc: kernel::ipc::IPC::new( board_kernel, kernel::ipc::DRIVER_NUM, &memory_allocation_capability, ), - adc: adc, + adc, scheduler, systick: cortexm4::systick::SysTick::new_with_calibration(48_000_000), wdt: &peripherals.wdt, @@ -445,7 +431,7 @@ pub unsafe fn main() { debug!("Initialization complete. Entering main loop"); - /// These symbols are defined in the linker script. + // These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -461,14 +447,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_management_capability, ) @@ -479,10 +465,14 @@ pub unsafe fn main() { .finalize(components::multi_alarm_test_component_buf!(msp432::timer::TimerA)) .run();*/ - board_kernel.kernel_loop( - &msp_exp432p4014, - chip, - Some(&msp_exp432p4014.ipc), - &main_loop_capability, - ); + (board_kernel, msp_exp432p4014, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, board, chip) = start(); + board_kernel.kernel_loop(&board, chip, Some(&board.ipc), &main_loop_capability); } diff --git a/boards/nano33ble/Cargo.toml b/boards/nano33ble/Cargo.toml index 00a27d8f44..dcce7834e2 100644 --- a/boards/nano33ble/Cargo.toml +++ b/boards/nano33ble/Cargo.toml @@ -1,15 +1,25 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "nano33ble" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] cortexm4 = { path = "../../arch/cortex-m4" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } nrf52 = { path = "../../chips/nrf52" } nrf52840 = { path = "../../chips/nrf52840" } components = { path = "../components" } nrf52_components = { path = "../nordic/nrf52_components" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/nano33ble/Makefile b/boards/nano33ble/Makefile index 0e5c40cb95..d16030a01b 100644 --- a/boards/nano33ble/Makefile +++ b/boards/nano33ble/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the Arduino Nano 33 BLE board. TOCK_ARCH=cortex-m4 diff --git a/boards/nano33ble/README.md b/boards/nano33ble/README.md index 659f91a981..c240bd1ed5 100644 --- a/boards/nano33ble/README.md +++ b/boards/nano33ble/README.md @@ -1,7 +1,7 @@ Arduino Nano 33 BLE =================== - + The [Arduino Nano 33 BLE](https://store.arduino.cc/usa/nano-33-ble) and [Arduino Nano 33 BLE Sense](https://store.arduino.cc/usa/nano-33-ble-sense) are compact diff --git a/boards/nano33ble/build.rs b/boards/nano33ble/build.rs deleted file mode 100644 index 3051054fc6..0000000000 --- a/boards/nano33ble/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/nano33ble/layout.ld b/boards/nano33ble/layout.ld index a429c16420..d5950e39c4 100644 --- a/boards/nano33ble/layout.ld +++ b/boards/nano33ble/layout.ld @@ -1,3 +1,7 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + MEMORY { rom (rx) : ORIGIN = 0x00010000, LENGTH = 256K @@ -5,7 +9,6 @@ MEMORY ram (rwx) : ORIGIN = 0x20000000, LENGTH = 256K } -MPU_MIN_ALIGN = 8K; PAGE_SIZE = 4K; INCLUDE ../kernel_layout.ld diff --git a/boards/nano33ble/src/io.rs b/boards/nano33ble/src/io.rs index e2e3e1391f..221b45a4c9 100644 --- a/boards/nano33ble/src/io.rs +++ b/boards/nano33ble/src/io.rs @@ -1,7 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; +use core::ptr::{addr_of, addr_of_mut}; -use cortexm4; use kernel::debug; use kernel::debug::IoWrite; use kernel::hil::led; @@ -46,7 +50,7 @@ impl uart::TransmitClient for DummyUsbClient { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { if !self.initialized { self.initialized = true; } @@ -93,8 +97,8 @@ impl IoWrite for Writer { // mutate it. let usb = &mut cdc.controller(); STATIC_PANIC_BUF[..max].copy_from_slice(&buf[..max]); - let static_buf = &mut STATIC_PANIC_BUF; - cdc.set_transmit_client(&DUMMY); + let static_buf = &mut *addr_of_mut!(STATIC_PANIC_BUF); + cdc.set_transmit_client(&*addr_of!(DUMMY)); let _ = cdc.transmit_buffer(static_buf, max); loop { if let Some(interrupt) = cortexm4::nvic::next_pending() { @@ -105,7 +109,7 @@ impl IoWrite for Writer { n.clear_pending(); n.enable(); } - if DUMMY.fired.get() == true { + if DUMMY.fired.get() { // buffer finished transmitting, return so we can output additional // messages when requested by the panic handler. break; @@ -114,6 +118,7 @@ impl IoWrite for Writer { DUMMY.fired.set(false); }); } + buf.len() } } @@ -123,17 +128,17 @@ impl IoWrite for Writer { #[cfg(not(test))] #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { let led_kernel_pin = &nrf52840::gpio::GPIOPin::new(Pin::P0_13); let led = &mut led::LedLow::new(led_kernel_pin); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led], writer, pi, &cortexm4::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/nano33ble/src/main.rs b/boards/nano33ble/src/main.rs index 7565380cdd..f838dee383 100644 --- a/boards/nano33ble/src/main.rs +++ b/boards/nano33ble/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tock kernel for the Arduino Nano 33 BLE. //! //! It is based on nRF52840 SoC (Cortex M4 core with a BLE + IEEE 802.15.4 transceiver). @@ -8,20 +12,17 @@ #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] -use capsules::virtual_aes_ccm::MuxAES128CCM; +use core::ptr::addr_of; +use core::ptr::addr_of_mut; + use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil::gpio::Configure; -use kernel::hil::gpio::Interrupt; use kernel::hil::gpio::Output; -use kernel::hil::i2c::I2CMaster; use kernel::hil::led::LedLow; -use kernel::hil::symmetric_encryption::AES128; use kernel::hil::time::Counter; use kernel::hil::usb::Client; use kernel::platform::chip::Chip; -use kernel::platform::mpu::MPU; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::scheduler::round_robin::RoundRobinSched; #[allow(unused_imports)] @@ -69,10 +70,10 @@ const APDS9960_PIN: Pin = Pin::P0_19; /// Personal Area Network ID for the IEEE 802.15.4 radio const PAN_ID: u16 = 0xABCD; /// Gateway (or next hop) MAC Address -const DST_MAC_ADDR: capsules::net::ieee802154::MacAddress = - capsules::net::ieee802154::MacAddress::Short(49138); +const DST_MAC_ADDR: capsules_extra::net::ieee802154::MacAddress = + capsules_extra::net::ieee802154::MacAddress::Short(49138); const DEFAULT_CTX_PREFIX_LEN: u8 = 8; //Length of context for 6LoWPAN compression -const DEFAULT_CTX_PREFIX: [u8; 16] = [0x0 as u8; 16]; //Context for 6LoWPAN Compression +const DEFAULT_CTX_PREFIX: [u8; 16] = [0x0_u8; 16]; //Context for 6LoWPAN Compression /// UART Writer for panic!()s. pub mod io; @@ -81,8 +82,8 @@ pub mod io; // to stop the app and print a notice, but not immediately panic. This allows // users to debug their apps, but avoids issues with using the USB/CDC stack // synchronously for panic! too early after the board boots. -const FAULT_RESPONSE: kernel::process::StopWithDebugFaultPolicy = - kernel::process::StopWithDebugFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::StopWithDebugFaultPolicy = + capsules_system::process_policies::StopWithDebugFaultPolicy {}; // Number of concurrent processes this platform supports. const NUM_PROCS: usize = 8; @@ -92,12 +93,13 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] [None; NUM_PROCS]; static mut CHIP: Option<&'static nrf52840::chip::NRF52> = None; -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; static mut CDC_REF_FOR_PANIC: Option< - &'static capsules::usb::cdc::CdcAcm< + &'static capsules_extra::usb::cdc::CdcAcm< 'static, nrf52::usbd::Usbd, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc>, >, > = None; static mut NRF52_POWER: Option<&'static nrf52840::power::Power> = None; @@ -116,37 +118,62 @@ fn baud_rate_reset_bootloader_enter() { } } +type HTS221Sensor = components::hts221::Hts221ComponentType< + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, nrf52840::i2c::TWI<'static>>, +>; +type TemperatureDriver = components::temperature::TemperatureComponentType; +type HumidityDriver = components::humidity::HumidityComponentType; +type Ieee802154MacDevice = components::ieee802154::Ieee802154ComponentMacDeviceType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; +type Ieee802154Driver = components::ieee802154::Ieee802154ComponentType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; +type RngDriver = components::rng::RngComponentType>; + /// Supported drivers by the platform pub struct Platform { - ble_radio: &'static capsules::ble_advertising_driver::BLE< + ble_radio: &'static capsules_extra::ble_advertising_driver::BLE< 'static, nrf52::ble_radio::Radio<'static>, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52::rtc::Rtc<'static>, + >, >, - ieee802154_radio: &'static capsules::ieee802154::RadioDriver<'static>, - console: &'static capsules::console::Console<'static>, - pconsole: &'static capsules::process_console::ProcessConsole< + ieee802154_radio: &'static Ieee802154Driver, + console: &'static capsules_core::console::Console<'static>, + pconsole: &'static capsules_core::process_console::ProcessConsole< 'static, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, + { capsules_core::process_console::DEFAULT_COMMAND_HISTORY_LEN }, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52::rtc::Rtc<'static>, + >, components::process_console::Capability, >, - proximity: &'static capsules::proximity::ProximitySensor<'static>, - temperature: &'static capsules::temperature::TemperatureSensor<'static>, - humidity: &'static capsules::humidity::HumiditySensor<'static>, - gpio: &'static capsules::gpio::GPIO<'static, nrf52::gpio::GPIOPin<'static>>, - led: &'static capsules::led::LedDriver< + proximity: &'static capsules_extra::proximity::ProximitySensor<'static>, + temperature: &'static TemperatureDriver, + humidity: &'static HumidityDriver, + gpio: &'static capsules_core::gpio::GPIO<'static, nrf52::gpio::GPIOPin<'static>>, + led: &'static capsules_core::led::LedDriver< 'static, LedLow<'static, nrf52::gpio::GPIOPin<'static>>, 3, >, - adc: &'static capsules::adc::AdcVirtualized<'static>, - rng: &'static capsules::rng::RngDriver<'static>, - ipc: kernel::ipc::IPC, - alarm: &'static capsules::alarm::AlarmDriver< + adc: &'static capsules_core::adc::AdcVirtualized<'static>, + rng: &'static RngDriver, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52::rtc::Rtc<'static>, + >, >, - udp_driver: &'static capsules::net::udp::UDPDriver<'static>, + udp_driver: &'static capsules_extra::net::udp::UDPDriver<'static>, scheduler: &'static RoundRobinSched<'static>, systick: cortexm4::systick::SysTick, } @@ -157,18 +184,18 @@ impl SyscallDriverLookup for Platform { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::proximity::DRIVER_NUM => f(Some(self.proximity)), - capsules::temperature::DRIVER_NUM => f(Some(self.temperature)), - capsules::humidity::DRIVER_NUM => f(Some(self.humidity)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::adc::DRIVER_NUM => f(Some(self.adc)), - capsules::rng::DRIVER_NUM => f(Some(self.rng)), - capsules::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), - capsules::ieee802154::DRIVER_NUM => f(Some(self.ieee802154_radio)), - capsules::net::udp::DRIVER_NUM => f(Some(self.udp_driver)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_extra::proximity::DRIVER_NUM => f(Some(self.proximity)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temperature)), + capsules_extra::humidity::DRIVER_NUM => f(Some(self.humidity)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_core::rng::DRIVER_NUM => f(Some(self.rng)), + capsules_extra::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), + capsules_extra::ieee802154::DRIVER_NUM => f(Some(self.ieee802154_radio)), + capsules_extra::net::udp::DRIVER_NUM => f(Some(self.udp_driver)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), _ => f(None), } @@ -187,7 +214,7 @@ impl KernelResources &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -213,23 +240,24 @@ impl KernelResources &'static mut Nrf52840DefaultPeripherals<'static> { +pub unsafe fn start() -> ( + &'static kernel::Kernel, + Platform, + &'static nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>, +) { + nrf52840::init(); + + let ieee802154_ack_buf = static_init!( + [u8; nrf52840::ieee802154_radio::ACK_BUF_SIZE], + [0; nrf52840::ieee802154_radio::ACK_BUF_SIZE] + ); + // Initialize chip peripheral drivers let nrf52840_peripherals = static_init!( Nrf52840DefaultPeripherals, - Nrf52840DefaultPeripherals::new() + Nrf52840DefaultPeripherals::new(ieee802154_ack_buf) ); - nrf52840_peripherals -} - -/// Main function called after RAM initialized. -#[no_mangle] -pub unsafe fn main() { - nrf52840::init(); - - let nrf52840_peripherals = get_peripherals(); - // set up circular peripheral dependencies nrf52840_peripherals.init(); let base_peripherals = &nrf52840_peripherals.nrf52; @@ -238,7 +266,7 @@ pub unsafe fn main() { // bootloader. NRF52_POWER = Some(&base_peripherals.pwr_clk); - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); //-------------------------------------------------------------------------- // CAPABILITIES @@ -248,7 +276,6 @@ pub unsafe fn main() { // functions. let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); //-------------------------------------------------------------------------- @@ -270,7 +297,7 @@ pub unsafe fn main() { let gpio = components::gpio::GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( nrf52840::gpio::GPIOPin, 2 => &nrf52840_peripherals.gpio_port[GPIO_D2], @@ -284,31 +311,19 @@ pub unsafe fn main() { 10 => &nrf52840_peripherals.gpio_port[GPIO_D10] ), ) - .finalize(components::gpio_component_buf!(nrf52840::gpio::GPIOPin)); + .finalize(components::gpio_component_static!(nrf52840::gpio::GPIOPin)); //-------------------------------------------------------------------------- // LEDs //-------------------------------------------------------------------------- - let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( LedLow<'static, nrf52840::gpio::GPIOPin>, LedLow::new(&nrf52840_peripherals.gpio_port[LED_RED_PIN]), LedLow::new(&nrf52840_peripherals.gpio_port[LED_GREEN_PIN]), LedLow::new(&nrf52840_peripherals.gpio_port[LED_BLUE_PIN]), )); - //-------------------------------------------------------------------------- - // Deferred Call (Dynamic) Setup - //-------------------------------------------------------------------------- - - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 4], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); - //-------------------------------------------------------------------------- // ALARM & TIMER //-------------------------------------------------------------------------- @@ -317,13 +332,13 @@ pub unsafe fn main() { let _ = rtc.start(); let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) - .finalize(components::alarm_mux_component_helper!(nrf52::rtc::Rtc)); + .finalize(components::alarm_mux_component_static!(nrf52::rtc::Rtc)); let alarm = components::alarm::AlarmDriverComponent::new( board_kernel, - capsules::alarm::DRIVER_NUM, + capsules_core::alarm::DRIVER_NUM, mux_alarm, ) - .finalize(components::alarm_component_helper!(nrf52::rtc::Rtc)); + .finalize(components::alarm_component_static!(nrf52::rtc::Rtc)); //-------------------------------------------------------------------------- // UART & CONSOLE & DEBUG @@ -348,48 +363,49 @@ pub unsafe fn main() { let cdc = components::cdc::CdcAcmComponent::new( &nrf52840_peripherals.usbd, - capsules::usb::cdc::MAX_CTRL_PACKET_SIZE_NRF52840, + capsules_extra::usb::cdc::MAX_CTRL_PACKET_SIZE_NRF52840, 0x2341, 0x005a, strings, mux_alarm, - dynamic_deferred_caller, Some(&baud_rate_reset_bootloader_enter), ) - .finalize(components::usb_cdc_acm_component_helper!( + .finalize(components::cdc_acm_component_static!( nrf52::usbd::Usbd, nrf52::rtc::Rtc )); CDC_REF_FOR_PANIC = Some(cdc); //for use by panic handler // Process Printer for displaying process information. - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); // Create a shared UART channel for the console and for kernel debug. - let uart_mux = components::console::UartMuxComponent::new(cdc, 115200, dynamic_deferred_caller) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(cdc, 115200) + .finalize(components::uart_mux_component_static!()); let pconsole = components::process_console::ProcessConsoleComponent::new( board_kernel, uart_mux, mux_alarm, process_printer, + Some(cortexm4::support::reset), ) - .finalize(components::process_console_component_helper!( + .finalize(components::process_console_component_static!( nrf52::rtc::Rtc<'static> )); // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); //-------------------------------------------------------------------------- // RANDOM NUMBERS @@ -397,10 +413,10 @@ pub unsafe fn main() { let rng = components::rng::RngComponent::new( board_kernel, - capsules::rng::DRIVER_NUM, + capsules_core::rng::DRIVER_NUM, &base_peripherals.trng, ) - .finalize(()); + .finalize(components::rng_component_static!(nrf52840::trng::Trng)); //-------------------------------------------------------------------------- // ADC @@ -408,161 +424,141 @@ pub unsafe fn main() { base_peripherals.adc.calibrate(); let adc_mux = components::adc::AdcMuxComponent::new(&base_peripherals.adc) - .finalize(components::adc_mux_component_helper!(nrf52840::adc::Adc)); + .finalize(components::adc_mux_component_static!(nrf52840::adc::Adc)); let adc_syscall = - components::adc::AdcVirtualComponent::new(board_kernel, capsules::adc::DRIVER_NUM) + components::adc::AdcVirtualComponent::new(board_kernel, capsules_core::adc::DRIVER_NUM) .finalize(components::adc_syscall_component_helper!( // A0 components::adc::AdcComponent::new( - &adc_mux, + adc_mux, nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput2) ) - .finalize(components::adc_component_helper!(nrf52840::adc::Adc)), + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), // A1 components::adc::AdcComponent::new( - &adc_mux, + adc_mux, nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput3) ) - .finalize(components::adc_component_helper!(nrf52840::adc::Adc)), + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), // A2 components::adc::AdcComponent::new( - &adc_mux, + adc_mux, nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput6) ) - .finalize(components::adc_component_helper!(nrf52840::adc::Adc)), + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), // A3 components::adc::AdcComponent::new( - &adc_mux, + adc_mux, nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput5) ) - .finalize(components::adc_component_helper!(nrf52840::adc::Adc)), + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), // A4 components::adc::AdcComponent::new( - &adc_mux, + adc_mux, nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput7) ) - .finalize(components::adc_component_helper!(nrf52840::adc::Adc)), + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), // A5 components::adc::AdcComponent::new( - &adc_mux, + adc_mux, nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput0) ) - .finalize(components::adc_component_helper!(nrf52840::adc::Adc)), + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), // A6 components::adc::AdcComponent::new( - &adc_mux, + adc_mux, nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput4) ) - .finalize(components::adc_component_helper!(nrf52840::adc::Adc)), + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), // A7 components::adc::AdcComponent::new( - &adc_mux, + adc_mux, nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput1) ) - .finalize(components::adc_component_helper!(nrf52840::adc::Adc)), + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), )); //-------------------------------------------------------------------------- // SENSORS //-------------------------------------------------------------------------- - let sensors_i2c_bus = static_init!( - capsules::virtual_i2c::MuxI2C<'static>, - capsules::virtual_i2c::MuxI2C::new(&base_peripherals.twi0, None, dynamic_deferred_caller) - ); - base_peripherals.twi0.configure( + let sensors_i2c_bus = components::i2c::I2CMuxComponent::new(&base_peripherals.twi1, None) + .finalize(components::i2c_mux_component_static!(nrf52840::i2c::TWI)); + base_peripherals.twi1.configure( nrf52840::pinmux::Pinmux::new(I2C_SCL_PIN as u32), nrf52840::pinmux::Pinmux::new(I2C_SDA_PIN as u32), ); - base_peripherals.twi0.set_master_client(sensors_i2c_bus); - let _ = &nrf52840_peripherals.gpio_port[I2C_PULLUP_PIN].make_output(); - let _ = &nrf52840_peripherals.gpio_port[I2C_PULLUP_PIN].set(); + nrf52840_peripherals.gpio_port[I2C_PULLUP_PIN].make_output(); + nrf52840_peripherals.gpio_port[I2C_PULLUP_PIN].set(); - let apds9960_i2c = static_init!( - capsules::virtual_i2c::I2CDevice, - capsules::virtual_i2c::I2CDevice::new(sensors_i2c_bus, 0x39) - ); - - let apds9960 = static_init!( - capsules::apds9960::APDS9960<'static>, - capsules::apds9960::APDS9960::new( - apds9960_i2c, - &nrf52840_peripherals.gpio_port[APDS9960_PIN], - &mut capsules::apds9960::BUFFER - ) - ); - apds9960_i2c.set_client(apds9960); - nrf52840_peripherals.gpio_port[APDS9960_PIN].set_client(apds9960); - - let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - - let proximity = static_init!( - capsules::proximity::ProximitySensor<'static>, - capsules::proximity::ProximitySensor::new( - apds9960, - board_kernel.create_grant(capsules::proximity::DRIVER_NUM, &grant_cap) - ) - ); - - kernel::hil::sensors::ProximityDriver::set_client(apds9960, proximity); + let apds9960 = components::apds9960::Apds9960Component::new( + sensors_i2c_bus, + 0x39, + &nrf52840_peripherals.gpio_port[APDS9960_PIN], + ) + .finalize(components::apds9960_component_static!(nrf52840::i2c::TWI)); + let proximity = components::proximity::ProximityComponent::new( + apds9960, + board_kernel, + capsules_extra::proximity::DRIVER_NUM, + ) + .finalize(components::proximity_component_static!()); let hts221 = components::hts221::Hts221Component::new(sensors_i2c_bus, 0x5f) - .finalize(components::hts221_component_helper!()); + .finalize(components::hts221_component_static!(nrf52840::i2c::TWI)); let temperature = components::temperature::TemperatureComponent::new( board_kernel, - capsules::temperature::DRIVER_NUM, + capsules_extra::temperature::DRIVER_NUM, hts221, ) - .finalize(()); + .finalize(components::temperature_component_static!(HTS221Sensor)); let humidity = components::humidity::HumidityComponent::new( board_kernel, - capsules::humidity::DRIVER_NUM, + capsules_extra::humidity::DRIVER_NUM, hts221, ) - .finalize(()); + .finalize(components::humidity_component_static!(HTS221Sensor)); //-------------------------------------------------------------------------- // WIRELESS //-------------------------------------------------------------------------- - let ble_radio = nrf52_components::BLEComponent::new( + let ble_radio = components::ble::BLEComponent::new( board_kernel, - capsules::ble_advertising_driver::DRIVER_NUM, + capsules_extra::ble_advertising_driver::DRIVER_NUM, &base_peripherals.ble_radio, mux_alarm, ) - .finalize(()); + .finalize(components::ble_component_static!( + nrf52840::rtc::Rtc, + nrf52840::ble_radio::Radio + )); - let aes_mux = static_init!( - MuxAES128CCM<'static, nrf52840::aes::AesECB>, - MuxAES128CCM::new(&base_peripherals.ecb, dynamic_deferred_caller) - ); - base_peripherals.ecb.set_client(aes_mux); - aes_mux.initialize_callback_handle( - dynamic_deferred_caller.register(aes_mux).unwrap(), // Unwrap fail = no deferred call slot available for ccm mux - ); - use capsules::net::ieee802154::MacAddress; - use capsules::virtual_alarm::VirtualMuxAlarm; + use capsules_extra::net::ieee802154::MacAddress; + + let aes_mux = components::ieee802154::MuxAes128ccmComponent::new(&base_peripherals.ecb) + .finalize(components::mux_aes128ccm_component_static!( + nrf52840::aes::AesECB + )); - let serial_num = nrf52840::ficr::FICR_INSTANCE.address(); - let serial_num_bottom_16 = u16::from_le_bytes([serial_num[0], serial_num[1]]); - let src_mac_from_serial_num: MacAddress = MacAddress::Short(serial_num_bottom_16); + let device_id = nrf52840::ficr::FICR_INSTANCE.id(); + let device_id_bottom_16 = u16::from_le_bytes([device_id[0], device_id[1]]); let (ieee802154_radio, mux_mac) = components::ieee802154::Ieee802154Component::new( board_kernel, - capsules::ieee802154::DRIVER_NUM, - &base_peripherals.ieee802154_radio, + capsules_extra::ieee802154::DRIVER_NUM, + &nrf52840_peripherals.ieee802154_radio, aes_mux, PAN_ID, - serial_num_bottom_16, - dynamic_deferred_caller, + device_id_bottom_16, + device_id, ) - .finalize(components::ieee802154_component_helper!( + .finalize(components::ieee802154_component_static!( nrf52840::ieee802154_radio::Radio, nrf52840::aes::AesECB<'static> )); - use capsules::net::ipv6::ip_utils::IPAddr; + use capsules_extra::net::ipv6::ip_utils::IPAddr; let local_ip_ifaces = static_init!( [IPAddr; 3], @@ -575,8 +571,8 @@ pub unsafe fn main() { 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, ]), - IPAddr::generate_from_mac(capsules::net::ieee802154::MacAddress::Short( - serial_num_bottom_16 + IPAddr::generate_from_mac(capsules_extra::net::ieee802154::MacAddress::Short( + device_id_bottom_16 )), ] ); @@ -586,22 +582,25 @@ pub unsafe fn main() { DEFAULT_CTX_PREFIX_LEN, DEFAULT_CTX_PREFIX, DST_MAC_ADDR, - src_mac_from_serial_num, + MacAddress::Short(device_id_bottom_16), local_ip_ifaces, mux_alarm, ) - .finalize(components::udp_mux_component_helper!(nrf52840::rtc::Rtc)); + .finalize(components::udp_mux_component_static!( + nrf52840::rtc::Rtc, + Ieee802154MacDevice + )); // UDP driver initialization happens here let udp_driver = components::udp_driver::UDPDriverComponent::new( board_kernel, - capsules::net::udp::DRIVER_NUM, + capsules_extra::net::udp::DRIVER_NUM, udp_send_mux, udp_recv_mux, udp_port_table, local_ip_ifaces, ) - .finalize(components::udp_driver_component_helper!(nrf52840::rtc::Rtc)); + .finalize(components::udp_driver_component_static!(nrf52840::rtc::Rtc)); //-------------------------------------------------------------------------- // FINAL SETUP AND BOARD BOOT @@ -611,8 +610,8 @@ pub unsafe fn main() { // approach than this. nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); let platform = Platform { ble_radio, @@ -655,12 +654,10 @@ pub unsafe fn main() { //-------------------------------------------------------------------------- // test::linear_log_test::run( // mux_alarm, - // dynamic_deferred_caller, // &nrf52840_peripherals.nrf52.nvmc, // ); // test::log_test::run( // mux_alarm, - // dynamic_deferred_caller, // &nrf52840_peripherals.nrf52.nvmc, // ); @@ -671,7 +668,7 @@ pub unsafe fn main() { // PROCESSES AND MAIN LOOP //-------------------------------------------------------------------------- - /// These symbols are defined in the linker script. + // These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -687,14 +684,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_management_capability, ) @@ -703,5 +700,14 @@ pub unsafe fn main() { debug!("{:?}", err); }); + (board_kernel, platform, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); } diff --git a/boards/nano33ble/src/test/linear_log_test.rs b/boards/nano33ble/src/test/linear_log_test.rs index 9ba1f51f01..76976b8bcb 100644 --- a/boards/nano33ble/src/test/linear_log_test.rs +++ b/boards/nano33ble/src/test/linear_log_test.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tests the log storage interface in linear mode. For testing in circular mode, see //! log_test.rs. //! @@ -11,7 +15,6 @@ //! ```rust //! test::linear_log_test::run( //! mux_alarm, -//! dynamic_deferred_caller, //! &nrf52840_peripherals.nrf52.nvmc, //! ); //! ``` @@ -19,11 +22,11 @@ //! and use the `USER` and `RESET` buttons to manually erase the log and reboot the imix, //! respectively. -use capsules::log; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_extra::log; use core::cell::Cell; +use core::ptr::addr_of_mut; use kernel::debug_verbose; -use kernel::dynamic_deferred_call::DynamicDeferredCall; use kernel::hil::flash; use kernel::hil::log::{LogRead, LogReadClient, LogWrite, LogWriteClient}; use kernel::hil::time::{Alarm, AlarmClient, ConvertTicks}; @@ -39,11 +42,7 @@ use nrf52840::{ // Allocate 8 KiB volume for log storage (the nano33ble page size is 4 KiB). storage_volume!(LINEAR_TEST_LOG, 8); -pub unsafe fn run( - mux_alarm: &'static MuxAlarm<'static, Rtc>, - deferred_caller: &'static DynamicDeferredCall, - flash_controller: &'static Nvmc, -) { +pub unsafe fn run(mux_alarm: &'static MuxAlarm<'static, Rtc>, flash_controller: &'static Nvmc) { // Set up flash controller. flash_controller.configure_writeable(); flash_controller.configure_eraseable(); @@ -52,20 +51,10 @@ pub unsafe fn run( // Create actual log storage abstraction on top of flash. let log = static_init!( Log, - log::Log::new( - &LINEAR_TEST_LOG, - &flash_controller, - pagebuffer, - deferred_caller, - false - ) + log::Log::new(&LINEAR_TEST_LOG, flash_controller, pagebuffer, false) ); flash::HasClient::set_client(flash_controller, log); - log.initialize_callback_handle( - deferred_caller - .register(log) - .expect("no deferred call slot available for log storage"), - ); + kernel::deferred_call::DeferredCallClient::register(log); let alarm = static_init!( VirtualMuxAlarm<'static, Rtc>, @@ -76,7 +65,7 @@ pub unsafe fn run( // Create and run test for log storage. let test = static_init!( LogTest>, - LogTest::new(log, &mut BUFFER, alarm, &TEST_OPS) + LogTest::new(log, &mut *addr_of_mut!(BUFFER), alarm, &TEST_OPS) ); log.set_read_client(test); log.set_append_client(test); @@ -175,7 +164,7 @@ impl> LogTest { } match self.log.read(buffer, buffer.len()) { - Ok(_) => debug_verbose!("Dispatched asynchronous read operation."), + Ok(()) => debug_verbose!("Dispatched asynchronous read operation."), Err((return_code, buffer)) => { self.buffer.replace(buffer); match return_code { diff --git a/boards/nano33ble/src/test/log_test.rs b/boards/nano33ble/src/test/log_test.rs index 13659b91b8..fe9a8674fe 100644 --- a/boards/nano33ble/src/test/log_test.rs +++ b/boards/nano33ble/src/test/log_test.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tests the log storage interface in circular mode. For testing in linear mode, see //! linear_log_test.rs. //! @@ -22,18 +26,17 @@ //! ``` //! test::log_test::run( //! mux_alarm, -//! dynamic_deferred_caller, //! &nrf52840_peripherals.nrf52.nvmc, //! ); //! ``` //! and use the `USER` and `RESET` buttons to manually erase the log and reboot the nano33ble, //! respectively. -use capsules::log; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_extra::log; use core::cell::Cell; +use core::ptr::addr_of_mut; use kernel::debug; -use kernel::dynamic_deferred_call::DynamicDeferredCall; use kernel::hil::flash; use kernel::hil::gpio::{self, Interrupt, InterruptEdge}; use kernel::hil::log::{LogRead, LogReadClient, LogWrite, LogWriteClient}; @@ -51,11 +54,7 @@ use nrf52840::{ // Allocate 16 KiB volume for log storage (the nano33ble page size is 4 KiB). storage_volume!(TEST_LOG, 16); -pub unsafe fn run( - mux_alarm: &'static MuxAlarm<'static, Rtc>, - deferred_caller: &'static DynamicDeferredCall, - flash_controller: &'static Nvmc, -) { +pub unsafe fn run(mux_alarm: &'static MuxAlarm<'static, Rtc>, flash_controller: &'static Nvmc) { // Set up flash controller. flash_controller.configure_writeable(); flash_controller.configure_eraseable(); @@ -64,20 +63,10 @@ pub unsafe fn run( // Create actual log storage abstraction on top of flash. let log = static_init!( Log, - log::Log::new( - &TEST_LOG, - &flash_controller, - pagebuffer, - deferred_caller, - true - ) + log::Log::new(&TEST_LOG, flash_controller, pagebuffer, true) ); flash::HasClient::set_client(flash_controller, log); - log.initialize_callback_handle( - deferred_caller - .register(log) - .expect("no deferred call slot available for log storage"), - ); + kernel::deferred_call::DeferredCallClient::register(log); let alarm = static_init!( VirtualMuxAlarm<'static, Rtc>, @@ -88,7 +77,7 @@ pub unsafe fn run( // Create and run test for log storage. let test = static_init!( LogTest>, - LogTest::new(log, &mut BUFFER, alarm, &TEST_OPS) + LogTest::new(log, &mut *addr_of_mut!(BUFFER), alarm, &TEST_OPS) ); log.set_read_client(test); log.set_append_client(test); @@ -130,7 +119,7 @@ static TEST_OPS: [TestOp; 24] = [ // Try bad seeks, should fail and not change read entry ID. TestOp::Write, TestOp::BadSeek(0), - TestOp::BadSeek(core::usize::MAX), + TestOp::BadSeek(usize::MAX), TestOp::Read, // Try bad write, nothing should change. TestOp::BadWrite, @@ -317,7 +306,7 @@ impl> LogTest { .take() .map( move |buffer| match self.log.read(buffer, buffer.len() + 1) { - Ok(_) => panic!("Read with too-large max read length succeeded unexpectedly!"), + Ok(()) => panic!("Read with too-large max read length succeeded unexpectedly!"), Err((error, original_buffer)) => { self.buffer.replace(original_buffer); assert_eq!(error, ErrorCode::INVAL); @@ -330,7 +319,7 @@ impl> LogTest { self.buffer .take() .map(move |buffer| match self.log.read(buffer, BUFFER_LEN - 1) { - Ok(_) => panic!("Read with too-small buffer succeeded unexpectedly!"), + Ok(()) => panic!("Read with too-small buffer succeeded unexpectedly!"), Err((error, original_buffer)) => { self.buffer.replace(original_buffer); if self.read_val.get() == self.write_val.get() { @@ -372,7 +361,7 @@ impl> LogTest { self.buffer .take() .map(move |buffer| match self.log.append(buffer, 0) { - Ok(_) => panic!("Appending entry of size 0 succeeded unexpectedly!"), + Ok(()) => panic!("Appending entry of size 0 succeeded unexpectedly!"), Err((error, original_buffer)) => { self.buffer.replace(original_buffer); assert_eq!(error, ErrorCode::INVAL); @@ -385,7 +374,7 @@ impl> LogTest { .take() .map( move |buffer| match self.log.append(buffer, buffer.len() + 1) { - Ok(_) => panic!("Appending with too-small buffer succeeded unexpectedly!"), + Ok(()) => panic!("Appending with too-small buffer succeeded unexpectedly!"), Err((error, original_buffer)) => { self.buffer.replace(original_buffer); assert_eq!(error, ErrorCode::INVAL); @@ -396,8 +385,11 @@ impl> LogTest { // Ensure failure if entry is too large to fit within a single flash page. unsafe { - match self.log.append(&mut DUMMY_BUFFER, DUMMY_BUFFER.len()) { - Ok(_) => panic!("Appending with too-small buffer succeeded unexpectedly!"), + match self + .log + .append(&mut *addr_of_mut!(DUMMY_BUFFER), DUMMY_BUFFER.len()) + { + Ok(()) => panic!("Appending with too-small buffer succeeded unexpectedly!"), Err((error, _original_buffer)) => assert_eq!(error, ErrorCode::SIZE), } } diff --git a/boards/nano33ble/src/test/mod.rs b/boards/nano33ble/src/test/mod.rs index eeed6f62a4..8915ab7069 100644 --- a/boards/nano33ble/src/test/mod.rs +++ b/boards/nano33ble/src/test/mod.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + pub(crate) mod linear_log_test; pub(crate) mod log_test; pub(crate) mod virtual_rng_test; diff --git a/boards/nano33ble/src/test/virtual_rng_test.rs b/boards/nano33ble/src/test/virtual_rng_test.rs index 3b4a4e741b..61432c0754 100644 --- a/boards/nano33ble/src/test/virtual_rng_test.rs +++ b/boards/nano33ble/src/test/virtual_rng_test.rs @@ -1,40 +1,43 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test file for the virtual_rng //! To run this test, include the code //! ``` //! test::virtual_rng_test::run(&base_peripherals.trng); //! ``` -use capsules::rng; -use capsules::test::virtual_rng::TestRng; -use kernel::hil::entropy::Entropy32; +use capsules_core::rng; +use capsules_core::test::virtual_rng::TestRng; use kernel::hil::rng::Rng; use kernel::{debug, static_init}; -pub unsafe fn run(trng: &'static dyn Entropy32<'static>) { +pub unsafe fn run(trng: &'static nrf52840::trng::Trng) { debug!("Starting virtual_rng get tests:"); let rng_obj = static_init!( - rng::Entropy32ToRandom<'static>, + rng::Entropy32ToRandom<'static, nrf52840::trng::Trng>, rng::Entropy32ToRandom::new(trng) ); // Create virtual rng mux device let mux = static_init!( - capsules::virtual_rng::MuxRngMaster<'static>, - capsules::virtual_rng::MuxRngMaster::new(rng_obj) + capsules_core::virtualizers::virtual_rng::MuxRngMaster<'static>, + capsules_core::virtualizers::virtual_rng::MuxRngMaster::new(rng_obj) ); // Create all devices for the virtual rng let device1 = static_init!( - capsules::virtual_rng::VirtualRngMasterDevice<'static>, - capsules::virtual_rng::VirtualRngMasterDevice::new(mux) + capsules_core::virtualizers::virtual_rng::VirtualRngMasterDevice<'static>, + capsules_core::virtualizers::virtual_rng::VirtualRngMasterDevice::new(mux) ); let device2 = static_init!( - capsules::virtual_rng::VirtualRngMasterDevice<'static>, - capsules::virtual_rng::VirtualRngMasterDevice::new(mux) + capsules_core::virtualizers::virtual_rng::VirtualRngMasterDevice<'static>, + capsules_core::virtualizers::virtual_rng::VirtualRngMasterDevice::new(mux) ); let device3 = static_init!( - capsules::virtual_rng::VirtualRngMasterDevice<'static>, - capsules::virtual_rng::VirtualRngMasterDevice::new(mux) + capsules_core::virtualizers::virtual_rng::VirtualRngMasterDevice<'static>, + capsules_core::virtualizers::virtual_rng::VirtualRngMasterDevice::new(mux) ); // Create independent tests for each device diff --git a/boards/nano33ble_rev2/Cargo.toml b/boards/nano33ble_rev2/Cargo.toml new file mode 100644 index 0000000000..c766df9871 --- /dev/null +++ b/boards/nano33ble_rev2/Cargo.toml @@ -0,0 +1,25 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +[package] +name = "nano33ble_rev2" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true + +[dependencies] +cortexm4 = { path = "../../arch/cortex-m4" } +kernel = { path = "../../kernel" } +nrf52 = { path = "../../chips/nrf52" } +nrf52840 = { path = "../../chips/nrf52840" } +components = { path = "../components" } +nrf52_components = { path = "../nordic/nrf52_components" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/nano33ble_rev2/Makefile b/boards/nano33ble_rev2/Makefile new file mode 100644 index 0000000000..f5d3a52c22 --- /dev/null +++ b/boards/nano33ble_rev2/Makefile @@ -0,0 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +# Makefile for building the tock kernel for the Arduino Nano 33 BLE Sense Rev2 board. + +TOCK_ARCH=cortex-m4 +TARGET=thumbv7em-none-eabi +PLATFORM=nano33ble_rev2 + +include ../Makefile.common + +ifdef PORT + FLAGS += --port $(PORT) +endif + +# Default target for installing the kernel. +.PHONY: install +install: program + +# Upload the kernel using tockloader and the tock bootloader +.PHONY: program +program: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin + tockloader $(FLAGS) flash --address 0x10000 $< diff --git a/boards/nano33ble_rev2/README.md b/boards/nano33ble_rev2/README.md new file mode 100644 index 0000000000..f153b65358 --- /dev/null +++ b/boards/nano33ble_rev2/README.md @@ -0,0 +1,282 @@ +Arduino Nano 33 BLE Sense Rev2 +=================== + + + +The [Arduino Nano 33 BLE Sense Rev2](https://store.arduino.cc/products/nano-33-ble-sense-rev2) is a +compact board based on the Nordic nRF52840 SoC. It includes the following sensors: + +- 3 axis geomagnetic sensor +- 6 axis inertial sensor +- humidity and temperature sensor +- barometric sensor +- microphone +- gesture, proximity, light color and light intensity sensor + + +## Getting Started + +First, follow the [Tock Getting Started guide](../../doc/Getting_Started.md). + +The Nano 33 comes pre-installed with a +[version](https://github.com/arduino/ArduinoCore-nRF528x-mbedos/tree/master/bootloaders/nano33ble) +of the BOSSA bootloader that Arduino uses on various boards. Unfortunately this +bootloader is not well suited for Tock development. Specifically, it doesn't +support reading from the board, so there is no way to automatically determine +what type of board it is or what is already installed. It's also [not open +source](https://github.com/arduino/ArduinoCore-nRF528x-mbedos/issues/23) (at +least as of December 2020). + +For Tock development we need to replace the bootloader with the [Tock +Bootloader](https://github.com/tock/tock-bootloader). The Tock bootloader +allows a lot more flexibility with reading and writing the board, and is also +implemented on top of Tock itself. + +This guide will walk through how to install the Tock bootloader, and describe +what is happening along the way. Our goal is to get the Tock bootloader flashed +to the nRF52840 at address 0x0 (overwriting the bossa bootloader). The bossa +bootloader does not have a mechanism for updating itself, however, so we have to +do this in a bit of a roundabout manner. + +We also have a guide for restoring the BOSSA bootloader in case you want to go +back. + +1. The first step is you will need the bossac tool. This tool is required to use + the existing bootloader the board ships with. + + You can compile this tool from source: + + ```shell + $ git clone https://github.com/arduino/BOSSA + $ cd BOSSA + $ make bossac + ``` + + Then you will need to add `BOSSA/bin` to your `$PATH` variable so that your + system can find the `bossac` program. + +2. Next we will use the bossa bootloader to load a copy of the Tock bootloader. + The bossa bootloader expects that all application code (i.e. not the + bootloader) starts at address 0x10000. That is, when the bootloader finishes + it starts executing at address 0x10000. + + So, we will load a copy of the Tock bootloader to address 0x10000. That also + means we need a version of the bootloader compiled to run at address + 0x10000. This bootloader has already been compiled for you. + + To load the first Tock bootloader ensure the Nano 33 is in bootloader mode + by double pressing the reset button (the light should pulse), and then: + + ```shell + $ bossac -e -w bootloaders/tock-bootloader.nano33ble.v1.1.0.0x10000.bin + ``` + + Now the board should boot into the Tock bootloader. When the Tock bootloader + is active it will turn on the "ON" LED. It will not pulse the yellow LED. + + You can test that this step worked by using tockloader. A simple test is to + run: + + ``` + $ tockloader info + ``` + + You should see various properties of the board. This indicates that tockloader + is able to communicate with the temporary bootloader. + +3. Now we use the temporary Tock bootloader to flash the real one + at address 0x0. When the board boots it should run the Tock bootloader + flashed at address 0x10000. To then flash the correct bootloader, run: + + ```shell + $ tockloader flash bootloaders/tock-bootloader.nano33ble.v1.1.0.0x00000.bin --address 0 + ``` + +4. At this point we have two copies of the Tock bootloader installed: the intended + one at address 0x10000 and the temporary one at address 0x0. By default, the first bootloader + will jump to the second, and the second will continue running if no kernel is installed. + To reduce confusion, we can remove the second bootloader. To do this, we will overwrite + the start of the bootloader with zeros. + + First, double tap the reset button. This will cause the first bootloader to stay + active. You should see the "ON" LED on. + + Then, overwrite the second bootloader: + + ```shell + $ tockloader write 0x10000 512 0 + ``` + +5. That's it! You now have the Tock bootloader. All `tockloader` commands should + now work. + + You can test Tockloader by running: + + ```shell + $ tockloader info + ``` + + You should see various properties of the board displayed. + + + +## Programming the Kernel + +You should be able to simply run `make program` in this directory +to install a fresh kernel. + +``` +$ make program +``` + +## Programming Applications + +After building an application, you can use `tockloader install` to install it. + +For example: + +```shell +$ cd libtock-c/examples/blink +$ make +$ tockloader install +``` + +### Userspace Resource Mapping + +This table shows the mappings between resources available in userspace +and the physical elements on the Nano 33 BLE board. + +| Software Resource | Physical Element | +|-------------------|---------------------| +| GPIO[2] | Pin D2 | +| GPIO[3] | Pin D3 | +| GPIO[4] | Pin D4 | +| GPIO[5] | Pin D5 | +| GPIO[6] | Pin D6 | +| GPIO[7] | Pin D7 | +| GPIO[8] | Pin D8 | +| GPIO[9] | Pin D9 | +| GPIO[10] | Pin D10 | +| LED[0] | Tri-color LED Red | +| LED[1] | Tri-color LED Green | +| LED[2] | Tri-color LED Blue | + +## Debugging + +The Nano 33 board uses a virtual serial console over USB to send debugging info +from the kernel and print messages from applications. You can use whatever your +favorite serial terminal program is to view the output. Tockloader also +supports reading and writing to a serial console with `tockloader listen`. + +### Kernel Panics + +If the kernel or an app encounter a `panic!()`, the panic handler specified in +`io.rs` is called. This causes the kernel to stop. You will notice the yellow +LED starts blinking in a repeating but slightly irregular pattern. There is also +a panic print out that provides a fair bit of debugging information. That panic +output is output over the USB CDC connection and so should be visible as part of +the output of `tockloader listen`, however if your kernel panics so early that +the USB connection has not yet been established you will be unable to view any +panic output. In this case, you can modify the panic handler to instead output +panic information over the UART pins, but you will have to separately interface +with the UART pins on the board in order to observe the serial output. + +## Factory Reset + +To restore the BOSSA bootloader we can largely reverse the steps used to install +the Tock bootloader. + +1. First we need to install a temporary copy of the Tock bootloader. + + ```shell + $ tockloader flash bootloaders/tock-bootloader.nano33ble.v1.1.0.0x10000.bin --address 0x10000 + ``` + +2. Now we can restore the BOSSA bootloader. + + ```shell + $ wget https://github.com/arduino/ArduinoCore-nRF528x-mbedos/raw/00ce64c29c4c2e139335b930fe693a00363936aa/bootloaders/nano33ble/bootloader.bin + $ tockloader flash bootloader.bin --address 0x0 + ``` + + Double clicking reset should enter the bossa bootloader now. + +### Using JTAG to flash the Nano 33 BLE + +When flashing bootloaders there is some risk of corrupting the bootloader such +that tools like Tockloader and BOSSA can no longer program the board. We try to +prevent this, but it can happen. To restore the bootloader you must use the JTAG +connection and an external JTAG programmer to flash the nRF52840 with a +bootloader. + +One method for doing this requires a nRF52840dk (the PCA10056 board from +Nordic). This development board includes on-board JTAG hardware that can flash +an external chip. You will need: + +- The + [nRF52840dk](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK). +- Three 0.1" pitch square-head male-to-female jumper wires ([example from + Pololu](https://www.pololu.com/category/67/male-female-premium-jumper-wires)). +- Three 0.1" pitch square-head jumper wires. Can have any ends. +- One 0.1" jumper or female-female jumper wire. + +On the bottom of the Nano 33 BLE are five circular pads underneath the BLE radio. +When looking at the BOTTOM, they are: + +``` +3V3 --> • • <-- RESET + +SWCLK --> • + +GND --> • • <-- SWDIO +``` + +The nRF52840dk has a header P20 which can be used for JTAG programming an +external chip. Here are the connections I made: + +1. Jumper P20 pin 2 (VDD nRF) to P20 pin 3 (VTG). This enables external + programming. +2. Connect a M/F jumper to P20 pin 4 (SWDIO). +3. Connect a M/F jumper to P20 pin 5 (SDDCLK). +4. Connect a M/F jumper to P20 pin 7 (RESET). +5. Plug a USB cable into the Nano 33 BLE and connect it to your computer. This + will power the Nano 33 BLE and provide ground. + +Now take the male end of the three jumpers you just connected, along with three +other 0.1" jumpers, and form them in a 2x3 bundle that matches the pad mapping +on the back of the Nano 33 BLE. I used tape to hold the 2x3 bundle together. You +will use the male pins and press them onto the pads to connect to the Nano 33 +BLE. The three other jumpers are just for spacing to make it easier to connect +the wires which matter. These jumpers do not actually have to make contact with +the Nano 33 BLE. + +Make sure that SW6 on the nRF52840dk is on "DEFAULT", and SW8 is on "ON". + +Now to flash a bootloader on to the Nano 33 BLE: + +1. Prepare your terminal. Go to `/boards/nano33ble` and enter (but do not run) + `tockloader flash --address 0 + bootloaders/tock-bootloader.nano33ble.v1.1.0.0x00000.bin --board nrf52dk + --jlink --debug`. +2. Ensure the LED5 on the nRF52840dk is on (it might flash a bit, that is OK). +3. Hold the jumper bundle against the JTAG pads on Nano 33 BLE. This can be a + bit tricky, but your goal is to get all the pins to make contact with the + pads. +4. Press [enter] on your terminal to run the command. You want to see output + like `Verify successful.` and other messages suggesting the nRF52840dk was + able to flash the chip. If you see `Connecting to target via SWD Cannot + connect to target.` that means it did NOT work. You will need to keep + adjusting the pins to make sure they make contact with the Nano 33 BLE. Also, + make sure LED5 on the nRF52840dk remains on. This LED might go off if things + get connected weird. To reset things switch SW8 to OFF and then back to ON. + +Assuming everything goes correctly you should have restored the bootloader! You +can now use tockloader as normal. + +#### Related resources + +I used a couple pages to help figure all of this out in January 2021: + +- https://devzone.nordicsemi.com/f/nordic-q-a/55203/programming-external-board-bc832-with-nrf52840-dk +- https://devzone.nordicsemi.com/f/nordic-q-a/20536/programming-external-custom-nrf52832-board-using-nrf52-dk +- https://devzone.nordicsemi.com/f/nordic-q-a/32661/external-programming-with-nrf5-dk-p20 diff --git a/boards/nano33ble_rev2/bootloaders/README.md b/boards/nano33ble_rev2/bootloaders/README.md new file mode 100644 index 0000000000..3035ec966a --- /dev/null +++ b/boards/nano33ble_rev2/bootloaders/README.md @@ -0,0 +1,23 @@ +Nano 33 BLE Bootloader Binaries +=============================== + +This folder contains pre-built bootloader binaries for the Nano 33 BLE board. +The file names contain information about which bootloader it is and which +address it is compiled for. + +Tock Bootloader Is Open Source +------------------------------ + +While we provide pre-built binaries for your convenience, you can build the +bootloader yourself. + +``` +$ git clone https://github.com/tock/tock-bootloader +$ cd tock-bootloader +$ git checkout v1.1.0 # Choose a version, or use the latest commit +$ cd boards/nano33ble-bootloader +$ make +``` + +There are instructions in the nano33ble bootloader board about compiling for +different addresses and retrieving the `.bin` file. diff --git a/boards/nano33ble_rev2/layout.ld b/boards/nano33ble_rev2/layout.ld new file mode 100644 index 0000000000..d5950e39c4 --- /dev/null +++ b/boards/nano33ble_rev2/layout.ld @@ -0,0 +1,14 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + +MEMORY +{ + rom (rx) : ORIGIN = 0x00010000, LENGTH = 256K + prog (rx) : ORIGIN = 0x00050000, LENGTH = 704K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 256K +} + +PAGE_SIZE = 4K; + +INCLUDE ../kernel_layout.ld diff --git a/boards/nano33ble_rev2/src/io.rs b/boards/nano33ble_rev2/src/io.rs new file mode 100644 index 0000000000..a9ec81faca --- /dev/null +++ b/boards/nano33ble_rev2/src/io.rs @@ -0,0 +1,145 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +use core::fmt::Write; +use core::panic::PanicInfo; +use core::ptr::{addr_of, addr_of_mut}; + +use cortexm4; +use kernel::debug; +use kernel::debug::IoWrite; +use kernel::hil::led; +use kernel::hil::uart::{self}; +use kernel::ErrorCode; +use nrf52840::gpio::Pin; + +use crate::CHIP; +use crate::PROCESSES; +use crate::PROCESS_PRINTER; +use kernel::hil::uart::Transmit; +use kernel::utilities::cells::VolatileCell; + +struct Writer { + initialized: bool, +} + +static mut WRITER: Writer = Writer { initialized: false }; + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> ::core::fmt::Result { + self.write(s.as_bytes()); + Ok(()) + } +} + +const BUF_LEN: usize = 512; +static mut STATIC_PANIC_BUF: [u8; BUF_LEN] = [0; BUF_LEN]; + +static mut DUMMY: DummyUsbClient = DummyUsbClient { + fired: VolatileCell::new(false), +}; + +struct DummyUsbClient { + fired: VolatileCell, +} + +impl uart::TransmitClient for DummyUsbClient { + fn transmitted_buffer(&self, _: &'static mut [u8], _: usize, _: Result<(), ErrorCode>) { + self.fired.set(true); + } +} + +impl IoWrite for Writer { + fn write(&mut self, buf: &[u8]) -> usize { + if !self.initialized { + self.initialized = true; + } + // Here we mimic a synchronous UART output by calling transmit_buffer + // on the CDC stack and then spinning on USB interrupts until the transaction + // is complete. If the USB or CDC stack panicked, this may fail. It will also + // fail if the panic occurred prior to the USB connection being initialized. + // In the latter case, the LEDs should still blink in the panic pattern. + + // spin so that if any USB DMA is ongoing it will finish + // we should only need this on the first call to write() + let mut i = 0; + loop { + i += 1; + cortexm4::support::nop(); + if i > 10000 { + break; + } + } + + // copy_from_slice() requires equal length slices + // This will truncate any writes longer than BUF_LEN, but simplifies the + // code. In practice, BUF_LEN=512 always seems sufficient for the size of + // individual calls to write made by the panic handler. + let mut max = BUF_LEN; + if buf.len() < BUF_LEN { + max = buf.len(); + } + + unsafe { + // If CDC_REF_FOR_PANIC is not yet set we panicked very early, + // and not much we can do. Don't want to double fault, + // so just return. + super::CDC_REF_FOR_PANIC.map(|cdc| { + // Lots of unsafe dereferencing of global static mut objects here. + // However, this should be okay, because it all happens within + // a single thread, and: + // - This is the only place the global CDC_REF_FOR_PANIC is used, the logic is the same + // as applies for the global CHIP variable used in the panic handler. + // - We do create multiple mutable references to the STATIC_PANIC_BUF, but we never + // access the STATIC_PANIC_BUF after a slice of it is passed to transmit_buffer + // until the slice has been returned in the uart callback. + // - Similarly, only this function uses the global DUMMY variable, and we do not + // mutate it. + let usb = &mut cdc.controller(); + STATIC_PANIC_BUF[..max].copy_from_slice(&buf[..max]); + let static_buf = &mut *addr_of_mut!(STATIC_PANIC_BUF); + cdc.set_transmit_client(&*addr_of!(DUMMY)); + let _ = cdc.transmit_buffer(static_buf, max); + loop { + if let Some(interrupt) = cortexm4::nvic::next_pending() { + if interrupt == 39 { + usb.handle_interrupt(); + } + let n = cortexm4::nvic::Nvic::new(interrupt); + n.clear_pending(); + n.enable(); + } + if DUMMY.fired.get() { + // buffer finished transmitting, return so we can output additional + // messages when requested by the panic handler. + break; + } + } + DUMMY.fired.set(false); + }); + } + buf.len() + } +} + +/// Default panic handler for the Nano 33 Board. +/// +/// We just use the standard default provided by the debug module in the kernel. +#[cfg(not(test))] +#[no_mangle] +#[panic_handler] +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { + let led_kernel_pin = &nrf52840::gpio::GPIOPin::new(Pin::P0_13); + let led = &mut led::LedLow::new(led_kernel_pin); + let writer = &mut *addr_of_mut!(WRITER); + debug::panic( + &mut [led], + writer, + pi, + &cortexm4::support::nop, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), + ) +} diff --git a/boards/nano33ble_rev2/src/main.rs b/boards/nano33ble_rev2/src/main.rs new file mode 100644 index 0000000000..521ddbfc13 --- /dev/null +++ b/boards/nano33ble_rev2/src/main.rs @@ -0,0 +1,736 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Tock kernel for the Arduino Nano 33 BLE Sense Rev2. +//! +//! It is based on nRF52840 SoC (Cortex M4 core with a BLE + IEEE 802.15.4 transceiver). + +#![no_std] +// Disable this attribute when documenting, as a workaround for +// https://github.com/rust-lang/rust/issues/62184. +#![cfg_attr(not(doc), no_main)] +#![deny(missing_docs)] + +use core::ptr::addr_of; +use core::ptr::addr_of_mut; + +use kernel::capabilities; +use kernel::component::Component; +use kernel::hil::gpio::Configure; +use kernel::hil::gpio::Output; +use kernel::hil::led::LedLow; +use kernel::hil::time::Counter; +use kernel::hil::usb::Client; +use kernel::platform::chip::Chip; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::scheduler::round_robin::RoundRobinSched; +#[allow(unused_imports)] +use kernel::{create_capability, debug, debug_gpio, debug_verbose, static_init}; + +use nrf52840::gpio::Pin; +use nrf52840::interrupt_service::Nrf52840DefaultPeripherals; + +// Three-color LED. +const LED_RED_PIN: Pin = Pin::P0_24; +const LED_GREEN_PIN: Pin = Pin::P0_16; +const LED_BLUE_PIN: Pin = Pin::P0_06; + +const LED_KERNEL_PIN: Pin = Pin::P0_13; + +const _BUTTON_RST_PIN: Pin = Pin::P0_18; + +const GPIO_D2: Pin = Pin::P1_11; +const GPIO_D3: Pin = Pin::P1_12; +const GPIO_D4: Pin = Pin::P1_15; +const GPIO_D5: Pin = Pin::P1_13; +const GPIO_D6: Pin = Pin::P1_14; +const GPIO_D7: Pin = Pin::P0_23; +const GPIO_D8: Pin = Pin::P0_21; +const GPIO_D9: Pin = Pin::P0_27; +const GPIO_D10: Pin = Pin::P1_02; + +const _UART_TX_PIN: Pin = Pin::P1_03; +const _UART_RX_PIN: Pin = Pin::P1_10; + +/// I2C pins for all of the sensors. +const I2C_SDA_PIN: Pin = Pin::P0_14; +const I2C_SCL_PIN: Pin = Pin::P0_15; + +/// GPIO tied to the VCC of the I2C pullup resistors. +const I2C_PULLUP_PIN: Pin = Pin::P1_00; + +/// Interrupt pin for the APDS9960 sensor. +const APDS9960_PIN: Pin = Pin::P0_19; + +// Constants related to the configuration of the 15.4 network stack +/// Personal Area Network ID for the IEEE 802.15.4 radio +const PAN_ID: u16 = 0xABCD; +/// Gateway (or next hop) MAC Address +const DST_MAC_ADDR: capsules_extra::net::ieee802154::MacAddress = + capsules_extra::net::ieee802154::MacAddress::Short(49138); +const DEFAULT_CTX_PREFIX_LEN: u8 = 8; //Length of context for 6LoWPAN compression +const DEFAULT_CTX_PREFIX: [u8; 16] = [0x0_u8; 16]; //Context for 6LoWPAN Compression + +/// UART Writer for panic!()s. +pub mod io; + +// How should the kernel respond when a process faults. For this board we choose +// to stop the app and print a notice, but not immediately panic. This allows +// users to debug their apps, but avoids issues with using the USB/CDC stack +// synchronously for panic! too early after the board boots. +const FAULT_RESPONSE: capsules_system::process_policies::StopWithDebugFaultPolicy = + capsules_system::process_policies::StopWithDebugFaultPolicy {}; + +// Number of concurrent processes this platform supports. +const NUM_PROCS: usize = 8; + +// State for loading and holding applications. +static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = + [None; NUM_PROCS]; + +static mut CHIP: Option<&'static nrf52840::chip::NRF52> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; +static mut CDC_REF_FOR_PANIC: Option< + &'static capsules_extra::usb::cdc::CdcAcm< + 'static, + nrf52::usbd::Usbd, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc>, + >, +> = None; +static mut NRF52_POWER: Option<&'static nrf52840::power::Power> = None; + +/// Dummy buffer that causes the linker to reserve enough space for the stack. +#[no_mangle] +#[link_section = ".stack_buffer"] +pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; + +// Function for the CDC/USB stack to use to enter the bootloader. +fn baud_rate_reset_bootloader_enter() { + unsafe { + // 0x90 is the magic value the bootloader expects + NRF52_POWER.unwrap().set_gpregret(0x90); + cortexm4::scb::reset(); + } +} + +type HS3003Sensor = components::hs3003::Hs3003ComponentType< + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, nrf52840::i2c::TWI<'static>>, +>; +type TemperatureDriver = components::temperature::TemperatureComponentType; +type HumidityDriver = components::humidity::HumidityComponentType; +type Ieee802154MacDevice = components::ieee802154::Ieee802154ComponentMacDeviceType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; +type Ieee802154Driver = components::ieee802154::Ieee802154ComponentType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; +type RngDriver = components::rng::RngComponentType>; + +/// Supported drivers by the platform +pub struct Platform { + ble_radio: &'static capsules_extra::ble_advertising_driver::BLE< + 'static, + nrf52::ble_radio::Radio<'static>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52::rtc::Rtc<'static>, + >, + >, + ieee802154_radio: &'static Ieee802154Driver, + console: &'static capsules_core::console::Console<'static>, + pconsole: &'static capsules_core::process_console::ProcessConsole< + 'static, + { capsules_core::process_console::DEFAULT_COMMAND_HISTORY_LEN }, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52::rtc::Rtc<'static>, + >, + components::process_console::Capability, + >, + proximity: &'static capsules_extra::proximity::ProximitySensor<'static>, + pressure: &'static capsules_extra::pressure::PressureSensor< + 'static, + capsules_extra::lps22hb::Lps22hb< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice< + 'static, + nrf52840::i2c::TWI<'static>, + >, + >, + >, + temperature: &'static TemperatureDriver, + humidity: &'static HumidityDriver, + gpio: &'static capsules_core::gpio::GPIO<'static, nrf52::gpio::GPIOPin<'static>>, + led: &'static capsules_core::led::LedDriver< + 'static, + LedLow<'static, nrf52::gpio::GPIOPin<'static>>, + 3, + >, + adc: &'static capsules_core::adc::AdcVirtualized<'static>, + rng: &'static RngDriver, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + alarm: &'static capsules_core::alarm::AlarmDriver< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52::rtc::Rtc<'static>, + >, + >, + udp_driver: &'static capsules_extra::net::udp::UDPDriver<'static>, + scheduler: &'static RoundRobinSched<'static>, + systick: cortexm4::systick::SysTick, +} + +impl SyscallDriverLookup for Platform { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_extra::proximity::DRIVER_NUM => f(Some(self.proximity)), + capsules_extra::pressure::DRIVER_NUM => f(Some(self.pressure)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temperature)), + capsules_extra::humidity::DRIVER_NUM => f(Some(self.humidity)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_core::rng::DRIVER_NUM => f(Some(self.rng)), + capsules_extra::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), + capsules_extra::ieee802154::DRIVER_NUM => f(Some(self.ieee802154_radio)), + capsules_extra::net::udp::DRIVER_NUM => f(Some(self.udp_driver)), + kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), + _ => f(None), + } + } +} + +impl KernelResources>> + for Platform +{ + type SyscallDriverLookup = Self; + type SyscallFilter = (); + type ProcessFault = (); + type Scheduler = RoundRobinSched<'static>; + type SchedulerTimer = cortexm4::systick::SysTick; + type WatchDog = (); + type ContextSwitchCallback = (); + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + &() + } + fn process_fault(&self) -> &Self::ProcessFault { + &() + } + fn scheduler(&self) -> &Self::Scheduler { + self.scheduler + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + &self.systick + } + fn watchdog(&self) -> &Self::WatchDog { + &() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + &() + } +} + +/// This is in a separate, inline(never) function so that its stack frame is +/// removed when this function returns. Otherwise, the stack space used for +/// these static_inits is wasted. +#[inline(never)] +pub unsafe fn start() -> ( + &'static kernel::Kernel, + Platform, + &'static nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>, +) { + nrf52840::init(); + + let ieee802154_ack_buf = static_init!( + [u8; nrf52840::ieee802154_radio::ACK_BUF_SIZE], + [0; nrf52840::ieee802154_radio::ACK_BUF_SIZE] + ); + + // Initialize chip peripheral drivers + let nrf52840_peripherals = static_init!( + Nrf52840DefaultPeripherals, + Nrf52840DefaultPeripherals::new(ieee802154_ack_buf) + ); + + // set up circular peripheral dependencies + nrf52840_peripherals.init(); + let base_peripherals = &nrf52840_peripherals.nrf52; + + // Save a reference to the power module for resetting the board into the + // bootloader. + NRF52_POWER = Some(&base_peripherals.pwr_clk); + + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); + + //-------------------------------------------------------------------------- + // CAPABILITIES + //-------------------------------------------------------------------------- + + // Create capabilities that the board needs to call certain protected kernel + // functions. + let process_management_capability = + create_capability!(capabilities::ProcessManagementCapability); + let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); + + //-------------------------------------------------------------------------- + // DEBUG GPIO + //-------------------------------------------------------------------------- + + // Configure kernel debug GPIOs as early as possible. These are used by the + // `debug_gpio!(0, toggle)` macro. We configure these early so that the + // macro is available during most of the setup code and kernel execution. + kernel::debug::assign_gpios( + Some(&nrf52840_peripherals.gpio_port[LED_KERNEL_PIN]), + None, + None, + ); + + //-------------------------------------------------------------------------- + // GPIO + //-------------------------------------------------------------------------- + + let gpio = components::gpio::GpioComponent::new( + board_kernel, + capsules_core::gpio::DRIVER_NUM, + components::gpio_component_helper!( + nrf52840::gpio::GPIOPin, + 2 => &nrf52840_peripherals.gpio_port[GPIO_D2], + 3 => &nrf52840_peripherals.gpio_port[GPIO_D3], + 4 => &nrf52840_peripherals.gpio_port[GPIO_D4], + 5 => &nrf52840_peripherals.gpio_port[GPIO_D5], + 6 => &nrf52840_peripherals.gpio_port[GPIO_D6], + 7 => &nrf52840_peripherals.gpio_port[GPIO_D7], + 8 => &nrf52840_peripherals.gpio_port[GPIO_D8], + 9 => &nrf52840_peripherals.gpio_port[GPIO_D9], + 10 => &nrf52840_peripherals.gpio_port[GPIO_D10] + ), + ) + .finalize(components::gpio_component_static!(nrf52840::gpio::GPIOPin)); + + //-------------------------------------------------------------------------- + // LEDs + //-------------------------------------------------------------------------- + + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( + LedLow<'static, nrf52840::gpio::GPIOPin>, + LedLow::new(&nrf52840_peripherals.gpio_port[LED_RED_PIN]), + LedLow::new(&nrf52840_peripherals.gpio_port[LED_GREEN_PIN]), + LedLow::new(&nrf52840_peripherals.gpio_port[LED_BLUE_PIN]), + )); + + //-------------------------------------------------------------------------- + // ALARM & TIMER + //-------------------------------------------------------------------------- + + let rtc = &base_peripherals.rtc; + let _ = rtc.start(); + + let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) + .finalize(components::alarm_mux_component_static!(nrf52::rtc::Rtc)); + let alarm = components::alarm::AlarmDriverComponent::new( + board_kernel, + capsules_core::alarm::DRIVER_NUM, + mux_alarm, + ) + .finalize(components::alarm_component_static!(nrf52::rtc::Rtc)); + + //-------------------------------------------------------------------------- + // UART & CONSOLE & DEBUG + //-------------------------------------------------------------------------- + + // Setup the CDC-ACM over USB driver that we will use for UART. + // We use the Arduino Vendor ID and Product ID since the device is the same. + + // Create the strings we include in the USB descriptor. We use the hardcoded + // DEVICEADDR register on the nRF52 to set the serial number. + let serial_number_buf = static_init!([u8; 17], [0; 17]); + let serial_number_string: &'static str = + nrf52::ficr::FICR_INSTANCE.address_str(serial_number_buf); + let strings = static_init!( + [&str; 3], + [ + "Arduino", // Manufacturer + "Nano 33 BLE Sense Rev2 - TockOS", // Product + serial_number_string, // Serial number + ] + ); + + let cdc = components::cdc::CdcAcmComponent::new( + &nrf52840_peripherals.usbd, + capsules_extra::usb::cdc::MAX_CTRL_PACKET_SIZE_NRF52840, + 0x2341, + 0x005a, + strings, + mux_alarm, + Some(&baud_rate_reset_bootloader_enter), + ) + .finalize(components::cdc_acm_component_static!( + nrf52::usbd::Usbd, + nrf52::rtc::Rtc + )); + CDC_REF_FOR_PANIC = Some(cdc); //for use by panic handler + + // Process Printer for displaying process information. + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); + PROCESS_PRINTER = Some(process_printer); + + // Create a shared UART channel for the console and for kernel debug. + let uart_mux = components::console::UartMuxComponent::new(cdc, 115200) + .finalize(components::uart_mux_component_static!()); + + let pconsole = components::process_console::ProcessConsoleComponent::new( + board_kernel, + uart_mux, + mux_alarm, + process_printer, + Some(cortexm4::support::reset), + ) + .finalize(components::process_console_component_static!( + nrf52::rtc::Rtc<'static> + )); + + // Setup the console. + let console = components::console::ConsoleComponent::new( + board_kernel, + capsules_core::console::DRIVER_NUM, + uart_mux, + ) + .finalize(components::console_component_static!()); + // Create the debugger object that handles calls to `debug!()`. + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); + + //-------------------------------------------------------------------------- + // RANDOM NUMBERS + //-------------------------------------------------------------------------- + + let rng = components::rng::RngComponent::new( + board_kernel, + capsules_core::rng::DRIVER_NUM, + &base_peripherals.trng, + ) + .finalize(components::rng_component_static!(nrf52840::trng::Trng)); + + //-------------------------------------------------------------------------- + // ADC + //-------------------------------------------------------------------------- + base_peripherals.adc.calibrate(); + + let adc_mux = components::adc::AdcMuxComponent::new(&base_peripherals.adc) + .finalize(components::adc_mux_component_static!(nrf52840::adc::Adc)); + + let adc_syscall = + components::adc::AdcVirtualComponent::new(board_kernel, capsules_core::adc::DRIVER_NUM) + .finalize(components::adc_syscall_component_helper!( + // A0 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput2) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A1 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput3) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A2 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput6) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A3 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput5) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A4 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput7) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A5 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput0) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A6 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput4) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // A7 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput1) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + )); + + //-------------------------------------------------------------------------- + // SENSORS + //-------------------------------------------------------------------------- + + let sensors_i2c_bus = components::i2c::I2CMuxComponent::new(&base_peripherals.twi1, None) + .finalize(components::i2c_mux_component_static!(nrf52840::i2c::TWI)); + base_peripherals.twi1.configure( + nrf52840::pinmux::Pinmux::new(I2C_SCL_PIN as u32), + nrf52840::pinmux::Pinmux::new(I2C_SDA_PIN as u32), + ); + + let _ = &nrf52840_peripherals.gpio_port[I2C_PULLUP_PIN].make_output(); + nrf52840_peripherals.gpio_port[I2C_PULLUP_PIN].set(); + + let apds9960 = components::apds9960::Apds9960Component::new( + sensors_i2c_bus, + 0x39, + &nrf52840_peripherals.gpio_port[APDS9960_PIN], + ) + .finalize(components::apds9960_component_static!(nrf52840::i2c::TWI)); + let proximity = components::proximity::ProximityComponent::new( + apds9960, + board_kernel, + capsules_extra::proximity::DRIVER_NUM, + ) + .finalize(components::proximity_component_static!()); + + let lps22hb = components::lps22hb::Lps22hbComponent::new(sensors_i2c_bus, 0x5C) + .finalize(components::lps22hb_component_static!(nrf52840::i2c::TWI)); + let pressure = components::pressure::PressureComponent::new( + board_kernel, + capsules_extra::pressure::DRIVER_NUM, + lps22hb, + ) + .finalize(components::pressure_component_static!( + capsules_extra::lps22hb::Lps22hb< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, nrf52840::i2c::TWI>, + > + )); + + let hs3003 = components::hs3003::Hs3003Component::new(sensors_i2c_bus, 0x44) + .finalize(components::hs3003_component_static!(nrf52840::i2c::TWI)); + let temperature = components::temperature::TemperatureComponent::new( + board_kernel, + capsules_extra::temperature::DRIVER_NUM, + hs3003, + ) + .finalize(components::temperature_component_static!(HS3003Sensor)); + let humidity = components::humidity::HumidityComponent::new( + board_kernel, + capsules_extra::humidity::DRIVER_NUM, + hs3003, + ) + .finalize(components::humidity_component_static!(HS3003Sensor)); + + //-------------------------------------------------------------------------- + // WIRELESS + //-------------------------------------------------------------------------- + + let ble_radio = components::ble::BLEComponent::new( + board_kernel, + capsules_extra::ble_advertising_driver::DRIVER_NUM, + &base_peripherals.ble_radio, + mux_alarm, + ) + .finalize(components::ble_component_static!( + nrf52840::rtc::Rtc, + nrf52840::ble_radio::Radio + )); + + use capsules_extra::net::ieee802154::MacAddress; + + let aes_mux = components::ieee802154::MuxAes128ccmComponent::new(&base_peripherals.ecb) + .finalize(components::mux_aes128ccm_component_static!( + nrf52840::aes::AesECB + )); + + let device_id = nrf52840::ficr::FICR_INSTANCE.id(); + let device_id_bottom_16 = u16::from_le_bytes([device_id[0], device_id[1]]); + let (ieee802154_radio, mux_mac) = components::ieee802154::Ieee802154Component::new( + board_kernel, + capsules_extra::ieee802154::DRIVER_NUM, + &nrf52840_peripherals.ieee802154_radio, + aes_mux, + PAN_ID, + device_id_bottom_16, + device_id, + ) + .finalize(components::ieee802154_component_static!( + nrf52840::ieee802154_radio::Radio, + nrf52840::aes::AesECB<'static> + )); + use capsules_extra::net::ipv6::ip_utils::IPAddr; + + let local_ip_ifaces = static_init!( + [IPAddr; 3], + [ + IPAddr([ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, + ]), + IPAddr([ + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, + ]), + IPAddr::generate_from_mac(capsules_extra::net::ieee802154::MacAddress::Short( + device_id_bottom_16 + )), + ] + ); + + let (udp_send_mux, udp_recv_mux, udp_port_table) = components::udp_mux::UDPMuxComponent::new( + mux_mac, + DEFAULT_CTX_PREFIX_LEN, + DEFAULT_CTX_PREFIX, + DST_MAC_ADDR, + MacAddress::Short(device_id_bottom_16), + local_ip_ifaces, + mux_alarm, + ) + .finalize(components::udp_mux_component_static!( + nrf52840::rtc::Rtc, + Ieee802154MacDevice + )); + + // UDP driver initialization happens here + let udp_driver = components::udp_driver::UDPDriverComponent::new( + board_kernel, + capsules_extra::net::udp::DRIVER_NUM, + udp_send_mux, + udp_recv_mux, + udp_port_table, + local_ip_ifaces, + ) + .finalize(components::udp_driver_component_static!(nrf52840::rtc::Rtc)); + + //-------------------------------------------------------------------------- + // FINAL SETUP AND BOARD BOOT + //-------------------------------------------------------------------------- + + // Start all of the clocks. Low power operation will require a better + // approach than this. + nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); + + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); + + let platform = Platform { + ble_radio, + ieee802154_radio, + console, + pconsole, + proximity, + pressure, + temperature, + humidity, + adc: adc_syscall, + led, + gpio, + rng, + alarm, + udp_driver, + ipc: kernel::ipc::IPC::new( + board_kernel, + kernel::ipc::DRIVER_NUM, + &memory_allocation_capability, + ), + scheduler, + systick: cortexm4::systick::SysTick::new_with_calibration(64000000), + }; + + let chip = static_init!( + nrf52840::chip::NRF52, + nrf52840::chip::NRF52::new(nrf52840_peripherals) + ); + CHIP = Some(chip); + + // Need to disable the MPU because the bootloader seems to set it up. + chip.mpu().clear_mpu(); + + // Configure the USB stack to enable a serial port over CDC-ACM. + cdc.enable(); + cdc.attach(); + + //-------------------------------------------------------------------------- + // TESTS + //-------------------------------------------------------------------------- + // test::linear_log_test::run( + // mux_alarm, + // &nrf52840_peripherals.nrf52.nvmc, + // ); + // test::log_test::run( + // mux_alarm, + // &nrf52840_peripherals.nrf52.nvmc, + // ); + + debug!("Initialization complete. Entering main loop."); + let _ = platform.pconsole.start(); + + //-------------------------------------------------------------------------- + // PROCESSES AND MAIN LOOP + //-------------------------------------------------------------------------- + + // These symbols are defined in the linker script. + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + } + + kernel::process::load_processes( + board_kernel, + chip, + core::slice::from_raw_parts( + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, + ), + core::slice::from_raw_parts_mut( + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, + ), + &mut *addr_of_mut!(PROCESSES), + &FAULT_RESPONSE, + &process_management_capability, + ) + .unwrap_or_else(|err| { + debug!("Error loading processes!"); + debug!("{:?}", err); + }); + + (board_kernel, platform, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); +} diff --git a/boards/nano_rp2040_connect/Cargo.toml b/boards/nano_rp2040_connect/Cargo.toml index 37744a86c8..633f9954ba 100644 --- a/boards/nano_rp2040_connect/Cargo.toml +++ b/boards/nano_rp2040_connect/Cargo.toml @@ -1,15 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "nano_rp2040_connect" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] cortexm0p = { path = "../../arch/cortex-m0p" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } rp2040 = { path = "../../chips/rp2040" } components = { path = "../components" } enum_primitive = { path = "../../libraries/enum_primitive" } +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/nano_rp2040_connect/Makefile b/boards/nano_rp2040_connect/Makefile index 0561cfdaf9..c66757a7c9 100644 --- a/boards/nano_rp2040_connect/Makefile +++ b/boards/nano_rp2040_connect/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the Arduino Nano RP2040 Connect board. TOCK_ARCH=cortex-m0p @@ -15,19 +19,12 @@ BOOTSEL_FOLDER?=/media/$(USER)/RPI-RP2 .PHONY: install install: flash -.PHONY: flash-debug -flash-debug: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).elf - elf2uf2 $< $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).uf2 - if [ -d $(BOOTSEL_FOLDER) ]; - then - cp $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM)-app.uf2 $(BOOTSEL_FOLDER)" - else - @echo Please edit the BOOTSEL_FOLDER variable to point to you Nano RP2040 Flash Drive Folder - fi +flash-openocd: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf + $(OPENOCD) $(OPENOCD_OPTIONS) -c "program $<; verify_image $<; reset; shutdown;" .PHONY: flash flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf - elf2uf2 $< $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).uf2 + elf2uf2-rs $< $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).uf2 @if [ -d $(BOOTSEL_FOLDER) ]; then cp $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).uf2 "$(BOOTSEL_FOLDER)"; else echo; echo Please edit the BOOTSEL_FOLDER variable to point to you Nano RP2040 Flash Drive Folder; fi .PHONY: flash-app @@ -36,7 +33,7 @@ ifeq ($(APP),) $(error Please define the APP variable with the TBF file to flash an application) endif arm-none-eabi-objcopy --update-section .apps=$(APP) $(KERNEL) $(KERNEL_WITH_APP) - elf2uf2 $(KERNEL_WITH_APP) $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.uf2 + elf2uf2-rs $(KERNEL_WITH_APP) $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.uf2 @if [ -d $(BOOTSEL_FOLDER) ]; then cp $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.uf2 "$(BOOTSEL_FOLDER)"; else echo; echo Please edit the BOOTSEL_FOLDER variable to point to you Nano RP2040 Flash Drive Folder; fi diff --git a/boards/nano_rp2040_connect/README.md b/boards/nano_rp2040_connect/README.md index fc62b52c96..b8aee30332 100644 --- a/boards/nano_rp2040_connect/README.md +++ b/boards/nano_rp2040_connect/README.md @@ -1,7 +1,7 @@ Arduino Nano RP2040 Connect =========================== - + The [Arduino Nano RP2040 Connect](https://docs.arduino.cc/hardware/nano-rp2040-connect) is an Arduino Nano board built using the Raspberry Pi Foundation's RP2040 chip. @@ -10,22 +10,15 @@ board built using the Raspberry Pi Foundation's RP2040 chip. First, follow the [Tock Getting Started guide](../../doc/Getting_Started.md) -## Installing elf2uf2 +## Installing elf2uf2-rs The Nano RP2040 uses UF2 files for flashing. Tock compiles to an ELF file. -The `elf2uf2` utility is needed to transform the Tock ELF file into an UF2 file. +The `elf2uf2-rs` utility is needed to transform the Tock ELF file into an UF2 file. To install `elf2uf2`, run the commands: ```bash -$ git clone https://github.com/raspberrypi/pico-sdk -$ cd pico-sdk -$ cd tools/elf2uf2 -$ mkdir build -$ cd build -$ cmake .. -$ make -$ sudo cp elf2uf2 /usr/local/bin +$ cargo install elf2uf2-rs ``` ## Flashing the kernel @@ -35,14 +28,14 @@ The Arduino Nano RP2040 Connect can be programmed using its bootloader, which re ### Enter BOOTSEL mode To flash the Nano RP2040, it needs to be put into BOOTSEL mode. This will mount -a flash drive that allows one to copy a UF2 file. While the offical -documentation states that bouble pressing the on-board button enter this mode, +a flash drive that allows one to copy a UF2 file. While the official +documentation states that double pressing the on-board button enter this mode, this seems to work only while running Arduino's original software. If double tapping the button does not enter BOOTSEL mode (the flash drive is not mounted), the device can be [forced into BOOTSEL mode using a jumper wire](https://docs.arduino.cc/tutorials/nano-rp2040-connect/rp2040-01-technical-reference#forcing-bootloader). -1. Disconenct the board from USB +1. Disconnect the board from USB 2. Connect the GND pin with the REC pin 3. Connect the board to USB 4. Wait for the flash drive to mount diff --git a/boards/nano_rp2040_connect/build.rs b/boards/nano_rp2040_connect/build.rs deleted file mode 100644 index 3051054fc6..0000000000 --- a/boards/nano_rp2040_connect/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/nano_rp2040_connect/layout.ld b/boards/nano_rp2040_connect/layout.ld index 4646352290..75b23a9d07 100644 --- a/boards/nano_rp2040_connect/layout.ld +++ b/boards/nano_rp2040_connect/layout.ld @@ -1,18 +1,21 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + MEMORY { /* uncomment this to boot from RAM */ /* reset (rx) : ORIGIN = 0x20000000, LENGTH = 16K - rom (rx) : ORIGIN = 0x20000100, LENGTH = 128K + rom (rx) : ORIGIN = 0x20000100, LENGTH = 256K prog (rx) : ORIGIN = 0x20040000, LENGTH = 1K ram (rwx) : ORIGIN = 0x20004000, LENGTH = 240K */ /* boot from Flash */ - rom (rx) : ORIGIN = 0x10000000, LENGTH = 128K - prog (rx) : ORIGIN = 0x10020000, LENGTH = 512K + rom (rx) : ORIGIN = 0x10000000, LENGTH = 256K + prog (rx) : ORIGIN = 0x10040000, LENGTH = 512K ram (rwx) : ORIGIN = 0x20000000, LENGTH = 264K } -MPU_MIN_ALIGN = 8K; PAGE_SIZE = 4K; ENTRY(jump_to_bootloader) @@ -26,4 +29,4 @@ SECTIONS { } > rom } -INCLUDE ../kernel_layout.ld \ No newline at end of file +INCLUDE ../kernel_layout.ld diff --git a/boards/nano_rp2040_connect/openocd.cfg b/boards/nano_rp2040_connect/openocd.cfg index 8a1f9c9aca..3d107f85c8 100644 --- a/boards/nano_rp2040_connect/openocd.cfg +++ b/boards/nano_rp2040_connect/openocd.cfg @@ -1,2 +1,6 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + source [find interface/raspberrypi-swd.cfg] source [find target/rp2040.cfg] diff --git a/boards/nano_rp2040_connect/src/flash_bootloader.rs b/boards/nano_rp2040_connect/src/flash_bootloader.rs index 80d3dde91e..ef16fa5a5a 100644 --- a/boards/nano_rp2040_connect/src/flash_bootloader.rs +++ b/boards/nano_rp2040_connect/src/flash_bootloader.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + /// Padded bootloader used to boot from flash /// /// The RP2040 chip requires a padded and signed bootloader (RP2040 Datasheet, 2.8 Bootrom Page, page 156) diff --git a/boards/nano_rp2040_connect/src/io.rs b/boards/nano_rp2040_connect/src/io.rs index dbad10d0a3..deeaef8cfc 100644 --- a/boards/nano_rp2040_connect/src/io.rs +++ b/boards/nano_rp2040_connect/src/io.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; @@ -23,7 +27,7 @@ impl Writer { self.uart.set(uart); } - fn write_to_uart<'a>(&self, uart: &'a Uart, buf: &[u8]) { + fn write_to_uart(&self, uart: &Uart, buf: &[u8]) { for &c in buf { uart.send_byte(c); } @@ -43,7 +47,7 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { self.uart.map_or_else( || { // If no UART is configured for panic print, use UART0 @@ -72,6 +76,7 @@ impl IoWrite for Writer { self.write_to_uart(uart, buf); }, ); + buf.len() } } @@ -81,19 +86,21 @@ impl IoWrite for Writer { #[cfg(not(test))] #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { - // LED is conneted to GPIO 25 +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { + // LED is connected to GPIO 25 + + use core::ptr::{addr_of, addr_of_mut}; let led_kernel_pin = &RPGpioPin::new(RPGpio::GPIO25); let led = &mut LedHigh::new(led_kernel_pin); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led], writer, pi, &cortexm0p::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/nano_rp2040_connect/src/main.rs b/boards/nano_rp2040_connect/src/main.rs index 8bc4fa2dd7..5bc8e38507 100644 --- a/boards/nano_rp2040_connect/src/main.rs +++ b/boards/nano_rp2040_connect/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tock kernel for the Arduino Nano RP2040 Connect. //! //! It is based on RP2040SoC SoC (Cortex M0+). @@ -7,21 +11,21 @@ // https://github.com/rust-lang/rust/issues/62184. #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] -#![feature(asm, naked_functions)] -use capsules::virtual_alarm::VirtualMuxAlarm; +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; use components::gpio::GpioComponent; use components::led::LedsComponent; use enum_primitive::cast::FromPrimitive; use kernel::component::Component; use kernel::debug; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil::led::LedHigh; +use kernel::hil::usb::Client; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::scheduler::round_robin::RoundRobinSched; use kernel::syscall::SyscallDriver; use kernel::{capabilities, create_capability, static_init, Kernel}; -use rp2040; use rp2040::adc::{Adc, Channel}; use rp2040::chip::{Rp2040, Rp2040DefaultPeripherals}; use rp2040::clocks::{ @@ -41,7 +45,7 @@ mod flash_bootloader; /// Allocate memory for the stack #[no_mangle] #[link_section = ".stack_buffer"] -pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; +pub static mut STACK_MEMORY: [u8; 0x1500] = [0; 0x1500]; // Manually setting the boot header section that contains the FCB header #[used] @@ -50,7 +54,8 @@ static FLASH_BOOTLOADER: [u8; 256] = flash_bootloader::FLASH_BOOTLOADER; // State for loading and holding applications. // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; // Number of concurrent processes this platform supports. const NUM_PROCS: usize = 4; @@ -59,22 +64,34 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] [None; NUM_PROCS]; static mut CHIP: Option<&'static Rp2040> = None; -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; + +type TemperatureRp2040Sensor = components::temperature_rp2040::TemperatureRp2040ComponentType< + capsules_core::virtualizers::virtual_adc::AdcDevice<'static, rp2040::adc::Adc<'static>>, +>; +type TemperatureDriver = components::temperature::TemperatureComponentType; /// Supported drivers by the platform pub struct NanoRP2040Connect { - ipc: kernel::ipc::IPC, - console: &'static capsules::console::Console<'static>, - alarm: &'static capsules::alarm::AlarmDriver< + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + console: &'static capsules_core::console::Console<'static>, + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, VirtualMuxAlarm<'static, rp2040::timer::RPTimer<'static>>, >, - gpio: &'static capsules::gpio::GPIO<'static, RPGpioPin<'static>>, - led: &'static capsules::led::LedDriver<'static, LedHigh<'static, RPGpioPin<'static>>, 1>, - adc: &'static capsules::adc::AdcVirtualized<'static>, - temperature: &'static capsules::temperature::TemperatureSensor<'static>, - ninedof: &'static capsules::ninedof::NineDof<'static>, - lsm6dsoxtr: &'static capsules::lsm6dsoxtr::Lsm6dsoxtrI2C<'static>, + gpio: &'static capsules_core::gpio::GPIO<'static, RPGpioPin<'static>>, + led: &'static capsules_core::led::LedDriver<'static, LedHigh<'static, RPGpioPin<'static>>, 1>, + adc: &'static capsules_core::adc::AdcVirtualized<'static>, + temperature: &'static TemperatureDriver, + ninedof: &'static capsules_extra::ninedof::NineDof<'static>, + lsm6dsoxtr: &'static capsules_extra::lsm6dsoxtr::Lsm6dsoxtrI2C< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice< + 'static, + rp2040::i2c::I2c<'static, 'static>, + >, + >, scheduler: &'static RoundRobinSched<'static>, systick: cortexm0p::systick::SysTick, @@ -86,15 +103,15 @@ impl SyscallDriverLookup for NanoRP2040Connect { F: FnOnce(Option<&dyn SyscallDriver>) -> R, { match driver_num { - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), - capsules::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), - capsules::adc::DRIVER_NUM => f(Some(self.adc)), - capsules::temperature::DRIVER_NUM => f(Some(self.temperature)), - capsules::lsm6dsoxtr::DRIVER_NUM => f(Some(self.lsm6dsoxtr)), - capsules::ninedof::DRIVER_NUM => f(Some(self.ninedof)), + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temperature)), + capsules_extra::lsm6dsoxtr::DRIVER_NUM => f(Some(self.lsm6dsoxtr)), + capsules_extra::ninedof::DRIVER_NUM => f(Some(self.ninedof)), _ => f(None), } } @@ -110,7 +127,7 @@ impl KernelResources>> for Nan type ContextSwitchCallback = (); fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -132,31 +149,36 @@ impl KernelResources>> for Nan } } -/// Entry point used for debuger -/// -/// When loaded using gdb, the Arduino Nano RP2040 Connect is not reset -/// by default. Without this function, gdb sets the PC to the -/// beginning of the flash. This is not correct, as the RP2040 -/// has a more complex boot process. -/// -/// This function is set to be the entry point for gdb and is used -/// to send the RP2040 back in the bootloader so that all the boot -/// sqeuence is performed. -#[no_mangle] -#[naked] -pub unsafe extern "C" fn jump_to_bootloader() { - asm!( - " +#[allow(dead_code)] +extern "C" { + /// Entry point used for debugger + /// + /// When loaded using gdb, the Arduino Nano RP2040 Connect is not reset + /// by default. Without this function, gdb sets the PC to the + /// beginning of the flash. This is not correct, as the RP2040 + /// has a more complex boot process. + /// + /// This function is set to be the entry point for gdb and is used + /// to send the RP2040 back in the bootloader so that all the boot + /// sequence is performed. + fn jump_to_bootloader(); +} + +#[cfg(all(target_arch = "arm", target_os = "none"))] +core::arch::global_asm!( + " + .section .jump_to_bootloader, \"ax\" + .global jump_to_bootloader + .thumb_func + jump_to_bootloader: movs r0, #0 ldr r1, =(0xe0000000 + 0x0000ed08) str r0, [r1] ldmia r0!, {{r1, r2}} msr msp, r1 bx r2 - ", - options(noreturn) - ); -} + " +); fn init_clocks(peripherals: &Rp2040DefaultPeripherals) { // Start tick in watchdog @@ -231,22 +253,17 @@ fn init_clocks(peripherals: &Rp2040DefaultPeripherals) { /// removed when this function returns. Otherwise, the stack space used for /// these static_inits is wasted. #[inline(never)] -unsafe fn get_peripherals() -> &'static mut Rp2040DefaultPeripherals<'static> { - static_init!(Rp2040DefaultPeripherals, Rp2040DefaultPeripherals::new()) -} - -/// Main function called after RAM initialized. -#[no_mangle] -pub unsafe fn main() { +pub unsafe fn start() -> ( + &'static kernel::Kernel, + NanoRP2040Connect, + &'static rp2040::chip::Rp2040<'static, Rp2040DefaultPeripherals<'static>>, +) { // Loads relocations and clears BSS rp2040::init(); - let peripherals = get_peripherals(); + let peripherals = static_init!(Rp2040DefaultPeripherals, Rp2040DefaultPeripherals::new()); peripherals.resolve_dependencies(); - // Set the UART used for panic - io::WRITER.set_uart(&peripherals.uart0); - // Reset all peripherals except QSPI (we might be booting from Flash), PLL USB and PLL SYS peripherals.resets.reset_all_except(&[ Peripheral::IOQSpi, @@ -270,16 +287,20 @@ pub unsafe fn main() { true, ); - init_clocks(&peripherals); + init_clocks(peripherals); // Unreset all peripherals peripherals.resets.unreset_all_except(&[], true); + // Set the UART used for panic + io::WRITER.set_uart(&peripherals.uart0); + //set RX and TX pins in UART mode let gpio_tx = peripherals.pins.get_pin(RPGpio::GPIO0); let gpio_rx = peripherals.pins.get_pin(RPGpio::GPIO1); gpio_rx.set_function(GpioFunction::UART); gpio_tx.set_function(GpioFunction::UART); + // Disable IE for pads 26-29 (the Pico SDK runtime does this, not sure why) for pin in 26..30 { peripherals @@ -295,109 +316,135 @@ pub unsafe fn main() { CHIP = Some(chip); - let board_kernel = static_init!(Kernel, Kernel::new(&PROCESSES)); + let board_kernel = static_init!(Kernel, Kernel::new(&*addr_of!(PROCESSES))); let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 2], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); - let mux_alarm = components::alarm::AlarmMuxComponent::new(&peripherals.timer) - .finalize(components::alarm_mux_component_helper!(RPTimer)); + .finalize(components::alarm_mux_component_static!(RPTimer)); let alarm = components::alarm::AlarmDriverComponent::new( board_kernel, - capsules::alarm::DRIVER_NUM, + capsules_core::alarm::DRIVER_NUM, mux_alarm, ) - .finalize(components::alarm_component_helper!(RPTimer)); + .finalize(components::alarm_component_static!(RPTimer)); + + // CDC + let strings = static_init!( + [&str; 3], + [ + "Arduino", // Manufacturer + "Nano RP2040 Connect - TockOS", // Product + "00000000000000000", // Serial number + ] + ); + + let cdc = components::cdc::CdcAcmComponent::new( + &peripherals.usb, + //capsules::usb::cdc::MAX_CTRL_PACKET_SIZE_RP2040, + 64, + 0x0, + 0x1, + strings, + mux_alarm, + None, + ) + .finalize(components::cdc_acm_component_static!( + rp2040::usb::UsbCtrl, + rp2040::timer::RPTimer + )); // UART // Create a shared UART channel for kernel debug. - let uart_mux = components::console::UartMuxComponent::new( - &peripherals.uart0, - 115200, - dynamic_deferred_caller, - ) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(cdc, 115200) + .finalize(components::uart_mux_component_static!()); + + // Uncomment this to use UART as an output + // let uart_mux2 = components::console::UartMuxComponent::new( + // &peripherals.uart0, + // 115200, + // ) + // .finalize(components::uart_mux_component_static!()); // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); + + cdc.enable(); + cdc.attach(); let gpio = GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( RPGpioPin, // Used for serial communication. Comment them in if you don't use serial. - // 0 => &peripherals.pins.get_pin(RPGpio::GPIO0), - // 1 => &peripherals.pins.get_pin(RPGpio::GPIO1), - 2 => &peripherals.pins.get_pin(RPGpio::GPIO2), - 3 => &peripherals.pins.get_pin(RPGpio::GPIO3), - // 4 => &peripherals.pins.get_pin(RPGpio::GPIO4), - 5 => &peripherals.pins.get_pin(RPGpio::GPIO5), - // 6 => &peripherals.pins.get_pin(RPGpio::GPIO6), - // 7 => &peripherals.pins.get_pin(RPGpio::GPIO7), - 8 => &peripherals.pins.get_pin(RPGpio::GPIO8), - 9 => &peripherals.pins.get_pin(RPGpio::GPIO9), - 10 => &peripherals.pins.get_pin(RPGpio::GPIO10), - 11 => &peripherals.pins.get_pin(RPGpio::GPIO11), - // 12 => &peripherals.pins.get_pin(RPGpio::GPIO12), - // 13 => &peripherals.pins.get_pin(RPGpio::GPIO13), - 14 => &peripherals.pins.get_pin(RPGpio::GPIO14), - 15 => &peripherals.pins.get_pin(RPGpio::GPIO15), - 16 => &peripherals.pins.get_pin(RPGpio::GPIO16), - 17 => &peripherals.pins.get_pin(RPGpio::GPIO17), - 18 => &peripherals.pins.get_pin(RPGpio::GPIO18), - 19 => &peripherals.pins.get_pin(RPGpio::GPIO19), - 20 => &peripherals.pins.get_pin(RPGpio::GPIO20), - 21 => &peripherals.pins.get_pin(RPGpio::GPIO21), - 22 => &peripherals.pins.get_pin(RPGpio::GPIO22), - 23 => &peripherals.pins.get_pin(RPGpio::GPIO23), - 24 => &peripherals.pins.get_pin(RPGpio::GPIO24), + // 0 => peripherals.pins.get_pin(RPGpio::GPIO0), + // 1 => peripherals.pins.get_pin(RPGpio::GPIO1), + 2 => peripherals.pins.get_pin(RPGpio::GPIO2), + 3 => peripherals.pins.get_pin(RPGpio::GPIO3), + // 4 => peripherals.pins.get_pin(RPGpio::GPIO4), + 5 => peripherals.pins.get_pin(RPGpio::GPIO5), + // 6 => peripherals.pins.get_pin(RPGpio::GPIO6), + // 7 => peripherals.pins.get_pin(RPGpio::GPIO7), + 8 => peripherals.pins.get_pin(RPGpio::GPIO8), + 9 => peripherals.pins.get_pin(RPGpio::GPIO9), + 10 => peripherals.pins.get_pin(RPGpio::GPIO10), + 11 => peripherals.pins.get_pin(RPGpio::GPIO11), + // 12 => peripherals.pins.get_pin(RPGpio::GPIO12), + // 13 => peripherals.pins.get_pin(RPGpio::GPIO13), + 14 => peripherals.pins.get_pin(RPGpio::GPIO14), + 15 => peripherals.pins.get_pin(RPGpio::GPIO15), + 16 => peripherals.pins.get_pin(RPGpio::GPIO16), + 17 => peripherals.pins.get_pin(RPGpio::GPIO17), + 18 => peripherals.pins.get_pin(RPGpio::GPIO18), + 19 => peripherals.pins.get_pin(RPGpio::GPIO19), + 20 => peripherals.pins.get_pin(RPGpio::GPIO20), + 21 => peripherals.pins.get_pin(RPGpio::GPIO21), + 22 => peripherals.pins.get_pin(RPGpio::GPIO22), + 23 => peripherals.pins.get_pin(RPGpio::GPIO23), + 24 => peripherals.pins.get_pin(RPGpio::GPIO24), // LED pin - // 25 => &peripherals.pins.get_pin(RPGpio::GPIO25), + // 25 => peripherals.pins.get_pin(RPGpio::GPIO25), // Uncomment to use these as GPIO pins instead of ADC pins - // 26 => &peripherals.pins.get_pin(RPGpio::GPIO26), - // 27 => &peripherals.pins.get_pin(RPGpio::GPIO27), - // 28 => &peripherals.pins.get_pin(RPGpio::GPIO28), - // 29 => &peripherals.pins.get_pin(RPGpio::GPIO29) + // 26 => peripherals.pins.get_pin(RPGpio::GPIO26), + // 27 => peripherals.pins.get_pin(RPGpio::GPIO27), + // 28 => peripherals.pins.get_pin(RPGpio::GPIO28), + // 29 => peripherals.pins.get_pin(RPGpio::GPIO29) ), ) - .finalize(components::gpio_component_buf!(RPGpioPin<'static>)); + .finalize(components::gpio_component_static!(RPGpioPin<'static>)); - let led = LedsComponent::new().finalize(components::led_component_helper!( + let led = LedsComponent::new().finalize(components::led_component_static!( LedHigh<'static, RPGpioPin<'static>>, - LedHigh::new(&peripherals.pins.get_pin(RPGpio::GPIO6)) + LedHigh::new(peripherals.pins.get_pin(RPGpio::GPIO6)) )); peripherals.adc.init(); let adc_mux = components::adc::AdcMuxComponent::new(&peripherals.adc) - .finalize(components::adc_mux_component_helper!(Adc)); + .finalize(components::adc_mux_component_static!(Adc)); - let temp_sensor = components::temperature_rp2040::TemperatureRp2040Component::new(1.721, 0.706) - .finalize(components::temperaturerp2040_adc_component_helper!( - rp2040::adc::Adc, - Channel::Channel4, - adc_mux - )); + let temp_sensor = components::temperature_rp2040::TemperatureRp2040Component::new( + adc_mux, + Channel::Channel4, + 1.721, + 0.706, + ) + .finalize(components::temperature_rp2040_adc_component_static!( + rp2040::adc::Adc + )); peripherals.i2c0.init(100 * 1000); //set SDA and SCL pins in I2C mode @@ -405,35 +452,41 @@ pub unsafe fn main() { let gpio_scl = peripherals.pins.get_pin(RPGpio::GPIO13); gpio_sda.set_function(GpioFunction::I2C); gpio_scl.set_function(GpioFunction::I2C); - let mux_i2c = - components::i2c::I2CMuxComponent::new(&peripherals.i2c0, None, dynamic_deferred_caller) - .finalize(components::i2c_mux_component_helper!()); + let mux_i2c = components::i2c::I2CMuxComponent::new(&peripherals.i2c0, None).finalize( + components::i2c_mux_component_static!(rp2040::i2c::I2c<'static, 'static>), + ); let lsm6dsoxtr = components::lsm6dsox::Lsm6dsoxtrI2CComponent::new( + mux_i2c, + capsules_extra::lsm6dsoxtr::ACCELEROMETER_BASE_ADDRESS, board_kernel, - capsules::lsm6dsoxtr::DRIVER_NUM, + capsules_extra::lsm6dsoxtr::DRIVER_NUM, ) - .finalize(components::lsm6ds_i2c_component_helper!(mux_i2c)); - - let ninedof = - components::ninedof::NineDofComponent::new(board_kernel, capsules::ninedof::DRIVER_NUM) - .finalize(components::ninedof_component_helper!(lsm6dsoxtr)); + .finalize(components::lsm6ds_i2c_component_static!( + rp2040::i2c::I2c<'static, 'static> + )); - let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let grant_temperature = - board_kernel.create_grant(capsules::temperature::DRIVER_NUM, &grant_cap); + let ninedof = components::ninedof::NineDofComponent::new( + board_kernel, + capsules_extra::ninedof::DRIVER_NUM, + ) + .finalize(components::ninedof_component_static!(lsm6dsoxtr)); - let temp = static_init!( - capsules::temperature::TemperatureSensor<'static>, - capsules::temperature::TemperatureSensor::new(temp_sensor, grant_temperature) - ); + let temp = components::temperature::TemperatureComponent::new( + board_kernel, + capsules_extra::temperature::DRIVER_NUM, + temp_sensor, + ) + .finalize(components::temperature_component_static!( + TemperatureRp2040Sensor + )); let _ = lsm6dsoxtr .configure( - capsules::lsm6dsoxtr::LSM6DSOXGyroDataRate::LSM6DSOX_GYRO_RATE_12_5_HZ, - capsules::lsm6dsoxtr::LSM6DSOXAccelDataRate::LSM6DSOX_ACCEL_RATE_12_5_HZ, - capsules::lsm6dsoxtr::LSM6DSOXAccelRange::LSM6DSOX_ACCEL_RANGE_2_G, - capsules::lsm6dsoxtr::LSM6DSOXTRGyroRange::LSM6DSOX_GYRO_RANGE_250_DPS, + capsules_extra::lsm6dsoxtr::LSM6DSOXGyroDataRate::LSM6DSOX_GYRO_RATE_12_5_HZ, + capsules_extra::lsm6dsoxtr::LSM6DSOXAccelDataRate::LSM6DSOX_ACCEL_RATE_12_5_HZ, + capsules_extra::lsm6dsoxtr::LSM6DSOXAccelRange::LSM6DSOX_ACCEL_RANGE_2_G, + capsules_extra::lsm6dsoxtr::LSM6DSOXTRGyroRange::LSM6DSOX_GYRO_RANGE_250_DPS, true, ) .map_err(|e| { @@ -448,26 +501,26 @@ pub unsafe fn main() { // Uncomment this block in order to use the temperature sensor from lsm6dsoxtr // let temp = static_init!( - // capsules::temperature::TemperatureSensor<'static>, - // capsules::temperature::TemperatureSensor::new(lsm6dsoxtr, grant_temperature) + // capsules_extra::temperature::TemperatureSensor<'static>, + // capsules_extra::temperature::TemperatureSensor::new(lsm6dsoxtr, grant_temperature) // ); kernel::hil::sensors::TemperatureDriver::set_client(temp_sensor, temp); - let adc_channel_0 = components::adc::AdcComponent::new(&adc_mux, Channel::Channel0) - .finalize(components::adc_component_helper!(Adc)); + let adc_channel_0 = components::adc::AdcComponent::new(adc_mux, Channel::Channel0) + .finalize(components::adc_component_static!(Adc)); - let adc_channel_1 = components::adc::AdcComponent::new(&adc_mux, Channel::Channel1) - .finalize(components::adc_component_helper!(Adc)); + let adc_channel_1 = components::adc::AdcComponent::new(adc_mux, Channel::Channel1) + .finalize(components::adc_component_static!(Adc)); - let adc_channel_2 = components::adc::AdcComponent::new(&adc_mux, Channel::Channel2) - .finalize(components::adc_component_helper!(Adc)); + let adc_channel_2 = components::adc::AdcComponent::new(adc_mux, Channel::Channel2) + .finalize(components::adc_component_static!(Adc)); - let adc_channel_3 = components::adc::AdcComponent::new(&adc_mux, Channel::Channel3) - .finalize(components::adc_component_helper!(Adc)); + let adc_channel_3 = components::adc::AdcComponent::new(adc_mux, Channel::Channel3) + .finalize(components::adc_component_static!(Adc)); let adc_syscall = - components::adc::AdcVirtualComponent::new(board_kernel, capsules::adc::DRIVER_NUM) + components::adc::AdcVirtualComponent::new(board_kernel, capsules_core::adc::DRIVER_NUM) .finalize(components::adc_syscall_component_helper!( adc_channel_0, adc_channel_1, @@ -475,8 +528,8 @@ pub unsafe fn main() { adc_channel_3, )); - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); // PROCESS CONSOLE @@ -485,12 +538,13 @@ pub unsafe fn main() { uart_mux, mux_alarm, process_printer, + Some(cortexm0p::support::reset), ) - .finalize(components::process_console_component_helper!(RPTimer)); + .finalize(components::process_console_component_static!(RPTimer)); let _ = process_console.start(); - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); let nano_rp2040_connect = NanoRP2040Connect { ipc: kernel::ipc::IPC::new( @@ -498,15 +552,15 @@ pub unsafe fn main() { kernel::ipc::DRIVER_NUM, &memory_allocation_capability, ), - alarm: alarm, - gpio: gpio, - led: led, - console: console, + alarm, + gpio, + led, + console, adc: adc_syscall, temperature: temp, - lsm6dsoxtr: lsm6dsoxtr, - ninedof: ninedof, + lsm6dsoxtr, + ninedof, scheduler, systick: cortexm0p::systick::SysTick::new_with_calibration(125_000_000), @@ -524,7 +578,7 @@ pub unsafe fn main() { ); debug!("Initialization complete. Enter main loop"); - /// These symbols are defined in the linker script. + // These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -540,14 +594,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_management_capability, ) @@ -556,10 +610,14 @@ pub unsafe fn main() { debug!("{:?}", err); }); - board_kernel.kernel_loop( - &nano_rp2040_connect, - chip, - Some(&nano_rp2040_connect.ipc), - &main_loop_capability, - ); + (board_kernel, nano_rp2040_connect, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); } diff --git a/boards/nordic/nrf52832_chip_layout.ld b/boards/nordic/nrf52832_chip_layout.ld index c2f2459df2..cecec84a38 100644 --- a/boards/nordic/nrf52832_chip_layout.ld +++ b/boards/nordic/nrf52832_chip_layout.ld @@ -1,3 +1,7 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + /* Memory Space Definitions, 512K flash, 64K ram */ MEMORY { @@ -6,5 +10,4 @@ MEMORY ram (rwx) : ORIGIN = 0x20000000, LENGTH = 64K } -MPU_MIN_ALIGN = 8K; PAGE_SIZE = 4K; diff --git a/boards/nordic/nrf52840_chip_layout.ld b/boards/nordic/nrf52840_chip_layout.ld index bafb4acd23..25468e3924 100644 --- a/boards/nordic/nrf52840_chip_layout.ld +++ b/boards/nordic/nrf52840_chip_layout.ld @@ -1,10 +1,13 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + /* Memory Space Definitions, 1M flash, 256K ram */ MEMORY { - rom (rx) : ORIGIN = 0x00000000, LENGTH = 192K - prog (rx) : ORIGIN = 0x00030000, LENGTH = 832K + rom (rx) : ORIGIN = 0x00000000, LENGTH = 256K + prog (rx) : ORIGIN = 0x00040000, LENGTH = 768K ram (rwx) : ORIGIN = 0x20000000, LENGTH = 256K } -MPU_MIN_ALIGN = 8K; PAGE_SIZE = 4K; diff --git a/boards/nordic/nrf52840_dongle/Cargo.toml b/boards/nordic/nrf52840_dongle/Cargo.toml index 0202ff9adb..b016724ea1 100644 --- a/boards/nordic/nrf52840_dongle/Cargo.toml +++ b/boards/nordic/nrf52840_dongle/Cargo.toml @@ -1,14 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "nrf52840_dongle" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../../build.rs" +edition.workspace = true [dependencies] components = { path = "../../components" } cortexm4 = { path = "../../../arch/cortex-m4" } -capsules = { path = "../../../capsules" } kernel = { path = "../../../kernel" } nrf52840 = { path = "../../../chips/nrf52840" } nrf52_components = { path = "../nrf52_components" } + +capsules-core = { path = "../../../capsules/core" } +capsules-extra = { path = "../../../capsules/extra" } +capsules-system = { path = "../../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/nordic/nrf52840_dongle/Makefile b/boards/nordic/nrf52840_dongle/Makefile index f730e86f07..28ad999a96 100644 --- a/boards/nordic/nrf52840_dongle/Makefile +++ b/boards/nordic/nrf52840_dongle/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the nRF development kit TARGET=thumbv7em-none-eabi diff --git a/boards/nordic/nrf52840_dongle/build.rs b/boards/nordic/nrf52840_dongle/build.rs deleted file mode 100644 index 1fdd4924f0..0000000000 --- a/boards/nordic/nrf52840_dongle/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../../kernel_layout.ld"); -} diff --git a/boards/nordic/nrf52840_dongle/jtag/gdbinit_pca10040.jlink b/boards/nordic/nrf52840_dongle/jtag/gdbinit_pca10040.jlink index 04d8f67de1..cf101a0297 100644 --- a/boards/nordic/nrf52840_dongle/jtag/gdbinit_pca10040.jlink +++ b/boards/nordic/nrf52840_dongle/jtag/gdbinit_pca10040.jlink @@ -1,25 +1,29 @@ -# -# -# -# J-LINK GDB SERVER initialization -# -# This connects to a GDB Server listening -# for commands on localhost at tcp port 2331 -target remote localhost:2331 -monitor speed 30 -file ../../../../target/thumbv7em-none-eabi/release/nrf52840_dongle -monitor reset -# -# CPU core initialization (to be done by user) -# -# Set the processor mode -# monitor reg cpsr = 0xd3 -# Set auto JTAG speed -monitor speed auto -# Setup GDB FOR FASTER DOWNLOADS -set remote memory-write-packet-size 1024 -set remote memory-write-packet-size fixed -# tui enable -# layout split -# layout service_pending_interrupts -b initialize_ram_jump_to_main +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2019. +# Copyright Google LLC 2019. +# +# +# +# J-LINK GDB SERVER initialization +# +# This connects to a GDB Server listening +# for commands on localhost at tcp port 2331 +target remote localhost:2331 +monitor speed 30 +file ../../../../target/thumbv7em-none-eabi/release/nrf52840_dongle +monitor reset +# +# CPU core initialization (to be done by user) +# +# Set the processor mode +# monitor reg cpsr = 0xd3 +# Set auto JTAG speed +monitor speed auto +# Setup GDB FOR FASTER DOWNLOADS +set remote memory-write-packet-size 1024 +set remote memory-write-packet-size fixed +# tui enable +# layout split +# layout service_pending_interrupts +b initialize_ram_jump_to_main diff --git a/boards/nordic/nrf52840_dongle/jtag/jdbserver_pca10040.sh b/boards/nordic/nrf52840_dongle/jtag/jdbserver_pca10040.sh index 1c9cd9b24f..a89425b592 100755 --- a/boards/nordic/nrf52840_dongle/jtag/jdbserver_pca10040.sh +++ b/boards/nordic/nrf52840_dongle/jtag/jdbserver_pca10040.sh @@ -1 +1,5 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + JLinkGDBServer -device nRF52840_xxAA -speed 1200 -if swd -AutoConnect 1 -port 2331 diff --git a/boards/nordic/nrf52840_dongle/layout.ld b/boards/nordic/nrf52840_dongle/layout.ld index a8da375997..3eccbaa12d 100644 --- a/boards/nordic/nrf52840_dongle/layout.ld +++ b/boards/nordic/nrf52840_dongle/layout.ld @@ -1,2 +1,6 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + INCLUDE ../nrf52840_chip_layout.ld INCLUDE ../../kernel_layout.ld diff --git a/boards/nordic/nrf52840_dongle/src/io.rs b/boards/nordic/nrf52840_dongle/src/io.rs index 8c60a865d4..e52f7ff959 100644 --- a/boards/nordic/nrf52840_dongle/src/io.rs +++ b/boards/nordic/nrf52840_dongle/src/io.rs @@ -1,11 +1,15 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; -use cortexm4; use kernel::debug; use kernel::debug::IoWrite; use kernel::hil::led; use kernel::hil::uart::{self, Configure}; use nrf52840::gpio::Pin; +use nrf52840::uart::{Uarte, UARTE0_BASE}; use crate::CHIP; use crate::PROCESSES; @@ -25,11 +29,11 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { // Here, we create a second instance of the Uarte struct. // This is okay because we only call this during a panic, and // we will never actually process the interrupts - let uart = nrf52840::uart::Uarte::new(); + let uart = Uarte::new(UARTE0_BASE); if !self.initialized { self.initialized = true; let _ = uart.configure(uart::Parameters { @@ -46,6 +50,7 @@ impl IoWrite for Writer { } while !uart.tx_ready() {} } + buf.len() } } @@ -53,18 +58,20 @@ impl IoWrite for Writer { #[no_mangle] #[panic_handler] /// Panic handler -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { // The nRF52840 Dongle LEDs (see back of board) + + use core::ptr::{addr_of, addr_of_mut}; let led_kernel_pin = &nrf52840::gpio::GPIOPin::new(Pin::P0_06); let led = &mut led::LedLow::new(led_kernel_pin); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led], writer, pi, &cortexm4::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/nordic/nrf52840_dongle/src/main.rs b/boards/nordic/nrf52840_dongle/src/main.rs index 497262d630..77e5f6efcd 100644 --- a/boards/nordic/nrf52840_dongle/src/main.rs +++ b/boards/nordic/nrf52840_dongle/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tock kernel for the Nordic Semiconductor nRF52840 dongle. //! //! It is based on nRF52840 SoC (Cortex M4 core with a BLE transceiver) with @@ -9,10 +13,12 @@ #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] -use capsules::virtual_aes_ccm::MuxAES128CCM; -use capsules::virtual_alarm::VirtualMuxAlarm; +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::virtualizers::virtual_aes_ccm::MuxAES128CCM; +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; +use kernel::deferred_call::DeferredCallClient; use kernel::hil::led::LedLow; use kernel::hil::symmetric_encryption::AES128; use kernel::hil::time::Counter; @@ -22,7 +28,7 @@ use kernel::scheduler::round_robin::RoundRobinSched; use kernel::{capabilities, create_capability, debug, debug_gpio, debug_verbose, static_init}; use nrf52840::gpio::Pin; use nrf52840::interrupt_service::Nrf52840DefaultPeripherals; -use nrf52_components::{self, UartChannel, UartPins}; +use nrf52_components::{UartChannel, UartPins}; // The nRF52840 Dongle LEDs const LED1_PIN: Pin = Pin::P0_06; @@ -44,16 +50,19 @@ const _SPI_MOSI: Pin = Pin::P1_01; const _SPI_MISO: Pin = Pin::P1_02; const _SPI_CLK: Pin = Pin::P1_04; -// Constants related to the configuration of the 15.4 network stack +// Constants related to the configuration of the 15.4 network stack; DEFAULT_EXT_SRC_MAC +// should be replaced by an extended src address generated from device serial number const SRC_MAC: u16 = 0xf00f; const PAN_ID: u16 = 0xABCD; +const DEFAULT_EXT_SRC_MAC: [u8; 8] = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77]; /// UART Writer pub mod io; // State for loading and holding applications. // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; // Number of concurrent processes this platform supports. const NUM_PROCS: usize = 8; @@ -64,44 +73,58 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] // Static reference to chip for panic dumps static mut CHIP: Option<&'static nrf52840::chip::NRF52> = None; // Static reference to process printer for panic dumps -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; /// Dummy buffer that causes the linker to reserve enough space for the stack. #[no_mangle] #[link_section = ".stack_buffer"] pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; +type TemperatureDriver = + components::temperature::TemperatureComponentType>; +type RngDriver = components::rng::RngComponentType>; + +type Ieee802154Driver = components::ieee802154::Ieee802154ComponentType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; + /// Supported drivers by the platform pub struct Platform { - ble_radio: &'static capsules::ble_advertising_driver::BLE< + ble_radio: &'static capsules_extra::ble_advertising_driver::BLE< 'static, nrf52840::ble_radio::Radio<'static>, VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, >, - ieee802154_radio: &'static capsules::ieee802154::RadioDriver<'static>, - button: &'static capsules::button::Button<'static, nrf52840::gpio::GPIOPin<'static>>, - pconsole: &'static capsules::process_console::ProcessConsole< + ieee802154_radio: &'static Ieee802154Driver, + button: &'static capsules_core::button::Button<'static, nrf52840::gpio::GPIOPin<'static>>, + pconsole: &'static capsules_core::process_console::ProcessConsole< 'static, + { capsules_core::process_console::DEFAULT_COMMAND_HISTORY_LEN }, VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, components::process_console::Capability, >, - console: &'static capsules::console::Console<'static>, - gpio: &'static capsules::gpio::GPIO<'static, nrf52840::gpio::GPIOPin<'static>>, - led: &'static capsules::led::LedDriver< + console: &'static capsules_core::console::Console<'static>, + gpio: &'static capsules_core::gpio::GPIO<'static, nrf52840::gpio::GPIOPin<'static>>, + led: &'static capsules_core::led::LedDriver< 'static, LedLow<'static, nrf52840::gpio::GPIOPin<'static>>, 4, >, - rng: &'static capsules::rng::RngDriver<'static>, - temp: &'static capsules::temperature::TemperatureSensor<'static>, - ipc: kernel::ipc::IPC, - analog_comparator: &'static capsules::analog_comparator::AnalogComparator< + rng: &'static RngDriver, + temp: &'static TemperatureDriver, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + analog_comparator: &'static capsules_extra::analog_comparator::AnalogComparator< 'static, nrf52840::acomp::Comparator<'static>, >, - alarm: &'static capsules::alarm::AlarmDriver< + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52840::rtc::Rtc<'static>, + >, >, scheduler: &'static RoundRobinSched<'static>, systick: cortexm4::systick::SysTick, @@ -113,16 +136,16 @@ impl SyscallDriverLookup for Platform { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::button::DRIVER_NUM => f(Some(self.button)), - capsules::rng::DRIVER_NUM => f(Some(self.rng)), - capsules::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), - capsules::ieee802154::DRIVER_NUM => f(Some(self.ieee802154_radio)), - capsules::temperature::DRIVER_NUM => f(Some(self.temp)), - capsules::analog_comparator::DRIVER_NUM => f(Some(self.analog_comparator)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::rng::DRIVER_NUM => f(Some(self.rng)), + capsules_extra::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), + capsules_extra::ieee802154::DRIVER_NUM => f(Some(self.ieee802154_radio)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temp)), + capsules_extra::analog_comparator::DRIVER_NUM => f(Some(self.analog_comparator)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), _ => f(None), } @@ -141,7 +164,7 @@ impl KernelResources &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -167,33 +190,33 @@ impl KernelResources &'static mut Nrf52840DefaultPeripherals<'static> { +pub unsafe fn start() -> ( + &'static kernel::Kernel, + Platform, + &'static nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>, +) { + nrf52840::init(); + + let ieee802154_ack_buf = static_init!( + [u8; nrf52840::ieee802154_radio::ACK_BUF_SIZE], + [0; nrf52840::ieee802154_radio::ACK_BUF_SIZE] + ); // Initialize chip peripheral drivers let nrf52840_peripherals = static_init!( Nrf52840DefaultPeripherals, - Nrf52840DefaultPeripherals::new() + Nrf52840DefaultPeripherals::new(ieee802154_ack_buf) ); - nrf52840_peripherals -} - -/// Main function called after RAM initialized. -#[no_mangle] -pub unsafe fn main() { - nrf52840::init(); - - let nrf52840_peripherals = get_peripherals(); - // set up circular peripheral dependencies nrf52840_peripherals.init(); let base_peripherals = &nrf52840_peripherals.nrf52; - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); // GPIOs let gpio = components::gpio::GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( nrf52840::gpio::GPIOPin, // left side of the USB plug @@ -225,11 +248,11 @@ pub unsafe fn main() { 23 => &nrf52840_peripherals.gpio_port[Pin::P1_02] ), ) - .finalize(components::gpio_component_buf!(nrf52840::gpio::GPIOPin)); + .finalize(components::gpio_component_static!(nrf52840::gpio::GPIOPin)); let button = components::button::ButtonComponent::new( board_kernel, - capsules::button::DRIVER_NUM, + capsules_core::button::DRIVER_NUM, components::button_component_helper!( nrf52840::gpio::GPIOPin, ( @@ -239,9 +262,11 @@ pub unsafe fn main() { ) ), ) - .finalize(components::button_component_buf!(nrf52840::gpio::GPIOPin)); + .finalize(components::button_component_static!( + nrf52840::gpio::GPIOPin + )); - let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( LedLow<'static, nrf52840::gpio::GPIOPin>, LedLow::new(&nrf52840_peripherals.gpio_port[LED1_PIN]), LedLow::new(&nrf52840_peripherals.gpio_port[LED2_R_PIN]), @@ -267,7 +292,6 @@ pub unsafe fn main() { // functions. let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); let gpio_port = &nrf52840_peripherals.gpio_port; @@ -282,122 +306,120 @@ pub unsafe fn main() { let rtc = &base_peripherals.rtc; let _ = rtc.start(); let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) - .finalize(components::alarm_mux_component_helper!(nrf52840::rtc::Rtc)); + .finalize(components::alarm_mux_component_static!(nrf52840::rtc::Rtc)); let alarm = components::alarm::AlarmDriverComponent::new( board_kernel, - capsules::alarm::DRIVER_NUM, + capsules_core::alarm::DRIVER_NUM, mux_alarm, ) - .finalize(components::alarm_component_helper!(nrf52840::rtc::Rtc)); + .finalize(components::alarm_component_static!(nrf52840::rtc::Rtc)); let uart_channel = UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD)); let channel = nrf52_components::UartChannelComponent::new( uart_channel, mux_alarm, &base_peripherals.uarte0, ) - .finalize(()); - - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 3], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + .finalize(nrf52_components::uart_channel_component_static!( + nrf52840::rtc::Rtc + )); - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); // Create a shared UART channel for the console and for kernel debug. - let uart_mux = - components::console::UartMuxComponent::new(channel, 115200, dynamic_deferred_caller) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(channel, 115200) + .finalize(components::uart_mux_component_static!()); let pconsole = components::process_console::ProcessConsoleComponent::new( board_kernel, uart_mux, mux_alarm, process_printer, + Some(cortexm4::support::reset), ) - .finalize(components::process_console_component_helper!( + .finalize(components::process_console_component_static!( nrf52840::rtc::Rtc<'static> )); // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); - let ble_radio = nrf52_components::BLEComponent::new( + let ble_radio = components::ble::BLEComponent::new( board_kernel, - capsules::ble_advertising_driver::DRIVER_NUM, + capsules_extra::ble_advertising_driver::DRIVER_NUM, &base_peripherals.ble_radio, mux_alarm, ) - .finalize(()); + .finalize(components::ble_component_static!( + nrf52840::rtc::Rtc, + nrf52840::ble_radio::Radio + )); let aes_mux = static_init!( MuxAES128CCM<'static, nrf52840::aes::AesECB>, - MuxAES128CCM::new(&base_peripherals.ecb, dynamic_deferred_caller) + MuxAES128CCM::new(&base_peripherals.ecb,) ); + aes_mux.register(); base_peripherals.ecb.set_client(aes_mux); - aes_mux.initialize_callback_handle( - dynamic_deferred_caller.register(aes_mux).unwrap(), // Unwrap fail = no deferred call slot available for ccm mux - ); let (ieee802154_radio, _mux_mac) = components::ieee802154::Ieee802154Component::new( board_kernel, - capsules::ieee802154::DRIVER_NUM, - &base_peripherals.ieee802154_radio, + capsules_extra::ieee802154::DRIVER_NUM, + &nrf52840_peripherals.ieee802154_radio, aes_mux, PAN_ID, SRC_MAC, - dynamic_deferred_caller, + DEFAULT_EXT_SRC_MAC, ) - .finalize(components::ieee802154_component_helper!( + .finalize(components::ieee802154_component_static!( nrf52840::ieee802154_radio::Radio, nrf52840::aes::AesECB<'static> )); let temp = components::temperature::TemperatureComponent::new( board_kernel, - capsules::temperature::DRIVER_NUM, + capsules_extra::temperature::DRIVER_NUM, &base_peripherals.temp, ) - .finalize(()); + .finalize(components::temperature_component_static!( + nrf52840::temperature::Temp + )); let rng = components::rng::RngComponent::new( board_kernel, - capsules::rng::DRIVER_NUM, + capsules_core::rng::DRIVER_NUM, &base_peripherals.trng, ) - .finalize(()); + .finalize(components::rng_component_static!(nrf52840::trng::Trng)); // Initialize AC using AIN5 (P0.29) as VIN+ and VIN- as AIN0 (P0.02) // These are hardcoded pin assignments specified in the driver - let analog_comparator = components::analog_comparator::AcComponent::new( + let analog_comparator = components::analog_comparator::AnalogComparatorComponent::new( &base_peripherals.acomp, - components::acomp_component_helper!( + components::analog_comparator_component_helper!( nrf52840::acomp::Channel, - &nrf52840::acomp::CHANNEL_AC0 + &*addr_of!(nrf52840::acomp::CHANNEL_AC0) ), board_kernel, - capsules::analog_comparator::DRIVER_NUM, + capsules_extra::analog_comparator::DRIVER_NUM, ) - .finalize(components::acomp_component_buf!( + .finalize(components::analog_comparator_component_static!( nrf52840::acomp::Comparator )); nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); let platform = Platform { button, @@ -422,9 +444,9 @@ pub unsafe fn main() { let _ = platform.pconsole.start(); debug!("Initialization complete. Entering main loop\r"); - debug!("{}", &nrf52840::ficr::FICR_INSTANCE); + debug!("{}", &*addr_of!(nrf52840::ficr::FICR_INSTANCE)); - /// These symbols are defined in the linker script. + // These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -440,14 +462,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_management_capability, ) @@ -456,5 +478,14 @@ pub unsafe fn main() { debug!("{:?}", err); }); + (board_kernel, platform, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); } diff --git a/boards/nordic/nrf52840dk/Cargo.toml b/boards/nordic/nrf52840dk/Cargo.toml index 5d62276788..ccde12cc68 100644 --- a/boards/nordic/nrf52840dk/Cargo.toml +++ b/boards/nordic/nrf52840dk/Cargo.toml @@ -1,14 +1,31 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "nrf52840dk" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../../build.rs" +edition.workspace = true + +[lib] +name = "nrf52840dk_lib" + +[[bin]] +name = "nrf52840dk" +path = "src/main.rs" [dependencies] components = { path = "../../components" } cortexm4 = { path = "../../../arch/cortex-m4" } -capsules = { path = "../../../capsules" } kernel = { path = "../../../kernel" } nrf52840 = { path = "../../../chips/nrf52840" } nrf52_components = { path = "../nrf52_components" } + +capsules-core = { path = "../../../capsules/core" } +capsules-extra = { path = "../../../capsules/extra" } +capsules-system = { path = "../../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/nordic/nrf52840dk/Makefile b/boards/nordic/nrf52840dk/Makefile index ea1746377f..0838920eff 100644 --- a/boards/nordic/nrf52840dk/Makefile +++ b/boards/nordic/nrf52840dk/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the nRF development kit TARGET=thumbv7em-none-eabi diff --git a/boards/nordic/nrf52840dk/README.md b/boards/nordic/nrf52840dk/README.md index 3a05a5e970..21a0352ead 100644 --- a/boards/nordic/nrf52840dk/README.md +++ b/boards/nordic/nrf52840dk/README.md @@ -1,45 +1,41 @@ Platform-Specific Instructions: nRF52840-DK -=================================== +=========================================== The [nRF52840 Development -Kit](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK) is a platform -based around the nRF52840, an SoC with an ARM Cortex-M4 and a BLE -radio. The kit is Arduino shield compatible and includes several -buttons. +Kit](https://www.nordicsemi.com/Software-and-Tools/Development-Kits/nRF52840-DK) +is a platform based around the nRF52840, an SoC with an ARM Cortex-M4 and a BLE +radio. The kit is Arduino shield compatible and includes several buttons. ## Getting Started First, follow the [Tock Getting Started guide](../../../doc/Getting_Started.md) -JTAG is the preferred method to program. The development kit has an -integrated JTAG debugger, you simply need to [install JTAG +JLinkExe is the preferred method to program the board. The development kit has +an integrated JTAG debugger, you simply need to [install the JLinkExe software](../../../doc/Getting_Started.md#loading-the-kernel-onto-a-board). ## Programming the kernel Once you have all software installed, you should be able to simply run -make flash in this directory to install a fresh kernel. +`make flash` in this directory to install a fresh kernel. ## Programming user-level applications -You can program an application over USB using the integrated JTAG and `tockloader`: +You can program an application over USB using `tockloader`: ```bash $ cd libtock-c/examples/ $ make -$ tockloader install --jlink --board nrf52dk +$ tockloader install ``` -The same options (`--jlink --board nrf52dk`) must be passed for other tockloader commands -such as `erase-apps` or `list`. +## Console output +To view the console output on the nrf52840dk: -Viewing console output on the nrf52840dk is slightly different from other boards. You must use ```bash $ tockloader listen ``` -**followed by a press of the reset button** in order to view console output starting from the boot -sequence. Notably, you should not -pass the `--jlink` option to `tockloader listen`. -## Console output +To view console output starting from the boot sequence **press the reset +button**. This board supports two methods for writing messages to a console interface (console driver for applications as well as debug statements in the kernel). @@ -47,17 +43,19 @@ This board supports two methods for writing messages to a console interface By default, messages are written to a UART interface over the GPIO pins `P0.05` to `P0.08` (see the [main.rs](src/main.rs) file). -If you don't have any UART cables or want to use a different interface, there is -also a console over the Segger RTT protocol. This only requires a micro-USB -cable on the USB debugging port (the same used to flash Tock on the board), and -is enabled by setting the `USB_DEBUGGING` constant to `true` in the -[main.rs](src/main.rs) file. -This disables the UART interface. +If you want a higher bandwidth communication channel, the nRF52840dk supports +the [Segger RTT protocol](rtt). This requires a micro USB cable attached to the +USB debugging port (the same used to flash Tock on the board), and is enabled by +setting the `USB_DEBUGGING` constant to `true` in the [main.rs](src/main.rs) +file. This disables the UART interface. For instructions about how to receive RTT messages on the host, see the -[corresponding capsule](../../../capsules/src/segger_rtt.rs). +[corresponding capsule](../../../capsules/extra/src/segger_rtt.rs). ## Debugging See the [nrf52dk README](../nrf52dk/README.md) for information about debugging the nRF52840dk. + + +[rtt]: https://www.segger.com/products/debug-probes/j-link/technology/about-real-time-transfer/ diff --git a/boards/nordic/nrf52840dk/build.rs b/boards/nordic/nrf52840dk/build.rs deleted file mode 100644 index 49e4dcebfd..0000000000 --- a/boards/nordic/nrf52840dk/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../../kernel_layout.ld"); - println!("cargo:rerun-if-changed=../nrf52840_chip_layout.ld"); -} diff --git a/boards/nordic/nrf52840dk/jtag/gdbinit_pca10040.jlink b/boards/nordic/nrf52840dk/jtag/gdbinit_pca10040.jlink index b86b052f64..8094e76819 100644 --- a/boards/nordic/nrf52840dk/jtag/gdbinit_pca10040.jlink +++ b/boards/nordic/nrf52840dk/jtag/gdbinit_pca10040.jlink @@ -1,25 +1,28 @@ -# -# -# -# J-LINK GDB SERVER initialization -# -# This connects to a GDB Server listening -# for commands on localhost at tcp port 2331 -target remote localhost:2331 -monitor speed 30 -file ../../../../target/thumbv7em-none-eabi/release/nrf52dk -monitor reset -# -# CPU core initialization (to be done by user) -# -# Set the processor mode -# monitor reg cpsr = 0xd3 -# Set auto JTAG speed -monitor speed auto -# Setup GDB FOR FASTER DOWNLOADS -set remote memory-write-packet-size 1024 -set remote memory-write-packet-size fixed -# tui enable -# layout split -# layout service_pending_interrupts -b initialize_ram_jump_to_main +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2018. +# +# +# +# J-LINK GDB SERVER initialization +# +# This connects to a GDB Server listening +# for commands on localhost at tcp port 2331 +target remote localhost:2331 +monitor speed 30 +file ../../../../target/thumbv7em-none-eabi/release/nrf52dk +monitor reset +# +# CPU core initialization (to be done by user) +# +# Set the processor mode +# monitor reg cpsr = 0xd3 +# Set auto JTAG speed +monitor speed auto +# Setup GDB FOR FASTER DOWNLOADS +set remote memory-write-packet-size 1024 +set remote memory-write-packet-size fixed +# tui enable +# layout split +# layout service_pending_interrupts +b initialize_ram_jump_to_main diff --git a/boards/nordic/nrf52840dk/jtag/jdbserver_pca10040.sh b/boards/nordic/nrf52840dk/jtag/jdbserver_pca10040.sh index b4bee90e5f..5edf8b5cb8 100755 --- a/boards/nordic/nrf52840dk/jtag/jdbserver_pca10040.sh +++ b/boards/nordic/nrf52840dk/jtag/jdbserver_pca10040.sh @@ -1 +1,5 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + JLinkGDBServer -device nrf52 -speed 1200 -if swd -AutoConnect 1 -port 2331 diff --git a/boards/nordic/nrf52840dk/layout.ld b/boards/nordic/nrf52840dk/layout.ld index a8da375997..3eccbaa12d 100644 --- a/boards/nordic/nrf52840dk/layout.ld +++ b/boards/nordic/nrf52840dk/layout.ld @@ -1,2 +1,6 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + INCLUDE ../nrf52840_chip_layout.ld INCLUDE ../../kernel_layout.ld diff --git a/boards/nordic/nrf52840dk/src/io.rs b/boards/nordic/nrf52840dk/src/io.rs index a3aa356e7f..14b60de8b8 100644 --- a/boards/nordic/nrf52840dk/src/io.rs +++ b/boards/nordic/nrf52840dk/src/io.rs @@ -1,33 +1,31 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; -use core::panic::PanicInfo; -use cortexm4; -use kernel::debug; use kernel::debug::IoWrite; -use kernel::hil::led; use kernel::hil::uart; use kernel::hil::uart::Configure; -use nrf52840::gpio::Pin; -use crate::CHIP; -use crate::PROCESSES; -use crate::PROCESS_PRINTER; +use nrf52840::uart::{Uarte, UARTE0_BASE}; enum Writer { WriterUart(/* initialized */ bool), - WriterRtt(&'static capsules::segger_rtt::SeggerRttMemory<'static>), + WriterRtt(&'static capsules_extra::segger_rtt::SeggerRttMemory<'static>), } static mut WRITER: Writer = Writer::WriterUart(false); +// Wait a fixed number of cycles to avoid missing characters over the RTT console fn wait() { - for _ in 0..100 { + for _ in 0..1000 { cortexm4::support::nop(); } } /// Set the RTT memory buffer used to output panic messages. pub unsafe fn set_rtt_memory( - rtt_memory: &'static mut capsules::segger_rtt::SeggerRttMemory<'static>, + rtt_memory: &'static capsules_extra::segger_rtt::SeggerRttMemory<'static>, ) { WRITER = Writer::WriterRtt(rtt_memory); } @@ -40,13 +38,13 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { match self { Writer::WriterUart(ref mut initialized) => { // Here, we create a second instance of the Uarte struct. // This is okay because we only call this during a panic, and // we will never actually process the interrupts - let uart = nrf52840::uart::Uarte::new(); + let uart = Uarte::new(UARTE0_BASE); if !*initialized { *initialized = true; let _ = uart.configure(uart::Parameters { @@ -84,6 +82,7 @@ impl IoWrite for Writer { } } }; + buf.len() } } @@ -91,18 +90,27 @@ impl IoWrite for Writer { #[no_mangle] #[panic_handler] /// Panic handler -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(pi: &core::panic::PanicInfo) -> ! { + use core::ptr::{addr_of, addr_of_mut}; + use kernel::debug; + use kernel::hil::led; + use nrf52840::gpio::Pin; + + use crate::CHIP; + use crate::PROCESSES; + use crate::PROCESS_PRINTER; + // The nRF52840DK LEDs (see back of board) let led_kernel_pin = &nrf52840::gpio::GPIOPin::new(Pin::P0_13); let led = &mut led::LedLow::new(led_kernel_pin); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led], writer, pi, &cortexm4::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/nordic/nrf52840dk/src/lib.rs b/boards/nordic/nrf52840dk/src/lib.rs new file mode 100644 index 0000000000..1166341986 --- /dev/null +++ b/boards/nordic/nrf52840dk/src/lib.rs @@ -0,0 +1,926 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Tock kernel for the Nordic Semiconductor nRF52840 development kit (DK). +//! +//! It is based on nRF52840 SoC (Cortex M4 core with a BLE transceiver) with +//! many exported I/O and peripherals. +//! +//! Pin Configuration +//! ------------------- +//! +//! ### `GPIO` +//! +//! | # | Pin | Ix | Header | Arduino | +//! |----|-------|----|--------|---------| +//! | 0 | P1.01 | 33 | P3 1 | D0 | +//! | 1 | P1.02 | 34 | P3 2 | D1 | +//! | 2 | P1.03 | 35 | P3 3 | D2 | +//! | 3 | P1.04 | 36 | P3 4 | D3 | +//! | 4 | P1.05 | 37 | P3 5 | D4 | +//! | 5 | P1.06 | 38 | P3 6 | D5 | +//! | 6 | P1.07 | 39 | P3 7 | D6 | +//! | 7 | P1.08 | 40 | P3 8 | D7 | +//! | 8 | P1.10 | 42 | P4 1 | D8 | +//! | 9 | P1.11 | 43 | P4 2 | D9 | +//! | 10 | P1.12 | 44 | P4 3 | D10 | +//! | 11 | P1.13 | 45 | P4 4 | D11 | +//! | 12 | P1.14 | 46 | P4 5 | D12 | +//! | 13 | P1.15 | 47 | P4 6 | D13 | +//! | 14 | P0.26 | 26 | P4 9 | D14 | +//! | 15 | P0.27 | 27 | P4 10 | D15 | +//! +//! ### `GPIO` / Analog Inputs +//! +//! | # | Pin | Header | Arduino | +//! |----|------------|--------|---------| +//! | 16 | P0.03 AIN1 | P2 1 | A0 | +//! | 17 | P0.04 AIN2 | P2 2 | A1 | +//! | 18 | P0.28 AIN4 | P2 3 | A2 | +//! | 19 | P0.29 AIN5 | P2 4 | A3 | +//! | 20 | P0.30 AIN6 | P2 5 | A4 | +//! | 21 | P0.31 AIN7 | P2 6 | A5 | +//! | 22 | P0.02 AIN0 | P4 8 | AVDD | +//! +//! ### Onboard Functions +//! +//! | Pin | Header | Function | +//! |-------|--------|----------| +//! | P0.05 | P6 3 | UART RTS | +//! | P0.06 | P6 4 | UART TXD | +//! | P0.07 | P6 5 | UART CTS | +//! | P0.08 | P6 6 | UART RXT | +//! | P0.11 | P24 1 | Button 1 | +//! | P0.12 | P24 2 | Button 2 | +//! | P0.13 | P24 3 | LED 1 | +//! | P0.14 | P24 4 | LED 2 | +//! | P0.15 | P24 5 | LED 3 | +//! | P0.16 | P24 6 | LED 4 | +//! | P0.18 | P24 8 | Reset | +//! | P0.19 | P24 9 | SPI CLK | +//! | P0.20 | P24 10 | SPI MOSI | +//! | P0.21 | P24 11 | SPI MISO | +//! | P0.22 | P24 12 | SPI CS | +//! | P0.24 | P24 14 | Button 3 | +//! | P0.25 | P24 15 | Button 4 | +//! | P0.26 | P24 16 | I2C SDA | +//! | P0.27 | P24 17 | I2C SCL | + +#![no_std] +#![deny(missing_docs)] + +use core::ptr::addr_of; + +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_extra::net::ieee802154::MacAddress; +use capsules_extra::net::ipv6::ip_utils::IPAddr; +use kernel::component::Component; +use kernel::hil::led::LedLow; +use kernel::hil::time::Counter; +#[allow(unused_imports)] +use kernel::hil::usb::Client; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::scheduler::round_robin::RoundRobinSched; +#[allow(unused_imports)] +use kernel::{capabilities, create_capability, debug, debug_gpio, debug_verbose, static_init}; +use nrf52840::gpio::Pin; +use nrf52840::interrupt_service::Nrf52840DefaultPeripherals; +use nrf52_components::{UartChannel, UartPins}; + +// The nRF52840DK LEDs (see back of board) +const LED1_PIN: Pin = Pin::P0_13; +const LED2_PIN: Pin = Pin::P0_14; +const LED3_PIN: Pin = Pin::P0_15; +const LED4_PIN: Pin = Pin::P0_16; + +// The nRF52840DK buttons (see back of board) +const BUTTON1_PIN: Pin = Pin::P0_11; +const BUTTON2_PIN: Pin = Pin::P0_12; +const BUTTON3_PIN: Pin = Pin::P0_24; +const BUTTON4_PIN: Pin = Pin::P0_25; +const BUTTON_RST_PIN: Pin = Pin::P0_18; + +const UART_RTS: Option = Some(Pin::P0_05); +const UART_TXD: Pin = Pin::P0_06; +const UART_CTS: Option = Some(Pin::P0_07); +const UART_RXD: Pin = Pin::P0_08; + +const SPI_MOSI: Pin = Pin::P0_20; +const SPI_MISO: Pin = Pin::P0_21; +const SPI_CLK: Pin = Pin::P0_19; +const SPI_CS: Pin = Pin::P0_22; + +const SPI_MX25R6435F_CHIP_SELECT: Pin = Pin::P0_17; +const SPI_MX25R6435F_WRITE_PROTECT_PIN: Pin = Pin::P0_22; +const SPI_MX25R6435F_HOLD_PIN: Pin = Pin::P0_23; + +/// I2C pins +const I2C_SDA_PIN: Pin = Pin::P0_26; +const I2C_SCL_PIN: Pin = Pin::P0_27; + +// Constants related to the configuration of the 15.4 network stack +const PAN_ID: u16 = 0xABCD; +const DST_MAC_ADDR: capsules_extra::net::ieee802154::MacAddress = + capsules_extra::net::ieee802154::MacAddress::Short(49138); +const DEFAULT_CTX_PREFIX_LEN: u8 = 8; //Length of context for 6LoWPAN compression +const DEFAULT_CTX_PREFIX: [u8; 16] = [0x0_u8; 16]; //Context for 6LoWPAN Compression + +/// Debug Writer +pub mod io; + +// Whether to use UART debugging or Segger RTT (USB) debugging. +// - Set to false to use UART. +// - Set to true to use Segger RTT over USB. +const USB_DEBUGGING: bool = false; + +/// This platform's chip type: +pub type Chip = nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>; + +/// Number of concurrent processes this platform supports. +pub const NUM_PROCS: usize = 8; + +/// Process array of this platform. +pub static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = + [None; NUM_PROCS]; + +static mut CHIP: Option<&'static nrf52840::chip::NRF52> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; + +/// Dummy buffer that causes the linker to reserve enough space for the stack. +#[no_mangle] +#[link_section = ".stack_buffer"] +pub static mut STACK_MEMORY: [u8; 0x2000] = [0; 0x2000]; + +//------------------------------------------------------------------------------ +// SYSCALL DRIVER TYPE DEFINITIONS +//------------------------------------------------------------------------------ + +type AlarmDriver = components::alarm::AlarmDriverComponentType>; +type RngDriver = components::rng::RngComponentType>; + +// TicKV +type Mx25r6435f = components::mx25r6435f::Mx25r6435fComponentType< + nrf52840::spi::SPIM<'static>, + nrf52840::gpio::GPIOPin<'static>, + nrf52840::rtc::Rtc<'static>, +>; +const TICKV_PAGE_SIZE: usize = + core::mem::size_of::<::Page>(); +type Siphasher24 = components::siphash::Siphasher24ComponentType; +type TicKVDedicatedFlash = + components::tickv::TicKVDedicatedFlashComponentType; +type TicKVKVStore = components::kv::TicKVKVStoreComponentType< + TicKVDedicatedFlash, + capsules_extra::tickv::TicKVKeyType, +>; +type KVStorePermissions = components::kv::KVStorePermissionsComponentType; +type VirtualKVPermissions = components::kv::VirtualKVPermissionsComponentType; +type KVDriver = components::kv::KVDriverComponentType; + +// Temperature +type TemperatureDriver = + components::temperature::TemperatureComponentType>; + +// IEEE 802.15.4 +type Ieee802154MacDevice = components::ieee802154::Ieee802154ComponentMacDeviceType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; +/// Userspace 802.15.4 driver with in-kernel packet framing and MAC layer. +pub type Ieee802154Driver = components::ieee802154::Ieee802154ComponentType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; + +// EUI64 +/// Userspace EUI64 driver. +pub type Eui64Driver = components::eui64::Eui64ComponentType; + +/// Supported drivers by the platform +pub struct Platform { + ble_radio: &'static capsules_extra::ble_advertising_driver::BLE< + 'static, + nrf52840::ble_radio::Radio<'static>, + VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, + >, + button: &'static capsules_core::button::Button<'static, nrf52840::gpio::GPIOPin<'static>>, + pconsole: &'static capsules_core::process_console::ProcessConsole< + 'static, + { capsules_core::process_console::DEFAULT_COMMAND_HISTORY_LEN }, + VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, + components::process_console::Capability, + >, + console: &'static capsules_core::console::Console<'static>, + gpio: &'static capsules_core::gpio::GPIO<'static, nrf52840::gpio::GPIOPin<'static>>, + led: &'static capsules_core::led::LedDriver< + 'static, + kernel::hil::led::LedLow<'static, nrf52840::gpio::GPIOPin<'static>>, + 4, + >, + rng: &'static RngDriver, + adc: &'static capsules_core::adc::AdcDedicated<'static, nrf52840::adc::Adc<'static>>, + temp: &'static TemperatureDriver, + /// The IPC driver. + pub ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + analog_comparator: &'static capsules_extra::analog_comparator::AnalogComparator< + 'static, + nrf52840::acomp::Comparator<'static>, + >, + alarm: &'static AlarmDriver, + i2c_master_slave: &'static capsules_core::i2c_master_slave_driver::I2CMasterSlaveDriver< + 'static, + nrf52840::i2c::TWI<'static>, + >, + spi_controller: &'static capsules_core::spi_controller::Spi< + 'static, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice< + 'static, + nrf52840::spi::SPIM<'static>, + >, + >, + kv_driver: &'static KVDriver, + scheduler: &'static RoundRobinSched<'static>, + systick: cortexm4::systick::SysTick, +} + +impl SyscallDriverLookup for Platform { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::rng::DRIVER_NUM => f(Some(self.rng)), + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_extra::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temp)), + capsules_extra::analog_comparator::DRIVER_NUM => f(Some(self.analog_comparator)), + kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), + capsules_core::i2c_master_slave_driver::DRIVER_NUM => f(Some(self.i2c_master_slave)), + capsules_core::spi_controller::DRIVER_NUM => f(Some(self.spi_controller)), + capsules_extra::kv_driver::DRIVER_NUM => f(Some(self.kv_driver)), + _ => f(None), + } + } +} + +impl KernelResources for Platform { + type SyscallDriverLookup = Self; + type SyscallFilter = (); + type ProcessFault = (); + type Scheduler = RoundRobinSched<'static>; + type SchedulerTimer = cortexm4::systick::SysTick; + type WatchDog = (); + type ContextSwitchCallback = (); + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + &() + } + fn process_fault(&self) -> &Self::ProcessFault { + &() + } + fn scheduler(&self) -> &Self::Scheduler { + self.scheduler + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + &self.systick + } + fn watchdog(&self) -> &Self::WatchDog { + &() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + &() + } +} + +/// Create the capsules needed for the in-kernel UDP and 15.4 stack. +pub unsafe fn ieee802154_udp( + board_kernel: &'static kernel::Kernel, + nrf52840_peripherals: &'static Nrf52840DefaultPeripherals<'static>, + mux_alarm: &'static MuxAlarm, +) -> ( + &'static Eui64Driver, + &'static Ieee802154Driver, + &'static capsules_extra::net::udp::UDPDriver<'static>, +) { + //-------------------------------------------------------------------------- + // AES + //-------------------------------------------------------------------------- + + let aes_mux = + components::ieee802154::MuxAes128ccmComponent::new(&nrf52840_peripherals.nrf52.ecb) + .finalize(components::mux_aes128ccm_component_static!( + nrf52840::aes::AesECB + )); + + //-------------------------------------------------------------------------- + // 802.15.4 + //-------------------------------------------------------------------------- + + let device_id = nrf52840::ficr::FICR_INSTANCE.id(); + let device_id_bottom_16: u16 = u16::from_le_bytes([device_id[0], device_id[1]]); + + let eui64_driver = components::eui64::Eui64Component::new(u64::from_le_bytes(device_id)) + .finalize(components::eui64_component_static!()); + + let (ieee802154_driver, mux_mac) = components::ieee802154::Ieee802154Component::new( + board_kernel, + capsules_extra::ieee802154::DRIVER_NUM, + &nrf52840_peripherals.ieee802154_radio, + aes_mux, + PAN_ID, + device_id_bottom_16, + device_id, + ) + .finalize(components::ieee802154_component_static!( + nrf52840::ieee802154_radio::Radio, + nrf52840::aes::AesECB<'static> + )); + + //-------------------------------------------------------------------------- + // UDP + //-------------------------------------------------------------------------- + + let local_ip_ifaces = static_init!( + [IPAddr; 3], + [ + IPAddr::generate_from_mac(capsules_extra::net::ieee802154::MacAddress::Long(device_id)), + IPAddr([ + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, + 0x1e, 0x1f, + ]), + IPAddr::generate_from_mac(capsules_extra::net::ieee802154::MacAddress::Short( + device_id_bottom_16 + )), + ] + ); + + let (udp_send_mux, udp_recv_mux, udp_port_table) = components::udp_mux::UDPMuxComponent::new( + mux_mac, + DEFAULT_CTX_PREFIX_LEN, + DEFAULT_CTX_PREFIX, + DST_MAC_ADDR, + MacAddress::Long(device_id), + local_ip_ifaces, + mux_alarm, + ) + .finalize(components::udp_mux_component_static!( + nrf52840::rtc::Rtc, + Ieee802154MacDevice + )); + + // UDP driver initialization happens here + let udp_driver = components::udp_driver::UDPDriverComponent::new( + board_kernel, + capsules_extra::net::udp::driver::DRIVER_NUM, + udp_send_mux, + udp_recv_mux, + udp_port_table, + local_ip_ifaces, + ) + .finalize(components::udp_driver_component_static!(nrf52840::rtc::Rtc)); + + (eui64_driver, ieee802154_driver, udp_driver) +} + +/// This is in a separate, inline(never) function so that its stack frame is +/// removed when this function returns. Otherwise, the stack space used for +/// these static_inits is wasted. +#[inline(never)] +pub unsafe fn start() -> ( + &'static kernel::Kernel, + Platform, + &'static Chip, + &'static Nrf52840DefaultPeripherals<'static>, + &'static MuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, +) { + //-------------------------------------------------------------------------- + // INITIAL SETUP + //-------------------------------------------------------------------------- + + // Apply errata fixes and enable interrupts. + nrf52840::init(); + + // Set up peripheral drivers. Called in separate function to reduce stack + // usage. + let ieee802154_ack_buf = static_init!( + [u8; nrf52840::ieee802154_radio::ACK_BUF_SIZE], + [0; nrf52840::ieee802154_radio::ACK_BUF_SIZE] + ); + // Initialize chip peripheral drivers + let nrf52840_peripherals = static_init!( + Nrf52840DefaultPeripherals, + Nrf52840DefaultPeripherals::new(ieee802154_ack_buf) + ); + + // Set up circular peripheral dependencies. + nrf52840_peripherals.init(); + let base_peripherals = &nrf52840_peripherals.nrf52; + + // Configure kernel debug GPIOs as early as possible. + kernel::debug::assign_gpios( + Some(&nrf52840_peripherals.gpio_port[LED1_PIN]), + Some(&nrf52840_peripherals.gpio_port[LED2_PIN]), + Some(&nrf52840_peripherals.gpio_port[LED3_PIN]), + ); + + // Choose the channel for serial output. This board can be configured to use + // either the Segger RTT channel or via UART with traditional TX/RX GPIO + // pins. + let uart_channel = if USB_DEBUGGING { + // Initialize early so any panic beyond this point can use the RTT + // memory object. + let mut rtt_memory_refs = components::segger_rtt::SeggerRttMemoryComponent::new() + .finalize(components::segger_rtt_memory_component_static!()); + + // XXX: This is inherently unsafe as it aliases the mutable reference to + // rtt_memory. This aliases reference is only used inside a panic + // handler, which should be OK, but maybe we should use a const + // reference to rtt_memory and leverage interior mutability instead. + self::io::set_rtt_memory(&*rtt_memory_refs.get_rtt_memory_ptr()); + + UartChannel::Rtt(rtt_memory_refs) + } else { + UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD)) + }; + + // Setup space to store the core kernel data structure. + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); + + // Create (and save for panic debugging) a chip object to setup low-level + // resources (e.g. MPU, systick). + let chip = static_init!(Chip, nrf52840::chip::NRF52::new(nrf52840_peripherals)); + CHIP = Some(chip); + + // Do nRF configuration and setup. This is shared code with other nRF-based + // platforms. + nrf52_components::startup::NrfStartupComponent::new( + false, + BUTTON_RST_PIN, + nrf52840::uicr::Regulator0Output::DEFAULT, + &base_peripherals.nvmc, + ) + .finalize(()); + + //-------------------------------------------------------------------------- + // CAPABILITIES + //-------------------------------------------------------------------------- + + // Create capabilities that the board needs to call certain protected kernel + // functions. + let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); + let gpio_port = &nrf52840_peripherals.gpio_port; + + //-------------------------------------------------------------------------- + // GPIO + //-------------------------------------------------------------------------- + + // Expose the D0-D13 Arduino GPIO pins to userspace. + let gpio = components::gpio::GpioComponent::new( + board_kernel, + capsules_core::gpio::DRIVER_NUM, + components::gpio_component_helper!( + nrf52840::gpio::GPIOPin, + 0 => &nrf52840_peripherals.gpio_port[Pin::P1_01], + 1 => &nrf52840_peripherals.gpio_port[Pin::P1_02], + 2 => &nrf52840_peripherals.gpio_port[Pin::P1_03], + 3 => &nrf52840_peripherals.gpio_port[Pin::P1_04], + 4 => &nrf52840_peripherals.gpio_port[Pin::P1_05], + 5 => &nrf52840_peripherals.gpio_port[Pin::P1_06], + 6 => &nrf52840_peripherals.gpio_port[Pin::P1_07], + 7 => &nrf52840_peripherals.gpio_port[Pin::P1_08], + // Avoid exposing the I2C pins to userspace, as these are used in + // some tutorials (e.g., `nrf52840dk-thread-tutorial`). + // + // In the future we might want to make this configurable. + // + // 8 => &nrf52840_peripherals.gpio_port[Pin::P1_10], + // 9 => &nrf52840_peripherals.gpio_port[Pin::P1_11], + 10 => &nrf52840_peripherals.gpio_port[Pin::P1_12], + 11 => &nrf52840_peripherals.gpio_port[Pin::P1_13], + 12 => &nrf52840_peripherals.gpio_port[Pin::P1_14], + 13 => &nrf52840_peripherals.gpio_port[Pin::P1_15], + ), + ) + .finalize(components::gpio_component_static!(nrf52840::gpio::GPIOPin)); + + //-------------------------------------------------------------------------- + // BUTTONS + //-------------------------------------------------------------------------- + + let button = components::button::ButtonComponent::new( + board_kernel, + capsules_core::button::DRIVER_NUM, + components::button_component_helper!( + nrf52840::gpio::GPIOPin, + ( + &nrf52840_peripherals.gpio_port[BUTTON1_PIN], + kernel::hil::gpio::ActivationMode::ActiveLow, + kernel::hil::gpio::FloatingState::PullUp + ), + ( + &nrf52840_peripherals.gpio_port[BUTTON2_PIN], + kernel::hil::gpio::ActivationMode::ActiveLow, + kernel::hil::gpio::FloatingState::PullUp + ), + ( + &nrf52840_peripherals.gpio_port[BUTTON3_PIN], + kernel::hil::gpio::ActivationMode::ActiveLow, + kernel::hil::gpio::FloatingState::PullUp + ), + ( + &nrf52840_peripherals.gpio_port[BUTTON4_PIN], + kernel::hil::gpio::ActivationMode::ActiveLow, + kernel::hil::gpio::FloatingState::PullUp + ) + ), + ) + .finalize(components::button_component_static!( + nrf52840::gpio::GPIOPin + )); + + //-------------------------------------------------------------------------- + // LEDs + //-------------------------------------------------------------------------- + + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( + LedLow<'static, nrf52840::gpio::GPIOPin>, + LedLow::new(&nrf52840_peripherals.gpio_port[LED1_PIN]), + LedLow::new(&nrf52840_peripherals.gpio_port[LED2_PIN]), + LedLow::new(&nrf52840_peripherals.gpio_port[LED3_PIN]), + LedLow::new(&nrf52840_peripherals.gpio_port[LED4_PIN]), + )); + + //-------------------------------------------------------------------------- + // TIMER + //-------------------------------------------------------------------------- + + let rtc = &base_peripherals.rtc; + let _ = rtc.start(); + let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) + .finalize(components::alarm_mux_component_static!(nrf52840::rtc::Rtc)); + let alarm = components::alarm::AlarmDriverComponent::new( + board_kernel, + capsules_core::alarm::DRIVER_NUM, + mux_alarm, + ) + .finalize(components::alarm_component_static!(nrf52840::rtc::Rtc)); + + //-------------------------------------------------------------------------- + // UART & CONSOLE & DEBUG + //-------------------------------------------------------------------------- + + let uart_channel = nrf52_components::UartChannelComponent::new( + uart_channel, + mux_alarm, + &base_peripherals.uarte0, + ) + .finalize(nrf52_components::uart_channel_component_static!( + nrf52840::rtc::Rtc + )); + + // Tool for displaying information about processes. + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); + PROCESS_PRINTER = Some(process_printer); + + // Virtualize the UART channel for the console and for kernel debug. + let uart_mux = components::console::UartMuxComponent::new(uart_channel, 115200) + .finalize(components::uart_mux_component_static!()); + + // Create the process console, an interactive terminal for managing + // processes. + let pconsole = components::process_console::ProcessConsoleComponent::new( + board_kernel, + uart_mux, + mux_alarm, + process_printer, + Some(cortexm4::support::reset), + ) + .finalize(components::process_console_component_static!( + nrf52840::rtc::Rtc<'static> + )); + + // Setup the serial console for userspace. + let console = components::console::ConsoleComponent::new( + board_kernel, + capsules_core::console::DRIVER_NUM, + uart_mux, + ) + .finalize(components::console_component_static!()); + + // Create the debugger object that handles calls to `debug!()`. + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); + + //-------------------------------------------------------------------------- + // BLE + //-------------------------------------------------------------------------- + + let ble_radio = components::ble::BLEComponent::new( + board_kernel, + capsules_extra::ble_advertising_driver::DRIVER_NUM, + &base_peripherals.ble_radio, + mux_alarm, + ) + .finalize(components::ble_component_static!( + nrf52840::rtc::Rtc, + nrf52840::ble_radio::Radio + )); + + //-------------------------------------------------------------------------- + // TEMPERATURE (internal) + //-------------------------------------------------------------------------- + + let temp = components::temperature::TemperatureComponent::new( + board_kernel, + capsules_extra::temperature::DRIVER_NUM, + &base_peripherals.temp, + ) + .finalize(components::temperature_component_static!( + nrf52840::temperature::Temp + )); + + //-------------------------------------------------------------------------- + // RANDOM NUMBER GENERATOR + //-------------------------------------------------------------------------- + + let rng = components::rng::RngComponent::new( + board_kernel, + capsules_core::rng::DRIVER_NUM, + &base_peripherals.trng, + ) + .finalize(components::rng_component_static!(nrf52840::trng::Trng)); + + //-------------------------------------------------------------------------- + // ADC + //-------------------------------------------------------------------------- + + let adc_channels = static_init!( + [nrf52840::adc::AdcChannelSetup; 6], + [ + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput1), + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput2), + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput4), + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput5), + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput6), + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput7), + ] + ); + let adc = components::adc::AdcDedicatedComponent::new( + &base_peripherals.adc, + adc_channels, + board_kernel, + capsules_core::adc::DRIVER_NUM, + ) + .finalize(components::adc_dedicated_component_static!( + nrf52840::adc::Adc + )); + + //-------------------------------------------------------------------------- + // SPI + //-------------------------------------------------------------------------- + + let mux_spi = components::spi::SpiMuxComponent::new(&base_peripherals.spim0) + .finalize(components::spi_mux_component_static!(nrf52840::spi::SPIM)); + + // Create the SPI system call capsule. + let spi_controller = components::spi::SpiSyscallComponent::new( + board_kernel, + mux_spi, + &gpio_port[SPI_CS], + capsules_core::spi_controller::DRIVER_NUM, + ) + .finalize(components::spi_syscall_component_static!( + nrf52840::spi::SPIM + )); + + base_peripherals.spim0.configure( + nrf52840::pinmux::Pinmux::new(SPI_MOSI as u32), + nrf52840::pinmux::Pinmux::new(SPI_MISO as u32), + nrf52840::pinmux::Pinmux::new(SPI_CLK as u32), + ); + + //-------------------------------------------------------------------------- + // ONBOARD EXTERNAL FLASH + //-------------------------------------------------------------------------- + + let mx25r6435f = components::mx25r6435f::Mx25r6435fComponent::new( + Some(&gpio_port[SPI_MX25R6435F_WRITE_PROTECT_PIN]), + Some(&gpio_port[SPI_MX25R6435F_HOLD_PIN]), + &gpio_port[SPI_MX25R6435F_CHIP_SELECT] as &dyn kernel::hil::gpio::Pin, + mux_alarm, + mux_spi, + ) + .finalize(components::mx25r6435f_component_static!( + nrf52840::spi::SPIM, + nrf52840::gpio::GPIOPin, + nrf52840::rtc::Rtc + )); + + //-------------------------------------------------------------------------- + // TICKV + //-------------------------------------------------------------------------- + + // Static buffer to use when reading/writing flash for TicKV. + let page_buffer = static_init!( + ::Page, + ::Page::default() + ); + + // SipHash for creating TicKV hashed keys. + let sip_hash = components::siphash::Siphasher24Component::new() + .finalize(components::siphasher24_component_static!()); + + // TicKV with Tock wrapper/interface. + let tickv = components::tickv::TicKVDedicatedFlashComponent::new( + sip_hash, + mx25r6435f, + 0, // start at the beginning of the flash chip + (capsules_extra::mx25r6435f::SECTOR_SIZE as usize) * 32, // arbitrary size of 32 pages + page_buffer, + ) + .finalize(components::tickv_dedicated_flash_component_static!( + Mx25r6435f, + Siphasher24, + TICKV_PAGE_SIZE, + )); + + // KVSystem interface to KV (built on TicKV). + let tickv_kv_store = components::kv::TicKVKVStoreComponent::new(tickv).finalize( + components::tickv_kv_store_component_static!( + TicKVDedicatedFlash, + capsules_extra::tickv::TicKVKeyType, + ), + ); + + let kv_store_permissions = components::kv::KVStorePermissionsComponent::new(tickv_kv_store) + .finalize(components::kv_store_permissions_component_static!( + TicKVKVStore + )); + + // Share the KV stack with a mux. + let mux_kv = components::kv::KVPermissionsMuxComponent::new(kv_store_permissions).finalize( + components::kv_permissions_mux_component_static!(KVStorePermissions), + ); + + // Create a virtual component for the userspace driver. + let virtual_kv_driver = components::kv::VirtualKVPermissionsComponent::new(mux_kv).finalize( + components::virtual_kv_permissions_component_static!(KVStorePermissions), + ); + + // Userspace driver for KV. + let kv_driver = components::kv::KVDriverComponent::new( + virtual_kv_driver, + board_kernel, + capsules_extra::kv_driver::DRIVER_NUM, + ) + .finalize(components::kv_driver_component_static!( + VirtualKVPermissions + )); + + //-------------------------------------------------------------------------- + // I2C CONTROLLER/TARGET + //-------------------------------------------------------------------------- + + let i2c_master_slave = components::i2c::I2CMasterSlaveDriverComponent::new( + board_kernel, + capsules_core::i2c_master_slave_driver::DRIVER_NUM, + &base_peripherals.twi1, + ) + .finalize(components::i2c_master_slave_component_static!( + nrf52840::i2c::TWI + )); + + base_peripherals.twi1.configure( + nrf52840::pinmux::Pinmux::new(I2C_SCL_PIN as u32), + nrf52840::pinmux::Pinmux::new(I2C_SDA_PIN as u32), + ); + base_peripherals.twi1.set_speed(nrf52840::i2c::Speed::K400); + + //-------------------------------------------------------------------------- + // ANALOG COMPARATOR + //-------------------------------------------------------------------------- + + // Initialize AC using AIN5 (P0.29) as VIN+ and VIN- as AIN0 (P0.02) + // These are hardcoded pin assignments specified in the driver + let analog_comparator = components::analog_comparator::AnalogComparatorComponent::new( + &base_peripherals.acomp, + components::analog_comparator_component_helper!( + nrf52840::acomp::Channel, + &*addr_of!(nrf52840::acomp::CHANNEL_AC0) + ), + board_kernel, + capsules_extra::analog_comparator::DRIVER_NUM, + ) + .finalize(components::analog_comparator_component_static!( + nrf52840::acomp::Comparator + )); + + //-------------------------------------------------------------------------- + // NRF CLOCK SETUP + //-------------------------------------------------------------------------- + + nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); + + //-------------------------------------------------------------------------- + // USB EXAMPLES + //-------------------------------------------------------------------------- + // Uncomment to experiment with this. + + // // Create the strings we include in the USB descriptor. + // let strings = static_init!( + // [&str; 3], + // [ + // "Nordic Semiconductor", // Manufacturer + // "nRF52840dk - TockOS", // Product + // "serial0001", // Serial number + // ] + // ); + + // CTAP Example + // + // let (ctap, _ctap_driver) = components::ctap::CtapComponent::new( + // board_kernel, + // capsules_extra::ctap::DRIVER_NUM, + // &nrf52840_peripherals.usbd, + // 0x1915, // Nordic Semiconductor + // 0x503a, // lowRISC generic FS USB + // strings, + // ) + // .finalize(components::ctap_component_static!(nrf52840::usbd::Usbd)); + + // ctap.enable(); + // ctap.attach(); + + // // Keyboard HID Example + // type UsbHw = nrf52840::usbd::Usbd<'static>; + // let usb_device = &nrf52840_peripherals.usbd; + + // let (keyboard_hid, keyboard_hid_driver) = components::keyboard_hid::KeyboardHidComponent::new( + // board_kernel, + // capsules_core::driver::NUM::KeyboardHid as usize, + // usb_device, + // 0x1915, // Nordic Semiconductor + // 0x503a, + // strings, + // ) + // .finalize(components::keyboard_hid_component_static!(UsbHw)); + + // keyboard_hid.enable(); + // keyboard_hid.attach(); + + //-------------------------------------------------------------------------- + // PLATFORM SETUP, SCHEDULER, AND START KERNEL LOOP + //-------------------------------------------------------------------------- + + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); + + let platform = Platform { + button, + ble_radio, + pconsole, + console, + led, + gpio, + rng, + adc, + temp, + alarm, + analog_comparator, + ipc: kernel::ipc::IPC::new( + board_kernel, + kernel::ipc::DRIVER_NUM, + &memory_allocation_capability, + ), + i2c_master_slave, + spi_controller, + kv_driver, + scheduler, + systick: cortexm4::systick::SysTick::new_with_calibration(64000000), + }; + + let _ = platform.pconsole.start(); + base_peripherals.adc.calibrate(); + + debug!("Initialization complete. Entering main loop\r"); + debug!("{}", &*addr_of!(nrf52840::ficr::FICR_INSTANCE)); + + ( + board_kernel, + platform, + chip, + nrf52840_peripherals, + mux_alarm, + ) +} diff --git a/boards/nordic/nrf52840dk/src/main.rs b/boards/nordic/nrf52840dk/src/main.rs index 668d4f2561..1462e653ba 100644 --- a/boards/nordic/nrf52840dk/src/main.rs +++ b/boards/nordic/nrf52840dk/src/main.rs @@ -1,67 +1,8 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tock kernel for the Nordic Semiconductor nRF52840 development kit (DK). -//! -//! It is based on nRF52840 SoC (Cortex M4 core with a BLE transceiver) with -//! many exported I/O and peripherals. -//! -//! Pin Configuration -//! ------------------- -//! -//! ### `GPIO` -//! -//! | # | Pin | Ix | Header | Arduino | -//! |----|-------|----|--------|---------| -//! | 0 | P1.01 | 33 | P3 1 | D0 | -//! | 1 | P1.02 | 34 | P3 2 | D1 | -//! | 2 | P1.03 | 35 | P3 3 | D2 | -//! | 3 | P1.04 | 36 | P3 4 | D3 | -//! | 4 | P1.05 | 37 | P3 5 | D4 | -//! | 5 | P1.06 | 38 | P3 6 | D5 | -//! | 6 | P1.07 | 39 | P3 7 | D6 | -//! | 7 | P1.08 | 40 | P3 8 | D7 | -//! | 8 | P1.10 | 42 | P4 1 | D8 | -//! | 9 | P1.11 | 43 | P4 2 | D9 | -//! | 10 | P1.12 | 44 | P4 3 | D10 | -//! | 11 | P1.13 | 45 | P4 4 | D11 | -//! | 12 | P1.14 | 46 | P4 5 | D12 | -//! | 13 | P1.15 | 47 | P4 6 | D13 | -//! | 14 | P0.26 | 26 | P4 9 | D14 | -//! | 15 | P0.27 | 27 | P4 10 | D15 | -//! -//! ### `GPIO` / Analog Inputs -//! -//! | # | Pin | Header | Arduino | -//! |----|------------|--------|---------| -//! | 16 | P0.03 AIN1 | P2 1 | A0 | -//! | 17 | P0.04 AIN2 | P2 2 | A1 | -//! | 18 | P0.28 AIN4 | P2 3 | A2 | -//! | 19 | P0.29 AIN5 | P2 4 | A3 | -//! | 20 | P0.30 AIN6 | P2 5 | A4 | -//! | 21 | P0.31 AIN7 | P2 6 | A5 | -//! | 22 | P0.02 AIN0 | P4 8 | AVDD | -//! -//! ### Onboard Functions -//! -//! | Pin | Header | Function | -//! |-------|--------|----------| -//! | P0.05 | P6 3 | UART RTS | -//! | P0.06 | P6 4 | UART TXD | -//! | P0.07 | P6 5 | UART CTS | -//! | P0.08 | P6 6 | UART RXT | -//! | P0.11 | P24 1 | Button 1 | -//! | P0.12 | P24 2 | Button 2 | -//! | P0.13 | P24 3 | LED 1 | -//! | P0.14 | P24 4 | LED 2 | -//! | P0.15 | P24 5 | LED 3 | -//! | P0.16 | P24 6 | LED 4 | -//! | P0.18 | P24 8 | Reset | -//! | P0.19 | P24 9 | SPI CLK | -//! | P0.20 | P24 10 | SPI MOSI | -//! | P0.21 | P24 11 | SPI MISO | -//! | P0.22 | P24 12 | SPI CS | -//! | P0.24 | P24 14 | Button 3 | -//! | P0.25 | P24 15 | Button 4 | -//! | P0.26 | P24 16 | I2C SDA | -//! | P0.27 | P24 17 | I2C SCL | #![no_std] // Disable this attribute when documenting, as a workaround for @@ -69,132 +10,23 @@ #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] -use capsules::i2c_master_slave_driver::I2CMasterSlaveDriver; -use capsules::net::ieee802154::MacAddress; -use capsules::net::ipv6::ip_utils::IPAddr; -use capsules::virtual_aes_ccm::MuxAES128CCM; -use capsules::virtual_alarm::VirtualMuxAlarm; -use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; -use kernel::hil::i2c::{I2CMaster, I2CSlave}; -use kernel::hil::led::LedLow; -use kernel::hil::symmetric_encryption::AES128; -use kernel::hil::time::Counter; -#[allow(unused_imports)] -use kernel::hil::usb::Client; -use kernel::platform::{KernelResources, SyscallDriverLookup}; -use kernel::scheduler::round_robin::RoundRobinSched; -#[allow(unused_imports)] -use kernel::{capabilities, create_capability, debug, debug_gpio, debug_verbose, static_init}; -use nrf52840::gpio::Pin; -use nrf52840::interrupt_service::Nrf52840DefaultPeripherals; -use nrf52_components::{self, UartChannel, UartPins}; - -// The nRF52840DK LEDs (see back of board) -const LED1_PIN: Pin = Pin::P0_13; -const LED2_PIN: Pin = Pin::P0_14; -const LED3_PIN: Pin = Pin::P0_15; -const LED4_PIN: Pin = Pin::P0_16; - -// The nRF52840DK buttons (see back of board) -const BUTTON1_PIN: Pin = Pin::P0_11; -const BUTTON2_PIN: Pin = Pin::P0_12; -const BUTTON3_PIN: Pin = Pin::P0_24; -const BUTTON4_PIN: Pin = Pin::P0_25; -const BUTTON_RST_PIN: Pin = Pin::P0_18; - -const UART_RTS: Option = Some(Pin::P0_05); -const UART_TXD: Pin = Pin::P0_06; -const UART_CTS: Option = Some(Pin::P0_07); -const UART_RXD: Pin = Pin::P0_08; - -const SPI_MOSI: Pin = Pin::P0_20; -const SPI_MISO: Pin = Pin::P0_21; -const SPI_CLK: Pin = Pin::P0_19; -const SPI_CS: Pin = Pin::P0_22; - -const SPI_MX25R6435F_CHIP_SELECT: Pin = Pin::P0_17; -const SPI_MX25R6435F_WRITE_PROTECT_PIN: Pin = Pin::P0_22; -const SPI_MX25R6435F_HOLD_PIN: Pin = Pin::P0_23; - -/// I2C pins -const I2C_SDA_PIN: Pin = Pin::P0_26; -const I2C_SCL_PIN: Pin = Pin::P0_27; +use core::ptr::addr_of_mut; -// Constants related to the configuration of the 15.4 network stack -const PAN_ID: u16 = 0xABCD; -const DST_MAC_ADDR: capsules::net::ieee802154::MacAddress = - capsules::net::ieee802154::MacAddress::Short(49138); -const DEFAULT_CTX_PREFIX_LEN: u8 = 8; //Length of context for 6LoWPAN compression -const DEFAULT_CTX_PREFIX: [u8; 16] = [0x0 as u8; 16]; //Context for 6LoWPAN Compression - -/// Debug Writer -pub mod io; - -// Whether to use UART debugging or Segger RTT (USB) debugging. -// - Set to false to use UART. -// - Set to true to use Segger RTT over USB. -const USB_DEBUGGING: bool = false; +use kernel::debug; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::{capabilities, create_capability}; +use nrf52840dk_lib::{self, PROCESSES}; // State for loading and holding applications. // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; - -// Number of concurrent processes this platform supports. -const NUM_PROCS: usize = 8; - -static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = - [None; NUM_PROCS]; - -static mut CHIP: Option<&'static nrf52840::chip::NRF52> = None; -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; - -/// Dummy buffer that causes the linker to reserve enough space for the stack. -#[no_mangle] -#[link_section = ".stack_buffer"] -pub static mut STACK_MEMORY: [u8; 0x2000] = [0; 0x2000]; - -/// Supported drivers by the platform -pub struct Platform { - ble_radio: &'static capsules::ble_advertising_driver::BLE< - 'static, - nrf52840::ble_radio::Radio<'static>, - VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, - >, - ieee802154_radio: &'static capsules::ieee802154::RadioDriver<'static>, - button: &'static capsules::button::Button<'static, nrf52840::gpio::GPIOPin<'static>>, - pconsole: &'static capsules::process_console::ProcessConsole< - 'static, - VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, - components::process_console::Capability, - >, - console: &'static capsules::console::Console<'static>, - gpio: &'static capsules::gpio::GPIO<'static, nrf52840::gpio::GPIOPin<'static>>, - led: &'static capsules::led::LedDriver< - 'static, - kernel::hil::led::LedLow<'static, nrf52840::gpio::GPIOPin<'static>>, - 4, - >, - rng: &'static capsules::rng::RngDriver<'static>, - temp: &'static capsules::temperature::TemperatureSensor<'static>, - ipc: kernel::ipc::IPC, - analog_comparator: &'static capsules::analog_comparator::AnalogComparator< - 'static, - nrf52840::acomp::Comparator<'static>, - >, - alarm: &'static capsules::alarm::AlarmDriver< - 'static, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, - >, - nonvolatile_storage: &'static capsules::nonvolatile_storage_driver::NonvolatileStorage<'static>, - udp_driver: &'static capsules::net::udp::UDPDriver<'static>, - i2c_master_slave: &'static capsules::i2c_master_slave_driver::I2CMasterSlaveDriver<'static>, - spi_controller: &'static capsules::spi_controller::Spi< - 'static, - capsules::virtual_spi::VirtualSpiMasterDevice<'static, nrf52840::spi::SPIM>, - >, - scheduler: &'static RoundRobinSched<'static>, - systick: cortexm4::systick::SysTick, +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; + +struct Platform { + base: nrf52840dk_lib::Platform, + eui64_driver: &'static nrf52840dk_lib::Eui64Driver, + ieee802154_driver: &'static nrf52840dk_lib::Ieee802154Driver, + udp_driver: &'static capsules_extra::net::udp::UDPDriver<'static>, } impl SyscallDriverLookup for Platform { @@ -203,498 +35,70 @@ impl SyscallDriverLookup for Platform { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::button::DRIVER_NUM => f(Some(self.button)), - capsules::rng::DRIVER_NUM => f(Some(self.rng)), - capsules::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), - capsules::ieee802154::DRIVER_NUM => f(Some(self.ieee802154_radio)), - capsules::temperature::DRIVER_NUM => f(Some(self.temp)), - capsules::analog_comparator::DRIVER_NUM => f(Some(self.analog_comparator)), - capsules::nonvolatile_storage_driver::DRIVER_NUM => f(Some(self.nonvolatile_storage)), - capsules::net::udp::DRIVER_NUM => f(Some(self.udp_driver)), - kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), - capsules::i2c_master_slave_driver::DRIVER_NUM => f(Some(self.i2c_master_slave)), - capsules::spi_controller::DRIVER_NUM => f(Some(self.spi_controller)), - _ => f(None), + capsules_extra::eui64::DRIVER_NUM => f(Some(self.eui64_driver)), + capsules_extra::net::udp::DRIVER_NUM => f(Some(self.udp_driver)), + capsules_extra::ieee802154::DRIVER_NUM => f(Some(self.ieee802154_driver)), + _ => self.base.with_driver(driver_num, f), } } } -/// This is in a separate, inline(never) function so that its stack frame is -/// removed when this function returns. Otherwise, the stack space used for -/// these static_inits is wasted. -#[inline(never)] -unsafe fn get_peripherals() -> &'static mut Nrf52840DefaultPeripherals<'static> { - // Initialize chip peripheral drivers - let nrf52840_peripherals = static_init!( - Nrf52840DefaultPeripherals, - Nrf52840DefaultPeripherals::new() - ); +type Chip = nrf52840dk_lib::Chip; - nrf52840_peripherals -} - -impl KernelResources>> - for Platform -{ +impl KernelResources for Platform { type SyscallDriverLookup = Self; - type SyscallFilter = (); - type ProcessFault = (); - type Scheduler = RoundRobinSched<'static>; - type SchedulerTimer = cortexm4::systick::SysTick; - type WatchDog = (); - type ContextSwitchCallback = (); + type SyscallFilter = >::SyscallFilter; + type ProcessFault = >::ProcessFault; + type Scheduler = >::Scheduler; + type SchedulerTimer = >::SchedulerTimer; + type WatchDog = >::WatchDog; + type ContextSwitchCallback = + >::ContextSwitchCallback; fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { - &() + self.base.syscall_filter() } fn process_fault(&self) -> &Self::ProcessFault { - &() + self.base.process_fault() } fn scheduler(&self) -> &Self::Scheduler { - self.scheduler + self.base.scheduler() } fn scheduler_timer(&self) -> &Self::SchedulerTimer { - &self.systick + self.base.scheduler_timer() } fn watchdog(&self) -> &Self::WatchDog { - &() + self.base.watchdog() } fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { - &() + self.base.context_switch_callback() } } /// Main function called after RAM initialized. #[no_mangle] pub unsafe fn main() { - nrf52840::init(); - - let nrf52840_peripherals = get_peripherals(); - - // set up circular peripheral dependencies - nrf52840_peripherals.init(); - let base_peripherals = &nrf52840_peripherals.nrf52; - - let uart_channel = if USB_DEBUGGING { - // Initialize early so any panic beyond this point can use the RTT memory object. - let mut rtt_memory_refs = - components::segger_rtt::SeggerRttMemoryComponent::new().finalize(()); - - // XXX: This is inherently unsafe as it aliases the mutable reference to rtt_memory. This - // aliases reference is only used inside a panic handler, which should be OK, but maybe we - // should use a const reference to rtt_memory and leverage interior mutability instead. - self::io::set_rtt_memory(&mut *rtt_memory_refs.get_rtt_memory_ptr()); - - UartChannel::Rtt(rtt_memory_refs) - } else { - UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD)) - }; - - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); - - let gpio = components::gpio::GpioComponent::new( - board_kernel, - capsules::gpio::DRIVER_NUM, - components::gpio_component_helper!( - nrf52840::gpio::GPIOPin, - 0 => &nrf52840_peripherals.gpio_port[Pin::P1_01], - 1 => &nrf52840_peripherals.gpio_port[Pin::P1_02], - 2 => &nrf52840_peripherals.gpio_port[Pin::P1_03], - 3 => &nrf52840_peripherals.gpio_port[Pin::P1_04], - 4 => &nrf52840_peripherals.gpio_port[Pin::P1_05], - 5 => &nrf52840_peripherals.gpio_port[Pin::P1_06], - 6 => &nrf52840_peripherals.gpio_port[Pin::P1_07], - 7 => &nrf52840_peripherals.gpio_port[Pin::P1_08], - 8 => &nrf52840_peripherals.gpio_port[Pin::P1_10], - 9 => &nrf52840_peripherals.gpio_port[Pin::P1_11], - 10 => &nrf52840_peripherals.gpio_port[Pin::P1_12], - 11 => &nrf52840_peripherals.gpio_port[Pin::P1_13], - 12 => &nrf52840_peripherals.gpio_port[Pin::P1_14], - 13 => &nrf52840_peripherals.gpio_port[Pin::P1_15], - ), - ) - .finalize(components::gpio_component_buf!(nrf52840::gpio::GPIOPin)); - - let button = components::button::ButtonComponent::new( - board_kernel, - capsules::button::DRIVER_NUM, - components::button_component_helper!( - nrf52840::gpio::GPIOPin, - ( - &nrf52840_peripherals.gpio_port[BUTTON1_PIN], - kernel::hil::gpio::ActivationMode::ActiveLow, - kernel::hil::gpio::FloatingState::PullUp - ), //13 - ( - &nrf52840_peripherals.gpio_port[BUTTON2_PIN], - kernel::hil::gpio::ActivationMode::ActiveLow, - kernel::hil::gpio::FloatingState::PullUp - ), //14 - ( - &nrf52840_peripherals.gpio_port[BUTTON3_PIN], - kernel::hil::gpio::ActivationMode::ActiveLow, - kernel::hil::gpio::FloatingState::PullUp - ), //15 - ( - &nrf52840_peripherals.gpio_port[BUTTON4_PIN], - kernel::hil::gpio::ActivationMode::ActiveLow, - kernel::hil::gpio::FloatingState::PullUp - ) //16 - ), - ) - .finalize(components::button_component_buf!(nrf52840::gpio::GPIOPin)); - - let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( - LedLow<'static, nrf52840::gpio::GPIOPin>, - LedLow::new(&nrf52840_peripherals.gpio_port[LED1_PIN]), - LedLow::new(&nrf52840_peripherals.gpio_port[LED2_PIN]), - LedLow::new(&nrf52840_peripherals.gpio_port[LED3_PIN]), - LedLow::new(&nrf52840_peripherals.gpio_port[LED4_PIN]), - )); - - let chip = static_init!( - nrf52840::chip::NRF52, - nrf52840::chip::NRF52::new(nrf52840_peripherals) - ); - CHIP = Some(chip); - - nrf52_components::startup::NrfStartupComponent::new( - false, - BUTTON_RST_PIN, - nrf52840::uicr::Regulator0Output::DEFAULT, - &base_peripherals.nvmc, - ) - .finalize(()); - - // Create capabilities that the board needs to call certain protected kernel - // functions. - let process_management_capability = - create_capability!(capabilities::ProcessManagementCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); - let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); - let gpio_port = &nrf52840_peripherals.gpio_port; - // Configure kernel debug gpios as early as possible - kernel::debug::assign_gpios( - Some(&gpio_port[LED1_PIN]), - Some(&gpio_port[LED2_PIN]), - Some(&gpio_port[LED3_PIN]), - ); - - let rtc = &base_peripherals.rtc; - let _ = rtc.start(); - let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) - .finalize(components::alarm_mux_component_helper!(nrf52840::rtc::Rtc)); - let alarm = components::alarm::AlarmDriverComponent::new( - board_kernel, - capsules::alarm::DRIVER_NUM, - mux_alarm, - ) - .finalize(components::alarm_component_helper!(nrf52840::rtc::Rtc)); - - let channel = nrf52_components::UartChannelComponent::new( - uart_channel, - mux_alarm, - &base_peripherals.uarte0, - ) - .finalize(()); - - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 4], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); - - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); - PROCESS_PRINTER = Some(process_printer); - - // Create a shared UART channel for the console and for kernel debug. - let uart_mux = - components::console::UartMuxComponent::new(channel, 115200, dynamic_deferred_caller) - .finalize(()); - - let pconsole = components::process_console::ProcessConsoleComponent::new( - board_kernel, - uart_mux, - mux_alarm, - process_printer, - ) - .finalize(components::process_console_component_helper!( - nrf52840::rtc::Rtc<'static> - )); - - // Setup the console. - let console = components::console::ConsoleComponent::new( - board_kernel, - capsules::console::DRIVER_NUM, - uart_mux, - ) - .finalize(()); - // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); - - let ble_radio = nrf52_components::BLEComponent::new( - board_kernel, - capsules::ble_advertising_driver::DRIVER_NUM, - &base_peripherals.ble_radio, - mux_alarm, - ) - .finalize(()); - - let aes_mux = static_init!( - MuxAES128CCM<'static, nrf52840::aes::AesECB>, - MuxAES128CCM::new(&base_peripherals.ecb, dynamic_deferred_caller) - ); - base_peripherals.ecb.set_client(aes_mux); - aes_mux.initialize_callback_handle( - dynamic_deferred_caller.register(aes_mux).unwrap(), // Unwrap fail = no deferred call slot available for ccm mux - ); - - let serial_num = nrf52840::ficr::FICR_INSTANCE.address(); - let serial_num_bottom_16 = serial_num[0] as u16 + ((serial_num[1] as u16) << 8); - let src_mac_from_serial_num: MacAddress = MacAddress::Short(serial_num_bottom_16); - let (ieee802154_radio, mux_mac) = components::ieee802154::Ieee802154Component::new( - board_kernel, - capsules::ieee802154::DRIVER_NUM, - &base_peripherals.ieee802154_radio, - aes_mux, - PAN_ID, - serial_num_bottom_16, - dynamic_deferred_caller, - ) - .finalize(components::ieee802154_component_helper!( - nrf52840::ieee802154_radio::Radio, - nrf52840::aes::AesECB<'static> - )); - - let local_ip_ifaces = static_init!( - [IPAddr; 3], - [ - IPAddr([ - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, - 0x0e, 0x0f, - ]), - IPAddr([ - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, - 0x1e, 0x1f, - ]), - IPAddr::generate_from_mac(capsules::net::ieee802154::MacAddress::Short( - serial_num_bottom_16 - )), - ] - ); - - let (udp_send_mux, udp_recv_mux, udp_port_table) = components::udp_mux::UDPMuxComponent::new( - mux_mac, - DEFAULT_CTX_PREFIX_LEN, - DEFAULT_CTX_PREFIX, - DST_MAC_ADDR, - src_mac_from_serial_num, - local_ip_ifaces, - mux_alarm, - ) - .finalize(components::udp_mux_component_helper!(nrf52840::rtc::Rtc)); - - // UDP driver initialization happens here - let udp_driver = components::udp_driver::UDPDriverComponent::new( - board_kernel, - capsules::net::udp::driver::DRIVER_NUM, - udp_send_mux, - udp_recv_mux, - udp_port_table, - local_ip_ifaces, - ) - .finalize(components::udp_driver_component_helper!(nrf52840::rtc::Rtc)); - - let temp = components::temperature::TemperatureComponent::new( - board_kernel, - capsules::temperature::DRIVER_NUM, - &base_peripherals.temp, - ) - .finalize(()); - - let rng = components::rng::RngComponent::new( - board_kernel, - capsules::rng::DRIVER_NUM, - &base_peripherals.trng, - ) - .finalize(()); - - // SPI - let mux_spi = - components::spi::SpiMuxComponent::new(&base_peripherals.spim0, dynamic_deferred_caller) - .finalize(components::spi_mux_component_helper!(nrf52840::spi::SPIM)); - // Create the SPI system call capsule. - let spi_controller = components::spi::SpiSyscallComponent::new( - board_kernel, - mux_spi, - &gpio_port[SPI_CS], - capsules::spi_controller::DRIVER_NUM, - ) - .finalize(components::spi_syscall_component_helper!( - nrf52840::spi::SPIM - )); - - base_peripherals.spim0.configure( - nrf52840::pinmux::Pinmux::new(SPI_MOSI as u32), - nrf52840::pinmux::Pinmux::new(SPI_MISO as u32), - nrf52840::pinmux::Pinmux::new(SPI_CLK as u32), - ); - - let mx25r6435f = components::mx25r6435f::Mx25r6435fComponent::new( - &gpio_port[SPI_MX25R6435F_WRITE_PROTECT_PIN], - &gpio_port[SPI_MX25R6435F_HOLD_PIN], - &gpio_port[SPI_MX25R6435F_CHIP_SELECT] as &dyn kernel::hil::gpio::Pin, - mux_alarm, - mux_spi, - ) - .finalize(components::mx25r6435f_component_helper!( - nrf52840::spi::SPIM, - nrf52840::gpio::GPIOPin, - nrf52840::rtc::Rtc - )); - - let nonvolatile_storage = components::nonvolatile_storage::NonvolatileStorageComponent::new( - board_kernel, - capsules::nonvolatile_storage_driver::DRIVER_NUM, - mx25r6435f, - 0x60000, // Start address for userspace accessible region - 0x20000, // Length of userspace accessible region - 0, // Start address of kernel region - 0x60000, // Length of kernel region - ) - .finalize(components::nv_storage_component_helper!( - capsules::mx25r6435f::MX25R6435F< - 'static, - capsules::virtual_spi::VirtualSpiMasterDevice<'static, nrf52840::spi::SPIM>, - nrf52840::gpio::GPIOPin, - VirtualMuxAlarm<'static, nrf52840::rtc::Rtc>, - > - )); - - let i2c_master_buffer = static_init!([u8; 32], [0; 32]); - let i2c_slave_buffer1 = static_init!([u8; 32], [0; 32]); - let i2c_slave_buffer2 = static_init!([u8; 32], [0; 32]); - - let i2c_master_slave = static_init!( - I2CMasterSlaveDriver, - I2CMasterSlaveDriver::new( - &base_peripherals.twi1, - i2c_master_buffer, - i2c_slave_buffer1, - i2c_slave_buffer2, - board_kernel.create_grant( - capsules::i2c_master_slave_driver::DRIVER_NUM, - &memory_allocation_capability - ), - ) - ); - base_peripherals.twi1.configure( - nrf52840::pinmux::Pinmux::new(I2C_SCL_PIN as u32), - nrf52840::pinmux::Pinmux::new(I2C_SDA_PIN as u32), - ); - base_peripherals.twi1.set_master_client(i2c_master_slave); - base_peripherals.twi1.set_slave_client(i2c_master_slave); - base_peripherals.twi1.set_speed(nrf52840::i2c::Speed::K400); - - // Initialize AC using AIN5 (P0.29) as VIN+ and VIN- as AIN0 (P0.02) - // These are hardcoded pin assignments specified in the driver - let analog_comparator = components::analog_comparator::AcComponent::new( - &base_peripherals.acomp, - components::acomp_component_helper!( - nrf52840::acomp::Channel, - &nrf52840::acomp::CHANNEL_AC0 - ), - board_kernel, - capsules::analog_comparator::DRIVER_NUM, - ) - .finalize(components::acomp_component_buf!( - nrf52840::acomp::Comparator - )); - - nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); - - // let alarm_test_component = - // components::test::multi_alarm_test::MultiAlarmTestComponent::new(&mux_alarm).finalize( - // components::multi_alarm_test_component_buf!(nrf52840::rtc::Rtc), - // ); + let (board_kernel, base_platform, chip, default_peripherals, mux_alarm) = + nrf52840dk_lib::start(); //-------------------------------------------------------------------------- - // USB CTAP EXAMPLE + // IEEE 802.15.4 and UDP //-------------------------------------------------------------------------- - // Uncomment to experiment with this. - - // // Create the strings we include in the USB descriptor. - // let strings = static_init!( - // [&str; 3], - // [ - // "Nordic Semiconductor", // Manufacturer - // "nRF52840dk - TockOS", // Product - // "serial0001", // Serial number - // ] - // ); - - // let ctap_send_buffer = static_init!([u8; 64], [0; 64]); - // let ctap_recv_buffer = static_init!([u8; 64], [0; 64]); - // let (ctap, _ctap_driver) = components::ctap::CtapComponent::new( - // &peripherals.usbd, - // 0x1915, // Nordic Semiconductor - // 0x503a, // lowRISC generic FS USB - // strings, - // board_kernel, - // ctap_send_buffer, - // ctap_recv_buffer, - // ) - // .finalize(components::usb_ctap_component_helper!(nrf52840::usbd::Usbd)); - - // ctap.enable(); - // ctap.attach(); - - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + let (eui64_driver, ieee802154_driver, udp_driver) = + nrf52840dk_lib::ieee802154_udp(board_kernel, default_peripherals, mux_alarm); let platform = Platform { - button, - ble_radio, - ieee802154_radio, - pconsole, - console, - led, - gpio, - rng, - temp, - alarm, - analog_comparator, - nonvolatile_storage, + base: base_platform, + eui64_driver, + ieee802154_driver, udp_driver, - ipc: kernel::ipc::IPC::new( - board_kernel, - kernel::ipc::DRIVER_NUM, - &memory_allocation_capability, - ), - i2c_master_slave, - spi_controller, - scheduler, - systick: cortexm4::systick::SysTick::new_with_calibration(64000000), }; - let _ = platform.pconsole.start(); - debug!("Initialization complete. Entering main loop\r"); - debug!("{}", &nrf52840::ficr::FICR_INSTANCE); - - // alarm_test_component.run(); - - /// These symbols are defined in the linker script. + // These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -706,18 +110,20 @@ pub unsafe fn main() { static _eappmem: u8; } + let process_management_capability = + create_capability!(capabilities::ProcessManagementCapability); kernel::process::load_processes( board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_management_capability, ) @@ -726,5 +132,11 @@ pub unsafe fn main() { debug!("{:?}", err); }); - board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + board_kernel.kernel_loop( + &platform, + chip, + Some(&platform.base.ipc), + &main_loop_capability, + ); } diff --git a/boards/nordic/nrf52_components/Cargo.toml b/boards/nordic/nrf52_components/Cargo.toml index d95debf908..b978c033a5 100644 --- a/boards/nordic/nrf52_components/Cargo.toml +++ b/boards/nordic/nrf52_components/Cargo.toml @@ -1,12 +1,22 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "nrf52_components" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] components = { path = "../../components" } cortexm4 = { path = "../../../arch/cortex-m4" } -capsules = { path = "../../../capsules" } kernel = { path = "../../../kernel" } nrf52 = { path = "../../../chips/nrf52" } + +capsules-core = { path = "../../../capsules/core" } +capsules-extra = { path = "../../../capsules/extra" } +capsules-system = { path = "../../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/nordic/nrf52_components/src/ble.rs b/boards/nordic/nrf52_components/src/ble.rs deleted file mode 100644 index 7399358379..0000000000 --- a/boards/nordic/nrf52_components/src/ble.rs +++ /dev/null @@ -1,84 +0,0 @@ -//! Component for BLE radio on nRF52 based platforms. -//! -//! Usage -//! ----- -//! ```rust -//! let ble_radio = BLEComponent::new(board_kernel, &nrf52::ble_radio::RADIO, mux_alarm).finalize(); -//! ``` - -use capsules; -use capsules::virtual_alarm::VirtualMuxAlarm; - -use nrf52::rtc::Rtc; - -use kernel::capabilities; -use kernel::component::Component; -use kernel::hil::time::Alarm; -use kernel::{create_capability, static_init}; - -// Save some deep nesting - -pub struct BLEComponent { - board_kernel: &'static kernel::Kernel, - driver_num: usize, - radio: &'static nrf52::ble_radio::Radio<'static>, - mux_alarm: &'static capsules::virtual_alarm::MuxAlarm<'static, nrf52::rtc::Rtc<'static>>, -} - -impl BLEComponent { - pub fn new( - board_kernel: &'static kernel::Kernel, - driver_num: usize, - radio: &'static nrf52::ble_radio::Radio, - mux_alarm: &'static capsules::virtual_alarm::MuxAlarm<'static, nrf52::rtc::Rtc>, - ) -> BLEComponent { - BLEComponent { - board_kernel: board_kernel, - driver_num: driver_num, - radio: radio, - mux_alarm: mux_alarm, - } - } -} - -impl Component for BLEComponent { - type StaticInput = (); - type Output = &'static capsules::ble_advertising_driver::BLE< - 'static, - nrf52::ble_radio::Radio<'static>, - VirtualMuxAlarm<'static, Rtc<'static>>, - >; - - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { - let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - - let ble_radio_virtual_alarm = static_init!( - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc>, - capsules::virtual_alarm::VirtualMuxAlarm::new(self.mux_alarm) - ); - ble_radio_virtual_alarm.setup(); - - let ble_radio = static_init!( - capsules::ble_advertising_driver::BLE< - 'static, - nrf52::ble_radio::Radio, - VirtualMuxAlarm<'static, Rtc>, - >, - capsules::ble_advertising_driver::BLE::new( - self.radio, - self.board_kernel.create_grant(self.driver_num, &grant_cap), - &mut capsules::ble_advertising_driver::BUF, - ble_radio_virtual_alarm - ) - ); - kernel::hil::ble_advertising::BleAdvertisementDriver::set_receive_client( - self.radio, ble_radio, - ); - kernel::hil::ble_advertising::BleAdvertisementDriver::set_transmit_client( - self.radio, ble_radio, - ); - ble_radio_virtual_alarm.set_alarm_client(ble_radio); - - ble_radio - } -} diff --git a/boards/nordic/nrf52_components/src/lib.rs b/boards/nordic/nrf52_components/src/lib.rs index 145a9363bb..de9c12849c 100644 --- a/boards/nordic/nrf52_components/src/lib.rs +++ b/boards/nordic/nrf52_components/src/lib.rs @@ -1,9 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + #![no_std] -pub mod ble; pub mod startup; -pub use self::ble::BLEComponent; pub use self::startup::{ NrfClockComponent, NrfStartupComponent, UartChannel, UartChannelComponent, UartPins, }; diff --git a/boards/nordic/nrf52_components/src/startup.rs b/boards/nordic/nrf52_components/src/startup.rs index e6cf3ff56e..16f7dbc7f4 100644 --- a/boards/nordic/nrf52_components/src/startup.rs +++ b/boards/nordic/nrf52_components/src/startup.rs @@ -1,10 +1,15 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Component for starting up nrf52 platforms. //! Contains 3 components, NrfStartupComponent, NrfClockComponent, //! and UartChannelComponent, as well as two helper structs for //! intializing Uart on Nordic boards. -use capsules::virtual_alarm::MuxAlarm; -use components; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_extra::segger_rtt::SeggerRtt; +use core::mem::MaybeUninit; use kernel::component::Component; use nrf52::gpio::Pin; use nrf52::uicr::Regulator0Output; @@ -35,7 +40,14 @@ impl<'a> NrfStartupComponent<'a> { impl<'a> Component for NrfStartupComponent<'a> { type StaticInput = (); type Output = (); - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { + fn finalize(self, _s: Self::StaticInput) -> Self::Output { + // Disable APPROTECT in software. This is required as of newer nRF52 + // hardware revisions. See + // https://devzone.nordicsemi.com/nordic/nordic-blog/b/blog/posts/working-with-the-nrf52-series-improved-approtect. + // If run on older HW revisions this function will do nothing. + let approtect = nrf52::approtect::Approtect::new(); + approtect.sw_disable_approtect(); + // Make non-volatile memory writable and activate the reset button let uicr = nrf52::uicr::Uicr::new(); @@ -53,6 +65,12 @@ impl<'a> Component for NrfStartupComponent<'a> { erase_uicr |= !uicr.is_nfc_pins_protection_enabled(); } + // On new nRF52 variants we need to ensure that the APPROTECT field in UICR is + // set to `HwDisable`. + if uicr.is_ap_protect_enabled() { + erase_uicr = true; + } + if erase_uicr { self.nvmc.erase_uicr(); } @@ -94,9 +112,18 @@ impl<'a> Component for NrfStartupComponent<'a> { needs_soft_reset = true; } + // If APPROTECT was not already disabled, ensure it is set to disabled. + if uicr.is_ap_protect_enabled() { + uicr.disable_ap_protect(); + while !self.nvmc.is_ready() {} + needs_soft_reset = true; + } + // Any modification of UICR needs a soft reset for the changes to be taken into account. if needs_soft_reset { - cortexm4::scb::reset(); + unsafe { + cortexm4::scb::reset(); + } } } } @@ -114,7 +141,7 @@ impl<'a> NrfClockComponent<'a> { impl<'a> Component for NrfClockComponent<'a> { type StaticInput = (); type Output = (); - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { + fn finalize(self, _s: Self::StaticInput) -> Self::Output { // Start all of the clocks. Low power operation will require a better // approach than this. self.clock.low_stop(); @@ -129,6 +156,13 @@ impl<'a> Component for NrfClockComponent<'a> { } } +#[macro_export] +macro_rules! uart_channel_component_static { + ($A:ty $(,)?) => {{ + components::segger_rtt_component_static!($A) + };}; +} + /// Pins for the UART #[derive(Debug)] pub struct UartPins { @@ -172,24 +206,31 @@ impl UartChannelComponent { } impl Component for UartChannelComponent { - type StaticInput = (); + type StaticInput = ( + &'static mut MaybeUninit>>, + &'static mut MaybeUninit< + SeggerRtt<'static, VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>>, + >, + ); type Output = &'static dyn kernel::hil::uart::Uart<'static>; - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { + fn finalize(self, s: Self::StaticInput) -> Self::Output { match self.uart_channel { UartChannel::Pins(uart_pins) => { - self.uarte0.initialize( - nrf52::pinmux::Pinmux::new(uart_pins.txd as u32), - nrf52::pinmux::Pinmux::new(uart_pins.rxd as u32), - uart_pins.cts.map(|x| nrf52::pinmux::Pinmux::new(x as u32)), - uart_pins.rts.map(|x| nrf52::pinmux::Pinmux::new(x as u32)), - ); + unsafe { + self.uarte0.initialize( + nrf52::pinmux::Pinmux::new(uart_pins.txd as u32), + nrf52::pinmux::Pinmux::new(uart_pins.rxd as u32), + uart_pins.cts.map(|x| nrf52::pinmux::Pinmux::new(x as u32)), + uart_pins.rts.map(|x| nrf52::pinmux::Pinmux::new(x as u32)), + ) + }; self.uarte0 } UartChannel::Rtt(rtt_memory) => { let rtt = components::segger_rtt::SeggerRttComponent::new(self.mux_alarm, rtt_memory) - .finalize(components::segger_rtt_component_helper!(nrf52::rtc::Rtc)); + .finalize(s); rtt } } diff --git a/boards/nordic/nrf52dk/Cargo.toml b/boards/nordic/nrf52dk/Cargo.toml index b204e2712c..2d669191ac 100644 --- a/boards/nordic/nrf52dk/Cargo.toml +++ b/boards/nordic/nrf52dk/Cargo.toml @@ -1,14 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "nrf52dk" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../../build.rs" +edition.workspace = true [dependencies] components = { path = "../../components" } cortexm4 = { path = "../../../arch/cortex-m4" } -capsules = { path = "../../../capsules" } kernel = { path = "../../../kernel" } nrf52832 = { path = "../../../chips/nrf52832" } nrf52_components = { path = "../nrf52_components" } + +capsules-core = { path = "../../../capsules/core" } +capsules-extra = { path = "../../../capsules/extra" } +capsules-system = { path = "../../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/nordic/nrf52dk/Makefile b/boards/nordic/nrf52dk/Makefile index 3db84f63ec..6aee9c5256 100644 --- a/boards/nordic/nrf52dk/Makefile +++ b/boards/nordic/nrf52dk/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the nRF52 development kit TARGET=thumbv7em-none-eabi diff --git a/boards/nordic/nrf52dk/build.rs b/boards/nordic/nrf52dk/build.rs deleted file mode 100644 index 1fdd4924f0..0000000000 --- a/boards/nordic/nrf52dk/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../../kernel_layout.ld"); -} diff --git a/boards/nordic/nrf52dk/jtag/flash-full.jlink b/boards/nordic/nrf52dk/jtag/flash-full.jlink index 2e1b4e61f2..15e10987c2 100644 --- a/boards/nordic/nrf52dk/jtag/flash-full.jlink +++ b/boards/nordic/nrf52dk/jtag/flash-full.jlink @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2017. + eoe 1 r loadfile ../../../target/thumbv7em-none-eabi/release/nrf52dk-blink.hex diff --git a/boards/nordic/nrf52dk/jtag/flash-kernel.jlink b/boards/nordic/nrf52dk/jtag/flash-kernel.jlink index 95d340146a..04993f1333 100644 --- a/boards/nordic/nrf52dk/jtag/flash-kernel.jlink +++ b/boards/nordic/nrf52dk/jtag/flash-kernel.jlink @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2017. + eoe 1 r loadfile ../../../target/thumbv7em-none-eabi/release/nrf52dk.hex diff --git a/boards/nordic/nrf52dk/jtag/gdbinit_pca10040.jlink b/boards/nordic/nrf52dk/jtag/gdbinit_pca10040.jlink index b86b052f64..8094e76819 100644 --- a/boards/nordic/nrf52dk/jtag/gdbinit_pca10040.jlink +++ b/boards/nordic/nrf52dk/jtag/gdbinit_pca10040.jlink @@ -1,25 +1,28 @@ -# -# -# -# J-LINK GDB SERVER initialization -# -# This connects to a GDB Server listening -# for commands on localhost at tcp port 2331 -target remote localhost:2331 -monitor speed 30 -file ../../../../target/thumbv7em-none-eabi/release/nrf52dk -monitor reset -# -# CPU core initialization (to be done by user) -# -# Set the processor mode -# monitor reg cpsr = 0xd3 -# Set auto JTAG speed -monitor speed auto -# Setup GDB FOR FASTER DOWNLOADS -set remote memory-write-packet-size 1024 -set remote memory-write-packet-size fixed -# tui enable -# layout split -# layout service_pending_interrupts -b initialize_ram_jump_to_main +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2018. +# +# +# +# J-LINK GDB SERVER initialization +# +# This connects to a GDB Server listening +# for commands on localhost at tcp port 2331 +target remote localhost:2331 +monitor speed 30 +file ../../../../target/thumbv7em-none-eabi/release/nrf52dk +monitor reset +# +# CPU core initialization (to be done by user) +# +# Set the processor mode +# monitor reg cpsr = 0xd3 +# Set auto JTAG speed +monitor speed auto +# Setup GDB FOR FASTER DOWNLOADS +set remote memory-write-packet-size 1024 +set remote memory-write-packet-size fixed +# tui enable +# layout split +# layout service_pending_interrupts +b initialize_ram_jump_to_main diff --git a/boards/nordic/nrf52dk/jtag/jdbserver_pca10040.sh b/boards/nordic/nrf52dk/jtag/jdbserver_pca10040.sh index b4bee90e5f..5edf8b5cb8 100755 --- a/boards/nordic/nrf52dk/jtag/jdbserver_pca10040.sh +++ b/boards/nordic/nrf52dk/jtag/jdbserver_pca10040.sh @@ -1 +1,5 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + JLinkGDBServer -device nrf52 -speed 1200 -if swd -AutoConnect 1 -port 2331 diff --git a/boards/nordic/nrf52dk/layout.ld b/boards/nordic/nrf52dk/layout.ld index 9b6d12e935..0142c25b8a 100644 --- a/boards/nordic/nrf52dk/layout.ld +++ b/boards/nordic/nrf52dk/layout.ld @@ -1,2 +1,6 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + INCLUDE ../nrf52832_chip_layout.ld INCLUDE ../../kernel_layout.ld diff --git a/boards/nordic/nrf52dk/src/io.rs b/boards/nordic/nrf52dk/src/io.rs index cffadc8a53..1a6bedf69c 100644 --- a/boards/nordic/nrf52dk/src/io.rs +++ b/boards/nordic/nrf52dk/src/io.rs @@ -1,11 +1,15 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; -use cortexm4; use kernel::debug; use kernel::debug::IoWrite; use kernel::hil::led; use kernel::hil::uart::{self, Configure}; use nrf52832::gpio::Pin; +use nrf52832::uart::{Uarte, UARTE0_BASE}; use crate::CHIP; use crate::PROCESSES; @@ -25,11 +29,11 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { // Here, we create a second instance of the Uarte struct. // This is okay because we only call this during a panic, and // we will never actually process the interrupts - let uart = nrf52832::uart::Uarte::new(); + let uart = Uarte::new(UARTE0_BASE); if !self.initialized { self.initialized = true; let _ = uart.configure(uart::Parameters { @@ -46,6 +50,7 @@ impl IoWrite for Writer { } while !uart.tx_ready() {} } + buf.len() } } @@ -53,18 +58,20 @@ impl IoWrite for Writer { #[no_mangle] #[panic_handler] /// Panic handler -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { // The nRF52 DK LEDs (see back of board) + + use core::ptr::{addr_of, addr_of_mut}; let led_kernel_pin = &nrf52832::gpio::GPIOPin::new(Pin::P0_17); let led = &mut led::LedLow::new(led_kernel_pin); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led], writer, pi, &cortexm4::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/nordic/nrf52dk/src/main.rs b/boards/nordic/nrf52dk/src/main.rs index 9b7b60d2d2..6ad5b7cc38 100644 --- a/boards/nordic/nrf52dk/src/main.rs +++ b/boards/nordic/nrf52dk/src/main.rs @@ -1,4 +1,9 @@ -//! Tock kernel for the Nordic Semiconductor nRF52 development kit (DK), a.k.a. the PCA10040.
+// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Tock kernel for the Nordic Semiconductor nRF52 development kit (DK), a.k.a. the PCA10040. +//! //! It is based on nRF52838 SoC (Cortex M4 core with a BLE transceiver) with many exported //! I/O and peripherals. //! @@ -66,9 +71,10 @@ #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] -use capsules::virtual_alarm::VirtualMuxAlarm; +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil::led::LedLow; use kernel::hil::time::Counter; use kernel::platform::{KernelResources, SyscallDriverLookup}; @@ -78,7 +84,7 @@ use kernel::{capabilities, create_capability, debug, debug_gpio, debug_verbose, use nrf52832::gpio::Pin; use nrf52832::interrupt_service::Nrf52832DefaultPeripherals; use nrf52832::rtc::Rtc; -use nrf52_components::{self, UartChannel, UartPins}; +use nrf52_components::{UartChannel, UartPins}; // The nRF52 DK LEDs (see back of board) const LED1_PIN: Pin = Pin::P0_17; @@ -114,7 +120,8 @@ mod tests; // State for loading and holding applications. // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; // Number of concurrent processes this platform supports. const NUM_PROCS: usize = 4; @@ -123,43 +130,52 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] // Static reference to chip for panic dumps static mut CHIP: Option<&'static nrf52832::chip::NRF52> = None; -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; /// Dummy buffer that causes the linker to reserve enough space for the stack. #[no_mangle] #[link_section = ".stack_buffer"] pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; +type TemperatureDriver = + components::temperature::TemperatureComponentType>; +type RngDriver = components::rng::RngComponentType>; + /// Supported drivers by the platform pub struct Platform { - ble_radio: &'static capsules::ble_advertising_driver::BLE< + ble_radio: &'static capsules_extra::ble_advertising_driver::BLE< 'static, nrf52832::ble_radio::Radio<'static>, VirtualMuxAlarm<'static, Rtc<'static>>, >, - button: &'static capsules::button::Button<'static, nrf52832::gpio::GPIOPin<'static>>, - pconsole: &'static capsules::process_console::ProcessConsole< + button: &'static capsules_core::button::Button<'static, nrf52832::gpio::GPIOPin<'static>>, + pconsole: &'static capsules_core::process_console::ProcessConsole< 'static, + { capsules_core::process_console::DEFAULT_COMMAND_HISTORY_LEN }, VirtualMuxAlarm<'static, Rtc<'static>>, components::process_console::Capability, >, - console: &'static capsules::console::Console<'static>, - gpio: &'static capsules::gpio::GPIO<'static, nrf52832::gpio::GPIOPin<'static>>, - led: &'static capsules::led::LedDriver< + console: &'static capsules_core::console::Console<'static>, + gpio: &'static capsules_core::gpio::GPIO<'static, nrf52832::gpio::GPIOPin<'static>>, + led: &'static capsules_core::led::LedDriver< 'static, LedLow<'static, nrf52832::gpio::GPIOPin<'static>>, 4, >, - rng: &'static capsules::rng::RngDriver<'static>, - temp: &'static capsules::temperature::TemperatureSensor<'static>, - ipc: kernel::ipc::IPC, - analog_comparator: &'static capsules::analog_comparator::AnalogComparator< + rng: &'static RngDriver, + temp: &'static TemperatureDriver, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + analog_comparator: &'static capsules_extra::analog_comparator::AnalogComparator< 'static, nrf52832::acomp::Comparator<'static>, >, - alarm: &'static capsules::alarm::AlarmDriver< + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, - capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52832::rtc::Rtc<'static>>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52832::rtc::Rtc<'static>, + >, >, scheduler: &'static RoundRobinSched<'static>, systick: cortexm4::systick::SysTick, @@ -171,15 +187,15 @@ impl SyscallDriverLookup for Platform { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::button::DRIVER_NUM => f(Some(self.button)), - capsules::rng::DRIVER_NUM => f(Some(self.rng)), - capsules::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), - capsules::temperature::DRIVER_NUM => f(Some(self.temp)), - capsules::analog_comparator::DRIVER_NUM => f(Some(self.analog_comparator)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::rng::DRIVER_NUM => f(Some(self.rng)), + capsules_extra::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temp)), + capsules_extra::analog_comparator::DRIVER_NUM => f(Some(self.analog_comparator)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), _ => f(None), } @@ -198,7 +214,7 @@ impl KernelResources &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -224,32 +240,27 @@ impl KernelResources &'static mut Nrf52832DefaultPeripherals<'static> { - // Initialize chip peripheral drivers +pub unsafe fn start() -> ( + &'static kernel::Kernel, + Platform, + &'static nrf52832::chip::NRF52<'static, Nrf52832DefaultPeripherals<'static>>, +) { + nrf52832::init(); + let nrf52832_peripherals = static_init!( Nrf52832DefaultPeripherals, Nrf52832DefaultPeripherals::new() ); - nrf52832_peripherals -} - -/// Main function called after RAM initialized. -#[no_mangle] -pub unsafe fn main() { - nrf52832::init(); - - let nrf52832_peripherals = get_peripherals(); - // set up circular peripheral dependencies nrf52832_peripherals.init(); let base_peripherals = &nrf52832_peripherals.nrf52; - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); let gpio = components::gpio::GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( nrf52832::gpio::GPIOPin, // Bottom right header on DK board @@ -269,11 +280,11 @@ pub unsafe fn main() { 11 => &nrf52832_peripherals.gpio_port[Pin::P0_25] ), ) - .finalize(components::gpio_component_buf!(nrf52832::gpio::GPIOPin)); + .finalize(components::gpio_component_static!(nrf52832::gpio::GPIOPin)); let button = components::button::ButtonComponent::new( board_kernel, - capsules::button::DRIVER_NUM, + capsules_core::button::DRIVER_NUM, components::button_component_helper!( nrf52832::gpio::GPIOPin, ( @@ -298,9 +309,11 @@ pub unsafe fn main() { ) //16 ), ) - .finalize(components::button_component_buf!(nrf52832::gpio::GPIOPin)); + .finalize(components::button_component_static!( + nrf52832::gpio::GPIOPin + )); - let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( LedLow<'static, nrf52832::gpio::GPIOPin>, LedLow::new(&nrf52832_peripherals.gpio_port[LED1_PIN]), LedLow::new(&nrf52832_peripherals.gpio_port[LED2_PIN]), @@ -326,7 +339,6 @@ pub unsafe fn main() { // functions. let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); let gpio_port = &nrf52832_peripherals.gpio_port; @@ -340,97 +352,97 @@ pub unsafe fn main() { let rtc = &base_peripherals.rtc; let _ = rtc.start(); let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) - .finalize(components::alarm_mux_component_helper!(nrf52832::rtc::Rtc)); + .finalize(components::alarm_mux_component_static!(nrf52832::rtc::Rtc)); let alarm = components::alarm::AlarmDriverComponent::new( board_kernel, - capsules::alarm::DRIVER_NUM, + capsules_core::alarm::DRIVER_NUM, mux_alarm, ) - .finalize(components::alarm_component_helper!(nrf52832::rtc::Rtc)); + .finalize(components::alarm_component_static!(nrf52832::rtc::Rtc)); let uart_channel = UartChannel::Pins(UartPins::new(UART_RTS, UART_TXD, UART_CTS, UART_RXD)); let channel = nrf52_components::UartChannelComponent::new( uart_channel, mux_alarm, &base_peripherals.uarte0, ) - .finalize(()); - - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 2], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + .finalize(nrf52_components::uart_channel_component_static!( + nrf52832::rtc::Rtc + )); - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); // Create a shared UART channel for the console and for kernel debug. - let uart_mux = - components::console::UartMuxComponent::new(channel, 115200, dynamic_deferred_caller) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(channel, 115200) + .finalize(components::uart_mux_component_static!()); let pconsole = components::process_console::ProcessConsoleComponent::new( board_kernel, uart_mux, mux_alarm, process_printer, + Some(cortexm4::support::reset), ) - .finalize(components::process_console_component_helper!(Rtc<'static>)); + .finalize(components::process_console_component_static!(Rtc<'static>)); // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); - let ble_radio = nrf52_components::BLEComponent::new( + let ble_radio = components::ble::BLEComponent::new( board_kernel, - capsules::ble_advertising_driver::DRIVER_NUM, + capsules_extra::ble_advertising_driver::DRIVER_NUM, &base_peripherals.ble_radio, mux_alarm, ) - .finalize(()); + .finalize(components::ble_component_static!( + nrf52832::rtc::Rtc, + nrf52832::ble_radio::Radio + )); let temp = components::temperature::TemperatureComponent::new( board_kernel, - capsules::temperature::DRIVER_NUM, + capsules_extra::temperature::DRIVER_NUM, &base_peripherals.temp, ) - .finalize(()); + .finalize(components::temperature_component_static!( + nrf52832::temperature::Temp + )); let rng = components::rng::RngComponent::new( board_kernel, - capsules::rng::DRIVER_NUM, + capsules_core::rng::DRIVER_NUM, &base_peripherals.trng, ) - .finalize(()); + .finalize(components::rng_component_static!(nrf52832::trng::Trng)); // Initialize AC using AIN5 (P0.29) as VIN+ and VIN- as AIN0 (P0.02) // These are hardcoded pin assignments specified in the driver - let analog_comparator = components::analog_comparator::AcComponent::new( + let analog_comparator = components::analog_comparator::AnalogComparatorComponent::new( &base_peripherals.acomp, - components::acomp_component_helper!( + components::analog_comparator_component_helper!( nrf52832::acomp::Channel, - &nrf52832::acomp::CHANNEL_AC0 + &*addr_of!(nrf52832::acomp::CHANNEL_AC0) ), board_kernel, - capsules::analog_comparator::DRIVER_NUM, + capsules_extra::analog_comparator::DRIVER_NUM, ) - .finalize(components::acomp_component_buf!( + .finalize(components::analog_comparator_component_static!( nrf52832::acomp::Comparator )); nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); let platform = Platform { button, @@ -454,9 +466,9 @@ pub unsafe fn main() { let _ = platform.pconsole.start(); debug!("Initialization complete. Entering main loop\r"); - debug!("{}", &nrf52832::ficr::FICR_INSTANCE); + debug!("{}", &*addr_of!(nrf52832::ficr::FICR_INSTANCE)); - /// These symbols are defined in the linker script. + // These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -472,14 +484,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_management_capability, ) @@ -488,5 +500,14 @@ pub unsafe fn main() { debug!("{:?}", err); }); + (board_kernel, platform, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); } diff --git a/boards/nordic/nrf52dk/src/tests/aes.rs b/boards/nordic/nrf52dk/src/tests/aes.rs index a16c91c555..3a1b7b78d1 100644 --- a/boards/nordic/nrf52dk/src/tests/aes.rs +++ b/boards/nordic/nrf52dk/src/tests/aes.rs @@ -1,4 +1,8 @@ -use capsules::test::aes::TestAes128Ctr; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use capsules_extra::test::aes::TestAes128Ctr; use kernel::hil::symmetric_encryption::{AES128, AES128_BLOCK_SIZE, AES128_KEY_SIZE}; use kernel::static_init; use nrf52832::aes::AesECB; @@ -26,6 +30,6 @@ unsafe fn static_init_test( static_init!( TestAes128Ctr<'static, AesECB>, - TestAes128Ctr::new(aesecb, key, iv, source, data) + TestAes128Ctr::new(aesecb, key, iv, source, data, true) ) } diff --git a/boards/nordic/nrf52dk/src/tests/mod.rs b/boards/nordic/nrf52dk/src/tests/mod.rs index c840bdf1a9..f3aab05d86 100644 --- a/boards/nordic/nrf52dk/src/tests/mod.rs +++ b/boards/nordic/nrf52dk/src/tests/mod.rs @@ -1,2 +1,6 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + pub mod aes; pub mod uart; diff --git a/boards/nordic/nrf52dk/src/tests/uart.rs b/boards/nordic/nrf52dk/src/tests/uart.rs index 0d51bcabba..5b3edd306b 100644 --- a/boards/nordic/nrf52dk/src/tests/uart.rs +++ b/boards/nordic/nrf52dk/src/tests/uart.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::hil::uart::Transmit; use kernel::static_init; use nrf52832::uart::Uarte; @@ -17,7 +21,7 @@ const BUFFER_SIZE_2048: usize = 2048; /// pub unsafe fn run(uart: &'static Uarte) { // Note: you can only one of these tests at the time because - // 1. It will generate race-conitions in the UART because we don't have any checks against that + // 1. It will generate race-conditions in the UART because we don't have any checks against that // 2. `buf` can only be `borrowed` once and avoid allocate four different buffers let buf = static_init!([u8; BUFFER_SIZE_2048], [0; BUFFER_SIZE_2048]); diff --git a/boards/nucleo_f429zi/Cargo.toml b/boards/nucleo_f429zi/Cargo.toml index 564b6a68fb..b2091b9db1 100644 --- a/boards/nucleo_f429zi/Cargo.toml +++ b/boards/nucleo_f429zi/Cargo.toml @@ -1,13 +1,23 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "nucleo_f429zi" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] components = { path = "../components" } cortexm4 = { path = "../../arch/cortex-m4" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } stm32f429zi = { path = "../../chips/stm32f429zi" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/nucleo_f429zi/Makefile b/boards/nucleo_f429zi/Makefile index 18ae7b7f4b..028aa98743 100644 --- a/boards/nucleo_f429zi/Makefile +++ b/boards/nucleo_f429zi/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the NUCLEO-429ZI platform # TARGET=thumbv7em-none-eabi @@ -6,7 +10,6 @@ PLATFORM=nucleo_f429zi include ../Makefile.common OPENOCD=openocd -OPENOCD_OPTIONS=-f openocd.cfg # Default target for installing the kernel. .PHONY: install @@ -14,11 +17,11 @@ install: flash .PHONY: flash-debug flash-debug: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).elf - $(OPENOCD) $(OPENOCD_OPTIONS) -c "init; reset halt; flash write_image erase $<; verify_image $<; reset; shutdown" + $(OPENOCD) -c "source [find board/st_nucleo_f4.cfg]; init; reset halt; flash write_image erase $<; verify_image $<; reset; shutdown" .PHONY: flash flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf - $(OPENOCD) $(OPENOCD_OPTIONS) -c "init; reset halt; flash write_image erase $<; verify_image $<; reset; shutdown" + $(OPENOCD) -c "source [find board/st_nucleo_f4.cfg]; init; reset halt; flash write_image erase $<; verify_image $<; reset; shutdown" .PHONY: program program: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).elf diff --git a/boards/nucleo_f429zi/README.md b/boards/nucleo_f429zi/README.md index 5404ba1736..1dc86ee319 100644 --- a/boards/nucleo_f429zi/README.md +++ b/boards/nucleo_f429zi/README.md @@ -56,3 +56,19 @@ $ make program ``` to flash the image. + +### (Linux): Adding a `udev` rule + +You may want to add a `udev` rule in `/etc/udev/rules.d` that allows you to +interact with the board as a user instead of as root. You can install this as +`/etc/udev/rules.d/99-stlinkv2-1.rules`: + +``` +# stm32 nucleo boards, with onboard st/linkv2-1 +# ie, STM32F0, STM32F4. +# STM32VL has st/linkv1, which is quite different + +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", \ + MODE:="0660", GROUP="dialout", \ + SYMLINK+="stlinkv2-1_%n" +``` diff --git a/boards/nucleo_f429zi/build.rs b/boards/nucleo_f429zi/build.rs deleted file mode 100644 index 46b1df0bac..0000000000 --- a/boards/nucleo_f429zi/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=chip_layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/nucleo_f429zi/chip_layout.ld b/boards/nucleo_f429zi/chip_layout.ld index 3e842ed3ab..cdd58a3eed 100644 --- a/boards/nucleo_f429zi/chip_layout.ld +++ b/boards/nucleo_f429zi/chip_layout.ld @@ -1,3 +1,7 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + /* Memory layout for the STM32F446RE * rom = 2MB (LENGTH = 0x02000000) * kernel = 256KB @@ -11,5 +15,4 @@ MEMORY ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00030000 } -MPU_MIN_ALIGN = 8K; PAGE_SIZE = 2K; diff --git a/boards/nucleo_f429zi/layout.ld b/boards/nucleo_f429zi/layout.ld index 234dcaea2c..6814c00acd 100644 --- a/boards/nucleo_f429zi/layout.ld +++ b/boards/nucleo_f429zi/layout.ld @@ -1,2 +1,6 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + INCLUDE ./chip_layout.ld INCLUDE ../kernel_layout.ld diff --git a/boards/nucleo_f429zi/openocd.cfg b/boards/nucleo_f429zi/openocd.cfg deleted file mode 100644 index 8028f46c46..0000000000 --- a/boards/nucleo_f429zi/openocd.cfg +++ /dev/null @@ -1,11 +0,0 @@ -#interface -interface hla -hla_layout stlink -hla_device_desc "ST-LINK/V2-1" -hla_vid_pid 0x0483 0x374b - -# The chip has 128KB sram, but we will still use 64KB -set WORKAREASIZE 0x10000 - -source [find target/stm32f4x.cfg] - diff --git a/boards/nucleo_f429zi/src/io.rs b/boards/nucleo_f429zi/src/io.rs index f191a5f3d4..694314669e 100644 --- a/boards/nucleo_f429zi/src/io.rs +++ b/boards/nucleo_f429zi/src/io.rs @@ -1,7 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; - -use cortexm4; +use core::ptr::addr_of; +use core::ptr::addr_of_mut; use kernel::debug; use kernel::debug::IoWrite; @@ -9,7 +13,7 @@ use kernel::hil::led; use kernel::hil::uart; use kernel::hil::uart::Configure; -use stm32f429zi; +use stm32f429zi::chip_specs::Stm32f429Specs; use stm32f429zi::gpio::PinId; use crate::CHIP; @@ -40,9 +44,11 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { let rcc = stm32f429zi::rcc::Rcc::new(); - let uart = stm32f429zi::usart::Usart::new_usart3(&rcc); + let clocks: stm32f429zi::clocks::Clocks = + stm32f429zi::clocks::Clocks::new(&rcc); + let uart = stm32f429zi::usart::Usart::new_usart3(&clocks); if !self.initialized { self.initialized = true; @@ -59,32 +65,35 @@ impl IoWrite for Writer { for &c in buf { uart.send_byte(c); } + buf.len() } } /// Panic handler. #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(info: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(info: &PanicInfo) -> ! { // User LD2 is connected to PB07 // Have to reinitialize several peripherals because otherwise can't access them here. let rcc = stm32f429zi::rcc::Rcc::new(); - let syscfg = stm32f429zi::syscfg::Syscfg::new(&rcc); + let clocks: stm32f429zi::clocks::Clocks = + stm32f429zi::clocks::Clocks::new(&rcc); + let syscfg = stm32f429zi::syscfg::Syscfg::new(&clocks); let exti = stm32f429zi::exti::Exti::new(&syscfg); let pin = stm32f429zi::gpio::Pin::new(PinId::PB07, &exti); - let gpio_ports = stm32f429zi::gpio::GpioPorts::new(&rcc, &exti); + let gpio_ports = stm32f429zi::gpio::GpioPorts::new(&clocks, &exti); pin.set_ports_ref(&gpio_ports); let led = &mut led::LedHigh::new(&pin); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led], writer, info, &cortexm4::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/nucleo_f429zi/src/main.rs b/boards/nucleo_f429zi/src/main.rs index 75bdcacfdf..a97147e350 100644 --- a/boards/nucleo_f429zi/src/main.rs +++ b/boards/nucleo_f429zi/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Board file for Nucleo-F429ZI development board //! //! - @@ -8,16 +12,18 @@ #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] -use capsules::virtual_alarm::VirtualMuxAlarm; +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; use components::gpio::GpioComponent; use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil::led::LedHigh; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::scheduler::round_robin::RoundRobinSched; use kernel::{create_capability, debug, static_init}; +use stm32f429zi::chip_specs::Stm32f429Specs; use stm32f429zi::gpio::{AlternateFunction, Mode, PinId, PortId}; use stm32f429zi::interrupt_service::Stm32f429ziDefaultPeripherals; @@ -33,37 +39,59 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] static mut CHIP: Option<&'static stm32f429zi::chip::Stm32f4xx> = None; -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; /// Dummy buffer that causes the linker to reserve enough space for the stack. #[no_mangle] #[link_section = ".stack_buffer"] pub static mut STACK_MEMORY: [u8; 0x2000] = [0; 0x2000]; +//------------------------------------------------------------------------------ +// SYSCALL DRIVER TYPE DEFINITIONS +//------------------------------------------------------------------------------ + +type TemperatureSTMSensor = components::temperature_stm::TemperatureSTMComponentType< + capsules_core::virtualizers::virtual_adc::AdcDevice<'static, stm32f429zi::adc::Adc<'static>>, +>; +type TemperatureDriver = components::temperature::TemperatureComponentType; +type RngDriver = components::rng::RngComponentType>; + +/// Nucleo F429ZI HSE frequency in MHz +pub const NUCLEO_F429ZI_HSE_FREQUENCY_MHZ: usize = 8; + /// A structure representing this platform that holds references to all /// capsules for this platform. struct NucleoF429ZI { - console: &'static capsules::console::Console<'static>, - ipc: kernel::ipc::IPC, - led: &'static capsules::led::LedDriver< + console: &'static capsules_core::console::Console<'static>, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + led: &'static capsules_core::led::LedDriver< 'static, LedHigh<'static, stm32f429zi::gpio::Pin<'static>>, 3, >, - button: &'static capsules::button::Button<'static, stm32f429zi::gpio::Pin<'static>>, - adc: &'static capsules::adc::AdcVirtualized<'static>, - alarm: &'static capsules::alarm::AlarmDriver< + button: &'static capsules_core::button::Button<'static, stm32f429zi::gpio::Pin<'static>>, + adc: &'static capsules_core::adc::AdcVirtualized<'static>, + dac: &'static capsules_extra::dac::Dac<'static>, + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, VirtualMuxAlarm<'static, stm32f429zi::tim2::Tim2<'static>>, >, - temperature: &'static capsules::temperature::TemperatureSensor<'static>, - gpio: &'static capsules::gpio::GPIO<'static, stm32f429zi::gpio::Pin<'static>>, + temperature: &'static TemperatureDriver, + gpio: &'static capsules_core::gpio::GPIO<'static, stm32f429zi::gpio::Pin<'static>>, + rng: &'static RngDriver, scheduler: &'static RoundRobinSched<'static>, systick: cortexm4::systick::SysTick, + can: &'static capsules_extra::can::CanCapsule<'static, stm32f429zi::can::Can<'static>>, + date_time: &'static capsules_extra::date_time::DateTimeCapsule< + 'static, + stm32f429zi::rtc::Rtc<'static>, + >, } /// Mapping of integer syscalls to objects that implement syscalls. @@ -73,14 +101,18 @@ impl SyscallDriverLookup for NucleoF429ZI { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::button::DRIVER_NUM => f(Some(self.button)), - capsules::adc::DRIVER_NUM => f(Some(self.adc)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::temperature::DRIVER_NUM => f(Some(self.temperature)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temperature)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::rng::DRIVER_NUM => f(Some(self.rng)), + capsules_extra::can::DRIVER_NUM => f(Some(self.can)), + capsules_extra::dac::DRIVER_NUM => f(Some(self.dac)), + capsules_extra::date_time::DRIVER_NUM => f(Some(self.date_time)), _ => f(None), } } @@ -103,7 +135,7 @@ impl type ContextSwitchCallback = (); fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -127,11 +159,11 @@ impl /// Helper function called during bring-up that configures DMA. unsafe fn setup_dma( - dma: &stm32f429zi::dma1::Dma1, - dma_streams: &'static [stm32f429zi::dma1::Stream; 8], - usart3: &'static stm32f429zi::usart::Usart, + dma: &stm32f429zi::dma::Dma1, + dma_streams: &'static [stm32f429zi::dma::Stream; 8], + usart3: &'static stm32f429zi::usart::Usart, ) { - use stm32f429zi::dma1::Dma1Peripheral; + use stm32f429zi::dma::Dma1Peripheral; use stm32f429zi::usart; dma.enable_clock(); @@ -223,24 +255,41 @@ unsafe fn set_pin_primary_functions( pin.set_mode(stm32f429zi::gpio::Mode::AnalogMode); }); - // Arduino A3 - gpio_ports.get_pin(PinId::PF03).map(|pin| { + // Arduino A6 + gpio_ports.get_pin(PinId::PB01).map(|pin| { pin.set_mode(stm32f429zi::gpio::Mode::AnalogMode); }); - // Arduino A4 - gpio_ports.get_pin(PinId::PF05).map(|pin| { + // Arduino A7 + gpio_ports.get_pin(PinId::PC02).map(|pin| { pin.set_mode(stm32f429zi::gpio::Mode::AnalogMode); }); - // Arduino A5 - gpio_ports.get_pin(PinId::PF10).map(|pin| { + gpio_ports.get_pin(PinId::PD00).map(|pin| { + pin.set_mode(Mode::AlternateFunctionMode); + // AF9 is CAN_RX + pin.set_alternate_function(AlternateFunction::AF9); + pin.set_floating_state(kernel::hil::gpio::FloatingState::PullDown); + }); + gpio_ports.get_pin(PinId::PD01).map(|pin| { + pin.set_mode(Mode::AlternateFunctionMode); + // AF9 is CAN_TX + pin.set_alternate_function(AlternateFunction::AF9); + }); + + // DAC Channel 1 + gpio_ports.get_pin(PinId::PA04).map(|pin| { pin.set_mode(stm32f429zi::gpio::Mode::AnalogMode); }); } /// Helper function for miscellaneous peripheral functions -unsafe fn setup_peripherals(tim2: &stm32f429zi::tim2::Tim2) { +unsafe fn setup_peripherals( + tim2: &stm32f429zi::tim2::Tim2, + trng: &stm32f429zi::trng::Trng, + can1: &'static stm32f429zi::can::Can, + rtc: &'static stm32f429zi::rtc::Rtc, +) { // USART3 IRQn is 39 cortexm4::nvic::Nvic::new(stm32f429zi::nvic::USART3).enable(); @@ -248,67 +297,69 @@ unsafe fn setup_peripherals(tim2: &stm32f429zi::tim2::Tim2) { tim2.enable_clock(); tim2.start(); cortexm4::nvic::Nvic::new(stm32f429zi::nvic::TIM2).enable(); + + // RNG + trng.enable_clock(); + + // CAN + can1.enable_clock(); + + // RTC + rtc.enable_clock(); } -/// Statically initialize the core peripherals for the chip. -/// /// This is in a separate, inline(never) function so that its stack frame is /// removed when this function returns. Otherwise, the stack space used for /// these static_inits is wasted. #[inline(never)] -unsafe fn get_peripherals() -> ( - &'static mut Stm32f429ziDefaultPeripherals<'static>, - &'static stm32f429zi::syscfg::Syscfg<'static>, - &'static stm32f429zi::dma1::Dma1<'static>, +unsafe fn start() -> ( + &'static kernel::Kernel, + NucleoF429ZI, + &'static stm32f429zi::chip::Stm32f4xx<'static, Stm32f429ziDefaultPeripherals<'static>>, ) { + stm32f429zi::init(); + // We use the default HSI 16Mhz clock let rcc = static_init!(stm32f429zi::rcc::Rcc, stm32f429zi::rcc::Rcc::new()); + let clocks = static_init!( + stm32f429zi::clocks::Clocks, + stm32f429zi::clocks::Clocks::new(rcc) + ); + let syscfg = static_init!( stm32f429zi::syscfg::Syscfg, - stm32f429zi::syscfg::Syscfg::new(rcc) + stm32f429zi::syscfg::Syscfg::new(clocks) ); let exti = static_init!( stm32f429zi::exti::Exti, stm32f429zi::exti::Exti::new(syscfg) ); - let dma1 = static_init!(stm32f429zi::dma1::Dma1, stm32f429zi::dma1::Dma1::new(rcc)); + let dma1 = static_init!(stm32f429zi::dma::Dma1, stm32f429zi::dma::Dma1::new(clocks)); + let dma2 = static_init!(stm32f429zi::dma::Dma2, stm32f429zi::dma::Dma2::new(clocks)); + let peripherals = static_init!( Stm32f429ziDefaultPeripherals, - Stm32f429ziDefaultPeripherals::new(rcc, exti, dma1) + Stm32f429ziDefaultPeripherals::new(clocks, exti, dma1, dma2) ); - (peripherals, syscfg, dma1) -} - -/// Main function. -/// -/// This is called after RAM initialization is complete. -#[no_mangle] -pub unsafe fn main() { - stm32f429zi::init(); - - let (peripherals, syscfg, dma1) = get_peripherals(); peripherals.init(); let base_peripherals = &peripherals.stm32f4; - setup_peripherals(&base_peripherals.tim2); + setup_peripherals( + &base_peripherals.tim2, + &peripherals.trng, + &peripherals.can1, + &peripherals.rtc, + ); set_pin_primary_functions(syscfg, &base_peripherals.gpio_ports); setup_dma( dma1, - &base_peripherals.dma_streams, + &base_peripherals.dma1_streams, &base_peripherals.usart3, ); - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); - - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 2], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); let chip = static_init!( stm32f429zi::chip::Stm32f4xx, @@ -320,38 +371,34 @@ pub unsafe fn main() { // Create a shared UART channel for kernel debug. base_peripherals.usart3.enable_clock(); - let uart_mux = components::console::UartMuxComponent::new( - &base_peripherals.usart3, - 115200, - dynamic_deferred_caller, - ) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(&base_peripherals.usart3, 115200) + .finalize(components::uart_mux_component_static!()); io::WRITER.set_initialized(); // Create capabilities that the board needs to call certain protected kernel // functions. let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); // LEDs // Clock to Port A is enabled in `set_pin_primary_functions()` let gpio_ports = &base_peripherals.gpio_ports; - let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( LedHigh<'static, stm32f429zi::gpio::Pin>, LedHigh::new(gpio_ports.get_pin(stm32f429zi::gpio::PinId::PB00).unwrap()), LedHigh::new(gpio_ports.get_pin(stm32f429zi::gpio::PinId::PB07).unwrap()), @@ -361,7 +408,7 @@ pub unsafe fn main() { // BUTTONs let button = components::button::ButtonComponent::new( board_kernel, - capsules::button::DRIVER_NUM, + capsules_core::button::DRIVER_NUM, components::button_component_helper!( stm32f429zi::gpio::Pin, ( @@ -371,26 +418,26 @@ pub unsafe fn main() { ) ), ) - .finalize(components::button_component_buf!(stm32f429zi::gpio::Pin)); + .finalize(components::button_component_static!(stm32f429zi::gpio::Pin)); // ALARM let tim2 = &base_peripherals.tim2; let mux_alarm = components::alarm::AlarmMuxComponent::new(tim2).finalize( - components::alarm_mux_component_helper!(stm32f429zi::tim2::Tim2), + components::alarm_mux_component_static!(stm32f429zi::tim2::Tim2), ); let alarm = components::alarm::AlarmDriverComponent::new( board_kernel, - capsules::alarm::DRIVER_NUM, + capsules_core::alarm::DRIVER_NUM, mux_alarm, ) - .finalize(components::alarm_component_helper!(stm32f429zi::tim2::Tim2)); + .finalize(components::alarm_component_static!(stm32f429zi::tim2::Tim2)); // GPIO let gpio = GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( stm32f429zi::gpio::Pin, // Arduino like RX/TX @@ -472,116 +519,153 @@ pub unsafe fn main() { 68 => gpio_ports.pins[5][0].as_ref().unwrap(), //D68 69 => gpio_ports.pins[5][1].as_ref().unwrap(), //D69 70 => gpio_ports.pins[5][2].as_ref().unwrap(), //D70 - 71 => gpio_ports.pins[0][7].as_ref().unwrap() //D71 + 71 => gpio_ports.pins[0][7].as_ref().unwrap(), //D71 // ADC Pins // Enable the to use the ADC pins as GPIO // 72 => gpio_ports.pins[0][3].as_ref().unwrap(), //A0 // 73 => gpio_ports.pins[2][0].as_ref().unwrap(), //A1 // 74 => gpio_ports.pins[2][3].as_ref().unwrap(), //A2 - // 75 => gpio_ports.pins[5][3].as_ref().unwrap(), //A3 - // 76 => gpio_ports.pins[5][5].as_ref().unwrap(), //A4 - // 77 => gpio_ports.pins[5][10].as_ref().unwrap(), //A5 + 75 => gpio_ports.pins[5][3].as_ref().unwrap(), //A3 + 76 => gpio_ports.pins[5][5].as_ref().unwrap(), //A4 + 77 => gpio_ports.pins[5][10].as_ref().unwrap(), //A5 // 78 => gpio_ports.pins[1][1].as_ref().unwrap(), //A6 // 79 => gpio_ports.pins[2][2].as_ref().unwrap(), //A7 - // 80 => gpio_ports.pins[5][4].as_ref().unwrap() //A8 + 80 => gpio_ports.pins[5][4].as_ref().unwrap() //A8 ), ) - .finalize(components::gpio_component_buf!(stm32f429zi::gpio::Pin)); + .finalize(components::gpio_component_static!(stm32f429zi::gpio::Pin)); // ADC let adc_mux = components::adc::AdcMuxComponent::new(&base_peripherals.adc1) - .finalize(components::adc_mux_component_helper!(stm32f429zi::adc::Adc)); - - let temp_sensor = components::temperature_stm::TemperatureSTMComponent::new(2.5, 0.76) - .finalize(components::temperaturestm_adc_component_helper!( - // spi type - stm32f429zi::adc::Adc, - // chip select - stm32f429zi::adc::Channel::Channel18, - // spi mux - adc_mux - )); - let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let grant_temperature = - board_kernel.create_grant(capsules::temperature::DRIVER_NUM, &grant_cap); - - let temp = static_init!( - capsules::temperature::TemperatureSensor<'static>, - capsules::temperature::TemperatureSensor::new(temp_sensor, grant_temperature) - ); - kernel::hil::sensors::TemperatureDriver::set_client(temp_sensor, temp); + .finalize(components::adc_mux_component_static!(stm32f429zi::adc::Adc)); + + let temp_sensor = components::temperature_stm::TemperatureSTMComponent::new( + adc_mux, + stm32f429zi::adc::Channel::Channel18, + 2.5, + 0.76, + ) + .finalize(components::temperature_stm_adc_component_static!( + stm32f429zi::adc::Adc + )); + + let temp = components::temperature::TemperatureComponent::new( + board_kernel, + capsules_extra::temperature::DRIVER_NUM, + temp_sensor, + ) + .finalize(components::temperature_component_static!( + TemperatureSTMSensor + )); let adc_channel_0 = - components::adc::AdcComponent::new(&adc_mux, stm32f429zi::adc::Channel::Channel3) - .finalize(components::adc_component_helper!(stm32f429zi::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f429zi::adc::Channel::Channel3) + .finalize(components::adc_component_static!(stm32f429zi::adc::Adc)); let adc_channel_1 = - components::adc::AdcComponent::new(&adc_mux, stm32f429zi::adc::Channel::Channel10) - .finalize(components::adc_component_helper!(stm32f429zi::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f429zi::adc::Channel::Channel10) + .finalize(components::adc_component_static!(stm32f429zi::adc::Adc)); let adc_channel_2 = - components::adc::AdcComponent::new(&adc_mux, stm32f429zi::adc::Channel::Channel13) - .finalize(components::adc_component_helper!(stm32f429zi::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f429zi::adc::Channel::Channel13) + .finalize(components::adc_component_static!(stm32f429zi::adc::Adc)); let adc_channel_3 = - components::adc::AdcComponent::new(&adc_mux, stm32f429zi::adc::Channel::Channel9) - .finalize(components::adc_component_helper!(stm32f429zi::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f429zi::adc::Channel::Channel9) + .finalize(components::adc_component_static!(stm32f429zi::adc::Adc)); let adc_channel_4 = - components::adc::AdcComponent::new(&adc_mux, stm32f429zi::adc::Channel::Channel15) - .finalize(components::adc_component_helper!(stm32f429zi::adc::Adc)); - - let adc_channel_5 = - components::adc::AdcComponent::new(&adc_mux, stm32f429zi::adc::Channel::Channel8) - .finalize(components::adc_component_helper!(stm32f429zi::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f429zi::adc::Channel::Channel12) + .finalize(components::adc_component_static!(stm32f429zi::adc::Adc)); let adc_syscall = - components::adc::AdcVirtualComponent::new(board_kernel, capsules::adc::DRIVER_NUM) + components::adc::AdcVirtualComponent::new(board_kernel, capsules_core::adc::DRIVER_NUM) .finalize(components::adc_syscall_component_helper!( adc_channel_0, adc_channel_1, adc_channel_2, adc_channel_3, adc_channel_4, - adc_channel_5 )); - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); + // DAC + let dac = components::dac::DacComponent::new(&base_peripherals.dac) + .finalize(components::dac_component_static!()); + + // RNG + let rng = components::rng::RngComponent::new( + board_kernel, + capsules_core::rng::DRIVER_NUM, + &peripherals.trng, + ) + .finalize(components::rng_component_static!(stm32f429zi::trng::Trng)); + + // CAN + let can = components::can::CanComponent::new( + board_kernel, + capsules_extra::can::DRIVER_NUM, + &peripherals.can1, + ) + .finalize(components::can_component_static!( + stm32f429zi::can::Can<'static> + )); + + // RTC DATE TIME + match peripherals.rtc.rtc_init() { + Err(e) => debug!("{:?}", e), + _ => (), + }; + + let date_time = components::date_time::DateTimeComponent::new( + board_kernel, + capsules_extra::date_time::DRIVER_NUM, + &peripherals.rtc, + ) + .finalize(components::date_time_component_static!( + stm32f429zi::rtc::Rtc<'static> + )); + // PROCESS CONSOLE let process_console = components::process_console::ProcessConsoleComponent::new( board_kernel, uart_mux, mux_alarm, process_printer, + Some(cortexm4::support::reset), ) - .finalize(components::process_console_component_helper!( + .finalize(components::process_console_component_static!( stm32f429zi::tim2::Tim2 )); let _ = process_console.start(); - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); let nucleo_f429zi = NucleoF429ZI { - console: console, + console, ipc: kernel::ipc::IPC::new( board_kernel, kernel::ipc::DRIVER_NUM, &memory_allocation_capability, ), adc: adc_syscall, - led: led, + dac, + led, temperature: temp, - button: button, - alarm: alarm, - gpio: gpio, + button, + alarm, + gpio, + rng, scheduler, systick: cortexm4::systick::SysTick::new(), + can, + date_time, }; // // Optional kernel tests @@ -591,7 +675,7 @@ pub unsafe fn main() { debug!("Initialization complete. Entering main loop"); - /// These symbols are defined in the linker script. + // These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -607,14 +691,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_management_capability, ) @@ -628,10 +712,14 @@ pub unsafe fn main() { .finalize(components::multi_alarm_test_component_buf!(stm32f429zi::tim2::Tim2)) .run();*/ - board_kernel.kernel_loop( - &nucleo_f429zi, - chip, - Some(&nucleo_f429zi.ipc), - &main_loop_capability, - ); + (board_kernel, nucleo_f429zi, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); } diff --git a/boards/nucleo_f446re/Cargo.toml b/boards/nucleo_f446re/Cargo.toml index 05d545db73..104ed244c9 100644 --- a/boards/nucleo_f446re/Cargo.toml +++ b/boards/nucleo_f446re/Cargo.toml @@ -1,13 +1,23 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "nucleo_f446re" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] components = { path = "../components" } cortexm4 = { path = "../../arch/cortex-m4" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } stm32f446re = { path = "../../chips/stm32f446re" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/nucleo_f446re/Makefile b/boards/nucleo_f446re/Makefile index 3d26155942..a816188618 100644 --- a/boards/nucleo_f446re/Makefile +++ b/boards/nucleo_f446re/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the NUCLEO-446RE platform # TARGET=thumbv7em-none-eabi @@ -6,7 +10,6 @@ PLATFORM=nucleo_f446re include ../Makefile.common OPENOCD=openocd -OPENOCD_OPTIONS=-f openocd.cfg # Default target for installing the kernel. .PHONY: install @@ -14,11 +17,11 @@ install: flash .PHONY: flash-debug flash-debug: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).elf - $(OPENOCD) $(OPENOCD_OPTIONS) -c "init; reset halt; flash write_image erase $<; verify_image $<; reset; shutdown" + $(OPENOCD) -c "source [find board/st_nucleo_f4.cfg]; init; reset halt; flash write_image erase $<; verify_image $<; reset; shutdown" .PHONY: flash flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf - $(OPENOCD) $(OPENOCD_OPTIONS) -c "init; reset halt; flash write_image erase $<; verify_image $<; reset; shutdown" + $(OPENOCD) -c "source [find board/st_nucleo_f4.cfg]; init; reset halt; flash write_image erase $<; verify_image $<; reset; shutdown" .PHONY: program program: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).elf diff --git a/boards/nucleo_f446re/README.md b/boards/nucleo_f446re/README.md index 768eeca701..3123473970 100644 --- a/boards/nucleo_f446re/README.md +++ b/boards/nucleo_f446re/README.md @@ -56,3 +56,19 @@ $ make program ``` to flash the image. + +### (Linux): Adding a `udev` rule + +You may want to add a `udev` rule in `/etc/udev/rules.d` that allows you to +interact with the board as a user instead of as root. You can install this as +`/etc/udev/rules.d/99-stlinkv2-1.rules`: + +``` +# stm32 nucleo boards, with onboard st/linkv2-1 +# ie, STM32F0, STM32F4. +# STM32VL has st/linkv1, which is quite different + +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", \ + MODE:="0660", GROUP="dialout", \ + SYMLINK+="stlinkv2-1_%n" +``` diff --git a/boards/nucleo_f446re/build.rs b/boards/nucleo_f446re/build.rs deleted file mode 100644 index 46b1df0bac..0000000000 --- a/boards/nucleo_f446re/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=chip_layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/nucleo_f446re/chip_layout.ld b/boards/nucleo_f446re/chip_layout.ld index d9aa72c4e8..3cc698939d 100644 --- a/boards/nucleo_f446re/chip_layout.ld +++ b/boards/nucleo_f446re/chip_layout.ld @@ -1,3 +1,7 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + /* Memory layout for the STM32F446RE * rom = 512KB (LENGTH = 0x00080000) * kernel = 256KB @@ -11,5 +15,4 @@ MEMORY ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00020000 } -MPU_MIN_ALIGN = 8K; PAGE_SIZE = 2K; diff --git a/boards/nucleo_f446re/layout.ld b/boards/nucleo_f446re/layout.ld index 234dcaea2c..6814c00acd 100644 --- a/boards/nucleo_f446re/layout.ld +++ b/boards/nucleo_f446re/layout.ld @@ -1,2 +1,6 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + INCLUDE ./chip_layout.ld INCLUDE ../kernel_layout.ld diff --git a/boards/nucleo_f446re/openocd.cfg b/boards/nucleo_f446re/openocd.cfg deleted file mode 100644 index 8028f46c46..0000000000 --- a/boards/nucleo_f446re/openocd.cfg +++ /dev/null @@ -1,11 +0,0 @@ -#interface -interface hla -hla_layout stlink -hla_device_desc "ST-LINK/V2-1" -hla_vid_pid 0x0483 0x374b - -# The chip has 128KB sram, but we will still use 64KB -set WORKAREASIZE 0x10000 - -source [find target/stm32f4x.cfg] - diff --git a/boards/nucleo_f446re/src/io.rs b/boards/nucleo_f446re/src/io.rs index 82e04fcf46..54c110ab84 100644 --- a/boards/nucleo_f446re/src/io.rs +++ b/boards/nucleo_f446re/src/io.rs @@ -1,7 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; - -use cortexm4; +use core::ptr::{addr_of, addr_of_mut}; use kernel::debug; use kernel::debug::IoWrite; @@ -9,7 +12,7 @@ use kernel::hil::led; use kernel::hil::uart; use kernel::hil::uart::Configure; -use stm32f446re; +use stm32f446re::chip_specs::Stm32f446Specs; use stm32f446re::gpio::PinId; use crate::CHIP; @@ -40,9 +43,11 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { let rcc = stm32f446re::rcc::Rcc::new(); - let uart = stm32f446re::usart::Usart::new_usart2(&rcc); + let clocks: stm32f446re::clocks::Clocks = + stm32f446re::clocks::Clocks::new(&rcc); + let uart = stm32f446re::usart::Usart::new_usart2(&clocks); if !self.initialized { self.initialized = true; @@ -59,32 +64,35 @@ impl IoWrite for Writer { for &c in buf { uart.send_byte(c); } + buf.len() } } /// Panic handler. #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(info: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(info: &PanicInfo) -> ! { // User LD2 is connected to PA05 // Have to reinitialize several peripherals because otherwise can't access them here. let rcc = stm32f446re::rcc::Rcc::new(); - let syscfg = stm32f446re::syscfg::Syscfg::new(&rcc); + let clocks: stm32f446re::clocks::Clocks = + stm32f446re::clocks::Clocks::new(&rcc); + let syscfg = stm32f446re::syscfg::Syscfg::new(&clocks); let exti = stm32f446re::exti::Exti::new(&syscfg); let pin = stm32f446re::gpio::Pin::new(PinId::PA05, &exti); - let gpio_ports = stm32f446re::gpio::GpioPorts::new(&rcc, &exti); + let gpio_ports = stm32f446re::gpio::GpioPorts::new(&clocks, &exti); pin.set_ports_ref(&gpio_ports); let led = &mut led::LedHigh::new(&pin); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led], writer, info, &cortexm4::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/nucleo_f446re/src/main.rs b/boards/nucleo_f446re/src/main.rs index 0f82c91d42..ee24c206b6 100644 --- a/boards/nucleo_f446re/src/main.rs +++ b/boards/nucleo_f446re/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Board file for Nucleo-F446RE development board //! //! - @@ -8,15 +12,19 @@ #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] -use capsules::virtual_alarm::VirtualMuxAlarm; +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; +use components::gpio::GpioComponent; use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil::gpio::Configure; use kernel::hil::led::LedHigh; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::scheduler::round_robin::RoundRobinSched; use kernel::{create_capability, debug, static_init}; +use stm32f446re::chip_specs::Stm32f446Specs; +use stm32f446re::gpio::{AlternateFunction, Mode, PinId, PortId}; use stm32f446re::interrupt_service::Stm32f446reDefaultPeripherals; /// Support routines for debugging I/O. @@ -37,32 +45,43 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] static mut CHIP: Option<&'static stm32f446re::chip::Stm32f4xx> = None; // Static reference to process printer for panic dumps. -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; /// Dummy buffer that causes the linker to reserve enough space for the stack. #[no_mangle] #[link_section = ".stack_buffer"] pub static mut STACK_MEMORY: [u8; 0x2000] = [0; 0x2000]; +type TemperatureSTMSensor = components::temperature_stm::TemperatureSTMComponentType< + capsules_core::virtualizers::virtual_adc::AdcDevice<'static, stm32f446re::adc::Adc<'static>>, +>; +type TemperatureDriver = components::temperature::TemperatureComponentType; + /// A structure representing this platform that holds references to all /// capsules for this platform. struct NucleoF446RE { - console: &'static capsules::console::Console<'static>, - ipc: kernel::ipc::IPC, - led: &'static capsules::led::LedDriver< + console: &'static capsules_core::console::Console<'static>, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + led: &'static capsules_core::led::LedDriver< 'static, LedHigh<'static, stm32f446re::gpio::Pin<'static>>, 1, >, - button: &'static capsules::button::Button<'static, stm32f446re::gpio::Pin<'static>>, - alarm: &'static capsules::alarm::AlarmDriver< + button: &'static capsules_core::button::Button<'static, stm32f446re::gpio::Pin<'static>>, + adc: &'static capsules_core::adc::AdcVirtualized<'static>, + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, VirtualMuxAlarm<'static, stm32f446re::tim2::Tim2<'static>>, >, + temperature: &'static TemperatureDriver, + gpio: &'static capsules_core::gpio::GPIO<'static, stm32f446re::gpio::Pin<'static>>, + scheduler: &'static RoundRobinSched<'static>, systick: cortexm4::systick::SysTick, } @@ -74,10 +93,13 @@ impl SyscallDriverLookup for NucleoF446RE { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::button::DRIVER_NUM => f(Some(self.button)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temperature)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), _ => f(None), } @@ -101,7 +123,7 @@ impl type ContextSwitchCallback = (); fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -125,11 +147,11 @@ impl /// Helper function called during bring-up that configures DMA. unsafe fn setup_dma( - dma: &stm32f446re::dma1::Dma1, - dma_streams: &'static [stm32f446re::dma1::Stream; 8], - usart2: &'static stm32f446re::usart::Usart, + dma: &stm32f446re::dma::Dma1, + dma_streams: &'static [stm32f446re::dma::Stream; 8], + usart2: &'static stm32f446re::usart::Usart, ) { - use stm32f446re::dma1::Dma1Peripheral; + use stm32f446re::dma::Dma1Peripheral; use stm32f446re::usart; dma.enable_clock(); @@ -157,11 +179,10 @@ unsafe fn set_pin_primary_functions( syscfg: &stm32f446re::syscfg::Syscfg, gpio_ports: &'static stm32f446re::gpio::GpioPorts<'static>, ) { - use stm32f446re::gpio::{AlternateFunction, Mode, PinId, PortId}; - syscfg.enable_clock(); gpio_ports.get_port_from_port_id(PortId::A).enable_clock(); + gpio_ports.get_port_from_port_id(PortId::B).enable_clock(); // User LD2 is connected to PA05. Configure PA05 as `debug_gpio!(0, ...)` gpio_ports.get_pin(PinId::PA05).map(|pin| { @@ -189,6 +210,41 @@ unsafe fn set_pin_primary_functions( gpio_ports.get_pin(PinId::PC13).map(|pin| { pin.enable_interrupt(); }); + + // enable interrupt for gpio 2 + gpio_ports.get_pin(PinId::PA10).map(|pin| { + pin.enable_interrupt(); + }); + + // Arduino A0 + gpio_ports.get_pin(PinId::PA00).map(|pin| { + pin.set_mode(stm32f446re::gpio::Mode::AnalogMode); + }); + + // Arduino A1 + gpio_ports.get_pin(PinId::PA01).map(|pin| { + pin.set_mode(stm32f446re::gpio::Mode::AnalogMode); + }); + + // Arduino A2 + gpio_ports.get_pin(PinId::PA04).map(|pin| { + pin.set_mode(stm32f446re::gpio::Mode::AnalogMode); + }); + + // Arduino A3 + gpio_ports.get_pin(PinId::PB00).map(|pin| { + pin.set_mode(stm32f446re::gpio::Mode::AnalogMode); + }); + + // Arduino A4 + gpio_ports.get_pin(PinId::PC01).map(|pin| { + pin.set_mode(stm32f446re::gpio::Mode::AnalogMode); + }); + + // Arduino A5 + gpio_ports.get_pin(PinId::PC00).map(|pin| { + pin.set_mode(stm32f446re::gpio::Mode::AnalogMode); + }); } /// Helper function for miscellaneous peripheral functions @@ -202,43 +258,39 @@ unsafe fn setup_peripherals(tim2: &stm32f446re::tim2::Tim2) { cortexm4::nvic::Nvic::new(stm32f446re::nvic::TIM2).enable(); } -/// Statically initialize the core peripherals for the chip. -/// /// This is in a separate, inline(never) function so that its stack frame is /// removed when this function returns. Otherwise, the stack space used for /// these static_inits is wasted. #[inline(never)] -unsafe fn get_peripherals() -> ( - &'static mut Stm32f446reDefaultPeripherals<'static>, - &'static stm32f446re::syscfg::Syscfg<'static>, - &'static stm32f446re::dma1::Dma1<'static>, +unsafe fn start() -> ( + &'static kernel::Kernel, + NucleoF446RE, + &'static stm32f446re::chip::Stm32f4xx<'static, Stm32f446reDefaultPeripherals<'static>>, ) { + stm32f446re::init(); + // We use the default HSI 16Mhz clock let rcc = static_init!(stm32f446re::rcc::Rcc, stm32f446re::rcc::Rcc::new()); + let clocks = static_init!( + stm32f446re::clocks::Clocks, + stm32f446re::clocks::Clocks::new(rcc) + ); + let syscfg = static_init!( stm32f446re::syscfg::Syscfg, - stm32f446re::syscfg::Syscfg::new(rcc) + stm32f446re::syscfg::Syscfg::new(clocks) ); let exti = static_init!( stm32f446re::exti::Exti, stm32f446re::exti::Exti::new(syscfg) ); - let dma1 = static_init!(stm32f446re::dma1::Dma1, stm32f446re::dma1::Dma1::new(rcc)); + let dma1 = static_init!(stm32f446re::dma::Dma1, stm32f446re::dma::Dma1::new(clocks)); + let dma2 = static_init!(stm32f446re::dma::Dma2, stm32f446re::dma::Dma2::new(clocks)); + let peripherals = static_init!( Stm32f446reDefaultPeripherals, - Stm32f446reDefaultPeripherals::new(rcc, exti, dma1) + Stm32f446reDefaultPeripherals::new(clocks, exti, dma1, dma2) ); - (peripherals, syscfg, dma1) -} - -/// Main function. -/// -/// This is called after RAM initialization is complete. -#[no_mangle] -pub unsafe fn main() { - stm32f446re::init(); - - let (peripherals, syscfg, dma1) = get_peripherals(); peripherals.init(); let base_peripherals = &peripherals.stm32f4; @@ -248,18 +300,11 @@ pub unsafe fn main() { setup_dma( dma1, - &base_peripherals.dma_streams, + &base_peripherals.dma1_streams, &base_peripherals.usart2, ); - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 2], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); let chip = static_init!( stm32f446re::chip::Stm32f4xx, @@ -271,12 +316,8 @@ pub unsafe fn main() { // Create a shared UART channel for kernel debug. base_peripherals.usart2.enable_clock(); - let uart_mux = components::console::UartMuxComponent::new( - &base_peripherals.usart2, - 115200, - dynamic_deferred_caller, - ) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(&base_peripherals.usart2, 115200) + .finalize(components::uart_mux_component_static!()); // `finalize()` configures the underlying USART, so we need to // tell `send_byte()` not to configure the USART again. @@ -285,25 +326,25 @@ pub unsafe fn main() { // Create capabilities that the board needs to call certain protected kernel // functions. let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); // LEDs let gpio_ports = &base_peripherals.gpio_ports; // Clock to Port A is enabled in `set_pin_primary_functions()` - let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( LedHigh<'static, stm32f446re::gpio::Pin>, LedHigh::new(gpio_ports.get_pin(stm32f446re::gpio::PinId::PA05).unwrap()), )); @@ -311,7 +352,7 @@ pub unsafe fn main() { // BUTTONs let button = components::button::ButtonComponent::new( board_kernel, - capsules::button::DRIVER_NUM, + capsules_core::button::DRIVER_NUM, components::button_component_helper!( stm32f446re::gpio::Pin, ( @@ -321,50 +362,149 @@ pub unsafe fn main() { ) ), ) - .finalize(components::button_component_buf!(stm32f446re::gpio::Pin)); + .finalize(components::button_component_static!(stm32f446re::gpio::Pin)); // ALARM let tim2 = &base_peripherals.tim2; let mux_alarm = components::alarm::AlarmMuxComponent::new(tim2).finalize( - components::alarm_mux_component_helper!(stm32f446re::tim2::Tim2), + components::alarm_mux_component_static!(stm32f446re::tim2::Tim2), ); let alarm = components::alarm::AlarmDriverComponent::new( board_kernel, - capsules::alarm::DRIVER_NUM, + capsules_core::alarm::DRIVER_NUM, mux_alarm, ) - .finalize(components::alarm_component_helper!(stm32f446re::tim2::Tim2)); + .finalize(components::alarm_component_static!(stm32f446re::tim2::Tim2)); - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + // ADC + let adc_mux = components::adc::AdcMuxComponent::new(&base_peripherals.adc1) + .finalize(components::adc_mux_component_static!(stm32f446re::adc::Adc)); + + let temp_sensor = components::temperature_stm::TemperatureSTMComponent::new( + adc_mux, + stm32f446re::adc::Channel::Channel18, + 2.5, + 0.76, + ) + .finalize(components::temperature_stm_adc_component_static!( + stm32f446re::adc::Adc + )); + + let temp = components::temperature::TemperatureComponent::new( + board_kernel, + capsules_extra::temperature::DRIVER_NUM, + temp_sensor, + ) + .finalize(components::temperature_component_static!( + TemperatureSTMSensor + )); + + let adc_channel_0 = + components::adc::AdcComponent::new(adc_mux, stm32f446re::adc::Channel::Channel0) + .finalize(components::adc_component_static!(stm32f446re::adc::Adc)); + + let adc_channel_1 = + components::adc::AdcComponent::new(adc_mux, stm32f446re::adc::Channel::Channel1) + .finalize(components::adc_component_static!(stm32f446re::adc::Adc)); + + let adc_channel_2 = + components::adc::AdcComponent::new(adc_mux, stm32f446re::adc::Channel::Channel4) + .finalize(components::adc_component_static!(stm32f446re::adc::Adc)); + + let adc_channel_3 = + components::adc::AdcComponent::new(adc_mux, stm32f446re::adc::Channel::Channel8) + .finalize(components::adc_component_static!(stm32f446re::adc::Adc)); + + let adc_channel_4 = + components::adc::AdcComponent::new(adc_mux, stm32f446re::adc::Channel::Channel11) + .finalize(components::adc_component_static!(stm32f446re::adc::Adc)); + + let adc_channel_5 = + components::adc::AdcComponent::new(adc_mux, stm32f446re::adc::Channel::Channel10) + .finalize(components::adc_component_static!(stm32f446re::adc::Adc)); + + let adc_syscall = + components::adc::AdcVirtualComponent::new(board_kernel, capsules_core::adc::DRIVER_NUM) + .finalize(components::adc_syscall_component_helper!( + adc_channel_0, + adc_channel_1, + adc_channel_2, + adc_channel_3, + adc_channel_4, + adc_channel_5 + )); + + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); + // GPIO + let gpio = GpioComponent::new( + board_kernel, + capsules_core::gpio::DRIVER_NUM, + components::gpio_component_helper!( + stm32f446re::gpio::Pin, + // Arduino like RX/TX + // 0 => gpio_ports.get_pin(PinId::PA03).unwrap(), //D0 + // 1 => gpio_ports.get_pin(PinId::PA02).unwrap(), //D1 + 2 => gpio_ports.get_pin(PinId::PA10).unwrap(), //D2 + 3 => gpio_ports.get_pin(PinId::PB03).unwrap(), //D3 + 4 => gpio_ports.get_pin(PinId::PB05).unwrap(), //D4 + 5 => gpio_ports.get_pin(PinId::PB04).unwrap(), //D5 + 6 => gpio_ports.get_pin(PinId::PB10).unwrap(), //D6 + 7 => gpio_ports.get_pin(PinId::PA08).unwrap(), //D7 + 8 => gpio_ports.get_pin(PinId::PA09).unwrap(), //D8 + 9 => gpio_ports.get_pin(PinId::PC07).unwrap(), //D9 + 10 => gpio_ports.get_pin(PinId::PB06).unwrap(), //D10 + 11 => gpio_ports.get_pin(PinId::PA07).unwrap(), //D11 + 12 => gpio_ports.get_pin(PinId::PA06).unwrap(), //D12 + 13 => gpio_ports.get_pin(PinId::PA05).unwrap(), //D13 + 14 => gpio_ports.get_pin(PinId::PB09).unwrap(), //D14 + 15 => gpio_ports.get_pin(PinId::PB08).unwrap(), //D15 + + // ADC Pins + // Enable the to use the ADC pins as GPIO + // 16 => gpio_ports.get_pin(PinId::PA00).unwrap(), //A0 + // 17 => gpio_ports.get_pin(PinId::PA01).unwrap(), //A1 + // 18 => gpio_ports.get_pin(PinId::PA04).unwrap(), //A2 + // 19 => gpio_ports.get_pin(PinId::PB00).unwrap(), //A3 + // 20 => gpio_ports.get_pin(PinId::PC01).unwrap(), //A4 + // 21 => gpio_ports.get_pin(PinId::PC00).unwrap(), //A5 + ), + ) + .finalize(components::gpio_component_static!(stm32f446re::gpio::Pin)); + // PROCESS CONSOLE let process_console = components::process_console::ProcessConsoleComponent::new( board_kernel, uart_mux, mux_alarm, process_printer, + Some(cortexm4::support::reset), ) - .finalize(components::process_console_component_helper!( + .finalize(components::process_console_component_static!( stm32f446re::tim2::Tim2 )); let _ = process_console.start(); - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); let nucleo_f446re = NucleoF446RE { - console: console, + console, ipc: kernel::ipc::IPC::new( board_kernel, kernel::ipc::DRIVER_NUM, &memory_allocation_capability, ), - led: led, - button: button, - alarm: alarm, + led, + button, + adc: adc_syscall, + alarm, + + temperature: temp, + gpio, scheduler, systick: cortexm4::systick::SysTick::new(), @@ -377,7 +517,7 @@ pub unsafe fn main() { debug!("Initialization complete. Entering main loop"); - /// These symbols are defined in the linker script. + // These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -393,14 +533,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_management_capability, ) @@ -413,10 +553,15 @@ pub unsafe fn main() { /*components::test::multi_alarm_test::MultiAlarmTestComponent::new(mux_alarm) .finalize(components::multi_alarm_test_component_buf!(stm32f446re::tim2::Tim2)) .run();*/ - board_kernel.kernel_loop( - &nucleo_f446re, - chip, - Some(&nucleo_f446re.ipc), - &main_loop_capability, - ); + + (board_kernel, nucleo_f446re, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); } diff --git a/boards/nucleo_f446re/src/virtual_uart_rx_test.rs b/boards/nucleo_f446re/src/virtual_uart_rx_test.rs index ba869dadca..81c6b126d3 100644 --- a/boards/nucleo_f446re/src/virtual_uart_rx_test.rs +++ b/boards/nucleo_f446re/src/virtual_uart_rx_test.rs @@ -1,10 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test reception on the virtualized UART by creating two readers that //! read in parallel. To add this test, include the line //! ``` //! virtual_uart_rx_test::run_virtual_uart_receive(mux_uart); //! ``` //! to the nucleo_446re boot sequence, where `mux_uart` is a -//! `capsules::virtual_uart::MuxUart`. There is a 3-byte and a 7-byte read +//! `capsules_core::virtualizers::virtual_uart::MuxUart`. There is a 3-byte and a 7-byte read //! running in parallel. Test that they are both working by typing and seeing //! that they both get all characters. If you repeatedly type 'a', for example //! (0x61), you should see something like: @@ -44,8 +48,10 @@ //! 61 //! ``` -use capsules::test::virtual_uart::TestVirtualUartReceive; -use capsules::virtual_uart::{MuxUart, UartDevice}; +use core::ptr::addr_of_mut; + +use capsules_core::test::virtual_uart::TestVirtualUartReceive; +use capsules_core::virtualizers::virtual_uart::{MuxUart, UartDevice}; use kernel::debug; use kernel::hil::uart::Receive; use kernel::static_init; @@ -66,7 +72,7 @@ unsafe fn static_init_test_receive_small( device.setup(); let test = static_init!( TestVirtualUartReceive, - TestVirtualUartReceive::new(device, &mut SMALL) + TestVirtualUartReceive::new(device, &mut *addr_of_mut!(SMALL)) ); device.set_receive_client(test); test @@ -80,7 +86,7 @@ unsafe fn static_init_test_receive_large( device.setup(); let test = static_init!( TestVirtualUartReceive, - TestVirtualUartReceive::new(device, &mut BUFFER) + TestVirtualUartReceive::new(device, &mut *addr_of_mut!(BUFFER)) ); device.set_receive_client(test); test diff --git a/boards/opentitan/README.md b/boards/opentitan/README.md index ebd888c25c..2cedd7d5c0 100644 --- a/boards/opentitan/README.md +++ b/boards/opentitan/README.md @@ -3,77 +3,60 @@ OpenTitan RISC-V Board - https://opentitan.org/ -OpenTitan is the first open source project building a transparent, +OpenTitan is an open source project building a transparent, high-quality reference design and integration guidelines for silicon root of trust (RoT) chips. -Tock currently supports OpenTitan on the Nexys Video and the ChipWhisperer -CW310 FPGA boards. For more details on the boards see: +Tock currently supports OpenTitan on the ChipWhisperer +CW310 FPGA board. For more details on the boards see: https://docs.opentitan.org/doc/ug/fpga_boards/ -You can get started with OpenTitan using either the Nexys Video FPGA -board, ChipWhisperer CW310 board or a simulation. See the OpenTitan -[getting started](https://docs.opentitan.org/doc/ug/getting_started/index.html) +You can get started with OpenTitan using either the ChipWhisperer CW310 +board or a simulation. See the OpenTitan +[getting started guide](https://opentitan.org/guides/getting_started/index.html) for more details. -Programming ------------ - -Tock on OpenTitan requires -lowRISC/opentitan@937d707e7b1a666bf2a06bbc2c774553d140497a or newer. In -general it is recommended that users start with the latest OpenTitan bitstream -and if that results in issues try the one mentioned above. - -For more information you can follow the -[OpenTitan development flow](https://docs.opentitan.org/doc/ug/getting_started_fpga/index.html#testing-the-demo-design) -to flash the image. +Supported version +----------------- -First setup the development board using the steps here: -https://docs.opentitan.org/doc/ug/getting_started_fpga/index.html. -You need to make sure the boot ROM is working and that your machine can -communicate with the OpenTitan ROM. You will need to use the `PROG` USB -port on the board for this. +The OpenTitan project is producing its first chip, _Earlgrey_. +You should use either an FPGA +[bitstream](https://storage.googleapis.com/opentitan-bitstreams/earlgrey_es/bitstream-edf5e35f5d50a5377641c90a315109a351de7635.tar.gz) +or simulator built from that version of the OpenTitan codebase. -Nexys Video +Programming ----------- -To use `make flash` you first need to clone the OpenTitan repo and build -the `spiflash` tool. +The latest supported version (commit SHA) of OpenTitan is specified in the +[OPENTITAN_SUPPORTED_SHA](https://github.com/tock/tock/blob/master/boards/opentitan/earlgrey-cw310/Makefile) +make variable found in `boards/opentitan/earlgrey-cw310/Makefile` -In the OpenTitan repo build the `spiflash` program. +In *general* it is recommended that users start with the commit specified by `OPENTITAN_SUPPORTED_SHA` as newer +versions **have not been** tested. -```shell -./meson_init.sh -ninja -C build-out sw/host/spiflash/spiflash_export -``` +> Note: when building, you can pass in `SKIP_OT_VERSION_CHECK=yes` to skip the trivial OpenTitan version check, this maybe useful when developing or testing across multiple versions of OpenTitan. -Export the `OPENTITAN_TREE` enviroment variable to point to the OpenTitan tree. +Setup +----- -```shell -export OPENTITAN_TREE=/home/opentitan/ -``` - -Back in the Tock board directory run `make flash` +### Setup OpenTitan -If everything works you should see something like this on the console. -If you need help getting console access check the -[testing the design](https://docs.opentitan.org/doc/ug/getting_started_fpga/index.html#testing-the-demo-design) -section in the OpenTitan documentation. +You can follow the steps at https://opentitan.org/book/doc/getting_started/index.html +for a guide to setup the OpenTitan repo. -``` -Bootstrap: DONE! -Boot ROM initialisation has completed, jump into flash! -OpenTitan initialisation complete. Entering main loop -``` +There are more details for setting up dependencies available at +https://opentitan.org/book/doc/getting_started/unofficial/index.html -You can also just use the `spiflash` program manually to download the image -to the board if you don't want to use `make flash`. +The quick steps for setting up the OpenTitan repo are ```shell -./sw/host/spiflash/spiflash --input=../../../target/riscv32imc-unknown-none-elf/release/opentitan.bin -``` +git clone https://github.com/lowRISC/opentitan.git +cd opentitan -NOTE: You will need to download the Tock binary after every power cycle. +# Use the OpenTitan_SHA currently supported by Tock +git checkout +pip3 install --user -r python-requirements.txt +``` ChipWhisper CW310 ----------------- @@ -81,8 +64,17 @@ ChipWhisper CW310 To use `make flash` you first need to clone the OpenTitan repo and ensure that the Python dependencies are installed. +Then you need to build the OpenTitan tools: + +```shell +./bazelisk.sh build //sw/host/opentitantool +``` + +You might need to run these commands to get it to work + ```shell -python3 pip install -r python-requirements.txt +ln -s /usr/bin/ld.lld /usr/sbin/ld.lld +ln -s /usr/bin/gcc /usr/sbin/gcc ``` Next connect to the board's serial with a second terminal: @@ -95,12 +87,12 @@ Then you need to flash the bitstream with: ```shell -./util/fpga/cw310_loader.py --bitstream lowrisc_systems_chip_earlgrey_cw310_0.1.bit --set-pll-defaults +./bazel-bin/sw/host/opentitantool/opentitantool.runfiles/lowrisc_opentitan/sw/host/opentitantool/opentitantool --interface=cw310 fpga load-bitstream lowrisc_systems_chip_earlgrey_cw310_0.1.bit.orig ``` After which you should see some output in the serial window. -Then in the Tock board directoty export the `OPENTITAN_TREE` enviroment +Then in the Tock board directory export the `OPENTITAN_TREE` environment variable to point to the OpenTitan tree. ```shell @@ -109,41 +101,159 @@ export OPENTITAN_TREE=/home/opentitan/ then you can run `make flash` or `make test-hardware` to use the board. -### Compiling the Kernel for FPGA or Verilator +Verilator +--------- Opentitan is supported on both an FPGA and in Verilator. Slightly different versions of the EarlGrey chip implementation are required for the different -platforms. By default the kernel is compiled for the FPGA. To compile for -Verilator, run: +platforms. By default the kernel is compiled for the FPGA. + +### Setting up Verilator + +For a full guide see the official [OpenTitan Verilator documentation](https://docs.opentitan.org/doc/ug/getting_started_verilator/) + +A quick summary on how to do this is included below though + +### Build Boot (test) Rom/OTP Image and FuseSOC + +Build **only the targets** we care about. To speed up the building, multi-thread the build process with `--jobs x` where x is the thread count. + +```shell +# To build the test-ROM +./bazelisk.sh build //sw/device/lib/testing/test_rom:test_rom + +# To build OTP +./bazelisk.sh build //hw/ip/otp_ctrl/... + +# To build FuseSOC +./bazelisk.sh build //hw:verilator +``` + +### Test Verilator + +You can use the following to automatically build the relevant targets and run a quick test with + +```shell +./bazelisk.sh test --test_output=streamed //sw/device/tests:uart_smoketest_sim_verilator +``` + +or manually with + +```shell +bazel-out/k8-fastbuild/bin/hw/build.verilator_real/sim-verilator/Vchip_sim_tb \ + --meminit=rom,./bazel-out/k8-fastbuild-ST-97f470ee3b14/bin/sw/device/lib/testing/test_rom/test_rom_sim_verilator.scr.39.vmem \ + --meminit=otp,./bazel-out/k8-fastbuild/bin/hw/ip/otp_ctrl/data/rma_image_verilator.vmem + +# Read the output, you want to attach screen to UART, for example +# "UART: Created /dev/pts/4 for uart0. Connect to it with any terminal program, " + +screen /dev/pts/4 + +# Wait a few minutes +# You should eventually see messages in screen +# Once you see "Test ROM complete, jumping to flash!" you know it works, note at this point we haven't provided flash image (so it ends here). +``` + +At this point Opentitan on Verilator should be ready to go! + +### Build and Run Tock + +You can also use the Tock Make target to automatically build Tock and run it with Verilator (within `boards/opentitan/earlgrey-cw310`) run: + +```shell +make BOARD_CONFIGURATION=sim_verilator verilator +``` +The above command should **compile relevant targets and start Verilator simulation**. + +However, to manually compile Tock for Verilator, run: ```shell make BOARD_CONFIGURATION=sim_verilator ``` -To explicitly specify the FPGA, run: +You will then need to generate a vmem file (must be at the TOP_DIR of tock to execute the following): ```shell -make BOARD_CONFIGURATION=fpga_nexysvideo +srec_cat \ + target/riscv32imc-unknown-none-elf/release/earlgrey-cw310.bin \ + --binary --offset 0 --byte-swap 8 --fill 0xff \ + -within target/riscv32imc-unknown-none-elf/release/earlgrey-cw310.bin\ + -binary -range-pad 8 --output binary.64.vmem --vmem 64 ``` +And Verilator can be run with: + +```shell +${OPENTITAN_TREE}/bazel-out/k8-fastbuild/bin/hw/build.verilator_real/sim-verilator/Vchip_sim_tb \ + --meminit=rom,${OPENTITAN_TREE}/bazel-out/k8-fastbuild-ST-97f470ee3b14/bin/sw/device/lib/testing/test_rom/test_rom_sim_verilator.scr.39.vmem \ + --meminit=flash,./binary.64.vmem \ + --meminit=otp,${OPENTITAN_TREE}/bazel-out/k8-fastbuild/bin/hw/ip/otp_ctrl/data/rma_image_verilator.vmem +``` + +In both cases expect Verilator to run for **tens of minutes** before you see anything. + Programming Apps ---------------- Tock apps for OpenTitan must be included in the Tock binary file flashed with the steps mentioned above. -Apps are built out of tree. +**Apps are built out of tree.** The OpenTitan Makefile can also handle this process automatically. Follow the steps above but instead run the `flash-app` make target. ```shell -$ make flash-app APP=<...> OPENTITAN_TREE=/home/opentitan/ +make flash-app APP=<...> OPENTITAN_TREE=/home/opentitan/ ``` -You will need to have the GCC version of RISC-V 32-bit objcopy installed as +You will need to have the GCC version of [RISC-V 32-bit objcopy](https://github.com/riscv-collab/riscv-gnu-toolchain/blob/master/README.md) installed as the LLVM one doesn't support updating sections. +### Programming Apps in Verilator + +A **single app** in `.tbf (tock binary format)` can be bundled and loaded with the kernel into Verilator with: + +```shell +make APP=<...> BOARD_CONFIGURATION=sim_verilator verilator +``` + +### Libtock-C App Verilator Example + +To load a libtock-c app, we can do the following to load the `c_hello` sample app: + +**Build app:** +```shell +git clone https://github.com/tock/libtock-c.git +cd libtock-c/examples/c_hello/ +make RISCV=1 +``` +**Load and Run:** + +Now, in the Opentitan board directory in tock (`tock/boards/opentitan/earlgrey-cw310`) + +```shell +make APP=/examples/c_hello/build/rv32imc/rv32imc.0x20030080.0x10005000.tbf BOARD_CONFIGURATION=sim_verilator verilator +``` + +Note: be sure to use the correct `tbf` file here, that is, +> use: `rv32imc.0x20030080.0x10005000.tbf` + +as this falls within the supported app flash region (0x20030080) and ram (0x10005000) regions for Opentitan. + +**App Output:** + +To see the output, when Verilator loads up it will show the endpoint for the +pseudoterminal slave, something like `UART: Created /dev/pts/7 for uart0`. + +```console +$ screen /dev/pts/7 +. +... +OpenTitan initialisation complete. Entering main loop +Hello World! +``` + Running in QEMU --------------- @@ -151,38 +261,84 @@ The OpenTitan application can be run in the QEMU emulation platform for RISC-V, allowing quick and easy testing. This is also a good option for those who can't afford the FPGA development board. -Unfortunately you need QEMU 6.1, which at the time of writing is unlikely -to be avaliable in your distro. Luckily Tock can build QEMU for you. From +Unfortunately you need QEMU 7.2, which at the time of writing is unlikely +to be available in your distro. Luckily Tock can build QEMU for you. From the top level of the Tock source just run `make ci-setup-qemu` and follow the steps. QEMU can be started with Tock using the `qemu` make target: ```shell -$ make OPENTITAN_BOOT_ROM=/sw/device/boot_rom/boot_rom_fpga_nexysvideo.elf qemu +make qemu ``` -Where OPENTITAN_BOOT_ROM is set to point to the OpenTitan ELF file. This is -usually located at `sw/device/boot_rom/boot_rom_fpga_nexysvideo.elf` in the -OpenTitan build output. Note that the `make ci-setup-qemu` target will also -download a ROM file. - QEMU can be started with Tock and a userspace app with the `qemu-app` make target: ```shell -$ make OPENTITAN_BOOT_ROM= APP=/path/to/app.tbf qemu-app +make APP=/path/to/app.tbf qemu-app ``` The TBF must be compiled for the OpenTitan board. For example, you can build the Hello World example app from the libtock-rs repository by running: +```shell +cd "$libtock_rs_dir" +make opentitan EXAMPLE=console +cd "${tock_dir}/boards/opentitan/earlgrey-cw310" +make APP=$"{libtock_rs_dir}/target/tbf/opentitan/console.tbf" qemu-app +``` + +QEMU GDB Debugging [**earlgrey-cw310**] +------------------ + +GDB can be used for debugging with QEMU. This can be useful when debugging a particular application/kernel. + +Start by installing the respective version of gdb. + +**Arch**: + +```shell +sudo pacman -S riscv32-elf-gdb +``` +**Ubuntu**: +```shell +sudo apt-get install gdb-multiarch +``` + +In the board directory, QEMU can be started in a suspended state with gdb ready to be connected. + +```shell +make qemu-gdb ``` -$ cd [LIBTOCK-RS-DIR] -$ make flash-opentitan -$ tar xf target/riscv32imac-unknown-none-elf/tab/opentitan/hello_world.tab -$ cd [TOCK_ROOT]/boards/opentitan -$ make APP=[LIBTOCK-RS-DIR]/rv32imac.tbf qemu-app + +or with an app ready to be loaded. + +```shell +make APP=/path/to/app.tbf qemu-app-gdb +``` + +In a separate shell, start gdb + +**Arch** + +```console +$ riscv32-elf-gdb [/path/to/tock.elf] +> target remote:1234 #1234 is the specified default port +``` + +**Ubuntu** + +```console +$ gdb-multiarch [/path/to/tock.elf] +> set arch riscv +> target remote:1234 #1234 is the specified default port +``` + +Once attached, standard gdb functionality is available. Additional debug symbols can be added with. +```console +add-symbol-file +add-symbol-file ``` Unit tests @@ -197,11 +353,35 @@ make test in the specific board directory. -To run the test on hardware use these commands to build the OTBN binary and run it on hardware: +To run the test on hardware use the following steps to build the OTBN binary and run it on hardware: + +**Note: You will need to have **Vivado 2020.2 Lab Edition** installed to be able to build `rsa.elf`. See here for an [installation guide](https://docs.opentitan.org/doc/getting_started/install_vivado/) from the OpenTitan docs. Once installed source the settings.sh file. Can be done with:** ```shell -elf2tab --verbose -n "otbn-rsa" --kernel-minor 0 --kernel-major 2 --app-heap 0 --kernel-heap 0 --stack 0 ${OPENTITAN_TREE}/build-out/sw/otbn/rsa.elf -OPENTITAN_TREE=<...> APP=${OPENTITAN_TREE}/build-out/sw/otbn/rsa.tbf make test-hardware +source /Xilinx/Vivado_Lab/2020.2/settings64.sh +``` +We can now build the `rsa.elf` with: +```shell +cd "${OPENTITAN_TREE}" +# Build OTBN Binary +./bazelisk.sh build //sw/device/tests:otbn_rsa_test + +# Package binary as a Tock app +elf2tab --verbose -n "otbn-rsa" --kernel-minor 0 --kernel-major 2 --disable --app-heap 0 --kernel-heap 0 --stack 0 ./bazel-out/k8-fastbuild-ST-2cc462681f62/bin/sw/otbn/crypto/rsa.elf + +# Run on hardware +cd "${tock_dir}/boards/opentitan/earlgrey-cw310" +make APP="${OPENTITAN_TREE}/bazel-out/sw/otbn/rsa.tbf" test-hardware +``` + +### For Verilator + +To load the OTBN binary and run it on Verilator, use: + +```shell +elf2tab --verbose -n "otbn-rsa" --kernel-minor 0 --kernel-major 2 --disable --app-heap 0 --kernel-heap 0 --stack 0 ./bazel-out/k8-fastbuild-ST-2cc462681f62/bin/sw/otbn/crypto/rsa.elf + +make APP="${OPENTITAN_TREE}/bazel-out/k8-fastbuild-ST-2cc462681f62/bin/sw/otbn/crypto/rsa.tbf" BOARD_CONFIGURATION=sim_verilator test-verilator ``` The output on a CW310 should look something like this: @@ -247,8 +427,8 @@ check otbn run binary... start TicKV append key test... ---Starting TicKV Tests--- Key: [18, 52, 86, 120, 154, 188, 222, 240] with value [16, 32, 48] was added -Now retriving the key -Key: [18, 52, 86, 120, 154, 188, 222, 240] with value [16, 32, 48, 0] was retrived +Now retrieving the key +Key: [18, 52, 86, 120, 154, 188, 222, 240] with value [16, 32, 48, 0] was retrieved Removed Key: [18, 52, 86, 120, 154, 188, 222, 240] Try to read removed key: [18, 52, 86, 120, 154, 188, 222, 240] Unable to find key: [18, 52, 86, 120, 154, 188, 222, 240] @@ -259,3 +439,11 @@ Finished garbage collection trivial assertion... [ok] ``` + +The tests can also be run on Verilator with: + +```shell +make BOARD_CONFIGURATION=sim_verilator test-verilator +``` + +Note that the Verilator tests can take hours to complete. diff --git a/boards/opentitan/earlgrey-cw310/.cargo/config b/boards/opentitan/earlgrey-cw310/.cargo/config deleted file mode 100644 index d2eca3121f..0000000000 --- a/boards/opentitan/earlgrey-cw310/.cargo/config +++ /dev/null @@ -1,2 +0,0 @@ -[target.'cfg(target_arch = "riscv32")'] -runner = "./run.sh" diff --git a/boards/opentitan/earlgrey-cw310/.cargo/config.toml b/boards/opentitan/earlgrey-cw310/.cargo/config.toml new file mode 100644 index 0000000000..7efa9d922a --- /dev/null +++ b/boards/opentitan/earlgrey-cw310/.cargo/config.toml @@ -0,0 +1,6 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[target.'cfg(target_arch = "riscv32")'] +runner = "./run.sh" diff --git a/boards/opentitan/earlgrey-cw310/Cargo.toml b/boards/opentitan/earlgrey-cw310/Cargo.toml index 2174f5e619..2e1d2bf6f4 100644 --- a/boards/opentitan/earlgrey-cw310/Cargo.toml +++ b/boards/opentitan/earlgrey-cw310/Cargo.toml @@ -1,20 +1,31 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "earlgrey-cw310" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../../build.rs" +edition.workspace = true [dependencies] components = { path = "../../components" } rv32i = { path = "../../../arch/rv32i" } -capsules = { path = "../../../capsules" } kernel = { path = "../../../kernel" } earlgrey = { path = "../../../chips/earlgrey" } lowrisc = { path = "../../../chips/lowrisc" } tock-tbf = { path = "../../../libraries/tock-tbf" } +capsules-core = { path = "../../../capsules/core" } +capsules-extra = { path = "../../../capsules/extra" } +capsules-aes-gcm = { path = "../../../capsules/aes_gcm" } +capsules-system = { path = "../../../capsules/system" } + + [features] +default = ["fpga_cw310"] + # OpenTitan SoC design can be synthesized or compiled for different targets. A # target can be a specific FPGA board, an ASIC technology, or a simulation tool. # Please see: https://docs.opentitan.org/doc/ug/getting_started/ for further @@ -23,12 +34,15 @@ tock-tbf = { path = "../../../libraries/tock-tbf" } # OpenTitan CPU and possibly other components must be configured appropriately # for a specific target: # - fpga_cw310: -# OpenTitan SoC design running on Nexys Video Artix-7 FPGA. +# OpenTitan SoC design running on CW310 FPGA. # # - sim_verilator: # OpenTitan SoC design simulated in Verilator. -fpga_cw310 = ["earlgrey/config_fpga_cw310"] -sim_verilator = ["earlgrey/config_sim_verilator"] +fpga_cw310 = [] +sim_verilator = [] # This is used to indicate that we should include tests that only pass on # hardware. hardware_tests = [] + +[lints] +workspace = true diff --git a/boards/opentitan/earlgrey-cw310/Makefile b/boards/opentitan/earlgrey-cw310/Makefile index 756dc34318..2a45f305b9 100644 --- a/boards/opentitan/earlgrey-cw310/Makefile +++ b/boards/opentitan/earlgrey-cw310/Makefile @@ -1,11 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the OpenTitan platform -DEFAULT_BOARD_CONFIGURATION=fpga_cw310 TARGET=riscv32imc-unknown-none-elf PLATFORM=earlgrey-cw310 FLASHID=--dev-id="0403:6010" -RISC_PREFIX ?= riscv64-elf +RISC_PREFIX ?= riscv64-linux-gnu QEMU ?= ../../../tools/qemu/build/qemu-system-riscv32 +# Override for the entry point +# This offset is calculated (from the linker file) as: +# ORIGIN(rom) + size_manifest = 0x20000000 + 0x400 +QEMU_ENTRY_POINT=0x20000400 +# OpenTitan commit edf5e35f5d50a5377641c90a315109a351de7635 is on the +# earlgrey_es branch. earlgrey_es is what is being taped out in the first +# Earlgrey chip. +OPENTITAN_SUPPORTED_SHA := edf5e35f5d50a5377641c90a315109a351de7635 +# Enable virtual function elimination +VFUNC_ELIM=1 include ../../Makefile.common @@ -13,30 +26,77 @@ include ../../Makefile.common # Pass OpenTitan board configuration option in `BOARD_CONFIGURATION` through # Cargo `--features`. Please see `Cargo.toml` for available options. ifneq ($(BOARD_CONFIGURATION),) - CARGO_FLAGS += --features=$(BOARD_CONFIGURATION) -else - CARGO_FLAGS += --features=$(DEFAULT_BOARD_CONFIGURATION) + CARGO_FLAGS += --no-default-features --features=$(BOARD_CONFIGURATION) endif +# Build test crates with the `panic = "abort"` strategy. Tests use `unwind` by +# default which causes incompatability with `core` built via `-Zbuild-std`. +CARGO_FLAGS += -Zpanic-abort-tests + +.PHONY: ot-check +ifneq ($(OPENTITAN_TREE),) + OPENTITAN_ACTUAL_SHA := $(shell cd $(OPENTITAN_TREE); git show --pretty=format:"%H" --no-patch) +endif +# TODO: Theres mix and match of tab/space indenting in the block below, +# should be changed to using `RECIPEPREFIX` make syntax when CI supports +# make version > 3.81 (macos) +ot-check: +ifeq ($(OPENTITAN_TREE),) + $(error "Please ensure that OPENTITAN_TREE is set") +endif +ifneq ($(OPENTITAN_ACTUAL_SHA), $(OPENTITAN_SUPPORTED_SHA)) + ifeq ($(SKIP_OT_VERSION_CHECK), yes) + $(warning Skipping trivial version check) + else + $(error Please ensure to build the correct version \ + of OpenTitan, latest supported is <$(OPENTITAN_SUPPORTED_SHA)>, \ + you are on <$(OPENTITAN_ACTUAL_SHA)>) + endif +endif + + # Default target for installing the kernel. .PHONY: install install: flash qemu: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf - $(call check_defined, OPENTITAN_BOOT_ROM) - $(QEMU) -M opentitan -kernel $^ -bios $(OPENTITAN_BOOT_ROM) -nographic -serial mon:stdio + $(QEMU) -M opentitan -kernel $^ -nographic -serial mon:stdio -global driver=riscv.lowrisc.ibex.soc,property=resetvec,value=${QEMU_ENTRY_POINT} + +qemu-gdb: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf + $(QEMU) -s -S -M opentitan -kernel $^ -nographic -serial mon:stdio -global driver=riscv.lowrisc.ibex.soc,property=resetvec,value=${QEMU_ENTRY_POINT} qemu-app: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf - $(call check_defined, OPENTITAN_BOOT_ROM) - $(QEMU) -M opentitan -kernel $^ -bios $(OPENTITAN_BOOT_ROM) -device loader,file=$(APP),addr=0x20030000 -nographic -serial mon:stdio + $(QEMU) -M opentitan -kernel $^ -device loader,file=$(APP),addr=0x20030000 -nographic -serial mon:stdio -global driver=riscv.lowrisc.ibex.soc,property=resetvec,value=${QEMU_ENTRY_POINT} + +qemu-app-gdb : $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf + $(QEMU) -s -S -M opentitan -kernel $^ -device loader,file=$(APP),addr=0x20030000 -nographic -serial mon:stdio -global driver=riscv.lowrisc.ibex.soc,property=resetvec,value=${QEMU_ENTRY_POINT} flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin - $(OPENTITAN_TREE)/util/fpga/cw310_loader.py --firmware $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin + $(OPENTITAN_TREE)/bazel-bin/sw/host/opentitantool/opentitantool.runfiles/lowrisc_opentitan/sw/host/opentitantool/opentitantool --interface=cw310 bootstrap $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin flash-app: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf $(RISC_PREFIX)-objcopy --update-section .apps=$(APP) $^ $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.elf $(RISC_PREFIX)-objcopy --output-target=binary $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.elf $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.bin - $(OPENTITAN_TREE)/util/fpga/cw310_loader.py --firmware $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.bin + $(OPENTITAN_TREE)/bazel-bin/sw/host/opentitantool/opentitantool.runfiles/lowrisc_opentitan/sw/host/opentitantool/opentitantool --interface=cw310 bootstrap $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.bin + +verilator: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf + $(call check_defined, OPENTITAN_TREE) +# Make a copy so we dont modify the original elf when linkng apps + $(RISC_PREFIX)-objcopy $^ $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)_verilator.elf +ifneq ($(APP),) + $(info [CW-130: Verilator]: Linking App) + $(RISC_PREFIX)-objcopy --update-section .apps=$(APP) $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)_verilator.elf $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)_verilator.elf +endif + $(info [CW-130: Verilator]: Starting) + $(RISC_PREFIX)-objcopy --output-target=binary $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)_verilator.elf $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)_verilator.bin + srec_cat $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)_verilator.bin \ + --binary --offset 0 --byte-swap 8 --fill 0xff \ + -within $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)_verilator.bin\ + -binary -range-pad 8 --output binary.64.vmem --vmem 64 + $(OPENTITAN_TREE)/bazel-out/k8-fastbuild/bin/hw/build.verilator_real/sim-verilator/Vchip_sim_tb \ + --meminit=rom,$(OPENTITAN_TREE)/bazel-out/k8-fastbuild-ST-2cc462681f62/bin/sw/device/lib/testing/test_rom/test_rom_sim_verilator.39.scr.vmem \ + --meminit=flash0,./binary.64.vmem \ + --meminit=otp,$(OPENTITAN_TREE)/bazel-out/k8-fastbuild/bin/hw/ip/otp_ctrl/data/img_rma.24.vmem test: ifneq ($(OPENTITAN_TREE),) @@ -45,13 +105,16 @@ endif mkdir -p $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/deps/ $(Q)cp test_layout.ld $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/deps/layout.ld $(Q)cp ../../kernel_layout.ld $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/ - $(Q)RUSTFLAGS="$(RUSTC_FLAGS_TOCK)" $(CARGO) test $(CARGO_FLAGS_TOCK_NO_BUILD_STD) $(NO_RUN) --bin $(PLATFORM) --release + $(Q)RUSTFLAGS="$(RUSTC_FLAGS_TOCK)" TOCK_ROOT_DIRECTORY=${TOCK_ROOT_DIRECTORY} QEMU_ENTRY_POINT=${QEMU_ENTRY_POINT} TARGET=${TARGET} $(CARGO) test $(CARGO_FLAGS_TOCK) $(NO_RUN) --bin $(PLATFORM) --release -test-hardware: -ifeq ($(OPENTITAN_TREE),) - $(error "Please ensure that OPENTITAN_TREE is set") -endif +test-hardware: ot-check + mkdir -p $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/deps/ + $(Q)cp test_layout.ld $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/deps/layout.ld + $(Q)cp ../../kernel_layout.ld $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/ + $(Q)RUSTFLAGS="$(RUSTC_FLAGS_TOCK)" OBJCOPY=$(RISC_PREFIX)-objcopy TOCK_ROOT_DIRECTORY=${TOCK_ROOT_DIRECTORY} TARGET=${TARGET} $(CARGO) test $(CARGO_FLAGS_TOCK) $(NO_RUN) --bin $(PLATFORM) --release --features=hardware_tests + +test-verilator: ot-check mkdir -p $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/deps/ $(Q)cp test_layout.ld $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/deps/layout.ld $(Q)cp ../../kernel_layout.ld $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/ - $(Q)RUSTFLAGS="$(RUSTC_FLAGS_TOCK)" $(CARGO) test $(CARGO_FLAGS_TOCK_NO_BUILD_STD) $(NO_RUN) --bin $(PLATFORM) --release --features=hardware_tests + $(Q)RUSTFLAGS="$(RUSTC_FLAGS_TOCK)" VERILATOR="yes" OBJCOPY=$(RISC_PREFIX)-objcopy TOCK_ROOT_DIRECTORY=${TOCK_ROOT_DIRECTORY} TARGET=${TARGET} $(CARGO) test $(CARGO_FLAGS_TOCK) $(NO_RUN) --bin $(PLATFORM) --release --features=hardware_tests,sim_verilator diff --git a/boards/opentitan/earlgrey-cw310/README.md b/boards/opentitan/earlgrey-cw310/README.md new file mode 100644 index 0000000000..8920275c43 --- /dev/null +++ b/boards/opentitan/earlgrey-cw310/README.md @@ -0,0 +1,7 @@ +OpenTitan on the ChipWhisper CW310 Board +====================== + +- https://opentitan.org/ +- https://docs.opentitan.org/doc/ug/fpga_boards/ + +See the main [OpenTitan board readme](../README.md) for more information. diff --git a/boards/opentitan/earlgrey-cw310/build.rs b/boards/opentitan/earlgrey-cw310/build.rs deleted file mode 100644 index 1fdd4924f0..0000000000 --- a/boards/opentitan/earlgrey-cw310/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../../kernel_layout.ld"); -} diff --git a/boards/opentitan/earlgrey-cw310/layout.ld b/boards/opentitan/earlgrey-cw310/layout.ld index 4359f89cea..868a66beae 100644 --- a/boards/opentitan/earlgrey-cw310/layout.ld +++ b/boards/opentitan/earlgrey-cw310/layout.ld @@ -1,3 +1,7 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + MEMORY { rom (rx) : ORIGIN = 0x20000000, LENGTH = 0x30000 @@ -5,21 +9,58 @@ MEMORY * and 0x2009_0000 to 0x2010_0000 is for flash storage. */ prog (rx) : ORIGIN = 0x20030000, LENGTH = 0x60000 - ram (!rx) : ORIGIN = 0x10000000, LENGTH = 0x10000 + fs (r) : ORIGIN = 0x20090000, LENGTH = 0x70000 + /* The first 0x650 bytes of RAM are reserved for the boot + * ROM, so we have to ignore that space. + * See https://github.com/lowRISC/opentitan/blob/master/sw/device/silicon_creator/lib/base/static_critical.ld + * for details + */ + ram (!rx) : ORIGIN = 0x10000650, LENGTH = 0x20000 - 0x650 } -MPU_MIN_ALIGN = 1K; SECTIONS { - /* - * The flash header needs to match what the boot ROM for OpenTitan is - * expecting. At the moment, it contains only the entry point, but it - * will eventually contain the signature -- and (hopefully?!) some - * versioning information to make it slightly easier to debug when the - * boot ROM and Tock are out of sync with respect to the definition... + /* Export the start & end of SRAM and flash as symbols for setting + * up the ePMP. Flash includes rom, prog and flash storage, such + * that we can use a single NAPOT region. The .text section will + * be made executable by a separate PMP region. */ - .flash_header : { - LONG(_stext) + _sflash = ORIGIN(rom); + _eflash = ORIGIN(fs) + LENGTH(fs); + + _ssram = ORIGIN(ram) - 0x650; + _esram = ORIGIN(ram) + LENGTH(ram); + + .manifest ORIGIN(rom): + { + _manifest = .; + /* see: sw/device/silicon_creator/lib/manifest.h */ + . += 384; /* rsa_signature */ + . += 4; /* usage_constraints.selector_bits */ + . += 32; /* usage_constraints.device_id */ + . += 4; /* usage_constraints.manuf_state_creator */ + . += 4; /* usage_constraints.manuf_state_owner */ + . += 4; /* usage_constraints.life_cycle_state */ + . += 384; /* rsa_modulus */ + . += 4; /* address_translation */ + . += 4; /* identifier */ + . += 4; /* manifest_version */ + . += 4; /* signed_region_end */ + . += 4; /* length */ + . += 4; /* version_major */ + . += 4; /* version_minor */ + . += 4; /* security_version */ + . += 8; /* timestamp */ + . += 32; /* binding_value */ + . += 4; /* max_key_version */ + . += 4; /* code_start */ + . += 4; /* code_end */ + LONG(_stext - ORIGIN(rom)); /* . = . + 4; entry_point */ + /* manifest extension table */ + /* see: sw/device/silicon_creator/lib/base/chip.h */ + /* CHIP_MANIFEST_EXT_TABLE_COUNT = 15 */ + /* sizeof(manifest_ext_table_entry) = 8 */ + . += 120; /* manifest_ext_table */ } > rom } - +ASSERT (((_etext - _manifest) > 0), "Error: PMP and Flash protection setup assumes _etext follows _manifest"); INCLUDE ../../kernel_layout.ld diff --git a/boards/opentitan/earlgrey-cw310/run.sh b/boards/opentitan/earlgrey-cw310/run.sh index a8ee25b0db..18e62fd976 100755 --- a/boards/opentitan/earlgrey-cw310/run.sh +++ b/boards/opentitan/earlgrey-cw310/run.sh @@ -1,10 +1,45 @@ #!/bin/bash -if [[ "${OPENTITAN_TREE}" != "" ]]; then - riscv64-linux-gnu-objcopy --update-section .apps=${APP} ${1} bundle.elf - riscv64-linux-gnu-objcopy --output-target=binary bundle.elf binary - ${OPENTITAN_TREE}/util/fpga/cw310_loader.py --firmware binary +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +BUILD_DIR="verilator_build/" + +# Preemptively cleanup layout (incase this was a test) so that following apps (non-tests) load the correct layout. +rm $TOCK_ROOT_DIRECTORY/target/$TARGET/release/deps/layout.ld + +if [[ "${VERILATOR}" == "yes" ]]; then + if [ -d "$BUILD_DIR" ]; then + # Cleanup before we build again + printf "\n[CW-130: Verilator Tests]: Cleaning up verilator_build...\n\n" + rm -R "$BUILD_DIR"/* + else + printf "\n[CW-130: Verilator Tests]: Setting up verilator_build...\n\n" + mkdir "$BUILD_DIR" + fi + # Copy in and covert from cargo test output to binary + ${OBJCOPY} ${1} "$BUILD_DIR"/earlgrey-cw310-tests.elf + if [[ "${APP}" != "" ]]; then + # An app was specified, copy it in + printf "[CW-130: Verilator Tests]: Linking APP\n\n" + ${OBJCOPY} --update-section .apps=${APP} "$BUILD_DIR"/earlgrey-cw310-tests.elf "$BUILD_DIR"/earlgrey-cw310-tests.elf + fi + ${OBJCOPY} --output-target=binary "$BUILD_DIR"/earlgrey-cw310-tests.elf "$BUILD_DIR"/earlgrey-cw310-tests.bin + # Create VMEM file from test binary + srec_cat "$BUILD_DIR"/earlgrey-cw310-tests.bin\ + --binary --offset 0 --byte-swap 8 --fill 0xff \ + -within "$BUILD_DIR"/earlgrey-cw310-tests.bin\ + -binary -range-pad 8 --output "$BUILD_DIR"/binary.64.vmem --vmem 64 + ${OPENTITAN_TREE}/bazel-bin/hw/build.verilator_real/sim-verilator/Vchip_sim_tb \ + --meminit=rom,${OPENTITAN_TREE}/bazel-out/k8-fastbuild-ST-2cc462681f62/bin/sw/device/lib/testing/test_rom/test_rom_sim_verilator.39.scr.vmem \ + --meminit=flash0,./"$BUILD_DIR"/binary.64.vmem \ + --meminit=otp,${OPENTITAN_TREE}/bazel-out/k8-fastbuild/bin/hw/ip/otp_ctrl/data/img_rma.24.vmem +elif [[ "${OPENTITAN_TREE}" != "" ]]; then + ${OBJCOPY} --update-section .apps=${APP} ${1} bundle.elf + ${OBJCOPY} --output-target=binary bundle.elf binary + + ${OPENTITAN_TREE}/bazel-bin/sw/host/opentitantool/opentitantool.runfiles/lowrisc_opentitan/sw/host/opentitantool/opentitantool --interface=cw310 bootstrap binary else - ../../../tools/qemu/build/qemu-system-riscv32 -M opentitan -bios ../../../tools/qemu-runner/opentitan-boot-rom.elf -nographic -serial stdio -monitor none -semihosting -kernel "${1}" + ../../../tools/qemu/build/qemu-system-riscv32 -M opentitan -nographic -serial stdio -monitor none -semihosting -kernel "${1}" -global driver=riscv.lowrisc.ibex.soc,property=resetvec,value=${QEMU_ENTRY_POINT} fi - diff --git a/boards/opentitan/earlgrey-cw310/test_layout.ld b/boards/opentitan/earlgrey-cw310/test_layout.ld index 0907bc2cc4..fbeb1739f6 100644 --- a/boards/opentitan/earlgrey-cw310/test_layout.ld +++ b/boards/opentitan/earlgrey-cw310/test_layout.ld @@ -1,3 +1,7 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + /* * This is used when building test binaries with `make test`. * It reduces the size for apps, as we don't use apps with the test @@ -11,21 +15,58 @@ MEMORY * and 0x2009_0000 to 0x2010_0000 is for flash storage. */ prog (rx) : ORIGIN = 0x20060000, LENGTH = 0x30000 - ram (!rx) : ORIGIN = 0x10000000, LENGTH = 0x10000 + fs (r) : ORIGIN = 0x20090000, LENGTH = 0x70000 + /* The first 0x650 bytes of RAM are reserved for the boot + * ROM, so we have to ignore that space. + * See https://github.com/lowRISC/opentitan/blob/master/sw/device/silicon_creator/lib/base/static_critical.ld + * for details + */ + ram (!rx) : ORIGIN = 0x10000650, LENGTH = 0x20000 - 0x650 } -MPU_MIN_ALIGN = 1K; SECTIONS { - /* - * The flash header needs to match what the boot ROM for OpenTitan is - * expecting. At the moment, it contains only the entry point, but it - * will eventually contain the signature -- and (hopefully?!) some - * versioning information to make it slightly easier to debug when the - * boot ROM and Tock are out of sync with respect to the definition... + /* Export the start & end of SRAM and flash as symbols for setting + * up the ePMP. Flash includes rom, prog and flash storage, such + * that we can use a single NAPOT region. The .text section will + * be made executable by a separate PMP region. */ - .flash_header : { - LONG(_stext) + _sflash = ORIGIN(rom); + _eflash = ORIGIN(fs) + LENGTH(fs); + + _ssram = ORIGIN(ram) - 0x650; + _esram = ORIGIN(ram) + LENGTH(ram); + + .manifest ORIGIN(rom): + { + _manifest = .; + /* see: sw/device/silicon_creator/lib/manifest.h */ + . += 384; /* rsa_signature */ + . += 4; /* usage_constraints.selector_bits */ + . += 32; /* usage_constraints.device_id */ + . += 4; /* usage_constraints.manuf_state_creator */ + . += 4; /* usage_constraints.manuf_state_owner */ + . += 4; /* usage_constraints.life_cycle_state */ + . += 384; /* rsa_modulus */ + . += 4; /* address_translation */ + . += 4; /* identifier */ + . += 4; /* manifest_version */ + . += 4; /* signed_region_end */ + . += 4; /* length */ + . += 4; /* version_major */ + . += 4; /* version_minor */ + . += 4; /* security_version */ + . += 8; /* timestamp */ + . += 32; /* binding_value */ + . += 4; /* max_key_version */ + . += 4; /* code_start */ + . += 4; /* code_end */ + LONG(_stext - ORIGIN(rom)); /* . = . + 4; entry_point */ + /* manifest extension table */ + /* see: sw/device/silicon_creator/lib/base/chip.h */ + /* CHIP_MANIFEST_EXT_TABLE_COUNT = 15 */ + /* sizeof(manifest_ext_table_entry) = 8 */ + . += 120; /* manifest_ext_table */ } > rom } - +ASSERT (((_etext - _manifest) > 0), "Error: PMP and Flash protection setup assumes _etext follows _manifest"); INCLUDE ../../kernel_layout.ld diff --git a/boards/opentitan/earlgrey-nexysvideo/.cargo/config b/boards/opentitan/earlgrey-nexysvideo/.cargo/config deleted file mode 100644 index d2eca3121f..0000000000 --- a/boards/opentitan/earlgrey-nexysvideo/.cargo/config +++ /dev/null @@ -1,2 +0,0 @@ -[target.'cfg(target_arch = "riscv32")'] -runner = "./run.sh" diff --git a/boards/opentitan/earlgrey-nexysvideo/Cargo.toml b/boards/opentitan/earlgrey-nexysvideo/Cargo.toml deleted file mode 100644 index 17d2fd711a..0000000000 --- a/boards/opentitan/earlgrey-nexysvideo/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "earlgrey-nexysvideo" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" - -[dependencies] -components = { path = "../../components" } -rv32i = { path = "../../../arch/rv32i" } -capsules = { path = "../../../capsules" } -kernel = { path = "../../../kernel" } -earlgrey = { path = "../../../chips/earlgrey" } -lowrisc = { path = "../../../chips/lowrisc" } -tock-tbf = { path = "../../../libraries/tock-tbf" } - -[features] -# OpenTitan SoC design can be synthesized or compiled for different targets. A -# target can be a specific FPGA board, an ASIC technology, or a simulation tool. -# Please see: https://docs.opentitan.org/doc/ug/getting_started/ for further -# information. -# -# OpenTitan CPU and possibly other components must be configured appropriately -# for a specific target: -# - fpga_nexysvideo: -# OpenTitan SoC design running on Nexys Video Artix-7 FPGA. -# -# - sim_verilator: -# OpenTitan SoC design simulated in Verilator. -fpga_nexysvideo = ["earlgrey/config_fpga_nexysvideo"] -sim_verilator = ["earlgrey/config_sim_verilator"] -# This is used to indicate that we should include tests that only pass on -# hardware. -hardware_tests = [] diff --git a/boards/opentitan/earlgrey-nexysvideo/Makefile b/boards/opentitan/earlgrey-nexysvideo/Makefile deleted file mode 100644 index 3e3054b4bf..0000000000 --- a/boards/opentitan/earlgrey-nexysvideo/Makefile +++ /dev/null @@ -1,57 +0,0 @@ -# Makefile for building the tock kernel for the OpenTitan platform - -DEFAULT_BOARD_CONFIGURATION=fpga_nexysvideo -TARGET=riscv32imc-unknown-none-elf -PLATFORM=earlgrey-nexysvideo -FLASHID=--dev-id="0403:6010" -RISC_PREFIX ?= riscv64-elf -QEMU ?= ../../../tools/qemu/build/qemu-system-riscv32 - - -include ../../Makefile.common - -# Pass OpenTitan board configuration option in `BOARD_CONFIGURATION` through -# Cargo `--features`. Please see `Cargo.toml` for available options. -ifneq ($(BOARD_CONFIGURATION),) - CARGO_FLAGS += --features=$(BOARD_CONFIGURATION) -else - CARGO_FLAGS += --features=$(DEFAULT_BOARD_CONFIGURATION) -endif - -# Default target for installing the kernel. -.PHONY: install -install: flash - -qemu: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf - $(call check_defined, OPENTITAN_BOOT_ROM) - $(QEMU) -M opentitan -kernel $^ -bios $(OPENTITAN_BOOT_ROM) -nographic -serial mon:stdio - -qemu-app: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf - $(call check_defined, OPENTITAN_BOOT_ROM) - $(QEMU) -M opentitan -kernel $^ -bios $(OPENTITAN_BOOT_ROM) -device loader,file=$(APP),addr=0x20030000 -nographic -serial mon:stdio - -flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin - $(OPENTITAN_TREE)/build-out/sw/host/spiflash/spiflash $(FLASHID) --input=$(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin - -flash-app: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf - $(RISC_PREFIX)-objcopy --update-section .apps=$(APP) $^ $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.elf - $(RISC_PREFIX)-objcopy --output-target=binary $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.elf $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.bin - $(OPENTITAN_TREE)/build-out/sw/host/spiflash/spiflash $(FLASHID) --input=$(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.bin - -test: -ifneq ($(OPENTITAN_TREE),) - $(error "Running on QEMU, use test-hardware to run on hardware") -endif - mkdir -p $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/deps/ - $(Q)cp test_layout.ld $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/deps/layout.ld - $(Q)cp ../../kernel_layout.ld $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/ - $(Q)RUSTFLAGS="$(RUSTC_FLAGS_TOCK)" $(CARGO) test $(CARGO_FLAGS_TOCK_NO_BUILD_STD) $(NO_RUN) --bin $(PLATFORM) --release - -test-hardware: -ifeq ($(OPENTITAN_TREE),) - $(error "Please ensure that OPENTITAN_TREE is set") -endif - mkdir -p $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/deps/ - $(Q)cp test_layout.ld $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/deps/layout.ld - $(Q)cp ../../kernel_layout.ld $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/ - $(Q)RUSTFLAGS="$(RUSTC_FLAGS_TOCK)" $(CARGO) test $(CARGO_FLAGS_TOCK_NO_BUILD_STD) $(NO_RUN) --bin $(PLATFORM) --release --features=hardware_tests diff --git a/boards/opentitan/earlgrey-nexysvideo/build.rs b/boards/opentitan/earlgrey-nexysvideo/build.rs deleted file mode 100644 index 1fdd4924f0..0000000000 --- a/boards/opentitan/earlgrey-nexysvideo/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../../kernel_layout.ld"); -} diff --git a/boards/opentitan/earlgrey-nexysvideo/layout.ld b/boards/opentitan/earlgrey-nexysvideo/layout.ld deleted file mode 100644 index a22487e2cb..0000000000 --- a/boards/opentitan/earlgrey-nexysvideo/layout.ld +++ /dev/null @@ -1,26 +0,0 @@ -MEMORY -{ - rom (rx) : ORIGIN = 0x20000000, LENGTH = 0x30000 - /* The Nexys video has half the amount of flash. - * So we only support up to 0x2006_0000 for apps - * and 0x2006_0000 to 0x2008_0000 is for flash storage. - */ - prog (rx) : ORIGIN = 0x20030000, LENGTH = 0x30000 - ram (!rx) : ORIGIN = 0x10000000, LENGTH = 0x10000 -} - -MPU_MIN_ALIGN = 1K; -SECTIONS { - /* - * The flash header needs to match what the boot ROM for OpenTitan is - * expecting. At the moment, it contains only the entry point, but it - * will eventually contain the signature -- and (hopefully?!) some - * versioning information to make it slightly easier to debug when the - * boot ROM and Tock are out of sync with respect to the definition... - */ - .flash_header : { - LONG(_stext) - } > rom -} - -INCLUDE ../../kernel_layout.ld diff --git a/boards/opentitan/earlgrey-nexysvideo/run.sh b/boards/opentitan/earlgrey-nexysvideo/run.sh deleted file mode 100755 index 8f69d0aae3..0000000000 --- a/boards/opentitan/earlgrey-nexysvideo/run.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/bin/bash - -if [[ "${OPENTITAN_TREE}" != "" ]]; then - riscv64-linux-gnu-objcopy --output-target=binary ${1} binary - ${OPENTITAN_TREE}/build-out/sw/host/spiflash/spiflash --dev-id=0403:6010 --input=binary -else - ../../../tools/qemu/build/qemu-system-riscv32 -M opentitan -bios ../../../tools/qemu-runner/opentitan-boot-rom.elf -nographic -serial stdio -monitor none -semihosting -kernel "${1}" -fi diff --git a/boards/opentitan/earlgrey-nexysvideo/src b/boards/opentitan/earlgrey-nexysvideo/src deleted file mode 120000 index e057607ed0..0000000000 --- a/boards/opentitan/earlgrey-nexysvideo/src +++ /dev/null @@ -1 +0,0 @@ -../src/ \ No newline at end of file diff --git a/boards/opentitan/earlgrey-nexysvideo/test_layout.ld b/boards/opentitan/earlgrey-nexysvideo/test_layout.ld deleted file mode 100644 index 993dd421ab..0000000000 --- a/boards/opentitan/earlgrey-nexysvideo/test_layout.ld +++ /dev/null @@ -1,32 +0,0 @@ -/* - * This is used when building test binaries with `make test`. - * It reduces the size for apps, as we don't use apps with the test - * binaries. - */ - -MEMORY -{ - rom (rx) : ORIGIN = 0x20000000, LENGTH = 0x50000 - /* The Nexys video has half the amount of flash. - * So we only support up to 0x2006_0000 for apps - * and 0x2006_0000 to 0x2008_0000 is for flash storage. - */ - prog (rx) : ORIGIN = 0x20050000, LENGTH = 0x10000 - ram (!rx) : ORIGIN = 0x10000000, LENGTH = 0x10000 -} - -MPU_MIN_ALIGN = 1K; -SECTIONS { - /* - * The flash header needs to match what the boot ROM for OpenTitan is - * expecting. At the moment, it contains only the entry point, but it - * will eventually contain the signature -- and (hopefully?!) some - * versioning information to make it slightly easier to debug when the - * boot ROM and Tock are out of sync with respect to the definition... - */ - .flash_header : { - LONG(_stext) - } > rom -} - -INCLUDE ../../kernel_layout.ld diff --git a/boards/opentitan/src/io.rs b/boards/opentitan/src/io.rs index cb7e0e0c0a..a109edd5cc 100644 --- a/boards/opentitan/src/io.rs +++ b/boards/opentitan/src/io.rs @@ -1,6 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; use core::str; +use earlgrey::chip_config::EarlGreyConfig; use kernel::debug; use kernel::debug::IoWrite; @@ -20,14 +25,15 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { // This creates a second instance of the UART peripheral, and should only be used // during panic. earlgrey::uart::Uart::new( earlgrey::uart::UART0_BASE, - earlgrey::chip_config::CONFIG.peripheral_freq, + crate::ChipConfig::PERIPHERAL_FREQ, ) .transmit_sync(buf); + buf.len() } } @@ -40,34 +46,52 @@ use kernel::hil::led; #[cfg(not(test))] #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { + use core::ptr::{addr_of, addr_of_mut}; let first_led_pin = &mut earlgrey::gpio::GpioPin::new( - earlgrey::gpio::GPIO0_BASE, - earlgrey::gpio::PADCTRL_BASE, + earlgrey::gpio::GPIO_BASE, + earlgrey::pinmux::PadConfig::Output( + earlgrey::registers::top_earlgrey::MuxedPads::Ioa6, + earlgrey::registers::top_earlgrey::PinmuxOutsel::GpioGpio7, + ), earlgrey::gpio::pins::pin7, ); first_led_pin.make_output(); let first_led = &mut led::LedLow::new(first_led_pin); + let writer = &mut *addr_of_mut!(WRITER); - let writer = &mut WRITER; + #[cfg(feature = "sim_verilator")] + debug::panic( + &mut [first_led], + writer, + pi, + &|| {}, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), + ); + #[cfg(not(feature = "sim_verilator"))] debug::panic( &mut [first_led], writer, pi, &rv32i::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, - ) + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), + ); } #[cfg(test)] #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { let writer = &mut WRITER; + #[cfg(feature = "sim_verilator")] + debug::panic_print(writer, pi, &|| {}, &PROCESSES, &CHIP, &PROCESS_PRINTER); + #[cfg(not(feature = "sim_verilator"))] debug::panic_print( writer, pi, diff --git a/boards/opentitan/src/main.rs b/boards/opentitan/src/main.rs index 0b0d698559..505d396323 100644 --- a/boards/opentitan/src/main.rs +++ b/boards/opentitan/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Board file for LowRISC OpenTitan RISC-V development platform. //! //! - @@ -11,36 +15,92 @@ #![reexport_test_harness_main = "test_main"] use crate::hil::symmetric_encryption::AES128_BLOCK_SIZE; -use capsules::virtual_aes_ccm; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; -use capsules::virtual_hmac::VirtualMuxHmac; -use capsules::virtual_sha::VirtualMuxSha; +use crate::otbn::OtbnComponent; +use crate::pinmux_layout::BoardPinmuxLayout; +use capsules_aes_gcm::aes_gcm; +use capsules_core::virtualizers::virtual_aes_ccm; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use core::ptr::{addr_of, addr_of_mut}; use earlgrey::chip::EarlGreyDefaultPeripherals; +use earlgrey::chip_config::EarlGreyConfig; +use earlgrey::pinmux_config::EarlGreyPinmuxConfig; use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil; -use kernel::hil::digest::Digest; use kernel::hil::entropy::Entropy32; +use kernel::hil::hasher::Hasher; use kernel::hil::i2c::I2CMaster; use kernel::hil::led::LedHigh; use kernel::hil::rng::Rng; use kernel::hil::symmetric_encryption::AES128; -use kernel::platform::mpu; -use kernel::platform::mpu::KernelMPU; use kernel::platform::scheduler_timer::VirtualSchedulerTimer; use kernel::platform::{KernelResources, SyscallDriverLookup, TbfHeaderFilterDefaultAllow}; use kernel::scheduler::priority::PrioritySched; use kernel::utilities::registers::interfaces::ReadWriteable; use kernel::{create_capability, debug, static_init}; +use lowrisc::flash_ctrl::FlashMPConfig; use rv32i::csr; +pub mod io; +mod otbn; +pub mod pinmux_layout; #[cfg(test)] mod tests; -pub mod io; -mod otbn; -pub mod usb; +/// The `earlgrey` chip crate supports multiple targets with slightly different +/// configurations, which are encoded through implementations of the +/// `earlgrey::chip_config::EarlGreyConfig` trait. This type provides different +/// implementations of the `EarlGreyConfig` trait, depending on Cargo's +/// conditional compilation feature flags. If no feature is selected, +/// compilation will error. +pub enum ChipConfig {} + +#[cfg(feature = "fpga_cw310")] +impl EarlGreyConfig for ChipConfig { + const NAME: &'static str = "fpga_cw310"; + + // Clock frequencies as of https://github.com/lowRISC/opentitan/pull/19479 + const CPU_FREQ: u32 = 24_000_000; + const PERIPHERAL_FREQ: u32 = 6_000_000; + const AON_TIMER_FREQ: u32 = 250_000; + const UART_BAUDRATE: u32 = 115200; +} + +#[cfg(feature = "sim_verilator")] +impl EarlGreyConfig for ChipConfig { + const NAME: &'static str = "sim_verilator"; + + // Clock frequencies as of https://github.com/lowRISC/opentitan/pull/19368 + const CPU_FREQ: u32 = 500_000; + const PERIPHERAL_FREQ: u32 = 125_000; + const AON_TIMER_FREQ: u32 = 125_000; + const UART_BAUDRATE: u32 = 7200; +} + +// Whether to check for a proper ePMP handover configuration prior to ePMP +// initialization: +pub const EPMP_HANDOVER_CONFIG_CHECK: bool = false; + +// EarlGrey ePMP debug mode +// +// This type determines whether JTAG access shall be enabled. When JTAG access +// is enabled, one less MPU region is available for use by userspace. +// +// Either +// - `earlgrey::epmp::EPMPDebugEnable`, or +// - `earlgrey::epmp::EPMPDebugDisable`. +pub type EPMPDebugConfig = earlgrey::epmp::EPMPDebugEnable; + +// EarlGrey Chip type signature, including generic PMP argument and peripherals +// type: +pub type EarlGreyChip = earlgrey::chip::EarlGrey< + 'static, + { ::TOR_USER_REGIONS }, + EarlGreyDefaultPeripherals<'static, ChipConfig, BoardPinmuxLayout>, + ChipConfig, + BoardPinmuxLayout, + earlgrey::epmp::EarlGreyEPMP<{ EPMP_HANDOVER_CONFIG_CHECK }, EPMPDebugConfig>, +>; const NUM_PROCS: usize = 4; @@ -51,127 +111,173 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; 4] = [None // Test access to the peripherals #[cfg(test)] -static mut PERIPHERALS: Option<&'static EarlGreyDefaultPeripherals> = None; +static mut PERIPHERALS: Option<&'static EarlGreyDefaultPeripherals> = + None; // Test access to board #[cfg(test)] static mut BOARD: Option<&'static kernel::Kernel> = None; // Test access to platform #[cfg(test)] -static mut PLATFORM: Option<&'static EarlGreyNexysVideo> = None; +static mut PLATFORM: Option<&'static EarlGrey> = None; // Test access to main loop capability #[cfg(test)] static mut MAIN_CAP: Option<&dyn kernel::capabilities::MainLoopCapability> = None; // Test access to alarm -static mut ALARM: Option<&'static MuxAlarm<'static, earlgrey::timer::RvTimer<'static>>> = None; +static mut ALARM: Option< + &'static MuxAlarm<'static, earlgrey::timer::RvTimer<'static, ChipConfig>>, +> = None; // Test access to TicKV static mut TICKV: Option< - &capsules::tickv::TicKVStore< + &capsules_extra::tickv::TicKVSystem< 'static, - capsules::virtual_flash::FlashUser<'static, lowrisc::flash_ctrl::FlashCtrl<'static>>, + capsules_core::virtualizers::virtual_flash::FlashUser< + 'static, + lowrisc::flash_ctrl::FlashCtrl<'static>, + >, + capsules_extra::sip_hash::SipHasher24<'static>, + 2048, >, > = None; -// Test access to AES CCM -static mut AES: Option<&virtual_aes_ccm::VirtualAES128CCM<'static, earlgrey::aes::Aes<'static>>> = - None; +// Test access to AES +static mut AES: Option< + &aes_gcm::Aes128Gcm< + 'static, + virtual_aes_ccm::VirtualAES128CCM<'static, earlgrey::aes::Aes<'static>>, + >, +> = None; +// Test access to SipHash +static mut SIPHASH: Option<&capsules_extra::sip_hash::SipHasher24<'static>> = None; +// Test access to RSA +static mut RSA_HARDWARE: Option<&lowrisc::rsa::OtbnRsa<'static>> = None; -static mut CHIP: Option<&'static earlgrey::chip::EarlGrey> = None; -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +// Test access to a software SHA256 +#[cfg(test)] +static mut SHA256SOFT: Option<&capsules_extra::sha256::Sha256Software<'static>> = None; + +static mut CHIP: Option<&'static EarlGreyChip> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; /// Dummy buffer that causes the linker to reserve enough space for the stack. #[no_mangle] #[link_section = ".stack_buffer"] -pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; +pub static mut STACK_MEMORY: [u8; 0x1400] = [0; 0x1400]; /// A structure representing this platform that holds references to all /// capsules for this platform. We've included an alarm and console. -struct EarlGreyNexysVideo { - led: &'static capsules::led::LedDriver< +struct EarlGrey { + led: &'static capsules_core::led::LedDriver< 'static, - LedHigh<'static, earlgrey::gpio::GpioPin<'static>>, + LedHigh<'static, earlgrey::gpio::GpioPin<'static, earlgrey::pinmux::PadConfig>>, 8, >, - gpio: &'static capsules::gpio::GPIO<'static, earlgrey::gpio::GpioPin<'static>>, - console: &'static capsules::console::Console<'static>, - alarm: &'static capsules::alarm::AlarmDriver< + gpio: &'static capsules_core::gpio::GPIO< 'static, - VirtualMuxAlarm<'static, earlgrey::timer::RvTimer<'static>>, + earlgrey::gpio::GpioPin<'static, earlgrey::pinmux::PadConfig>, >, - hmac: &'static capsules::hmac::HmacDriver< + console: &'static capsules_core::console::Console<'static>, + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, - VirtualMuxHmac< - 'static, - capsules::virtual_digest::VirtualMuxDigest<'static, lowrisc::hmac::Hmac<'static>, 32>, - 32, - >, - 32, + VirtualMuxAlarm<'static, earlgrey::timer::RvTimer<'static, ChipConfig>>, + >, + hmac: &'static capsules_extra::hmac::HmacDriver<'static, lowrisc::hmac::Hmac<'static>, 32>, + lldb: &'static capsules_core::low_level_debug::LowLevelDebug< + 'static, + capsules_core::virtualizers::virtual_uart::UartDevice<'static>, >, - sha: &'static capsules::sha::ShaDriver< + i2c_master: + &'static capsules_core::i2c_master::I2CMasterDriver<'static, lowrisc::i2c::I2c<'static>>, + spi_controller: &'static capsules_core::spi_controller::Spi< 'static, - VirtualMuxSha< + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice< 'static, - capsules::virtual_digest::VirtualMuxDigest<'static, lowrisc::hmac::Hmac<'static>, 32>, - 32, + lowrisc::spi_host::SpiHost<'static>, >, - 32, >, - lldb: &'static capsules::low_level_debug::LowLevelDebug< + rng: &'static capsules_core::rng::RngDriver< 'static, - capsules::virtual_uart::UartDevice<'static>, + capsules_core::rng::Entropy32ToRandom<'static, lowrisc::csrng::CsRng<'static>>, >, - i2c_master: &'static capsules::i2c_master::I2CMasterDriver<'static, lowrisc::i2c::I2c<'static>>, - rng: &'static capsules::rng::RngDriver<'static>, - aes: &'static capsules::symmetric_encryption::aes::AesDriver< + aes: &'static capsules_extra::symmetric_encryption::aes::AesDriver< 'static, - virtual_aes_ccm::VirtualAES128CCM<'static, earlgrey::aes::Aes<'static>>, + aes_gcm::Aes128Gcm< + 'static, + virtual_aes_ccm::VirtualAES128CCM<'static, earlgrey::aes::Aes<'static>>, + >, + >, + kv_driver: &'static capsules_extra::kv_driver::KVStoreDriver< + 'static, + capsules_extra::virtual_kv::VirtualKVPermissions< + 'static, + capsules_extra::kv_store_permissions::KVStorePermissions< + 'static, + capsules_extra::tickv_kv_store::TicKVKVStore< + 'static, + capsules_extra::tickv::TicKVSystem< + 'static, + capsules_core::virtualizers::virtual_flash::FlashUser< + 'static, + lowrisc::flash_ctrl::FlashCtrl<'static>, + >, + capsules_extra::sip_hash::SipHasher24<'static>, + 2048, + >, + [u8; 8], + >, + >, + >, >, syscall_filter: &'static TbfHeaderFilterDefaultAllow, scheduler: &'static PrioritySched, - scheduler_timer: - &'static VirtualSchedulerTimer>>, + scheduler_timer: &'static VirtualSchedulerTimer< + VirtualMuxAlarm<'static, earlgrey::timer::RvTimer<'static, ChipConfig>>, + >, + watchdog: &'static lowrisc::aon_timer::AonTimer, } /// Mapping of integer syscalls to objects that implement syscalls. -impl SyscallDriverLookup for EarlGreyNexysVideo { +impl SyscallDriverLookup for EarlGrey { fn with_driver(&self, driver_num: usize, f: F) -> R where F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::hmac::DRIVER_NUM => f(Some(self.hmac)), - capsules::sha::DRIVER_NUM => f(Some(self.sha)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::low_level_debug::DRIVER_NUM => f(Some(self.lldb)), - capsules::i2c_master::DRIVER_NUM => f(Some(self.i2c_master)), - capsules::rng::DRIVER_NUM => f(Some(self.rng)), - capsules::symmetric_encryption::aes::DRIVER_NUM => f(Some(self.aes)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_extra::hmac::DRIVER_NUM => f(Some(self.hmac)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::low_level_debug::DRIVER_NUM => f(Some(self.lldb)), + capsules_core::i2c_master::DRIVER_NUM => f(Some(self.i2c_master)), + capsules_core::spi_controller::DRIVER_NUM => f(Some(self.spi_controller)), + capsules_core::rng::DRIVER_NUM => f(Some(self.rng)), + capsules_extra::symmetric_encryption::aes::DRIVER_NUM => f(Some(self.aes)), + capsules_extra::kv_driver::DRIVER_NUM => f(Some(self.kv_driver)), _ => f(None), } } } -impl KernelResources>> - for EarlGreyNexysVideo -{ +impl KernelResources for EarlGrey { type SyscallDriverLookup = Self; type SyscallFilter = TbfHeaderFilterDefaultAllow; type ProcessFault = (); type Scheduler = PrioritySched; - type SchedulerTimer = - VirtualSchedulerTimer>>; - type WatchDog = (); + type SchedulerTimer = VirtualSchedulerTimer< + VirtualMuxAlarm<'static, earlgrey::timer::RvTimer<'static, ChipConfig>>, + >; + type WatchDog = lowrisc::aon_timer::AonTimer; type ContextSwitchCallback = (); fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { - &self.syscall_filter + self.syscall_filter } fn process_fault(&self) -> &Self::ProcessFault { &() @@ -180,10 +286,10 @@ impl KernelResources &Self::SchedulerTimer { - &self.scheduler_timer + self.scheduler_timer } fn watchdog(&self) -> &Self::WatchDog { - &() + self.watchdog } fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { &() @@ -192,31 +298,99 @@ impl KernelResources ( &'static kernel::Kernel, - &'static EarlGreyNexysVideo, - &'static earlgrey::chip::EarlGrey<'static, EarlGreyDefaultPeripherals<'static>>, - &'static EarlGreyDefaultPeripherals<'static>, + &'static EarlGrey, + &'static EarlGreyChip, + &'static EarlGreyDefaultPeripherals<'static, ChipConfig, BoardPinmuxLayout>, ) { + // These symbols are defined in the linker script. + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + /// The start of the kernel text (Included only for kernel PMP) + static _stext: u8; + /// The end of the kernel text (Included only for kernel PMP) + static _etext: u8; + /// The start of the kernel / app / storage flash (Included only for kernel PMP) + static _sflash: u8; + /// The end of the kernel / app / storage flash (Included only for kernel PMP) + static _eflash: u8; + /// The start of the kernel / app RAM (Included only for kernel PMP) + static _ssram: u8; + /// The end of the kernel / app RAM (Included only for kernel PMP) + static _esram: u8; + /// The start of the OpenTitan manifest + static _manifest: u8; + } + // Ibex-specific handler earlgrey::chip::configure_trap_handler(); + // Set up memory protection immediately after setting the trap handler, to + // ensure that much of the board initialization routine runs with ePMP + // protection. + let earlgrey_epmp = earlgrey::epmp::EarlGreyEPMP::new_debug( + earlgrey::epmp::FlashRegion( + rv32i::pmp::NAPOTRegionSpec::new( + core::ptr::addr_of!(_sflash), + core::ptr::addr_of!(_eflash) as usize - core::ptr::addr_of!(_sflash) as usize, + ) + .unwrap(), + ), + earlgrey::epmp::RAMRegion( + rv32i::pmp::NAPOTRegionSpec::new( + core::ptr::addr_of!(_ssram), + core::ptr::addr_of!(_esram) as usize - core::ptr::addr_of!(_ssram) as usize, + ) + .unwrap(), + ), + earlgrey::epmp::MMIORegion( + rv32i::pmp::NAPOTRegionSpec::new( + 0x40000000 as *const u8, // start + 0x10000000, // size + ) + .unwrap(), + ), + earlgrey::epmp::KernelTextRegion( + rv32i::pmp::TORRegionSpec::new( + core::ptr::addr_of!(_stext), + core::ptr::addr_of!(_etext), + ) + .unwrap(), + ), + // RV Debug Manager memory region (required for JTAG debugging). + // This access can be disabled by changing the EarlGreyEPMP type + // parameter `EPMPDebugConfig` to `EPMPDebugDisable`, in which case + // this expects to be passed a unit (`()`) type. + earlgrey::epmp::RVDMRegion( + rv32i::pmp::NAPOTRegionSpec::new( + 0x00010000 as *const u8, // start + 0x00001000, // size + ) + .unwrap(), + ), + ) + .unwrap(); + + // Configure board layout in pinmux + BoardPinmuxLayout::setup(); + // initialize capabilities let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability); let memory_allocation_cap = create_capability!(capabilities::MemoryAllocationCapability); - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); - - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 3], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); let peripherals = static_init!( - EarlGreyDefaultPeripherals, - EarlGreyDefaultPeripherals::new(dynamic_deferred_caller) + EarlGreyDefaultPeripherals, + EarlGreyDefaultPeripherals::new() ); + peripherals.init(); // Configure kernel debug gpios as early as possible kernel::debug::assign_gpios( @@ -226,17 +400,14 @@ unsafe fn setup() -> ( ); // Create a shared UART channel for the console and for kernel debug. - let uart_mux = components::console::UartMuxComponent::new( - &peripherals.uart0, - earlgrey::uart::UART0_BAUDRATE, - dynamic_deferred_caller, - ) - .finalize(()); + let uart_mux = + components::console::UartMuxComponent::new(&peripherals.uart0, ChipConfig::UART_BAUDRATE) + .finalize(components::uart_mux_component_static!()); // LEDs // Start with half on and half off - let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( - LedHigh<'static, earlgrey::gpio::GpioPin>, + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( + LedHigh<'static, earlgrey::gpio::GpioPin>, LedHigh::new(&peripherals.gpio_port[8]), LedHigh::new(&peripherals.gpio_port[9]), LedHigh::new(&peripherals.gpio_port[10]), @@ -249,9 +420,9 @@ unsafe fn setup() -> ( let gpio = components::gpio::GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( - earlgrey::gpio::GpioPin, + earlgrey::gpio::GpioPin, 0 => &peripherals.gpio_port[0], 1 => &peripherals.gpio_port[1], 2 => &peripherals.gpio_port[2], @@ -262,15 +433,20 @@ unsafe fn setup() -> ( 7 => &peripherals.gpio_port[15] ), ) - .finalize(components::gpio_component_buf!(earlgrey::gpio::GpioPin)); + .finalize(components::gpio_component_static!( + earlgrey::gpio::GpioPin + )); - let hardware_alarm = static_init!(earlgrey::timer::RvTimer, earlgrey::timer::RvTimer::new()); + let hardware_alarm = static_init!( + earlgrey::timer::RvTimer, + earlgrey::timer::RvTimer::new() + ); hardware_alarm.setup(); // Create a shared virtualization mux layer on top of a single hardware // alarm. let mux_alarm = static_init!( - MuxAlarm<'static, earlgrey::timer::RvTimer>, + MuxAlarm<'static, earlgrey::timer::RvTimer>, MuxAlarm::new(hardware_alarm) ); hil::time::Alarm::set_alarm_client(hardware_alarm, mux_alarm); @@ -279,139 +455,120 @@ unsafe fn setup() -> ( // Alarm let virtual_alarm_user = static_init!( - VirtualMuxAlarm<'static, earlgrey::timer::RvTimer>, + VirtualMuxAlarm<'static, earlgrey::timer::RvTimer>, VirtualMuxAlarm::new(mux_alarm) ); virtual_alarm_user.setup(); let scheduler_timer_virtual_alarm = static_init!( - VirtualMuxAlarm<'static, earlgrey::timer::RvTimer>, + VirtualMuxAlarm<'static, earlgrey::timer::RvTimer>, VirtualMuxAlarm::new(mux_alarm) ); scheduler_timer_virtual_alarm.setup(); let alarm = static_init!( - capsules::alarm::AlarmDriver<'static, VirtualMuxAlarm<'static, earlgrey::timer::RvTimer>>, - capsules::alarm::AlarmDriver::new( + capsules_core::alarm::AlarmDriver< + 'static, + VirtualMuxAlarm<'static, earlgrey::timer::RvTimer>, + >, + capsules_core::alarm::AlarmDriver::new( virtual_alarm_user, - board_kernel.create_grant(capsules::alarm::DRIVER_NUM, &memory_allocation_cap) + board_kernel.create_grant(capsules_core::alarm::DRIVER_NUM, &memory_allocation_cap) ) ); hil::time::Alarm::set_alarm_client(virtual_alarm_user, alarm); let scheduler_timer = static_init!( - VirtualSchedulerTimer>>, + VirtualSchedulerTimer< + VirtualMuxAlarm<'static, earlgrey::timer::RvTimer<'static, ChipConfig>>, + >, VirtualSchedulerTimer::new(scheduler_timer_virtual_alarm) ); let chip = static_init!( - earlgrey::chip::EarlGrey< - EarlGreyDefaultPeripherals, - >, - earlgrey::chip::EarlGrey::new(peripherals, hardware_alarm) + EarlGreyChip, + earlgrey::chip::EarlGrey::new(peripherals, hardware_alarm, earlgrey_epmp) ); CHIP = Some(chip); // Need to enable all interrupts for Tock Kernel chip.enable_plic_interrupts(); // enable interrupts globally - csr::CSR - .mie - .modify(csr::mie::mie::msoft::SET + csr::mie::mie::mtimer::SET + csr::mie::mie::mext::SET); + csr::CSR.mie.modify( + csr::mie::mie::msoft::SET + csr::mie::mie::mtimer::CLEAR + csr::mie::mie::mext::SET, + ); csr::CSR.mstatus.modify(csr::mstatus::mstatus::mie::SET); // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); let lldb = components::lldb::LowLevelDebugComponent::new( board_kernel, - capsules::low_level_debug::DRIVER_NUM, + capsules_core::low_level_debug::DRIVER_NUM, uart_mux, ) - .finalize(()); - - let mux_digest = components::digest::DigestMuxComponent::new(&peripherals.hmac).finalize( - components::digest_mux_component_helper!(lowrisc::hmac::Hmac, 32), - ); - - let digest_key_buffer = static_init!([u8; 32], [0; 32]); - - let digest = components::digest::DigestComponent::new(&mux_digest, digest_key_buffer).finalize( - components::digest_component_helper!(lowrisc::hmac::Hmac, 32,), - ); - - peripherals.hmac.set_client(digest); - - let hmac_key_buffer = static_init!([u8; 32], [0; 32]); - let hmac_data_buffer = static_init!([u8; 64], [0; 64]); - let hmac_dest_buffer = static_init!([u8; 32], [0; 32]); - - let mux_hmac = components::hmac::HmacMuxComponent::new(digest).finalize( - components::hmac_mux_component_helper!(capsules::virtual_digest::VirtualMuxDigest, 32), - ); + .finalize(components::low_level_debug_component_static!()); let hmac = components::hmac::HmacComponent::new( board_kernel, - capsules::hmac::DRIVER_NUM, - &mux_hmac, - hmac_key_buffer, - hmac_data_buffer, - hmac_dest_buffer, + capsules_extra::hmac::DRIVER_NUM, + &peripherals.hmac, ) - .finalize(components::hmac_component_helper!( - capsules::virtual_digest::VirtualMuxDigest, - 32, - )); - - digest.set_hmac_client(hmac); - - let sha_data_buffer = static_init!([u8; 64], [0; 64]); - let sha_dest_buffer = static_init!([u8; 32], [0; 32]); + .finalize(components::hmac_component_static!(lowrisc::hmac::Hmac, 32)); - let mux_sha = components::sha::ShaMuxComponent::new(digest).finalize( - components::sha_mux_component_helper!(capsules::virtual_digest::VirtualMuxDigest, 32), + let i2c_master_buffer = static_init!( + [u8; capsules_core::i2c_master::BUFFER_LENGTH], + [0; capsules_core::i2c_master::BUFFER_LENGTH] ); - - let sha = components::sha::ShaComponent::new( - board_kernel, - capsules::sha::DRIVER_NUM, - &mux_sha, - sha_data_buffer, - sha_dest_buffer, - ) - .finalize(components::sha_component_helper!(capsules::virtual_digest::VirtualMuxDigest, 32)); - - digest.set_sha_client(sha); - let i2c_master = static_init!( - capsules::i2c_master::I2CMasterDriver<'static, lowrisc::i2c::I2c<'static>>, - capsules::i2c_master::I2CMasterDriver::new( + capsules_core::i2c_master::I2CMasterDriver<'static, lowrisc::i2c::I2c<'static>>, + capsules_core::i2c_master::I2CMasterDriver::new( &peripherals.i2c0, - &mut capsules::i2c_master::BUF, - board_kernel.create_grant(capsules::i2c_master::DRIVER_NUM, &memory_allocation_cap) + i2c_master_buffer, + board_kernel.create_grant( + capsules_core::i2c_master::DRIVER_NUM, + &memory_allocation_cap + ) ) ); peripherals.i2c0.set_master_client(i2c_master); - peripherals.aes.initialise( - dynamic_deferred_caller.register(&peripherals.aes).unwrap(), // Unwrap fail = dynamic deferred caller out of slots + //SPI + let mux_spi = components::spi::SpiMuxComponent::new(&peripherals.spi_host0).finalize( + components::spi_mux_component_static!(lowrisc::spi_host::SpiHost), ); - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let spi_controller = components::spi::SpiSyscallComponent::new( + board_kernel, + mux_spi, + 0, + capsules_core::spi_controller::DRIVER_NUM, + ) + .finalize(components::spi_syscall_component_static!( + lowrisc::spi_host::SpiHost + )); + + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); // USB support is currently broken in the OpenTitan hardware // See https://github.com/lowRISC/opentitan/issues/2598 for more details - // let usb = usb::UsbComponent::new(board_kernel).finalize(()); + // let usb = components::usb::UsbComponent::new( + // board_kernel, + // capsules_extra::usb::usb_user::DRIVER_NUM, + // &peripherals.usb, + // ) + // .finalize(components::usb_component_static!(earlgrey::usbdev::Usb)); // Kernel storage region, allocated with the storage_volume! // macro in common/utils.rs @@ -421,6 +578,32 @@ unsafe fn setup() -> ( static _estorage: u8; } + // Flash setup memory protection for the ROM/Kernel + // Only allow reads for this region, any other ops will cause an MP fault + let mp_cfg = FlashMPConfig { + read_en: true, + write_en: false, + erase_en: false, + scramble_en: false, + ecc_en: false, + he_en: false, + }; + + // Allocate a flash protection region (associated cfg number: 0), for the code section. + if let Err(e) = peripherals.flash_ctrl.mp_set_region_perms( + core::ptr::addr_of!(_manifest) as usize, + core::ptr::addr_of!(_etext) as usize, + 0, + &mp_cfg, + ) { + debug!("Failed to set flash memory protection: {:?}", e); + } else { + // Lock region 0, until next system reset. + if let Err(e) = peripherals.flash_ctrl.mp_lock_region_cfg(0) { + debug!("Failed to lock memory protection config: {:?}", e); + } + } + // Flash let flash_ctrl_read_buf = static_init!( [u8; lowrisc::flash_ctrl::PAGE_SIZE], @@ -432,210 +615,263 @@ unsafe fn setup() -> ( ); let mux_flash = components::flash::FlashMuxComponent::new(&peripherals.flash_ctrl).finalize( - components::flash_mux_component_helper!(lowrisc::flash_ctrl::FlashCtrl), + components::flash_mux_component_static!(lowrisc::flash_ctrl::FlashCtrl), + ); + + // SipHash + let sip_hash = static_init!( + capsules_extra::sip_hash::SipHasher24, + capsules_extra::sip_hash::SipHasher24::new() ); + kernel::deferred_call::DeferredCallClient::register(sip_hash); + SIPHASH = Some(sip_hash); // TicKV - #[cfg(not(feature = "fpga_nexysvideo"))] let tickv = components::tickv::TicKVComponent::new( - &mux_flash, // Flash controller - 0x20090000 / lowrisc::flash_ctrl::PAGE_SIZE, // Region offset (size / page_size) - 0x70000, // Region size - flash_ctrl_read_buf, // Buffer used internally in TicKV - page_buffer, // Buffer used with the flash controller + sip_hash, + mux_flash, // Flash controller + lowrisc::flash_ctrl::FLASH_PAGES_PER_BANK - 1, // Region offset (End of Bank0/Use Bank1) + // Region Size + lowrisc::flash_ctrl::FLASH_PAGES_PER_BANK * lowrisc::flash_ctrl::PAGE_SIZE, + flash_ctrl_read_buf, // Buffer used internally in TicKV + page_buffer, // Buffer used with the flash controller ) - .finalize(components::tickv_component_helper!( - lowrisc::flash_ctrl::FlashCtrl - )); - #[cfg(any(feature = "fpga_nexysvideo"))] - let tickv = components::tickv::TicKVComponent::new( - &mux_flash, // Flash controller - 0x20060000 / lowrisc::flash_ctrl::PAGE_SIZE, // Region offset (size / page_size) - 0x20000, // Region size - flash_ctrl_read_buf, // Buffer used internally in TicKV - page_buffer, // Buffer used with the flash controller - ) - .finalize(components::tickv_component_helper!( - lowrisc::flash_ctrl::FlashCtrl + .finalize(components::tickv_component_static!( + lowrisc::flash_ctrl::FlashCtrl, + capsules_extra::sip_hash::SipHasher24, + 2048 )); hil::flash::HasClient::set_client(&peripherals.flash_ctrl, mux_flash); + sip_hash.set_client(tickv); TICKV = Some(tickv); - // Newer FPGA builds of OpenTitan don't include the OTBN, so any accesses - // to the OTBN hardware will hang. - // OTBN is still connected though as it works on simulation runs - let _mux_otbn = crate::otbn::AccelMuxComponent::new(&peripherals.otbn) - .finalize(otbn_mux_component_helper!(1024)); + let kv_store = components::kv::TicKVKVStoreComponent::new(tickv).finalize( + components::tickv_kv_store_component_static!( + capsules_extra::tickv::TicKVSystem< + capsules_core::virtualizers::virtual_flash::FlashUser< + lowrisc::flash_ctrl::FlashCtrl, + >, + capsules_extra::sip_hash::SipHasher24<'static>, + 2048, + >, + capsules_extra::tickv::TicKVKeyType, + ), + ); + + let kv_store_permissions = components::kv::KVStorePermissionsComponent::new(kv_store).finalize( + components::kv_store_permissions_component_static!( + capsules_extra::tickv_kv_store::TicKVKVStore< + capsules_extra::tickv::TicKVSystem< + capsules_core::virtualizers::virtual_flash::FlashUser< + lowrisc::flash_ctrl::FlashCtrl, + >, + capsules_extra::sip_hash::SipHasher24<'static>, + 2048, + >, + capsules_extra::tickv::TicKVKeyType, + > + ), + ); + + let mux_kv = components::kv::KVPermissionsMuxComponent::new(kv_store_permissions).finalize( + components::kv_permissions_mux_component_static!( + capsules_extra::kv_store_permissions::KVStorePermissions< + capsules_extra::tickv_kv_store::TicKVKVStore< + capsules_extra::tickv::TicKVSystem< + capsules_core::virtualizers::virtual_flash::FlashUser< + lowrisc::flash_ctrl::FlashCtrl, + >, + capsules_extra::sip_hash::SipHasher24<'static>, + 2048, + >, + capsules_extra::tickv::TicKVKeyType, + >, + > + ), + ); + + let virtual_kv_driver = components::kv::VirtualKVPermissionsComponent::new(mux_kv).finalize( + components::virtual_kv_permissions_component_static!( + capsules_extra::kv_store_permissions::KVStorePermissions< + capsules_extra::tickv_kv_store::TicKVKVStore< + capsules_extra::tickv::TicKVSystem< + capsules_core::virtualizers::virtual_flash::FlashUser< + lowrisc::flash_ctrl::FlashCtrl, + >, + capsules_extra::sip_hash::SipHasher24<'static>, + 2048, + >, + capsules_extra::tickv::TicKVKeyType, + >, + > + ), + ); + + let kv_driver = components::kv::KVDriverComponent::new( + virtual_kv_driver, + board_kernel, + capsules_extra::kv_driver::DRIVER_NUM, + ) + .finalize(components::kv_driver_component_static!( + capsules_extra::virtual_kv::VirtualKVPermissions< + capsules_extra::kv_store_permissions::KVStorePermissions< + capsules_extra::tickv_kv_store::TicKVKVStore< + capsules_extra::tickv::TicKVSystem< + capsules_core::virtualizers::virtual_flash::FlashUser< + lowrisc::flash_ctrl::FlashCtrl, + >, + capsules_extra::sip_hash::SipHasher24<'static>, + 2048, + >, + capsules_extra::tickv::TicKVKeyType, + >, + >, + > + )); + + let mux_otbn = crate::otbn::AccelMuxComponent::new(&peripherals.otbn) + .finalize(otbn_mux_component_static!()); + + let otbn = OtbnComponent::new(mux_otbn).finalize(crate::otbn_component_static!()); + + let otbn_rsa_internal_buf = static_init!([u8; 512], [0; 512]); + + // Use the OTBN to create an RSA engine + if let Ok((rsa_imem_start, rsa_imem_length, rsa_dmem_start, rsa_dmem_length)) = + crate::otbn::find_app( + "otbn-rsa", + core::slice::from_raw_parts( + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, + ), + ) + { + let rsa_hardware = static_init!( + lowrisc::rsa::OtbnRsa<'static>, + lowrisc::rsa::OtbnRsa::new( + otbn, + lowrisc::rsa::AppAddresses { + imem_start: rsa_imem_start, + imem_size: rsa_imem_length, + dmem_start: rsa_dmem_start, + dmem_size: rsa_dmem_length + }, + otbn_rsa_internal_buf, + ) + ); + peripherals.otbn.set_client(rsa_hardware); + RSA_HARDWARE = Some(rsa_hardware); + } else { + debug!("Unable to find otbn-rsa, disabling RSA support"); + } // Convert hardware RNG to the Random interface. let entropy_to_random = static_init!( - capsules::rng::Entropy32ToRandom<'static>, - capsules::rng::Entropy32ToRandom::new(&peripherals.rng) + capsules_core::rng::Entropy32ToRandom<'static, lowrisc::csrng::CsRng<'static>>, + capsules_core::rng::Entropy32ToRandom::new(&peripherals.rng) ); peripherals.rng.set_client(entropy_to_random); // Setup RNG for userspace let rng = static_init!( - capsules::rng::RngDriver<'static>, - capsules::rng::RngDriver::new( + capsules_core::rng::RngDriver< + 'static, + capsules_core::rng::Entropy32ToRandom<'static, lowrisc::csrng::CsRng<'static>>, + >, + capsules_core::rng::RngDriver::new( entropy_to_random, - board_kernel.create_grant(capsules::rng::DRIVER_NUM, &memory_allocation_cap) + board_kernel.create_grant(capsules_core::rng::DRIVER_NUM, &memory_allocation_cap) ) ); entropy_to_random.set_client(rng); const CRYPT_SIZE: usize = 7 * AES128_BLOCK_SIZE; - let aes_source_buffer = static_init!([u8; 16], [0; 16]); - let aes_dest_buffer = static_init!([u8; CRYPT_SIZE], [0; CRYPT_SIZE]); - let ccm_mux = static_init!( virtual_aes_ccm::MuxAES128CCM<'static, earlgrey::aes::Aes<'static>>, - virtual_aes_ccm::MuxAES128CCM::new(&peripherals.aes, dynamic_deferred_caller) + virtual_aes_ccm::MuxAES128CCM::new(&peripherals.aes) ); + kernel::deferred_call::DeferredCallClient::register(ccm_mux); peripherals.aes.set_client(ccm_mux); - ccm_mux.initialize_callback_handle( - dynamic_deferred_caller.register(ccm_mux).unwrap(), // Unwrap fail = no deferred call slot available for ccm mux - ); - let crypt_buf1 = static_init!([u8; CRYPT_SIZE], [0x00; CRYPT_SIZE]); - let ccm_client1 = static_init!( - virtual_aes_ccm::VirtualAES128CCM<'static, earlgrey::aes::Aes<'static>>, - virtual_aes_ccm::VirtualAES128CCM::new(ccm_mux, crypt_buf1) + let ccm_client = components::aes::AesVirtualComponent::new(ccm_mux).finalize( + components::aes_virtual_component_static!(earlgrey::aes::Aes<'static>), ); - ccm_client1.setup(); - // ccm_mux.set_client(ccm_client1); - let aes = static_init!( - capsules::symmetric_encryption::aes::AesDriver< + let crypt_buf2 = static_init!([u8; CRYPT_SIZE], [0x00; CRYPT_SIZE]); + let gcm_client = static_init!( + aes_gcm::Aes128Gcm< 'static, virtual_aes_ccm::VirtualAES128CCM<'static, earlgrey::aes::Aes<'static>>, >, - capsules::symmetric_encryption::aes::AesDriver::new( - ccm_client1, - aes_source_buffer, - aes_dest_buffer, - board_kernel.create_grant( - capsules::symmetric_encryption::aes::DRIVER_NUM, - &memory_allocation_cap - ) - ) + aes_gcm::Aes128Gcm::new(ccm_client, crypt_buf2) ); + ccm_client.set_client(gcm_client); - AES = Some(ccm_client1); + let aes = components::aes::AesDriverComponent::new( + board_kernel, + capsules_extra::symmetric_encryption::aes::DRIVER_NUM, + gcm_client, + ) + .finalize(components::aes_driver_component_static!( + aes_gcm::Aes128Gcm< + 'static, + virtual_aes_ccm::VirtualAES128CCM<'static, earlgrey::aes::Aes<'static>>, + >, + )); - hil::symmetric_encryption::AES128CCM::set_client(ccm_client1, aes); - hil::symmetric_encryption::AES128::set_client(ccm_client1, aes); + AES = Some(gcm_client); - /// These symbols are defined in the linker script. - extern "C" { - /// Beginning of the ROM region containing app images. - static _sapps: u8; - /// End of the ROM region containing app images. - static _eapps: u8; - /// Beginning of the RAM region for app memory. - static mut _sappmem: u8; - /// End of the RAM region for app memory. - static _eappmem: u8; - /// The start of the kernel stack (Included only for kernel PMP) - static _sstack: u8; - /// The end of the kernel stack (Included only for kernel PMP) - static _estack: u8; - /// The start of the kernel text (Included only for kernel PMP) - static _stext: u8; - /// The end of the kernel text (Included only for kernel PMP) - static _etext: u8; - /// The start of the kernel relocation region - /// (Included only for kernel PMP) - static _srelocate: u8; - /// The end of the kernel relocation region - /// (Included only for kernel PMP) - static _erelocate: u8; - /// The start of the kernel BSS (Included only for kernel PMP) - static _szero: u8; - /// The end of the kernel BSS (Included only for kernel PMP) - static _ezero: u8; + #[cfg(test)] + { + use capsules_extra::sha256::Sha256Software; + + let sha_soft = static_init!(Sha256Software<'static>, Sha256Software::new()); + kernel::deferred_call::DeferredCallClient::register(sha_soft); + + SHA256SOFT = Some(sha_soft); } + hil::symmetric_encryption::AES128GCM::set_client(gcm_client, aes); + hil::symmetric_encryption::AES128::set_client(gcm_client, ccm_client); + let syscall_filter = static_init!(TbfHeaderFilterDefaultAllow, TbfHeaderFilterDefaultAllow {}); - let scheduler = components::sched::priority::PriorityComponent::new(board_kernel).finalize(()); - - let earlgrey_nexysvideo = static_init!( - EarlGreyNexysVideo, - EarlGreyNexysVideo { - gpio: gpio, - led: led, - console: console, - alarm: alarm, + let scheduler = components::sched::priority::PriorityComponent::new(board_kernel) + .finalize(components::priority_component_static!()); + let watchdog = &peripherals.watchdog; + + let earlgrey = static_init!( + EarlGrey, + EarlGrey { + led, + gpio, + console, + alarm, hmac, - sha, - rng, - lldb: lldb, + lldb, i2c_master, + spi_controller, + rng, aes, + kv_driver, syscall_filter, scheduler, scheduler_timer, + watchdog, } ); - let mut mpu_config = rv32i::epmp::PMPConfig::default(); - // The kernel stack - chip.pmp.allocate_kernel_region( - &_sstack as *const u8, - &_estack as *const u8 as usize - &_sstack as *const u8 as usize, - mpu::Permissions::ReadWriteOnly, - &mut mpu_config, - ); - // The kernel text - chip.pmp.allocate_kernel_region( - &_stext as *const u8, - &_etext as *const u8 as usize - &_stext as *const u8 as usize, - mpu::Permissions::ReadExecuteOnly, - &mut mpu_config, - ); - // The kernel relocate data - chip.pmp.allocate_kernel_region( - &_srelocate as *const u8, - &_erelocate as *const u8 as usize - &_srelocate as *const u8 as usize, - mpu::Permissions::ReadWriteOnly, - &mut mpu_config, - ); - // The kernel BSS - chip.pmp.allocate_kernel_region( - &_szero as *const u8, - &_ezero as *const u8 as usize - &_szero as *const u8 as usize, - mpu::Permissions::ReadWriteOnly, - &mut mpu_config, - ); - // The app locations - chip.pmp.allocate_kernel_region( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, - mpu::Permissions::ReadWriteOnly, - &mut mpu_config, - ); - // The app memory locations - chip.pmp.allocate_kernel_region( - &_sappmem as *const u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, - mpu::Permissions::ReadWriteOnly, - &mut mpu_config, - ); - - chip.pmp.enable_kernel_mpu(&mut mpu_config); - kernel::process::load_processes( board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_mgmt_cap, ) @@ -645,7 +881,7 @@ unsafe fn setup() -> ( }); debug!("OpenTitan initialisation complete. Entering main loop"); - (board_kernel, earlgrey_nexysvideo, chip, peripherals) + (board_kernel, earlgrey, chip, peripherals) } /// Main function. @@ -659,16 +895,11 @@ pub unsafe fn main() { #[cfg(not(test))] { - let (board_kernel, earlgrey_nexysvideo, chip, _peripherals) = setup(); + let (board_kernel, earlgrey, chip, _peripherals) = setup(); let main_loop_cap = create_capability!(capabilities::MainLoopCapability); - board_kernel.kernel_loop( - earlgrey_nexysvideo, - chip, - None::<&kernel::ipc::IPC>, - &main_loop_cap, - ); + board_kernel.kernel_loop(earlgrey, chip, None::<&kernel::ipc::IPC<0>>, &main_loop_cap); } } @@ -678,10 +909,10 @@ use kernel::platform::watchdog::WatchDog; #[cfg(test)] fn test_runner(tests: &[&dyn Fn()]) { unsafe { - let (board_kernel, earlgrey_nexysvideo, _chip, peripherals) = setup(); + let (board_kernel, earlgrey, _chip, peripherals) = setup(); BOARD = Some(board_kernel); - PLATFORM = Some(&earlgrey_nexysvideo); + PLATFORM = Some(&earlgrey); PERIPHERALS = Some(peripherals); MAIN_CAP = Some(&create_capability!(capabilities::MainLoopCapability)); diff --git a/boards/opentitan/src/otbn.rs b/boards/opentitan/src/otbn.rs index 028d64e7d4..039311ad5d 100644 --- a/boards/opentitan/src/otbn.rs +++ b/boards/opentitan/src/otbn.rs @@ -1,10 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Components for collections of Hardware Accelerators. //! //! Usage //! ----- //! ```rust //! let _mux_otbn = crate::otbn::AccelMuxComponent::new(&peripherals.otbn) -//! .finalize(otbn_mux_component_helper!()); +//! .finalize(otbn_mux_component_static!()); //! //! peripherals.otbn.initialise( //! dynamic_deferred_caller @@ -15,18 +19,20 @@ use core::mem::MaybeUninit; use kernel::component::Component; -use kernel::static_init_half; use lowrisc::otbn::Otbn; -use lowrisc::virtual_otbn::MuxAccel; +use lowrisc::virtual_otbn::{MuxAccel, VirtualMuxAccel}; + +#[macro_export] +macro_rules! otbn_mux_component_static { + () => {{ + kernel::static_buf!(lowrisc::virtual_otbn::MuxAccel<'static>) + }}; +} -// Setup static space for the objects. #[macro_export] -macro_rules! otbn_mux_component_helper { - ($T:expr $(,)?) => {{ - use core::mem::MaybeUninit; - use lowrisc::virtual_otbn::MuxAccel; - static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); - &mut BUF1 +macro_rules! otbn_component_static { + () => {{ + kernel::static_buf!(lowrisc::virtual_otbn::VirtualMuxAccel<'static>) }}; } @@ -44,10 +50,30 @@ impl Component for AccelMuxComponent { type StaticInput = &'static mut MaybeUninit>; type Output = &'static MuxAccel<'static>; - unsafe fn finalize(self, s: Self::StaticInput) -> Self::Output { - let mux_otbn = static_init_half!(s, MuxAccel<'static>, MuxAccel::new(self.otbn)); + fn finalize(self, s: Self::StaticInput) -> Self::Output { + s.write(MuxAccel::new(self.otbn)) + } +} + +pub struct OtbnComponent { + mux_otbn: &'static MuxAccel<'static>, +} + +impl OtbnComponent { + pub fn new(mux_otbn: &'static MuxAccel<'static>) -> OtbnComponent { + OtbnComponent { mux_otbn } + } +} + +impl Component for OtbnComponent { + type StaticInput = &'static mut MaybeUninit>; + + type Output = &'static VirtualMuxAccel<'static>; + + fn finalize(self, s: Self::StaticInput) -> Self::Output { + let virtual_otbn_user = s.write(VirtualMuxAccel::new(self.mux_otbn)); - mux_otbn + virtual_otbn_user } } @@ -55,7 +81,7 @@ impl Component for AccelMuxComponent { /// /// This will iterate through the app list inside the `app_flash` looking /// for a disabled app with the same name as `name`. -/// On sucess this function will return the following information: +/// On success this function will return the following information: /// * OTBN imem start address /// * OTBN imem size /// * OTBN dmem start address diff --git a/boards/opentitan/src/pinmux_layout.rs b/boards/opentitan/src/pinmux_layout.rs new file mode 100644 index 0000000000..43e1f49205 --- /dev/null +++ b/boards/opentitan/src/pinmux_layout.rs @@ -0,0 +1,137 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +use earlgrey::pinmux_config::{EarlGreyPinmuxConfig, INPUT_NUM, OUTPUT_NUM}; +use earlgrey::registers::top_earlgrey::{PinmuxInsel, PinmuxOutsel}; + +type In = PinmuxInsel; +type Out = PinmuxOutsel; + +/// Pinmux configuration for hyperdebug with cw310. The primary reference for +/// this config is: +/// /hw/top_earlgrey/data/pins_cw310_hyperdebug.xdc +/// pins_cw310_hyperdebug.xdc sometimes underspecifies the layout (e.g. it +/// doesn't indicate UART numbering); we resolve the ambiguity by using +/// /hw/top_earlgrey/data/pins_cw310.xdc +pub enum BoardPinmuxLayout {} + +impl EarlGreyPinmuxConfig for BoardPinmuxLayout { + #[rustfmt::skip] + const INPUT: &'static [PinmuxInsel; INPUT_NUM] = &[ + In::Ioa2, // GpioGpio0 + In::Ioa3, // GpioGpio1 + In::Ioa6, // GpioGpio2 + In::Iob0, // GpioGpio3 + In::Iob1, // GpioGpio4 + In::Iob2, // GpioGpio5 + In::Iob3, // GpioGpio6 + In::Iob6, // GpioGpio7 + In::Iob7, // GpioGpio8 + In::Iob8, // GpioGpio9 + In::Ioc0, // GpioGpio10 + In::Ioc1, // GpioGpio11 + In::Ioc2, // GpioGpio12 + In::Ioc5, // GpioGpio13 + In::Ioc6, // GpioGpio14 + In::Ioc7, // GpioGpio15 + In::Ioc8, // GpioGpio16 + In::Ioc9, // GpioGpio17 + In::Ioc10, // GpioGpio18 + In::Ioc11, // GpioGpio19 + In::Ioc12, // GpioGpio20 + In::Ior0, // GpioGpio21 + In::Ior1, // GpioGpio22 + In::Ior2, // GpioGpio23 + In::Ior3, // GpioGpio24 + In::Ior4, // GpioGpio25 + In::Ior5, // GpioGpio26 + In::Ior6, // GpioGpio27 + In::Ior7, // GpioGpio28 + In::Ior10, // GpioGpio29 + In::Ior11, // GpioGpio30 + In::Ior12, // GpioGpio31 + In::Iob12, // I2c0Sda + In::Iob11, // I2c0Scl + In::Ioa7, // I2c1Sda + In::Ioa8, // I2c1Scl + In::Iob9, // I2c2Sda + In::Iob10, // I2c2Scl + In::ConstantZero, // SpiHost1Sd0 + In::ConstantZero, // SpiHost1Sd1 + In::ConstantZero, // SpiHost1Sd2 + In::ConstantZero, // SpiHost1Sd3 + In::Ioc3, // Uart0Rx + In::Iob4, // Uart1Rx + In::Ioa0, // Uart2Rx + In::Ioa4, // Uart3Rx + In::ConstantZero, // SpiDeviceTpmCsb + In::ConstantZero, // FlashCtrlTck + In::ConstantZero, // FlashCtrlTms + In::ConstantZero, // FlashCtrlTdi + In::ConstantZero, // SysrstCtrlAonAcPresent + In::ConstantZero, // SysrstCtrlAonKey0In + In::ConstantZero, // SysrstCtrlAonKey1In + In::ConstantZero, // SysrstCtrlAonKey2In + In::ConstantZero, // SysrstCtrlAonPwrbIn + In::ConstantZero, // SysrstCtrlAonLidOpen + In::ConstantZero, // UsbdevSense + ]; + + #[rustfmt::skip] + const OUTPUT: &'static [PinmuxOutsel; OUTPUT_NUM] = &[ + // __________ BANK IOA __________ + Out::ConstantHighZ, // Ioa0 UART2_RX + Out::Uart2Tx, // Ioa1 UART2_TX + Out::GpioGpio0, // Ioa2 + Out::GpioGpio1, // Ioa3 + Out::ConstantHighZ, // Ioa4 UART3_RX + Out::Uart3Tx, // Ioa5 UART3_TX + Out::GpioGpio2, // Ioa6 + Out::I2c1Sda, // Ioa7 I2C1_SDA + Out::I2c1Scl, // Ioa8 I2C1_SCL + // __________ BANK IOB __________ + Out::GpioGpio3, // Iob0 SPI_HOST_CS + Out::GpioGpio4, // Iob1 SPI_HOST_DI + Out::GpioGpio5, // Iob2 SPI_HOST_DO + Out::GpioGpio6, // Iob3 SPI_HOST_CLK + Out::ConstantHighZ, // Iob4 UART1_RX + Out::Uart1Tx, // Iob5 UART1_TX + Out::GpioGpio7, // Iob6 + Out::GpioGpio8, // Iob7 + Out::GpioGpio9, // Iob8 + Out::I2c2Sda, // Iob9 I2C2_SDA + Out::I2c2Scl, // Iob10 I2C2_SCL + Out::I2c0Scl, // Iob11 I2C0_SCL + Out::I2c0Sda, // Iob12 I2C0_SDA + // __________ BANK IOC __________ + Out::GpioGpio10, // Ioc0 + Out::GpioGpio11, // Ioc1 + Out::GpioGpio12, // Ioc2 + Out::ConstantHighZ, // Ioc3 UART0_RX + Out::Uart0Tx, // Ioc4 UART0_TX + Out::ConstantHighZ, // Ioc5 (TAP STRAP 1) + Out::GpioGpio14, // Ioc6 + Out::GpioGpio15, // Ioc7 + Out::ConstantHighZ, // Ioc8 (TAP STRAP 0) + Out::GpioGpio17, // Ioc9 + Out::GpioGpio18, // Ioc10 + Out::GpioGpio19, // Ioc11 + Out::GpioGpio20, // Ioc12 + // __________ BANK IOR __________ + Out::GpioGpio21, // Ior0 + Out::GpioGpio22, // Ior1 + Out::GpioGpio23, // Ior2 + Out::GpioGpio24, // Ior3 + Out::GpioGpio25, // Ior4 + Out::GpioGpio26, // Ior5 + Out::GpioGpio27, // Ior6 + Out::GpioGpio28, // Ior7 + // DIO CW310_hyp // Ior8 + // DIO CW310_hyp // Ior9 + Out::GpioGpio29, // Ior10 + Out::GpioGpio30, // Ior11 + Out::GpioGpio31, // Ior12 + Out::ConstantHighZ, // Ior13 + ]; +} diff --git a/boards/opentitan/src/tests/aes_test.rs b/boards/opentitan/src/tests/aes_test.rs index 9e4a8d5a54..5f3d80b62a 100644 --- a/boards/opentitan/src/tests/aes_test.rs +++ b/boards/opentitan/src/tests/aes_test.rs @@ -1,16 +1,33 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test that AES ECB mode is working properly. use crate::tests::run_kernel_op; use crate::{AES, PERIPHERALS}; -use capsules::test::aes::{TestAes128Cbc, TestAes128Ctr, TestAes128Ecb}; -use capsules::test::aes_ccm::Test; -use capsules::virtual_aes_ccm; +use capsules_aes_gcm::aes_gcm::Aes128Gcm; +use capsules_core::virtualizers::virtual_aes_ccm; +use capsules_extra::test::aes::{TestAes128Cbc, TestAes128Ctr, TestAes128Ecb}; +use capsules_extra::test::aes_ccm; +use capsules_extra::test::aes_gcm; use earlgrey::aes::Aes; use kernel::debug; use kernel::hil::symmetric_encryption::{AES128, AES128_BLOCK_SIZE, AES128_KEY_SIZE}; use kernel::static_init; +/// The only 'test_case' for aes_test as directly invoked by the test runner, +/// this calls all the other tests, preserving the order in which they must +/// be ran. #[test_case] +fn aes_tester() { + run_aes128_ccm(); + run_aes128_gcm(); + run_aes128_ecb(); + run_aes128_cbc(); + run_aes128_ctr(); +} + fn run_aes128_ccm() { debug!("check run AES128 CCM... "); run_kernel_op(100); @@ -29,17 +46,57 @@ fn run_aes128_ccm() { } unsafe fn static_init_test_ccm( - aes: &'static virtual_aes_ccm::VirtualAES128CCM<'static, Aes>, -) -> &'static Test<'static, virtual_aes_ccm::VirtualAES128CCM<'static, Aes<'static>>> { + aes: &'static Aes128Gcm<'static, virtual_aes_ccm::VirtualAES128CCM<'static, Aes<'static>>>, +) -> &'static aes_ccm::Test< + 'static, + Aes128Gcm<'static, virtual_aes_ccm::VirtualAES128CCM<'static, Aes<'static>>>, +> { let buf = static_init!([u8; 4 * AES128_BLOCK_SIZE], [0; 4 * AES128_BLOCK_SIZE]); static_init!( - Test<'static, virtual_aes_ccm::VirtualAES128CCM<'static, Aes>>, - Test::new(aes, buf) + aes_ccm::Test< + 'static, + Aes128Gcm<'static, virtual_aes_ccm::VirtualAES128CCM<'static, Aes<'static>>>, + >, + aes_ccm::Test::new(aes, buf) + ) +} + +fn run_aes128_gcm() { + debug!("check run AES128 GCM... "); + run_kernel_op(100); + + unsafe { + let aes = AES.unwrap(); + + let t = static_init_test_gcm(&aes); + kernel::hil::symmetric_encryption::AES128GCM::set_client(aes, t); + + #[cfg(feature = "hardware_tests")] + t.run(); + } + run_kernel_op(10000); + debug!(" [ok]"); + run_kernel_op(100); +} + +unsafe fn static_init_test_gcm( + aes: &'static Aes128Gcm<'static, virtual_aes_ccm::VirtualAES128CCM<'static, Aes<'static>>>, +) -> &'static aes_gcm::Test< + 'static, + Aes128Gcm<'static, virtual_aes_ccm::VirtualAES128CCM<'static, Aes<'static>>>, +> { + let buf = static_init!([u8; 9 * AES128_BLOCK_SIZE], [0; 9 * AES128_BLOCK_SIZE]); + + static_init!( + aes_gcm::Test< + 'static, + Aes128Gcm<'static, virtual_aes_ccm::VirtualAES128CCM<'static, Aes<'static>>>, + >, + aes_gcm::Test::new(aes, buf) ) } -#[test_case] fn run_aes128_ecb() { debug!("check run AES128 ECB... "); run_kernel_op(100); @@ -52,7 +109,10 @@ fn run_aes128_ecb() { aes.set_client(t); #[cfg(feature = "hardware_tests")] - t.run(); + { + while !aes.idle() {} + t.run(); + } } run_kernel_op(1000); debug!(" [ok]"); @@ -66,11 +126,10 @@ unsafe fn static_init_test_ecb(aes: &'static Aes) -> &'static TestAes128Ecb<'sta static_init!( TestAes128Ecb<'static, Aes>, - TestAes128Ecb::new(aes, key, source, data) + TestAes128Ecb::new(aes, key, source, data, true) ) } -#[test_case] fn run_aes128_cbc() { debug!("check run AES128 CBC... "); run_kernel_op(100); @@ -83,7 +142,10 @@ fn run_aes128_cbc() { aes.set_client(t); #[cfg(feature = "hardware_tests")] - t.run(); + { + while !aes.idle() {} + t.run(); + } } run_kernel_op(1000); debug!(" [ok]"); @@ -98,11 +160,10 @@ unsafe fn static_init_test_cbc(aes: &'static Aes) -> &'static TestAes128Cbc<'sta static_init!( TestAes128Cbc<'static, Aes>, - TestAes128Cbc::new(aes, key, iv, source, data) + TestAes128Cbc::new(aes, key, iv, source, data, true) ) } -#[test_case] fn run_aes128_ctr() { debug!("check run AES128 CTR... "); run_kernel_op(100); @@ -115,7 +176,10 @@ fn run_aes128_ctr() { aes.set_client(t); #[cfg(feature = "hardware_tests")] - t.run(); + { + while !aes.idle() {} + t.run(); + } } run_kernel_op(1000); debug!(" [ok]"); @@ -130,6 +194,6 @@ unsafe fn static_init_test_ctr(aes: &'static Aes) -> &'static TestAes128Ctr<'sta static_init!( TestAes128Ctr<'static, Aes>, - TestAes128Ctr::new(aes, key, iv, source, data) + TestAes128Ctr::new(aes, key, iv, source, data, true) ) } diff --git a/boards/opentitan/src/tests/csrng.rs b/boards/opentitan/src/tests/csrng.rs index 7fe9b70ed2..d8bcccd3e3 100644 --- a/boards/opentitan/src/tests/csrng.rs +++ b/boards/opentitan/src/tests/csrng.rs @@ -1,8 +1,12 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test that the RNG works use crate::tests::run_kernel_op; use crate::PERIPHERALS; -use capsules::test::rng::TestEntropy32; +use capsules_core::test::rng::TestEntropy32; use kernel::debug; use kernel::hil::entropy::Entropy32; use kernel::static_init; diff --git a/boards/opentitan/src/tests/flash.rs b/boards/opentitan/src/tests/flash.rs new file mode 100644 index 0000000000..652ae80852 --- /dev/null +++ b/boards/opentitan/src/tests/flash.rs @@ -0,0 +1,459 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Test the opentitan flash implementation + +mod key_value { + use crate::tests::run_kernel_op; + use crate::{SIPHASH, TICKV}; + use capsules_core::virtualizers::virtual_flash::FlashUser; + use capsules_extra::test::kv_system::KVSystemTest; + use capsules_extra::tickv::KVSystem; + use capsules_extra::tickv::{TicKVKeyType, TicKVSystem}; + use kernel::debug; + use kernel::hil::hasher::Hasher; + use kernel::static_init; + use kernel::utilities::leasable_buffer::SubSliceMut; + + #[test_case] + fn tickv_append_key() { + debug!("start TicKV append key test..."); + + unsafe { + let tickv = TICKV.unwrap(); + let sip_hasher = SIPHASH.unwrap(); + + let key_input = static_init!( + [u8; 16], + [ + 0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x12, 0x34, 0x56, 0x78, 0x9A, + 0xBC, 0xDE, 0xF0 + ] + ); + let key = static_init!([u8; 8], [0; 8]); + let value = static_init!([u8; 3], [0x10, 0x20, 0x30]); + let ret = static_init!([u8; 4], [0; 4]); + + let test = static_init!( + KVSystemTest< + 'static, + TicKVSystem< + 'static, + FlashUser<'static, lowrisc::flash_ctrl::FlashCtrl<'static>>, + capsules_extra::sip_hash::SipHasher24, + 2048, + >, + TicKVKeyType, + >, + KVSystemTest::new(tickv, SubSliceMut::new(value), ret) + ); + + sip_hasher.set_client(tickv); + tickv.set_client(test); + + // Kick start the tests by generating a key + tickv + .generate_key(SubSliceMut::new(key_input), key) + .unwrap(); + } + run_kernel_op(100000); + + debug!(" [ok]"); + run_kernel_op(100); + } +} + +mod protections_and_controller { + use crate::tests::run_kernel_op; + use crate::PERIPHERALS; + use core::cell::Cell; + use kernel::debug; + use kernel::hil; + use kernel::hil::flash::HasClient; + use kernel::static_init; + use kernel::utilities::cells::TakeCell; + + struct FlashCtlCallBack { + read_pending: Cell, + write_pending: Cell, + // We recover the callback returned buffer into these + read_out_buf: TakeCell<'static, [u8]>, + write_out_buf: TakeCell<'static, [u8]>, + // Flag if an MP fault was detected + mp_fault_detect: Cell, + } + + impl<'a> FlashCtlCallBack { + fn new() -> Self { + FlashCtlCallBack { + read_pending: Cell::new(false), + write_pending: Cell::new(false), + read_out_buf: TakeCell::empty(), + write_out_buf: TakeCell::empty(), + mp_fault_detect: Cell::new(false), + } + } + + fn reset(&self) { + self.read_pending.set(false); + self.write_pending.set(false); + self.mp_fault_detect.set(false); + } + } + + impl<'a, F: hil::flash::Flash> hil::flash::Client for FlashCtlCallBack { + fn read_complete(&self, page: &'static mut F::Page, error: Result<(), hil::flash::Error>) { + if self.read_pending.get() { + if error == Err(hil::flash::Error::FlashMemoryProtectionError) { + self.mp_fault_detect.set(true); + } else { + assert_eq!(error, Ok(())); + } + self.read_out_buf.replace(page.as_mut()); + self.read_pending.set(false); + } + } + + fn write_complete(&self, page: &'static mut F::Page, error: Result<(), hil::flash::Error>) { + if self.write_pending.get() { + if error == Err(hil::flash::Error::FlashMemoryProtectionError) { + self.mp_fault_detect.set(true); + } else { + assert_eq!(error, Ok(())); + } + self.write_out_buf.replace(page.as_mut()); + self.write_pending.set(false); + } + } + + fn erase_complete(&self, error: Result<(), hil::flash::Error>) { + // Caller may check by a successive page read to assert the erased + // page is composed of 0xFF (all erased bits should be 1) + if error == Err(hil::flash::Error::FlashMemoryProtectionError) { + self.mp_fault_detect.set(true); + } else { + assert_eq!(error, Ok(())); + } + } + } + + macro_rules! static_init_test { + () => {{ + let r_in_page = static_init!( + lowrisc::flash_ctrl::LowRiscPage, + lowrisc::flash_ctrl::LowRiscPage::default() + ); + let w_in_page = static_init!( + lowrisc::flash_ctrl::LowRiscPage, + lowrisc::flash_ctrl::LowRiscPage::default() + ); + let mut val: u8 = 0; + + for i in 0..lowrisc::flash_ctrl::PAGE_SIZE { + val = val.wrapping_add(10); + r_in_page[i] = 0x00; + w_in_page[i] = 0xAA; // Arbitrary Data + } + static_init!(FlashCtlCallBack, FlashCtlCallBack::new()) + }}; + } + + /// The only 'test_case' for flash_ctrl as directly invoked by the test runner, + /// this calls all the other tests, preserving the order in which they must + /// be ran. + #[test_case] + fn flash_ctrl_tester() { + flash_ctrl_read_write_page(); + flash_ctrl_erase_page(); + flash_ctrl_mp_basic(); + flash_ctrl_mp_functionality(); + } + + // Note: the tests below need to run in a particular order, hence the a, b, c... + // function name prefix (test runner seems to invoke them alphabetically). + + /// Tests: Erase Page -> Write Page -> Read Page + /// + /// Compare the data we wrote is stored in flash with a + /// successive read. + fn flash_ctrl_read_write_page() { + let perf = unsafe { PERIPHERALS.unwrap() }; + let flash_ctl = &perf.flash_ctrl; + + let cb = unsafe { static_init_test!() }; + flash_ctl.set_client(cb); + cb.reset(); + + debug!("[FLASH_CTRL] Test page read/write...."); + + #[cfg(feature = "hardware_tests")] + { + let page_num: usize = 511; + run_kernel_op(100); + // Lets do a page erase + assert!(flash_ctl.erase_page(page_num).is_ok()); + run_kernel_op(100); + + // Do Page Write + let write_page = cb.write_in_page.take().unwrap(); + assert!(flash_ctl.write_page(page_num, write_page).is_ok()); + cb.write_pending.set(true); + run_kernel_op(100); + // OP Complete, buffer recovered. + assert!(!cb.write_pending.get()); + cb.reset(); + + // Read the same page + let read_page = cb.read_in_page.take().unwrap(); + assert!(flash_ctl.read_page(page_num, read_page).is_ok()); + cb.read_pending.set(true); + run_kernel_op(100); + assert!(!cb.read_pending.get()); + cb.reset(); + + // Compare r/w buffer + let write_in = cb.write_out_buf.take().unwrap(); // Recovered buffer is saved here as &[u8] + let read_out = cb.read_out_buf.take().unwrap(); + + assert_eq!(write_in.len(), read_out.len()); + assert!( + write_in.iter().zip(read_out.iter()).all(|(i, j)| i == j), + "[ERR] Read data indicates flash write error on page {}", + page_num + ); + + cb.write_out_buf.replace(write_in); + cb.read_out_buf.replace(read_out); + } + + run_kernel_op(100); + debug!(" [ok]"); + run_kernel_op(100); + } + + /// Tests: Erase Page -> Write Page -> Erase Page -> Read Page + /// A page erased should set all bits to `1`s or all bytes in page to + /// `0xFF`. Assert this is true after writing data to a page and erasing + /// the page. + fn flash_ctrl_erase_page() { + let perf = unsafe { PERIPHERALS.unwrap() }; + let flash_ctl = &perf.flash_ctrl; + + let cb = unsafe { static_init_test!() }; + cb.reset(); + flash_ctl.set_client(cb); + + debug!("[FLASH_CTRL] Test page erase...."); + + #[cfg(feature = "hardware_tests")] + { + let page_num: usize = 500; + run_kernel_op(100); + // Lets do a page erase + assert!(flash_ctl.erase_page(page_num).is_ok()); + run_kernel_op(100); + + // Do Page Write + let write_page = cb.write_in_page.take().unwrap(); + assert!(flash_ctl.write_page(page_num, write_page).is_ok()); + cb.write_pending.set(true); + run_kernel_op(100); + // OP Complete, buffer recovered. + assert!(!cb.write_pending.get()); + cb.reset(); + + // Erase again + assert!(flash_ctl.erase_page(page_num).is_ok()); + run_kernel_op(100); + + // Read Page + let read_page = cb.read_in_page.take().unwrap(); + assert!(flash_ctl.read_page(page_num, read_page).is_ok()); + cb.read_pending.set(true); + run_kernel_op(100); + assert!(!cb.read_pending.get()); + cb.reset(); + + // Check that the erased paged is all `0xFF` bytes + let read_out = cb.read_out_buf.take().unwrap(); + assert!( + read_out.iter().all(|&a| a == 0xFF), + "[ERR] Read data indicates erase failure on page {}", + page_num + ); + + cb.read_out_buf.replace(read_out); + } + run_kernel_op(100); + debug!(" [ok]"); + run_kernel_op(100); + } + + /// Tests: The basic api functionality and error handling of invalid arguments. + fn flash_ctrl_mp_basic() { + debug!("[FLASH_CTRL] Test memory protection api...."); + + #[cfg(feature = "hardware_tests")] + { + let perf = unsafe { PERIPHERALS.unwrap() }; + let flash_ctl = &perf.flash_ctrl; + // BANK1 + let base_page_addr: usize = (400 * PAGE_SIZE).saturating_add(FLASH_ADDR_OFFSET); + // Pages indexing starts with 0 and we have 512 pages. + let invalid_page_addr: usize = (512 * PAGE_SIZE).saturating_add(FLASH_ADDR_OFFSET); + let valid_num_pages: usize = 10; + // Note: Region 0 is occupied by board setup + let valid_region: usize = 6; + let invalid_region: usize = 8; + let num_regions = flash_ctl.mp_get_num_regions().unwrap(); + // WARN: Revisit these tests if cfgs have changed in HW + assert_eq!(num_regions, lowrisc::flash_ctrl::FLASH_MP_MAX_CFGS as u32); + + for region_num in 0..8 { + // All 8 regions should be unlocked at reset + assert!(flash_ctl.mp_is_region_locked(region_num).is_ok()) + } + + let cfg_set = FlashMPConfig { + read_en: false, + write_en: true, + erase_en: false, + scramble_en: false, + ecc_en: true, + he_en: false, + }; + // Expect Fail + assert_eq!( + flash_ctl.mp_set_region_perms( + base_page_addr, + invalid_page_addr, + valid_region, + &cfg_set + ), + Err(ErrorCode::NOSUPPORT) + ); + assert_eq!( + flash_ctl.mp_set_region_perms( + invalid_page_addr, + base_page_addr, + valid_region, + &cfg_set + ), + Err(ErrorCode::NOSUPPORT) + ); + assert_eq!( + flash_ctl.mp_set_region_perms( + base_page_addr, + base_page_addr.saturating_add(valid_num_pages * PAGE_SIZE), + invalid_region, + &cfg_set + ), + Err(ErrorCode::NOSUPPORT) + ); + // Set Perms + assert!(flash_ctl + .mp_set_region_perms( + base_page_addr, + base_page_addr.saturating_add(valid_num_pages * PAGE_SIZE), + valid_region, + &cfg_set + ) + .is_ok()); + // Check Perms + assert_eq!( + flash_ctl.mp_read_region_perms(valid_region).unwrap(), + cfg_set + ); + // Lock region + assert!(flash_ctl.mp_lock_region_cfg(valid_region).is_ok()); + assert!(flash_ctl.mp_is_region_locked(valid_region).unwrap()); + } + + run_kernel_op(100); + debug!(" [ok]"); + run_kernel_op(100); + } + + /// Tests the memory protection functionality of the flash_ctrl + /// Test: Setup memory protection -> Do bad OP/cause an MP Fault -> Expect fail/assert Err(FlashMPFault) + fn flash_ctrl_mp_functionality() { + debug!("[FLASH_CTRL] Test memory protection functionality...."); + + #[cfg(feature = "hardware_tests")] + { + let perf = unsafe { PERIPHERALS.unwrap() }; + let flash_ctl = &perf.flash_ctrl; + let cb = unsafe { static_init_test!() }; + cb.reset(); + flash_ctl.set_client(cb); + + // BANK1 + let page_num: usize = 450; + let base_page_addr: usize = (page_num * PAGE_SIZE).saturating_add(FLASH_ADDR_OFFSET); + let num_pages: usize = 25; + // Note: Region 0 is occupied by board setup + let region: usize = 7; + let invalid_region: usize = 142; + + for region_num in 0..8 { + // All 8 regions should be unlocked at reset + assert!(flash_ctl.mp_is_region_locked(region_num).is_ok()) + } + + let cfg_set = FlashMPConfig { + // NOTE: We disable read access, then later try to read to trigger the fault + read_en: false, + write_en: true, + // NOTE: We disable erase perms, then later try to erase and trigger the fault + erase_en: false, + scramble_en: true, + ecc_en: false, + he_en: true, + }; + // Set Perms + assert!(flash_ctl + .mp_set_region_perms( + base_page_addr, + base_page_addr.saturating_add(num_pages * PAGE_SIZE), + region, + &cfg_set + ) + .is_ok()); + // Check Perms + assert_eq!(flash_ctl.mp_read_region_perms(region).unwrap(), cfg_set); + // Lock Config - Expect Fail + assert_eq!( + flash_ctl.mp_lock_region_cfg(invalid_region), + Err(ErrorCode::NOSUPPORT) + ); + // Lock Config + assert!(flash_ctl.mp_lock_region_cfg(region).is_ok()); + assert!(flash_ctl.mp_is_region_locked(region).unwrap()); + + // Functionality Test 1: We disabled erase for this region, lets try to erase + assert!(flash_ctl.erase_page(page_num).is_ok()); + run_kernel_op(100); + // Ensure that a MP violation was detected + assert!(cb.mp_fault_detect.get()); + // Clear the fault + cb.reset(); + + // Functionality Test 2: We disabled read for this region, lets try to read + // This should trigger an MP fault + // Read Page + let read_page = cb.read_in_page.take().unwrap(); + assert!(flash_ctl.read_page(page_num, read_page).is_ok()); + cb.read_pending.set(true); + run_kernel_op(100); + assert!(!cb.read_pending.get()); + // Ensure that a MP violation was detected + assert!(cb.mp_fault_detect.get()); + cb.reset(); + } + + run_kernel_op(100); + debug!(" [ok]"); + run_kernel_op(100); + } +} diff --git a/boards/opentitan/src/tests/hmac.rs b/boards/opentitan/src/tests/hmac.rs index 1074ee6da7..f9761fde44 100644 --- a/boards/opentitan/src/tests/hmac.rs +++ b/boards/opentitan/src/tests/hmac.rs @@ -1,80 +1,116 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use crate::tests::run_kernel_op; use crate::PERIPHERALS; use core::cell::Cell; -use kernel::hil::digest::{self, Digest, DigestData, DigestVerify, HMACSha256}; -use kernel::utilities::leasable_buffer::LeasableBuffer; +#[allow(unused_imports)] // Can be unused if software only test +use kernel::hil::digest::DigestData; +use kernel::hil::digest::{self, Digest, DigestVerify, HmacSha256}; +use kernel::static_init; +use kernel::utilities::cells::TakeCell; +use kernel::utilities::leasable_buffer::SubSlice; +use kernel::utilities::leasable_buffer::SubSliceMut; use kernel::{debug, ErrorCode}; static KEY: [u8; 32] = [0xA1; 32]; -static mut INPUT: [u8; 32] = [32; 32]; -static mut DIGEST: [u8; 32] = [ - 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, - 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, -]; struct HmacTestCallback { - add_data_done: Cell, + add_mut_data_done: Cell, verification_done: Cell, + input_buffer: TakeCell<'static, [u8]>, + digest_buffer: TakeCell<'static, [u8; 32]>, } unsafe impl Sync for HmacTestCallback {} impl<'a> HmacTestCallback { - const fn new() -> Self { + fn new(input_buffer: &'static mut [u8], digest_buffer: &'static mut [u8; 32]) -> Self { HmacTestCallback { - add_data_done: Cell::new(false), + add_mut_data_done: Cell::new(false), verification_done: Cell::new(false), + input_buffer: TakeCell::new(input_buffer), + digest_buffer: TakeCell::new(digest_buffer), } } fn reset(&self) { - self.add_data_done.set(false); + self.add_mut_data_done.set(false); self.verification_done.set(false); } } -impl<'a> digest::ClientData<'a, 32> for HmacTestCallback { - fn add_data_done(&'a self, result: Result<(), ErrorCode>, _data: &'static mut [u8]) { - self.add_data_done.set(true); +impl<'a> digest::ClientData<32> for HmacTestCallback { + fn add_mut_data_done(&self, result: Result<(), ErrorCode>, data: SubSliceMut<'static, u8>) { + self.add_mut_data_done.set(true); + // Check that all of the data was accepted and the active slice is length 0 + assert_eq!(data.len(), 0); + // Input data has been loaded, hold copy of data + self.input_buffer.replace(data.take()); assert_eq!(result, Ok(())); } + + fn add_data_done(&self, _result: Result<(), ErrorCode>, _data: SubSlice<'static, u8>) { + unimplemented!() + } } -impl<'a> digest::ClientHash<'a, 32> for HmacTestCallback { - fn hash_done(&'a self, _result: Result<(), ErrorCode>, _digest: &'static mut [u8; 32]) { +impl<'a> digest::ClientHash<32> for HmacTestCallback { + fn hash_done(&self, _result: Result<(), ErrorCode>, _digest: &'static mut [u8; 32]) { unimplemented!() } } -impl<'a> digest::ClientVerify<'a, 32> for HmacTestCallback { - fn verification_done( - &'a self, - result: Result, - _compare: &'static mut [u8; 32], - ) { +impl<'a> digest::ClientVerify<32> for HmacTestCallback { + fn verification_done(&self, result: Result, compare: &'static mut [u8; 32]) { + self.digest_buffer.replace(compare); self.verification_done.set(true); assert_eq!(result, Ok(true)); } } -static CALLBACK: HmacTestCallback = HmacTestCallback::new(); +/// Static init an HmacTestCallback, with +/// respective buffers allocated for data fields. +macro_rules! static_init_test_cb { + () => {{ + let input_data = static_init!([u8; 32], [32; 32]); + let digest_data = static_init!( + [u8; 32], + [ + 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, + 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, + 0xaf, 0x58, 0x57, 0x40, + ] + ); + + static_init!( + HmacTestCallback, + HmacTestCallback::new(input_data, digest_data) + ) + }}; +} #[test_case] fn hmac_check_load_binary() { let perf = unsafe { PERIPHERALS.unwrap() }; let hmac = &perf.hmac; - let buf = unsafe { LeasableBuffer::new(&mut INPUT) }; + + let callback = unsafe { static_init_test_cb!() }; + let _buf = SubSliceMut::new(callback.input_buffer.take().unwrap()); debug!("check hmac load binary... "); run_kernel_op(100); - hmac.set_client(&CALLBACK); - CALLBACK.reset(); - assert_eq!(hmac.add_data(buf), Ok(32)); + hmac.set_client(callback); + callback.reset(); + + #[cfg(feature = "hardware_tests")] + assert_eq!(hmac.add_mut_data(_buf), Ok(())); run_kernel_op(1000); #[cfg(feature = "hardware_tests")] - assert_eq!(CALLBACK.add_data_done.get(), true); + assert_eq!(callback.add_mut_data_done.get(), true); run_kernel_op(100); debug!(" [ok]"); @@ -85,28 +121,32 @@ fn hmac_check_load_binary() { fn hmac_check_verify() { let perf = unsafe { PERIPHERALS.unwrap() }; let hmac = &perf.hmac; - let buf = unsafe { LeasableBuffer::new(&mut INPUT) }; + + let callback = unsafe { static_init_test_cb!() }; + + let _buf = SubSliceMut::new(callback.input_buffer.take().unwrap()); debug!("check hmac check verify... "); run_kernel_op(100); - hmac.set_client(&CALLBACK); - CALLBACK.reset(); - hmac.set_mode_hmacsha256(&KEY).unwrap(); - assert_eq!(hmac.add_data(buf), Ok(32)); + hmac.set_client(callback); + callback.reset(); + assert_eq!(hmac.set_mode_hmacsha256(&KEY), Ok(())); + + #[cfg(feature = "hardware_tests")] + assert_eq!(hmac.add_mut_data(_buf), Ok(())); run_kernel_op(1000); #[cfg(feature = "hardware_tests")] - assert_eq!(CALLBACK.add_data_done.get(), true); - CALLBACK.reset(); + assert_eq!(callback.add_mut_data_done.get(), true); + callback.reset(); - unsafe { - assert_eq!(hmac.verify(&mut DIGEST), Ok(())); - } + /* Get digest from callback digest buffer */ + assert_eq!(hmac.verify(callback.digest_buffer.take().unwrap()), Ok(())); run_kernel_op(1000); #[cfg(feature = "hardware_tests")] - assert_eq!(CALLBACK.verification_done.get(), true); + assert_eq!(callback.verification_done.get(), true); run_kernel_op(100); debug!(" [ok]"); diff --git a/boards/opentitan/src/tests/mod.rs b/boards/opentitan/src/tests/mod.rs index 1a865224d0..9e47cab984 100644 --- a/boards/opentitan/src/tests/mod.rs +++ b/boards/opentitan/src/tests/mod.rs @@ -1,11 +1,16 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use crate::BOARD; use crate::CHIP; use crate::MAIN_CAP; -use crate::NUM_PROCS; use crate::PLATFORM; use kernel::debug; pub fn semihost_command_exit_success() -> ! { + run_kernel_op(10000); + // Exit QEMU with a return code of 0 unsafe { rv32i::semihost_command(0x18, 0x20026, 0); @@ -14,6 +19,8 @@ pub fn semihost_command_exit_success() -> ! { } pub fn semihost_command_exit_failure() -> ! { + run_kernel_op(10000); + // Exit QEMU with a return code of 1 unsafe { rv32i::semihost_command(0x18, 1, 0); @@ -27,7 +34,7 @@ fn run_kernel_op(loops: usize) { BOARD.unwrap().kernel_loop_operation( PLATFORM.unwrap(), CHIP.unwrap(), - None::<&kernel::ipc::IPC>, + None::<&kernel::ipc::IPC<0>>, true, MAIN_CAP.unwrap(), ); @@ -48,7 +55,12 @@ fn trivial_assertion() { mod aes_test; mod csrng; +mod flash; mod hmac; mod multi_alarm; mod otbn; -mod tickv_test; +mod rsa; +mod rsa_4096; +mod sha256soft_test; // Test software SHA capsule +mod sip_hash; +mod spi_host; diff --git a/boards/opentitan/src/tests/multi_alarm.rs b/boards/opentitan/src/tests/multi_alarm.rs index 62c28d1ee6..ff187a4dcc 100644 --- a/boards/opentitan/src/tests/multi_alarm.rs +++ b/boards/opentitan/src/tests/multi_alarm.rs @@ -1,10 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test the behavior of a single alarm. //! To add this test, include the line //! ``` //! multi_alarm_test::run_alarm(alarm_mux); //! ``` //! to the OpenTitan boot sequence, where `alarm_mux` is a -//! `capsules::virtual_alarm::MuxAlarm`. The test sets up 3 +//! `capsules_core::virtualizers::virtual_alarm::MuxAlarm`. The test sets up 3 //! different virtualized alarms of random durations and prints //! out when they fire. The durations are uniformly random with //! one caveat, that 1 in 11 is of duration 0; this is to test @@ -13,15 +17,18 @@ use crate::tests::run_kernel_op; use crate::ALARM; -use capsules::test::random_alarm::TestRandomAlarm; -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use capsules_core::test::random_alarm::TestRandomAlarm; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; use earlgrey::timer::RvTimer; use kernel::debug; use kernel::hil::time::Alarm; use kernel::static_init; static mut TESTS: Option< - [&'static TestRandomAlarm<'static, VirtualMuxAlarm<'static, RvTimer<'static>>>; 3], + [&'static TestRandomAlarm< + 'static, + VirtualMuxAlarm<'static, RvTimer<'static, crate::ChipConfig>>, + >; 3], > = None; #[test_case] @@ -47,40 +54,43 @@ pub fn run_multi_alarm() { } unsafe fn static_init_multi_alarm_test( - mux: &'static MuxAlarm<'static, RvTimer<'static>>, -) -> [&'static TestRandomAlarm<'static, VirtualMuxAlarm<'static, RvTimer<'static>>>; 3] { + mux: &'static MuxAlarm<'static, RvTimer<'static, crate::ChipConfig>>, +) -> [&'static TestRandomAlarm< + 'static, + VirtualMuxAlarm<'static, RvTimer<'static, crate::ChipConfig>>, +>; 3] { let virtual_alarm1 = static_init!( - VirtualMuxAlarm<'static, RvTimer<'static>>, + VirtualMuxAlarm<'static, RvTimer<'static, crate::ChipConfig>>, VirtualMuxAlarm::new(mux) ); virtual_alarm1.setup(); let test1 = static_init!( - TestRandomAlarm<'static, VirtualMuxAlarm<'static, RvTimer<'static>>>, + TestRandomAlarm<'static, VirtualMuxAlarm<'static, RvTimer<'static, crate::ChipConfig>>>, TestRandomAlarm::new(virtual_alarm1, 19, 'A', false) ); virtual_alarm1.set_alarm_client(test1); let virtual_alarm2 = static_init!( - VirtualMuxAlarm<'static, RvTimer<'static>>, + VirtualMuxAlarm<'static, RvTimer<'static, crate::ChipConfig>>, VirtualMuxAlarm::new(mux) ); virtual_alarm2.setup(); let test2 = static_init!( - TestRandomAlarm<'static, VirtualMuxAlarm<'static, RvTimer<'static>>>, + TestRandomAlarm<'static, VirtualMuxAlarm<'static, RvTimer<'static, crate::ChipConfig>>>, TestRandomAlarm::new(virtual_alarm2, 37, 'B', false) ); virtual_alarm2.set_alarm_client(test2); let virtual_alarm3 = static_init!( - VirtualMuxAlarm<'static, RvTimer<'static>>, + VirtualMuxAlarm<'static, RvTimer<'static, crate::ChipConfig>>, VirtualMuxAlarm::new(mux) ); virtual_alarm3.setup(); let test3 = static_init!( - TestRandomAlarm<'static, VirtualMuxAlarm<'static, RvTimer<'static>>>, + TestRandomAlarm<'static, VirtualMuxAlarm<'static, RvTimer<'static, crate::ChipConfig>>>, TestRandomAlarm::new(virtual_alarm3, 89, 'C', false) ); virtual_alarm3.set_alarm_client(test3); diff --git a/boards/opentitan/src/tests/otbn.rs b/boards/opentitan/src/tests/otbn.rs index 4cf6ef5062..380fb266b7 100644 --- a/boards/opentitan/src/tests/otbn.rs +++ b/boards/opentitan/src/tests/otbn.rs @@ -1,12 +1,15 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use crate::tests::run_kernel_op; use crate::PERIPHERALS; use core::cell::Cell; +use kernel::static_init; +use kernel::utilities::cells::TakeCell; use kernel::{debug, ErrorCode}; use lowrisc::otbn::Client; -static mut OUTPUT: [u8; 1024] = [0; 1024]; -static mut TEMP: [u8; 4] = [0; 4]; - static MODULUS: [u8; 256] = [ 0xf9, 0x90, 0xc7, 0x94, 0xcf, 0x96, 0xd3, 0x12, 0x6f, 0x16, 0xa6, 0x50, 0x5d, 0xcb, 0xe9, 0x29, 0x53, 0xc8, 0x44, 0x04, 0xda, 0x69, 0x2d, 0x1a, 0xc1, 0xb8, 0xa8, 0x70, 0x97, 0xb5, 0x96, 0xd8, @@ -47,14 +50,16 @@ static EXPECTING: [u8; 256] = [ struct OtbnTestCallback { op_done: Cell, + output_buf: TakeCell<'static, [u8]>, } unsafe impl Sync for OtbnTestCallback {} impl<'a> OtbnTestCallback { - const fn new() -> Self { + fn new(output_buf: &'static mut [u8]) -> Self { OtbnTestCallback { op_done: Cell::new(false), + output_buf: TakeCell::new(output_buf), } } @@ -68,12 +73,12 @@ impl<'a> Client<'a> for OtbnTestCallback { self.op_done.set(true); assert_eq!(result, Ok(())); assert_eq!(output[0..256], EXPECTING); + // Keep copy of output + self.output_buf.replace(output); } } -static CALLBACK: OtbnTestCallback = OtbnTestCallback::new(); - -/// These symbols are defined in the linker script. +// These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -81,79 +86,91 @@ extern "C" { static _eapps: u8; } +/// Static init an OtbnTestCallback, with +/// a respective buffer allocated. +/// Note: static_init!() returns an &'static mut reference. +unsafe fn static_init_test_cb() -> &'static OtbnTestCallback { + let output_buf = static_init!([u8; 256], [0; 256]); + + static_init!(OtbnTestCallback, OtbnTestCallback::new(output_buf)) +} + #[test_case] fn otbn_run_rsa_binary() { let perf = unsafe { PERIPHERALS.unwrap() }; let otbn = &perf.otbn; + let cb = unsafe { static_init_test_cb() }; + let output = cb.output_buf.take().unwrap(); + let mut temp: [u8; 4] = [0; 4]; + + debug!("check otbn run binary..."); if let Ok((imem_start, imem_length, dmem_start, dmem_length)) = unsafe { crate::otbn::find_app( "otbn-rsa", core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), ) } { let slice = unsafe { core::slice::from_raw_parts(imem_start as *const u8, imem_length) }; - debug!("check otbn run binary..."); + debug!("check otbn run rsa binary..."); run_kernel_op(100); - CALLBACK.reset(); - otbn.set_client(&CALLBACK); + cb.reset(); + otbn.set_client(cb); assert_eq!(otbn.load_binary(slice), Ok(())); run_kernel_op(1000); let slice = unsafe { core::slice::from_raw_parts(dmem_start as *const u8, dmem_length) }; - CALLBACK.reset(); + cb.reset(); assert_eq!(otbn.load_data(0, slice), Ok(())); run_kernel_op(1000); // Set the RSA mode to encryption // The address is the offset of `mode` in the RSA elf - unsafe { - TEMP[0] = 1; - assert_eq!(otbn.load_data(0, &TEMP[0..4]), Ok(())); - } + temp[0] = 1; + assert_eq!(otbn.load_data(0, &temp), Ok(())); + run_kernel_op(1000); // Set the RSA length // The address is the offset of `n_limbs` in the RSA elf - unsafe { - TEMP[0] = (MODULUS.len() / 32) as u8; - assert_eq!(otbn.load_data(4, &TEMP[0..4]), Ok(())); - } + temp[0] = (MODULUS.len() / 32) as u8; + assert_eq!(otbn.load_data(4, &temp), Ok(())); + run_kernel_op(1000); // Set the RSA modulus // The address is the offset of `modulus` in the RSA elf - assert_eq!(otbn.load_data(0x420, &MODULUS), Ok(())); + assert_eq!(otbn.load_data(0x20, &MODULUS), Ok(())); run_kernel_op(1000); // Set the data in - // The address is the offset of `in` in the RSA elf + // The address is the offset of `inout` in the RSA elf let mut source: [u8; 256] = [0; 256]; source[0..14].copy_from_slice(b"OTBN is great!"); - assert_eq!(otbn.load_data(0x820, &source), Ok(())); + assert_eq!(otbn.load_data(0x420, &source), Ok(())); run_kernel_op(1000); - CALLBACK.reset(); - // The address is the offset of `out` in the RSA elf - assert_eq!(unsafe { otbn.run(0x288, &mut OUTPUT) }, Ok(())); + cb.reset(); + // The address is the offset of `inout` in the RSA elf + assert_eq!(otbn.run(0x420, output), Ok(())); run_kernel_op(10000); #[cfg(feature = "hardware_tests")] - assert_eq!(CALLBACK.op_done.get(), true); + assert_eq!(cb.op_done.get(), true); debug!(" [ok]"); run_kernel_op(100); } else { - debug!(" [FAIL]"); + debug!(" [FAIL] No OTBN binary"); run_kernel_op(100); } } diff --git a/boards/opentitan/src/tests/rsa.rs b/boards/opentitan/src/tests/rsa.rs new file mode 100644 index 0000000000..c8b0996aca --- /dev/null +++ b/boards/opentitan/src/tests/rsa.rs @@ -0,0 +1,245 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::tests::run_kernel_op; +use crate::PERIPHERALS; +use crate::RSA_HARDWARE; +use capsules_extra::public_key_crypto::rsa_keys::RSA2048Keys; +use core::cell::Cell; +use kernel::hil::public_key_crypto::keys::{PubKey, PubPrivKey, RsaKey, RsaPrivKey}; +use kernel::hil::public_key_crypto::rsa_math::{Client, RsaCryptoBase}; +use kernel::static_init; +use kernel::{debug, ErrorCode}; + +static mut SOURCE: [u8; 64] = [0x23; 64]; +static mut DEST: [u8; 256] = [0x56; 256]; +static PUB_KEY: [u8; 256] = [ + // Modulus + 0xc9, 0x03, 0x2e, 0x93, 0x05, 0x1c, 0xe8, 0x6b, 0x0f, 0x41, 0x5c, 0x7e, 0x2d, 0x1e, 0x3b, 0xee, + 0x9a, 0x37, 0xfe, 0x6b, 0x1b, 0xa5, 0x9f, 0x8b, 0x51, 0x69, 0x10, 0x79, 0x1d, 0xd1, 0x2c, 0x22, + 0xf1, 0x88, 0xed, 0xf4, 0xda, 0x1c, 0x8d, 0xa0, 0xd0, 0x5e, 0x29, 0xf3, 0x36, 0x78, 0xef, 0x07, + 0x43, 0xc5, 0xd6, 0xf5, 0x8a, 0x2a, 0x69, 0x70, 0x7d, 0x21, 0x45, 0x00, 0x5b, 0x13, 0x2b, 0x8e, + 0x7a, 0x7a, 0xaf, 0xe3, 0x97, 0x26, 0x54, 0x49, 0x34, 0x90, 0x69, 0x89, 0xaf, 0xc7, 0xc2, 0xa7, + 0x2a, 0x31, 0xc3, 0x78, 0x8b, 0x6d, 0x8a, 0x4c, 0xa9, 0xea, 0xe7, 0xc4, 0x2e, 0xa4, 0xc2, 0xce, + 0x4b, 0x98, 0xae, 0xa9, 0x73, 0xf2, 0x60, 0xde, 0xcb, 0x47, 0x9c, 0x22, 0x24, 0xe7, 0x5a, 0x95, + 0x6b, 0x61, 0xd9, 0x15, 0x41, 0xb4, 0x0f, 0x27, 0x77, 0x0b, 0x7c, 0xff, 0x29, 0xc1, 0xff, 0x86, + 0xae, 0x28, 0xfd, 0x33, 0x9f, 0x7e, 0xac, 0xfc, 0x39, 0x08, 0x72, 0x28, 0x62, 0x5d, 0xc1, 0x21, + 0x27, 0xa1, 0xbb, 0x2a, 0x38, 0xe5, 0x17, 0x74, 0xbc, 0x1e, 0x76, 0x3f, 0x1c, 0xa7, 0xa8, 0x57, + 0x81, 0x3c, 0x60, 0x56, 0xed, 0xe3, 0xa9, 0x7f, 0xb4, 0x3d, 0xfb, 0xbf, 0x4f, 0x38, 0x58, 0x3d, + 0x1b, 0x23, 0x6f, 0x39, 0xde, 0x5c, 0xc3, 0xdb, 0x47, 0x33, 0x4d, 0x7d, 0xa4, 0xf8, 0xce, 0xeb, + 0xc1, 0x4a, 0x6a, 0xe2, 0xe8, 0x5f, 0xac, 0xe5, 0x19, 0x09, 0xc7, 0xe4, 0x8d, 0xd3, 0xca, 0x66, + 0xca, 0xe9, 0x76, 0x4c, 0x75, 0x1d, 0x37, 0xf2, 0xc6, 0xe5, 0x74, 0x1f, 0xee, 0x5a, 0x0b, 0x8a, + 0x67, 0x90, 0xe6, 0x5e, 0x6a, 0x77, 0xe4, 0x36, 0xd7, 0x10, 0x40, 0x74, 0xa6, 0xfb, 0xf9, 0xfc, + 0xdb, 0x73, 0x8e, 0x7a, 0x32, 0x2d, 0xf6, 0xbc, 0xb2, 0x08, 0xdb, 0x1e, 0x3c, 0x01, 0xde, 0x4d, +]; +static PRIV_KEY: [u8; 256] = [ + // Private Exponent + 0xbb, 0xbb, 0x4c, 0x09, 0x24, 0xf2, 0x4d, 0xa2, 0x87, 0x39, 0xdc, 0xff, 0x3e, 0x76, 0x09, 0x35, + 0x1b, 0x35, 0x06, 0x58, 0xd4, 0x16, 0x47, 0xbe, 0x1e, 0xc7, 0x48, 0x0a, 0x45, 0xad, 0xb0, 0x51, + 0xe6, 0x50, 0xa3, 0x24, 0x55, 0x7b, 0xeb, 0x4d, 0xf0, 0xac, 0xb0, 0xf3, 0x23, 0xc1, 0xa8, 0x43, + 0x99, 0xa9, 0x30, 0xcc, 0x5b, 0x40, 0xa6, 0xfe, 0xd6, 0xee, 0x76, 0x7a, 0x11, 0x95, 0x02, 0xcd, + 0xeb, 0x57, 0x9f, 0xe3, 0xa7, 0xab, 0xb5, 0x76, 0x35, 0x30, 0x56, 0x50, 0xb0, 0x29, 0x99, 0x82, + 0xf3, 0xe6, 0x4c, 0x0f, 0xcd, 0xef, 0xfd, 0x05, 0x02, 0x80, 0x8f, 0xfa, 0x6a, 0x31, 0x98, 0x7a, + 0x80, 0xa1, 0xd5, 0x26, 0x0f, 0x52, 0xa3, 0xe4, 0x0f, 0xe8, 0x0e, 0x4a, 0xd9, 0x3a, 0x75, 0x20, + 0x2d, 0x8c, 0xd1, 0xe8, 0x87, 0x57, 0x79, 0xfb, 0xba, 0xb3, 0xb1, 0x06, 0xc1, 0xe2, 0x1c, 0x18, + 0xeb, 0xc9, 0xd2, 0x8b, 0xa2, 0xf7, 0xc4, 0xf1, 0x1c, 0x8b, 0x2c, 0xd4, 0x2d, 0x55, 0xa9, 0x1b, + 0xee, 0xe3, 0x9f, 0x6f, 0xa0, 0x42, 0xa5, 0x09, 0x2f, 0x22, 0x93, 0x35, 0x16, 0xd5, 0xa2, 0x4d, + 0x1d, 0x1f, 0x54, 0x85, 0xd9, 0xdf, 0xbd, 0x74, 0x3c, 0x84, 0x59, 0x9a, 0xa4, 0x56, 0xdb, 0xb9, + 0x92, 0x89, 0x82, 0x89, 0x0f, 0xe0, 0x84, 0xc5, 0xdc, 0xca, 0x31, 0xd7, 0xc8, 0x06, 0xdf, 0x68, + 0xff, 0x14, 0xb1, 0x65, 0x7d, 0x1b, 0xb4, 0xa2, 0x7e, 0xd5, 0x06, 0xc0, 0x72, 0x2e, 0xbb, 0xc9, + 0x87, 0xd8, 0xb2, 0xa6, 0x82, 0xdf, 0x59, 0xdb, 0xc7, 0x79, 0x29, 0xff, 0xe2, 0xd2, 0x9f, 0x65, + 0x42, 0x83, 0x78, 0x53, 0xda, 0x18, 0x63, 0xf4, 0xdd, 0xdd, 0x4a, 0xf9, 0xc3, 0x02, 0x35, 0x64, + 0x5f, 0x37, 0xe8, 0x5c, 0xe4, 0xfb, 0x95, 0xec, 0xdd, 0x48, 0x37, 0x76, 0xc5, 0xad, 0x9c, 0xa5, +]; + +static EXPECTING: [u8; 256] = [ + 0x13, 0x59, 0x55, 0x8b, 0x88, 0x7e, 0x9d, 0xc8, 0x4c, 0xfc, 0x8f, 0x4c, 0x0e, 0xa0, 0x20, 0xae, + 0x3e, 0x9d, 0x73, 0x0f, 0x89, 0x3c, 0xc4, 0x55, 0xa4, 0xe5, 0xaa, 0x8f, 0x61, 0x74, 0xd9, 0xae, + 0x23, 0xcf, 0xc6, 0xd8, 0xca, 0x00, 0x17, 0x6d, 0x43, 0x3e, 0x18, 0x30, 0xb9, 0x6f, 0xe4, 0x8f, + 0x30, 0x4f, 0x5c, 0xfa, 0xda, 0xc2, 0xe5, 0xa4, 0xa7, 0x28, 0x51, 0x41, 0xbd, 0x35, 0x91, 0x07, + 0x39, 0xb6, 0x89, 0x9d, 0xce, 0xdd, 0x43, 0x0a, 0x4a, 0xb4, 0xfb, 0x2a, 0x63, 0x3b, 0xb5, 0xfd, + 0x97, 0xcf, 0xfb, 0xd2, 0xd6, 0xb9, 0x74, 0x2b, 0x13, 0x7d, 0xb7, 0x43, 0x6b, 0x5c, 0x57, 0x3f, + 0xcb, 0x6c, 0xe0, 0x13, 0x37, 0x64, 0xa9, 0xd7, 0x7d, 0xe7, 0x8d, 0x04, 0x58, 0x36, 0xd1, 0x98, + 0x26, 0xd1, 0xb8, 0x3a, 0x23, 0xa2, 0xe3, 0x29, 0x49, 0x1f, 0x22, 0xa7, 0xd3, 0x23, 0x0c, 0x2a, + 0x5f, 0xb1, 0x47, 0xbc, 0xce, 0xc1, 0x0e, 0x8d, 0x6f, 0xb3, 0xe3, 0x63, 0x02, 0x1d, 0x9d, 0x9f, + 0xbe, 0xc9, 0x9a, 0xbb, 0xfa, 0xe0, 0x91, 0x67, 0x63, 0x10, 0x8b, 0xb9, 0x9b, 0x29, 0x3d, 0x48, + 0xff, 0xa1, 0x1d, 0xc1, 0xca, 0x19, 0x49, 0xf6, 0x7d, 0xdd, 0xdb, 0xfc, 0x26, 0xbe, 0xf3, 0x38, + 0xfe, 0x41, 0xd2, 0xf8, 0x72, 0x67, 0x73, 0x83, 0x38, 0xa5, 0x67, 0xca, 0x54, 0x20, 0xa2, 0x2a, + 0x30, 0x00, 0x2e, 0x51, 0xeb, 0x40, 0x09, 0x3f, 0xe5, 0x03, 0x8a, 0x26, 0xd0, 0x13, 0xf2, 0x39, + 0xd0, 0x06, 0x8f, 0x63, 0xcf, 0x1e, 0xe7, 0x57, 0x18, 0xb7, 0x06, 0xfe, 0x1b, 0x0a, 0xa3, 0xd0, + 0xb3, 0x02, 0xc4, 0x0f, 0x88, 0x37, 0xbc, 0xf9, 0x38, 0x9a, 0x4b, 0x01, 0x2f, 0x51, 0x34, 0x5b, + 0x17, 0x9b, 0xc5, 0x1a, 0x44, 0x79, 0xd1, 0x3f, 0x65, 0xd6, 0x21, 0x7c, 0xf3, 0x69, 0xe7, 0x6a, +]; + +struct RsaTestCallback { + mod_exp_done: Cell, + run: Cell, +} + +unsafe impl Sync for RsaTestCallback {} + +impl<'a> RsaTestCallback { + const fn new() -> Self { + RsaTestCallback { + mod_exp_done: Cell::new(false), + run: Cell::new(0), + } + } + + fn reset(&self) { + self.mod_exp_done.set(false); + } +} + +impl<'a> Client<'a> for RsaTestCallback { + fn mod_exponent_done( + &'a self, + status: Result, + _message: &'static mut [u8], + _modulus: &'static [u8], + _exponent: &'static [u8], + result: &'static mut [u8], + ) { + assert_eq!(status, Ok(true)); + + if self.run.get() == 0 { + assert_eq!(result, EXPECTING); + } + + self.run.set(self.run.get() + 1); + self.mod_exp_done.set(true); + } +} + +static CALLBACK: RsaTestCallback = RsaTestCallback::new(); + +#[test_case] +fn rsa_check_exponent() { + let perf = unsafe { PERIPHERALS.unwrap() }; + let otbn = &perf.otbn; + if let Some(rsa) = unsafe { RSA_HARDWARE } { + let key = unsafe { static_init!(RSA2048Keys, RSA2048Keys::new()) }; + + debug!("check rsa exponent... "); + run_kernel_op(100); + + // Possibly overridden by other tests + otbn.set_client(rsa); + rsa.set_client(&CALLBACK); + + if let Err(e) = key.import_public_key(&PUB_KEY) { + panic!("Failed to import public key: {:?}", e.0); + } + if let Err(e) = key.import_private_key(&PRIV_KEY) { + panic!("Failed to import private key: {:?}", e.0); + } + + CALLBACK.reset(); + unsafe { + match rsa.mod_exponent( + &mut SOURCE, + key.take_modulus().unwrap(), + key.take_exponent().unwrap(), + &mut DEST, + ) { + Ok(_) => {} + Err(_) => panic!("exponent failed"), + } + } + + run_kernel_op(120000); + assert_eq!(CALLBACK.mod_exp_done.get(), true); + unsafe { + assert_eq!(DEST, EXPECTING); + } + + debug!(" [ok]"); + run_kernel_op(100); + } else { + debug!("Not running RSA tests"); + } +} + +static mut PUB_EXPONENT: [u8; 4] = [0x0; 4]; + +#[test_case] +fn rsa_check_exponent_one() { + let perf = unsafe { PERIPHERALS.unwrap() }; + let otbn = &perf.otbn; + if let Some(rsa) = unsafe { RSA_HARDWARE } { + let key = unsafe { static_init!(RSA2048Keys, RSA2048Keys::new()) }; + + debug!("check rsa exponent one... "); + run_kernel_op(100); + + // Possibly overridden by other tests + otbn.set_client(rsa); + rsa.set_client(&CALLBACK); + + if let Err(e) = key.import_public_key(&PUB_KEY) { + panic!("Failed to import public key: {:?}", e.0); + } + if let Err(e) = key.import_private_key(&PRIV_KEY) { + panic!("Failed to import private key: {:?}", e.0); + } + + CALLBACK.reset(); + unsafe { + PUB_EXPONENT.copy_from_slice(&key.public_exponent().unwrap().to_be_bytes()); + match rsa.mod_exponent( + &mut SOURCE, + key.take_modulus().unwrap(), + &mut PUB_EXPONENT, + &mut DEST, + ) { + Ok(_) => {} + Err(e) => panic!("exponent failed: {:?}", e), + } + } + + run_kernel_op(120000); + assert_eq!(CALLBACK.mod_exp_done.get(), true); + + debug!(" [ok]"); + run_kernel_op(100); + } else { + debug!("Not running RSA tests"); + } +} + +#[test_case] +fn rsa_import_key() { + let key = unsafe { static_init!(RSA2048Keys, RSA2048Keys::new()) }; + + debug!("check rsa key import... "); + run_kernel_op(100); + + if let Err(e) = key.import_public_key(&PUB_KEY) { + panic!("Failed to import public key: {:?}", e.0); + } + if let Err(e) = key.import_private_key(&PRIV_KEY) { + panic!("Failed to import private key: {:?}", e.0); + } + + run_kernel_op(1000); + + assert_eq!( + key.map_modulus(&|modulus| { + assert_eq!(modulus, PUB_KEY); + }), + Some(()) + ); + + assert_eq!( + key.map_exponent(&|exponent| { + assert_eq!(exponent, PRIV_KEY); + }), + Some(()) + ); + + assert_eq!(key.public_exponent(), Some(0x10001)); + + debug!(" [ok]"); + run_kernel_op(100); +} diff --git a/boards/opentitan/src/tests/rsa_4096.rs b/boards/opentitan/src/tests/rsa_4096.rs new file mode 100644 index 0000000000..9b13af1532 --- /dev/null +++ b/boards/opentitan/src/tests/rsa_4096.rs @@ -0,0 +1,246 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::tests::run_kernel_op; +use crate::PERIPHERALS; +use crate::RSA_HARDWARE; +use capsules_extra::public_key_crypto::rsa_keys::RSA4096Keys; +use core::cell::Cell; +use kernel::hil::public_key_crypto::keys::{PubKey, PubPrivKey, RsaKey, RsaPrivKey}; +use kernel::hil::public_key_crypto::rsa_math::{Client, RsaCryptoBase}; +use kernel::static_init; +use kernel::{debug, ErrorCode}; + +static mut SOURCE: [u8; 64] = [0x23; 64]; +static mut DEST: [u8; 512] = [0x56; 512]; +static PUB_KEY: [u8; 512] = [ + // Modulus + 0x95, 0x5a, 0x06, 0xf3, 0x64, 0x65, 0x1c, 0x41, 0xc5, 0x41, 0xa3, 0x6e, 0xf4, 0xcb, 0xe9, 0x67, + 0x9f, 0xb0, 0x6e, 0x65, 0xf2, 0xe2, 0x31, 0xea, 0x21, 0x2e, 0xd1, 0xb1, 0x84, 0x82, 0xb9, 0x13, + 0x3b, 0x48, 0x0c, 0xf4, 0xc6, 0x72, 0xf3, 0xc8, 0x25, 0x8e, 0xc7, 0x6a, 0x91, 0xf8, 0x3a, 0x8f, + 0x23, 0xb4, 0x6c, 0x84, 0x9c, 0xac, 0xa0, 0xa1, 0x7b, 0x2e, 0xfb, 0xd6, 0x22, 0x99, 0x4c, 0x24, + 0xed, 0x46, 0x60, 0x67, 0xd7, 0xa4, 0x4c, 0xf1, 0xa0, 0x4e, 0x3a, 0x66, 0x9e, 0x47, 0xb2, 0x4d, + 0x69, 0x8a, 0x76, 0x56, 0x69, 0x2c, 0x2b, 0x45, 0xb6, 0x24, 0x2f, 0x30, 0x29, 0x22, 0xb8, 0xb8, + 0x35, 0xb4, 0xd1, 0x81, 0xff, 0x2a, 0xbe, 0x95, 0xe3, 0x35, 0x76, 0xd7, 0x45, 0x67, 0x99, 0xae, + 0x37, 0x28, 0x75, 0xfc, 0x1a, 0xcb, 0xb7, 0x67, 0x6d, 0x63, 0x7b, 0x8f, 0x12, 0x74, 0x26, 0x0a, + 0x32, 0x6c, 0x10, 0x4f, 0x50, 0x04, 0xf9, 0x84, 0xf1, 0x66, 0x99, 0x20, 0x1d, 0x4a, 0x10, 0x5e, + 0xce, 0xa6, 0xd6, 0xf4, 0xaa, 0xb6, 0x2f, 0xe5, 0xe6, 0xa7, 0xdb, 0xed, 0x46, 0xc6, 0x36, 0x2d, + 0xca, 0x03, 0xb2, 0xb7, 0x6e, 0x20, 0xa3, 0x1d, 0x80, 0x89, 0xa6, 0xc0, 0x31, 0x95, 0xf4, 0xa6, + 0x18, 0x68, 0x68, 0xaa, 0x47, 0xcd, 0xaa, 0x09, 0xa9, 0x22, 0xde, 0x61, 0xa5, 0xf9, 0x99, 0x0b, + 0x50, 0x0e, 0xa2, 0x0d, 0xda, 0x4d, 0x14, 0xcc, 0xec, 0xb1, 0xff, 0xfd, 0x85, 0xad, 0xe7, 0x97, + 0xbf, 0x12, 0xf8, 0xad, 0xbe, 0x21, 0xed, 0xbb, 0xa6, 0xf8, 0x48, 0xe3, 0xe2, 0x7c, 0x69, 0xff, + 0xf7, 0x7e, 0x8f, 0x86, 0x04, 0x7b, 0x6b, 0x91, 0x6c, 0xa4, 0x69, 0xa0, 0x7b, 0x15, 0xf9, 0x9c, + 0x5c, 0x13, 0x3c, 0x69, 0x82, 0x10, 0x4d, 0x0f, 0x85, 0xac, 0xd5, 0xe5, 0x37, 0x80, 0xee, 0xcb, + 0xa5, 0x76, 0x18, 0x14, 0x72, 0x99, 0x08, 0xb7, 0x91, 0x4b, 0x76, 0xc5, 0x93, 0xd1, 0xf5, 0x9c, + 0x6b, 0x29, 0x5d, 0xec, 0x0c, 0xd8, 0xf7, 0xcc, 0xd2, 0xe4, 0xad, 0xd7, 0x4c, 0x95, 0x29, 0x84, + 0x10, 0x6b, 0xed, 0x0c, 0x95, 0x27, 0xc3, 0x94, 0xf4, 0x3f, 0x3b, 0x66, 0x49, 0xec, 0x6c, 0x7e, + 0x18, 0xc1, 0x78, 0xe7, 0xa6, 0x78, 0x6c, 0x13, 0x85, 0x43, 0xd1, 0x85, 0x21, 0xe4, 0xce, 0x81, + 0xac, 0x47, 0x7a, 0xef, 0x38, 0xa5, 0x17, 0xbc, 0x6b, 0xf2, 0x23, 0xf5, 0xd0, 0x61, 0x79, 0x67, + 0x5e, 0x81, 0xd2, 0x24, 0x43, 0xdb, 0x4a, 0xb2, 0x17, 0xa3, 0x4d, 0x34, 0x0c, 0x95, 0xba, 0xfd, + 0x78, 0xe0, 0x6a, 0xa7, 0x50, 0x55, 0x63, 0xca, 0xb0, 0x7a, 0x70, 0xf0, 0x00, 0xc4, 0xa6, 0xdc, + 0x31, 0xd8, 0x91, 0xe0, 0x96, 0xc1, 0x87, 0x38, 0x48, 0xa6, 0x0d, 0x3e, 0xe2, 0xcd, 0xee, 0x76, + 0x55, 0x44, 0xb2, 0x76, 0xf9, 0x00, 0x92, 0x1d, 0x30, 0xd5, 0xb4, 0x7a, 0xd5, 0x6a, 0x70, 0x68, + 0x25, 0x94, 0x47, 0x7a, 0x6a, 0xbc, 0xfe, 0xb8, 0xa1, 0xe7, 0xfe, 0xc6, 0x54, 0x0f, 0xfe, 0x62, + 0xfc, 0x4d, 0x55, 0x45, 0xcc, 0x13, 0xda, 0xdb, 0xf8, 0x80, 0xf8, 0xe5, 0x28, 0x26, 0xec, 0x88, + 0xb0, 0x40, 0xa9, 0x73, 0x24, 0x1d, 0x53, 0x25, 0xaa, 0x09, 0x30, 0xb6, 0x96, 0xa6, 0x81, 0x92, + 0xd5, 0x9f, 0x43, 0x18, 0xf2, 0x14, 0x80, 0x9a, 0x47, 0xb3, 0xbe, 0xb9, 0xa2, 0x1f, 0xc1, 0x4d, + 0x21, 0x07, 0x0a, 0x98, 0xc5, 0x13, 0xd4, 0x2f, 0x04, 0x30, 0x35, 0xb3, 0x61, 0x21, 0x06, 0x05, + 0xbe, 0x7a, 0x1b, 0xf5, 0xd6, 0xb3, 0x63, 0xa8, 0x74, 0x68, 0x2e, 0xdb, 0xac, 0x8e, 0xd6, 0xd0, + 0xf2, 0x60, 0x35, 0x04, 0xdb, 0x74, 0x7b, 0x4d, 0x17, 0xb8, 0xd8, 0xc0, 0x1d, 0x0e, 0x86, 0xd3, +]; +static PRIV_KEY: [u8; 512] = [ + // Private Exponent + 0x07, 0x18, 0x1c, 0xa6, 0x69, 0x09, 0x68, 0x7b, 0x33, 0x4c, 0x77, 0xdf, 0xe8, 0x5e, 0xdb, 0x3a, + 0x61, 0xda, 0x76, 0x93, 0xff, 0x22, 0x81, 0x6e, 0x76, 0x9f, 0x0b, 0xb4, 0xdb, 0xef, 0x7d, 0xad, + 0x0d, 0x2e, 0xd1, 0xf6, 0xba, 0x8a, 0x71, 0x4b, 0xfb, 0x84, 0xb9, 0xb2, 0x35, 0x36, 0xce, 0x49, + 0x48, 0x4f, 0xe4, 0xab, 0xb3, 0xe9, 0x7b, 0x43, 0xd0, 0x5f, 0x1d, 0xf5, 0x40, 0xf5, 0x79, 0x29, + 0x73, 0xdf, 0xd8, 0xea, 0x75, 0xd2, 0xc7, 0x18, 0xdf, 0x1d, 0x78, 0x26, 0xb1, 0xb4, 0x04, 0x23, + 0x2b, 0x35, 0x39, 0x83, 0xc7, 0x41, 0x22, 0xd9, 0x0f, 0xda, 0xce, 0x27, 0x02, 0x7d, 0x34, 0xbb, + 0x03, 0x4a, 0x10, 0x7d, 0x95, 0x4a, 0x49, 0x7d, 0x43, 0x2a, 0xa1, 0xf7, 0x7d, 0xc3, 0x7b, 0x08, + 0x4c, 0x74, 0x6e, 0x8e, 0x48, 0x13, 0x8f, 0x25, 0xa9, 0x8b, 0x85, 0x2d, 0xf9, 0x99, 0x6c, 0xc9, + 0x25, 0x35, 0xfe, 0xdc, 0x55, 0x97, 0xb6, 0xe6, 0x7a, 0xb4, 0xfd, 0xe7, 0x09, 0x9d, 0x20, 0x03, + 0xf0, 0xda, 0xf9, 0xf0, 0xeb, 0x3e, 0xf6, 0x2d, 0x7c, 0x74, 0x52, 0xbd, 0x05, 0x94, 0x9a, 0xb4, + 0x38, 0x19, 0x4e, 0xde, 0xe9, 0xab, 0x41, 0x34, 0x40, 0x8b, 0xc9, 0x50, 0xed, 0xca, 0x0a, 0xb1, + 0xed, 0x0c, 0xe0, 0x93, 0xde, 0x0f, 0x45, 0xd9, 0x69, 0xa0, 0x4f, 0x61, 0xe2, 0x09, 0x66, 0x1c, + 0xa4, 0x73, 0xdd, 0x7c, 0xbc, 0xf6, 0xd6, 0x5b, 0x19, 0x8f, 0x26, 0xf2, 0xba, 0xeb, 0xd4, 0xc9, + 0x5f, 0x79, 0x1e, 0x38, 0xda, 0x84, 0x30, 0x0f, 0xae, 0xd1, 0xb2, 0x5c, 0xa8, 0xc0, 0x72, 0x24, + 0x87, 0x74, 0x0a, 0x8b, 0x3a, 0x21, 0x46, 0xad, 0xc4, 0xb4, 0x56, 0x94, 0x19, 0xe4, 0xa4, 0x53, + 0xaa, 0x07, 0xfb, 0xe1, 0xa3, 0x1c, 0xf5, 0x1c, 0x1c, 0xa4, 0x66, 0x10, 0x9a, 0x3f, 0x54, 0x98, + 0x63, 0x32, 0x8f, 0xc9, 0x3f, 0x0a, 0x29, 0x56, 0x83, 0xb2, 0xfc, 0xd4, 0x81, 0x77, 0xa2, 0xe1, + 0x85, 0x5e, 0x1a, 0x28, 0x03, 0x2c, 0xda, 0xcf, 0x0d, 0xd7, 0x65, 0x78, 0x5c, 0xe7, 0xf2, 0x87, + 0x92, 0x02, 0x6b, 0xbd, 0x49, 0x4a, 0xd7, 0x0d, 0x7e, 0x10, 0xf8, 0x7b, 0x6c, 0xcb, 0xbc, 0x49, + 0x85, 0x0b, 0xd8, 0x46, 0xf9, 0x1d, 0xe5, 0x45, 0x54, 0x16, 0x71, 0xf9, 0x58, 0xa6, 0xa8, 0x9a, + 0x17, 0xcc, 0x2b, 0x4c, 0xd5, 0xc1, 0xa0, 0xa9, 0xd4, 0x38, 0xcb, 0x32, 0x30, 0x08, 0x62, 0x97, + 0x8b, 0xe8, 0xb4, 0x79, 0xb4, 0xee, 0x74, 0xef, 0x36, 0xbb, 0x44, 0x35, 0x57, 0x55, 0xd5, 0xb1, + 0x69, 0x13, 0xc4, 0x76, 0x6e, 0x1e, 0x8e, 0x5b, 0xef, 0x04, 0xda, 0xa1, 0x21, 0xe4, 0xa1, 0x55, + 0x64, 0x56, 0x03, 0xbf, 0xd2, 0x9d, 0xdb, 0xfa, 0x77, 0xbd, 0xc2, 0xe7, 0x4f, 0xbc, 0x15, 0x96, + 0xfc, 0x93, 0x7e, 0x8d, 0xd1, 0x1f, 0x33, 0x94, 0x5c, 0x12, 0x51, 0xa3, 0x38, 0x72, 0x2e, 0xfa, + 0xce, 0xec, 0x80, 0x8c, 0xd0, 0xbe, 0x55, 0x24, 0xb5, 0x96, 0x4e, 0xd8, 0x89, 0x1f, 0x62, 0xdf, + 0xbe, 0x01, 0x66, 0x87, 0x9f, 0xb2, 0x9a, 0x47, 0x15, 0xf3, 0x74, 0x64, 0xd7, 0x78, 0xc0, 0x7b, + 0x98, 0x79, 0xc8, 0x36, 0xf0, 0x1e, 0x36, 0x60, 0x4f, 0x99, 0xdd, 0x44, 0x0a, 0x6d, 0x45, 0x1f, + 0x6b, 0x8b, 0x06, 0xc0, 0x05, 0x05, 0x1e, 0x66, 0xb8, 0x72, 0x2c, 0x9a, 0xd2, 0xc7, 0x93, 0x81, + 0xcd, 0x2f, 0x6c, 0xbd, 0xf4, 0x44, 0xd9, 0xb8, 0x2e, 0xbb, 0x2b, 0x87, 0x67, 0xbd, 0x9f, 0x9d, + 0xc1, 0x8d, 0xd4, 0x56, 0x51, 0x01, 0x54, 0xe1, 0x26, 0xe4, 0xb2, 0x47, 0x87, 0xaa, 0x7f, 0xf3, + 0x23, 0xaa, 0x93, 0xfe, 0xe0, 0xf6, 0x5d, 0x6a, 0x2d, 0xde, 0xc5, 0x61, 0x27, 0xff, 0xed, 0x21, +]; + +static EXPECTING: [u8; 512] = [ + 0x88, 0x8c, 0x2b, 0x88, 0xb7, 0xe0, 0x55, 0x23, 0xaa, 0x52, 0xea, 0x6f, 0x99, 0xd5, 0x08, 0x36, + 0x85, 0xd3, 0x0c, 0x66, 0xd9, 0x70, 0x75, 0xa6, 0x44, 0x9a, 0x20, 0x28, 0x4d, 0x17, 0x85, 0x2a, + 0x04, 0x0a, 0x86, 0x92, 0xd3, 0xf8, 0xd5, 0xf2, 0xec, 0x9a, 0x9e, 0x2b, 0x56, 0x7d, 0x5b, 0x3f, + 0x50, 0xde, 0x26, 0x73, 0x8f, 0xd8, 0xa7, 0xbf, 0xe7, 0xa3, 0xf3, 0x47, 0x9a, 0xc7, 0x74, 0x3f, + 0x71, 0x98, 0x6a, 0xfa, 0x35, 0x59, 0x96, 0xa1, 0x4c, 0xda, 0x6f, 0x44, 0x90, 0x24, 0x47, 0x48, + 0xc3, 0x28, 0x35, 0x54, 0x4d, 0x82, 0xfe, 0xdf, 0xb9, 0x38, 0x22, 0xa2, 0x3f, 0x6c, 0x54, 0x93, + 0xb2, 0xb9, 0x2f, 0x7e, 0x60, 0x59, 0x74, 0x6a, 0xbb, 0xab, 0xdb, 0x5a, 0xbb, 0x42, 0xc9, 0x2d, + 0xd8, 0x81, 0x2a, 0x66, 0xb2, 0xcd, 0x01, 0xdd, 0x64, 0x59, 0xb9, 0x7e, 0x15, 0x37, 0x8d, 0xe3, + 0x99, 0x39, 0x4f, 0xc6, 0x9b, 0xa5, 0xe8, 0xcd, 0x96, 0x20, 0x34, 0x8d, 0xa0, 0x52, 0x5c, 0x09, + 0x7e, 0x72, 0x02, 0x75, 0xda, 0x2f, 0x10, 0x2d, 0xcc, 0x20, 0xdf, 0x10, 0x67, 0xfe, 0x87, 0x03, + 0xe8, 0xd6, 0x8f, 0xad, 0xa6, 0xaa, 0xfb, 0x41, 0xd7, 0xcf, 0x84, 0xa0, 0x3d, 0xbc, 0x1a, 0xaf, + 0x71, 0xab, 0xdf, 0x69, 0xb6, 0x63, 0xcc, 0x99, 0xca, 0x86, 0xeb, 0xc0, 0x92, 0x03, 0x40, 0xe4, + 0xbd, 0x9b, 0xbd, 0x5b, 0x06, 0xfd, 0xdb, 0xb3, 0x38, 0x11, 0x98, 0x70, 0x08, 0x59, 0xc0, 0x20, + 0xd4, 0x3d, 0x58, 0x76, 0xca, 0xd0, 0x00, 0x6a, 0xce, 0x67, 0xaf, 0x4f, 0x5f, 0x96, 0x0e, 0x34, + 0x0e, 0xa8, 0xdf, 0x04, 0x4c, 0xcc, 0x8e, 0xb8, 0x21, 0xfe, 0x9b, 0x15, 0xfd, 0x7d, 0xc0, 0x3d, + 0xb3, 0xe5, 0xac, 0xcd, 0x58, 0xcc, 0x1a, 0x4f, 0x10, 0x83, 0x0e, 0x46, 0xff, 0x13, 0xa0, 0x8e, + 0x78, 0xfe, 0xf2, 0xb1, 0x91, 0x79, 0x21, 0x0f, 0x6c, 0x44, 0x26, 0x52, 0xaa, 0x4e, 0xcc, 0x74, + 0xe0, 0x9d, 0x32, 0xdc, 0x6a, 0x41, 0x59, 0xfb, 0xa3, 0x52, 0xdc, 0xad, 0x26, 0xdf, 0xed, 0xb5, + 0xc6, 0x94, 0x86, 0x8d, 0xfb, 0x97, 0x26, 0xc2, 0xec, 0x3c, 0xf6, 0x53, 0x0c, 0x5f, 0x3d, 0x1d, + 0x42, 0x34, 0x83, 0x41, 0xaf, 0xa9, 0xcc, 0xba, 0xe6, 0xcb, 0xed, 0x44, 0xe1, 0xa5, 0xbf, 0x75, + 0x6d, 0x02, 0x98, 0x85, 0x51, 0xcd, 0x08, 0xba, 0xfc, 0x98, 0x2e, 0xd3, 0x36, 0x5a, 0x8a, 0x7c, + 0x7b, 0x27, 0x06, 0x78, 0x2d, 0x87, 0x54, 0x30, 0x42, 0xa1, 0xdf, 0xec, 0xc7, 0xb3, 0xfa, 0x2e, + 0x79, 0xe5, 0xa2, 0x4d, 0x3e, 0xff, 0xc3, 0xad, 0xa6, 0x1e, 0xa0, 0x70, 0x1a, 0xa0, 0xb0, 0xeb, + 0xa1, 0xaa, 0x29, 0xfb, 0x8d, 0x5b, 0x9d, 0x45, 0x63, 0x4d, 0x13, 0xa0, 0xb7, 0x06, 0x1e, 0x64, + 0x58, 0x86, 0xed, 0x22, 0xa3, 0xbd, 0x5c, 0xf9, 0xa6, 0x13, 0xb0, 0x2c, 0x57, 0xcc, 0x8d, 0xd7, + 0x25, 0x23, 0x98, 0xc5, 0xf3, 0xf5, 0x53, 0x07, 0x7a, 0x2c, 0x92, 0xd8, 0x6a, 0xf5, 0x8e, 0x79, + 0xdd, 0x84, 0x5c, 0x62, 0x02, 0x9a, 0x02, 0x6c, 0x4d, 0xf3, 0x9e, 0x9f, 0x8e, 0xc4, 0xce, 0xc6, + 0x44, 0x8e, 0x96, 0x8d, 0xa1, 0x1b, 0xa0, 0x08, 0x0b, 0x89, 0x84, 0x3d, 0xb6, 0x67, 0x45, 0x65, + 0xc5, 0xc4, 0x6b, 0x2b, 0x8f, 0xcf, 0x0a, 0xc9, 0xa7, 0x44, 0x97, 0xff, 0x55, 0x38, 0x30, 0x2b, + 0xdd, 0x94, 0x5d, 0x91, 0xeb, 0xe4, 0x75, 0x91, 0x61, 0xeb, 0xdd, 0x97, 0x58, 0x57, 0x18, 0x55, + 0x2e, 0x8f, 0xaf, 0x17, 0xaf, 0x76, 0x3a, 0x92, 0x63, 0x71, 0xde, 0x7e, 0x94, 0xae, 0x47, 0x11, + 0xf1, 0x1f, 0x46, 0x27, 0x7f, 0x94, 0x97, 0xcd, 0x44, 0x1d, 0x91, 0x20, 0x52, 0xcf, 0x5e, 0x4a, +]; + +struct RsaTestCallback { + mod_exp_done: Cell, + run: Cell, +} + +unsafe impl Sync for RsaTestCallback {} + +impl<'a> RsaTestCallback { + const fn new() -> Self { + RsaTestCallback { + mod_exp_done: Cell::new(false), + run: Cell::new(0), + } + } + + fn reset(&self) { + self.mod_exp_done.set(false); + } +} + +impl<'a> Client<'a> for RsaTestCallback { + fn mod_exponent_done( + &'a self, + status: Result, + _message: &'static mut [u8], + _modulus: &'static [u8], + _exponent: &'static [u8], + result: &'static mut [u8], + ) { + assert_eq!(status, Ok(true)); + + if self.run.get() == 0 { + assert_eq!(result, EXPECTING); + } + + self.run.set(self.run.get() + 1); + self.mod_exp_done.set(true); + } +} + +static CALLBACK: RsaTestCallback = RsaTestCallback::new(); + +#[test_case] +fn rsa_import_key() { + let key = unsafe { static_init!(RSA4096Keys, RSA4096Keys::new()) }; + + debug!("check rsa 4096 bit key import... "); + run_kernel_op(100); + + if let Err(e) = key.import_public_key(&PUB_KEY) { + panic!("Failed to import public key: {:?}", e.0); + } + if let Err(e) = key.import_private_key(&PRIV_KEY) { + panic!("Failed to import private key: {:?}", e.0); + } + + run_kernel_op(1000); + + assert_eq!( + key.map_modulus(&|modulus| { + assert_eq!(modulus, PUB_KEY); + }), + Some(()) + ); + + assert_eq!( + key.map_exponent(&|exponent| { + assert_eq!(exponent, PRIV_KEY); + }), + Some(()) + ); + + assert_eq!(key.public_exponent(), Some(0x10001)); + + debug!(" [ok]"); + run_kernel_op(100); +} + +#[test_case] +fn rsa_check_exponent() { + let perf = unsafe { PERIPHERALS.unwrap() }; + let otbn = &perf.otbn; + if let Some(rsa) = unsafe { RSA_HARDWARE } { + let key = unsafe { static_init!(RSA4096Keys, RSA4096Keys::new()) }; + + debug!("check rsa 4096 exponent... "); + run_kernel_op(100); + + // Possibly overridden by other tests + otbn.set_client(rsa); + rsa.set_client(&CALLBACK); + + if let Err(e) = key.import_public_key(&PUB_KEY) { + panic!("Failed to import public key: {:?}", e.0); + } + if let Err(e) = key.import_private_key(&PRIV_KEY) { + panic!("Failed to import private key: {:?}", e.0); + } + + CALLBACK.reset(); + unsafe { + match rsa.mod_exponent( + &mut SOURCE, + key.take_modulus().unwrap(), + key.take_exponent().unwrap(), + &mut DEST, + ) { + Ok(_) => {} + Err(_) => panic!("exponent failed"), + } + } + + run_kernel_op(1000000); + assert_eq!(CALLBACK.mod_exp_done.get(), true); + unsafe { + assert_eq!(DEST, EXPECTING); + } + + debug!(" [ok]"); + run_kernel_op(100); + } else { + debug!("Not running RSA tests"); + } +} diff --git a/boards/opentitan/src/tests/sha256soft_test.rs b/boards/opentitan/src/tests/sha256soft_test.rs new file mode 100644 index 0000000000..02c8165cc1 --- /dev/null +++ b/boards/opentitan/src/tests/sha256soft_test.rs @@ -0,0 +1,46 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Test SHA256 software implementation + +use crate::tests::run_kernel_op; +use crate::SHA256SOFT; +use capsules_extra::test::sha256::TestSha256; +use kernel::debug; +use kernel::static_init; + +#[test_case] +fn sha256software_verify() { + debug!("start SHA256 verify test"); + + let sha = unsafe { SHA256SOFT.unwrap() }; + + let lstring = unsafe { static_init!([u8; 72], [0; 72]) }; + let bytes = b"hello "; + + for i in 0..12 { + for j in 0..6 { + lstring[i * 6 + j] = bytes[j]; + } + } + + let lhash = unsafe { + static_init!( + [u8; 32], + [ + 0x59, 0x42, 0xc3, 0x71, 0x6f, 0x02, 0x82, 0x89, 0x3f, 0xbe, 0x04, 0x9b, 0xa2, 0x0e, + 0x56, 0x0e, 0x45, 0x94, 0xd5, 0xee, 0x15, 0xcb, 0x8a, 0x1e, 0x28, 0x7c, 0x20, 0x12, + 0xc2, 0xce, 0xb5, 0xa9 + ] + ) + }; + + let test = unsafe { static_init!(TestSha256, TestSha256::new(sha, lstring, lhash, true)) }; + test.run(); + + run_kernel_op(1000); + + debug!(" [ok]"); + run_kernel_op(100); +} diff --git a/boards/opentitan/src/tests/sip_hash.rs b/boards/opentitan/src/tests/sip_hash.rs new file mode 100644 index 0000000000..f439081cd9 --- /dev/null +++ b/boards/opentitan/src/tests/sip_hash.rs @@ -0,0 +1,221 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::tests::run_kernel_op; +use crate::SIPHASH; +use core::cell::Cell; +use kernel::hil::hasher::{self, Hasher}; +use kernel::static_init; +use kernel::utilities::cells::TakeCell; +use kernel::utilities::leasable_buffer::{SubSlice, SubSliceMut}; +use kernel::{debug, ErrorCode}; + +struct SipHashTestCallback { + data_add_done: Cell, + hash_done: Cell, + input_buf: [TakeCell<'static, [u8; 8]>; 20], + output_buf: TakeCell<'static, [u8; 8]>, + cb_count: Cell, + run_count: Cell, +} + +unsafe impl Sync for SipHashTestCallback {} + +impl<'a> SipHashTestCallback { + fn new(input_buf: [TakeCell<'static, [u8; 8]>; 20], output: &'static mut [u8; 8]) -> Self { + SipHashTestCallback { + data_add_done: Cell::new(false), + hash_done: Cell::new(false), + input_buf, + output_buf: TakeCell::new(output), + cb_count: Cell::new(0), + run_count: Cell::new(0), + } + } + + fn run_reset(&self) { + self.data_add_done.set(false); + self.hash_done.set(false); + } + + fn full_reset(&self) { + self.cb_count.set(0); + self.run_count.set(self.run_count.get() + 1); + self.run_reset(); + } +} + +impl<'a> hasher::Client<8> for SipHashTestCallback { + fn add_data_done(&self, _result: Result<(), ErrorCode>, _data: SubSlice<'static, u8>) { + unimplemented!() + } + + fn add_mut_data_done(&self, result: Result<(), ErrorCode>, data: SubSliceMut<'static, u8>) { + assert_eq!(result, Ok(())); + self.data_add_done.set(true); + + // Stay within valid ranges + assert_eq!(self.cb_count.get() < 20, true); + + // Replace the input buffer with all of cb data + self.input_buf[self.cb_count.get()].replace(data.take().try_into().unwrap()); + + self.cb_count.set(self.cb_count.get() + 1); + } + + fn hash_done(&self, result: Result<(), ErrorCode>, digest: &'static mut [u8; 8]) { + let ret = u64::from_le_bytes(*digest); + + assert_eq!(result, Ok(())); + // Value calculated from: + // https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=08a45842acb9dc1abf9dbc303b5eaa50 + if self.run_count.get() == 0 { + assert_eq!(ret, 0x9ed5975598371f51); + } else if self.run_count.get() == 1 { + assert_eq!(ret, 0xB5326A7D96F8A5B7); + } else { + panic!("Unsupported number of callbacks"); + } + + self.hash_done.set(true); + self.output_buf.replace(digest); + } +} + +unsafe fn static_init_test_cb() -> &'static SipHashTestCallback { + let output = static_init!([u8; 8], [0; 8]); + let input = [ + TakeCell::new(static_init!( + [u8; 8], + [0x31, 0x0e, 0x0e, 0xdd, 0x47, 0xdb, 0x6f, 0x72] + )), + TakeCell::new(static_init!( + [u8; 8], + [0xfd, 0x67, 0xdc, 0x93, 0xc5, 0x39, 0xf8, 0x74] + )), + TakeCell::new(static_init!( + [u8; 8], + [0x5a, 0x4f, 0xa9, 0xd9, 0x09, 0x80, 0x6c, 0x0d] + )), + TakeCell::new(static_init!( + [u8; 8], + [0x2d, 0x7e, 0xfb, 0xd7, 0x96, 0x66, 0x67, 0x85] + )), + TakeCell::new(static_init!( + [u8; 8], + [0xb7, 0x87, 0x71, 0x27, 0xe0, 0x94, 0x27, 0xcf] + )), + TakeCell::new(static_init!( + [u8; 8], + [0x8d, 0xa6, 0x99, 0xcd, 0x64, 0x55, 0x76, 0x18] + )), + TakeCell::new(static_init!( + [u8; 8], + [0xce, 0xe3, 0xfe, 0x58, 0x6e, 0x46, 0xc9, 0xcb] + )), + TakeCell::new(static_init!( + [u8; 8], + [0x37, 0xd1, 0x01, 0x8b, 0xf5, 0x00, 0x02, 0xab] + )), + TakeCell::new(static_init!( + [u8; 8], + [0x62, 0x24, 0x93, 0x9a, 0x79, 0xf5, 0xf5, 0x93] + )), + TakeCell::new(static_init!( + [u8; 8], + [0xb0, 0xe4, 0xa9, 0x0b, 0xdf, 0x82, 0x00, 0x9e] + )), + TakeCell::new(static_init!( + [u8; 8], + [0xf3, 0xb9, 0xdd, 0x94, 0xc5, 0xbb, 0x5d, 0x7a] + )), + TakeCell::new(static_init!( + [u8; 8], + [0xa7, 0xad, 0x6b, 0x22, 0x46, 0x2f, 0xb3, 0xf4] + )), + TakeCell::new(static_init!( + [u8; 8], + [0xfb, 0xe5, 0x0e, 0x86, 0xbc, 0x8f, 0x1e, 0x75] + )), + TakeCell::new(static_init!( + [u8; 8], + [0x90, 0x3d, 0x84, 0xc0, 0x27, 0x56, 0xea, 0x14] + )), + TakeCell::new(static_init!( + [u8; 8], + [0xee, 0xf2, 0x7a, 0x8e, 0x90, 0xca, 0x23, 0xf7] + )), + TakeCell::new(static_init!( + [u8; 8], + [0xe5, 0x45, 0xbe, 0x49, 0x61, 0xca, 0x29, 0xa1] + )), + TakeCell::new(static_init!( + [u8; 8], + [0xdb, 0x9b, 0xc2, 0x57, 0x7f, 0xcc, 0x2a, 0x3f] + )), + TakeCell::new(static_init!( + [u8; 8], + [0x94, 0x47, 0xbe, 0x2c, 0xf5, 0xe9, 0x9a, 0x69] + )), + TakeCell::new(static_init!( + [u8; 8], + [0x9c, 0xd3, 0x8d, 0x96, 0xf0, 0xb3, 0xc1, 0x4b] + )), + TakeCell::new(static_init!( + [u8; 8], + [0x28, 0xef, 0x49, 0x5c, 0x53, 0xa3, 0x87, 0xad] + )), + ]; + + static_init!(SipHashTestCallback, SipHashTestCallback::new(input, output)) +} + +#[test_case] +fn sip_hasher_2_4() { + let sip_hasher = unsafe { SIPHASH.unwrap() }; + let cb = unsafe { static_init_test_cb() }; + + debug!("check SipHash 2-4... "); + run_kernel_op(100); + + sip_hasher.set_client(cb); + + for slice in cb.input_buf.iter() { + // Data add done should be reset per each slice + cb.run_reset(); + assert_eq!( + sip_hasher.add_mut_data(SubSliceMut::new(slice.take().unwrap())), + Ok(8) + ); + + run_kernel_op(100); + assert_eq!(cb.data_add_done.get(), true); + } + + assert_eq!(sip_hasher.run(cb.output_buf.take().unwrap()), Ok(())); + run_kernel_op(100); + assert_eq!(cb.hash_done.get(), true); + + cb.full_reset(); + + for slice in cb.input_buf.iter() { + // Data add done should be reset per each slice + cb.run_reset(); + assert_eq!( + sip_hasher.add_mut_data(SubSliceMut::new(slice.take().unwrap())), + Ok(8) + ); + + run_kernel_op(100); + assert_eq!(cb.data_add_done.get(), true); + } + + assert_eq!(sip_hasher.run(cb.output_buf.take().unwrap()), Ok(())); + run_kernel_op(100); + assert_eq!(cb.hash_done.get(), true); + + run_kernel_op(100); + debug!(" [ok]"); + run_kernel_op(100); +} diff --git a/boards/opentitan/src/tests/spi_host.rs b/boards/opentitan/src/tests/spi_host.rs new file mode 100644 index 0000000000..bb53e86283 --- /dev/null +++ b/boards/opentitan/src/tests/spi_host.rs @@ -0,0 +1,227 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::tests::run_kernel_op; +use crate::PERIPHERALS; +use core::cell::Cell; +#[allow(unused_imports)] +use kernel::hil::spi::{ClockPhase, ClockPolarity}; +use kernel::hil::spi::{SpiMaster, SpiMasterClient}; +use kernel::static_init; +use kernel::utilities::cells::TakeCell; +use kernel::{debug, ErrorCode}; + +struct SpiHostCallback { + transfer_done: Cell, + tx_len: Cell, + tx_data: TakeCell<'static, [u8]>, + rx_data: TakeCell<'static, [u8]>, +} + +impl<'a> SpiHostCallback { + fn new(tx_data: &'static mut [u8], rx_data: &'static mut [u8]) -> Self { + SpiHostCallback { + transfer_done: Cell::new(false), + tx_len: Cell::new(0), + tx_data: TakeCell::new(tx_data), + rx_data: TakeCell::new(rx_data), + } + } + + fn reset(&self) { + self.transfer_done.set(false); + self.tx_len.set(0); + } +} + +impl<'a> SpiMasterClient for SpiHostCallback { + fn read_write_done( + &self, + tx_data: &'static mut [u8], + rx_done: Option<&'static mut [u8]>, + tx_len: usize, + rc: Result<(), ErrorCode>, + ) { + //Transfer Complete + assert_eq!(rc, Ok(())); + assert_eq!(tx_len, self.tx_len.get()); + + //Capture Buffers + self.tx_data.replace(tx_data); + + match rx_done { + Some(rx_buf) => { + self.rx_data.replace(rx_buf); + } + None => { + panic!("RX Buffer Lost"); + } + } + + if self.tx_len.get() == tx_len { + self.transfer_done.set(true); + } + } +} + +unsafe fn static_init_test_cb() -> &'static SpiHostCallback { + let rx_data = static_init!([u8; 32], [0; 32]); + + let tx_data = static_init!( + [u8; 32], + [ + 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, + 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, + 0x58, 0x57, 0x40, + ] + ); + + static_init!(SpiHostCallback, SpiHostCallback::new(tx_data, rx_data)) +} + +unsafe fn static_init_test_partial_cb() -> &'static SpiHostCallback { + let rx_data = static_init!([u8; 513], [0; 513]); + //Buffer Size to exceed TXFIFO (Force partial transfers) + let tx_data = static_init!( + [u8; 513], + [ + 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, + 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, + 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, + 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, + 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, + 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, + 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, + 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, + 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, + 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, + 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, + 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, + 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, + 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, + 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, + 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, + 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, + 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, + 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, + 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, + 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, + 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, + 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, + 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, + 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, + 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, + 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, + 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, + 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, + 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, + 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, + 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, 0x82, 0xf7, 0x8b, + 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, 0xf6, 0xeb, 0xaf, + 0x58, 0x57, 0x40, 0xdc, 0x55, 0x51, 0x5e, 0x30, 0xac, 0x50, 0xc7, 0x65, 0xbd, 0xe, 0x2, + 0x82, 0xf7, 0x8b, 0xe1, 0xef, 0xd1, 0xb, 0xdc, 0xa8, 0xba, 0xe1, 0xfa, 0x11, 0x3f, + 0xf6, 0xeb, 0xaf, 0x58, 0x57, 0x40, 0x40, + ] + ); + + static_init!(SpiHostCallback, SpiHostCallback::new(tx_data, rx_data)) +} + +/// Tests transferring a data set that exceeds the TXFIFO (256) +/// The driver must do 3 transfers (256, 256, 1) to transfer the full 513 byte +/// dataset. This tests partial transfers and continued offset write outs. +#[test_case] +fn spi_host_transfer_partial() { + let perf = unsafe { PERIPHERALS.unwrap() }; + let spi_host = &perf.spi_host0; + + let cb = unsafe { static_init_test_partial_cb() }; + + debug!("[SPI] Setup spi_host0 partial_transfer... "); + run_kernel_op(100); + + spi_host.set_client(cb); + cb.reset(); + + let tx = cb.tx_data.take().unwrap(); + let rx = cb.rx_data.take().unwrap(); + cb.tx_len.set(tx.len()); + + //Set SPI_HOST0 Configs + spi_host.specify_chip_select(0).ok(); + spi_host.set_rate(100000).ok(); + spi_host.set_polarity(ClockPolarity::IdleLow).ok(); + spi_host.set_phase(ClockPhase::SampleLeading).ok(); + + assert_eq!( + spi_host.read_write_bytes(tx, Some(rx), cb.tx_len.get()), + Ok(()) + ); + run_kernel_op(5000); + + assert_eq!(cb.transfer_done.get(), true); + + run_kernel_op(100); + debug!(" [ok]"); + run_kernel_op(100); +} + +/// Tests two single transfers that do not exceed the TXFIFO +/// The second test, is to ensure that the driver is left in a clean state +/// after a transfer (reset internal offsets and counts etc...) +#[test_case] +fn spi_host_transfer_single() { + let perf = unsafe { PERIPHERALS.unwrap() }; + let spi_host = &perf.spi_host0; + + let cb = unsafe { static_init_test_cb() }; + + debug!("[SPI] Setup spi_host0 transfers... "); + run_kernel_op(100); + spi_host.set_client(cb); + cb.reset(); + + let tx = cb.tx_data.take().unwrap(); + let rx = cb.rx_data.take().unwrap(); + cb.tx_len.set(tx.len()); + + //Set SPI_HOST0 Configs + spi_host.specify_chip_select(0).ok(); + spi_host.set_rate(100000).ok(); + spi_host.set_polarity(ClockPolarity::IdleLow).ok(); + spi_host.set_phase(ClockPhase::SampleLeading).ok(); + + assert_eq!( + spi_host.read_write_bytes(tx, Some(rx), cb.tx_len.get()), + Ok(()) + ); + run_kernel_op(5000); + + assert_eq!(cb.transfer_done.get(), true); + + run_kernel_op(100); + debug!(" [ok]"); + run_kernel_op(100); + + debug!("[SPI] Setup spi_host0 transfer...x2 "); + run_kernel_op(100); + + cb.reset(); + + let tx2 = cb.tx_data.take().unwrap(); + let rx2 = cb.rx_data.take().unwrap(); + cb.tx_len.set(tx2.len()); + + assert_eq!( + spi_host.read_write_bytes(tx2, Some(rx2), cb.tx_len.get()), + Ok(()) + ); + run_kernel_op(5000); + + assert_eq!(cb.transfer_done.get(), true); + + run_kernel_op(100); + debug!(" [ok]"); + run_kernel_op(100); +} diff --git a/boards/opentitan/src/tests/tickv_test.rs b/boards/opentitan/src/tests/tickv_test.rs deleted file mode 100644 index 904e7d7b74..0000000000 --- a/boards/opentitan/src/tests/tickv_test.rs +++ /dev/null @@ -1,40 +0,0 @@ -//! Test TicKV - -use crate::tests::run_kernel_op; -use crate::TICKV; -use capsules::test::kv_system::KVSystemTest; -use capsules::tickv::{TicKVKeyType, TicKVStore}; -use capsules::virtual_flash::FlashUser; -use kernel::debug; -use kernel::hil::kv_system::KVSystem; -use kernel::static_init; - -#[test_case] -fn tickv_append_key() { - debug!("start TicKV append key test..."); - - unsafe { - let tickv = TICKV.unwrap(); - let key = static_init!([u8; 8], [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0]); - let value = static_init!([u8; 3], [0x10, 0x20, 0x30]); - let ret = static_init!([u8; 4], [0; 4]); - - let test = static_init!( - KVSystemTest< - 'static, - TicKVStore<'static, FlashUser<'static, lowrisc::flash_ctrl::FlashCtrl<'static>>>, - TicKVKeyType, - >, - KVSystemTest::new(tickv, ret) - ); - - tickv.set_client(test); - - // Kick start the tests by adding a key - tickv.append_key(key, value).unwrap(); - } - run_kernel_op(100000); - - debug!(" [ok]"); - run_kernel_op(100); -} diff --git a/boards/opentitan/src/usb.rs b/boards/opentitan/src/usb.rs deleted file mode 100644 index 2d313b7606..0000000000 --- a/boards/opentitan/src/usb.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! Component for USB -//! -//! This provides one Component, UsbComponent, which implements -//! a userspace syscall interface to the USB peripheral on the EarlGrey SoC. -//! -//! Usage -//! ----- -//! ```rust -//! let usb = UsbComponent::new().finalize(()); -//! ``` - -#![allow(dead_code)] // Components are intended to be conditionally included - -use earlgrey::usbdev::Usb; -use kernel::capabilities; -use kernel::component::Component; -use kernel::create_capability; -use kernel::static_init; - -pub struct UsbComponent { - board_kernel: &'static kernel::Kernel, - driver_num: usize, - usb: &'static Usb<'static>, -} - -impl UsbComponent { - pub fn new( - usb: &'static Usb, - board_kernel: &'static kernel::Kernel, - driver_num: usize, - ) -> Self { - Self { - usb, - board_kernel, - driver_num, - } - } -} - -impl Component for UsbComponent { - type StaticInput = (); - type Output = &'static capsules::usb::usb_user::UsbSyscallDriver< - 'static, - capsules::usb::usbc_client::Client<'static, lowrisc::usbdev::Usb<'static>>, - >; - - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { - let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - - // Configure the USB controller - let usb_client = static_init!( - capsules::usb::usbc_client::Client<'static, lowrisc::usbdev::Usb<'static>>, - capsules::usb::usbc_client::Client::new( - self.usb, - capsules::usb::usbc_client::MAX_CTRL_PACKET_SIZE_EARLGREY - ) - ); - - // Configure the USB userspace driver - let usb_driver = static_init!( - capsules::usb::usb_user::UsbSyscallDriver< - 'static, - capsules::usb::usbc_client::Client<'static, lowrisc::usbdev::Usb<'static>>, - >, - capsules::usb::usb_user::UsbSyscallDriver::new( - usb_client, - self.board_kernel.create_grant(self.driver_num, &grant_cap) - ) - ); - - usb_driver - } -} diff --git a/boards/particle_boron/Cargo.toml b/boards/particle_boron/Cargo.toml new file mode 100644 index 0000000000..384758c56c --- /dev/null +++ b/boards/particle_boron/Cargo.toml @@ -0,0 +1,28 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[package] +name = "particle_boron" +version.workspace = true +authors = [ + "Tock Project Developers ", + "Wilfred Mallawa " + ] +build = "../build.rs" +edition.workspace = true + +[dependencies] +components = { path = "../components" } +cortexm4 = { path = "../../arch/cortex-m4" } +kernel = { path = "../../kernel" } +nrf52 = { path = "../../chips/nrf52" } +nrf52840 = { path = "../../chips/nrf52840" } +nrf52_components = { path = "../nordic/nrf52_components" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/particle_boron/Makefile b/boards/particle_boron/Makefile new file mode 100644 index 0000000000..3ac8098250 --- /dev/null +++ b/boards/particle_boron/Makefile @@ -0,0 +1,27 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +# Makefile for building the tock kernel for the Particle Boron + +TARGET=thumbv7em-none-eabi +PLATFORM=particle_boron + +include ../Makefile.common + +TOCKLOADER=tockloader + +# Where in the nrf52840 flash to load the kernel with `tockloader` +KERNEL_ADDRESS=0x00000 + +# Can be flashed with nrf52dk config +TOCKLOADER_JTAG_FLAGS = --jlink --board particle_boron + +# Default target for installing the kernel. +.PHONY: install +install: flash + +# Upload the kernel over JTAG +.PHONY: flash +flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin + $(TOCKLOADER) $(TOCKLOADER_GENERAL_FLAGS) flash --address $(KERNEL_ADDRESS) $(TOCKLOADER_JTAG_FLAGS) $< diff --git a/boards/particle_boron/README.md b/boards/particle_boron/README.md new file mode 100644 index 0000000000..0a3e33bdf0 --- /dev/null +++ b/boards/particle_boron/README.md @@ -0,0 +1,120 @@ +Platform-Specific Instructions: Particle-Boron +=================================== + +## Particle Boron + + +

+ +The [Particle_Boron](https://docs.particle.io/reference/datasheets/b-series/boron-datasheet/) is development board based on the `nRF52840 SOC`. "The Boron is a powerful LTE Cat M1 or 2G/3G enabled development kit that supports cellular networks and Bluetooth LE (BLE). It is based on the Nordic nRF52840 and has built-in battery charging circuitry so it’s easy to connect a Li-Po and deploy your local network in minutes." + +To program the [Particle_Boron](https://docs.particle.io/reference/datasheets/b-series/boron-datasheet/) with Tock, you will need a JLink JTAG device and the +appropriate cables. + +Then, follow the [Tock Getting Started guide](../../../doc/Getting_Started.md) + +## Programming the kernel + +Once you have all software installed, you should be able to simply run +`make install` in this directory to install a fresh kernel. Make sure to install +the [tockloader](https://github.com/tock/tockloader). + +## Hardware Setup + +Required: + +* Segger JLink +* USB Cable for power & Debugging +* FDTI/Serial UART converter (Optional for debugging/Console) + +Ensure JLink is connected and the Particle Boron is powered using either mUSB cable or a Li-Po battery. + +## Software Setup + +To flash using JLink, make sure the segger software is installed on your machine. See [here](https://www.segger.com/downloads/jlink). You can test that it is installed properly by running the following command. + +```bash +$ JLinkExe +``` + +## Programming user-level applications + +You can program an application via JTAG using `tockloader`: + +First clone libtock-c, more instructions [here](https://github.com/tock/libtock-c). + +Note: We currently use the `nrf52dk` (until tockloader supports) board alias for the Particle Boron to flash using the tockloader. + +```bash +$ git clone git@github.com:tock/libtock-c.git +$ cd libtock-c/examples/ +$ make +$ tockloader install --board nrf52dk --jlink blink/build/blink.tab +``` +You should see the rgb-led light show on the board now! + +## Board Reset + +A reset on the baord can be performed by the **_RESET** push button, to reboot the kernel. + +## Console output + +Console is interfaced with UART. On the left side of the Particle Boron, you can see the marked UART RX/TX pins. Connect a serial converter to the appropriate pins (`UART1_RX->CONVERTER_TX` & `UART1_TX->CONVERTER_RX`), see the figure above for the pin-out. + +If you are on a linux machine, it's a good idea to monitor the kernel message buffer with `dmesg -w` when plugging the serial converter in. This can quickly tell us which USB device to open. + +```bash +$ dmesg -w + +[26244.142183] usb 3-4: new full-speed USB device number 50 using xhci_hcd +[26244.295996] usb 3-4: New USB device found, idVendor=0403, idProduct=6001, bcdDevice= 6.00 +[26244.296001] usb 3-4: New USB device strings: Mfr=1, Product=2, SerialNumber=3 +[26244.296004] usb 3-4: Product: FT232R USB UART +[26244.296006] usb 3-4: Manufacturer: FTDI +[26244.296007] usb 3-4: SerialNumber: A50285BI +[26244.302027] ftdi_sio 3-4:1.0: FTDI USB Serial Device converter detected +[26244.302055] usb 3-4: Detected FT232R +[26244.309214] usb 3-4: FTDI USB Serial Device converter now attached to ttyUSB0 +``` +Based on the last kernel message, we are mapped to `ttyUSB0`, now, we can access the console with: +```bash +$ screen /dev/ttyUSB0 115200 +. +.. +Particle Boron: Initialization complete. Entering main loop +tock$ +``` + +OR + +```bash +$ tockloader listen +. +. +. +[INFO ] No device name specified. Using default name "tock". +[INFO ] Using "/dev/ttyACM0 - Boron - TockOS". +[INFO ] Listening for serial output. + +tock$ help +Welcome to the process console. +Valid commands are: help status list stop start fault process kernel +tock$ list + PID Name Quanta Syscalls Dropped Upcalls Restarts State Grants + 0 buttons 0 22 0 0 Yielded 1/11 +``` + +## Tested LibTock-C Samples + +``` +* adc -- [OK] +* ble_advertising -- [OK] +* ble_passive_scanning -- [OK] +* blink -- [OK] +* buttons -- [OK] +* c_hello -- [OK] +``` +## Currently Unsupported Board Features + +* LTE [u-blox SARA-R410M-02B or R410M-03] or 2G/3G [u-blox SARA-U201 (2G/3G)] modem(s). +* PMIC/Fuel Gauge (I2C device). diff --git a/boards/particle_boron/layout.ld b/boards/particle_boron/layout.ld new file mode 100644 index 0000000000..89d8ab00fc --- /dev/null +++ b/boards/particle_boron/layout.ld @@ -0,0 +1,6 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + +INCLUDE ../nordic/nrf52840_chip_layout.ld +INCLUDE ../kernel_layout.ld diff --git a/boards/particle_boron/src/io.rs b/boards/particle_boron/src/io.rs new file mode 100644 index 0000000000..f87d9b217e --- /dev/null +++ b/boards/particle_boron/src/io.rs @@ -0,0 +1,85 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use core::fmt::Write; +use core::panic::PanicInfo; +use kernel::debug; +use kernel::debug::IoWrite; +use kernel::hil::led; +use kernel::hil::uart; +use kernel::hil::uart::Configure; +use nrf52840::gpio::Pin; +use nrf52840::uart::{Uarte, UARTE0_BASE}; + +use crate::CHIP; +use crate::PROCESSES; +use crate::PROCESS_PRINTER; + +// Expand here with more writing methods as required (rtt/cdc etc...) +enum Writer { + WriterUart(/* initialized */ bool), +} + +static mut WRITER: Writer = Writer::WriterUart(false); + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> ::core::fmt::Result { + self.write(s.as_bytes()); + Ok(()) + } +} + +impl IoWrite for Writer { + fn write(&mut self, buf: &[u8]) -> usize { + match self { + Writer::WriterUart(ref mut initialized) => { + // Here, we create a second instance of the Uarte struct. + // This is okay because we only call this during a panic, and + // we will never actually process the interrupts + let uart = Uarte::new(UARTE0_BASE); + if !*initialized { + *initialized = true; + let _ = uart.configure(uart::Parameters { + baud_rate: 115200, + stop_bits: uart::StopBits::One, + parity: uart::Parity::None, + hw_flow_control: false, + width: uart::Width::Eight, + }); + } + for &c in buf { + unsafe { + uart.send_byte(c); + } + while !uart.tx_ready() {} + } + } + }; + buf.len() + } +} + +const LED2_R_PIN: Pin = Pin::P0_13; + +#[cfg(not(test))] +#[no_mangle] +#[panic_handler] +/// Panic handler +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { + // The nRF52840DK LEDs (see back of board) + + use core::ptr::{addr_of, addr_of_mut}; + let led_kernel_pin = &nrf52840::gpio::GPIOPin::new(LED2_R_PIN); + let led = &mut led::LedLow::new(led_kernel_pin); + let writer = &mut *addr_of_mut!(WRITER); + debug::panic( + &mut [led], + writer, + pi, + &cortexm4::support::nop, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), + ) +} diff --git a/boards/particle_boron/src/main.rs b/boards/particle_boron/src/main.rs new file mode 100644 index 0000000000..b19300a5c5 --- /dev/null +++ b/boards/particle_boron/src/main.rs @@ -0,0 +1,635 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Tock kernel for the Particle Boron. +//! +//! It is based on nRF52840 SoC (Cortex M4 core with a BLE transceiver) with +//! many exported I/O and peripherals. + +#![no_std] +// Disable this attribute when documenting, as a workaround for +// https://github.com/rust-lang/rust/issues/62184. +#![cfg_attr(not(doc), no_main)] +#![deny(missing_docs)] + +use core::ptr::addr_of; +use core::ptr::addr_of_mut; + +use capsules_core::i2c_master_slave_driver::I2CMasterSlaveDriver; +use capsules_core::virtualizers::virtual_aes_ccm::MuxAES128CCM; +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; +use kernel::component::Component; +use kernel::deferred_call::DeferredCallClient; +use kernel::hil::gpio::Configure; +use kernel::hil::gpio::FloatingState; +use kernel::hil::i2c::{I2CMaster, I2CSlave}; +use kernel::hil::led::LedLow; +use kernel::hil::symmetric_encryption::AES128; +use kernel::hil::time::Counter; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::scheduler::round_robin::RoundRobinSched; +#[allow(unused_imports)] +use kernel::{capabilities, create_capability, debug, debug_gpio, debug_verbose, static_init}; +use nrf52840::gpio::Pin; +use nrf52840::interrupt_service::Nrf52840DefaultPeripherals; +#[allow(unused_imports)] +use nrf52_components::{self, UartChannel, UartPins}; + +// The Particle Boron LEDs +const LED_USR_PIN: Pin = Pin::P1_12; +const LED2_R_PIN: Pin = Pin::P0_13; +const LED2_G_PIN: Pin = Pin::P0_14; +const LED2_B_PIN: Pin = Pin::P0_15; + +// The Particle Boron buttons +const BUTTON_PIN: Pin = Pin::P0_11; +const BUTTON_RST_PIN: Pin = Pin::P0_18; + +// UART Pins (CTS/RTS Unused) +const _UART_RTS: Option = Some(Pin::P0_30); +const _UART_CTS: Option = Some(Pin::P0_31); +const UART_TXD: Pin = Pin::P0_06; +const UART_RXD: Pin = Pin::P0_08; + +// SPI pins not currently in use, but left here for convenience +const _SPI_MOSI: Pin = Pin::P1_13; +const _SPI_MISO: Pin = Pin::P1_14; +const _SPI_CLK: Pin = Pin::P1_15; + +// I2C Pins +const I2C_SDA_PIN: Pin = Pin::P0_26; +const I2C_SCL_PIN: Pin = Pin::P0_27; + +// Constants related to the configuration of the 15.4 network stack; DEFAULT_EXT_SRC_MAC +// should be replaced by an extended src address generated from device serial number +const SRC_MAC: u16 = 0xf00f; +const PAN_ID: u16 = 0xABCD; +const DEFAULT_EXT_SRC_MAC: [u8; 8] = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77]; + +/// UART Writer +pub mod io; + +// State for loading and holding applications. +// How should the kernel respond when a process faults. +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; + +// Number of concurrent processes this platform supports. +const NUM_PROCS: usize = 8; + +static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = + [None; NUM_PROCS]; + +// Static reference to chip for panic dumps +static mut CHIP: Option<&'static nrf52840::chip::NRF52> = None; +// Static reference to process printer for panic dumps +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; +static mut NRF52_POWER: Option<&'static nrf52840::power::Power> = None; + +/// Dummy buffer that causes the linker to reserve enough space for the stack. +#[no_mangle] +#[link_section = ".stack_buffer"] +pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; + +type TemperatureDriver = + components::temperature::TemperatureComponentType>; +type RngDriver = components::rng::RngComponentType>; + +type Ieee802154Driver = components::ieee802154::Ieee802154ComponentType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; + +/// Supported drivers by the platform +pub struct Platform { + ble_radio: &'static capsules_extra::ble_advertising_driver::BLE< + 'static, + nrf52840::ble_radio::Radio<'static>, + VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, + >, + ieee802154_radio: &'static Ieee802154Driver, + button: &'static capsules_core::button::Button<'static, nrf52840::gpio::GPIOPin<'static>>, + console: &'static capsules_core::console::Console<'static>, + gpio: &'static capsules_core::gpio::GPIO<'static, nrf52840::gpio::GPIOPin<'static>>, + led: &'static capsules_core::led::LedDriver< + 'static, + LedLow<'static, nrf52840::gpio::GPIOPin<'static>>, + 4, + >, + adc: &'static capsules_core::adc::AdcVirtualized<'static>, + rng: &'static RngDriver, + temp: &'static TemperatureDriver, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + i2c_master_slave: &'static capsules_core::i2c_master_slave_driver::I2CMasterSlaveDriver< + 'static, + nrf52840::i2c::TWI<'static>, + >, + alarm: &'static capsules_core::alarm::AlarmDriver< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52840::rtc::Rtc<'static>, + >, + >, + scheduler: &'static RoundRobinSched<'static>, + systick: cortexm4::systick::SysTick, +} + +impl SyscallDriverLookup for Platform { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_core::rng::DRIVER_NUM => f(Some(self.rng)), + capsules_extra::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), + capsules_extra::ieee802154::DRIVER_NUM => f(Some(self.ieee802154_radio)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temp)), + kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), + capsules_core::i2c_master_slave_driver::DRIVER_NUM => f(Some(self.i2c_master_slave)), + _ => f(None), + } + } +} + +impl KernelResources>> + for Platform +{ + type SyscallDriverLookup = Self; + type SyscallFilter = (); + type ProcessFault = (); + type Scheduler = RoundRobinSched<'static>; + type SchedulerTimer = cortexm4::systick::SysTick; + type WatchDog = (); + type ContextSwitchCallback = (); + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + &() + } + fn process_fault(&self) -> &Self::ProcessFault { + &() + } + fn scheduler(&self) -> &Self::Scheduler { + self.scheduler + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + &self.systick + } + fn watchdog(&self) -> &Self::WatchDog { + &() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + &() + } +} + +/// This is in a separate, inline(never) function so that its stack frame is +/// removed when this function returns. Otherwise, the stack space used for +/// these static_inits is wasted. +#[inline(never)] +unsafe fn create_peripherals() -> &'static mut Nrf52840DefaultPeripherals<'static> { + let ieee802154_ack_buf = static_init!( + [u8; nrf52840::ieee802154_radio::ACK_BUF_SIZE], + [0; nrf52840::ieee802154_radio::ACK_BUF_SIZE] + ); + // Initialize chip peripheral drivers + let nrf52840_peripherals = static_init!( + Nrf52840DefaultPeripherals, + Nrf52840DefaultPeripherals::new(ieee802154_ack_buf) + ); + + nrf52840_peripherals +} + +/// This is in a separate, inline(never) function so that its stack frame is +/// removed when this function returns. Otherwise, the stack space used for +/// these static_inits is wasted. +#[inline(never)] +pub unsafe fn start_particle_boron() -> ( + &'static kernel::Kernel, + Platform, + &'static nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>, +) { + nrf52840::init(); + + let nrf52840_peripherals = create_peripherals(); + + // set up circular peripheral dependencies + nrf52840_peripherals.init(); + let base_peripherals = &nrf52840_peripherals.nrf52; + + // Save a reference to the power module for resetting the board into the + // bootloader. + NRF52_POWER = Some(&base_peripherals.pwr_clk); + + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); + + //-------------------------------------------------------------------------- + // CAPABILITIES + //-------------------------------------------------------------------------- + + // Create capabilities that the board needs to call certain protected kernel + // functions. + let process_management_capability = + create_capability!(capabilities::ProcessManagementCapability); + let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); + + //-------------------------------------------------------------------------- + // DEBUG GPIO + //-------------------------------------------------------------------------- + + let gpio_port = &nrf52840_peripherals.gpio_port; + // Configure kernel debug GPIOs as early as possible. These are used by the + // `debug_gpio!(0, toggle)` macro. We configure these early so that the + // macro is available during most of the setup code and kernel execution. + kernel::debug::assign_gpios(Some(&gpio_port[LED2_R_PIN]), None, None); + + let uart_channel = UartChannel::Pins(UartPins::new(None, UART_TXD, None, UART_RXD)); + + //-------------------------------------------------------------------------- + // GPIO + //-------------------------------------------------------------------------- + + let gpio = components::gpio::GpioComponent::new( + board_kernel, + capsules_core::gpio::DRIVER_NUM, + components::gpio_component_helper!( + nrf52840::gpio::GPIOPin, + // Left Side pins on mesh feather + // A0 - ADC + // 0 => &nrf52840_peripherals.gpio_port[Pin::P0_03], + // A1 - ADC + // 1 => &nrf52840_peripherals.gpio_port[Pin::P0_04], + // A2 - ADC + // 2 => &nrf52840_peripherals.gpio_port[Pin::P0_28], + // A3 - ADC + // 3 => &nrf52840_peripherals.gpio_port[Pin::P0_29], + // A4 - ADC + // 4 => &nrf52840_peripherals.gpio_port[Pin::P0_30], + // A5 - ADC + // 5 => &nrf52840_peripherals.gpio_port[Pin::P0_31], + //D13 + 6 => &nrf52840_peripherals.gpio_port[Pin::P1_15], + //D12 + 7 => &nrf52840_peripherals.gpio_port[Pin::P1_13], + //D11 + 8 => &nrf52840_peripherals.gpio_port[Pin::P1_14], + //D10 + 9 => &nrf52840_peripherals.gpio_port[Pin::P0_08], + //D9 + 10 => &nrf52840_peripherals.gpio_port[Pin::P0_06], + // Right Side pins on mesh feather + //D8 + 11 => &nrf52840_peripherals.gpio_port[Pin::P1_03], + //D7: Bound to LED_USR_PIN (Active Low) + 12 => &nrf52840_peripherals.gpio_port[Pin::P1_12], + //D6 + 13 => &nrf52840_peripherals.gpio_port[Pin::P1_11], + //D5 + 14 => &nrf52840_peripherals.gpio_port[Pin::P1_10], + //D4 + 15 => &nrf52840_peripherals.gpio_port[Pin::P1_08], + //D3 + 16 => &nrf52840_peripherals.gpio_port[Pin::P1_02], + //D2 + 17 => &nrf52840_peripherals.gpio_port[Pin::P0_01], + //D1 + 18 => &nrf52840_peripherals.gpio_port[Pin::P0_27], + //D0 + 19 => &nrf52840_peripherals.gpio_port[Pin::P0_26], + ), + ) + .finalize(components::gpio_component_static!(nrf52840::gpio::GPIOPin)); + + //-------------------------------------------------------------------------- + // Buttons + //-------------------------------------------------------------------------- + + let button = components::button::ButtonComponent::new( + board_kernel, + capsules_core::button::DRIVER_NUM, + components::button_component_helper!( + nrf52840::gpio::GPIOPin, + ( + &nrf52840_peripherals.gpio_port[BUTTON_PIN], + kernel::hil::gpio::ActivationMode::ActiveLow, + kernel::hil::gpio::FloatingState::PullUp + ) + ), + ) + .finalize(components::button_component_static!( + nrf52840::gpio::GPIOPin + )); + + //-------------------------------------------------------------------------- + // LEDs + //-------------------------------------------------------------------------- + + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( + LedLow<'static, nrf52840::gpio::GPIOPin>, + LedLow::new(&nrf52840_peripherals.gpio_port[LED_USR_PIN]), + LedLow::new(&nrf52840_peripherals.gpio_port[LED2_R_PIN]), + LedLow::new(&nrf52840_peripherals.gpio_port[LED2_G_PIN]), + LedLow::new(&nrf52840_peripherals.gpio_port[LED2_B_PIN]), + )); + + nrf52_components::startup::NrfStartupComponent::new( + false, + BUTTON_RST_PIN, + nrf52840::uicr::Regulator0Output::V3_0, + &base_peripherals.nvmc, + ) + .finalize(()); + + //-------------------------------------------------------------------------- + // ALARM & TIMER + //-------------------------------------------------------------------------- + + let rtc = &base_peripherals.rtc; + let _ = rtc.start(); + let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) + .finalize(components::alarm_mux_component_static!(nrf52840::rtc::Rtc)); + let alarm = components::alarm::AlarmDriverComponent::new( + board_kernel, + capsules_core::alarm::DRIVER_NUM, + mux_alarm, + ) + .finalize(components::alarm_component_static!(nrf52840::rtc::Rtc)); + + //-------------------------------------------------------------------------- + // UART & CONSOLE & DEBUG + //-------------------------------------------------------------------------- + + let uart_channel = nrf52_components::UartChannelComponent::new( + uart_channel, + mux_alarm, + &base_peripherals.uarte0, + ) + .finalize(nrf52_components::uart_channel_component_static!( + nrf52840::rtc::Rtc + )); + + // Process Printer for displaying process information. + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); + PROCESS_PRINTER = Some(process_printer); + + // Create a shared UART channel for the console and for kernel debug. + let uart_mux = components::console::UartMuxComponent::new(uart_channel, 115200) + .finalize(components::uart_mux_component_static!(132)); + + // Setup the console. + let console = components::console::ConsoleComponent::new( + board_kernel, + capsules_core::console::DRIVER_NUM, + uart_mux, + ) + .finalize(components::console_component_static!(132, 132)); + // Create the debugger object that handles calls to `debug!()`. + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); + + //-------------------------------------------------------------------------- + // WIRELESS + //-------------------------------------------------------------------------- + + let ble_radio = components::ble::BLEComponent::new( + board_kernel, + capsules_extra::ble_advertising_driver::DRIVER_NUM, + &base_peripherals.ble_radio, + mux_alarm, + ) + .finalize(components::ble_component_static!( + nrf52840::rtc::Rtc, + nrf52840::ble_radio::Radio + )); + + let aes_mux = static_init!( + MuxAES128CCM<'static, nrf52840::aes::AesECB>, + MuxAES128CCM::new(&base_peripherals.ecb,) + ); + base_peripherals.ecb.set_client(aes_mux); + aes_mux.register(); + + let (ieee802154_radio, _mux_mac) = components::ieee802154::Ieee802154Component::new( + board_kernel, + capsules_extra::ieee802154::DRIVER_NUM, + &nrf52840_peripherals.ieee802154_radio, + aes_mux, + PAN_ID, + SRC_MAC, + DEFAULT_EXT_SRC_MAC, + ) + .finalize(components::ieee802154_component_static!( + nrf52840::ieee802154_radio::Radio, + nrf52840::aes::AesECB<'static> + )); + + //-------------------------------------------------------------------------- + // Sensor + //-------------------------------------------------------------------------- + + let temp = components::temperature::TemperatureComponent::new( + board_kernel, + capsules_extra::temperature::DRIVER_NUM, + &base_peripherals.temp, + ) + .finalize(components::temperature_component_static!( + nrf52840::temperature::Temp + )); + + //-------------------------------------------------------------------------- + // RANDOM NUMBERS + //-------------------------------------------------------------------------- + + let rng = components::rng::RngComponent::new( + board_kernel, + capsules_core::rng::DRIVER_NUM, + &base_peripherals.trng, + ) + .finalize(components::rng_component_static!(nrf52840::trng::Trng)); + + //-------------------------------------------------------------------------- + // ADC + //-------------------------------------------------------------------------- + + base_peripherals.adc.calibrate(); + + let adc_mux = components::adc::AdcMuxComponent::new(&base_peripherals.adc) + .finalize(components::adc_mux_component_static!(nrf52840::adc::Adc)); + + let adc_syscall = + components::adc::AdcVirtualComponent::new(board_kernel, capsules_core::adc::DRIVER_NUM) + .finalize(components::adc_syscall_component_helper!( + // BRD_A0 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput1) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // BRD_A1 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput2) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // BRD_A2 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput4) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // BRD_A3 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput5) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // BRD_A4 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput6) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + // BRD_A5 + components::adc::AdcComponent::new( + adc_mux, + nrf52840::adc::AdcChannelSetup::new(nrf52840::adc::AdcChannel::AnalogInput7) + ) + .finalize(components::adc_component_static!(nrf52840::adc::Adc)), + )); + + //-------------------------------------------------------------------------- + // I2C Master/Slave + //-------------------------------------------------------------------------- + + let i2c_master_buffer = static_init!([u8; 128], [0; 128]); + let i2c_slave_buffer1 = static_init!([u8; 128], [0; 128]); + let i2c_slave_buffer2 = static_init!([u8; 128], [0; 128]); + + let i2c_master_slave = static_init!( + I2CMasterSlaveDriver>, + I2CMasterSlaveDriver::new( + &base_peripherals.twi1, + i2c_master_buffer, + i2c_slave_buffer1, + i2c_slave_buffer2, + board_kernel.create_grant( + capsules_core::i2c_master_slave_driver::DRIVER_NUM, + &memory_allocation_capability + ), + ) + ); + base_peripherals.twi1.configure( + nrf52840::pinmux::Pinmux::new(I2C_SCL_PIN as u32), + nrf52840::pinmux::Pinmux::new(I2C_SDA_PIN as u32), + ); + base_peripherals.twi1.set_master_client(i2c_master_slave); + base_peripherals.twi1.set_slave_client(i2c_master_slave); + // Note: strongly suggested to use external pull-ups for higher speeds + // to maintain signal integrity. + base_peripherals.twi1.set_speed(nrf52840::i2c::Speed::K400); + + // I2C pin cfg for target + nrf52840_peripherals.gpio_port[I2C_SDA_PIN].set_i2c_pin_cfg(); + nrf52840_peripherals.gpio_port[I2C_SCL_PIN].set_i2c_pin_cfg(); + // Enable internal pull-ups + nrf52840_peripherals.gpio_port[I2C_SDA_PIN].set_floating_state(FloatingState::PullUp); + nrf52840_peripherals.gpio_port[I2C_SCL_PIN].set_floating_state(FloatingState::PullUp); + + //-------------------------------------------------------------------------- + // FINAL SETUP AND BOARD BOOT + //-------------------------------------------------------------------------- + + nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); + + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); + + let platform = Platform { + button, + ble_radio, + ieee802154_radio, + console, + led, + gpio, + adc: adc_syscall, + rng, + temp, + alarm, + ipc: kernel::ipc::IPC::new( + board_kernel, + kernel::ipc::DRIVER_NUM, + &memory_allocation_capability, + ), + i2c_master_slave, + scheduler, + systick: cortexm4::systick::SysTick::new_with_calibration(64000000), + }; + + let chip = static_init!( + nrf52840::chip::NRF52, + nrf52840::chip::NRF52::new(nrf52840_peripherals) + ); + CHIP = Some(chip); + + debug!("Particle Boron: Initialization complete. Entering main loop\r"); + + //-------------------------------------------------------------------------- + // PROCESSES AND MAIN LOOP + //-------------------------------------------------------------------------- + + // These symbols are defined in the linker script. + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + } + + kernel::process::load_processes( + board_kernel, + chip, + core::slice::from_raw_parts( + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, + ), + core::slice::from_raw_parts_mut( + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, + ), + &mut *addr_of_mut!(PROCESSES), + &FAULT_RESPONSE, + &process_management_capability, + ) + .unwrap_or_else(|err| { + debug!("Error loading processes!"); + debug!("{:?}", err); + }); + + (board_kernel, platform, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start_particle_boron(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); +} diff --git a/boards/pico_explorer_base/Cargo.toml b/boards/pico_explorer_base/Cargo.toml index 807d18a08c..bcaaeb1d7a 100644 --- a/boards/pico_explorer_base/Cargo.toml +++ b/boards/pico_explorer_base/Cargo.toml @@ -1,15 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "pico_explorer_base" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] cortexm0p = { path = "../../arch/cortex-m0p" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } rp2040 = { path = "../../chips/rp2040" } components = { path = "../components" } enum_primitive = { path = "../../libraries/enum_primitive" } +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/pico_explorer_base/Makefile b/boards/pico_explorer_base/Makefile index c56781450b..927633fa92 100644 --- a/boards/pico_explorer_base/Makefile +++ b/boards/pico_explorer_base/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the Pico Explorer Base board. TOCK_ARCH=cortex-m0p @@ -7,29 +11,34 @@ PLATFORM=pico_explorer_base include ../Makefile.common OPENOCD=openocd -OPENOCD_OPTIONS=-f openocd.cfg +OPENOCD_INTERFACE=swd +OPENOCD_OPTIONS=-f openocd-$(OPENOCD_INTERFACE).cfg + +BOOTSEL_FOLDER?=/media/$(USER)/RPI-RP2 -KERNEL=$(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).elf -KERNEL_WITH_APP=$(TOCK_ROOT_DIRECTORY)/target/$(TARGET)/debug/$(PLATFORM)-app.elf +KERNEL=$(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf +KERNEL_WITH_APP=$(TOCK_ROOT_DIRECTORY)/target/$(TARGET)/release/$(PLATFORM)-app.elf # Default target for installing the kernel. .PHONY: install install: flash -.PHONY: flash-debug -flash-debug: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).elf +.PHONY: flash-openocd +flash-openocd: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf $(OPENOCD) $(OPENOCD_OPTIONS) -c "program $<; verify_image $<; reset; shutdown;" .PHONY: flash -flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf - $(OPENOCD) $(OPENOCD_OPTIONS) -c "program $<; verify_image $<; reset; shutdown;" +flash: $(KERNEL) + elf2uf2-rs $< $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).uf2 + @if [ -d $(BOOTSEL_FOLDER) ]; then cp $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).uf2 "$(BOOTSEL_FOLDER)"; else echo; echo Please edit the BOOTSEL_FOLDER variable to point to you Raspberry Pi Pico Flash Drive Folder; echo You can download and flash $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).uf2; fi .PHONY: program -program: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).bin +program: $(KERNEL) ifeq ($(APP),) $(error Please define the APP variable with the TBF file to flash an application) endif arm-none-eabi-objcopy --update-section .apps=$(APP) $(KERNEL) $(KERNEL_WITH_APP) - $(OPENOCD) $(OPENOCD_OPTIONS) -c "program $(KERNEL_WITH_APP); verify_image $(KERNEL_WITH_APP); reset; shutdown;" + elf2uf2-rs $(KERNEL_WITH_APP) $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.uf2 + @if [ -d $(BOOTSEL_FOLDER) ]; then cp $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.uf2 "$(BOOTSEL_FOLDER)"; else echo; echo Please edit the BOOTSEL_FOLDER variable to point to you Raspberry Pi Pico Flash Drive Folder; echo You can download and flash $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.uf2; fi diff --git a/boards/pico_explorer_base/README.md b/boards/pico_explorer_base/README.md index f4b5246cb5..cf8cf92e06 100644 --- a/boards/pico_explorer_base/README.md +++ b/boards/pico_explorer_base/README.md @@ -1,7 +1,7 @@ Pico Explorer Base for the Raspberry Pi Pico - RP2040 ===================================================== - + The [Pico Explorer Base](https://shop.pimoroni.com/products/pico-explorer-base) is an expansion board for the [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/) @@ -11,15 +11,60 @@ board developed by the Raspberry Pi Foundation based on the RP2040 chip. First, follow the [Tock Getting Started guide](../../doc/Getting_Started.md) +## Installing elf2uf2-rs + +The Nano RP2040 uses UF2 files for flashing. Tock compiles to an ELF file. +The `elf2uf2-rs` utility is needed to transform the Tock ELF file into an UF2 file. + +To install `elf2uf2`, run the commands: + +```bash +$ cargo install elf2uf2-rs +``` + ## Flashing the kernel -The Raspberry Pi Pico can be programmed via an SWD connection, which requires the Pico to be connected to a regular Raspberry Pi device that exposes the necessary pins. The kernel is transferred to the Raspberry Pi Pico using a [custom version of OpenOCD](https://github.com/raspberrypi/openocd). +The Raspberry Pi Pico RP2040 Connect can be programmed using its bootloader, which requires an UF2 file. + +### Enter BOOTSEL mode + +To flash the Pico RP2040, it needs to be put into BOOTSEL mode. This will mount +a flash drive that allows one to copy a UF2 file. To enter BOOTSEL mode, press the BOOTSEL button and hold it while you connect the other end of the micro USB cable to your computer. + +Then `cd` into `boards/raspberry_pi_pico` directory and run: + +```bash +$ make flash + +(or) + +$ make flash-debug +``` + +> Note: The Makefile provides the BOOTSEL_FOLDER variable that points towards the mount point of +> the Pico RP2040 flash drive. By default, this is located in `/media/$(USER)/RP2040`. This might +> be different on several systems, make sure to adjust it. + +## Flashing app + +Enter BOOTSEL mode. + +Apps are built out-of-tree. Once an app is built, you can add the path to it in the Makefile (APP variable), then run: +```bash +$ APP="" make flash-app +``` + +## Debugging + +The Raspberry Pi Pico can also be programmed via an SWD connection, which requires the Pico to be connected to a regular Raspberry Pi device that exposes the necessary pins OR using another Raspberry Pi Pico set up in “Picoprobe” mode. The kernel is transferred to the Raspberry Pi Pico using a [custom version of OpenOCD](https://github.com/raspberrypi/openocd). + +### Flashing Setup -### Raspberry Pi Setup +#### From a regular Raspberry Pi (option 1) To install OpenOCD on the Raspberry Pi run the following commands on the Pi: ```bash -$ sudo apt-get update +$ sudo apt update $ sudo apt install automake autoconf build-essential texinfo libtool libftdi-dev libusb-1.0-0-dev git $ git clone https://github.com/raspberrypi/openocd.git --recursive --branch rp2040 --depth=1 $ cd openocd @@ -27,7 +72,6 @@ $ ./bootstrap $ ./configure --enable-ftdi --enable-sysfsgpio --enable-bcm2835gpio $ make -j4 $ sudo make install -$ cd ~ ``` Enable SSH on the Raspberry Pi by following the [instructions on the Raspberry Pi website](https://www.raspberrypi.org/documentation/remote-access/ssh/). @@ -35,7 +79,45 @@ Enable SSH on the Raspberry Pi by following the [instructions on the Raspberry P Next, connect the SWD pins of the Pico (the tree lower wires) to GND, GPIO 24, and GPIO 25 of the Raspberry Pi. You can follow the schematic in the [official documentation](https://datasheets.raspberrypi.org/pico/getting-started-with-pico.pdf#%5B%7B%22num%22%3A22%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C115%2C431.757%2Cnull%5D) and connect the blue, black, and purple wires. Also connect the other three wires as shown in the schematic, which will connect the Pico UART to the Raspberry Pi. This will enable the serial communication between the two devices. -### Flash the tock kernel + +#### From a Linux Host using a Picoprobe (option 2) + +To install OpenOCD on Debian/Ubuntu run the following commands: +```bash +$ sudo apt update +$ sudo apt install automake autoconf build-essential texinfo libtool libftdi-dev libusb-1.0-0-dev git +$ git clone https://github.com/raspberrypi/openocd.git --recursive --branch rp2040 --depth=1 +$ cd openocd +$ ./bootstrap +$ ./configure --enable-picoprobe +$ make -j4 +$ sudo make install +``` + +Download the Picoprobe UF2 file onto the USB mass storage device presented by the Pico that is going to act as Picoprobe device after plugging it into some USB port: https://datasheets.raspberrypi.com/soft/picoprobe.uf2 (the device should automatically restart after the file has been written). + +Next, connect the SWD pins of the Pico target (the tree lower wires, left-to-right) to GP2, GND, and GP3 of the Pico that will act as Picoprobe. You can follow the schematic in the [official documentation](https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf#%5B%7B%22num%22%3A64%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C115%2C696.992%2Cnull%5D) and connect the blue, black, and purple wires. + +Also connect the other four wires as shown in the schematic, which will connect the Pico UART and power to the Picoprobe. This will enable the serial communication between the two devices. + +### Flashing the tock kernel + +#### Building and Deploying from the same System +`cd` into `boards/raspberry_pi_pico` directory and run: + +```bash +$ make flash OPENOCD_INTERFACE=[swd|picoprobe] +``` + +The *OPENOCD_INTERFACE* parameter selects which mode to flash the target Pico device in: `swd` flashes directly via SWD over GPIO (default, needs to be run a regular Raspberry Pi), `picoprobe` flashes indirectly via another Raspberry Pi Pico device. + +You can also open a serial console on to view debug messages: +```bash +$ sudo apt install picocom +$ picocom /dev/ttyACM0 -b 115200 -l +``` + +#### Building on a Desktop/Laptop then Flashing via regular Raspberry Pi `cd` into `boards/raspberry_pi_pico` directory and run: @@ -76,3 +158,8 @@ $ make program ``` This will generate a new ELF file that can be deployed on the Raspberry Pi Pico via gdb and OpenOCD as described in the [section above](#flash-the-tock-kernel). + +## Book + +For further details and examples about how to use Tock with the Raspberry Pi Pico, you might +want to check out the [Getting Started with Secure Embedded Systems](https://link.springer.com/book/10.1007/978-1-4842-7789-8) book. diff --git a/boards/pico_explorer_base/build.rs b/boards/pico_explorer_base/build.rs deleted file mode 100644 index 3051054fc6..0000000000 --- a/boards/pico_explorer_base/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/pico_explorer_base/layout.ld b/boards/pico_explorer_base/layout.ld index 1d056d62da..ce99f68d6d 100644 --- a/boards/pico_explorer_base/layout.ld +++ b/boards/pico_explorer_base/layout.ld @@ -1,18 +1,21 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + MEMORY { /* uncomment this to boot from RAM */ /* reset (rx) : ORIGIN = 0x20000000, LENGTH = 16K - rom (rx) : ORIGIN = 0x20000100, LENGTH = 128K + rom (rx) : ORIGIN = 0x20000100, LENGTH = 256K prog (rx) : ORIGIN = 0x20040000, LENGTH = 1K ram (rwx) : ORIGIN = 0x20004000, LENGTH = 240K */ /* boot from Flash */ - rom (rx) : ORIGIN = 0x10000000, LENGTH = 128K - prog (rx) : ORIGIN = 0x10020000, LENGTH = 256K + rom (rx) : ORIGIN = 0x10000000, LENGTH = 256K + prog (rx) : ORIGIN = 0x10040000, LENGTH = 256K ram (rwx) : ORIGIN = 0x20000000, LENGTH = 264K } -MPU_MIN_ALIGN = 8K; PAGE_SIZE = 4K; ENTRY(jump_to_bootloader) @@ -26,4 +29,4 @@ SECTIONS { } > rom } -INCLUDE ../kernel_layout.ld \ No newline at end of file +INCLUDE ../kernel_layout.ld diff --git a/boards/pico_explorer_base/openocd-picoprobe.cfg b/boards/pico_explorer_base/openocd-picoprobe.cfg new file mode 100644 index 0000000000..3583b9e038 --- /dev/null +++ b/boards/pico_explorer_base/openocd-picoprobe.cfg @@ -0,0 +1,6 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +source [find interface/picoprobe.cfg] +source [find target/rp2040.cfg] diff --git a/boards/pico_explorer_base/openocd-swd.cfg b/boards/pico_explorer_base/openocd-swd.cfg new file mode 100644 index 0000000000..3d107f85c8 --- /dev/null +++ b/boards/pico_explorer_base/openocd-swd.cfg @@ -0,0 +1,6 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +source [find interface/raspberrypi-swd.cfg] +source [find target/rp2040.cfg] diff --git a/boards/pico_explorer_base/openocd.cfg b/boards/pico_explorer_base/openocd.cfg deleted file mode 100644 index 8a1f9c9aca..0000000000 --- a/boards/pico_explorer_base/openocd.cfg +++ /dev/null @@ -1,2 +0,0 @@ -source [find interface/raspberrypi-swd.cfg] -source [find target/rp2040.cfg] diff --git a/boards/pico_explorer_base/src/flash_bootloader.rs b/boards/pico_explorer_base/src/flash_bootloader.rs index 2b720b1bcd..7d09250a0d 100644 --- a/boards/pico_explorer_base/src/flash_bootloader.rs +++ b/boards/pico_explorer_base/src/flash_bootloader.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + /// Padded bootloader used to boot from flash /// /// The RP2040 chip requires a padded and signed bootloader (RP2040 Datasheet, 2.8 Bootrom Page, page 156) diff --git a/boards/pico_explorer_base/src/io.rs b/boards/pico_explorer_base/src/io.rs index 9b3df45930..f63e2a2c76 100644 --- a/boards/pico_explorer_base/src/io.rs +++ b/boards/pico_explorer_base/src/io.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; @@ -5,6 +9,7 @@ use kernel::debug::{self, IoWrite}; use kernel::hil::led::LedHigh; use kernel::hil::uart::{Configure, Parameters, Parity, StopBits, Width}; use kernel::utilities::cells::OptionalCell; + use rp2040::gpio::{GpioFunction, RPGpio, RPGpioPin}; use rp2040::uart::Uart; @@ -22,7 +27,26 @@ impl Writer { self.uart.set(uart); } - fn write_to_uart<'a>(&self, uart: &'a Uart, buf: &[u8]) { + fn configure_uart(&self, uart: &Uart) { + if !uart.is_configured() { + let parameters = Parameters { + baud_rate: 115200, + width: Width::Eight, + parity: Parity::None, + stop_bits: StopBits::One, + hw_flow_control: false, + }; + //configure parameters of uart for sending bytes + let _ = uart.configure(parameters); + //set RX and TX pins in UART mode + let gpio_tx = RPGpioPin::new(RPGpio::GPIO0); + let gpio_rx = RPGpioPin::new(RPGpio::GPIO1); + gpio_rx.set_function(GpioFunction::UART); + gpio_tx.set_function(GpioFunction::UART); + } + } + + fn write_to_uart(&self, uart: &Uart, buf: &[u8]) { for &c in buf { uart.send_byte(c); } @@ -42,57 +66,43 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { self.uart.map_or_else( || { - // If no UART is configured for panic print, use UART0 - let uart0 = &Uart::new_uart0(); - - if !uart0.is_configured() { - let parameters = Parameters { - baud_rate: 115200, - width: Width::Eight, - parity: Parity::None, - stop_bits: StopBits::One, - hw_flow_control: false, - }; - //configure parameters of uart for sending bytes - let _result = uart0.configure(parameters); - //set RX and TX pins in UART mode - let gpio_tx = RPGpioPin::new(RPGpio::GPIO0); - let gpio_rx = RPGpioPin::new(RPGpio::GPIO1); - gpio_rx.set_function(GpioFunction::UART); - gpio_tx.set_function(GpioFunction::UART); - } - - self.write_to_uart(uart0, buf); + let uart = Uart::new_uart0(); + self.configure_uart(&uart); + self.write_to_uart(&uart, buf); }, |uart| { + self.configure_uart(uart); self.write_to_uart(uart, buf); }, ); + buf.len() } } -/// Default panic handler for the Raspberry Pi Pico board. +/// Default panic handler for the pico explorer base board. /// /// We just use the standard default provided by the debug module in the kernel. #[cfg(not(test))] #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { // LED is conneted to GPIO 25 + + use core::ptr::{addr_of, addr_of_mut}; let led_kernel_pin = &RPGpioPin::new(RPGpio::GPIO25); let led = &mut LedHigh::new(led_kernel_pin); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led], writer, pi, &cortexm0p::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/pico_explorer_base/src/main.rs b/boards/pico_explorer_base/src/main.rs index 8d3f8a7aa5..4ecfc1e63c 100644 --- a/boards/pico_explorer_base/src/main.rs +++ b/boards/pico_explorer_base/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tock kernel for the Raspberry Pi Pico. //! //! It is based on RP2040SoC SoC (Cortex M0+). @@ -7,21 +11,20 @@ // https://github.com/rust-lang/rust/issues/62184. #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] -#![feature(asm, naked_functions)] -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; +use core::ptr::{addr_of, addr_of_mut}; -use capsules::virtual_alarm::VirtualMuxAlarm; +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; use components::gpio::GpioComponent; use components::led::LedsComponent; use enum_primitive::cast::FromPrimitive; use kernel::component::Component; use kernel::debug; use kernel::hil::led::LedHigh; +use kernel::hil::usb::Client; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::scheduler::round_robin::RoundRobinSched; use kernel::{capabilities, create_capability, static_init, Kernel}; -use rp2040; use rp2040::adc::{Adc, Channel}; use rp2040::chip::{Rp2040, Rp2040DefaultPeripherals}; @@ -43,7 +46,7 @@ mod flash_bootloader; /// Allocate memory for the stack #[no_mangle] #[link_section = ".stack_buffer"] -pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; +pub static mut STACK_MEMORY: [u8; 0x1500] = [0; 0x1500]; // Manually setting the boot header section that contains the FCB header #[used] @@ -52,7 +55,8 @@ static FLASH_BOOTLOADER: [u8; 256] = flash_bootloader::FLASH_BOOTLOADER; // State for loading and holding applications. // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; // Number of concurrent processes this platform supports. const NUM_PROCS: usize = 4; @@ -61,23 +65,42 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] [None; NUM_PROCS]; static mut CHIP: Option<&'static Rp2040> = None; -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; + +type TemperatureRp2040Sensor = components::temperature_rp2040::TemperatureRp2040ComponentType< + capsules_core::virtualizers::virtual_adc::AdcDevice<'static, rp2040::adc::Adc<'static>>, +>; +type TemperatureDriver = components::temperature::TemperatureComponentType; /// Supported drivers by the platform pub struct PicoExplorerBase { - ipc: kernel::ipc::IPC, - console: &'static capsules::console::Console<'static>, - alarm: &'static capsules::alarm::AlarmDriver< + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + console: &'static capsules_core::console::Console<'static>, + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, VirtualMuxAlarm<'static, rp2040::timer::RPTimer<'static>>, >, - gpio: &'static capsules::gpio::GPIO<'static, RPGpioPin<'static>>, - led: &'static capsules::led::LedDriver<'static, LedHigh<'static, RPGpioPin<'static>>, 1>, - adc: &'static capsules::adc::AdcVirtualized<'static>, - temperature: &'static capsules::temperature::TemperatureSensor<'static>, - - button: &'static capsules::button::Button<'static, RPGpioPin<'static>>, - screen: &'static capsules::screen::Screen<'static>, + gpio: &'static capsules_core::gpio::GPIO<'static, RPGpioPin<'static>>, + led: &'static capsules_core::led::LedDriver<'static, LedHigh<'static, RPGpioPin<'static>>, 1>, + adc: &'static capsules_core::adc::AdcVirtualized<'static>, + temperature: &'static TemperatureDriver, + buzzer_driver: &'static capsules_extra::buzzer_driver::Buzzer< + 'static, + capsules_extra::buzzer_pwm::PwmBuzzer< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + rp2040::timer::RPTimer<'static>, + >, + capsules_core::virtualizers::virtual_pwm::PwmPinUser< + 'static, + rp2040::pwm::Pwm<'static>, + >, + >, + >, + button: &'static capsules_core::button::Button<'static, RPGpioPin<'static>>, + screen: &'static capsules_extra::screen::Screen<'static>, scheduler: &'static RoundRobinSched<'static>, systick: cortexm0p::systick::SysTick, @@ -89,17 +112,16 @@ impl SyscallDriverLookup for PicoExplorerBase { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), - capsules::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), - capsules::adc::DRIVER_NUM => f(Some(self.adc)), - capsules::temperature::DRIVER_NUM => f(Some(self.temperature)), - - capsules::button::DRIVER_NUM => f(Some(self.button)), - capsules::screen::DRIVER_NUM => f(Some(self.screen)), - + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temperature)), + capsules_extra::buzzer_driver::DRIVER_NUM => f(Some(self.buzzer_driver)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_extra::screen::DRIVER_NUM => f(Some(self.screen)), _ => f(None), } } @@ -115,7 +137,7 @@ impl KernelResources>> for Pic type ContextSwitchCallback = (); fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -137,31 +159,36 @@ impl KernelResources>> for Pic } } -/// Entry point used for debuger -/// -/// When loaded using gdb, the Raspberry Pi Pico is not reset -/// by default. Without this function, gdb sets the PC to the -/// beginning of the flash. This is not correct, as the RP2040 -/// has a more complex boot process. -/// -/// This function is set to be the entry point for gdb and is used -/// to send the RP2040 back in the bootloader so that all the boot -/// sqeuence is performed. -#[no_mangle] -#[naked] -pub unsafe extern "C" fn jump_to_bootloader() { - asm!( - " +#[allow(dead_code)] +extern "C" { + /// Entry point used for debugger + /// + /// When loaded using gdb, the Raspberry Pi Pico is not reset + /// by default. Without this function, gdb sets the PC to the + /// beginning of the flash. This is not correct, as the RP2040 + /// has a more complex boot process. + /// + /// This function is set to be the entry point for gdb and is used + /// to send the RP2040 back in the bootloader so that all the boot + /// sequence is performed. + fn jump_to_bootloader(); +} + +#[cfg(all(target_arch = "arm", target_os = "none"))] +core::arch::global_asm!( + " + .section .jump_to_bootloader, \"ax\" + .global jump_to_bootloader + .thumb_func + jump_to_bootloader: movs r0, #0 ldr r1, =(0xe0000000 + 0x0000ed08) str r0, [r1] ldmia r0!, {{r1, r2}} msr msp, r1 bx r2 - ", - options(noreturn) - ); -} + " +); fn init_clocks(peripherals: &Rp2040DefaultPeripherals) { // Start tick in watchdog @@ -170,7 +197,7 @@ fn init_clocks(peripherals: &Rp2040DefaultPeripherals) { // Disable the Resus clock peripherals.clocks.disable_resus(); - // Setup the external Osciallator + // Setup the external Oscillator peripherals.xosc.init(); // disable ref and sys clock aux sources @@ -236,22 +263,17 @@ fn init_clocks(peripherals: &Rp2040DefaultPeripherals) { /// removed when this function returns. Otherwise, the stack space used for /// these static_inits is wasted. #[inline(never)] -unsafe fn get_peripherals() -> &'static mut Rp2040DefaultPeripherals<'static> { - static_init!(Rp2040DefaultPeripherals, Rp2040DefaultPeripherals::new()) -} - -/// Main function called after RAM initialized. -#[no_mangle] -pub unsafe fn main() { +pub unsafe fn start() -> ( + &'static kernel::Kernel, + PicoExplorerBase, + &'static rp2040::chip::Rp2040<'static, Rp2040DefaultPeripherals<'static>>, +) { // Loads relocations and clears BSS rp2040::init(); - let peripherals = get_peripherals(); + let peripherals = static_init!(Rp2040DefaultPeripherals, Rp2040DefaultPeripherals::new()); peripherals.resolve_dependencies(); - // Set the UART used for panic - io::WRITER.set_uart(&peripherals.uart0); - // Reset all peripherals except QSPI (we might be booting from Flash), PLL USB and PLL SYS peripherals.resets.reset_all_except(&[ Peripheral::IOQSpi, @@ -275,16 +297,23 @@ pub unsafe fn main() { true, ); - init_clocks(&peripherals); + init_clocks(peripherals); // Unreset all peripherals peripherals.resets.unreset_all_except(&[], true); + // Set the UART used for panic + io::WRITER.set_uart(&peripherals.uart0); + //set RX and TX pins in UART mode let gpio_tx = peripherals.pins.get_pin(RPGpio::GPIO0); let gpio_rx = peripherals.pins.get_pin(RPGpio::GPIO1); gpio_rx.set_function(GpioFunction::UART); gpio_tx.set_function(GpioFunction::UART); + + // Set the UART used for panic + io::WRITER.set_uart(&peripherals.uart0); + // Disable IE for pads 26-29 (the Pico SDK runtime does this, not sure why) for pin in 26..30 { peripherals @@ -300,101 +329,131 @@ pub unsafe fn main() { CHIP = Some(chip); - let board_kernel = static_init!(Kernel, Kernel::new(&PROCESSES)); + let board_kernel = static_init!(Kernel, Kernel::new(&*addr_of!(PROCESSES))); let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 2], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); - let mux_alarm = components::alarm::AlarmMuxComponent::new(&peripherals.timer) - .finalize(components::alarm_mux_component_helper!(RPTimer)); + .finalize(components::alarm_mux_component_static!(RPTimer)); let alarm = components::alarm::AlarmDriverComponent::new( board_kernel, - capsules::alarm::DRIVER_NUM, + capsules_core::alarm::DRIVER_NUM, mux_alarm, ) - .finalize(components::alarm_component_helper!(RPTimer)); + .finalize(components::alarm_component_static!(RPTimer)); + + // CDC + let strings = static_init!( + [&str; 3], + [ + "Raspberry Pi", // Manufacturer + "pico explorer base - TockOS", // Product + "00000000000000000", // Serial number + ] + ); + + let cdc = components::cdc::CdcAcmComponent::new( + &peripherals.usb, + //capsules::usb::cdc::MAX_CTRL_PACKET_SIZE_RP2040, + 64, + peripherals.sysinfo.get_manufacturer_rp2040(), + peripherals.sysinfo.get_part(), + strings, + mux_alarm, + None, + ) + .finalize(components::cdc_acm_component_static!( + rp2040::usb::UsbCtrl, + rp2040::timer::RPTimer + )); // UART // Create a shared UART channel for kernel debug. - let uart_mux = components::console::UartMuxComponent::new( - &peripherals.uart0, - 115200, - dynamic_deferred_caller, - ) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(cdc, 115200) + .finalize(components::uart_mux_component_static!()); + + // Uncomment this to use UART as an output + // let uart_mux = components::console::UartMuxComponent::new( + // &peripherals.uart0, + // 115200, + // ) + // .finalize(components::uart_mux_component_static!()); // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); + + cdc.enable(); + cdc.attach(); let gpio = GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( RPGpioPin, // Used for serial communication. Comment them in if you don't use serial. - 2 => &peripherals.pins.get_pin(RPGpio::GPIO2), - 3 => &peripherals.pins.get_pin(RPGpio::GPIO3), - 4 => &peripherals.pins.get_pin(RPGpio::GPIO4), - 5 => &peripherals.pins.get_pin(RPGpio::GPIO5), - 6 => &peripherals.pins.get_pin(RPGpio::GPIO6), - 7 => &peripherals.pins.get_pin(RPGpio::GPIO7), - 8 => &peripherals.pins.get_pin(RPGpio::GPIO8), - 9 => &peripherals.pins.get_pin(RPGpio::GPIO9), - 10 => &peripherals.pins.get_pin(RPGpio::GPIO10), - 11 => &peripherals.pins.get_pin(RPGpio::GPIO11), - 20 => &peripherals.pins.get_pin(RPGpio::GPIO20), - 21 => &peripherals.pins.get_pin(RPGpio::GPIO21), - 22 => &peripherals.pins.get_pin(RPGpio::GPIO22), - 23 => &peripherals.pins.get_pin(RPGpio::GPIO23), - 24 => &peripherals.pins.get_pin(RPGpio::GPIO24), + // 0 => &peripherals.pins.get_pin(RPGpio::GPIO0), + // 1 => &peripherals.pins.get_pin(RPGpio::GPIO1), + // Used for Buzzer. + // 2 => &peripherals.pins.get_pin(RPGpio::GPIO2), + 3 => peripherals.pins.get_pin(RPGpio::GPIO3), + 4 => peripherals.pins.get_pin(RPGpio::GPIO4), + 5 => peripherals.pins.get_pin(RPGpio::GPIO5), + 6 => peripherals.pins.get_pin(RPGpio::GPIO6), + 7 => peripherals.pins.get_pin(RPGpio::GPIO7), + 20 => peripherals.pins.get_pin(RPGpio::GPIO20), + 21 => peripherals.pins.get_pin(RPGpio::GPIO21), + 22 => peripherals.pins.get_pin(RPGpio::GPIO22), + 23 => peripherals.pins.get_pin(RPGpio::GPIO23), + 24 => peripherals.pins.get_pin(RPGpio::GPIO24), ), ) - .finalize(components::gpio_component_buf!(RPGpioPin<'static>)); + .finalize(components::gpio_component_static!(RPGpioPin<'static>)); - let led = LedsComponent::new().finalize(components::led_component_helper!( + let led = LedsComponent::new().finalize(components::led_component_static!( LedHigh<'static, RPGpioPin<'static>>, - LedHigh::new(&peripherals.pins.get_pin(RPGpio::GPIO25)) + LedHigh::new(peripherals.pins.get_pin(RPGpio::GPIO25)) )); peripherals.adc.init(); + // Set PWM function for Buzzer. + peripherals + .pins + .get_pin(RPGpio::GPIO2) + .set_function(GpioFunction::PWM); + let adc_mux = components::adc::AdcMuxComponent::new(&peripherals.adc) - .finalize(components::adc_mux_component_helper!(Adc)); - - let temp_sensor = components::temperature_rp2040::TemperatureRp2040Component::new(1.721, 0.706) - .finalize(components::temperaturerp2040_adc_component_helper!( - rp2040::adc::Adc, - Channel::Channel4, - adc_mux - )); - - let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let grant_temperature = - board_kernel.create_grant(capsules::temperature::DRIVER_NUM, &grant_cap); - - let temp = static_init!( - capsules::temperature::TemperatureSensor<'static>, - capsules::temperature::TemperatureSensor::new(temp_sensor, grant_temperature) - ); - kernel::hil::sensors::TemperatureDriver::set_client(temp_sensor, temp); + .finalize(components::adc_mux_component_static!(Adc)); + + let temp_sensor = components::temperature_rp2040::TemperatureRp2040Component::new( + adc_mux, + Channel::Channel4, + 1.721, + 0.706, + ) + .finalize(components::temperature_rp2040_adc_component_static!( + rp2040::adc::Adc + )); + + let temp = components::temperature::TemperatureComponent::new( + board_kernel, + capsules_extra::temperature::DRIVER_NUM, + temp_sensor, + ) + .finalize(components::temperature_component_static!( + TemperatureRp2040Sensor + )); //set CLK, MOSI and CS pins in SPI mode let spi_clk = peripherals.pins.get_pin(RPGpio::GPIO18); @@ -403,113 +462,174 @@ pub unsafe fn main() { spi_clk.set_function(GpioFunction::SPI); spi_csn.set_function(GpioFunction::SPI); spi_mosi.set_function(GpioFunction::SPI); - let mux_spi = components::spi::SpiMuxComponent::new(&peripherals.spi0, dynamic_deferred_caller) - .finalize(components::spi_mux_component_helper!(Spi)); + let mux_spi = components::spi::SpiMuxComponent::new(&peripherals.spi0) + .finalize(components::spi_mux_component_static!(Spi)); let bus = components::bus::SpiMasterBusComponent::new( + mux_spi, + peripherals.pins.get_pin(RPGpio::GPIO17), 20_000_000, kernel::hil::spi::ClockPhase::SampleLeading, kernel::hil::spi::ClockPolarity::IdleLow, ) - .finalize(components::spi_bus_component_helper!( - // spi type - Spi, - // chip select - &peripherals.pins.get_pin(RPGpio::GPIO17), - // spi mux - mux_spi - )); + .finalize(components::spi_bus_component_static!(Spi)); - let tft = components::st77xx::ST77XXComponent::new(mux_alarm).finalize( - components::st77xx_component_helper!( - // screen - &capsules::st77xx::ST7789H2, - // bus type - capsules::bus::SpiMasterBus<'static, VirtualSpiMasterDevice<'static, Spi>>, - // bus - &bus, - // timer type - RPTimer, - // pin type - RPGpioPin, - // dc pin (optional) - Some(peripherals.pins.get_pin(RPGpio::GPIO16)), - // reset pin - None - ), - ); + let tft = components::st77xx::ST77XXComponent::new( + mux_alarm, + bus, + Some(peripherals.pins.get_pin(RPGpio::GPIO16)), + None, + &capsules_extra::st77xx::ST7789H2, + ) + .finalize(components::st77xx_component_static!( + // bus type + capsules_extra::bus::SpiMasterBus< + 'static, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice<'static, Spi>, + >, + // timer type + RPTimer, + // pin type + RPGpioPin, + )); let _ = tft.init(); let button = components::button::ButtonComponent::new( board_kernel, - capsules::button::DRIVER_NUM, + capsules_core::button::DRIVER_NUM, components::button_component_helper!( RPGpioPin, ( - &peripherals.pins.get_pin(RPGpio::GPIO12), + peripherals.pins.get_pin(RPGpio::GPIO12), kernel::hil::gpio::ActivationMode::ActiveLow, kernel::hil::gpio::FloatingState::PullUp ), // A ( - &peripherals.pins.get_pin(RPGpio::GPIO13), + peripherals.pins.get_pin(RPGpio::GPIO13), kernel::hil::gpio::ActivationMode::ActiveLow, kernel::hil::gpio::FloatingState::PullUp ), // B ( - &peripherals.pins.get_pin(RPGpio::GPIO14), + peripherals.pins.get_pin(RPGpio::GPIO14), kernel::hil::gpio::ActivationMode::ActiveLow, kernel::hil::gpio::FloatingState::PullUp ), // X ( - &peripherals.pins.get_pin(RPGpio::GPIO15), + peripherals.pins.get_pin(RPGpio::GPIO15), kernel::hil::gpio::ActivationMode::ActiveLow, kernel::hil::gpio::FloatingState::PullUp ), // Y ), ) - .finalize(components::button_component_buf!(RPGpioPin)); + .finalize(components::button_component_static!(RPGpioPin)); let screen = components::screen::ScreenComponent::new( board_kernel, - capsules::screen::DRIVER_NUM, + capsules_extra::screen::DRIVER_NUM, tft, Some(tft), ) - .finalize(components::screen_buffer_size!(57600)); + .finalize(components::screen_component_static!(57600)); - let adc_channel_0 = components::adc::AdcComponent::new(&adc_mux, Channel::Channel0) - .finalize(components::adc_component_helper!(Adc)); + let adc_channel_0 = components::adc::AdcComponent::new(adc_mux, Channel::Channel0) + .finalize(components::adc_component_static!(Adc)); - let adc_channel_1 = components::adc::AdcComponent::new(&adc_mux, Channel::Channel1) - .finalize(components::adc_component_helper!(Adc)); + let adc_channel_1 = components::adc::AdcComponent::new(adc_mux, Channel::Channel1) + .finalize(components::adc_component_static!(Adc)); - let adc_channel_2 = components::adc::AdcComponent::new(&adc_mux, Channel::Channel2) - .finalize(components::adc_component_helper!(Adc)); + let adc_channel_2 = components::adc::AdcComponent::new(adc_mux, Channel::Channel2) + .finalize(components::adc_component_static!(Adc)); let adc_syscall = - components::adc::AdcVirtualComponent::new(board_kernel, capsules::adc::DRIVER_NUM) + components::adc::AdcVirtualComponent::new(board_kernel, capsules_core::adc::DRIVER_NUM) .finalize(components::adc_syscall_component_helper!( adc_channel_0, adc_channel_1, adc_channel_2, )); - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); - PROCESS_PRINTER = Some(process_printer); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); + PROCESS_PRINTER = Some(process_printer); // PROCESS CONSOLE let process_console = components::process_console::ProcessConsoleComponent::new( board_kernel, uart_mux, mux_alarm, process_printer, + Some(cortexm0p::support::reset), ) - .finalize(components::process_console_component_helper!(RPTimer)); + .finalize(components::process_console_component_static!(RPTimer)); let _ = process_console.start(); - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); + + //-------------------------------------------------------------------------- + // BUZZER + //-------------------------------------------------------------------------- + use kernel::hil::buzzer::Buzzer; + use kernel::hil::time::Alarm; + + let mux_pwm = components::pwm::PwmMuxComponent::new(&peripherals.pwm) + .finalize(components::pwm_mux_component_static!(rp2040::pwm::Pwm)); + + let virtual_pwm_buzzer = + components::pwm::PwmPinUserComponent::new(mux_pwm, rp2040::gpio::RPGpio::GPIO2) + .finalize(components::pwm_pin_user_component_static!(rp2040::pwm::Pwm)); + + let virtual_alarm_buzzer = static_init!( + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + rp2040::timer::RPTimer, + >, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm::new(mux_alarm) + ); + + virtual_alarm_buzzer.setup(); + + let pwm_buzzer = static_init!( + capsules_extra::buzzer_pwm::PwmBuzzer< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + rp2040::timer::RPTimer, + >, + capsules_core::virtualizers::virtual_pwm::PwmPinUser<'static, rp2040::pwm::Pwm>, + >, + capsules_extra::buzzer_pwm::PwmBuzzer::new( + virtual_pwm_buzzer, + virtual_alarm_buzzer, + capsules_extra::buzzer_pwm::DEFAULT_MAX_BUZZ_TIME_MS, + ) + ); + + let buzzer_driver = static_init!( + capsules_extra::buzzer_driver::Buzzer< + 'static, + capsules_extra::buzzer_pwm::PwmBuzzer< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + rp2040::timer::RPTimer, + >, + capsules_core::virtualizers::virtual_pwm::PwmPinUser<'static, rp2040::pwm::Pwm>, + >, + >, + capsules_extra::buzzer_driver::Buzzer::new( + pwm_buzzer, + capsules_extra::buzzer_driver::DEFAULT_MAX_BUZZ_TIME_MS, + board_kernel.create_grant( + capsules_extra::buzzer_driver::DRIVER_NUM, + &memory_allocation_capability + ) + ) + ); + + pwm_buzzer.set_client(buzzer_driver); + + virtual_alarm_buzzer.set_alarm_client(pwm_buzzer); let pico_explorer_base = PicoExplorerBase { ipc: kernel::ipc::IPC::new( @@ -523,10 +643,9 @@ pub unsafe fn main() { console, adc: adc_syscall, temperature: temp, - + buzzer_driver, button, screen, - scheduler, systick: cortexm0p::systick::SysTick::new_with_calibration(125_000_000), }; @@ -543,7 +662,7 @@ pub unsafe fn main() { ); debug!("Initialization complete. Enter main loop"); - /// These symbols are defined in the linker script. + // These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -559,14 +678,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_management_capability, ) @@ -575,10 +694,14 @@ pub unsafe fn main() { debug!("{:?}", err); }); - board_kernel.kernel_loop( - &pico_explorer_base, - chip, - Some(&pico_explorer_base.ipc), - &main_loop_capability, - ); + (board_kernel, pico_explorer_base, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); } diff --git a/boards/qemu_rv32_virt/Cargo.toml b/boards/qemu_rv32_virt/Cargo.toml new file mode 100644 index 0000000000..d8460e259a --- /dev/null +++ b/boards/qemu_rv32_virt/Cargo.toml @@ -0,0 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[package] +name = "qemu_rv32_virt" +version.workspace = true +authors.workspace = true +edition.workspace = true +build = "../build.rs" + +[dependencies] +components = { path = "../components" } +sifive = { path = "../../chips/sifive" } +rv32i = { path = "../../arch/rv32i" } +kernel = { path = "../../kernel" } +qemu_rv32_virt_chip = { path = "../../chips/qemu_rv32_virt_chip" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/qemu_rv32_virt/Makefile b/boards/qemu_rv32_virt/Makefile new file mode 100644 index 0000000000..61327ab577 --- /dev/null +++ b/boards/qemu_rv32_virt/Makefile @@ -0,0 +1,101 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +# Makefile for building the Tock kernel for the qemu-system-riscv32 `virt` +# platform / machine type. + +TARGET = riscv32imac-unknown-none-elf +PLATFORM = qemu_rv32_virt + +include ../Makefile.common + +QEMU_CMD := qemu-system-riscv32 +WORKING_QEMU_VERSION := 7.2.0 + +# Whether a VirtIO network device shall be attached to the QEMU +# machine, and which backend should be used. The following options are +# available: +# +# - NETDEV: SLIRP +# +# Use the QEMU userspace slirp network backend. This causes QEMU to +# behave as a NAT-router and gateway to the VM, transparently +# routing any outgoing traffic through the host's userspace network +# sockets. This option also accepts an optional NETDEV_SLIRP_ARGS +# which is appended to the provided string. +# +# To forward TCP port 1234 on the emulated Tock device (having IP +# 192.168.1.50) to the host port 5678, set the following variable: +# +# NETDEV_SLIRP_ARGS=hostfwd=tcp::5678-192.168.1.50:1234 +# +# - NETDEV: TAP +# +# Creates a TAP network interface to act as a layer-2 Ethernet +# connection between the guest interface and the host. Must have the +# proper permissions to let QEMU create the tap interface on the +# host. Use SUDO-TAP instead to run QEMU through `sudo`. +NETDEV ?= NONE +ifneq ($(NETDEV_SLIRP_ARGS),) + NETDEV_SLIRP_ARGS_INT := ,$(NETDEV_SLIRP_ARGS) +else + NETDEV_SLIRP_ARGS_INT := +endif + +ifeq ($(NETDEV),NONE) + QEMU_NETDEV_CMDLINE = "" +else ifeq ($(NETDEV),SLIRP) + QEMU_NETDEV_CMDLINE = \ + -netdev user,id=n0,net=192.168.1.0/24,dhcpstart=192.168.1.255$(NETDEV_SLIRP_ARGS_INT) \ + -device virtio-net-device,netdev=n0 +else ifneq (,$(filter $(NETDEV),TAP SUDO-TAP)) + QEMU_NETDEV_CMDLINE = \ + -netdev tap,id=n0,script=no,downscript=no \ + -device virtio-net-device,netdev=n0 + ifeq ($(NETDEV),SUDO-TAP) + QEMU_CMD := sudo $(QEMU_CMD) + endif +else + $(error Invalid argument provided for variable NETDEV) +endif + +# Peripherals attached by default: +# - 16550 UART (attached to stdio by default) +# - VirtIO EntropySource (default backend /dev/random) +QEMU_BASE_CMDLINE := \ + $(QEMU_CMD) \ + -machine virt \ + -semihosting \ + -global driver=riscv-cpu,property=smepmp,value=true \ + -global virtio-mmio.force-legacy=false \ + -device virtio-rng-device \ + $(QEMU_NETDEV_CMDLINE) \ + -nographic + +# Run the kernel inside a qemu-riscv32-system "virt" machine type simulation +.PHONY: run +run: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf + @echo + @echo -e "Running $$(qemu-system-riscv32 --version | head -n1)"\ + "(tested: $(WORKING_QEMU_VERSION)) with\n"\ + " - kernel $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf" + @echo "To exit type C-a x" + @echo + $(QEMU_BASE_CMDLINE) \ + -bios $< + +# Same as `run`, but load an application specified by $(APP) into the respective +# memory location. +.PHONY: run-app +run-app: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf + @echo + @echo -e "Running $$(qemu-system-riscv32 --version | head -n1)"\ + "(tested: $(WORKING_QEMU_VERSION)) with\n"\ + " - kernel $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf\n"\ + " - app $(APP)" + @echo "To exit type C-a x" + @echo + $(QEMU_BASE_CMDLINE) \ + -bios $< \ + -device loader,file=$(APP),addr=0x80100000 diff --git a/boards/qemu_rv32_virt/README.md b/boards/qemu_rv32_virt/README.md new file mode 100644 index 0000000000..8043596356 --- /dev/null +++ b/boards/qemu_rv32_virt/README.md @@ -0,0 +1,91 @@ +QEMU RISC-V 32 bit `virt` Platform +================================== + +This board crate targets the QEMU RISC-V 32 bit `virt` platform. It +can utilize paravirtualized peripherals through the VirtIO +transport. Currently supported periphals of this board are: + +- the primary 16550-compatible UART +- VirtIO-based network adapters +- VirtIO-based random number generators + +While this target does not feature many peripherals for now, it represents a +stable QEMU target for using Tock in a virtualized RISC-V environment. This can +be useful for CI and other purposes. In the future, this target can be extended +to support VirtIO peripherals. + +Starting from at least QEMU v7.0.0 up to and including v7.1.0, QEMU cotained a +bug which caused spurious memory access faults raised by the emulated Physical +Memory Protection (PMP), part of the emulated RISC-V CPU core. Therefore, **this +board requires at least QEMU v7.2.0** to function properly. Symptomps of the +aforementioned bug are crashes of userspace processes with a memory-access fault +reported by the kernel. + +Running QEMU +------------ + +To run the board in QEMU, `qemu-system-riscv32` must be started with the +`-machine virt` argument. The Tock kernel expects to be loaded as the BIOS by +passing `-bios $TOCK_KERNEL.bin`, such that it runs in RISC-V machine mode and +has full control over the virtual board. `-nographic` can be used to suppress +QEMU's graphical interface. + +The [`Makefile`] further contains two targets for running this board's kernel in +QEMU standalone, or with a single app. These can be executed through the +**`run`** and **`run-app`** targets, respectively. + +- **`run`**: Start Tock on an emulated QEMU board without an app: + + ``` + tock/boards/qemu_rv32_virt $ make run + Finished release [optimized + debuginfo] target(s) in 0.05s + text data bss dec hex filename + 64880 12 11248 76140 1296c tock/target/riscv32imac-unknown-none-elf/release/qemu_rv32_virt + a9de4df9486d724e6bf6a3423af669903dfd2bd1fd65c1dd867ddf9d7bcbec9b tock/target/riscv32imac-unknown-none-elf/release/qemu_rv32_virt.bin + + Running QEMU emulator version 7.0.0 (tested: 7.0.0) with + - kernel tock/target/riscv32imac-unknown-none-elf/release/qemu_rv32_virt.bin + To exit type C-a x + + qemu-system-riscv32 \ + -machine virt \ + -bios tock/target/riscv32imac-unknown-none-elf/release/qemu_rv32_virt.bin \ + -global virtio-mmio.force-legacy=false \ + -device virtio-rng-device \ + -nographic + QEMU RISC-V 32-bit "virt" machine, initialization complete. + Entering main loop. + ``` + +- **`run`**: Start Tock on an emulated QEMU board without an app: + + ``` + tock/boards/qemu_rv32_virt $ make run-app APP=$PATH_TO_APP.tbf + ``` + +Through the **`NETDEV`** environment variable, QEMU can be instructed to attach +a VirtIO-based network adapter to the target. The following options are available: + +- `NETDEV=NONE` (default): Do not expose a network adapter to the guest. + +- `NETDEV=SLIRP`: Use QEMU's userspace networking capabilities (through + `libslirp`), which provides the target with an emulated network and a gateway + bridging outgoing TCP and UDP connections onto sockets of the host operating + system. `NETDEV_SLIRP_ARGS` can be used to pass further arguments to the + `netdev`, for instance to forward ports from host to guest. For example, to + forward the TCP port `8080` to the guest at `192.168.1.50:80`, use the + following command line: + + ``` + $ make run NETDEV=SLIRP NETDEV_SLIRP_ARGS=hostfwd=tcp::8080-192.168.1.50:80 + ``` + +- `NETDEV=TAP`: Create a TAP network device on the host and expose the + corresponding remote end to the guest's VirtIO network card. This establishes + a layer-2 link between the host and guest. This option assumes that QEMU has + the necessary permissions to use (or create) the device on the host. The + interface will be unconfigured and needs to be made active and be assigned an + IP address manually. + +- `NETDEV=SUDO-TAP`: Like `TAP`, but run QEMU as root through `sudo`. This will + likely prompt for a password. diff --git a/boards/qemu_rv32_virt/layout.ld b/boards/qemu_rv32_virt/layout.ld new file mode 100644 index 0000000000..6c477a76ae --- /dev/null +++ b/boards/qemu_rv32_virt/layout.ld @@ -0,0 +1,33 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + +/** + * QEMU emulated DRAM region. Tock is currently designed to be placed + * at the start of DRAM, using the `-bios` option in qemu-system-riscv32. + * + * We are using 4MB of RAM, which easily fits into the 128MB default + * assignment of QEMU, and we can have compact VMs with `-m 4MB` + */ + +MEMORY +{ + rom (rx) : ORIGIN = 0x80000000, LENGTH = 0x100000 + prog (rx) : ORIGIN = 0x80100000, LENGTH = 0x100000 + ram (rwx) : ORIGIN = 0x80200000, LENGTH = 0x200000 +} + +SECTIONS { + /* Export the start & end of SRAM and flash as symbols for setting + * up the ePMP. Flash includes rom, prog and flash storage, such + * that we can use a single NAPOT region. The .text section will + * be made executable by a separate PMP region. + */ + _sflash = ORIGIN(rom); + _eflash = ORIGIN(prog) + LENGTH(prog); + + _ssram = ORIGIN(ram); + _esram = ORIGIN(ram) + LENGTH(ram); +} + +INCLUDE ../kernel_layout.ld diff --git a/boards/qemu_rv32_virt/src/io.rs b/boards/qemu_rv32_virt/src/io.rs new file mode 100644 index 0000000000..db52e4be65 --- /dev/null +++ b/boards/qemu_rv32_virt/src/io.rs @@ -0,0 +1,59 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use core::fmt::Write; +use core::panic::PanicInfo; +use core::str; + +use kernel::debug; +use kernel::debug::IoWrite; + +use crate::CHIP; +use crate::PROCESSES; +use crate::PROCESS_PRINTER; + +struct Writer {} + +static mut WRITER: Writer = Writer {}; + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> ::core::fmt::Result { + self.write(s.as_bytes()); + Ok(()) + } +} + +impl IoWrite for Writer { + fn write(&mut self, buf: &[u8]) -> usize { + let uart = qemu_rv32_virt_chip::uart::Uart16550::new(qemu_rv32_virt_chip::uart::UART0_BASE); + uart.transmit_sync(buf); + buf.len() + } +} + +/// Panic handler. +#[cfg(not(test))] +#[no_mangle] +#[panic_handler] +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { + use core::ptr::{addr_of, addr_of_mut}; + + let writer = &mut *addr_of_mut!(WRITER); + + debug::panic_print::<_, _, _>( + writer, + pi, + &rv32i::support::nop, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), + ); + + // The system is no longer in a well-defined state. Use + // semihosting commands to exit QEMU with a return code of 1. + rv32i::semihost_command(0x18, 1, 0); + + // To satisfy the ! return type constraints. + loop {} +} diff --git a/boards/qemu_rv32_virt/src/main.rs b/boards/qemu_rv32_virt/src/main.rs new file mode 100644 index 0000000000..5e074b7d38 --- /dev/null +++ b/boards/qemu_rv32_virt/src/main.rs @@ -0,0 +1,576 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Board file for qemu-system-riscv32 "virt" machine type + +#![no_std] +// Disable this attribute when documenting, as a workaround for +// https://github.com/rust-lang/rust/issues/62184. +#![cfg_attr(not(doc), no_main)] + +use core::ptr::addr_of; +use core::ptr::addr_of_mut; + +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use kernel::capabilities; +use kernel::component::Component; +use kernel::hil; +use kernel::platform::scheduler_timer::VirtualSchedulerTimer; +use kernel::platform::KernelResources; +use kernel::platform::SyscallDriverLookup; +use kernel::scheduler::cooperative::CooperativeSched; +use kernel::utilities::registers::interfaces::ReadWriteable; +use kernel::{create_capability, debug, static_init}; +use qemu_rv32_virt_chip::chip::{QemuRv32VirtChip, QemuRv32VirtDefaultPeripherals}; +use rv32i::csr; + +pub mod io; + +pub const NUM_PROCS: usize = 4; + +// Actual memory for holding the active process structures. Need an empty list +// at least. +static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = + [None; NUM_PROCS]; + +// Reference to the chip for panic dumps. +static mut CHIP: Option<&'static QemuRv32VirtChip> = None; + +// Reference to the process printer for panic dumps. +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; + +// How should the kernel respond when a process faults. +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; + +/// Dummy buffer that causes the linker to reserve enough space for the stack. +#[no_mangle] +#[link_section = ".stack_buffer"] +pub static mut STACK_MEMORY: [u8; 0x8000] = [0; 0x8000]; + +/// A structure representing this platform that holds references to all +/// capsules for this platform. We've included an alarm and console. +struct QemuRv32VirtPlatform { + pconsole: &'static capsules_core::process_console::ProcessConsole< + 'static, + { capsules_core::process_console::DEFAULT_COMMAND_HISTORY_LEN }, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + qemu_rv32_virt_chip::chip::QemuRv32VirtClint<'static>, + >, + components::process_console::Capability, + >, + console: &'static capsules_core::console::Console<'static>, + lldb: &'static capsules_core::low_level_debug::LowLevelDebug< + 'static, + capsules_core::virtualizers::virtual_uart::UartDevice<'static>, + >, + alarm: &'static capsules_core::alarm::AlarmDriver< + 'static, + VirtualMuxAlarm<'static, qemu_rv32_virt_chip::chip::QemuRv32VirtClint<'static>>, + >, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + scheduler: &'static CooperativeSched<'static>, + scheduler_timer: &'static VirtualSchedulerTimer< + VirtualMuxAlarm<'static, qemu_rv32_virt_chip::chip::QemuRv32VirtClint<'static>>, + >, + virtio_rng: Option< + &'static capsules_core::rng::RngDriver< + 'static, + qemu_rv32_virt_chip::virtio::devices::virtio_rng::VirtIORng<'static, 'static>, + >, + >, +} + +/// Mapping of integer syscalls to objects that implement syscalls. +impl SyscallDriverLookup for QemuRv32VirtPlatform { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::low_level_debug::DRIVER_NUM => f(Some(self.lldb)), + capsules_core::rng::DRIVER_NUM => { + if let Some(rng_driver) = self.virtio_rng { + f(Some(rng_driver)) + } else { + f(None) + } + } + kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), + _ => f(None), + } + } +} + +impl + KernelResources< + qemu_rv32_virt_chip::chip::QemuRv32VirtChip< + 'static, + QemuRv32VirtDefaultPeripherals<'static>, + >, + > for QemuRv32VirtPlatform +{ + type SyscallDriverLookup = Self; + type SyscallFilter = (); + type ProcessFault = (); + type Scheduler = CooperativeSched<'static>; + type SchedulerTimer = VirtualSchedulerTimer< + VirtualMuxAlarm<'static, qemu_rv32_virt_chip::chip::QemuRv32VirtClint<'static>>, + >; + type WatchDog = (); + type ContextSwitchCallback = (); + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + &() + } + fn process_fault(&self) -> &Self::ProcessFault { + &() + } + fn scheduler(&self) -> &Self::Scheduler { + self.scheduler + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + self.scheduler_timer + } + fn watchdog(&self) -> &Self::WatchDog { + &() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + &() + } +} + +/// This is in a separate, inline(never) function so that its stack frame is +/// removed when this function returns. Otherwise, the stack space used for +/// these static_inits is wasted. +#[inline(never)] +unsafe fn start() -> ( + &'static kernel::Kernel, + QemuRv32VirtPlatform, + &'static qemu_rv32_virt_chip::chip::QemuRv32VirtChip< + 'static, + QemuRv32VirtDefaultPeripherals<'static>, + >, +) { + // These symbols are defined in the linker script. + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + /// The start of the kernel text (Included only for kernel PMP) + static _stext: u8; + /// The end of the kernel text (Included only for kernel PMP) + static _etext: u8; + /// The start of the kernel / app / storage flash (Included only for kernel PMP) + static _sflash: u8; + /// The end of the kernel / app / storage flash (Included only for kernel PMP) + static _eflash: u8; + /// The start of the kernel / app RAM (Included only for kernel PMP) + static _ssram: u8; + /// The end of the kernel / app RAM (Included only for kernel PMP) + static _esram: u8; + } + + // ---------- BASIC INITIALIZATION ----------- + + // Basic setup of the RISC-V IMAC platform + rv32i::configure_trap_handler(); + + // Set up memory protection immediately after setting the trap handler, to + // ensure that much of the board initialization routine runs with ePMP + // protection. + let epmp = rv32i::pmp::kernel_protection_mml_epmp::KernelProtectionMMLEPMP::new( + rv32i::pmp::kernel_protection_mml_epmp::FlashRegion( + rv32i::pmp::NAPOTRegionSpec::new( + core::ptr::addr_of!(_sflash), + core::ptr::addr_of!(_eflash) as usize - core::ptr::addr_of!(_sflash) as usize, + ) + .unwrap(), + ), + rv32i::pmp::kernel_protection_mml_epmp::RAMRegion( + rv32i::pmp::NAPOTRegionSpec::new( + core::ptr::addr_of!(_ssram), + core::ptr::addr_of!(_esram) as usize - core::ptr::addr_of!(_ssram) as usize, + ) + .unwrap(), + ), + rv32i::pmp::kernel_protection_mml_epmp::MMIORegion( + rv32i::pmp::NAPOTRegionSpec::new( + core::ptr::null::(), // start + 0x20000000, // size + ) + .unwrap(), + ), + rv32i::pmp::kernel_protection_mml_epmp::KernelTextRegion( + rv32i::pmp::TORRegionSpec::new( + core::ptr::addr_of!(_stext), + core::ptr::addr_of!(_etext), + ) + .unwrap(), + ), + ) + .unwrap(); + + // Acquire required capabilities + let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability); + let memory_allocation_cap = create_capability!(capabilities::MemoryAllocationCapability); + + // Create a board kernel instance + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); + + // ---------- QEMU-SYSTEM-RISCV32 "virt" MACHINE PERIPHERALS ---------- + + let peripherals = static_init!( + QemuRv32VirtDefaultPeripherals, + QemuRv32VirtDefaultPeripherals::new(), + ); + + // Create a shared UART channel for the console and for kernel + // debug over the provided memory-mapped 16550-compatible + // UART. + let uart_mux = components::console::UartMuxComponent::new(&peripherals.uart0, 115200) + .finalize(components::uart_mux_component_static!()); + + // Use the RISC-V machine timer timesource + let hardware_timer = static_init!( + qemu_rv32_virt_chip::chip::QemuRv32VirtClint, + qemu_rv32_virt_chip::chip::QemuRv32VirtClint::new(&qemu_rv32_virt_chip::clint::CLINT_BASE) + ); + + // Create a shared virtualization mux layer on top of a single hardware + // alarm. + let mux_alarm = static_init!( + MuxAlarm<'static, qemu_rv32_virt_chip::chip::QemuRv32VirtClint>, + MuxAlarm::new(hardware_timer) + ); + hil::time::Alarm::set_alarm_client(hardware_timer, mux_alarm); + + // Virtual alarm for the scheduler + let systick_virtual_alarm = static_init!( + VirtualMuxAlarm<'static, qemu_rv32_virt_chip::chip::QemuRv32VirtClint>, + VirtualMuxAlarm::new(mux_alarm) + ); + systick_virtual_alarm.setup(); + + // Virtual alarm and driver for userspace + let virtual_alarm_user = static_init!( + VirtualMuxAlarm<'static, qemu_rv32_virt_chip::chip::QemuRv32VirtClint>, + VirtualMuxAlarm::new(mux_alarm) + ); + virtual_alarm_user.setup(); + + let alarm = static_init!( + capsules_core::alarm::AlarmDriver< + 'static, + VirtualMuxAlarm<'static, qemu_rv32_virt_chip::chip::QemuRv32VirtClint>, + >, + capsules_core::alarm::AlarmDriver::new( + virtual_alarm_user, + board_kernel.create_grant(capsules_core::alarm::DRIVER_NUM, &memory_allocation_cap) + ) + ); + hil::time::Alarm::set_alarm_client(virtual_alarm_user, alarm); + + // ---------- VIRTIO PERIPHERAL DISCOVERY ---------- + // + // This board has 8 virtio-mmio (v2 personality required!) devices + // + // Collect supported VirtIO peripheral indicies and initialize them if they + // are found. If there are two instances of a supported peripheral, the one + // on a higher-indexed VirtIO transport is used. + let (mut virtio_net_idx, mut virtio_rng_idx) = (None, None); + for (i, virtio_device) in peripherals.virtio_mmio.iter().enumerate() { + use qemu_rv32_virt_chip::virtio::devices::VirtIODeviceType; + match virtio_device.query() { + Some(VirtIODeviceType::NetworkCard) => { + virtio_net_idx = Some(i); + } + Some(VirtIODeviceType::EntropySource) => { + virtio_rng_idx = Some(i); + } + _ => (), + } + } + + // If there is a VirtIO EntropySource present, use the appropriate VirtIORng + // driver and expose it to userspace though the RngDriver + let virtio_rng_driver: Option< + &'static capsules_core::rng::RngDriver< + 'static, + qemu_rv32_virt_chip::virtio::devices::virtio_rng::VirtIORng<'static, 'static>, + >, + > = if let Some(rng_idx) = virtio_rng_idx { + use kernel::hil::rng::Rng; + use qemu_rv32_virt_chip::virtio::devices::virtio_rng::VirtIORng; + use qemu_rv32_virt_chip::virtio::queues::split_queue::{ + SplitVirtqueue, VirtqueueAvailableRing, VirtqueueDescriptors, VirtqueueUsedRing, + }; + use qemu_rv32_virt_chip::virtio::queues::Virtqueue; + use qemu_rv32_virt_chip::virtio::transports::VirtIOTransport; + + // EntropySource requires a single Virtqueue for retrieved entropy + let descriptors = static_init!(VirtqueueDescriptors<1>, VirtqueueDescriptors::default(),); + let available_ring = + static_init!(VirtqueueAvailableRing<1>, VirtqueueAvailableRing::default(),); + let used_ring = static_init!(VirtqueueUsedRing<1>, VirtqueueUsedRing::default(),); + let queue = static_init!( + SplitVirtqueue<1>, + SplitVirtqueue::new(descriptors, available_ring, used_ring), + ); + queue.set_transport(&peripherals.virtio_mmio[rng_idx]); + + // VirtIO EntropySource device driver instantiation + let rng = static_init!(VirtIORng, VirtIORng::new(queue)); + kernel::deferred_call::DeferredCallClient::register(rng); + queue.set_client(rng); + + // Register the queues and driver with the transport, so interrupts + // are routed properly + let mmio_queues = static_init!([&'static dyn Virtqueue; 1], [queue; 1]); + peripherals.virtio_mmio[rng_idx] + .initialize(rng, mmio_queues) + .unwrap(); + + // Provide an internal randomness buffer + let rng_buffer = static_init!([u8; 64], [0; 64]); + rng.provide_buffer(rng_buffer) + .expect("rng: providing initial buffer failed"); + + // Userspace RNG driver over the VirtIO EntropySource + let rng_driver = static_init!( + capsules_core::rng::RngDriver, + capsules_core::rng::RngDriver::new( + rng, + board_kernel.create_grant(capsules_core::rng::DRIVER_NUM, &memory_allocation_cap), + ), + ); + rng.set_client(rng_driver); + + Some(rng_driver as &'static capsules_core::rng::RngDriver) + } else { + // No VirtIO EntropySource discovered + None + }; + + // If there is a VirtIO NetworkCard present, use the appropriate VirtIONet + // driver. Currently this is not used, as work on the userspace network + // driver and kernel network stack is in progress. + // + // A template dummy driver is provided to verify basic functionality of this + // interface. + let _virtio_net_if: Option< + &'static qemu_rv32_virt_chip::virtio::devices::virtio_net::VirtIONet<'static>, + > = if let Some(net_idx) = virtio_net_idx { + use qemu_rv32_virt_chip::virtio::devices::virtio_net::VirtIONet; + use qemu_rv32_virt_chip::virtio::queues::split_queue::{ + SplitVirtqueue, VirtqueueAvailableRing, VirtqueueDescriptors, VirtqueueUsedRing, + }; + use qemu_rv32_virt_chip::virtio::queues::Virtqueue; + use qemu_rv32_virt_chip::virtio::transports::VirtIOTransport; + + // A VirtIO NetworkCard requires 2 Virtqueues: + // - a TX Virtqueue with buffers for outgoing packets + // - a RX Virtqueue where incoming packet buffers are + // placed and filled by the device + + // TX Virtqueue + let tx_descriptors = + static_init!(VirtqueueDescriptors<2>, VirtqueueDescriptors::default(),); + let tx_available_ring = + static_init!(VirtqueueAvailableRing<2>, VirtqueueAvailableRing::default(),); + let tx_used_ring = static_init!(VirtqueueUsedRing<2>, VirtqueueUsedRing::default(),); + let tx_queue = static_init!( + SplitVirtqueue<2>, + SplitVirtqueue::new(tx_descriptors, tx_available_ring, tx_used_ring), + ); + tx_queue.set_transport(&peripherals.virtio_mmio[net_idx]); + + // RX Virtqueue + let rx_descriptors = + static_init!(VirtqueueDescriptors<2>, VirtqueueDescriptors::default(),); + let rx_available_ring = + static_init!(VirtqueueAvailableRing<2>, VirtqueueAvailableRing::default(),); + let rx_used_ring = static_init!(VirtqueueUsedRing<2>, VirtqueueUsedRing::default(),); + let rx_queue = static_init!( + SplitVirtqueue<2>, + SplitVirtqueue::new(rx_descriptors, rx_available_ring, rx_used_ring), + ); + rx_queue.set_transport(&peripherals.virtio_mmio[net_idx]); + + // Incoming and outgoing packets are prefixed by a 12-byte + // VirtIO specific header + let tx_header_buf = static_init!([u8; 12], [0; 12]); + let rx_header_buf = static_init!([u8; 12], [0; 12]); + + // Currently, provide a single receive buffer to write + // incoming packets into + let rx_buffer = static_init!([u8; 1526], [0; 1526]); + + // Instantiate the VirtIONet (NetworkCard) driver and set + // the queues + let virtio_net = static_init!( + VirtIONet<'static>, + VirtIONet::new( + 0, + tx_queue, + tx_header_buf, + rx_queue, + rx_header_buf, + rx_buffer, + ), + ); + tx_queue.set_client(virtio_net); + rx_queue.set_client(virtio_net); + + // Register the queues and driver with the transport, so + // interrupts are routed properly + let mmio_queues = static_init!([&'static dyn Virtqueue; 2], [rx_queue, tx_queue]); + peripherals.virtio_mmio[net_idx] + .initialize(virtio_net, mmio_queues) + .unwrap(); + + // Don't forget to enable RX once when integrating this into a + // proper Ethernet stack: + // virtio_net.enable_rx(); + + // TODO: When we have a proper Ethernet driver available for userspace, + // return that. For now, just return a reference to the raw VirtIONet + // driver: + Some(virtio_net as &'static VirtIONet) + } else { + // No VirtIO NetworkCard discovered + None + }; + + // ---------- INITIALIZE CHIP, ENABLE INTERRUPTS --------- + + let chip = static_init!( + QemuRv32VirtChip, + QemuRv32VirtChip::new(peripherals, hardware_timer, epmp), + ); + CHIP = Some(chip); + + // Need to enable all interrupts for Tock Kernel + chip.enable_plic_interrupts(); + + // enable interrupts globally + csr::CSR + .mie + .modify(csr::mie::mie::mext::SET + csr::mie::mie::msoft::SET + csr::mie::mie::mtimer::SET); + csr::CSR.mstatus.modify(csr::mstatus::mstatus::mie::SET); + + // ---------- FINAL SYSTEM INITIALIZATION ---------- + + // Create the process printer used in panic prints, etc. + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); + PROCESS_PRINTER = Some(process_printer); + + // Initialize the kernel's process console. + let pconsole = components::process_console::ProcessConsoleComponent::new( + board_kernel, + uart_mux, + mux_alarm, + process_printer, + None, + ) + .finalize(components::process_console_component_static!( + qemu_rv32_virt_chip::chip::QemuRv32VirtClint + )); + + // Setup the console. + let console = components::console::ConsoleComponent::new( + board_kernel, + capsules_core::console::DRIVER_NUM, + uart_mux, + ) + .finalize(components::console_component_static!()); + // Create the debugger object that handles calls to `debug!()`. + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); + + let lldb = components::lldb::LowLevelDebugComponent::new( + board_kernel, + capsules_core::low_level_debug::DRIVER_NUM, + uart_mux, + ) + .finalize(components::low_level_debug_component_static!()); + + let scheduler = + components::sched::cooperative::CooperativeComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::cooperative_component_static!(NUM_PROCS)); + + let scheduler_timer = static_init!( + VirtualSchedulerTimer< + VirtualMuxAlarm<'static, qemu_rv32_virt_chip::chip::QemuRv32VirtClint<'static>>, + >, + VirtualSchedulerTimer::new(systick_virtual_alarm) + ); + + let platform = QemuRv32VirtPlatform { + pconsole, + console, + alarm, + lldb, + scheduler, + scheduler_timer, + virtio_rng: virtio_rng_driver, + ipc: kernel::ipc::IPC::new( + board_kernel, + kernel::ipc::DRIVER_NUM, + &memory_allocation_cap, + ), + }; + + // Start the process console: + let _ = platform.pconsole.start(); + + debug!("QEMU RISC-V 32-bit \"virt\" machine, initialization complete."); + debug!("Entering main loop."); + + // ---------- PROCESS LOADING, SCHEDULER LOOP ---------- + + kernel::process::load_processes( + board_kernel, + chip, + core::slice::from_raw_parts( + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, + ), + core::slice::from_raw_parts_mut( + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, + ), + &mut *addr_of_mut!(PROCESSES), + &FAULT_RESPONSE, + &process_mgmt_cap, + ) + .unwrap_or_else(|err| { + debug!("Error loading processes!"); + debug!("{:?}", err); + }); + + (board_kernel, platform, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); +} diff --git a/boards/raspberry_pi_pico/Cargo.toml b/boards/raspberry_pi_pico/Cargo.toml index 7eaf3c7979..d38acf1ce0 100644 --- a/boards/raspberry_pi_pico/Cargo.toml +++ b/boards/raspberry_pi_pico/Cargo.toml @@ -1,15 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "raspberry_pi_pico" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] cortexm0p = { path = "../../arch/cortex-m0p" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } rp2040 = { path = "../../chips/rp2040" } components = { path = "../components" } enum_primitive = { path = "../../libraries/enum_primitive" } +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/raspberry_pi_pico/Makefile b/boards/raspberry_pi_pico/Makefile index 3acb2fd82f..e61730a3fc 100644 --- a/boards/raspberry_pi_pico/Makefile +++ b/boards/raspberry_pi_pico/Makefile @@ -1,4 +1,8 @@ -# Makefile for building the tock kernel for the Raspberry Pi Pico board. +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +# Makefile for building the tock kernel for the Pico Explorer Base board. TOCK_ARCH=cortex-m0p TARGET=thumbv6m-none-eabi @@ -7,29 +11,34 @@ PLATFORM=raspberry_pi_pico include ../Makefile.common OPENOCD=openocd -OPENOCD_OPTIONS=-f openocd.cfg +OPENOCD_INTERFACE=swd +OPENOCD_OPTIONS=-f openocd-$(OPENOCD_INTERFACE).cfg + +BOOTSEL_FOLDER?=/media/$(USER)/RPI-RP2 -KERNEL=$(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).elf -KERNEL_WITH_APP=$(TOCK_ROOT_DIRECTORY)/target/$(TARGET)/debug/$(PLATFORM)-app.elf +KERNEL=$(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf +KERNEL_WITH_APP=$(TOCK_ROOT_DIRECTORY)/target/$(TARGET)/release/$(PLATFORM)-app.elf # Default target for installing the kernel. .PHONY: install install: flash -.PHONY: flash-debug -flash-debug: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).elf +.PHONY: flash-openocd +flash-openocd: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf $(OPENOCD) $(OPENOCD_OPTIONS) -c "program $<; verify_image $<; reset; shutdown;" .PHONY: flash -flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf - $(OPENOCD) $(OPENOCD_OPTIONS) -c "program $<; verify_image $<; reset; shutdown;" +flash: $(KERNEL) + elf2uf2-rs $< $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).uf2 + @if [ -d $(BOOTSEL_FOLDER) ]; then cp $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).uf2 "$(BOOTSEL_FOLDER)"; else echo; echo Please edit the BOOTSEL_FOLDER variable to point to you Raspberry Pi Pico Flash Drive Folder; echo You can download and flash $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).uf2; fi .PHONY: program -program: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).bin +program: $(KERNEL) ifeq ($(APP),) $(error Please define the APP variable with the TBF file to flash an application) endif arm-none-eabi-objcopy --update-section .apps=$(APP) $(KERNEL) $(KERNEL_WITH_APP) - $(OPENOCD) $(OPENOCD_OPTIONS) -c "program $(KERNEL_WITH_APP); verify_image $(KERNEL_WITH_APP); reset; shutdown;" + elf2uf2-rs $(KERNEL_WITH_APP) $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.uf2 + @if [ -d $(BOOTSEL_FOLDER) ]; then cp $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.uf2 "$(BOOTSEL_FOLDER)"; else echo; echo Please edit the BOOTSEL_FOLDER variable to point to you Raspberry Pi Pico Flash Drive Folder; echo You can download and flash $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM)-app.uf2; fi diff --git a/boards/raspberry_pi_pico/README.md b/boards/raspberry_pi_pico/README.md index 097280421b..dccf71a112 100644 --- a/boards/raspberry_pi_pico/README.md +++ b/boards/raspberry_pi_pico/README.md @@ -1,7 +1,7 @@ Raspberry Pi Pico - RP2040 ========================== - + The [Raspberry Pi Pico](https://www.raspberrypi.org/products/raspberry-pi-pico/) is a board developed by the Raspberry Pi Foundation and is based on the RP2040 chip. @@ -10,15 +10,60 @@ board developed by the Raspberry Pi Foundation and is based on the RP2040 chip. First, follow the [Tock Getting Started guide](../../doc/Getting_Started.md) +## Installing elf2uf2-rs + +The Nano RP2040 uses UF2 files for flashing. Tock compiles to an ELF file. +The `elf2uf2-rs` utility is needed to transform the Tock ELF file into an UF2 file. + +To install `elf2uf2`, run the commands: + +```bash +$ cargo install elf2uf2-rs +``` + ## Flashing the kernel -The Raspberry Pi Pico can be programmed via an SWD connection, which requires the Pico to be connected to a regular Raspberry Pi device that exposes the necessary pins. The kernel is transferred to the Raspberry Pi Pico using a [custom version of OpenOCD](https://github.com/raspberrypi/openocd). +The Raspberry Pi Pico RP2040 Connect can be programmed using its bootloader, which requires an UF2 file. + +### Enter BOOTSEL mode + +To flash the Pico RP2040, it needs to be put into BOOTSEL mode. This will mount +a flash drive that allows one to copy a UF2 file. To enter BOOTSEL mode, press the BOOTSEL button and hold it while you connect the other end of the micro USB cable to your computer. + +Then `cd` into `boards/raspberry_pi_pico` directory and run: + +```bash +$ make flash + +(or) + +$ make flash-debug +``` + +> Note: The Makefile provides the BOOTSEL_FOLDER variable that points towards the mount point of +> the Pico RP2040 flash drive. By default, this is located in `/media/$(USER)/RP2040`. This might +> be different on several systems, make sure to adjust it. + +## Flashing app + +Enter BOOTSEL mode. + +Apps are built out-of-tree. Once an app is built, you can add the path to it in the Makefile (APP variable), then run: +```bash +$ APP="" make flash-app +``` + +## Debugging + +The Raspberry Pi Pico can also be programmed via an SWD connection, which requires the Pico to be connected to a regular Raspberry Pi device that exposes the necessary pins OR using another Raspberry Pi Pico set up in “Picoprobe” mode. The kernel is transferred to the Raspberry Pi Pico using a [custom version of OpenOCD](https://github.com/raspberrypi/openocd). + +### Flashing Setup -### Raspberry Pi Setup +#### From a regular Raspberry Pi (option 1) To install OpenOCD on the Raspberry Pi run the following commands on the Pi: ```bash -$ sudo apt-get update +$ sudo apt update $ sudo apt install automake autoconf build-essential texinfo libtool libftdi-dev libusb-1.0-0-dev git $ git clone https://github.com/raspberrypi/openocd.git --recursive --branch rp2040 --depth=1 $ cd openocd @@ -26,7 +71,6 @@ $ ./bootstrap $ ./configure --enable-ftdi --enable-sysfsgpio --enable-bcm2835gpio $ make -j4 $ sudo make install -$ cd ~ ``` Enable SSH on the Raspberry Pi by following the [instructions on the Raspberry Pi website](https://www.raspberrypi.org/documentation/remote-access/ssh/). @@ -34,7 +78,45 @@ Enable SSH on the Raspberry Pi by following the [instructions on the Raspberry P Next, connect the SWD pins of the Pico (the tree lower wires) to GND, GPIO 24, and GPIO 25 of the Raspberry Pi. You can follow the schematic in the [official documentation](https://datasheets.raspberrypi.org/pico/getting-started-with-pico.pdf#%5B%7B%22num%22%3A22%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C115%2C431.757%2Cnull%5D) and connect the blue, black, and purple wires. Also connect the other three wires as shown in the schematic, which will connect the Pico UART to the Raspberry Pi. This will enable the serial communication between the two devices. -### Flash the tock kernel + +#### From a Linux Host using a Picoprobe (option 2) + +To install OpenOCD on Debian/Ubuntu run the following commands: +```bash +$ sudo apt update +$ sudo apt install automake autoconf build-essential texinfo libtool libftdi-dev libusb-1.0-0-dev git +$ git clone https://github.com/raspberrypi/openocd.git --recursive --branch rp2040 --depth=1 +$ cd openocd +$ ./bootstrap +$ ./configure --enable-picoprobe +$ make -j4 +$ sudo make install +``` + +Download the Picoprobe UF2 file onto the USB mass storage device presented by the Pico that is going to act as Picoprobe device after plugging it into some USB port: https://datasheets.raspberrypi.com/soft/picoprobe.uf2 (the device should automatically restart after the file has been written). + +Next, connect the SWD pins of the Pico target (the tree lower wires, left-to-right) to GP2, GND, and GP3 of the Pico that will act as Picoprobe. You can follow the schematic in the [official documentation](https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf#%5B%7B%22num%22%3A64%2C%22gen%22%3A0%7D%2C%7B%22name%22%3A%22XYZ%22%7D%2C115%2C696.992%2Cnull%5D) and connect the blue, black, and purple wires. + +Also connect the other four wires as shown in the schematic, which will connect the Pico UART and power to the Picoprobe. This will enable the serial communication between the two devices. + +### Flashing the tock kernel + +#### Building and Deploying from the same System +`cd` into `boards/raspberry_pi_pico` directory and run: + +```bash +$ make flash OPENOCD_INTERFACE=[swd|picoprobe] +``` + +The *OPENOCD_INTERFACE* parameter selects which mode to flash the target Pico device in: `swd` flashes directly via SWD over GPIO (default, needs to be run a regular Raspberry Pi), `picoprobe` flashes indirectly via another Raspberry Pi Pico device. + +You can also open a serial console on to view debug messages: +```bash +$ sudo apt install picocom +$ picocom /dev/ttyACM0 -b 115200 -l +``` + +#### Building on a Desktop/Laptop then Flashing via regular Raspberry Pi `cd` into `boards/raspberry_pi_pico` directory and run: @@ -75,3 +157,8 @@ $ make program ``` This will generate a new ELF file that can be deployed on the Raspberry Pi Pico via gdb and OpenOCD as described in the [section above](#flash-the-tock-kernel). + +## Book + +For further details and examples about how to use Tock with the Raspberry Pi Pico, you might +want to check out the [Getting Started with Secure Embedded Systems](https://link.springer.com/book/10.1007/978-1-4842-7789-8) book. diff --git a/boards/raspberry_pi_pico/build.rs b/boards/raspberry_pi_pico/build.rs deleted file mode 100644 index 3051054fc6..0000000000 --- a/boards/raspberry_pi_pico/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/raspberry_pi_pico/layout.ld b/boards/raspberry_pi_pico/layout.ld index 1d056d62da..ce99f68d6d 100644 --- a/boards/raspberry_pi_pico/layout.ld +++ b/boards/raspberry_pi_pico/layout.ld @@ -1,18 +1,21 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + MEMORY { /* uncomment this to boot from RAM */ /* reset (rx) : ORIGIN = 0x20000000, LENGTH = 16K - rom (rx) : ORIGIN = 0x20000100, LENGTH = 128K + rom (rx) : ORIGIN = 0x20000100, LENGTH = 256K prog (rx) : ORIGIN = 0x20040000, LENGTH = 1K ram (rwx) : ORIGIN = 0x20004000, LENGTH = 240K */ /* boot from Flash */ - rom (rx) : ORIGIN = 0x10000000, LENGTH = 128K - prog (rx) : ORIGIN = 0x10020000, LENGTH = 256K + rom (rx) : ORIGIN = 0x10000000, LENGTH = 256K + prog (rx) : ORIGIN = 0x10040000, LENGTH = 256K ram (rwx) : ORIGIN = 0x20000000, LENGTH = 264K } -MPU_MIN_ALIGN = 8K; PAGE_SIZE = 4K; ENTRY(jump_to_bootloader) @@ -26,4 +29,4 @@ SECTIONS { } > rom } -INCLUDE ../kernel_layout.ld \ No newline at end of file +INCLUDE ../kernel_layout.ld diff --git a/boards/raspberry_pi_pico/openocd-picoprobe.cfg b/boards/raspberry_pi_pico/openocd-picoprobe.cfg new file mode 100644 index 0000000000..3583b9e038 --- /dev/null +++ b/boards/raspberry_pi_pico/openocd-picoprobe.cfg @@ -0,0 +1,6 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +source [find interface/picoprobe.cfg] +source [find target/rp2040.cfg] diff --git a/boards/raspberry_pi_pico/openocd-swd.cfg b/boards/raspberry_pi_pico/openocd-swd.cfg new file mode 100644 index 0000000000..3d107f85c8 --- /dev/null +++ b/boards/raspberry_pi_pico/openocd-swd.cfg @@ -0,0 +1,6 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +source [find interface/raspberrypi-swd.cfg] +source [find target/rp2040.cfg] diff --git a/boards/raspberry_pi_pico/openocd.cfg b/boards/raspberry_pi_pico/openocd.cfg deleted file mode 100644 index 8a1f9c9aca..0000000000 --- a/boards/raspberry_pi_pico/openocd.cfg +++ /dev/null @@ -1,2 +0,0 @@ -source [find interface/raspberrypi-swd.cfg] -source [find target/rp2040.cfg] diff --git a/boards/raspberry_pi_pico/src/flash_bootloader.rs b/boards/raspberry_pi_pico/src/flash_bootloader.rs index 2b720b1bcd..7d09250a0d 100644 --- a/boards/raspberry_pi_pico/src/flash_bootloader.rs +++ b/boards/raspberry_pi_pico/src/flash_bootloader.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + /// Padded bootloader used to boot from flash /// /// The RP2040 chip requires a padded and signed bootloader (RP2040 Datasheet, 2.8 Bootrom Page, page 156) diff --git a/boards/raspberry_pi_pico/src/io.rs b/boards/raspberry_pi_pico/src/io.rs index 0437977005..d4375affe3 100644 --- a/boards/raspberry_pi_pico/src/io.rs +++ b/boards/raspberry_pi_pico/src/io.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; @@ -23,7 +27,26 @@ impl Writer { self.uart.set(uart); } - fn write_to_uart<'a>(&self, uart: &'a Uart, buf: &[u8]) { + fn configure_uart(&self, uart: &Uart) { + if !uart.is_configured() { + let parameters = Parameters { + baud_rate: 115200, + width: Width::Eight, + parity: Parity::None, + stop_bits: StopBits::One, + hw_flow_control: false, + }; + //configure parameters of uart for sending bytes + let _ = uart.configure(parameters); + //set RX and TX pins in UART mode + let gpio_tx = RPGpioPin::new(RPGpio::GPIO0); + let gpio_rx = RPGpioPin::new(RPGpio::GPIO1); + gpio_rx.set_function(GpioFunction::UART); + gpio_tx.set_function(GpioFunction::UART); + } + } + + fn write_to_uart(&self, uart: &Uart, buf: &[u8]) { for &c in buf { uart.send_byte(c); } @@ -43,35 +66,19 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { self.uart.map_or_else( || { - // If no UART is configured for panic print, use UART0 - let uart0 = &Uart::new_uart0(); - - if !uart0.is_configured() { - let parameters = Parameters { - baud_rate: 115200, - width: Width::Eight, - parity: Parity::None, - stop_bits: StopBits::One, - hw_flow_control: false, - }; - //configure parameters of uart for sending bytes - let _result = uart0.configure(parameters); - //set RX and TX pins in UART mode - let gpio_tx = RPGpioPin::new(RPGpio::GPIO0); - let gpio_rx = RPGpioPin::new(RPGpio::GPIO1); - gpio_rx.set_function(GpioFunction::UART); - gpio_tx.set_function(GpioFunction::UART); - } - - self.write_to_uart(uart0, buf); + let uart = Uart::new_uart0(); + self.configure_uart(&uart); + self.write_to_uart(&uart, buf); }, |uart| { + self.configure_uart(uart); self.write_to_uart(uart, buf); }, ); + buf.len() } } @@ -81,19 +88,21 @@ impl IoWrite for Writer { #[cfg(not(test))] #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { - // LED is conneted to GPIO 25 +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { + // LED is connected to GPIO 25 + + use core::ptr::{addr_of, addr_of_mut}; let led_kernel_pin = &RPGpioPin::new(RPGpio::GPIO25); let led = &mut LedHigh::new(led_kernel_pin); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led], writer, pi, &cortexm0p::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/raspberry_pi_pico/src/main.rs b/boards/raspberry_pi_pico/src/main.rs index ca65784a33..7ab8013ca0 100644 --- a/boards/raspberry_pi_pico/src/main.rs +++ b/boards/raspberry_pi_pico/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tock kernel for the Raspberry Pi Pico. //! //! It is based on RP2040SoC SoC (Cortex M0+). @@ -7,25 +11,26 @@ // https://github.com/rust-lang/rust/issues/62184. #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] -#![feature(asm, naked_functions)] -use capsules::i2c_master::I2CMasterDriver; -use capsules::virtual_alarm::VirtualMuxAlarm; +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::i2c_master::I2CMasterDriver; +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; +use components::date_time_component_static; use components::gpio::GpioComponent; use components::led::LedsComponent; use enum_primitive::cast::FromPrimitive; use kernel::component::Component; use kernel::debug; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil::gpio::{Configure, FloatingState}; use kernel::hil::i2c::I2CMaster; use kernel::hil::led::LedHigh; +use kernel::hil::usb::Client; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::scheduler::round_robin::RoundRobinSched; use kernel::syscall::SyscallDriver; use kernel::{capabilities, create_capability, static_init, Kernel}; -use rp2040; use rp2040::adc::{Adc, Channel}; use rp2040::chip::{Rp2040, Rp2040DefaultPeripherals}; use rp2040::clocks::{ @@ -46,7 +51,7 @@ mod flash_bootloader; /// Allocate memory for the stack #[no_mangle] #[link_section = ".stack_buffer"] -pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; +pub static mut STACK_MEMORY: [u8; 0x1500] = [0; 0x1500]; // Manually setting the boot header section that contains the FCB header #[used] @@ -55,7 +60,8 @@ static FLASH_BOOTLOADER: [u8; 256] = flash_bootloader::FLASH_BOOTLOADER; // State for loading and holding applications. // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; // Number of concurrent processes this platform supports. const NUM_PROCS: usize = 4; @@ -64,22 +70,30 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] [None; NUM_PROCS]; static mut CHIP: Option<&'static Rp2040> = None; -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; + +type TemperatureRp2040Sensor = components::temperature_rp2040::TemperatureRp2040ComponentType< + capsules_core::virtualizers::virtual_adc::AdcDevice<'static, rp2040::adc::Adc<'static>>, +>; +type TemperatureDriver = components::temperature::TemperatureComponentType; /// Supported drivers by the platform pub struct RaspberryPiPico { - ipc: kernel::ipc::IPC, - console: &'static capsules::console::Console<'static>, - alarm: &'static capsules::alarm::AlarmDriver< + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + console: &'static capsules_core::console::Console<'static>, + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, VirtualMuxAlarm<'static, rp2040::timer::RPTimer<'static>>, >, - gpio: &'static capsules::gpio::GPIO<'static, RPGpioPin<'static>>, - led: &'static capsules::led::LedDriver<'static, LedHigh<'static, RPGpioPin<'static>>, 1>, - adc: &'static capsules::adc::AdcVirtualized<'static>, - temperature: &'static capsules::temperature::TemperatureSensor<'static>, - i2c: &'static capsules::i2c_master::I2CMasterDriver<'static, I2c<'static>>, - + gpio: &'static capsules_core::gpio::GPIO<'static, RPGpioPin<'static>>, + led: &'static capsules_core::led::LedDriver<'static, LedHigh<'static, RPGpioPin<'static>>, 1>, + adc: &'static capsules_core::adc::AdcVirtualized<'static>, + temperature: &'static TemperatureDriver, + i2c: &'static capsules_core::i2c_master::I2CMasterDriver<'static, I2c<'static, 'static>>, + + date_time: + &'static capsules_extra::date_time::DateTimeCapsule<'static, rp2040::rtc::Rtc<'static>>, scheduler: &'static RoundRobinSched<'static>, systick: cortexm0p::systick::SysTick, } @@ -90,14 +104,15 @@ impl SyscallDriverLookup for RaspberryPiPico { F: FnOnce(Option<&dyn SyscallDriver>) -> R, { match driver_num { - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), - capsules::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), - capsules::adc::DRIVER_NUM => f(Some(self.adc)), - capsules::temperature::DRIVER_NUM => f(Some(self.temperature)), - capsules::i2c_master::DRIVER_NUM => f(Some(self.i2c)), + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temperature)), + capsules_core::i2c_master::DRIVER_NUM => f(Some(self.i2c)), + capsules_extra::date_time::DRIVER_NUM => f(Some(self.date_time)), _ => f(None), } } @@ -113,7 +128,7 @@ impl KernelResources>> for Ras type ContextSwitchCallback = (); fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -135,31 +150,36 @@ impl KernelResources>> for Ras } } -/// Entry point used for debugger -/// -/// When loaded using gdb, the Raspberry Pi Pico is not reset -/// by default. Without this function, gdb sets the PC to the -/// beginning of the flash. This is not correct, as the RP2040 -/// has a more complex boot process. -/// -/// This function is set to be the entry point for gdb and is used -/// to send the RP2040 back in the bootloader so that all the boot -/// sequence is performed. -#[no_mangle] -#[naked] -pub unsafe extern "C" fn jump_to_bootloader() { - asm!( - " +#[allow(dead_code)] +extern "C" { + /// Entry point used for debugger + /// + /// When loaded using gdb, the Raspberry Pi Pico is not reset + /// by default. Without this function, gdb sets the PC to the + /// beginning of the flash. This is not correct, as the RP2040 + /// has a more complex boot process. + /// + /// This function is set to be the entry point for gdb and is used + /// to send the RP2040 back in the bootloader so that all the boot + /// sequence is performed. + fn jump_to_bootloader(); +} + +#[cfg(all(target_arch = "arm", target_os = "none"))] +core::arch::global_asm!( + " + .section .jump_to_bootloader, \"ax\" + .global jump_to_bootloader + .thumb_func + jump_to_bootloader: movs r0, #0 ldr r1, =(0xe0000000 + 0x0000ed08) str r0, [r1] ldmia r0!, {{r1, r2}} msr msp, r1 bx r2 - ", - options(noreturn) - ); -} + " +); fn init_clocks(peripherals: &Rp2040DefaultPeripherals) { // Start tick in watchdog @@ -168,7 +188,7 @@ fn init_clocks(peripherals: &Rp2040DefaultPeripherals) { // Disable the Resus clock peripherals.clocks.disable_resus(); - // Setup the external Osciallator + // Setup the external Oscillator peripherals.xosc.init(); // disable ref and sys clock aux sources @@ -234,22 +254,17 @@ fn init_clocks(peripherals: &Rp2040DefaultPeripherals) { /// removed when this function returns. Otherwise, the stack space used for /// these static_inits is wasted. #[inline(never)] -unsafe fn get_peripherals() -> &'static mut Rp2040DefaultPeripherals<'static> { - static_init!(Rp2040DefaultPeripherals, Rp2040DefaultPeripherals::new()) -} - -/// Main function called after RAM initialized. -#[no_mangle] -pub unsafe fn main() { +pub unsafe fn start() -> ( + &'static kernel::Kernel, + RaspberryPiPico, + &'static rp2040::chip::Rp2040<'static, Rp2040DefaultPeripherals<'static>>, +) { // Loads relocations and clears BSS rp2040::init(); - let peripherals = get_peripherals(); + let peripherals = static_init!(Rp2040DefaultPeripherals, Rp2040DefaultPeripherals::new()); peripherals.resolve_dependencies(); - // Set the UART used for panic - io::WRITER.set_uart(&peripherals.uart0); - // Reset all peripherals except QSPI (we might be booting from Flash), PLL USB and PLL SYS peripherals.resets.reset_all_except(&[ Peripheral::IOQSpi, @@ -273,16 +288,20 @@ pub unsafe fn main() { true, ); - init_clocks(&peripherals); + init_clocks(peripherals); // Unreset all peripherals peripherals.resets.unreset_all_except(&[], true); + // Set the UART used for panic + io::WRITER.set_uart(&peripherals.uart0); + //set RX and TX pins in UART mode let gpio_tx = peripherals.pins.get_pin(RPGpio::GPIO0); let gpio_rx = peripherals.pins.get_pin(RPGpio::GPIO1); gpio_rx.set_function(GpioFunction::UART); gpio_tx.set_function(GpioFunction::UART); + // Disable IE for pads 26-29 (the Pico SDK runtime does this, not sure why) for pin in 26..30 { peripherals @@ -298,135 +317,176 @@ pub unsafe fn main() { CHIP = Some(chip); - let board_kernel = static_init!(Kernel, Kernel::new(&PROCESSES)); + let board_kernel = static_init!(Kernel, Kernel::new(&*addr_of!(PROCESSES))); let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 2], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); - let mux_alarm = components::alarm::AlarmMuxComponent::new(&peripherals.timer) - .finalize(components::alarm_mux_component_helper!(RPTimer)); + .finalize(components::alarm_mux_component_static!(RPTimer)); let alarm = components::alarm::AlarmDriverComponent::new( board_kernel, - capsules::alarm::DRIVER_NUM, + capsules_core::alarm::DRIVER_NUM, mux_alarm, ) - .finalize(components::alarm_component_helper!(RPTimer)); + .finalize(components::alarm_component_static!(RPTimer)); + + // CDC + let strings = static_init!( + [&str; 3], + [ + "Raspberry Pi", // Manufacturer + "Pico - TockOS", // Product + "00000000000000000", // Serial number + ] + ); + + let cdc = components::cdc::CdcAcmComponent::new( + &peripherals.usb, + //capsules_extra::usb::cdc::MAX_CTRL_PACKET_SIZE_RP2040, + 64, + peripherals.sysinfo.get_manufacturer_rp2040() as u16, + peripherals.sysinfo.get_part() as u16, + strings, + mux_alarm, + None, + ) + .finalize(components::cdc_acm_component_static!( + rp2040::usb::UsbCtrl, + rp2040::timer::RPTimer + )); // UART // Create a shared UART channel for kernel debug. - let uart_mux = components::console::UartMuxComponent::new( - &peripherals.uart0, - 115200, - dynamic_deferred_caller, - ) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(cdc, 115200) + .finalize(components::uart_mux_component_static!()); + + // Uncomment this to use UART as an output + // let uart_mux2 = components::console::UartMuxComponent::new( + // &peripherals.uart0, + // 115200, + // ) + // .finalize(components::uart_mux_component_static!()); // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); + + cdc.enable(); + cdc.attach(); let gpio = GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( RPGpioPin, // Used for serial communication. Comment them in if you don't use serial. - // 0 => &peripherals.pins.get_pin(RPGpio::GPIO0), - // 1 => &peripherals.pins.get_pin(RPGpio::GPIO1), - 2 => &peripherals.pins.get_pin(RPGpio::GPIO2), - 3 => &peripherals.pins.get_pin(RPGpio::GPIO3), + // 0 => peripherals.pins.get_pin(RPGpio::GPIO0), + // 1 => peripherals.pins.get_pin(RPGpio::GPIO1), + 2 => peripherals.pins.get_pin(RPGpio::GPIO2), + 3 => peripherals.pins.get_pin(RPGpio::GPIO3), // Used for i2c. Comment them in if you don't use i2c. - // 4 => &peripherals.pins.get_pin(RPGpio::GPIO4), - // 5 => &peripherals.pins.get_pin(RPGpio::GPIO5), - 6 => &peripherals.pins.get_pin(RPGpio::GPIO6), - 7 => &peripherals.pins.get_pin(RPGpio::GPIO7), - 8 => &peripherals.pins.get_pin(RPGpio::GPIO8), - 9 => &peripherals.pins.get_pin(RPGpio::GPIO9), - 10 => &peripherals.pins.get_pin(RPGpio::GPIO10), - 11 => &peripherals.pins.get_pin(RPGpio::GPIO11), - 12 => &peripherals.pins.get_pin(RPGpio::GPIO12), - 13 => &peripherals.pins.get_pin(RPGpio::GPIO13), - 14 => &peripherals.pins.get_pin(RPGpio::GPIO14), - 15 => &peripherals.pins.get_pin(RPGpio::GPIO15), - 16 => &peripherals.pins.get_pin(RPGpio::GPIO16), - 17 => &peripherals.pins.get_pin(RPGpio::GPIO17), - 18 => &peripherals.pins.get_pin(RPGpio::GPIO18), - 19 => &peripherals.pins.get_pin(RPGpio::GPIO19), - 20 => &peripherals.pins.get_pin(RPGpio::GPIO20), - 21 => &peripherals.pins.get_pin(RPGpio::GPIO21), - 22 => &peripherals.pins.get_pin(RPGpio::GPIO22), - 23 => &peripherals.pins.get_pin(RPGpio::GPIO23), - 24 => &peripherals.pins.get_pin(RPGpio::GPIO24), + // 4 => peripherals.pins.get_pin(RPGpio::GPIO4), + // 5 => peripherals.pins.get_pin(RPGpio::GPIO5), + 6 => peripherals.pins.get_pin(RPGpio::GPIO6), + 7 => peripherals.pins.get_pin(RPGpio::GPIO7), + 8 => peripherals.pins.get_pin(RPGpio::GPIO8), + 9 => peripherals.pins.get_pin(RPGpio::GPIO9), + 10 => peripherals.pins.get_pin(RPGpio::GPIO10), + 11 => peripherals.pins.get_pin(RPGpio::GPIO11), + 12 => peripherals.pins.get_pin(RPGpio::GPIO12), + 13 => peripherals.pins.get_pin(RPGpio::GPIO13), + 14 => peripherals.pins.get_pin(RPGpio::GPIO14), + 15 => peripherals.pins.get_pin(RPGpio::GPIO15), + 16 => peripherals.pins.get_pin(RPGpio::GPIO16), + 17 => peripherals.pins.get_pin(RPGpio::GPIO17), + 18 => peripherals.pins.get_pin(RPGpio::GPIO18), + 19 => peripherals.pins.get_pin(RPGpio::GPIO19), + 20 => peripherals.pins.get_pin(RPGpio::GPIO20), + 21 => peripherals.pins.get_pin(RPGpio::GPIO21), + 22 => peripherals.pins.get_pin(RPGpio::GPIO22), + 23 => peripherals.pins.get_pin(RPGpio::GPIO23), + 24 => peripherals.pins.get_pin(RPGpio::GPIO24), // LED pin - // 25 => &peripherals.pins.get_pin(RPGpio::GPIO25), + // 25 => peripherals.pins.get_pin(RPGpio::GPIO25), // Uncomment to use these as GPIO pins instead of ADC pins - // 26 => &peripherals.pins.get_pin(RPGpio::GPIO26), - // 27 => &peripherals.pins.get_pin(RPGpio::GPIO27), - // 28 => &peripherals.pins.get_pin(RPGpio::GPIO28), - // 29 => &peripherals.pins.get_pin(RPGpio::GPIO29) + // 26 => peripherals.pins.get_pin(RPGpio::GPIO26), + // 27 => peripherals.pins.get_pin(RPGpio::GPIO27), + // 28 => peripherals.pins.get_pin(RPGpio::GPIO28), + // 29 => peripherals.pins.get_pin(RPGpio::GPIO29) ), ) - .finalize(components::gpio_component_buf!(RPGpioPin<'static>)); + .finalize(components::gpio_component_static!(RPGpioPin<'static>)); - let led = LedsComponent::new().finalize(components::led_component_helper!( + let led = LedsComponent::new().finalize(components::led_component_static!( LedHigh<'static, RPGpioPin<'static>>, - LedHigh::new(&peripherals.pins.get_pin(RPGpio::GPIO25)) + LedHigh::new(peripherals.pins.get_pin(RPGpio::GPIO25)) )); peripherals.adc.init(); let adc_mux = components::adc::AdcMuxComponent::new(&peripherals.adc) - .finalize(components::adc_mux_component_helper!(Adc)); - - let temp_sensor = components::temperature_rp2040::TemperatureRp2040Component::new(1.721, 0.706) - .finalize(components::temperaturerp2040_adc_component_helper!( - rp2040::adc::Adc, - Channel::Channel4, - adc_mux - )); - - let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let grant_temperature = - board_kernel.create_grant(capsules::temperature::DRIVER_NUM, &grant_cap); - - let temp = static_init!( - capsules::temperature::TemperatureSensor<'static>, - capsules::temperature::TemperatureSensor::new(temp_sensor, grant_temperature) - ); - kernel::hil::sensors::TemperatureDriver::set_client(temp_sensor, temp); + .finalize(components::adc_mux_component_static!(Adc)); - let adc_channel_0 = components::adc::AdcComponent::new(&adc_mux, Channel::Channel0) - .finalize(components::adc_component_helper!(Adc)); + let temp_sensor = components::temperature_rp2040::TemperatureRp2040Component::new( + adc_mux, + Channel::Channel4, + 1.721, + 0.706, + ) + .finalize(components::temperature_rp2040_adc_component_static!( + rp2040::adc::Adc + )); - let adc_channel_1 = components::adc::AdcComponent::new(&adc_mux, Channel::Channel1) - .finalize(components::adc_component_helper!(Adc)); + // RTC DATE TIME - let adc_channel_2 = components::adc::AdcComponent::new(&adc_mux, Channel::Channel2) - .finalize(components::adc_component_helper!(Adc)); + match peripherals.rtc.rtc_init() { + Ok(()) => {} + Err(e) => { + debug!("error starting rtc {:?}", e); + } + }; + + let date_time = components::date_time::DateTimeComponent::new( + board_kernel, + capsules_extra::date_time::DRIVER_NUM, + &peripherals.rtc, + ) + .finalize(date_time_component_static!(rp2040::rtc::Rtc<'static>)); + + let temp = components::temperature::TemperatureComponent::new( + board_kernel, + capsules_extra::temperature::DRIVER_NUM, + temp_sensor, + ) + .finalize(components::temperature_component_static!( + TemperatureRp2040Sensor + )); - let adc_channel_3 = components::adc::AdcComponent::new(&adc_mux, Channel::Channel3) - .finalize(components::adc_component_helper!(Adc)); + let adc_channel_0 = components::adc::AdcComponent::new(adc_mux, Channel::Channel0) + .finalize(components::adc_component_static!(Adc)); + + let adc_channel_1 = components::adc::AdcComponent::new(adc_mux, Channel::Channel1) + .finalize(components::adc_component_static!(Adc)); + + let adc_channel_2 = components::adc::AdcComponent::new(adc_mux, Channel::Channel2) + .finalize(components::adc_component_static!(Adc)); + + let adc_channel_3 = components::adc::AdcComponent::new(adc_mux, Channel::Channel3) + .finalize(components::adc_component_static!(Adc)); let adc_syscall = - components::adc::AdcVirtualComponent::new(board_kernel, capsules::adc::DRIVER_NUM) + components::adc::AdcVirtualComponent::new(board_kernel, capsules_core::adc::DRIVER_NUM) .finalize(components::adc_syscall_component_helper!( adc_channel_0, adc_channel_1, @@ -434,8 +494,8 @@ pub unsafe fn main() { adc_channel_3, )); // PROCESS CONSOLE - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); let process_console = components::process_console::ProcessConsoleComponent::new( @@ -443,8 +503,9 @@ pub unsafe fn main() { uart_mux, mux_alarm, process_printer, + Some(cortexm0p::support::reset), ) - .finalize(components::process_console_component_helper!(RPTimer)); + .finalize(components::process_console_component_static!(RPTimer)); let _ = process_console.start(); let sda_pin = peripherals.pins.get_pin(RPGpio::GPIO4); @@ -456,14 +517,18 @@ pub unsafe fn main() { sda_pin.set_floating_state(FloatingState::PullUp); scl_pin.set_floating_state(FloatingState::PullUp); + let i2c_master_buffer = static_init!( + [u8; capsules_core::i2c_master::BUFFER_LENGTH], + [0; capsules_core::i2c_master::BUFFER_LENGTH] + ); let i2c0 = &peripherals.i2c0; let i2c = static_init!( - I2CMasterDriver, + I2CMasterDriver>, I2CMasterDriver::new( i2c0, - &mut capsules::i2c_master::BUF, + i2c_master_buffer, board_kernel.create_grant( - capsules::i2c_master::DRIVER_NUM, + capsules_core::i2c_master::DRIVER_NUM, &memory_allocation_capability ), ) @@ -471,8 +536,8 @@ pub unsafe fn main() { i2c0.init(10 * 1000); i2c0.set_master_client(i2c); - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); let raspberry_pi_pico = RaspberryPiPico { ipc: kernel::ipc::IPC::new( @@ -487,6 +552,7 @@ pub unsafe fn main() { adc: adc_syscall, temperature: temp, i2c, + date_time, scheduler, systick: cortexm0p::systick::SysTick::new_with_calibration(125_000_000), @@ -502,9 +568,10 @@ pub unsafe fn main() { peripherals.sysinfo.get_revision(), platform_type ); + debug!("Initialization complete. Enter main loop"); - /// These symbols are defined in the linker script. + // These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -520,14 +587,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_management_capability, ) @@ -536,10 +603,14 @@ pub unsafe fn main() { debug!("{:?}", err); }); - board_kernel.kernel_loop( - &raspberry_pi_pico, - chip, - Some(&raspberry_pi_pico.ipc), - &main_loop_capability, - ); + (board_kernel, raspberry_pi_pico, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); } diff --git a/boards/redboard_artemis_nano/Cargo.toml b/boards/redboard_artemis_nano/Cargo.toml deleted file mode 100644 index b19c382371..0000000000 --- a/boards/redboard_artemis_nano/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "redboard_artemis_nano" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" - -[dependencies] -components = { path = "../components" } -cortexm4 = { path = "../../arch/cortex-m4" } -capsules = { path = "../../capsules" } -kernel = { path = "../../kernel" } -apollo3 = { path = "../../chips/apollo3" } diff --git a/boards/redboard_artemis_nano/Makefile b/boards/redboard_artemis_nano/Makefile deleted file mode 100644 index 7e0717ecef..0000000000 --- a/boards/redboard_artemis_nano/Makefile +++ /dev/null @@ -1,26 +0,0 @@ -# Makefile for building the tock kernel for the RedBoard Artemis Nano platform -# -TARGET=thumbv7em-none-eabi -PLATFORM=redboard_artemis_nano - -include ../Makefile.common - -ifndef PORT - PORT="/dev/ttyUSB0" -endif - -# Default target for installing the kernel. -.PHONY: install -install: flash - -.PHONY: flash -flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin - python ambiq/ambiq_bin2board.py --bin $< --load-address-blob 0x20000 -b 115200 -port $(PORT) -r 2 -v --magic-num 0xCB --version 0x0 --load-address-wired 0xc000 -i 6 --options 0x1 - -.PHONY: flash-debug -flash-debug: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).bin - python ambiq/ambiq_bin2board.py --bin $< --load-address-blob 0x20000 -b 115200 -port $(PORT) -r 2 -v --magic-num 0xCB --version 0x0 --load-address-wired 0xc000 -i 6 --options 0x1 - -.PHONY: flash-app -flash-app: - python ambiq/ambiq_bin2board.py --bin $(APP) --load-address-blob 0x20000 -b 115200 -port $(PORT) -r 2 -v --magic-num 0xCB --version 0x0 --load-address-wired 0x40000 -i 6 --options 0x1 diff --git a/boards/redboard_artemis_nano/README.md b/boards/redboard_artemis_nano/README.md deleted file mode 100644 index 0aeb73c6e1..0000000000 --- a/boards/redboard_artemis_nano/README.md +++ /dev/null @@ -1,38 +0,0 @@ -SparkFun RedBoard Artemis Nano -============================== - -## Board features - - - 17 GPIO - all interrupt capable - - 8 ADC channels with 14-bit precision - - 17 PWM channels - - 2 UARTs - - 4 I2C buses - - 2 SPI buses - - PDM Digital Microphone - - Qwiic Connector - -For more details [visit the SparkFun -website](https://www.sparkfun.com/products/15443). - -## Flashing the kernel - -The kernel can be programmed using the Ambiq python scrips. `cd` into `boards/sparkfun_redboard_artemis_nano` -directory and run: - -```shell -$ make flash - -(or) - -$ make flash-debug -``` - -This will flash Tock onto the board via the /dev/ttyUSB0 port. If you would like to use a different port you can specify it from the `PORT` variable. - -```bash -$ PORT=/dev/ttyUSB2 make flash -``` - -This will flash Tock over the SparkFun Variable Loader (SVL) using the Ambiq loader. -The SVL can always be re-flashed if you want to. diff --git a/boards/redboard_artemis_nano/build.rs b/boards/redboard_artemis_nano/build.rs deleted file mode 100644 index 3051054fc6..0000000000 --- a/boards/redboard_artemis_nano/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/redboard_artemis_nano/layout.ld b/boards/redboard_artemis_nano/layout.ld deleted file mode 100644 index bf0d911749..0000000000 --- a/boards/redboard_artemis_nano/layout.ld +++ /dev/null @@ -1,11 +0,0 @@ -MEMORY -{ - rom (rx) : ORIGIN = 0x0000C000, LENGTH = 0x00030000 - prog (rx) : ORIGIN = 0x00040000, LENGTH = 0x08000000 - 0x00030000 - ram (rwx) : ORIGIN = 0x10000000, LENGTH = 0x60000 -} - -MPU_MIN_ALIGN = 8K; -PAGE_SIZE = 8K; - -INCLUDE ../kernel_layout.ld diff --git a/boards/redboard_artemis_nano/src/ble.rs b/boards/redboard_artemis_nano/src/ble.rs deleted file mode 100644 index 88101e3b42..0000000000 --- a/boards/redboard_artemis_nano/src/ble.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! Component for BLE radio on Apollo3 based platforms. -//! -//! Usage -//! ----- -//! ```rust -//! let ble_radio = BLEComponent::new(board_kernel, &apollo3::ble::BLE, mux_alarm).finalize(); -//! ``` - -#![allow(dead_code)] // Components are intended to be conditionally included - -use capsules; -use capsules::virtual_alarm::VirtualMuxAlarm; -use kernel::capabilities; -use kernel::component::Component; -use kernel::hil; -use kernel::{create_capability, static_init}; - -/// BLE component for Apollo3 BLE -pub struct BLEComponent { - board_kernel: &'static kernel::Kernel, - driver_num: usize, - radio: &'static apollo3::ble::Ble<'static>, - mux_alarm: - &'static capsules::virtual_alarm::MuxAlarm<'static, apollo3::stimer::STimer<'static>>, -} - -/// BLE component for Apollo3 BLE -impl BLEComponent { - /// New instance - pub fn new( - board_kernel: &'static kernel::Kernel, - driver_num: usize, - radio: &'static apollo3::ble::Ble, - mux_alarm: &'static capsules::virtual_alarm::MuxAlarm<'static, apollo3::stimer::STimer>, - ) -> BLEComponent { - BLEComponent { - board_kernel: board_kernel, - driver_num: driver_num, - radio: radio, - mux_alarm: mux_alarm, - } - } -} - -impl Component for BLEComponent { - type StaticInput = (); - type Output = &'static capsules::ble_advertising_driver::BLE< - 'static, - apollo3::ble::Ble<'static>, - VirtualMuxAlarm<'static, apollo3::stimer::STimer<'static>>, - >; - - unsafe fn finalize(self, _s: Self::StaticInput) -> Self::Output { - let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - - let ble_radio_virtual_alarm = static_init!( - capsules::virtual_alarm::VirtualMuxAlarm<'static, apollo3::stimer::STimer>, - capsules::virtual_alarm::VirtualMuxAlarm::new(self.mux_alarm) - ); - ble_radio_virtual_alarm.setup(); - - let ble_radio = static_init!( - capsules::ble_advertising_driver::BLE< - 'static, - apollo3::ble::Ble, - VirtualMuxAlarm<'static, apollo3::stimer::STimer>, - >, - capsules::ble_advertising_driver::BLE::new( - self.radio, - self.board_kernel.create_grant(self.driver_num, &grant_cap), - &mut capsules::ble_advertising_driver::BUF, - ble_radio_virtual_alarm - ) - ); - kernel::hil::ble_advertising::BleAdvertisementDriver::set_receive_client( - self.radio, ble_radio, - ); - kernel::hil::ble_advertising::BleAdvertisementDriver::set_transmit_client( - self.radio, ble_radio, - ); - hil::time::Alarm::set_alarm_client(ble_radio_virtual_alarm, ble_radio); - - ble_radio - } -} diff --git a/boards/redboard_artemis_nano/src/main.rs b/boards/redboard_artemis_nano/src/main.rs deleted file mode 100644 index bb35d84244..0000000000 --- a/boards/redboard_artemis_nano/src/main.rs +++ /dev/null @@ -1,327 +0,0 @@ -//! Board file for SparkFun Redboard Artemis Nano -//! -//! - - -#![no_std] -// Disable this attribute when documenting, as a workaround for -// https://github.com/rust-lang/rust/issues/62184. -#![cfg_attr(not(doc), no_main)] -#![deny(missing_docs)] - -use apollo3::chip::Apollo3DefaultPeripherals; -use capsules::virtual_alarm::VirtualMuxAlarm; -use kernel::capabilities; -use kernel::component::Component; -use kernel::dynamic_deferred_call::DynamicDeferredCall; -use kernel::dynamic_deferred_call::DynamicDeferredCallClientState; -use kernel::hil::i2c::I2CMaster; -use kernel::hil::led::LedHigh; -use kernel::hil::time::Counter; -use kernel::platform::{KernelResources, SyscallDriverLookup}; -use kernel::scheduler::round_robin::RoundRobinSched; -use kernel::{create_capability, debug, static_init}; - -pub mod ble; -/// Support routines for debugging I/O. -pub mod io; - -// Number of concurrent processes this platform supports. -const NUM_PROCS: usize = 4; - -// Actual memory for holding the active process structures. -static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = [None; 4]; - -// Static reference to chip for panic dumps. -static mut CHIP: Option<&'static apollo3::chip::Apollo3> = None; -// Static reference to process printer for panic dumps. -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; - -// How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; - -/// Dummy buffer that causes the linker to reserve enough space for the stack. -#[no_mangle] -#[link_section = ".stack_buffer"] -pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; - -/// A structure representing this platform that holds references to all -/// capsules for this platform. -struct RedboardArtemisNano { - alarm: &'static capsules::alarm::AlarmDriver< - 'static, - VirtualMuxAlarm<'static, apollo3::stimer::STimer<'static>>, - >, - led: &'static capsules::led::LedDriver< - 'static, - LedHigh<'static, apollo3::gpio::GpioPin<'static>>, - 1, - >, - gpio: &'static capsules::gpio::GPIO<'static, apollo3::gpio::GpioPin<'static>>, - console: &'static capsules::console::Console<'static>, - i2c_master: &'static capsules::i2c_master::I2CMasterDriver<'static, apollo3::iom::Iom<'static>>, - ble_radio: &'static capsules::ble_advertising_driver::BLE< - 'static, - apollo3::ble::Ble<'static>, - VirtualMuxAlarm<'static, apollo3::stimer::STimer<'static>>, - >, - scheduler: &'static RoundRobinSched<'static>, - systick: cortexm4::systick::SysTick, -} - -/// Mapping of integer syscalls to objects that implement syscalls. -impl SyscallDriverLookup for RedboardArtemisNano { - fn with_driver(&self, driver_num: usize, f: F) -> R - where - F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, - { - match driver_num { - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::i2c_master::DRIVER_NUM => f(Some(self.i2c_master)), - capsules::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), - _ => f(None), - } - } -} - -impl KernelResources> for RedboardArtemisNano { - type SyscallDriverLookup = Self; - type SyscallFilter = (); - type ProcessFault = (); - type Scheduler = RoundRobinSched<'static>; - type SchedulerTimer = cortexm4::systick::SysTick; - type WatchDog = (); - type ContextSwitchCallback = (); - - fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { - &self - } - fn syscall_filter(&self) -> &Self::SyscallFilter { - &() - } - fn process_fault(&self) -> &Self::ProcessFault { - &() - } - fn scheduler(&self) -> &Self::Scheduler { - self.scheduler - } - fn scheduler_timer(&self) -> &Self::SchedulerTimer { - &self.systick - } - fn watchdog(&self) -> &Self::WatchDog { - &() - } - fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { - &() - } -} - -/// Main function. -/// -/// This is called after RAM initialization is complete. -#[no_mangle] -pub unsafe fn main() { - apollo3::init(); - - let peripherals = static_init!(Apollo3DefaultPeripherals, Apollo3DefaultPeripherals::new()); - - // No need to statically allocate mcu/pwr/clk_ctrl because they are only used in main! - let mcu_ctrl = apollo3::mcuctrl::McuCtrl::new(); - let pwr_ctrl = apollo3::pwrctrl::PwrCtrl::new(); - let clkgen = apollo3::clkgen::ClkGen::new(); - - clkgen.set_clock_frequency(apollo3::clkgen::ClockFrequency::Freq48MHz); - - // initialize capabilities - let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability); - let main_loop_cap = create_capability!(capabilities::MainLoopCapability); - let memory_allocation_cap = create_capability!(capabilities::MemoryAllocationCapability); - - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 1], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); - - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); - - // Power up components - pwr_ctrl.enable_uart0(); - pwr_ctrl.enable_iom2(); - - // Enable PinCfg - let _ = &peripherals - .gpio_port - .enable_uart(&&peripherals.gpio_port[48], &&peripherals.gpio_port[49]); - // Enable SDA and SCL for I2C2 (exposed via Qwiic) - let _ = &peripherals - .gpio_port - .enable_i2c(&&peripherals.gpio_port[25], &&peripherals.gpio_port[27]); - - // Configure kernel debug gpios as early as possible - kernel::debug::assign_gpios( - Some(&peripherals.gpio_port[19]), // Blue LED - None, - None, - ); - - // Create a shared UART channel for the console and for kernel debug. - let uart_mux = components::console::UartMuxComponent::new( - &peripherals.uart0, - 115200, - dynamic_deferred_caller, - ) - .finalize(()); - - // Setup the console. - let console = components::console::ConsoleComponent::new( - board_kernel, - capsules::console::DRIVER_NUM, - uart_mux, - ) - .finalize(()); - // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); - - // LEDs - let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( - LedHigh<'static, apollo3::gpio::GpioPin>, - LedHigh::new(&peripherals.gpio_port[19]), - )); - - // GPIOs - // These are also ADC channels, but let's expose them as GPIOs - let gpio = components::gpio::GpioComponent::new( - board_kernel, - capsules::gpio::DRIVER_NUM, - components::gpio_component_helper!( - apollo3::gpio::GpioPin, - 0 => &&peripherals.gpio_port[13], // A0 - 1 => &&peripherals.gpio_port[33], // A1 - 2 => &&peripherals.gpio_port[11], // A2 - 3 => &&peripherals.gpio_port[29], // A3 - 5 => &&peripherals.gpio_port[31] // A5 - ), - ) - .finalize(components::gpio_component_buf!(apollo3::gpio::GpioPin)); - - // Create a shared virtualisation mux layer on top of a single hardware - // alarm. - let _ = peripherals.stimer.start(); - let mux_alarm = components::alarm::AlarmMuxComponent::new(&peripherals.stimer).finalize( - components::alarm_mux_component_helper!(apollo3::stimer::STimer), - ); - let alarm = components::alarm::AlarmDriverComponent::new( - board_kernel, - capsules::alarm::DRIVER_NUM, - mux_alarm, - ) - .finalize(components::alarm_component_helper!(apollo3::stimer::STimer)); - - // Create a process printer for panic. - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); - PROCESS_PRINTER = Some(process_printer); - - // Init the I2C device attached via Qwiic - let i2c_master = static_init!( - capsules::i2c_master::I2CMasterDriver<'static, apollo3::iom::Iom<'static>>, - capsules::i2c_master::I2CMasterDriver::new( - &peripherals.iom2, - &mut capsules::i2c_master::BUF, - board_kernel.create_grant(capsules::i2c_master::DRIVER_NUM, &memory_allocation_cap) - ) - ); - - let _ = &peripherals.iom2.set_master_client(i2c_master); - let _ = &peripherals.iom2.enable(); - - // Setup BLE - mcu_ctrl.enable_ble(); - clkgen.enable_ble(); - pwr_ctrl.enable_ble(); - let _ = &peripherals.ble.setup_clocks(); - mcu_ctrl.reset_ble(); - let _ = &peripherals.ble.power_up(); - let _ = &peripherals.ble.ble_initialise(); - - let ble_radio = ble::BLEComponent::new( - board_kernel, - capsules::ble_advertising_driver::DRIVER_NUM, - &peripherals.ble, - mux_alarm, - ) - .finalize(()); - - mcu_ctrl.print_chip_revision(); - - debug!("Initialization complete. Entering main loop"); - - /// These symbols are defined in the linker script. - extern "C" { - /// Beginning of the ROM region containing app images. - static _sapps: u8; - /// End of the ROM region containing app images. - static _eapps: u8; - /// Beginning of the RAM region for app memory. - static mut _sappmem: u8; - /// End of the RAM region for app memory. - static _eappmem: u8; - } - - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); - - let systick = cortexm4::systick::SysTick::new_with_calibration(48_000_000); - - let artemis_nano = static_init!( - RedboardArtemisNano, - RedboardArtemisNano { - alarm, - console, - gpio, - led, - i2c_master, - ble_radio, - scheduler, - systick, - } - ); - - let chip = static_init!( - apollo3::chip::Apollo3, - apollo3::chip::Apollo3::new(peripherals) - ); - CHIP = Some(chip); - - kernel::process::load_processes( - board_kernel, - chip, - core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, - ), - core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, - ), - &mut PROCESSES, - &FAULT_RESPONSE, - &process_mgmt_cap, - ) - .unwrap_or_else(|err| { - debug!("Error loading processes!"); - debug!("{:?}", err); - }); - - board_kernel.kernel_loop( - artemis_nano, - chip, - None::<&kernel::ipc::IPC>, - &main_loop_cap, - ); -} diff --git a/boards/redboard_redv/Cargo.toml b/boards/redboard_redv/Cargo.toml new file mode 100644 index 0000000000..62215335f0 --- /dev/null +++ b/boards/redboard_redv/Cargo.toml @@ -0,0 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[package] +name = "redboard_redv" +version.workspace = true +authors.workspace = true +edition.workspace = true +build = "../build.rs" + +[dependencies] +components = { path = "../components" } +rv32i = { path = "../../arch/rv32i" } +kernel = { path = "../../kernel" } +e310_g002 = { path = "../../chips/e310_g002" } +sifive = { path = "../../chips/sifive" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/redboard_redv/Makefile b/boards/redboard_redv/Makefile new file mode 100644 index 0000000000..2a7bf9f60b --- /dev/null +++ b/boards/redboard_redv/Makefile @@ -0,0 +1,35 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +# Makefile for building the tock kernel for the RedV platform + +TARGET=riscv32imac-unknown-none-elf +PLATFORM=redboard_redv +QEMU ?= qemu-system-riscv32 + +include ../Makefile.common + +# Default target for installing the kernel. +.PHONY: install +install: flash + +flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf + openocd \ + -c "source [find board/sifive-hifive1-revb.cfg]; program $<; resume 0x20000000; exit" + +qemu: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf + $(QEMU) -M sifive_e,revb=true -kernel $^ -nographic + +qemu-app: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf + $(QEMU) -M sifive_e,revb=true -kernel $^ -device loader,file=$(APP),addr=0x20010000 -nographic + + +TOCKLOADER=tockloader +TOCKLOADER_JTAG_FLAGS = --jlink --board hifive1b +KERNEL_ADDRESS = 0x20010000 + +# upload kernel over JTAG +.PHONY: flash +flash-jlink: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin + $(TOCKLOADER) $(TOCKLOADER_GENERAL_FLAGS) flash --address $(KERNEL_ADDRESS) $(TOCKLOADER_JTAG_FLAGS) $< diff --git a/boards/redboard_redv/README.md b/boards/redboard_redv/README.md new file mode 100644 index 0000000000..8fc6a73f75 --- /dev/null +++ b/boards/redboard_redv/README.md @@ -0,0 +1,59 @@ +Redboard Red-V B RISC-V Board +================================== + +- https://www.sparkfun.com/products/15594 + +Arduino-compatible dev board for RISC-V clone of the Hifive1. + +Programming +----------- + +Running `make flash` should load the kernel onto the board. + +The kernel also assumes there is the default HiFive1 software bootloader running +on the chip. + +Running in QEMU +--------------- + +The HiFive1 application can be run in the QEMU emulation platform for RISC-V, allowing quick and easy testing. + +QEMU can be started with Tock using the following arguments (in Tock's top-level directory): + +```bash +$ qemu-system-riscv32 -M sifive_e,revb=true -kernel $TOCK_ROOT/target/riscv32imac-unknown-none-elf/release/hifive1.elf -nographic +``` + +Or with the `qemu` make target: + +```bash +$ make qemu +``` + +QEMU can be started with Tock and a userspace app using the following arguments (in Tock's top-level directory): + +``` +qemu-system-riscv32 -M sifive_e,revb=true -kernel $TOCK_ROOT/target/riscv32imac-unknown-none-elf/release/hifive1.elf -device loader,file=./examples/hello.tbf,addr=0x20040000 -nographic +``` +Or with the `qemu-app` make target: + +```bash +$ make APP=/path/to/app.tbf qemu-app +``` + +The TBF must be compiled for the HiFive board which is, at the time of writing, +supported for Rust userland apps using libtock-rs. For example, you can build +the Hello World example app from the libtock-rs repository by running: + +``` +$ cd [LIBTOCK-RS-DIR] +$ make EXAMPLE=hello_world flash-hifive1 +$ tar xf target/riscv32imac-unknown-none-elf/tab/hifive1/hello_world.tab +$ cd [TOCK_ROOT]/boards/hifive +$ make APP=[LIBTOCK-RS-DIR]/rv32imac.tbf qemu-app +``` + +Changes between Red-V and Hifive1-RevB +------------------ + +Hifive1 contains a BT module. The LED layout has changed. The boards seem identical otherwise. \ No newline at end of file diff --git a/boards/redboard_redv/layout.ld b/boards/redboard_redv/layout.ld new file mode 100644 index 0000000000..ec098cebcb --- /dev/null +++ b/boards/redboard_redv/layout.ld @@ -0,0 +1,17 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + +/* The RedV board has 4 MB of flash. The first 0x10000 is reserved for + * the default bootloader provided by SiFive. We also reserve room for apps to + * make all of the linker files work. + */ + +MEMORY +{ + rom (rx) : ORIGIN = 0x20010000, LENGTH = 192K + prog (rx) : ORIGIN = 0x20040000, LENGTH = 4M-192K + ram (rwx) : ORIGIN = 0x80000000, LENGTH = 16K +} + +INCLUDE ../kernel_layout.ld diff --git a/boards/redboard_redv/src/io.rs b/boards/redboard_redv/src/io.rs new file mode 100644 index 0000000000..1073464ca3 --- /dev/null +++ b/boards/redboard_redv/src/io.rs @@ -0,0 +1,80 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use core::fmt::Write; +use core::panic::PanicInfo; +use core::str; +use kernel::debug; +use kernel::debug::IoWrite; +use kernel::hil::gpio; +use kernel::hil::led; + +use crate::CHIP; +use crate::PROCESSES; +use crate::PROCESS_PRINTER; + +struct Writer {} + +static mut WRITER: Writer = Writer {}; + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> ::core::fmt::Result { + self.write(s.as_bytes()); + Ok(()) + } +} + +impl IoWrite for Writer { + fn write(&mut self, buf: &[u8]) -> usize { + let uart = sifive::uart::Uart::new(e310_g002::uart::UART0_BASE, 16_000_000); + uart.transmit_sync(buf); + buf.len() + } +} + +/// Panic handler. +#[cfg(not(test))] +#[no_mangle] +#[panic_handler] +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { + // turn off the non panic leds, just in case + + use core::ptr::{addr_of, addr_of_mut}; + let led_green = sifive::gpio::GpioPin::new( + e310_g002::gpio::GPIO0_BASE, + sifive::gpio::pins::pin19, + sifive::gpio::pins::pin19::SET, + sifive::gpio::pins::pin19::CLEAR, + ); + gpio::Configure::make_output(&led_green); + gpio::Output::set(&led_green); + + let led_blue = sifive::gpio::GpioPin::new( + e310_g002::gpio::GPIO0_BASE, + sifive::gpio::pins::pin21, + sifive::gpio::pins::pin21::SET, + sifive::gpio::pins::pin21::CLEAR, + ); + gpio::Configure::make_output(&led_blue); + gpio::Output::set(&led_blue); + + let led_red_pin = sifive::gpio::GpioPin::new( + e310_g002::gpio::GPIO0_BASE, + sifive::gpio::pins::pin22, + sifive::gpio::pins::pin22::SET, + sifive::gpio::pins::pin22::CLEAR, + ); + let led_red = &mut led::LedLow::new(&led_red_pin); + let writer = &mut *addr_of_mut!(WRITER); + + debug::panic( + &mut [led_red], + writer, + pi, + &rv32i::support::nop, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), + ) +} diff --git a/boards/redboard_redv/src/main.rs b/boards/redboard_redv/src/main.rs new file mode 100644 index 0000000000..1e4f54380a --- /dev/null +++ b/boards/redboard_redv/src/main.rs @@ -0,0 +1,347 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Board file for SparkFun RedBoard Red-V development platform. +//! +//! - +//! +//! This board is a clone of the Hifive1-revB from SiFive with minor changes. + +#![no_std] +// Disable this attribute when documenting, as a workaround for +// https://github.com/rust-lang/rust/issues/62184. +#![cfg_attr(not(doc), no_main)] + +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use e310_g002::interrupt_service::E310G002DefaultPeripherals; +use kernel::capabilities; +use kernel::component::Component; +use kernel::hil; +use kernel::hil::led::LedLow; +use kernel::platform::scheduler_timer::VirtualSchedulerTimer; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::scheduler::cooperative::CooperativeSched; +use kernel::utilities::registers::interfaces::ReadWriteable; +use kernel::{create_capability, debug, static_init}; +use rv32i::csr; + +pub mod io; + +pub const NUM_PROCS: usize = 4; +// +// Actual memory for holding the active process structures. Need an empty list +// at least. +static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = + [None; NUM_PROCS]; + +// Reference to the chip for panic dumps. +static mut CHIP: Option<&'static e310_g002::chip::E310x> = None; +// Reference to the process printer for panic dumps. +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; + +// How should the kernel respond when a process faults. +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; + +/// Dummy buffer that causes the linker to reserve enough space for the stack. +#[no_mangle] +#[link_section = ".stack_buffer"] +pub static mut STACK_MEMORY: [u8; 0x900] = [0; 0x900]; + +/// A structure representing this platform that holds references to all +/// capsules for this platform. We've included an alarm and console. +struct RedV { + led: &'static capsules_core::led::LedDriver< + 'static, + LedLow<'static, sifive::gpio::GpioPin<'static>>, + 1, + >, + console: &'static capsules_core::console::Console<'static>, + lldb: &'static capsules_core::low_level_debug::LowLevelDebug< + 'static, + capsules_core::virtualizers::virtual_uart::UartDevice<'static>, + >, + alarm: &'static capsules_core::alarm::AlarmDriver< + 'static, + VirtualMuxAlarm<'static, e310_g002::chip::E310xClint<'static>>, + >, + scheduler: &'static CooperativeSched<'static>, + scheduler_timer: &'static VirtualSchedulerTimer< + VirtualMuxAlarm<'static, e310_g002::chip::E310xClint<'static>>, + >, +} + +/// Mapping of integer syscalls to objects that implement syscalls. +impl SyscallDriverLookup for RedV { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::low_level_debug::DRIVER_NUM => f(Some(self.lldb)), + _ => f(None), + } + } +} + +impl KernelResources>> + for RedV +{ + type SyscallDriverLookup = Self; + type SyscallFilter = (); + type ProcessFault = (); + type Scheduler = CooperativeSched<'static>; + type SchedulerTimer = + VirtualSchedulerTimer>>; + type WatchDog = (); + type ContextSwitchCallback = (); + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + &() + } + fn process_fault(&self) -> &Self::ProcessFault { + &() + } + fn scheduler(&self) -> &Self::Scheduler { + self.scheduler + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + self.scheduler_timer + } + fn watchdog(&self) -> &Self::WatchDog { + &() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + &() + } +} + +/// This is in a separate, inline(never) function so that its stack frame is +/// removed when this function returns. Otherwise, the stack space used for +/// these static_inits is wasted. +#[inline(never)] +unsafe fn start() -> ( + &'static kernel::Kernel, + RedV, + &'static e310_g002::chip::E310x<'static, E310G002DefaultPeripherals<'static>>, +) { + // only machine mode + rv32i::configure_trap_handler(); + + let peripherals = static_init!( + E310G002DefaultPeripherals, + E310G002DefaultPeripherals::new(16_000_000) + ); + + // Setup any recursive dependencies and register deferred calls: + peripherals.init(); + + // initialize capabilities + let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability); + let memory_allocation_cap = create_capability!(capabilities::MemoryAllocationCapability); + + peripherals.e310x.watchdog.disable(); + peripherals.e310x.rtc.disable(); + peripherals.e310x.pwm0.disable(); + peripherals.e310x.pwm1.disable(); + peripherals.e310x.pwm2.disable(); + peripherals.e310x.uart1.disable(); + + peripherals + .e310x + .prci + .set_clock_frequency(sifive::prci::ClockFrequency::Freq16Mhz); + + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); + + // Configure kernel debug gpios as early as possible + kernel::debug::assign_gpios( + Some(&peripherals.e310x.gpio_port[5]), // Blue/only LED + None, + None, + ); + + // Create a shared UART channel for the console and for kernel debug. + let uart_mux = components::console::UartMuxComponent::new(&peripherals.e310x.uart0, 115200) + .finalize(components::uart_mux_component_static!()); + + // LEDs + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( + LedLow<'static, sifive::gpio::GpioPin>, + LedLow::new(&peripherals.e310x.gpio_port[5]), // Blue + )); + + peripherals.e310x.uart0.initialize_gpio_pins( + &peripherals.e310x.gpio_port[17], + &peripherals.e310x.gpio_port[16], + ); + + let hardware_timer = static_init!( + e310_g002::chip::E310xClint, + e310_g002::chip::E310xClint::new(&e310_g002::clint::CLINT_BASE) + ); + + // Create a shared virtualization mux layer on top of a single hardware + // alarm. + let mux_alarm = static_init!( + MuxAlarm<'static, e310_g002::chip::E310xClint>, + MuxAlarm::new(hardware_timer) + ); + hil::time::Alarm::set_alarm_client(hardware_timer, mux_alarm); + + // Alarm + let virtual_alarm_user = static_init!( + VirtualMuxAlarm<'static, e310_g002::chip::E310xClint>, + VirtualMuxAlarm::new(mux_alarm) + ); + virtual_alarm_user.setup(); + + let systick_virtual_alarm = static_init!( + VirtualMuxAlarm<'static, e310_g002::chip::E310xClint>, + VirtualMuxAlarm::new(mux_alarm) + ); + systick_virtual_alarm.setup(); + + let alarm = static_init!( + capsules_core::alarm::AlarmDriver< + 'static, + VirtualMuxAlarm<'static, e310_g002::chip::E310xClint>, + >, + capsules_core::alarm::AlarmDriver::new( + virtual_alarm_user, + board_kernel.create_grant(capsules_core::alarm::DRIVER_NUM, &memory_allocation_cap) + ) + ); + hil::time::Alarm::set_alarm_client(virtual_alarm_user, alarm); + + let chip = static_init!( + e310_g002::chip::E310x, + e310_g002::chip::E310x::new(peripherals, hardware_timer) + ); + CHIP = Some(chip); + + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); + PROCESS_PRINTER = Some(process_printer); + + let process_console = components::process_console::ProcessConsoleComponent::new( + board_kernel, + uart_mux, + mux_alarm, + process_printer, + None, + ) + .finalize(components::process_console_component_static!( + e310_g002::chip::E310xClint + )); + let _ = process_console.start(); + + // Need to enable all interrupts for Tock Kernel + chip.enable_plic_interrupts(); + + // enable interrupts globally + csr::CSR + .mie + .modify(csr::mie::mie::mext::SET + csr::mie::mie::msoft::SET + csr::mie::mie::mtimer::SET); + csr::CSR.mstatus.modify(csr::mstatus::mstatus::mie::SET); + + // Setup the console. + let console = components::console::ConsoleComponent::new( + board_kernel, + capsules_core::console::DRIVER_NUM, + uart_mux, + ) + .finalize(components::console_component_static!()); + // Create the debugger object that handles calls to `debug!()`. + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); + + let lldb = components::lldb::LowLevelDebugComponent::new( + board_kernel, + capsules_core::low_level_debug::DRIVER_NUM, + uart_mux, + ) + .finalize(components::low_level_debug_component_static!()); + + // Need two debug!() calls to actually test with QEMU. QEMU seems to have a + // much larger UART TX buffer (or it transmits faster). With a single call + // the entire message is printed to console even if the kernel loop does not run + debug!("Red-V initialization complete."); + debug!("Entering main loop."); + + // These symbols are defined in the linker script. + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + } + + let scheduler = + components::sched::cooperative::CooperativeComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::cooperative_component_static!(NUM_PROCS)); + + let scheduler_timer = static_init!( + VirtualSchedulerTimer>>, + VirtualSchedulerTimer::new(systick_virtual_alarm) + ); + + let redv = RedV { + led, + console, + lldb, + alarm, + scheduler, + scheduler_timer, + }; + + kernel::process::load_processes( + board_kernel, + chip, + core::slice::from_raw_parts( + addr_of!(_sapps), + addr_of!(_eapps) as usize - addr_of!(_sapps) as usize, + ), + core::slice::from_raw_parts_mut( + addr_of_mut!(_sappmem), + addr_of!(_eappmem) as usize - addr_of!(_sappmem) as usize, + ), + &mut *addr_of_mut!(PROCESSES), + &FAULT_RESPONSE, + &process_mgmt_cap, + ) + .unwrap_or_else(|err| { + debug!("Error loading processes!"); + debug!("{:?}", err); + }); + + (board_kernel, redv, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop( + &platform, + chip, + None::<&kernel::ipc::IPC<{ NUM_PROCS as u8 }>>, + &main_loop_capability, + ); +} diff --git a/boards/sma_q3/Cargo.toml b/boards/sma_q3/Cargo.toml new file mode 100644 index 0000000000..ed94dd124d --- /dev/null +++ b/boards/sma_q3/Cargo.toml @@ -0,0 +1,28 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[package] +name = "sma_q3" +version.workspace = true +authors = [ + "Tock Project Developers ", + "Dorota " +] +build = "../build.rs" +edition.workspace = true + +[dependencies] +components = { path = "../components" } +cortexm4 = { path = "../../arch/cortex-m4" } +enum_primitive = { path = "../../libraries/enum_primitive" } +kernel = { path = "../../kernel" } +nrf52840 = { path = "../../chips/nrf52840" } +nrf52_components = { path = "../nordic/nrf52_components" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/sma_q3/Makefile b/boards/sma_q3/Makefile new file mode 100644 index 0000000000..8061e67037 --- /dev/null +++ b/boards/sma_q3/Makefile @@ -0,0 +1,31 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +# Makefile for building the tock kernel for the SMA Q3 smart watch + +TARGET=thumbv7em-none-eabi +PLATFORM=sma_q3 + +include ../Makefile.common + +TOCKLOADER=tockloader + +# Where in the nrf52 flash to load the kernel with `tockloader` +KERNEL_ADDRESS=0x00000 + +TOCKLOADER_SWD_FLAGS = --openocd --board sma_q3 + +# Default target for installing the kernel. +.PHONY: install +install: flash + +# Upload the kernel over SWD +.PHONY: flash +flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin + $(TOCKLOADER) flash --address $(KERNEL_ADDRESS) $(TOCKLOADER_SWD_FLAGS) $< + +# Upload the kernel over serial/bootloader +.PHONY: program +program: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).hex + $(error Cannot program SMA Q3 over USB. Use \`make flash\` and SWD) diff --git a/boards/sma_q3/README.md b/boards/sma_q3/README.md new file mode 100644 index 0000000000..912ea66397 --- /dev/null +++ b/boards/sma_q3/README.md @@ -0,0 +1,35 @@ +Platform-Specific Instructions: SMA Q3 +=================================== + +The SMA Q3 is the smart watch used in [Jazda 2.0](https://jazda.org) and Bangle.js 2. +It is a platform based around the nRF52840, an SoC with an ARM Cortex-M4 and a BLE radio. +The smart watch exposes 2 SWD pins, and includes 1 button, 1 backlight LED and a lot of assorted peripherals. +[The documentation](https://hackaday.io/project/175577-hackable-nrf52840-smart-watch) comes from reverse engineering. + + +## Getting Started + +To program the SMA Q3 with Tock, you will need a STLink 2.0 device and the +appropriate cables. An example setup is going to be available on the Jazda website. + +You'll also need to install [OpenOCD](../../../doc/Getting_Started.md) to proress with the programming. + +Then, follow the [Tock Getting Started guide](../../../doc/Getting_Started.md) + +## Programming the kernel +Once you have all software installed, you should be able to simply run +make flash in this directory to install a fresh kernel. + +## Programming user-level applications +You can program an application via JTAG using `tockloader`: + +```shell +$ cd libtock-c/examples/ +$ make +$ tockloader install --openocd --board sma_q3 +``` + +## Debugging + +See the [nrf52dk README](../nordic/nrf52dk/README.md) for information about debugging +the SMA Q3. Note: may need adjustment due to missing serial interface. diff --git a/boards/sma_q3/layout.ld b/boards/sma_q3/layout.ld new file mode 100644 index 0000000000..89d8ab00fc --- /dev/null +++ b/boards/sma_q3/layout.ld @@ -0,0 +1,6 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + +INCLUDE ../nordic/nrf52840_chip_layout.ld +INCLUDE ../kernel_layout.ld diff --git a/boards/sma_q3/src/io.rs b/boards/sma_q3/src/io.rs new file mode 100644 index 0000000000..170085deef --- /dev/null +++ b/boards/sma_q3/src/io.rs @@ -0,0 +1,92 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use core::fmt::Write; +use core::panic::PanicInfo; +use kernel::debug; +use kernel::debug::IoWrite; +use kernel::hil::led; +use nrf52840::gpio::Pin; + +use crate::CHIP; +use crate::PROCESSES; +use crate::PROCESS_PRINTER; + +enum Writer { + Uninitialized, + WriterRtt(&'static capsules_extra::segger_rtt::SeggerRttMemory<'static>), +} + +static mut WRITER: Writer = Writer::Uninitialized; + +fn wait() { + for _ in 0..300 { + cortexm4::support::nop(); + } +} + +/// Set the RTT memory buffer used to output panic messages. +pub unsafe fn set_rtt_memory( + rtt_memory: &'static capsules_extra::segger_rtt::SeggerRttMemory<'static>, +) { + WRITER = Writer::WriterRtt(rtt_memory); +} + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> ::core::fmt::Result { + self.write(s.as_bytes()); + Ok(()) + } +} + +impl IoWrite for Writer { + fn write(&mut self, buf: &[u8]) -> usize { + match self { + Writer::Uninitialized => {} + Writer::WriterRtt(rtt_memory) => { + let up_buffer = unsafe { &*rtt_memory.get_up_buffer_ptr() }; + let buffer_len = up_buffer.length.get(); + let buffer = unsafe { + core::slice::from_raw_parts_mut( + up_buffer.buffer.get() as *mut u8, + buffer_len as usize, + ) + }; + + let mut write_position = up_buffer.write_position.get(); + + for &c in buf { + wait(); + buffer[write_position as usize] = c; + write_position = (write_position + 1) % buffer_len; + up_buffer.write_position.set(write_position); + wait(); + } + } + }; + buf.len() + } +} + +#[cfg(not(test))] +#[no_mangle] +#[panic_handler] +/// Panic handler +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { + // The display LEDs (see back of board) + + use core::ptr::{addr_of, addr_of_mut}; + let led_kernel_pin = &nrf52840::gpio::GPIOPin::new(Pin::P0_13); + let led = &mut led::LedLow::new(led_kernel_pin); + let writer = &mut *addr_of_mut!(WRITER); + debug::panic( + &mut [led], + writer, + pi, + &cortexm4::support::nop, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), + ) +} diff --git a/boards/sma_q3/src/main.rs b/boards/sma_q3/src/main.rs new file mode 100644 index 0000000000..67e02de17b --- /dev/null +++ b/boards/sma_q3/src/main.rs @@ -0,0 +1,523 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Tock kernel for the SMA Q3 smartwatch. +//! +//! It is based on nRF52840 SoC (Cortex M4 core with a BLE transceiver) with +//! SWD as I/O and many peripherals. +//! +//! Reverse-engineered documentation available at: +//! + +#![no_std] +// Disable this attribute when documenting, as a workaround for +// https://github.com/rust-lang/rust/issues/62184. +#![cfg_attr(not(doc), no_main)] +#![deny(missing_docs)] + +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::virtualizers::virtual_aes_ccm::MuxAES128CCM; +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; +use kernel::component::Component; +use kernel::deferred_call::DeferredCallClient; +use kernel::hil::i2c::I2CMaster; +use kernel::hil::led::LedHigh; +use kernel::hil::symmetric_encryption::AES128; +use kernel::hil::time::Counter; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::scheduler::round_robin::RoundRobinSched; +#[allow(unused_imports)] +use kernel::{capabilities, create_capability, debug, debug_gpio, debug_verbose, static_init}; +use nrf52840::gpio::Pin; +use nrf52840::interrupt_service::Nrf52840DefaultPeripherals; + +// The backlight LED +const LED1_PIN: Pin = Pin::P0_08; + +// Vibration motor +const VIBRA1_PIN: Pin = Pin::P0_19; + +// The side button +const BUTTON_PIN: Pin = Pin::P0_17; + +/// I2C pins for the temp/pressure sensor +const I2C_TEMP_SDA_PIN: Pin = Pin::P1_15; +const I2C_TEMP_SCL_PIN: Pin = Pin::P0_02; + +// Constants related to the configuration of the 15.4 network stack; DEFAULT_EXT_SRC_MAC +// should be replaced by an extended src address generated from device serial number +const SRC_MAC: u16 = 0xf00f; +const PAN_ID: u16 = 0xABCD; +const DEFAULT_EXT_SRC_MAC: [u8; 8] = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77]; + +/// UART Writer +pub mod io; + +// State for loading and holding applications. +// How should the kernel respond when a process faults. +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; + +// Number of concurrent processes this platform supports. +const NUM_PROCS: usize = 8; + +static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = + [None; NUM_PROCS]; + +// Static reference to chip for panic dumps +static mut CHIP: Option<&'static nrf52840::chip::NRF52> = None; +// Static reference to process printer for panic dumps +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; + +/// Dummy buffer that causes the linker to reserve enough space for the stack. +#[no_mangle] +#[link_section = ".stack_buffer"] +pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; + +type Bmp280Sensor = components::bmp280::Bmp280ComponentType< + VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, nrf52840::i2c::TWI<'static>>, +>; +type TemperatureDriver = components::temperature::TemperatureComponentType; +type RngDriver = components::rng::RngComponentType>; + +type Ieee802154Driver = components::ieee802154::Ieee802154ComponentType< + nrf52840::ieee802154_radio::Radio<'static>, + nrf52840::aes::AesECB<'static>, +>; + +/// Supported drivers by the platform +pub struct Platform { + temperature: &'static TemperatureDriver, + ble_radio: &'static capsules_extra::ble_advertising_driver::BLE< + 'static, + nrf52840::ble_radio::Radio<'static>, + VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, + >, + ieee802154_radio: &'static Ieee802154Driver, + button: &'static capsules_core::button::Button<'static, nrf52840::gpio::GPIOPin<'static>>, + pconsole: &'static capsules_core::process_console::ProcessConsole< + 'static, + { capsules_core::process_console::DEFAULT_COMMAND_HISTORY_LEN }, + VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, + components::process_console::Capability, + >, + console: &'static capsules_core::console::Console<'static>, + gpio: &'static capsules_core::gpio::GPIO<'static, nrf52840::gpio::GPIOPin<'static>>, + led: &'static capsules_core::led::LedDriver< + 'static, + LedHigh<'static, nrf52840::gpio::GPIOPin<'static>>, + 2, + >, + rng: &'static RngDriver, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + analog_comparator: &'static capsules_extra::analog_comparator::AnalogComparator< + 'static, + nrf52840::acomp::Comparator<'static>, + >, + alarm: &'static capsules_core::alarm::AlarmDriver< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52840::rtc::Rtc<'static>, + >, + >, + scheduler: &'static RoundRobinSched<'static>, + systick: cortexm4::systick::SysTick, +} + +impl SyscallDriverLookup for Platform { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::rng::DRIVER_NUM => f(Some(self.rng)), + capsules_extra::ble_advertising_driver::DRIVER_NUM => f(Some(self.ble_radio)), + capsules_extra::ieee802154::DRIVER_NUM => f(Some(self.ieee802154_radio)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temperature)), + capsules_extra::analog_comparator::DRIVER_NUM => f(Some(self.analog_comparator)), + kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), + _ => f(None), + } + } +} + +impl KernelResources>> + for Platform +{ + type SyscallDriverLookup = Self; + type SyscallFilter = (); + type ProcessFault = (); + type Scheduler = RoundRobinSched<'static>; + type SchedulerTimer = cortexm4::systick::SysTick; + type WatchDog = (); + type ContextSwitchCallback = (); + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + &() + } + fn process_fault(&self) -> &Self::ProcessFault { + &() + } + fn scheduler(&self) -> &Self::Scheduler { + self.scheduler + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + &self.systick + } + fn watchdog(&self) -> &Self::WatchDog { + &() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + &() + } +} + +/// This is in a separate, inline(never) function so that its stack frame is +/// removed when this function returns. Otherwise, the stack space used for +/// these static_inits is wasted. +#[inline(never)] +pub unsafe fn start() -> ( + &'static kernel::Kernel, + Platform, + &'static nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>, +) { + nrf52840::init(); + + let ieee802154_ack_buf = static_init!( + [u8; nrf52840::ieee802154_radio::ACK_BUF_SIZE], + [0; nrf52840::ieee802154_radio::ACK_BUF_SIZE] + ); + // Initialize chip peripheral drivers + let nrf52840_peripherals = static_init!( + Nrf52840DefaultPeripherals, + Nrf52840DefaultPeripherals::new(ieee802154_ack_buf) + ); + + // set up circular peripheral dependencies + nrf52840_peripherals.init(); + let base_peripherals = &nrf52840_peripherals.nrf52; + + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); + + // GPIOs + let gpio = components::gpio::GpioComponent::new( + board_kernel, + capsules_core::gpio::DRIVER_NUM, + components::gpio_component_helper!( + nrf52840::gpio::GPIOPin, + 0 => &nrf52840_peripherals.gpio_port[Pin::P0_29], + ), + ) + .finalize(components::gpio_component_static!(nrf52840::gpio::GPIOPin)); + + let button = components::button::ButtonComponent::new( + board_kernel, + capsules_core::button::DRIVER_NUM, + components::button_component_helper!( + nrf52840::gpio::GPIOPin, + ( + &nrf52840_peripherals.gpio_port[BUTTON_PIN], + kernel::hil::gpio::ActivationMode::ActiveLow, + kernel::hil::gpio::FloatingState::PullUp + ) + ), + ) + .finalize(components::button_component_static!( + nrf52840::gpio::GPIOPin + )); + + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( + LedHigh<'static, nrf52840::gpio::GPIOPin>, + LedHigh::new(&nrf52840_peripherals.gpio_port[LED1_PIN]), + LedHigh::new(&nrf52840_peripherals.gpio_port[VIBRA1_PIN]), + )); + + let chip = static_init!( + nrf52840::chip::NRF52, + nrf52840::chip::NRF52::new(nrf52840_peripherals) + ); + CHIP = Some(chip); + + nrf52_components::startup::NrfStartupComponent::new( + false, + // the button pin cannot be used to reset the device, + // but the API expects some pin, + // so might as well give a useless one. + BUTTON_PIN, + nrf52840::uicr::Regulator0Output::V3_0, + &base_peripherals.nvmc, + ) + .finalize(()); + + // Create capabilities that the board needs to call certain protected kernel + // functions. + + let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); + + let gpio_port = &nrf52840_peripherals.gpio_port; + + // Configure kernel debug gpios as early as possible + kernel::debug::assign_gpios(Some(&gpio_port[LED1_PIN]), None, None); + + let rtc = &base_peripherals.rtc; + let _ = rtc.start(); + let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) + .finalize(components::alarm_mux_component_static!(nrf52840::rtc::Rtc)); + let alarm = components::alarm::AlarmDriverComponent::new( + board_kernel, + capsules_core::alarm::DRIVER_NUM, + mux_alarm, + ) + .finalize(components::alarm_component_static!(nrf52840::rtc::Rtc)); + + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); + PROCESS_PRINTER = Some(process_printer); + + // Initialize early so any panic beyond this point can use the RTT memory object. + let uart_channel = { + // RTT communication channel + let mut rtt_memory = components::segger_rtt::SeggerRttMemoryComponent::new() + .finalize(components::segger_rtt_memory_component_static!()); + + // TODO: This is inherently unsafe as it aliases the mutable reference to rtt_memory. This + // aliases reference is only used inside a panic handler, which should be OK, but maybe we + // should use a const reference to rtt_memory and leverage interior mutability instead. + self::io::set_rtt_memory(&*rtt_memory.get_rtt_memory_ptr()); + + components::segger_rtt::SeggerRttComponent::new(mux_alarm, rtt_memory) + .finalize(components::segger_rtt_component_static!(nrf52840::rtc::Rtc)) + }; + + // Create a shared UART channel for the console and for kernel debug. + let uart_mux = components::console::UartMuxComponent::new(uart_channel, 115200) + .finalize(components::uart_mux_component_static!()); + + let pconsole = components::process_console::ProcessConsoleComponent::new( + board_kernel, + uart_mux, + mux_alarm, + process_printer, + Some(cortexm4::support::reset), + ) + .finalize(components::process_console_component_static!( + nrf52840::rtc::Rtc<'static> + )); + + // Setup the console. + let console = components::console::ConsoleComponent::new( + board_kernel, + capsules_core::console::DRIVER_NUM, + uart_mux, + ) + .finalize(components::console_component_static!()); + // Create the debugger object that handles calls to `debug!()`. + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); + + let ble_radio = components::ble::BLEComponent::new( + board_kernel, + capsules_extra::ble_advertising_driver::DRIVER_NUM, + &base_peripherals.ble_radio, + mux_alarm, + ) + .finalize(components::ble_component_static!( + nrf52840::rtc::Rtc, + nrf52840::ble_radio::Radio + )); + + let aes_mux = static_init!( + MuxAES128CCM<'static, nrf52840::aes::AesECB>, + MuxAES128CCM::new(&base_peripherals.ecb,) + ); + base_peripherals.ecb.set_client(aes_mux); + aes_mux.register(); + + let (ieee802154_radio, _mux_mac) = components::ieee802154::Ieee802154Component::new( + board_kernel, + capsules_extra::ieee802154::DRIVER_NUM, + &nrf52840_peripherals.ieee802154_radio, + aes_mux, + PAN_ID, + SRC_MAC, + DEFAULT_EXT_SRC_MAC, + ) + .finalize(components::ieee802154_component_static!( + nrf52840::ieee802154_radio::Radio, + nrf52840::aes::AesECB<'static> + )); + + // Not exposed in favor of the BMP280, but present. + // Possibly needs power management all the same. + let _temp = components::temperature::TemperatureComponent::new( + board_kernel, + capsules_extra::temperature::DRIVER_NUM, + &base_peripherals.temp, + ) + .finalize(components::temperature_component_static!( + nrf52840::temperature::Temp + )); + + let sensors_i2c_bus = static_init!( + capsules_core::virtualizers::virtual_i2c::MuxI2C<'static, nrf52840::i2c::TWI>, + capsules_core::virtualizers::virtual_i2c::MuxI2C::new(&base_peripherals.twi1, None,) + ); + sensors_i2c_bus.register(); + + base_peripherals.twi1.configure( + nrf52840::pinmux::Pinmux::new(I2C_TEMP_SCL_PIN as u32), + nrf52840::pinmux::Pinmux::new(I2C_TEMP_SDA_PIN as u32), + ); + base_peripherals.twi1.set_master_client(sensors_i2c_bus); + + let bmp280 = components::bmp280::Bmp280Component::new( + sensors_i2c_bus, + capsules_extra::bmp280::BASE_ADDR, + mux_alarm, + ) + .finalize(components::bmp280_component_static!( + nrf52840::rtc::Rtc<'static>, + nrf52840::i2c::TWI + )); + + let temperature = components::temperature::TemperatureComponent::new( + board_kernel, + capsules_extra::temperature::DRIVER_NUM, + bmp280, + ) + .finalize(components::temperature_component_static!(Bmp280Sensor)); + + let rng = components::rng::RngComponent::new( + board_kernel, + capsules_core::rng::DRIVER_NUM, + &base_peripherals.trng, + ) + .finalize(components::rng_component_static!(nrf52840::trng::Trng)); + + // Initialize AC using AIN5 (P0.29) as VIN+ and VIN- as AIN0 (P0.02) + // These are hardcoded pin assignments specified in the driver + let analog_comparator = components::analog_comparator::AnalogComparatorComponent::new( + &base_peripherals.acomp, + components::analog_comparator_component_helper!( + nrf52840::acomp::Channel, + &*addr_of!(nrf52840::acomp::CHANNEL_AC0) + ), + board_kernel, + capsules_extra::analog_comparator::DRIVER_NUM, + ) + .finalize(components::analog_comparator_component_static!( + nrf52840::acomp::Comparator + )); + + nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); + + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); + + let periodic_virtual_alarm = static_init!( + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, nrf52840::rtc::Rtc>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm::new(mux_alarm) + ); + periodic_virtual_alarm.setup(); + + let platform = Platform { + temperature, + button, + ble_radio, + ieee802154_radio, + pconsole, + console, + led, + gpio, + rng, + alarm, + analog_comparator, + ipc: kernel::ipc::IPC::new( + board_kernel, + kernel::ipc::DRIVER_NUM, + &memory_allocation_capability, + ), + scheduler, + systick: cortexm4::systick::SysTick::new_with_calibration(64000000), + }; + + /// I split this out to be able to start applications with a delay + /// after the board is initialized. + /// The benefit to debugging is that if I want to print + /// some debug information while the board initalizes, + /// it won't be affected by an application that prints so much + /// that it overflows the output buffer. + /// + /// It's also useful for a future "fake off" functionality, + /// where if a button is pressed, processes are stopped, + /// but when pressed again, they are loaded anew. + fn load_processes( + board_kernel: &'static kernel::Kernel, + chip: &'static nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>, + ) { + let process_management_capability = + create_capability!(capabilities::ProcessManagementCapability); + unsafe { + kernel::process::load_processes( + board_kernel, + chip, + core::slice::from_raw_parts( + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, + ), + core::slice::from_raw_parts_mut( + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, + ), + &mut *addr_of_mut!(PROCESSES), + &FAULT_RESPONSE, + &process_management_capability, + ) + .unwrap_or_else(|err| { + debug!("Error loading processes!"); + debug!("{:?}", err); + }); + } + } + + let _ = platform.pconsole.start(); + debug!("Initialization complete. Entering main loop\r"); + debug!("{}", &*addr_of!(nrf52840::ficr::FICR_INSTANCE)); + + load_processes(board_kernel, chip); + // These symbols are defined in the linker script. + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + } + + (board_kernel, platform, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); +} diff --git a/boards/stm32f3discovery/Cargo.toml b/boards/stm32f3discovery/Cargo.toml index fa44174f6a..85bdda5dce 100644 --- a/boards/stm32f3discovery/Cargo.toml +++ b/boards/stm32f3discovery/Cargo.toml @@ -1,13 +1,23 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "stm32f3discovery" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] components = { path = "../components" } cortexm4 = { path = "../../arch/cortex-m4" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } stm32f303xc = { path = "../../chips/stm32f303xc" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/stm32f3discovery/Makefile b/boards/stm32f3discovery/Makefile index 8e78a6cf5f..d14974b371 100644 --- a/boards/stm32f3discovery/Makefile +++ b/boards/stm32f3discovery/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the STM32F3DISCOVERY platform # TARGET=thumbv7em-none-eabi @@ -6,7 +10,6 @@ PLATFORM=stm32f3discovery include ../Makefile.common OPENOCD=openocd -OPENOCD_OPTIONS=-f openocd.cfg # Default target for installing the kernel. .PHONY: install @@ -14,11 +17,11 @@ install: flash .PHONY: flash-debug flash-debug: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).elf - $(OPENOCD) $(OPENOCD_OPTIONS) -c "init; reset halt; flash write_image erase $<; verify_image $<; reset; shutdown" + $(OPENOCD) -c "source [find board/stm32f3discovery.cfg]; init; reset halt; flash write_image erase $<; verify_image $<; reset; shutdown" .PHONY: flash flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf - $(OPENOCD) $(OPENOCD_OPTIONS) -c "init; reset halt; flash write_image erase $<; verify_image $<; reset; shutdown" + $(OPENOCD) -c "source [find board/stm32f3discovery.cfg]; init; reset halt; flash write_image erase $<; verify_image $<; reset; shutdown" .PHONY: program program: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf diff --git a/boards/stm32f3discovery/build.rs b/boards/stm32f3discovery/build.rs deleted file mode 100644 index 46b1df0bac..0000000000 --- a/boards/stm32f3discovery/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=chip_layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/stm32f3discovery/chip_layout.ld b/boards/stm32f3discovery/chip_layout.ld index 9b0301c131..e5e7605c89 100644 --- a/boards/stm32f3discovery/chip_layout.ld +++ b/boards/stm32f3discovery/chip_layout.ld @@ -1,15 +1,18 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + /* Memory layout for the STM32F303VCT6 * rom = 256KB (LENGTH = 0x00040000) * kernel = 128KB * user = 128KB - * ram = 48KB */ + * ram = 40KB */ MEMORY { rom (rx) : ORIGIN = 0x08000000, LENGTH = 128K prog (rx) : ORIGIN = 0x08020000, LENGTH = 128K - ram (rwx) : ORIGIN = 0x20000000, LENGTH = 48K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 40K } -MPU_MIN_ALIGN = 8K; PAGE_SIZE = 2K; diff --git a/boards/stm32f3discovery/layout.ld b/boards/stm32f3discovery/layout.ld index 234dcaea2c..6814c00acd 100644 --- a/boards/stm32f3discovery/layout.ld +++ b/boards/stm32f3discovery/layout.ld @@ -1,2 +1,6 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + INCLUDE ./chip_layout.ld INCLUDE ../kernel_layout.ld diff --git a/boards/stm32f3discovery/openocd.cfg b/boards/stm32f3discovery/openocd.cfg deleted file mode 100644 index b6bcd743c7..0000000000 --- a/boards/stm32f3discovery/openocd.cfg +++ /dev/null @@ -1,18 +0,0 @@ -#interface -interface hla -hla_layout stlink -hla_device_desc "ST-LINK/V2-1" -hla_vid_pid 0x0483 0x374b - -# The chip has 48KB sram -set WORKAREASIZE 0xC000 - -# Revision C (newer revision) -# source [find interface/stlink-v2-1.cfg] - -# Revision A and B (older revisions) -# source [find interface/stlink-v2.cfg] - -source [find target/stm32f3x.cfg] - - diff --git a/boards/stm32f3discovery/src/io.rs b/boards/stm32f3discovery/src/io.rs index 4db06b5ec2..f551763d9c 100644 --- a/boards/stm32f3discovery/src/io.rs +++ b/boards/stm32f3discovery/src/io.rs @@ -1,5 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; +use core::ptr::addr_of; +use core::ptr::addr_of_mut; use kernel::debug; use kernel::debug::IoWrite; @@ -7,7 +13,6 @@ use kernel::hil::led; use kernel::hil::uart; use kernel::hil::uart::Configure; -use stm32f303xc; use stm32f303xc::gpio::PinId; use crate::CHIP; @@ -38,7 +43,7 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { let rcc = stm32f303xc::rcc::Rcc::new(); let uart = stm32f303xc::usart::Usart::new_usart1(&rcc); @@ -46,7 +51,7 @@ impl IoWrite for Writer { self.initialized = true; let _ = uart.configure(uart::Parameters { - baud_rate: 11500, + baud_rate: 115200, stop_bits: uart::StopBits::One, parity: uart::Parity::None, hw_flow_control: false, @@ -57,13 +62,14 @@ impl IoWrite for Writer { for &c in buf { uart.send_byte(c); } + buf.len() } } /// Panic handler. #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(info: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(info: &PanicInfo) -> ! { // User LD3 is connected to PE09 // Have to reinitialize several peripherals because otherwise can't access them here. let rcc = stm32f303xc::rcc::Rcc::new(); @@ -73,15 +79,15 @@ pub unsafe extern "C" fn panic_fmt(info: &PanicInfo) -> ! { let gpio_ports = stm32f303xc::gpio::GpioPorts::new(&rcc, &exti); pin.set_ports_ref(&gpio_ports); let led = &mut led::LedHigh::new(&pin); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led], writer, info, &cortexm4::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/stm32f3discovery/src/main.rs b/boards/stm32f3discovery/src/main.rs index 173352997a..b8eab2950b 100644 --- a/boards/stm32f3discovery/src/main.rs +++ b/boards/stm32f3discovery/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Board file for STM32F3Discovery Kit development board //! //! - @@ -8,12 +12,15 @@ #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] -use capsules::lsm303xx; -use capsules::virtual_alarm::VirtualMuxAlarm; +use core::ptr::addr_of; +use core::ptr::addr_of_mut; + +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; +use capsules_extra::lsm303xx; +use capsules_system::process_printer::ProcessPrinterText; use components::gpio::GpioComponent; use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil::gpio::Configure; use kernel::hil::gpio::Output; use kernel::hil::led::LedHigh; @@ -41,38 +48,54 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] // Static reference to chip for panic dumps. static mut CHIP: Option<&'static stm32f303xc::chip::Stm32f3xx> = None; // Static reference to process printer for panic dumps. -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static ProcessPrinterText> = None; // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; /// Dummy buffer that causes the linker to reserve enough space for the stack. #[no_mangle] #[link_section = ".stack_buffer"] -pub static mut STACK_MEMORY: [u8; 0x1400] = [0; 0x1400]; +pub static mut STACK_MEMORY: [u8; 0x1700] = [0; 0x1700]; + +type L3GD20Sensor = components::l3gd20::L3gd20ComponentType< + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice< + 'static, + stm32f303xc::spi::Spi<'static>, + >, +>; +type TemperatureDriver = components::temperature::TemperatureComponentType; /// A structure representing this platform that holds references to all /// capsules for this platform. struct STM32F3Discovery { - console: &'static capsules::console::Console<'static>, - ipc: kernel::ipc::IPC, - gpio: &'static capsules::gpio::GPIO<'static, stm32f303xc::gpio::Pin<'static>>, - led: &'static capsules::led::LedDriver< + console: &'static capsules_core::console::Console<'static>, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + gpio: &'static capsules_core::gpio::GPIO<'static, stm32f303xc::gpio::Pin<'static>>, + led: &'static capsules_core::led::LedDriver< 'static, LedHigh<'static, stm32f303xc::gpio::Pin<'static>>, 8, >, - button: &'static capsules::button::Button<'static, stm32f303xc::gpio::Pin<'static>>, - ninedof: &'static capsules::ninedof::NineDof<'static>, - l3gd20: &'static capsules::l3gd20::L3gd20Spi<'static>, - lsm303dlhc: &'static capsules::lsm303dlhc::Lsm303dlhcI2C<'static>, - temp: &'static capsules::temperature::TemperatureSensor<'static>, - alarm: &'static capsules::alarm::AlarmDriver< + button: &'static capsules_core::button::Button<'static, stm32f303xc::gpio::Pin<'static>>, + ninedof: &'static capsules_extra::ninedof::NineDof<'static>, + l3gd20: &'static L3GD20Sensor, + lsm303dlhc: &'static capsules_extra::lsm303dlhc::Lsm303dlhcI2C< + 'static, + capsules_core::virtualizers::virtual_i2c::I2CDevice< + 'static, + stm32f303xc::i2c::I2C<'static>, + >, + >, + temp: &'static TemperatureDriver, + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, VirtualMuxAlarm<'static, stm32f303xc::tim2::Tim2<'static>>, >, - adc: &'static capsules::adc::AdcVirtualized<'static>, - nonvolatile_storage: &'static capsules::nonvolatile_storage_driver::NonvolatileStorage<'static>, + adc: &'static capsules_core::adc::AdcVirtualized<'static>, + nonvolatile_storage: + &'static capsules_extra::nonvolatile_storage_driver::NonvolatileStorage<'static>, scheduler: &'static RoundRobinSched<'static>, systick: cortexm4::systick::SysTick, @@ -86,18 +109,20 @@ impl SyscallDriverLookup for STM32F3Discovery { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::button::DRIVER_NUM => f(Some(self.button)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), - capsules::l3gd20::DRIVER_NUM => f(Some(self.l3gd20)), - capsules::lsm303dlhc::DRIVER_NUM => f(Some(self.lsm303dlhc)), - capsules::ninedof::DRIVER_NUM => f(Some(self.ninedof)), - capsules::temperature::DRIVER_NUM => f(Some(self.temp)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_extra::l3gd20::DRIVER_NUM => f(Some(self.l3gd20)), + capsules_extra::lsm303dlhc::DRIVER_NUM => f(Some(self.lsm303dlhc)), + capsules_extra::ninedof::DRIVER_NUM => f(Some(self.ninedof)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temp)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), - capsules::adc::DRIVER_NUM => f(Some(self.adc)), - capsules::nonvolatile_storage_driver::DRIVER_NUM => f(Some(self.nonvolatile_storage)), + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_extra::nonvolatile_storage_driver::DRIVER_NUM => { + f(Some(self.nonvolatile_storage)) + } _ => f(None), } } @@ -120,7 +145,7 @@ impl type ContextSwitchCallback = (); fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -329,6 +354,8 @@ unsafe fn set_pin_primary_functions( unsafe fn setup_peripherals(tim2: &stm32f303xc::tim2::Tim2) { // USART1 IRQn is 37 cortexm4::nvic::Nvic::new(stm32f303xc::nvic::USART1).enable(); + // USART2 IRQn is 38 + cortexm4::nvic::Nvic::new(stm32f303xc::nvic::USART2).enable(); // TIM2 IRQn is 28 tim2.enable_clock(); @@ -336,19 +363,20 @@ unsafe fn setup_peripherals(tim2: &stm32f303xc::tim2::Tim2) { cortexm4::nvic::Nvic::new(stm32f303xc::nvic::TIM2).enable(); } -/// Statically initialize the core peripherals for the chip. +/// Main function. /// /// This is in a separate, inline(never) function so that its stack frame is /// removed when this function returns. Otherwise, the stack space used for /// these static_inits is wasted. #[inline(never)] -unsafe fn get_peripherals() -> ( - &'static mut Stm32f3xxDefaultPeripherals<'static>, - &'static stm32f303xc::syscfg::Syscfg<'static>, - &'static stm32f303xc::rcc::Rcc, +unsafe fn start() -> ( + &'static kernel::Kernel, + STM32F3Discovery, + &'static stm32f303xc::chip::Stm32f3xx<'static, Stm32f3xxDefaultPeripherals<'static>>, ) { - // We use the default HSI 8Mhz clock + stm32f303xc::init(); + // We use the default HSI 8Mhz clock let rcc = static_init!(stm32f303xc::rcc::Rcc, stm32f303xc::rcc::Rcc::new()); let syscfg = static_init!( stm32f303xc::syscfg::Syscfg, @@ -364,17 +392,6 @@ unsafe fn get_peripherals() -> ( Stm32f3xxDefaultPeripherals::new(rcc, exti) ); - (peripherals, syscfg, rcc) -} - -/// Main function. -/// -/// This is called after RAM initialization is complete. -#[no_mangle] -pub unsafe fn main() { - stm32f303xc::init(); - - let (peripherals, syscfg, _rcc) = get_peripherals(); peripherals.setup_circular_deps(); set_pin_primary_functions( @@ -386,14 +403,7 @@ pub unsafe fn main() { setup_peripherals(&peripherals.tim2); - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 3], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); let chip = static_init!( stm32f303xc::chip::Stm32f3xx, @@ -405,12 +415,10 @@ pub unsafe fn main() { // Create a shared UART channel for kernel debug. peripherals.usart1.enable_clock(); - let uart_mux = components::console::UartMuxComponent::new( - &peripherals.usart1, - 115200, - dynamic_deferred_caller, - ) - .finalize(()); + peripherals.usart2.enable_clock(); + + let uart_mux = components::console::UartMuxComponent::new(&peripherals.usart1, 115200) + .finalize(components::uart_mux_component_static!()); // `finalize()` configures the underlying USART, so we need to // tell `send_byte()` not to configure the USART again. @@ -419,70 +427,70 @@ pub unsafe fn main() { // Create capabilities that the board needs to call certain protected kernel // functions. let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); // LEDs // Clock to Port E is enabled in `set_pin_primary_functions()` - let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( LedHigh<'static, stm32f303xc::gpio::Pin<'static>>, LedHigh::new( - &peripherals + peripherals .gpio_ports .get_pin(stm32f303xc::gpio::PinId::PE09) .unwrap() ), LedHigh::new( - &peripherals + peripherals .gpio_ports .get_pin(stm32f303xc::gpio::PinId::PE08) .unwrap() ), LedHigh::new( - &peripherals + peripherals .gpio_ports .get_pin(stm32f303xc::gpio::PinId::PE10) .unwrap() ), LedHigh::new( - &peripherals + peripherals .gpio_ports .get_pin(stm32f303xc::gpio::PinId::PE15) .unwrap() ), LedHigh::new( - &peripherals + peripherals .gpio_ports .get_pin(stm32f303xc::gpio::PinId::PE11) .unwrap() ), LedHigh::new( - &peripherals + peripherals .gpio_ports .get_pin(stm32f303xc::gpio::PinId::PE14) .unwrap() ), LedHigh::new( - &peripherals + peripherals .gpio_ports .get_pin(stm32f303xc::gpio::PinId::PE12) .unwrap() ), LedHigh::new( - &peripherals + peripherals .gpio_ports .get_pin(stm32f303xc::gpio::PinId::PE13) .unwrap() @@ -492,11 +500,11 @@ pub unsafe fn main() { // BUTTONs let button = components::button::ButtonComponent::new( board_kernel, - capsules::button::DRIVER_NUM, + capsules_core::button::DRIVER_NUM, components::button_component_helper!( stm32f303xc::gpio::Pin<'static>, ( - &peripherals + peripherals .gpio_ports .get_pin(stm32f303xc::gpio::PinId::PA00) .unwrap(), @@ -505,7 +513,7 @@ pub unsafe fn main() { ) ), ) - .finalize(components::button_component_buf!( + .finalize(components::button_component_static!( stm32f303xc::gpio::Pin<'static> )); @@ -513,159 +521,160 @@ pub unsafe fn main() { let tim2 = &peripherals.tim2; let mux_alarm = components::alarm::AlarmMuxComponent::new(tim2).finalize( - components::alarm_mux_component_helper!(stm32f303xc::tim2::Tim2), + components::alarm_mux_component_static!(stm32f303xc::tim2::Tim2), ); let alarm = components::alarm::AlarmDriverComponent::new( board_kernel, - capsules::alarm::DRIVER_NUM, + capsules_core::alarm::DRIVER_NUM, mux_alarm, ) - .finalize(components::alarm_component_helper!(stm32f303xc::tim2::Tim2)); + .finalize(components::alarm_component_static!(stm32f303xc::tim2::Tim2)); let gpio_ports = &peripherals.gpio_ports; // GPIO let gpio = GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( stm32f303xc::gpio::Pin<'static>, // Left outer connector - 0 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC01).unwrap(), - 1 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC03).unwrap(), - // 2 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA01).unwrap(), - // 3 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA03).unwrap(), - // 4 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PF04).unwrap(), - // 5 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA05).unwrap(), - // 6 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA07).unwrap(), - // 7 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC05).unwrap(), - // 8 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB01).unwrap(), - 9 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE07).unwrap(), - // 10 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE09).unwrap(), - 11 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE11).unwrap(), - // 12 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE13).unwrap(), - // 13 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE15).unwrap(), - 14 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB11).unwrap(), - // 15 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB13).unwrap(), - // 16 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB15).unwrap(), - 17 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD09).unwrap(), - 18 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD11).unwrap(), - 19 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD13).unwrap(), - 20 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD15).unwrap(), - 21 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC06).unwrap(), + 0 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC01).unwrap(), + 1 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC03).unwrap(), + // 2 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA01).unwrap(), + // 3 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA03).unwrap(), + // 4 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PF04).unwrap(), + // 5 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA05).unwrap(), + // 6 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA07).unwrap(), + // 7 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC05).unwrap(), + // 8 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB01).unwrap(), + 9 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE07).unwrap(), + // 10 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE09).unwrap(), + 11 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE11).unwrap(), + // 12 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE13).unwrap(), + // 13 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE15).unwrap(), + 14 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB11).unwrap(), + // 15 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB13).unwrap(), + // 16 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB15).unwrap(), + 17 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD09).unwrap(), + 18 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD11).unwrap(), + 19 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD13).unwrap(), + 20 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD15).unwrap(), + 21 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC06).unwrap(), // Left inner connector - 22 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC00).unwrap(), - 23 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC02).unwrap(), - 24 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PF02).unwrap(), - // 25 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA00).unwrap(), - // 26 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA02).unwrap(), - // 27 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA04).unwrap(), - // 28 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA06).unwrap(), - // 29 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC04).unwrap(), - 30 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB00).unwrap(), - 31 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB02).unwrap(), - 32 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE08).unwrap(), - 33 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE10).unwrap(), - 34 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE12).unwrap(), - // 35 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE14).unwrap(), - 36 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB10).unwrap(), - // 37 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB12).unwrap(), - // 38 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB14).unwrap(), - 39 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD08).unwrap(), - 40 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD10).unwrap(), - 41 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD12).unwrap(), - 42 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD14).unwrap(), - 43 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC07).unwrap(), + 22 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC00).unwrap(), + 23 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC02).unwrap(), + 24 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PF02).unwrap(), + // 25 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA00).unwrap(), + // 26 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA02).unwrap(), + // 27 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA04).unwrap(), + // 28 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA06).unwrap(), + // 29 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC04).unwrap(), + 30 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB00).unwrap(), + 31 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB02).unwrap(), + 32 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE08).unwrap(), + 33 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE10).unwrap(), + 34 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE12).unwrap(), + // 35 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE14).unwrap(), + 36 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB10).unwrap(), + // 37 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB12).unwrap(), + // 38 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB14).unwrap(), + 39 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD08).unwrap(), + 40 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD10).unwrap(), + 41 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD12).unwrap(), + 42 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD14).unwrap(), + 43 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC07).unwrap(), // Right inner connector - 44 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PF09).unwrap(), - 45 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PF00).unwrap(), - 46 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC14).unwrap(), - 47 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE06).unwrap(), - 48 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE04).unwrap(), - 49 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE02).unwrap(), - 50 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE00).unwrap(), - 51 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB08).unwrap(), + 44 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PF09).unwrap(), + 45 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PF00).unwrap(), + 46 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC14).unwrap(), + 47 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE06).unwrap(), + 48 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE04).unwrap(), + 49 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE02).unwrap(), + 50 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE00).unwrap(), + 51 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB08).unwrap(), // 52 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB06).unwrap(), - 53 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB04).unwrap(), - 54 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD07).unwrap(), - 55 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD05).unwrap(), - 56 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD03).unwrap(), - 57 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD01).unwrap(), - 58 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC12).unwrap(), - 59 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC10).unwrap(), - 60 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA14).unwrap(), - 61 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PF06).unwrap(), - 62 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA12).unwrap(), - 63 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA10).unwrap(), - 64 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA08).unwrap(), - 65 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC08).unwrap(), + 53 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB04).unwrap(), + 54 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD07).unwrap(), + 55 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD05).unwrap(), + 56 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD03).unwrap(), + 57 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD01).unwrap(), + 58 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC12).unwrap(), + 59 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC10).unwrap(), + 60 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA14).unwrap(), + 61 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PF06).unwrap(), + 62 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA12).unwrap(), + 63 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA10).unwrap(), + 64 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA08).unwrap(), + 65 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC08).unwrap(), // Right outer connector - 66 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PF10).unwrap(), - 67 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PF01).unwrap(), - 68 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC15).unwrap(), - 69 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC13).unwrap(), - 70 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE05).unwrap(), - 71 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE03).unwrap(), - 72 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE01).unwrap(), - 73 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB09).unwrap(), - // 74 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB07).unwrap(), - 75 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB05).unwrap(), - 76 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB03).unwrap(), - 77 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD06).unwrap(), - 78 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD04).unwrap(), - 79 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD02).unwrap(), - 80 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD00).unwrap(), - 81 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC11).unwrap(), - 82 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA15).unwrap(), - 83 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA13).unwrap(), - 84 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA11).unwrap(), - 85 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA09).unwrap(), - 86 => &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC09).unwrap() + 66 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PF10).unwrap(), + 67 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PF01).unwrap(), + 68 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC15).unwrap(), + 69 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC13).unwrap(), + 70 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE05).unwrap(), + 71 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE03).unwrap(), + 72 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE01).unwrap(), + 73 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB09).unwrap(), + // 74 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB07).unwrap(), + 75 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB05).unwrap(), + 76 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PB03).unwrap(), + 77 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD06).unwrap(), + 78 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD04).unwrap(), + 79 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD02).unwrap(), + 80 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PD00).unwrap(), + 81 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC11).unwrap(), + 82 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA15).unwrap(), + 83 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA13).unwrap(), + 84 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA11).unwrap(), + 85 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PA09).unwrap(), + 86 => gpio_ports.get_pin(stm32f303xc::gpio::PinId::PC09).unwrap() ), ) - .finalize(components::gpio_component_buf!( + .finalize(components::gpio_component_static!( stm32f303xc::gpio::Pin<'static> )); // L3GD20 sensor - let spi_mux = components::spi::SpiMuxComponent::new(&peripherals.spi1, dynamic_deferred_caller) - .finalize(components::spi_mux_component_helper!(stm32f303xc::spi::Spi)); - - let l3gd20 = - components::l3gd20::L3gd20SpiComponent::new(board_kernel, capsules::l3gd20::DRIVER_NUM) - .finalize(components::l3gd20_spi_component_helper!( - // spi type - stm32f303xc::spi::Spi, - // chip select - &gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE03).unwrap(), - // spi mux - spi_mux - )); + let spi_mux = components::spi::SpiMuxComponent::new(&peripherals.spi1) + .finalize(components::spi_mux_component_static!(stm32f303xc::spi::Spi)); - l3gd20.power_on(); + let l3gd20 = components::l3gd20::L3gd20Component::new( + spi_mux, + gpio_ports.get_pin(stm32f303xc::gpio::PinId::PE03).unwrap(), + board_kernel, + capsules_extra::l3gd20::DRIVER_NUM, + ) + .finalize(components::l3gd20_component_static!( + // spi type + stm32f303xc::spi::Spi + )); - let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let grant_temperature = - board_kernel.create_grant(capsules::temperature::DRIVER_NUM, &grant_cap); + l3gd20.power_on(); // Comment this if you want to use the ADC MCU temp sensor - let temp = static_init!( - capsules::temperature::TemperatureSensor<'static>, - capsules::temperature::TemperatureSensor::new(l3gd20, grant_temperature) - ); - kernel::hil::sensors::TemperatureDriver::set_client(l3gd20, temp); + let temp = components::temperature::TemperatureComponent::new( + board_kernel, + capsules_extra::temperature::DRIVER_NUM, + l3gd20, + ) + .finalize(components::temperature_component_static!(L3GD20Sensor)); // LSM303DLHC - let mux_i2c = - components::i2c::I2CMuxComponent::new(&peripherals.i2c1, None, dynamic_deferred_caller) - .finalize(components::i2c_mux_component_helper!()); + let mux_i2c = components::i2c::I2CMuxComponent::new(&peripherals.i2c1, None) + .finalize(components::i2c_mux_component_static!(stm32f303xc::i2c::I2C)); let lsm303dlhc = components::lsm303dlhc::Lsm303dlhcI2CComponent::new( + mux_i2c, + None, + None, board_kernel, - capsules::lsm303dlhc::DRIVER_NUM, + capsules_extra::lsm303dlhc::DRIVER_NUM, ) - .finalize(components::lsm303dlhc_i2c_component_helper!(mux_i2c)); + .finalize(components::lsm303dlhc_component_static!( + stm32f303xc::i2c::I2C + )); if let Err(error) = lsm303dlhc.configure( lsm303xx::Lsm303AccelDataRate::DataRate25Hz, @@ -679,16 +688,18 @@ pub unsafe fn main() { debug!("Failed to configure LSM303DLHC sensor ({:?})", error); } - let ninedof = - components::ninedof::NineDofComponent::new(board_kernel, capsules::ninedof::DRIVER_NUM) - .finalize(components::ninedof_component_helper!(l3gd20, lsm303dlhc)); + let ninedof = components::ninedof::NineDofComponent::new( + board_kernel, + capsules_extra::ninedof::DRIVER_NUM, + ) + .finalize(components::ninedof_component_static!(l3gd20, lsm303dlhc)); let adc_mux = components::adc::AdcMuxComponent::new(&peripherals.adc1) - .finalize(components::adc_mux_component_helper!(stm32f303xc::adc::Adc)); + .finalize(components::adc_mux_component_static!(stm32f303xc::adc::Adc)); // Uncomment this if you want to use ADC MCU temp sensor // let temp_sensor = components::temperature_stm::TemperatureSTMComponent::new(4.3, 1.43) - // .finalize(components::temperaturestm_adc_component_helper!( + // .finalize(components::temperaturestm_adc_component_static!( // // spi type // stm32f303xc::adc::Adc, // // chip select @@ -700,34 +711,34 @@ pub unsafe fn main() { // let grant_temperature = board_kernel.create_grant(&grant_cap); // let temp = static_init!( - // capsules::temperature::TemperatureSensor<'static>, - // capsules::temperature::TemperatureSensor::new(temp_sensor, grant_temperature) + // capsules_extra::temperature::TemperatureSensor<'static>, + // capsules_extra::temperature::TemperatureSensor::new(temp_sensor, grant_temperature) // ); // kernel::hil::sensors::TemperatureDriver::set_client(temp_sensor, temp); // shared with button // let adc_channel_1 = // components::adc::AdcComponent::new(&adc_mux, stm32f303xc::adc::Channel::Channel1) - // .finalize(components::adc_component_helper!(stm32f303xc::adc::Adc)); + // .finalize(components::adc_component_static!(stm32f303xc::adc::Adc)); let adc_channel_2 = - components::adc::AdcComponent::new(&adc_mux, stm32f303xc::adc::Channel::Channel2) - .finalize(components::adc_component_helper!(stm32f303xc::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f303xc::adc::Channel::Channel2) + .finalize(components::adc_component_static!(stm32f303xc::adc::Adc)); let adc_channel_3 = - components::adc::AdcComponent::new(&adc_mux, stm32f303xc::adc::Channel::Channel3) - .finalize(components::adc_component_helper!(stm32f303xc::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f303xc::adc::Channel::Channel3) + .finalize(components::adc_component_static!(stm32f303xc::adc::Adc)); let adc_channel_4 = - components::adc::AdcComponent::new(&adc_mux, stm32f303xc::adc::Channel::Channel4) - .finalize(components::adc_component_helper!(stm32f303xc::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f303xc::adc::Channel::Channel4) + .finalize(components::adc_component_static!(stm32f303xc::adc::Adc)); let adc_channel_5 = - components::adc::AdcComponent::new(&adc_mux, stm32f303xc::adc::Channel::Channel5) - .finalize(components::adc_component_helper!(stm32f303xc::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f303xc::adc::Channel::Channel5) + .finalize(components::adc_component_static!(stm32f303xc::adc::Adc)); let adc_syscall = - components::adc::AdcVirtualComponent::new(board_kernel, capsules::adc::DRIVER_NUM) + components::adc::AdcVirtualComponent::new(board_kernel, capsules_core::adc::DRIVER_NUM) .finalize(components::adc_syscall_component_helper!( adc_channel_2, adc_channel_3, @@ -745,19 +756,19 @@ pub unsafe fn main() { let nonvolatile_storage = components::nonvolatile_storage::NonvolatileStorageComponent::new( board_kernel, - capsules::nonvolatile_storage_driver::DRIVER_NUM, + capsules_extra::nonvolatile_storage_driver::DRIVER_NUM, &peripherals.flash, 0x08038000, // Start address for userspace accesible region 0x8000, // Length of userspace accesible region (16 pages) - &_sstorage as *const u8 as usize, - &_estorage as *const u8 as usize - &_sstorage as *const u8 as usize, + core::ptr::addr_of!(_sstorage) as usize, + core::ptr::addr_of!(_estorage) as usize - core::ptr::addr_of!(_sstorage) as usize, ) - .finalize(components::nv_storage_component_helper!( + .finalize(components::nonvolatile_storage_component_static!( stm32f303xc::flash::Flash )); - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); // PROCESS CONSOLE @@ -766,32 +777,33 @@ pub unsafe fn main() { uart_mux, mux_alarm, process_printer, + Some(cortexm4::support::reset), ) - .finalize(components::process_console_component_helper!( + .finalize(components::process_console_component_static!( stm32f303xc::tim2::Tim2 )); let _ = process_console.start(); - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); let stm32f3discovery = STM32F3Discovery { - console: console, + console, ipc: kernel::ipc::IPC::new( board_kernel, kernel::ipc::DRIVER_NUM, &memory_allocation_capability, ), - gpio: gpio, - led: led, - button: button, - alarm: alarm, - l3gd20: l3gd20, - lsm303dlhc: lsm303dlhc, - ninedof: ninedof, - temp: temp, + gpio, + led, + button, + alarm, + l3gd20, + lsm303dlhc, + ninedof, + temp, adc: adc_syscall, - nonvolatile_storage: nonvolatile_storage, + nonvolatile_storage, scheduler, systick: cortexm4::systick::SysTick::new(), @@ -805,7 +817,7 @@ pub unsafe fn main() { debug!("Initialization complete. Entering main loop"); - /// These symbols are defined in the linker script. + // These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -821,14 +833,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_management_capability, ) @@ -844,10 +856,15 @@ pub unsafe fn main() { /*components::test::multi_alarm_test::MultiAlarmTestComponent::new(mux_alarm) .finalize(components::multi_alarm_test_component_buf!(stm32f303xc::tim2::Tim2)) .run();*/ - board_kernel.kernel_loop( - &stm32f3discovery, - chip, - Some(&stm32f3discovery.ipc), - &main_loop_capability, - ); + + (board_kernel, stm32f3discovery, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); } diff --git a/boards/stm32f3discovery/src/virtual_uart_rx_test.rs b/boards/stm32f3discovery/src/virtual_uart_rx_test.rs index ba869dadca..81c6b126d3 100644 --- a/boards/stm32f3discovery/src/virtual_uart_rx_test.rs +++ b/boards/stm32f3discovery/src/virtual_uart_rx_test.rs @@ -1,10 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test reception on the virtualized UART by creating two readers that //! read in parallel. To add this test, include the line //! ``` //! virtual_uart_rx_test::run_virtual_uart_receive(mux_uart); //! ``` //! to the nucleo_446re boot sequence, where `mux_uart` is a -//! `capsules::virtual_uart::MuxUart`. There is a 3-byte and a 7-byte read +//! `capsules_core::virtualizers::virtual_uart::MuxUart`. There is a 3-byte and a 7-byte read //! running in parallel. Test that they are both working by typing and seeing //! that they both get all characters. If you repeatedly type 'a', for example //! (0x61), you should see something like: @@ -44,8 +48,10 @@ //! 61 //! ``` -use capsules::test::virtual_uart::TestVirtualUartReceive; -use capsules::virtual_uart::{MuxUart, UartDevice}; +use core::ptr::addr_of_mut; + +use capsules_core::test::virtual_uart::TestVirtualUartReceive; +use capsules_core::virtualizers::virtual_uart::{MuxUart, UartDevice}; use kernel::debug; use kernel::hil::uart::Receive; use kernel::static_init; @@ -66,7 +72,7 @@ unsafe fn static_init_test_receive_small( device.setup(); let test = static_init!( TestVirtualUartReceive, - TestVirtualUartReceive::new(device, &mut SMALL) + TestVirtualUartReceive::new(device, &mut *addr_of_mut!(SMALL)) ); device.set_receive_client(test); test @@ -80,7 +86,7 @@ unsafe fn static_init_test_receive_large( device.setup(); let test = static_init!( TestVirtualUartReceive, - TestVirtualUartReceive::new(device, &mut BUFFER) + TestVirtualUartReceive::new(device, &mut *addr_of_mut!(BUFFER)) ); device.set_receive_client(test); test diff --git a/boards/stm32f412gdiscovery/Cargo.toml b/boards/stm32f412gdiscovery/Cargo.toml index 336c6090dc..ad53dd58d2 100644 --- a/boards/stm32f412gdiscovery/Cargo.toml +++ b/boards/stm32f412gdiscovery/Cargo.toml @@ -1,13 +1,23 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "stm32f412gdiscovery" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] components = { path = "../components" } cortexm4 = { path = "../../arch/cortex-m4" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } stm32f412g = { path = "../../chips/stm32f412g" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/stm32f412gdiscovery/Makefile b/boards/stm32f412gdiscovery/Makefile index bc5643f5b3..2c353b7f8f 100644 --- a/boards/stm32f412gdiscovery/Makefile +++ b/boards/stm32f412gdiscovery/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the stm32412gdiscovery platform # TARGET=thumbv7em-none-eabi @@ -6,7 +10,6 @@ PLATFORM=stm32f412gdiscovery include ../Makefile.common OPENOCD=openocd -OPENOCD_OPTIONS=-f openocd.cfg # Default target for installing the kernel. .PHONY: install @@ -14,11 +17,11 @@ install: flash .PHONY: flash-debug flash-debug: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).elf - $(OPENOCD) $(OPENOCD_OPTIONS) -c "init; reset halt; flash write_image erase $<; verify_image $<; reset; shutdown" + $(OPENOCD) -c "source [find board/stm32f412g-disco.cfg]; init; reset halt; flash write_image erase $<; verify_image $<; reset; shutdown" .PHONY: flash flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf - $(OPENOCD) $(OPENOCD_OPTIONS) -c "init; reset halt; flash write_image erase $<; verify_image $<; reset; shutdown" + $(OPENOCD) -c "source [find board/stm32f412g-disco.cfg]; init; reset halt; flash write_image erase $<; verify_image $<; reset; shutdown" .PHONY: program program: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).elf diff --git a/boards/stm32f412gdiscovery/README.md b/boards/stm32f412gdiscovery/README.md index 5e53fed433..39e4674c8a 100644 --- a/boards/stm32f412gdiscovery/README.md +++ b/boards/stm32f412gdiscovery/README.md @@ -70,7 +70,7 @@ $ git fetch http://openocd.zylin.com/openocd refs/changes/21/4321/7 && git cherr $ ./bootstrap $ ./configure --disable-werror $ make -# optinally use sudo make install +# optionally use sudo make install ``` > Please note that you may have some conflicts in a file containing a list of diff --git a/boards/stm32f412gdiscovery/build.rs b/boards/stm32f412gdiscovery/build.rs deleted file mode 100644 index 46b1df0bac..0000000000 --- a/boards/stm32f412gdiscovery/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=chip_layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/stm32f412gdiscovery/chip_layout.ld b/boards/stm32f412gdiscovery/chip_layout.ld index b8a2fb9487..3e844d875b 100644 --- a/boards/stm32f412gdiscovery/chip_layout.ld +++ b/boards/stm32f412gdiscovery/chip_layout.ld @@ -1,14 +1,16 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + /* Memory layout for the STM32F412G * rom = 1MB (LENGTH = 0x01000000) - * kernel = 192KB - * user = 832KB + * kernel = 256KB + * user = 744KB * ram = 256KB */ MEMORY { - rom (rx) : ORIGIN = 0x08000000, LENGTH = 0x00030000 - prog (rx) : ORIGIN = 0x08030000, LENGTH = 0x000D0000 + rom (rx) : ORIGIN = 0x08000000, LENGTH = 0x00040000 + prog (rx) : ORIGIN = 0x08040000, LENGTH = 0x000C0000 ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x0003FFFF } - -MPU_MIN_ALIGN = 8K; diff --git a/boards/stm32f412gdiscovery/layout.ld b/boards/stm32f412gdiscovery/layout.ld index 234dcaea2c..6814c00acd 100644 --- a/boards/stm32f412gdiscovery/layout.ld +++ b/boards/stm32f412gdiscovery/layout.ld @@ -1,2 +1,6 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + INCLUDE ./chip_layout.ld INCLUDE ../kernel_layout.ld diff --git a/boards/stm32f412gdiscovery/openocd.cfg b/boards/stm32f412gdiscovery/openocd.cfg deleted file mode 100644 index 5e2cc76c87..0000000000 --- a/boards/stm32f412gdiscovery/openocd.cfg +++ /dev/null @@ -1,21 +0,0 @@ -#interface -interface hla -hla_layout stlink -hla_device_desc "ST-LINK/V2-1" -hla_vid_pid 0x0483 0x374b - -set WORKAREASIZE 0x40000 - -source [find target/stm32f4x.cfg] - -# patched openocd -# this setup requires a patched version of openocd that fully supports stm32f412g-disco - -# #interface -# source [find board/stm32f412g-disco.cfg] -# source [find interface/stlink.cfg] -# hla_serial "\x30\x36\x37\x31\x46\x46\x33\x33\x33\x30\x33\x36\x34\x33\x34\x42\x34\x33\x30\x37\x33\x30\x30\x39" - -# set WORKAREASIZE 0x40000 - - diff --git a/boards/stm32f412gdiscovery/src/io.rs b/boards/stm32f412gdiscovery/src/io.rs index 8db62c1aa3..00310a860f 100644 --- a/boards/stm32f412gdiscovery/src/io.rs +++ b/boards/stm32f412gdiscovery/src/io.rs @@ -1,7 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; - -use cortexm4; +use core::ptr::addr_of; +use core::ptr::addr_of_mut; use kernel::debug; use kernel::debug::IoWrite; @@ -9,7 +13,7 @@ use kernel::hil::led; use kernel::hil::uart; use kernel::hil::uart::Configure; -use stm32f412g; +use stm32f412g::chip_specs::Stm32f412Specs; use stm32f412g::gpio::PinId; use crate::CHIP; @@ -40,9 +44,11 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { let rcc = stm32f412g::rcc::Rcc::new(); - let uart = stm32f412g::usart::Usart::new_usart2(&rcc); + let clocks: stm32f412g::clocks::Clocks = + stm32f412g::clocks::Clocks::new(&rcc); + let uart = stm32f412g::usart::Usart::new_usart2(&clocks); if !self.initialized { self.initialized = true; @@ -59,31 +65,34 @@ impl IoWrite for Writer { for &c in buf { uart.send_byte(c); } + + buf.len() } } /// Panic handler. #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(info: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(info: &PanicInfo) -> ! { // User LD2 is connected to PB07 // Have to reinitialize several peripherals because otherwise can't access them here. let rcc = stm32f412g::rcc::Rcc::new(); - let syscfg = stm32f412g::syscfg::Syscfg::new(&rcc); + let clocks: stm32f412g::clocks::Clocks = stm32f412g::clocks::Clocks::new(&rcc); + let syscfg = stm32f412g::syscfg::Syscfg::new(&clocks); let exti = stm32f412g::exti::Exti::new(&syscfg); let pin = stm32f412g::gpio::Pin::new(PinId::PE02, &exti); - let gpio_ports = stm32f412g::gpio::GpioPorts::new(&rcc, &exti); + let gpio_ports = stm32f412g::gpio::GpioPorts::new(&clocks, &exti); pin.set_ports_ref(&gpio_ports); let led = &mut led::LedHigh::new(&pin); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led], writer, info, &cortexm4::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/stm32f412gdiscovery/src/main.rs b/boards/stm32f412gdiscovery/src/main.rs index bdc7a9d457..acecf89583 100644 --- a/boards/stm32f412gdiscovery/src/main.rs +++ b/boards/stm32f412gdiscovery/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Board file for STM32F412GDiscovery Discovery kit development board //! //! - @@ -7,18 +11,20 @@ // https://github.com/rust-lang/rust/issues/62184. #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] -use capsules::virtual_alarm::VirtualMuxAlarm; +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; use components::gpio::GpioComponent; use components::rng::RngComponent; use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil::gpio; use kernel::hil::led::LedLow; use kernel::hil::screen::ScreenRotation; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::scheduler::round_robin::RoundRobinSched; use kernel::{create_capability, debug, static_init}; +use stm32f412g::chip_specs::Stm32f412Specs; use stm32f412g::interrupt_service::Stm32f412gDefaultPeripherals; /// Support routines for debugging I/O. @@ -32,37 +38,45 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] [None, None, None, None]; static mut CHIP: Option<&'static stm32f412g::chip::Stm32f4xx> = None; -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; /// Dummy buffer that causes the linker to reserve enough space for the stack. #[no_mangle] #[link_section = ".stack_buffer"] pub static mut STACK_MEMORY: [u8; 0x2000] = [0; 0x2000]; +type TemperatureSTMSensor = components::temperature_stm::TemperatureSTMComponentType< + capsules_core::virtualizers::virtual_adc::AdcDevice<'static, stm32f412g::adc::Adc<'static>>, +>; +type TemperatureDriver = components::temperature::TemperatureComponentType; +type RngDriver = components::rng::RngComponentType>; + /// A structure representing this platform that holds references to all /// capsules for this platform. struct STM32F412GDiscovery { - console: &'static capsules::console::Console<'static>, - ipc: kernel::ipc::IPC, - led: &'static capsules::led::LedDriver< + console: &'static capsules_core::console::Console<'static>, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + led: &'static capsules_core::led::LedDriver< 'static, LedLow<'static, stm32f412g::gpio::Pin<'static>>, 4, >, - button: &'static capsules::button::Button<'static, stm32f412g::gpio::Pin<'static>>, - alarm: &'static capsules::alarm::AlarmDriver< + button: &'static capsules_core::button::Button<'static, stm32f412g::gpio::Pin<'static>>, + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, VirtualMuxAlarm<'static, stm32f412g::tim2::Tim2<'static>>, >, - gpio: &'static capsules::gpio::GPIO<'static, stm32f412g::gpio::Pin<'static>>, - adc: &'static capsules::adc::AdcVirtualized<'static>, - touch: &'static capsules::touch::Touch<'static>, - screen: &'static capsules::screen::Screen<'static>, - temperature: &'static capsules::temperature::TemperatureSensor<'static>, - rng: &'static capsules::rng::RngDriver<'static>, + gpio: &'static capsules_core::gpio::GPIO<'static, stm32f412g::gpio::Pin<'static>>, + adc: &'static capsules_core::adc::AdcVirtualized<'static>, + touch: &'static capsules_extra::touch::Touch<'static>, + screen: &'static capsules_extra::screen::Screen<'static>, + temperature: &'static TemperatureDriver, + rng: &'static RngDriver, scheduler: &'static RoundRobinSched<'static>, systick: cortexm4::systick::SysTick, @@ -75,17 +89,17 @@ impl SyscallDriverLookup for STM32F412GDiscovery { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::button::DRIVER_NUM => f(Some(self.button)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), - capsules::adc::DRIVER_NUM => f(Some(self.adc)), - capsules::touch::DRIVER_NUM => f(Some(self.touch)), - capsules::screen::DRIVER_NUM => f(Some(self.screen)), - capsules::temperature::DRIVER_NUM => f(Some(self.temperature)), - capsules::rng::DRIVER_NUM => f(Some(self.rng)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_extra::touch::DRIVER_NUM => f(Some(self.touch)), + capsules_extra::screen::DRIVER_NUM => f(Some(self.screen)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temperature)), + capsules_core::rng::DRIVER_NUM => f(Some(self.rng)), _ => f(None), } } @@ -108,7 +122,7 @@ impl type ContextSwitchCallback = (); fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -132,11 +146,11 @@ impl /// Helper function called during bring-up that configures DMA. unsafe fn setup_dma( - dma: &stm32f412g::dma1::Dma1, - dma_streams: &'static [stm32f412g::dma1::Stream; 8], - usart2: &'static stm32f412g::usart::Usart, + dma: &stm32f412g::dma::Dma1, + dma_streams: &'static [stm32f412g::dma::Stream; 8], + usart2: &'static stm32f412g::usart::Usart, ) { - use stm32f412g::dma1::Dma1Peripheral; + use stm32f412g::dma::Dma1Peripheral; use stm32f412g::usart; dma.enable_clock(); @@ -360,44 +374,44 @@ unsafe fn setup_peripherals( // FSMC fsmc.enable(); + // RNG trng.enable_clock(); } -/// Statically initialize the core peripherals for the chip. +/// Main function. /// /// This is in a separate, inline(never) function so that its stack frame is /// removed when this function returns. Otherwise, the stack space used for /// these static_inits is wasted. #[inline(never)] -unsafe fn get_peripherals() -> ( - &'static mut Stm32f412gDefaultPeripherals<'static>, - &'static stm32f412g::syscfg::Syscfg<'static>, - &'static stm32f412g::dma1::Dma1<'static>, +unsafe fn start() -> ( + &'static kernel::Kernel, + STM32F412GDiscovery, + &'static stm32f412g::chip::Stm32f4xx<'static, Stm32f412gDefaultPeripherals<'static>>, ) { + stm32f412g::init(); + let rcc = static_init!(stm32f412g::rcc::Rcc, stm32f412g::rcc::Rcc::new()); + let clocks = static_init!( + stm32f412g::clocks::Clocks, + stm32f412g::clocks::Clocks::new(rcc) + ); + let syscfg = static_init!( stm32f412g::syscfg::Syscfg, - stm32f412g::syscfg::Syscfg::new(rcc) + stm32f412g::syscfg::Syscfg::new(clocks) ); let exti = static_init!(stm32f412g::exti::Exti, stm32f412g::exti::Exti::new(syscfg)); - let dma1 = static_init!(stm32f412g::dma1::Dma1, stm32f412g::dma1::Dma1::new(rcc)); + + let dma1 = static_init!(stm32f412g::dma::Dma1, stm32f412g::dma::Dma1::new(clocks)); + let dma2 = static_init!(stm32f412g::dma::Dma2, stm32f412g::dma::Dma2::new(clocks)); let peripherals = static_init!( Stm32f412gDefaultPeripherals, - Stm32f412gDefaultPeripherals::new(rcc, exti, dma1) + Stm32f412gDefaultPeripherals::new(clocks, exti, dma1, dma2) ); - (peripherals, syscfg, dma1) -} - -/// Main function. -/// -/// This is called after RAM initialization is complete. -#[no_mangle] -pub unsafe fn main() { - stm32f412g::init(); - let (peripherals, syscfg, dma1) = get_peripherals(); peripherals.init(); let base_peripherals = &peripherals.stm32f4; setup_peripherals( @@ -411,19 +425,11 @@ pub unsafe fn main() { setup_dma( dma1, - &base_peripherals.dma_streams, + &base_peripherals.dma1_streams, &base_peripherals.usart2, ); - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); - - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 2], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); let chip = static_init!( stm32f412g::chip::Stm32f4xx, @@ -435,37 +441,33 @@ pub unsafe fn main() { // Create a shared UART channel for kernel debug. base_peripherals.usart2.enable_clock(); - let uart_mux = components::console::UartMuxComponent::new( - &base_peripherals.usart2, - 115200, - dynamic_deferred_caller, - ) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(&base_peripherals.usart2, 115200) + .finalize(components::uart_mux_component_static!()); io::WRITER.set_initialized(); // Create capabilities that the board needs to call certain protected kernel // functions. let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); // LEDs // Clock to Port A is enabled in `set_pin_primary_functions()` - let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( LedLow<'static, stm32f412g::gpio::Pin>, LedLow::new( base_peripherals @@ -496,7 +498,7 @@ pub unsafe fn main() { // BUTTONs let button = components::button::ButtonComponent::new( board_kernel, - capsules::button::DRIVER_NUM, + capsules_core::button::DRIVER_NUM, components::button_component_helper!( stm32f412g::gpio::Pin, // Select @@ -546,26 +548,26 @@ pub unsafe fn main() { ) ), ) - .finalize(components::button_component_buf!(stm32f412g::gpio::Pin)); + .finalize(components::button_component_static!(stm32f412g::gpio::Pin)); // ALARM let tim2 = &base_peripherals.tim2; let mux_alarm = components::alarm::AlarmMuxComponent::new(tim2).finalize( - components::alarm_mux_component_helper!(stm32f412g::tim2::Tim2), + components::alarm_mux_component_static!(stm32f412g::tim2::Tim2), ); let alarm = components::alarm::AlarmDriverComponent::new( board_kernel, - capsules::alarm::DRIVER_NUM, + capsules_core::alarm::DRIVER_NUM, mux_alarm, ) - .finalize(components::alarm_component_helper!(stm32f412g::tim2::Tim2)); + .finalize(components::alarm_component_static!(stm32f412g::tim2::Tim2)); // GPIO let gpio = GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( stm32f412g::gpio::Pin, // Arduino like RX/TX @@ -595,77 +597,71 @@ pub unsafe fn main() { // 20 => base_peripherals.gpio_ports.get_pin(stm32f412g::gpio::PinId::PB00).unwrap() //A5 ), ) - .finalize(components::gpio_component_buf!(stm32f412g::gpio::Pin)); + .finalize(components::gpio_component_static!(stm32f412g::gpio::Pin)); // RNG - let rng = - RngComponent::new(board_kernel, capsules::rng::DRIVER_NUM, &peripherals.trng).finalize(()); + let rng = RngComponent::new( + board_kernel, + capsules_core::rng::DRIVER_NUM, + &peripherals.trng, + ) + .finalize(components::rng_component_static!(stm32f412g::trng::Trng)); // FT6206 - let mux_i2c = components::i2c::I2CMuxComponent::new( - &base_peripherals.i2c1, - None, - dynamic_deferred_caller, - ) - .finalize(components::i2c_mux_component_helper!()); + let mux_i2c = components::i2c::I2CMuxComponent::new(&base_peripherals.i2c1, None) + .finalize(components::i2c_mux_component_static!(stm32f412g::i2c::I2C)); let ft6x06 = components::ft6x06::Ft6x06Component::new( + mux_i2c, + 0x38, base_peripherals .gpio_ports .get_pin(stm32f412g::gpio::PinId::PG05) .unwrap(), ) - .finalize(components::ft6x06_i2c_component_helper!(mux_i2c)); - - let bus = components::bus::Bus8080BusComponent::new().finalize( - components::bus8080_bus_component_helper!( - // bus type - stm32f412g::fsmc::Fsmc, - // bus - &base_peripherals.fsmc - ), - ); + .finalize(components::ft6x06_component_static!(stm32f412g::i2c::I2C)); - let tft = components::st77xx::ST77XXComponent::new(mux_alarm).finalize( - components::st77xx_component_helper!( - // screen - &capsules::st77xx::ST7789H2, - // bus type - capsules::bus::Bus8080Bus<'static, stm32f412g::fsmc::Fsmc>, - // bus - &bus, - // timer type - stm32f412g::tim2::Tim2, - // pin type - stm32f412g::gpio::Pin, - // dc pin (optional) - None, - // reset pin - base_peripherals - .gpio_ports - .get_pin(stm32f412g::gpio::PinId::PD11) - ), + let bus = components::bus::Bus8080BusComponent::new(&base_peripherals.fsmc).finalize( + components::bus8080_bus_component_static!(stm32f412g::fsmc::Fsmc,), ); + let tft = components::st77xx::ST77XXComponent::new( + mux_alarm, + bus, + None, + base_peripherals + .gpio_ports + .get_pin(stm32f412g::gpio::PinId::PD11), + &capsules_extra::st77xx::ST7789H2, + ) + .finalize(components::st77xx_component_static!( + // bus type + capsules_extra::bus::Bus8080Bus<'static, stm32f412g::fsmc::Fsmc>, + // timer type + stm32f412g::tim2::Tim2, + // pin type + stm32f412g::gpio::Pin, + )); + let _ = tft.init(); let screen = components::screen::ScreenComponent::new( board_kernel, - capsules::screen::DRIVER_NUM, + capsules_extra::screen::DRIVER_NUM, tft, Some(tft), ) - .finalize(components::screen_buffer_size!(57600)); + .finalize(components::screen_component_static!(57600)); let touch = components::touch::MultiTouchComponent::new( board_kernel, - capsules::touch::DRIVER_NUM, + capsules_extra::touch::DRIVER_NUM, ft6x06, Some(ft6x06), Some(tft), ) - .finalize(()); + .finalize(components::touch_component_static!()); touch.set_screen_rotation_offset(ScreenRotation::Rotated90); @@ -676,53 +672,53 @@ pub unsafe fn main() { // ADC let adc_mux = components::adc::AdcMuxComponent::new(&base_peripherals.adc1) - .finalize(components::adc_mux_component_helper!(stm32f412g::adc::Adc)); - - let temp_sensor = components::temperature_stm::TemperatureSTMComponent::new(2.5, 0.76) - .finalize(components::temperaturestm_adc_component_helper!( - // spi type - stm32f412g::adc::Adc, - // chip select - stm32f412g::adc::Channel::Channel18, - // spi mux - adc_mux - )); - let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); - let grant_temperature = - board_kernel.create_grant(capsules::temperature::DRIVER_NUM, &grant_cap); - - let temp = static_init!( - capsules::temperature::TemperatureSensor<'static>, - capsules::temperature::TemperatureSensor::new(temp_sensor, grant_temperature) - ); - kernel::hil::sensors::TemperatureDriver::set_client(temp_sensor, temp); + .finalize(components::adc_mux_component_static!(stm32f412g::adc::Adc)); + + let temp_sensor = components::temperature_stm::TemperatureSTMComponent::new( + adc_mux, + stm32f412g::adc::Channel::Channel18, + 2.5, + 0.76, + ) + .finalize(components::temperature_stm_adc_component_static!( + stm32f412g::adc::Adc + )); + + let temp = components::temperature::TemperatureComponent::new( + board_kernel, + capsules_extra::temperature::DRIVER_NUM, + temp_sensor, + ) + .finalize(components::temperature_component_static!( + TemperatureSTMSensor + )); let adc_channel_0 = - components::adc::AdcComponent::new(&adc_mux, stm32f412g::adc::Channel::Channel1) - .finalize(components::adc_component_helper!(stm32f412g::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f412g::adc::Channel::Channel1) + .finalize(components::adc_component_static!(stm32f412g::adc::Adc)); let adc_channel_1 = - components::adc::AdcComponent::new(&adc_mux, stm32f412g::adc::Channel::Channel11) - .finalize(components::adc_component_helper!(stm32f412g::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f412g::adc::Channel::Channel11) + .finalize(components::adc_component_static!(stm32f412g::adc::Adc)); let adc_channel_2 = - components::adc::AdcComponent::new(&adc_mux, stm32f412g::adc::Channel::Channel13) - .finalize(components::adc_component_helper!(stm32f412g::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f412g::adc::Channel::Channel13) + .finalize(components::adc_component_static!(stm32f412g::adc::Adc)); let adc_channel_3 = - components::adc::AdcComponent::new(&adc_mux, stm32f412g::adc::Channel::Channel14) - .finalize(components::adc_component_helper!(stm32f412g::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f412g::adc::Channel::Channel14) + .finalize(components::adc_component_static!(stm32f412g::adc::Adc)); let adc_channel_4 = - components::adc::AdcComponent::new(&adc_mux, stm32f412g::adc::Channel::Channel15) - .finalize(components::adc_component_helper!(stm32f412g::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f412g::adc::Channel::Channel15) + .finalize(components::adc_component_static!(stm32f412g::adc::Adc)); let adc_channel_5 = - components::adc::AdcComponent::new(&adc_mux, stm32f412g::adc::Channel::Channel8) - .finalize(components::adc_component_helper!(stm32f412g::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f412g::adc::Channel::Channel8) + .finalize(components::adc_component_static!(stm32f412g::adc::Adc)); let adc_syscall = - components::adc::AdcVirtualComponent::new(board_kernel, capsules::adc::DRIVER_NUM) + components::adc::AdcVirtualComponent::new(board_kernel, capsules_core::adc::DRIVER_NUM) .finalize(components::adc_syscall_component_helper!( adc_channel_0, adc_channel_1, @@ -732,8 +728,8 @@ pub unsafe fn main() { adc_channel_5 )); - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); // PROCESS CONSOLE @@ -742,14 +738,15 @@ pub unsafe fn main() { uart_mux, mux_alarm, process_printer, + Some(cortexm4::support::reset), ) - .finalize(components::process_console_component_helper!( + .finalize(components::process_console_component_static!( stm32f412g::tim2::Tim2 )); let _ = process_console.start(); - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); let stm32f412g = STM32F412GDiscovery { console, @@ -807,14 +804,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_management_capability, ) @@ -828,10 +825,14 @@ pub unsafe fn main() { .finalize(components::multi_alarm_test_component_buf!(stm32f412g::tim2::Tim2)) .run();*/ - board_kernel.kernel_loop( - &stm32f412g, - chip, - Some(&stm32f412g.ipc), - &main_loop_capability, - ); + (board_kernel, stm32f412g, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); } diff --git a/boards/stm32f429idiscovery/Cargo.toml b/boards/stm32f429idiscovery/Cargo.toml new file mode 100644 index 0000000000..a0b25541f5 --- /dev/null +++ b/boards/stm32f429idiscovery/Cargo.toml @@ -0,0 +1,23 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[package] +name = "stm32f429idiscovery" +version.workspace = true +authors.workspace = true +edition.workspace = true +build = "../build.rs" + +[dependencies] +components = { path = "../components" } +cortexm4 = { path = "../../arch/cortex-m4" } +kernel = { path = "../../kernel" } +stm32f429zi = { path = "../../chips/stm32f429zi" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/stm32f429idiscovery/Makefile b/boards/stm32f429idiscovery/Makefile new file mode 100644 index 0000000000..5af464414f --- /dev/null +++ b/boards/stm32f429idiscovery/Makefile @@ -0,0 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +# Makefile for building the tock kernel for the STM32F429i Discovery board +# +TARGET=thumbv7em-none-eabi +PLATFORM=stm32f429idiscovery + +include ../Makefile.common + +OPENOCD=openocd + +# Default target for installing the kernel. +.PHONY: install +install: flash + +.PHONY: flash-debug +flash-debug: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).bin + $(OPENOCD) -c "source [find board/stm32f429discovery.cfg]; init; reset halt; program $< verify 0x08000000; reset; shutdown" + +.PHONY: flash +flash: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin + $(OPENOCD) -c "source [find board/stm32f429discovery.cfg]; init; reset halt; program $< verify 0x08000000; reset; shutdown" diff --git a/boards/stm32f429idiscovery/README.md b/boards/stm32f429idiscovery/README.md new file mode 100644 index 0000000000..ba26df9e8d --- /dev/null +++ b/boards/stm32f429idiscovery/README.md @@ -0,0 +1,59 @@ +STM32F429I Discovery development board with STM32F429ZI MCU +====================================================== + +Note: This board layout is based on the nucleo_f429zi board layout. + +For more details [visit the STM32F429I Discovery website](https://www.st.com/en/evaluation-tools/32f429idiscovery.html). + +## Flashing the kernel + +The kernel can be programmed using OpenOCD. `cd` into `boards/std32f429idiscovery` +directory and run: + +```bash +$ make flash + +(or) + +$ make flash-debug +``` + +> **Note:** Unlike other Tock platforms, the default kernel image for this +> board will clear flashed apps when the kernel is loaded. This is to support +> the non-tockloader based app flash procedure below. To preserve loaded apps, +> comment out the `APP_HACK` variable in `src/main.rs`. + +## Flashing app + +Apps are built out-of-tree. Once an app is built, you can use +`arm-none-eabi-objcopy` with `--update-section` to create an ELF image with the +apps included. + +```bash +$ arm-none-eabi-objcopy \ + --update-section .apps=../../../libtock-c/examples/c_hello/build/cortex-m4/cortex-m4.tbf \ + target/thumbv7em-none-eabi/debug/stm32f429idiscovery.elf \ + target/thumbv7em-none-eabi/debug/stm32f429idiscovery-app.elf +``` + +For example, you can update `Makefile` as follows. + +``` +APP=../../../libtock-c/examples/c_hello/build/cortex-m4/cortex-m4.tbf +KERNEL=$(TOCK_ROOT_DIRECTORY)/target/$(TARGET)/debug/$(PLATFORM).elf +KERNEL_WITH_APP=$(TOCK_ROOT_DIRECTORY)/target/$(TARGET)/debug/$(PLATFORM)-app.elf + +.PHONY: program +program: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/debug/$(PLATFORM).elf + arm-none-eabi-objcopy --update-section .apps=$(APP) $(KERNEL) $(KERNEL_WITH_APP) + $(OPENOCD) $(OPENOCD_OPTIONS) -c "init; reset halt; flash write_image erase $(KERNEL_WITH_APP); verify_image $(KERNEL_WITH_APP); reset; shutdown" +``` + +After setting `APP`, `KERNEL`, `KERNEL_WITH_APP`, and `program` target +dependency, you can do + +```bash +$ make program +``` + +to flash the image. diff --git a/boards/stm32f429idiscovery/chip_layout.ld b/boards/stm32f429idiscovery/chip_layout.ld new file mode 100644 index 0000000000..cdd58a3eed --- /dev/null +++ b/boards/stm32f429idiscovery/chip_layout.ld @@ -0,0 +1,18 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + +/* Memory layout for the STM32F446RE + * rom = 2MB (LENGTH = 0x02000000) + * kernel = 256KB + * user = 256KB + * ram = 192KB */ + +MEMORY +{ + rom (rx) : ORIGIN = 0x08000000, LENGTH = 0x00040000 + prog (rx) : ORIGIN = 0x08040000, LENGTH = 0x00040000 + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 0x00030000 +} + +PAGE_SIZE = 2K; diff --git a/boards/stm32f429idiscovery/layout.ld b/boards/stm32f429idiscovery/layout.ld new file mode 100644 index 0000000000..6814c00acd --- /dev/null +++ b/boards/stm32f429idiscovery/layout.ld @@ -0,0 +1,6 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + +INCLUDE ./chip_layout.ld +INCLUDE ../kernel_layout.ld diff --git a/boards/stm32f429idiscovery/src/io.rs b/boards/stm32f429idiscovery/src/io.rs new file mode 100644 index 0000000000..7f1e5d0e8d --- /dev/null +++ b/boards/stm32f429idiscovery/src/io.rs @@ -0,0 +1,100 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use core::fmt::Write; +use core::panic::PanicInfo; +use core::ptr::addr_of; +use core::ptr::addr_of_mut; + +use kernel::debug; +use kernel::debug::IoWrite; +use kernel::hil::led; +use kernel::hil::uart; +use kernel::hil::uart::Configure; + +use stm32f429zi::chip_specs::Stm32f429Specs; +use stm32f429zi::gpio::PinId; + +use crate::CHIP; +use crate::PROCESSES; +use crate::PROCESS_PRINTER; + +/// Writer is used by kernel::debug to panic message to the serial port. +pub struct Writer { + initialized: bool, +} + +/// Global static for debug writer +pub static mut WRITER: Writer = Writer { initialized: false }; + +impl Writer { + /// Indicate that USART has already been initialized. Trying to double + /// initialize USART1 causes stm32f429zi to go into in in-deterministic state. + pub fn set_initialized(&mut self) { + self.initialized = true; + } +} + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> ::core::fmt::Result { + self.write(s.as_bytes()); + Ok(()) + } +} + +impl IoWrite for Writer { + fn write(&mut self, buf: &[u8]) -> usize { + let rcc = stm32f429zi::rcc::Rcc::new(); + let clocks: stm32f429zi::clocks::Clocks = + stm32f429zi::clocks::Clocks::new(&rcc); + let uart = stm32f429zi::usart::Usart::new_usart1(&clocks); + + if !self.initialized { + self.initialized = true; + + let _ = uart.configure(uart::Parameters { + baud_rate: 115200, + stop_bits: uart::StopBits::One, + parity: uart::Parity::None, + hw_flow_control: false, + width: uart::Width::Eight, + }); + } + + for &c in buf { + uart.send_byte(c); + } + + buf.len() + } +} + +/// Panic handler. +#[no_mangle] +#[panic_handler] +pub unsafe fn panic_fmt(info: &PanicInfo) -> ! { + // User LD4 is connected to PG14 + // Have to reinitialize several peripherals because otherwise can't access them here. + let rcc = stm32f429zi::rcc::Rcc::new(); + let clocks: stm32f429zi::clocks::Clocks = + stm32f429zi::clocks::Clocks::new(&rcc); + let syscfg = stm32f429zi::syscfg::Syscfg::new(&clocks); + let exti = stm32f429zi::exti::Exti::new(&syscfg); + let pin = stm32f429zi::gpio::Pin::new(PinId::PG14, &exti); + let gpio_ports = stm32f429zi::gpio::GpioPorts::new(&clocks, &exti); + pin.set_ports_ref(&gpio_ports); + let led = &mut led::LedHigh::new(&pin); + + let writer = &mut *addr_of_mut!(WRITER); + + debug::panic( + &mut [led], + writer, + info, + &cortexm4::support::nop, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), + ) +} diff --git a/boards/stm32f429idiscovery/src/main.rs b/boards/stm32f429idiscovery/src/main.rs new file mode 100644 index 0000000000..44e1b90dd5 --- /dev/null +++ b/boards/stm32f429idiscovery/src/main.rs @@ -0,0 +1,651 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Board file for STM32F429I Discovery development board +//! +//! - + +#![no_std] +// Disable this attribute when documenting, as a workaround for +// https://github.com/rust-lang/rust/issues/62184. +#![cfg_attr(not(doc), no_main)] +#![deny(missing_docs)] + +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; +use components::gpio::GpioComponent; +use kernel::capabilities; +use kernel::component::Component; +use kernel::hil::led::LedHigh; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::scheduler::round_robin::RoundRobinSched; +use kernel::{create_capability, debug, static_init}; + +use stm32f429zi::chip_specs::Stm32f429Specs; +use stm32f429zi::gpio::{AlternateFunction, Mode, PinId, PortId}; +use stm32f429zi::interrupt_service::Stm32f429ziDefaultPeripherals; + +/// Support routines for debugging I/O. +pub mod io; + +// Number of concurrent processes this platform supports. +const NUM_PROCS: usize = 4; + +// Actual memory for holding the active process structures. +static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = + [None, None, None, None]; + +static mut CHIP: Option<&'static stm32f429zi::chip::Stm32f4xx> = + None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; + +// How should the kernel respond when a process faults. +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; + +/// Dummy buffer that causes the linker to reserve enough space for the stack. +#[no_mangle] +#[link_section = ".stack_buffer"] +pub static mut STACK_MEMORY: [u8; 0x2000] = [0; 0x2000]; + +type TemperatureSTMSensor = components::temperature_stm::TemperatureSTMComponentType< + capsules_core::virtualizers::virtual_adc::AdcDevice<'static, stm32f429zi::adc::Adc<'static>>, +>; +type TemperatureDriver = components::temperature::TemperatureComponentType; + +/// A structure representing this platform that holds references to all +/// capsules for this platform. +struct STM32F429IDiscovery { + console: &'static capsules_core::console::Console<'static>, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + led: &'static capsules_core::led::LedDriver< + 'static, + LedHigh<'static, stm32f429zi::gpio::Pin<'static>>, + 4, + >, + button: &'static capsules_core::button::Button<'static, stm32f429zi::gpio::Pin<'static>>, + adc: &'static capsules_core::adc::AdcVirtualized<'static>, + alarm: &'static capsules_core::alarm::AlarmDriver< + 'static, + VirtualMuxAlarm<'static, stm32f429zi::tim2::Tim2<'static>>, + >, + temperature: &'static TemperatureDriver, + gpio: &'static capsules_core::gpio::GPIO<'static, stm32f429zi::gpio::Pin<'static>>, + + scheduler: &'static RoundRobinSched<'static>, + systick: cortexm4::systick::SysTick, +} + +/// Mapping of integer syscalls to objects that implement syscalls. +impl SyscallDriverLookup for STM32F429IDiscovery { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temperature)), + kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + _ => f(None), + } + } +} + +impl + KernelResources< + stm32f429zi::chip::Stm32f4xx< + 'static, + stm32f429zi::interrupt_service::Stm32f429ziDefaultPeripherals<'static>, + >, + > for STM32F429IDiscovery +{ + type SyscallDriverLookup = Self; + type SyscallFilter = (); + type ProcessFault = (); + type Scheduler = RoundRobinSched<'static>; + type SchedulerTimer = cortexm4::systick::SysTick; + type WatchDog = (); + type ContextSwitchCallback = (); + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + &() + } + fn process_fault(&self) -> &Self::ProcessFault { + &() + } + fn scheduler(&self) -> &Self::Scheduler { + self.scheduler + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + &self.systick + } + fn watchdog(&self) -> &Self::WatchDog { + &() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + &() + } +} + +/// Helper function called during bring-up that configures DMA. +unsafe fn setup_dma( + dma: &stm32f429zi::dma::Dma2, + dma_streams: &'static [stm32f429zi::dma::Stream<'static, stm32f429zi::dma::Dma2>; 8], + usart1: &'static stm32f429zi::usart::Usart, +) { + use stm32f429zi::dma::Dma2Peripheral; + use stm32f429zi::usart; + + dma.enable_clock(); + + let usart1_tx_stream = &dma_streams[Dma2Peripheral::USART1_TX.get_stream_idx()]; + let usart1_rx_stream = &dma_streams[Dma2Peripheral::USART1_RX.get_stream_idx()]; + + usart1.set_dma( + usart::TxDMA(usart1_tx_stream), + usart::RxDMA(usart1_rx_stream), + ); + + usart1_tx_stream.set_client(usart1); + usart1_rx_stream.set_client(usart1); + + usart1_tx_stream.setup(Dma2Peripheral::USART1_TX); + usart1_rx_stream.setup(Dma2Peripheral::USART1_RX); + + cortexm4::nvic::Nvic::new(Dma2Peripheral::USART1_TX.get_stream_irqn()).enable(); + cortexm4::nvic::Nvic::new(Dma2Peripheral::USART1_RX.get_stream_irqn()).enable(); +} + +/// Helper function called during bring-up that configures multiplexed I/O. +unsafe fn set_pin_primary_functions( + syscfg: &stm32f429zi::syscfg::Syscfg, + gpio_ports: &'static stm32f429zi::gpio::GpioPorts<'static>, +) { + use kernel::hil::gpio::Configure; + + syscfg.enable_clock(); + + gpio_ports.get_port_from_port_id(PortId::G).enable_clock(); + + // User LD4 (red) is connected to PG14. Configure PG14 as `debug_gpio!(0, ...)` + gpio_ports.get_pin(PinId::PG14).map(|pin| { + pin.make_output(); + + // Configure kernel debug gpios as early as possible + kernel::debug::assign_gpios(Some(pin), None, None); + }); + + gpio_ports.get_port_from_port_id(PortId::A).enable_clock(); + + // Configure USART1 on Pins PA09 and PA10. + // USART1 is connected to ST-LINK virtual COM port on Rev.1 of the Stm32f429i Discovery board + gpio_ports.get_pin(PinId::PA09).map(|pin| { + pin.set_mode(Mode::AlternateFunctionMode); + // AF7 is USART1_TX + pin.set_alternate_function(AlternateFunction::AF7); + }); + gpio_ports.get_pin(PinId::PA10).map(|pin| { + pin.set_mode(Mode::AlternateFunctionMode); + // AF7 is USART1_RX + pin.set_alternate_function(AlternateFunction::AF7); + }); + + // User button B1 is connected on pa00 + gpio_ports.get_pin(PinId::PA00).map(|pin| { + // By default, upon reset, the pin is in input mode, with no internal + // pull-up, no internal pull-down (i.e., floating). + // + // Only set the mapping between EXTI line and the Pin and let capsule do + // the rest. + pin.enable_interrupt(); + }); + // EXTI0 interrupts is delivered at IRQn 6 (EXTI0) + cortexm4::nvic::Nvic::new(stm32f429zi::nvic::EXTI0).enable(); // TODO check if this is still necessary! + + // Enable clocks for GPIO Ports + // Disable some of them if you don't need some of the GPIOs + // Ports A, and B are already enabled + // A: already enabled + gpio_ports.get_port_from_port_id(PortId::B).enable_clock(); + gpio_ports.get_port_from_port_id(PortId::C).enable_clock(); + gpio_ports.get_port_from_port_id(PortId::D).enable_clock(); + gpio_ports.get_port_from_port_id(PortId::E).enable_clock(); + gpio_ports.get_port_from_port_id(PortId::F).enable_clock(); + // G: already enabled + gpio_ports.get_port_from_port_id(PortId::H).enable_clock(); + + // Arduino A0 + gpio_ports.get_pin(PinId::PA03).map(|pin| { + pin.set_mode(stm32f429zi::gpio::Mode::AnalogMode); + }); + + // Arduino A1 + gpio_ports.get_pin(PinId::PC00).map(|pin| { + pin.set_mode(stm32f429zi::gpio::Mode::AnalogMode); + }); + + // Arduino A2 + gpio_ports.get_pin(PinId::PC03).map(|pin| { + pin.set_mode(stm32f429zi::gpio::Mode::AnalogMode); + }); + + // Arduino A3 + gpio_ports.get_pin(PinId::PF03).map(|pin| { + pin.set_mode(stm32f429zi::gpio::Mode::AnalogMode); + }); + + // Arduino A4 + gpio_ports.get_pin(PinId::PF05).map(|pin| { + pin.set_mode(stm32f429zi::gpio::Mode::AnalogMode); + }); + + // Arduino A5 + gpio_ports.get_pin(PinId::PF10).map(|pin| { + pin.set_mode(stm32f429zi::gpio::Mode::AnalogMode); + }); +} + +/// Helper function for miscellaneous peripheral functions +unsafe fn setup_peripherals(tim2: &stm32f429zi::tim2::Tim2) { + // USART1 IRQn is 37 + cortexm4::nvic::Nvic::new(stm32f429zi::nvic::USART1).enable(); + + // TIM2 IRQn is 28 + tim2.enable_clock(); + tim2.start(); + cortexm4::nvic::Nvic::new(stm32f429zi::nvic::TIM2).enable(); +} + +/// Main function +/// +/// This is in a separate, inline(never) function so that its stack frame is +/// removed when this function returns. Otherwise, the stack space used for +/// these static_inits is wasted. +#[inline(never)] +unsafe fn start() -> ( + &'static kernel::Kernel, + STM32F429IDiscovery, + &'static stm32f429zi::chip::Stm32f4xx<'static, Stm32f429ziDefaultPeripherals<'static>>, +) { + stm32f429zi::init(); + + // We use the default HSI 16Mhz clock + let rcc = static_init!(stm32f429zi::rcc::Rcc, stm32f429zi::rcc::Rcc::new()); + let clocks = static_init!( + stm32f429zi::clocks::Clocks, + stm32f429zi::clocks::Clocks::new(rcc) + ); + let syscfg = static_init!( + stm32f429zi::syscfg::Syscfg, + stm32f429zi::syscfg::Syscfg::new(clocks) + ); + let exti = static_init!( + stm32f429zi::exti::Exti, + stm32f429zi::exti::Exti::new(syscfg) + ); + let dma1 = static_init!(stm32f429zi::dma::Dma1, stm32f429zi::dma::Dma1::new(clocks)); + let dma2 = static_init!(stm32f429zi::dma::Dma2, stm32f429zi::dma::Dma2::new(clocks)); + let peripherals = static_init!( + Stm32f429ziDefaultPeripherals, + Stm32f429ziDefaultPeripherals::new(clocks, exti, dma1, dma2) + ); + + peripherals.init(); + let base_peripherals = &peripherals.stm32f4; + + setup_peripherals(&base_peripherals.tim2); + + set_pin_primary_functions(syscfg, &base_peripherals.gpio_ports); + + setup_dma( + dma2, + &base_peripherals.dma2_streams, + &base_peripherals.usart1, + ); + + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); + + let chip = static_init!( + stm32f429zi::chip::Stm32f4xx, + stm32f429zi::chip::Stm32f4xx::new(peripherals) + ); + CHIP = Some(chip); + + // UART + + // Create a shared UART channel for kernel debug. + // USART1 is only connected to the ST-LINK port in the DISC1 revision of + // the STM32F429I boards, DISC0 does not have this connection and will + // not have USART output available! + base_peripherals.usart1.enable_clock(); + let uart_mux = components::console::UartMuxComponent::new(&base_peripherals.usart1, 115200) + .finalize(components::uart_mux_component_static!()); + + io::WRITER.set_initialized(); + + // Create capabilities that the board needs to call certain protected kernel + // functions. + let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); + let process_management_capability = + create_capability!(capabilities::ProcessManagementCapability); + + // Setup the console. + let console = components::console::ConsoleComponent::new( + board_kernel, + capsules_core::console::DRIVER_NUM, + uart_mux, + ) + .finalize(components::console_component_static!()); + // Create the debugger object that handles calls to `debug!()`. + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); + + // LEDs + + // Clock to all GPIO Ports is enabled in `set_pin_primary_functions()` + let gpio_ports = &base_peripherals.gpio_ports; + + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( + LedHigh<'static, stm32f429zi::gpio::Pin>, + LedHigh::new(gpio_ports.get_pin(stm32f429zi::gpio::PinId::PG13).unwrap()), + LedHigh::new(gpio_ports.get_pin(stm32f429zi::gpio::PinId::PG14).unwrap()), + LedHigh::new(gpio_ports.get_pin(stm32f429zi::gpio::PinId::PB13).unwrap()), + LedHigh::new(gpio_ports.get_pin(stm32f429zi::gpio::PinId::PC05).unwrap()), + )); + + // BUTTONs + let button = components::button::ButtonComponent::new( + board_kernel, + capsules_core::button::DRIVER_NUM, + components::button_component_helper!( + stm32f429zi::gpio::Pin, + ( + gpio_ports.get_pin(stm32f429zi::gpio::PinId::PA00).unwrap(), + kernel::hil::gpio::ActivationMode::ActiveHigh, + kernel::hil::gpio::FloatingState::PullNone + ) + ), + ) + .finalize(components::button_component_static!(stm32f429zi::gpio::Pin)); + + // ALARM + + let tim2 = &base_peripherals.tim2; + let mux_alarm = components::alarm::AlarmMuxComponent::new(tim2).finalize( + components::alarm_mux_component_static!(stm32f429zi::tim2::Tim2), + ); + + let alarm = components::alarm::AlarmDriverComponent::new( + board_kernel, + capsules_core::alarm::DRIVER_NUM, + mux_alarm, + ) + .finalize(components::alarm_component_static!(stm32f429zi::tim2::Tim2)); + + // GPIO + let gpio = GpioComponent::new( + board_kernel, + capsules_core::gpio::DRIVER_NUM, + components::gpio_component_helper!( + stm32f429zi::gpio::Pin, + // Arduino like RX/TX + 0 => gpio_ports.get_pin(PinId::PG09).unwrap(), //D0 + 1 => gpio_ports.pins[6][14].as_ref().unwrap(), //D1 + 2 => gpio_ports.pins[5][15].as_ref().unwrap(), //D2 + 3 => gpio_ports.pins[4][13].as_ref().unwrap(), //D3 + 4 => gpio_ports.pins[5][14].as_ref().unwrap(), //D4 + 5 => gpio_ports.pins[4][11].as_ref().unwrap(), //D5 + 6 => gpio_ports.pins[4][9].as_ref().unwrap(), //D6 + 7 => gpio_ports.pins[5][13].as_ref().unwrap(), //D7 + 8 => gpio_ports.pins[5][12].as_ref().unwrap(), //D8 + 9 => gpio_ports.pins[3][15].as_ref().unwrap(), //D9 + // SPI Pins + 10 => gpio_ports.pins[3][14].as_ref().unwrap(), //D10 + 11 => gpio_ports.pins[0][7].as_ref().unwrap(), //D11 + 12 => gpio_ports.pins[0][6].as_ref().unwrap(), //D12 + 13 => gpio_ports.pins[0][5].as_ref().unwrap(), //D13 + // I2C Pins + 14 => gpio_ports.pins[1][9].as_ref().unwrap(), //D14 + 15 => gpio_ports.pins[1][8].as_ref().unwrap(), //D15 + 16 => gpio_ports.pins[2][6].as_ref().unwrap(), //D16 + 17 => gpio_ports.pins[1][15].as_ref().unwrap(), //D17 + 18 => gpio_ports.pins[1][13].as_ref().unwrap(), //D18 + 19 => gpio_ports.pins[1][12].as_ref().unwrap(), //D19 + 20 => gpio_ports.pins[0][15].as_ref().unwrap(), //D20 + 21 => gpio_ports.pins[2][7].as_ref().unwrap(), //D21 + // SPI B Pins + // 22 => gpio_ports.pins[1][5].as_ref().unwrap(), //D22 + // 23 => gpio_ports.pins[1][3].as_ref().unwrap(), //D23 + // 24 => gpio_ports.pins[0][4].as_ref().unwrap(), //D24 + // 24 => gpio_ports.pins[1][4].as_ref().unwrap(), //D25 + // QSPI + 26 => gpio_ports.pins[1][6].as_ref().unwrap(), //D26 + 27 => gpio_ports.pins[1][2].as_ref().unwrap(), //D27 + 28 => gpio_ports.pins[3][13].as_ref().unwrap(), //D28 + 29 => gpio_ports.pins[3][12].as_ref().unwrap(), //D29 + 30 => gpio_ports.pins[3][11].as_ref().unwrap(), //D30 + 31 => gpio_ports.pins[4][2].as_ref().unwrap(), //D31 + // Timer Pins + // PA00 (or PIN[0][0]) is used for the button component so cannot + // be used for this component as well, otherwise interrupts will + // not reach the button component. + // 32 => stm32f429zi::gpio::PIN[0][0].as_ref().unwrap(), //D32 + 33 => gpio_ports.pins[1][0].as_ref().unwrap(), //D33 + 34 => gpio_ports.pins[4][0].as_ref().unwrap(), //D34 + 35 => gpio_ports.pins[1][11].as_ref().unwrap(), //D35 + 36 => gpio_ports.pins[1][10].as_ref().unwrap(), //D36 + 37 => gpio_ports.pins[4][15].as_ref().unwrap(), //D37 + 38 => gpio_ports.pins[4][14].as_ref().unwrap(), //D38 + 39 => gpio_ports.pins[4][12].as_ref().unwrap(), //D39 + 40 => gpio_ports.pins[4][10].as_ref().unwrap(), //D40 + 41 => gpio_ports.pins[4][7].as_ref().unwrap(), //D41 + 42 => gpio_ports.pins[4][8].as_ref().unwrap(), //D42 + // SDMMC + 43 => gpio_ports.pins[2][8].as_ref().unwrap(), //D43 + 44 => gpio_ports.pins[2][9].as_ref().unwrap(), //D44 + 45 => gpio_ports.pins[2][10].as_ref().unwrap(), //D45 + 46 => gpio_ports.pins[2][11].as_ref().unwrap(), //D46 + 47 => gpio_ports.pins[2][12].as_ref().unwrap(), //D47 + 48 => gpio_ports.pins[3][2].as_ref().unwrap(), //D48 + 49 => gpio_ports.pins[6][2].as_ref().unwrap(), //D49 + 50 => gpio_ports.pins[6][3].as_ref().unwrap(), //D50 + // USART + 51 => gpio_ports.pins[3][7].as_ref().unwrap(), //D51 + 52 => gpio_ports.pins[3][6].as_ref().unwrap(), //D52 + 53 => gpio_ports.pins[3][5].as_ref().unwrap(), //D53 + 54 => gpio_ports.pins[3][4].as_ref().unwrap(), //D54 + 55 => gpio_ports.pins[3][3].as_ref().unwrap(), //D55 + 56 => gpio_ports.pins[4][2].as_ref().unwrap(), //D56 + 57 => gpio_ports.pins[4][4].as_ref().unwrap(), //D57 + 58 => gpio_ports.pins[4][5].as_ref().unwrap(), //D58 + 59 => gpio_ports.pins[4][6].as_ref().unwrap(), //D59 + 60 => gpio_ports.pins[4][3].as_ref().unwrap(), //D60 + 61 => gpio_ports.pins[5][8].as_ref().unwrap(), //D61 + 62 => gpio_ports.pins[5][7].as_ref().unwrap(), //D62 + 63 => gpio_ports.pins[5][9].as_ref().unwrap(), //D63 + 64 => gpio_ports.pins[6][1].as_ref().unwrap(), //D64 + 65 => gpio_ports.pins[6][0].as_ref().unwrap(), //D65 + 66 => gpio_ports.pins[3][1].as_ref().unwrap(), //D66 + 67 => gpio_ports.pins[3][0].as_ref().unwrap(), //D67 + 68 => gpio_ports.pins[5][0].as_ref().unwrap(), //D68 + 69 => gpio_ports.pins[5][1].as_ref().unwrap(), //D69 + 70 => gpio_ports.pins[5][2].as_ref().unwrap(), //D70 + 71 => gpio_ports.pins[0][7].as_ref().unwrap() //D71 + + // ADC Pins + // Enable the to use the ADC pins as GPIO + // 72 => gpio_ports.pins[0][3].as_ref().unwrap(), //A0 + // 73 => gpio_ports.pins[2][0].as_ref().unwrap(), //A1 + // 74 gpio_ports.pins::PIN[2][3].as_ref().unwrap(), //A2 + // 75 gpio_ports.pins::PIN[5][3].as_ref().unwrap(), //A3 + // 76 gpio_ports.pins::PIN[5][5].as_ref().unwrap(), //A4 + // 77 gpio_ports.pins::PIN[5][10].as_ref().unwrap(), //A5 + // 78 gpio_ports.pins::PIN[1][1].as_ref().unwrap(), //A6 + // 79 gpio_ports.pins::PIN[2][2].as_ref().unwrap(), //A7 + // 80 gpio_ports.pins::PIN[5][4].as_ref().unwrap() //A8 + ), + ) + .finalize(components::gpio_component_static!(stm32f429zi::gpio::Pin)); + + // ADC + let adc_mux = components::adc::AdcMuxComponent::new(&base_peripherals.adc1) + .finalize(components::adc_mux_component_static!(stm32f429zi::adc::Adc)); + + let temp_sensor = components::temperature_stm::TemperatureSTMComponent::new( + adc_mux, + stm32f429zi::adc::Channel::Channel18, + 2.5, + 0.76, + ) + .finalize(components::temperature_stm_adc_component_static!( + stm32f429zi::adc::Adc + )); + + let temp = components::temperature::TemperatureComponent::new( + board_kernel, + capsules_extra::temperature::DRIVER_NUM, + temp_sensor, + ) + .finalize(components::temperature_component_static!( + TemperatureSTMSensor + )); + + let adc_channel_0 = + components::adc::AdcComponent::new(adc_mux, stm32f429zi::adc::Channel::Channel3) + .finalize(components::adc_component_static!(stm32f429zi::adc::Adc)); + + let adc_channel_1 = + components::adc::AdcComponent::new(adc_mux, stm32f429zi::adc::Channel::Channel10) + .finalize(components::adc_component_static!(stm32f429zi::adc::Adc)); + + let adc_channel_2 = + components::adc::AdcComponent::new(adc_mux, stm32f429zi::adc::Channel::Channel13) + .finalize(components::adc_component_static!(stm32f429zi::adc::Adc)); + + let adc_channel_3 = + components::adc::AdcComponent::new(adc_mux, stm32f429zi::adc::Channel::Channel9) + .finalize(components::adc_component_static!(stm32f429zi::adc::Adc)); + + let adc_channel_4 = + components::adc::AdcComponent::new(adc_mux, stm32f429zi::adc::Channel::Channel15) + .finalize(components::adc_component_static!(stm32f429zi::adc::Adc)); + + let adc_channel_5 = + components::adc::AdcComponent::new(adc_mux, stm32f429zi::adc::Channel::Channel8) + .finalize(components::adc_component_static!(stm32f429zi::adc::Adc)); + + let adc_syscall = + components::adc::AdcVirtualComponent::new(board_kernel, capsules_core::adc::DRIVER_NUM) + .finalize(components::adc_syscall_component_helper!( + adc_channel_0, + adc_channel_1, + adc_channel_2, + adc_channel_3, + adc_channel_4, + adc_channel_5 + )); + + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); + PROCESS_PRINTER = Some(process_printer); + + // PROCESS CONSOLE + let process_console = components::process_console::ProcessConsoleComponent::new( + board_kernel, + uart_mux, + mux_alarm, + process_printer, + Some(cortexm4::support::reset), + ) + .finalize(components::process_console_component_static!( + stm32f429zi::tim2::Tim2 + )); + let _ = process_console.start(); + + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); + + let stm32f429i_discovery = STM32F429IDiscovery { + console, + ipc: kernel::ipc::IPC::new( + board_kernel, + kernel::ipc::DRIVER_NUM, + &memory_allocation_capability, + ), + adc: adc_syscall, + led, + temperature: temp, + button, + alarm, + gpio, + + scheduler, + systick: cortexm4::systick::SysTick::new(), + }; + + // // Optional kernel tests + // // + // // See comment in `boards/imix/src/main.rs` + // virtual_uart_rx_test::run_virtual_uart_receive(mux_uart); + + debug!("Initialization complete. Entering main loop"); + + // These symbols are defined in the linker script. + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + } + + kernel::process::load_processes( + board_kernel, + chip, + core::slice::from_raw_parts( + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, + ), + core::slice::from_raw_parts_mut( + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, + ), + &mut *addr_of_mut!(PROCESSES), + &FAULT_RESPONSE, + &process_management_capability, + ) + .unwrap_or_else(|err| { + debug!("Error loading processes!"); + debug!("{:?}", err); + }); + + //Uncomment to run multi alarm test + /*components::test::multi_alarm_test::MultiAlarmTestComponent::new(mux_alarm) + .finalize(components::multi_alarm_test_component_buf!(stm32f429zi::tim2::Tim2)) + .run();*/ + + (board_kernel, stm32f429i_discovery, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); +} diff --git a/boards/stm32f429idiscovery/src/multi_alarm_test.rs b/boards/stm32f429idiscovery/src/multi_alarm_test.rs new file mode 100644 index 0000000000..737d4cd77c --- /dev/null +++ b/boards/stm32f429idiscovery/src/multi_alarm_test.rs @@ -0,0 +1,67 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Test the behavior of a single alarm. +//! To add this test, include the line +//! ``` +//! multi_alarm_test::run_alarm(alarm_mux); +//! ``` +//! to the boot sequence, where `alarm_mux` is a +//! `capsules_core::virtualizers::virtual_alarm::MuxAlarm`. The test sets up 3 +//! different virtualized alarms of random durations and prints +//! out when they fire. The durations are uniformly random with +//! one caveat, that 1 in 11 is of duration 0; this is to test +//! that alarms whose expiration was in the past due to the +//! latency of software work correctly. + +use capsules_core::test::random_alarm::TestRandomAlarm; +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use kernel::debug; +use kernel::hil::time::Alarm; +use kernel::static_init; +use stm32f429zi::tim2::Tim2; + +pub unsafe fn run_multi_alarm(mux: &'static MuxAlarm<'static, Tim2<'static>>) { + debug!("Starting multi alarm test."); + let tests: [&'static TestRandomAlarm<'static, VirtualMuxAlarm<'static, Tim2<'static>>>; 3] = + static_init_multi_alarm_test(mux); + tests[0].run(); + tests[1].run(); + tests[2].run(); +} + +unsafe fn static_init_multi_alarm_test( + mux: &'static MuxAlarm<'static, Tim2<'static>>, +) -> [&'static TestRandomAlarm<'static, VirtualMuxAlarm<'static, Tim2<'static>>>; 3] { + let virtual_alarm1 = static_init!( + VirtualMuxAlarm<'static, Tim2<'static>>, + VirtualMuxAlarm::new(mux) + ); + let test1 = static_init!( + TestRandomAlarm<'static, VirtualMuxAlarm<'static, Tim2<'static>>>, + TestRandomAlarm::new(virtual_alarm1, 19, 'A') + ); + virtual_alarm1.set_alarm_client(test1); + + let virtual_alarm2 = static_init!( + VirtualMuxAlarm<'static, Tim2<'static>>, + VirtualMuxAlarm::new(mux) + ); + let test2 = static_init!( + TestRandomAlarm<'static, VirtualMuxAlarm<'static, Tim2<'static>>>, + TestRandomAlarm::new(virtual_alarm2, 37, 'B') + ); + virtual_alarm2.set_alarm_client(test2); + + let virtual_alarm3 = static_init!( + VirtualMuxAlarm<'static, Tim2<'static>>, + VirtualMuxAlarm::new(mux) + ); + let test3 = static_init!( + TestRandomAlarm<'static, VirtualMuxAlarm<'static, Tim2<'static>>>, + TestRandomAlarm::new(virtual_alarm3, 89, 'C') + ); + virtual_alarm3.set_alarm_client(test3); + [&*test1, &*test2, &*test3] +} diff --git a/boards/swervolf/Cargo.toml b/boards/swervolf/Cargo.toml index 660c7c8469..463718fe54 100644 --- a/boards/swervolf/Cargo.toml +++ b/boards/swervolf/Cargo.toml @@ -1,14 +1,24 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "swervolf" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] components = { path = "../components" } rv32i = { path = "../../arch/rv32i" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } swervolf-eh1 = { path = "../../chips/swervolf-eh1" } swerv = { path = "../../chips/swerv" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/swervolf/Makefile b/boards/swervolf/Makefile index d8465dc1c5..e21213b87c 100644 --- a/boards/swervolf/Makefile +++ b/boards/swervolf/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the SweRVolf platform TARGET=riscv32imc-unknown-none-elf diff --git a/boards/swervolf/README.md b/boards/swervolf/README.md index b7d13aceea..e3d6622714 100644 --- a/boards/swervolf/README.md +++ b/boards/swervolf/README.md @@ -15,9 +15,9 @@ Running ------- FuseSoC can be used to run SweRVolf on either an FPGA board or in a simulation -enviroment. +environment. -Tock has been tested on the simulation enviroment. +Tock has been tested on the simulation environment. Running in simulation with EH1 core ----------------------------------- @@ -54,8 +54,8 @@ make sim NOTE: The Verilator simulation can be slow. Below are some rough estimates of time when running on a standard x64 laptop. -Boot, hardware initalise, first print: 20 seconds -Boot, hardware initalise, panic: 30 seconds +Boot, hardware initialise, first print: 20 seconds +Boot, hardware initialise, panic: 30 seconds The below diff below can be used to increase the simulation speed, with no functionality impact. diff --git a/boards/swervolf/build.rs b/boards/swervolf/build.rs deleted file mode 100644 index 3051054fc6..0000000000 --- a/boards/swervolf/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/swervolf/layout.ld b/boards/swervolf/layout.ld index 3e8732b9c1..6591304808 100644 --- a/boards/swervolf/layout.ld +++ b/boards/swervolf/layout.ld @@ -1,3 +1,7 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + MEMORY { rom (rx) : ORIGIN = 0x00000000, LENGTH = 0x30000 @@ -5,6 +9,4 @@ MEMORY ram (rwx) : ORIGIN = 0x60000, LENGTH = 0x30000 } -MPU_MIN_ALIGN = 1K; - INCLUDE ../kernel_layout.ld diff --git a/boards/swervolf/makehex.py b/boards/swervolf/makehex.py index 43d8f01a37..8e87191676 100644 --- a/boards/swervolf/makehex.py +++ b/boards/swervolf/makehex.py @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + import sys with open(sys.argv[1], "rb") as f: diff --git a/boards/swervolf/src/io.rs b/boards/swervolf/src/io.rs index 69a6726588..75a08ec69f 100644 --- a/boards/swervolf/src/io.rs +++ b/boards/swervolf/src/io.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; use core::ptr::write_volatile; @@ -21,13 +25,14 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { for b in buf { // Print to a special address for simulation output unsafe { - write_volatile(0x8000_1008 as *mut u8, *b as u8); + write_volatile(0x8000_1008 as *mut u8, *b); } } + buf.len() } } @@ -35,16 +40,18 @@ impl IoWrite for Writer { #[cfg(not(test))] #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(pi: &PanicInfo) -> ! { - let writer = &mut WRITER; +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { + use core::ptr::{addr_of, addr_of_mut}; + + let writer = &mut *addr_of_mut!(WRITER); debug::panic_print( writer, pi, &rv32i::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ); // By writing to address 0x80001009 we can exit the simulation. diff --git a/boards/swervolf/src/main.rs b/boards/swervolf/src/main.rs index f0e8e2023c..7065b550ef 100644 --- a/boards/swervolf/src/main.rs +++ b/boards/swervolf/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Board file for SweRVolf RISC-V development platform. //! //! - @@ -8,10 +12,11 @@ // https://github.com/rust-lang/rust/issues/62184. #![cfg_attr(not(doc), no_main)] -use capsules::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::virtualizers::virtual_alarm::{MuxAlarm, VirtualMuxAlarm}; use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::scheduler::cooperative::CooperativeSched; @@ -32,10 +37,12 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] // Reference to the chip for panic dumps. static mut CHIP: Option<&'static swervolf_eh1::chip::SweRVolf> = None; // Static reference to process printer for panic dumps. -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; /// Dummy buffer that causes the linker to reserve enough space for the stack. #[no_mangle] @@ -45,8 +52,8 @@ pub static mut STACK_MEMORY: [u8; 0x900] = [0; 0x900]; /// A structure representing this platform that holds references to all /// capsules for this platform. We've included an alarm and console. struct SweRVolf { - console: &'static capsules::console::Console<'static>, - alarm: &'static capsules::alarm::AlarmDriver< + console: &'static capsules_core::console::Console<'static>, + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, VirtualMuxAlarm<'static, swervolf_eh1::syscon::SysCon<'static>>, >, @@ -61,8 +68,8 @@ impl SyscallDriverLookup for SweRVolf { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), _ => f(None), } } @@ -80,7 +87,7 @@ impl KernelResources &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -92,7 +99,7 @@ impl KernelResources &Self::SchedulerTimer { - &self.scheduler_timer + self.scheduler_timer } fn watchdog(&self) -> &Self::WatchDog { &() @@ -102,11 +109,17 @@ impl KernelResources ( + &'static kernel::Kernel, + SweRVolf, + &'static swervolf_eh1::chip::SweRVolf<'static, SweRVolfDefaultPeripherals<'static>>, +) { // only machine mode - rv32i::configure_trap_handler(rv32i::PermissionMode::Machine); + rv32i::configure_trap_handler(); let peripherals = static_init!( SweRVolfDefaultPeripherals, @@ -117,28 +130,14 @@ pub unsafe fn main() { let process_mgmt_cap = create_capability!(capabilities::ProcessManagementCapability); let memory_allocation_cap = create_capability!(capabilities::MemoryAllocationCapability); - let main_loop_cap = create_capability!(capabilities::MainLoopCapability); - - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); - - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 1], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); // Configure kernel debug gpios as early as possible kernel::debug::assign_gpios(None, None, None); // Create a shared UART channel for the console and for kernel debug. - let uart_mux = components::console::UartMuxComponent::new( - &peripherals.uart, - 115200, - dynamic_deferred_caller, - ) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(&peripherals.uart, 115200) + .finalize(components::uart_mux_component_static!()); let mtimer = static_init!( swervolf_eh1::syscon::SysCon, @@ -161,13 +160,13 @@ pub unsafe fn main() { virtual_alarm_user.setup(); let alarm = static_init!( - capsules::alarm::AlarmDriver< + capsules_core::alarm::AlarmDriver< 'static, VirtualMuxAlarm<'static, swervolf_eh1::syscon::SysCon>, >, - capsules::alarm::AlarmDriver::new( + capsules_core::alarm::AlarmDriver::new( virtual_alarm_user, - board_kernel.create_grant(capsules::alarm::DRIVER_NUM, &memory_allocation_cap) + board_kernel.create_grant(capsules_core::alarm::DRIVER_NUM, &memory_allocation_cap) ) ); hil::time::Alarm::set_alarm_client(virtual_alarm_user, alarm); @@ -181,8 +180,8 @@ pub unsafe fn main() { CHIP = Some(chip); // Create a process printer for panic. - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); // Need to enable all interrupts for Tock Kernel @@ -201,17 +200,18 @@ pub unsafe fn main() { // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); debug!("SweRVolf initialisation complete."); debug!("Entering main loop."); - /// These symbols are defined in the linker script. + // These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -223,8 +223,9 @@ pub unsafe fn main() { static _eappmem: u8; } - let scheduler = components::sched::cooperative::CooperativeComponent::new(&PROCESSES) - .finalize(components::coop_component_helper!(NUM_PROCS)); + let scheduler = + components::sched::cooperative::CooperativeComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::cooperative_component_static!(NUM_PROCS)); let swervolf = SweRVolf { console, @@ -237,14 +238,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_mgmt_cap, ) @@ -253,10 +254,19 @@ pub unsafe fn main() { debug!("{:?}", err); }); + (board_kernel, swervolf, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); board_kernel.kernel_loop( - &swervolf, + &platform, chip, - None::<&kernel::ipc::IPC>, - &main_loop_cap, + None::<&kernel::ipc::IPC<0>>, + &main_loop_capability, ); } diff --git a/boards/teensy40/Cargo.toml b/boards/teensy40/Cargo.toml index 4222c7eb24..b58b74e07e 100644 --- a/boards/teensy40/Cargo.toml +++ b/boards/teensy40/Cargo.toml @@ -1,13 +1,23 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "teensy40" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] components = { path = "../components" } cortexm7 = { path = "../../arch/cortex-m7" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } imxrt10xx = { path = "../../chips/imxrt10xx" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/teensy40/Makefile b/boards/teensy40/Makefile index ddf88a7019..30098ec9f8 100644 --- a/boards/teensy40/Makefile +++ b/boards/teensy40/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the Teensy 4 TARGET=thumbv7em-none-eabi diff --git a/boards/teensy40/README.md b/boards/teensy40/README.md index 9c02fe6c6d..98564e7f0b 100644 --- a/boards/teensy40/README.md +++ b/boards/teensy40/README.md @@ -73,7 +73,7 @@ KERNEL_WITH_APP_HEX=$(TOCK_ROOT_DIRECTORY)/target/teensy40/release/teensy40-app. .PHONY: program program: target/thumbv7em-none-eabi/release/teensy40.elf arm-none-eabi-objcopy --update-section .apps=$(APP) $(KERNEL) $(KERNEL_WITH_APP) - arm-none-eabi-objcopy -O ihex $(KENERL_WITH_APP) $(KERNEL_WITH_APP_HEX) + arm-none-eabi-objcopy -O ihex $(KERNEL_WITH_APP) $(KERNEL_WITH_APP_HEX) teensy_loader_cli -w -v --mcu=TEENSY40 $(KERNEL_WITH_APP_HEX) ``` diff --git a/boards/teensy40/build.rs b/boards/teensy40/build.rs deleted file mode 100644 index 3051054fc6..0000000000 --- a/boards/teensy40/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/teensy40/layout.ld b/boards/teensy40/layout.ld index 681dda017d..954fb11fd3 100644 --- a/boards/teensy40/layout.ld +++ b/boards/teensy40/layout.ld @@ -1,3 +1,7 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + /* * Teensy 4 Linker Script * @@ -19,8 +23,6 @@ MEMORY ram (rwx) : ORIGIN = 0x20200000, LENGTH = 512K } -MPU_MIN_ALIGN = 8K; - SECTIONS { .boot : @@ -59,7 +61,7 @@ SECTIONS _boot_data = .; LONG(ORIGIN(rom)); /* Start of image */ /* Size of the program image (assuming that we also need the boot section) */ - LONG((_etext-_stext) + (_erelocate-_srelocate) + SIZEOF(.boot)); + LONG((_etext - _stext) + (_erelocate - _srelocate) + SIZEOF(.boot)); LONG(0x00000000); /* Plugin flag (unused) */ /* End of iMXRT10xx magic * diff --git a/boards/teensy40/src/fcb.rs b/boards/teensy40/src/fcb.rs index 401f06f1ae..5ddc37bcf0 100644 --- a/boards/teensy40/src/fcb.rs +++ b/boards/teensy40/src/fcb.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! FlexSPI Configuration Block (FCB) //! //! The FCB holds command sequences and configurations necessary to boot diff --git a/boards/teensy40/src/io.rs b/boards/teensy40/src/io.rs index 611cb1f03c..85792b22fc 100644 --- a/boards/teensy40/src/io.rs +++ b/boards/teensy40/src/io.rs @@ -1,4 +1,9 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::{self, Write}; +use core::ptr::addr_of; use kernel::debug::{self, IoWrite}; use kernel::hil::{ @@ -30,10 +35,11 @@ impl<'a> Writer<'a> { } impl IoWrite for Writer<'_> { - fn write(&mut self, bytes: &[u8]) { + fn write(&mut self, bytes: &[u8]) -> usize { for byte in bytes { self.output.send_byte(*byte); } + bytes.len() } } @@ -57,8 +63,8 @@ unsafe fn panic_handler(panic_info: &core::panic::PanicInfo) -> ! { &mut writer, panic_info, &cortexm7::support::nop, - &crate::PROCESSES, - &crate::CHIP, - &crate::PROCESS_PRINTER, + &*addr_of!(crate::PROCESSES), + &*addr_of!(crate::CHIP), + &*addr_of!(crate::PROCESS_PRINTER), ) } diff --git a/boards/teensy40/src/main.rs b/boards/teensy40/src/main.rs index e52918ae89..5dd14b0d74 100644 --- a/boards/teensy40/src/main.rs +++ b/boards/teensy40/src/main.rs @@ -1,3 +1,9 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Teensy 4.0 Development Board +//! //! System configuration //! //! - LED on pin 13 @@ -10,12 +16,13 @@ mod fcb; mod io; +use core::ptr::{addr_of, addr_of_mut}; + use imxrt1060::gpio::PinId; use imxrt1060::iomuxc::{MuxMode, PadId, Sion}; use imxrt10xx as imxrt1060; use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil::{gpio::Configure, led::LedHigh}; use kernel::platform::chip::ClockInterface; use kernel::platform::{KernelResources, SyscallDriverLookup}; @@ -30,20 +37,24 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] [None; NUM_PROCS]; /// What should we do if a process faults? -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; /// Teensy 4 platform struct Teensy40 { - led: &'static capsules::led::LedDriver< + led: &'static capsules_core::led::LedDriver< 'static, LedHigh<'static, imxrt1060::gpio::Pin<'static>>, 1, >, - console: &'static capsules::console::Console<'static>, - ipc: kernel::ipc::IPC, - alarm: &'static capsules::alarm::AlarmDriver< + console: &'static capsules_core::console::Console<'static>, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, - capsules::virtual_alarm::VirtualMuxAlarm<'static, imxrt1060::gpt::Gpt1<'static>>, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + imxrt1060::gpt::Gpt1<'static>, + >, >, scheduler: &'static RoundRobinSched<'static>, @@ -56,10 +67,10 @@ impl SyscallDriverLookup for Teensy40 { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), _ => f(None), } } @@ -77,7 +88,7 @@ impl KernelResources &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -125,23 +136,10 @@ mod dma_config { } } -/// This is in a separate, inline(never) function so that its stack frame is -/// removed when this function returns. Otherwise, the stack space used for -/// these static_inits is wasted. -#[inline(never)] -unsafe fn get_peripherals() -> &'static mut imxrt1060::chip::Imxrt10xxDefaultPeripherals { - let ccm = static_init!(imxrt1060::ccm::Ccm, imxrt1060::ccm::Ccm::new()); - let peripherals = static_init!( - imxrt1060::chip::Imxrt10xxDefaultPeripherals, - imxrt1060::chip::Imxrt10xxDefaultPeripherals::new(ccm) - ); - - peripherals -} - type Chip = imxrt1060::chip::Imxrt10xx; static mut CHIP: Option<&'static Chip> = None; -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; /// Set the ARM clock frequency to 600MHz /// @@ -177,16 +175,24 @@ fn set_arm_clock(ccm: &imxrt1060::ccm::Ccm, ccm_analog: &imxrt1060::ccm_analog:: ccm.set_peripheral_clock_selection(PeripheralClockSelection::PrePeripheralClock); } -#[no_mangle] -pub unsafe fn main() { +/// This is in a separate, inline(never) function so that its stack frame is +/// removed when this function returns. Otherwise, the stack space used for +/// these static_inits is wasted. +#[inline(never)] +unsafe fn start() -> (&'static kernel::Kernel, Teensy40, &'static Chip) { imxrt1060::init(); - let peripherals = get_peripherals(); + let ccm = static_init!(imxrt1060::ccm::Ccm, imxrt1060::ccm::Ccm::new()); + let peripherals = static_init!( + imxrt1060::chip::Imxrt10xxDefaultPeripherals, + imxrt1060::chip::Imxrt10xxDefaultPeripherals::new(ccm) + ); + peripherals.ccm.set_low_power_mode(); peripherals.dcdc.clock().enable(); peripherals.dcdc.set_target_vdd_soc(1250); - set_arm_clock(&peripherals.ccm, &peripherals.ccm_analog); + set_arm_clock(peripherals.ccm, &peripherals.ccm_analog); // IPG clock is 600MHz / 4 == 150MHz peripherals.ccm.set_ipg_divider(4); @@ -248,55 +254,44 @@ pub unsafe fn main() { CHIP = Some(chip); // Start loading the kernel - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); // TODO how many of these should there be...? - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 2], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); - let uart_mux = components::console::UartMuxComponent::new( - &peripherals.lpuart2, - 115_200, - dynamic_deferred_caller, - ) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(&peripherals.lpuart2, 115_200) + .finalize(components::uart_mux_component_static!()); // Create the debugger object that handles calls to `debug!()` - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); // Setup the console let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // LED - let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( LedHigh, LedHigh::new(peripherals.ports.pin(PinId::B0_03)) )); // Alarm let mux_alarm = components::alarm::AlarmMuxComponent::new(&peripherals.gpt1).finalize( - components::alarm_mux_component_helper!(imxrt1060::gpt::Gpt1), + components::alarm_mux_component_static!(imxrt1060::gpt::Gpt1), ); let alarm = components::alarm::AlarmDriverComponent::new( board_kernel, - capsules::alarm::DRIVER_NUM, + capsules_core::alarm::DRIVER_NUM, mux_alarm, ) - .finalize(components::alarm_component_helper!(imxrt1060::gpt::Gpt1)); + .finalize(components::alarm_component_static!(imxrt1060::gpt::Gpt1)); // // Capabilities // let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); @@ -306,12 +301,12 @@ pub unsafe fn main() { &memory_allocation_capability, ); - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); // // Platform @@ -348,20 +343,29 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_management_capability, ) .unwrap(); - board_kernel.kernel_loop(&teensy40, chip, Some(&teensy40.ipc), &main_loop_capability); + (board_kernel, teensy40, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); } /// Space for the stack buffer diff --git a/boards/tutorials/nrf52840dk-hotp-tutorial/Cargo.toml b/boards/tutorials/nrf52840dk-hotp-tutorial/Cargo.toml new file mode 100644 index 0000000000..57b4e2e6c2 --- /dev/null +++ b/boards/tutorials/nrf52840dk-hotp-tutorial/Cargo.toml @@ -0,0 +1,27 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +[package] +name = "nrf52840dk-hotp-tutorial" +version.workspace = true +authors.workspace = true +build = "../../build.rs" +edition.workspace = true + +[features] +default = ["screen_ssd1306"] +screen_ssd1306 = [] +screen_sh1106 = [] + +[dependencies] +kernel = { path = "../../../kernel" } +nrf52840 = { path = "../../../chips/nrf52840" } +nrf52840dk = { path = "../../nordic/nrf52840dk" } +capsules-core = { path = "../../../capsules/core" } +capsules-extra = { path = "../../../capsules/extra" } +capsules-system = { path = "../../../capsules/system" } +components = { path = "../../components" } + +[lints] +workspace = true diff --git a/boards/tutorials/nrf52840dk-hotp-tutorial/Makefile b/boards/tutorials/nrf52840dk-hotp-tutorial/Makefile new file mode 100644 index 0000000000..211bf81cbf --- /dev/null +++ b/boards/tutorials/nrf52840dk-hotp-tutorial/Makefile @@ -0,0 +1,9 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +TARGET=thumbv7em-none-eabi +PLATFORM=nrf52840dk-hotp-tutorial + +include ../../Makefile.common +include ../../configurations/nrf52840dk/nrf52840dk.mk diff --git a/boards/tutorials/nrf52840dk-hotp-tutorial/README.md b/boards/tutorials/nrf52840dk-hotp-tutorial/README.md new file mode 100644 index 0000000000..9382ce5ab7 --- /dev/null +++ b/boards/tutorials/nrf52840dk-hotp-tutorial/README.md @@ -0,0 +1,9 @@ +nRF52840DK Board Definition for the Tock HOTP Tutorial +======================================================== + +This is the board definition for the nRF52840DK target used in the +[HOTP tutorial](https://book.tockos.org/course/usb-security-key/key-overview). + +Please follow the instructions in that tutorial. You may also want to look at +the documentation of the base nRF52840DK board definition +[here](../../nordic/nrf52840dk/README.md). diff --git a/boards/tutorials/nrf52840dk-hotp-tutorial/layout.ld b/boards/tutorials/nrf52840dk-hotp-tutorial/layout.ld new file mode 100644 index 0000000000..52d52ab3ac --- /dev/null +++ b/boards/tutorials/nrf52840dk-hotp-tutorial/layout.ld @@ -0,0 +1,6 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2024. */ + +INCLUDE ../../nordic/nrf52840_chip_layout.ld +INCLUDE ../../kernel_layout.ld diff --git a/boards/tutorials/nrf52840dk-hotp-tutorial/src/main.rs b/boards/tutorials/nrf52840dk-hotp-tutorial/src/main.rs new file mode 100644 index 0000000000..e00130dc46 --- /dev/null +++ b/boards/tutorials/nrf52840dk-hotp-tutorial/src/main.rs @@ -0,0 +1,249 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Tock kernel for the Nordic Semiconductor nRF52840 development kit (DK). + +#![no_std] +// Disable this attribute when documenting, as a workaround for +// https://github.com/rust-lang/rust/issues/62184. +#![cfg_attr(not(doc), no_main)] +#![deny(missing_docs)] + +use core::ptr::addr_of_mut; +use kernel::component::Component; +use kernel::debug; +use kernel::hil::usb::Client; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::static_init; +use kernel::{capabilities, create_capability}; +use nrf52840::gpio::Pin; +use nrf52840dk_lib::{self, PROCESSES}; + +// State for loading and holding applications. +// How should the kernel respond when a process faults. +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; + +// Screen +type ScreenDriver = components::screen::ScreenComponentType; + +// USB Keyboard HID - for nRF52840dk +type UsbHw = nrf52840::usbd::Usbd<'static>; // For any nRF52840 board. +type KeyboardHidDriver = components::keyboard_hid::KeyboardHidComponentType; + +// HMAC +type HmacSha256Software = components::hmac::HmacSha256SoftwareComponentType< + capsules_extra::sha256::Sha256Software<'static>, +>; +type HmacDriver = components::hmac::HmacComponentType; + +struct Platform { + keyboard_hid_driver: &'static KeyboardHidDriver, + hmac: &'static HmacDriver, + screen: &'static ScreenDriver, + base: nrf52840dk_lib::Platform, +} + +const KEYBOARD_HID_DRIVER_NUM: usize = capsules_core::driver::NUM::KeyboardHid as usize; + +impl SyscallDriverLookup for Platform { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_extra::hmac::DRIVER_NUM => f(Some(self.hmac)), + KEYBOARD_HID_DRIVER_NUM => f(Some(self.keyboard_hid_driver)), + capsules_extra::screen::DRIVER_NUM => f(Some(self.screen)), + _ => self.base.with_driver(driver_num, f), + } + } +} + +type Chip = nrf52840dk_lib::Chip; + +impl KernelResources for Platform { + type SyscallDriverLookup = Self; + type SyscallFilter = >::SyscallFilter; + type ProcessFault = >::ProcessFault; + type Scheduler = >::Scheduler; + type SchedulerTimer = >::SchedulerTimer; + type WatchDog = >::WatchDog; + type ContextSwitchCallback = + >::ContextSwitchCallback; + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + self.base.syscall_filter() + } + fn process_fault(&self) -> &Self::ProcessFault { + self.base.process_fault() + } + fn scheduler(&self) -> &Self::Scheduler { + self.base.scheduler() + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + self.base.scheduler_timer() + } + fn watchdog(&self) -> &Self::WatchDog { + self.base.watchdog() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + self.base.context_switch_callback() + } +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + // Create the base board: + let (board_kernel, base_platform, chip, nrf52840_peripherals, _mux_alarm) = + nrf52840dk_lib::start(); + + //-------------------------------------------------------------------------- + // HMAC-SHA256 + //-------------------------------------------------------------------------- + + let sha256_sw = components::sha::ShaSoftware256Component::new() + .finalize(components::sha_software_256_component_static!()); + + let hmac_sha256_sw = components::hmac::HmacSha256SoftwareComponent::new(sha256_sw).finalize( + components::hmac_sha256_software_component_static!(capsules_extra::sha256::Sha256Software), + ); + + let hmac = components::hmac::HmacComponent::new( + board_kernel, + capsules_extra::hmac::DRIVER_NUM, + hmac_sha256_sw, + ) + .finalize(components::hmac_component_static!(HmacSha256Software, 32)); + + //-------------------------------------------------------------------------- + // SCREEN + //-------------------------------------------------------------------------- + + const SCREEN_I2C_SDA_PIN: Pin = Pin::P1_10; + const SCREEN_I2C_SCL_PIN: Pin = Pin::P1_11; + + let i2c_bus = components::i2c::I2CMuxComponent::new(&nrf52840_peripherals.nrf52.twi1, None) + .finalize(components::i2c_mux_component_static!(nrf52840::i2c::TWI)); + nrf52840_peripherals.nrf52.twi1.configure( + nrf52840::pinmux::Pinmux::new(SCREEN_I2C_SCL_PIN as u32), + nrf52840::pinmux::Pinmux::new(SCREEN_I2C_SDA_PIN as u32), + ); + nrf52840_peripherals + .nrf52 + .twi1 + .set_speed(nrf52840::i2c::Speed::K400); + + // I2C address is b011110X, and on this board D/C̅ is GND. + let ssd1306_sh1106_i2c = components::i2c::I2CComponent::new(i2c_bus, 0x3c) + .finalize(components::i2c_component_static!(nrf52840::i2c::TWI)); + + // Create the ssd1306 object for the actual screen driver. + #[cfg(feature = "screen_ssd1306")] + let ssd1306_sh1106 = components::ssd1306::Ssd1306Component::new(ssd1306_sh1106_i2c, true) + .finalize(components::ssd1306_component_static!(nrf52840::i2c::TWI)); + + #[cfg(feature = "screen_sh1106")] + let ssd1306_sh1106 = components::sh1106::Sh1106Component::new(ssd1306_sh1106_i2c, true) + .finalize(components::sh1106_component_static!(nrf52840::i2c::TWI)); + + let screen = components::screen::ScreenComponent::new( + board_kernel, + capsules_extra::screen::DRIVER_NUM, + ssd1306_sh1106, + None, + ) + .finalize(components::screen_component_static!(1032)); + + ssd1306_sh1106.init_screen(); + + //-------------------------------------------------------------------------- + // KEYBOARD + //-------------------------------------------------------------------------- + + // Create the strings we include in the USB descriptor. + let strings = static_init!( + [&str; 3], + [ + "Nordic Semiconductor", // Manufacturer + "nRF52840dk - TockOS", // Product + "serial0001", // Serial number + ] + ); + + let usb_device = &nrf52840_peripherals.usbd; + + // Generic HID Keyboard component usage + let (keyboard_hid, keyboard_hid_driver) = components::keyboard_hid::KeyboardHidComponent::new( + board_kernel, + capsules_core::driver::NUM::KeyboardHid as usize, + usb_device, + 0x1915, // Nordic Semiconductor + 0x503a, + strings, + ) + .finalize(components::keyboard_hid_component_static!(UsbHw)); + + keyboard_hid.enable(); + keyboard_hid.attach(); + + //-------------------------------------------------------------------------- + // PLATFORM SETUP, SCHEDULER, AND START KERNEL LOOP + //-------------------------------------------------------------------------- + + let platform = Platform { + base: base_platform, + keyboard_hid_driver, + hmac, + screen, + }; + + // These symbols are defined in the linker script. + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + } + + let process_management_capability = + create_capability!(capabilities::ProcessManagementCapability); + + kernel::process::load_processes( + board_kernel, + chip, + core::slice::from_raw_parts( + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, + ), + core::slice::from_raw_parts_mut( + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, + ), + &mut *addr_of_mut!(PROCESSES), + &FAULT_RESPONSE, + &process_management_capability, + ) + .unwrap_or_else(|err| { + debug!("Error loading processes!"); + debug!("{:?}", err); + }); + + board_kernel.kernel_loop( + &platform, + chip, + Some(&platform.base.ipc), + &main_loop_capability, + ); +} diff --git a/boards/tutorials/nrf52840dk-thread-tutorial/Cargo.toml b/boards/tutorials/nrf52840dk-thread-tutorial/Cargo.toml new file mode 100644 index 0000000000..cdadcc88dd --- /dev/null +++ b/boards/tutorials/nrf52840dk-thread-tutorial/Cargo.toml @@ -0,0 +1,27 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[package] +name = "nrf52840dk-thread-tutorial" +version.workspace = true +authors.workspace = true +build = "../../build.rs" +edition.workspace = true + +[features] +default = ["screen_ssd1306"] +screen_ssd1306 = [] +screen_sh1106 = [] + +[dependencies] +kernel = { path = "../../../kernel" } +nrf52840 = { path = "../../../chips/nrf52840" } +nrf52840dk = { path = "../../nordic/nrf52840dk" } +capsules-core = { path = "../../../capsules/core" } +capsules-extra = { path = "../../../capsules/extra" } +capsules-system = { path = "../../../capsules/system" } +components = { path = "../../components" } + +[lints] +workspace = true diff --git a/boards/tutorials/nrf52840dk-thread-tutorial/Makefile b/boards/tutorials/nrf52840dk-thread-tutorial/Makefile new file mode 100644 index 0000000000..6bf14928c1 --- /dev/null +++ b/boards/tutorials/nrf52840dk-thread-tutorial/Makefile @@ -0,0 +1,9 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +TARGET=thumbv7em-none-eabi +PLATFORM=nrf52840dk-thread-tutorial + +include ../../Makefile.common +include ../../configurations/nrf52840dk/nrf52840dk.mk diff --git a/boards/tutorials/nrf52840dk-thread-tutorial/README.md b/boards/tutorials/nrf52840dk-thread-tutorial/README.md new file mode 100644 index 0000000000..b556efe494 --- /dev/null +++ b/boards/tutorials/nrf52840dk-thread-tutorial/README.md @@ -0,0 +1,9 @@ +nRF52840DK Board Definition for the Tock Thread Tutorial +======================================================== + +This is the board definition for the nRF52840DK target used in the +[Thread Network tutorial](https://book.tockos.org/course/thread-net/overview). + +Please follow the instructions in that tutorial. You may also want to look at +the documentation of the base nRF52840DK board definition +[here](../../nordic/nrf52840dk/README.md). diff --git a/boards/tutorials/nrf52840dk-thread-tutorial/jtag/gdbinit_pca10040.jlink b/boards/tutorials/nrf52840dk-thread-tutorial/jtag/gdbinit_pca10040.jlink new file mode 100644 index 0000000000..8094e76819 --- /dev/null +++ b/boards/tutorials/nrf52840dk-thread-tutorial/jtag/gdbinit_pca10040.jlink @@ -0,0 +1,28 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2018. +# +# +# +# J-LINK GDB SERVER initialization +# +# This connects to a GDB Server listening +# for commands on localhost at tcp port 2331 +target remote localhost:2331 +monitor speed 30 +file ../../../../target/thumbv7em-none-eabi/release/nrf52dk +monitor reset +# +# CPU core initialization (to be done by user) +# +# Set the processor mode +# monitor reg cpsr = 0xd3 +# Set auto JTAG speed +monitor speed auto +# Setup GDB FOR FASTER DOWNLOADS +set remote memory-write-packet-size 1024 +set remote memory-write-packet-size fixed +# tui enable +# layout split +# layout service_pending_interrupts +b initialize_ram_jump_to_main diff --git a/boards/tutorials/nrf52840dk-thread-tutorial/jtag/jdbserver_pca10040.sh b/boards/tutorials/nrf52840dk-thread-tutorial/jtag/jdbserver_pca10040.sh new file mode 100755 index 0000000000..5edf8b5cb8 --- /dev/null +++ b/boards/tutorials/nrf52840dk-thread-tutorial/jtag/jdbserver_pca10040.sh @@ -0,0 +1,5 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +JLinkGDBServer -device nrf52 -speed 1200 -if swd -AutoConnect 1 -port 2331 diff --git a/boards/tutorials/nrf52840dk-thread-tutorial/layout.ld b/boards/tutorials/nrf52840dk-thread-tutorial/layout.ld new file mode 100644 index 0000000000..ece13c78e4 --- /dev/null +++ b/boards/tutorials/nrf52840dk-thread-tutorial/layout.ld @@ -0,0 +1,6 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + +INCLUDE ../../nordic/nrf52840_chip_layout.ld +INCLUDE ../../kernel_layout.ld diff --git a/boards/tutorials/nrf52840dk-thread-tutorial/src/main.rs b/boards/tutorials/nrf52840dk-thread-tutorial/src/main.rs new file mode 100644 index 0000000000..c5ef377575 --- /dev/null +++ b/boards/tutorials/nrf52840dk-thread-tutorial/src/main.rs @@ -0,0 +1,235 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Tock kernel for the Nordic Semiconductor nRF52840 development kit (DK). + +#![no_std] +// Disable this attribute when documenting, as a workaround for +// https://github.com/rust-lang/rust/issues/62184. +#![cfg_attr(not(doc), no_main)] +#![deny(missing_docs)] + +use core::ptr::addr_of_mut; + +use kernel::component::Component; +use kernel::debug; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::{capabilities, create_capability}; +use nrf52840::gpio::Pin; +use nrf52840dk_lib::{self, PROCESSES}; + +type ScreenDriver = components::screen::ScreenComponentType; + +// State for loading and holding applications. +// How should the kernel respond when a process faults. +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; + +type Ieee802154RawDriver = + components::ieee802154::Ieee802154RawComponentType>; + +struct Platform { + base: nrf52840dk_lib::Platform, + ieee802154: &'static Ieee802154RawDriver, + eui64: &'static nrf52840dk_lib::Eui64Driver, + screen: &'static ScreenDriver, + nonvolatile_storage: + &'static capsules_extra::nonvolatile_storage_driver::NonvolatileStorage<'static>, +} + +impl SyscallDriverLookup for Platform { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_extra::eui64::DRIVER_NUM => f(Some(self.eui64)), + capsules_extra::ieee802154::DRIVER_NUM => f(Some(self.ieee802154)), + capsules_extra::screen::DRIVER_NUM => f(Some(self.screen)), + capsules_extra::nonvolatile_storage_driver::DRIVER_NUM => { + f(Some(self.nonvolatile_storage)) + } + _ => self.base.with_driver(driver_num, f), + } + } +} + +type Chip = nrf52840dk_lib::Chip; + +impl KernelResources for Platform { + type SyscallDriverLookup = Self; + type SyscallFilter = >::SyscallFilter; + type ProcessFault = >::ProcessFault; + type Scheduler = >::Scheduler; + type SchedulerTimer = >::SchedulerTimer; + type WatchDog = >::WatchDog; + type ContextSwitchCallback = + >::ContextSwitchCallback; + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + self.base.syscall_filter() + } + fn process_fault(&self) -> &Self::ProcessFault { + self.base.process_fault() + } + fn scheduler(&self) -> &Self::Scheduler { + self.base.scheduler() + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + self.base.scheduler_timer() + } + fn watchdog(&self) -> &Self::WatchDog { + self.base.watchdog() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + self.base.context_switch_callback() + } +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + // Create the base board: + let (board_kernel, base_platform, chip, nrf52840_peripherals, _mux_alarm) = + nrf52840dk_lib::start(); + + //-------------------------------------------------------------------------- + // RAW 802.15.4 + //-------------------------------------------------------------------------- + + let device_id = nrf52840::ficr::FICR_INSTANCE.id(); + + let eui64 = components::eui64::Eui64Component::new(u64::from_le_bytes(device_id)) + .finalize(components::eui64_component_static!()); + + let ieee802154 = components::ieee802154::Ieee802154RawComponent::new( + board_kernel, + capsules_extra::ieee802154::DRIVER_NUM, + &nrf52840_peripherals.ieee802154_radio, + ) + .finalize(components::ieee802154_raw_component_static!( + nrf52840::ieee802154_radio::Radio, + )); + + //-------------------------------------------------------------------------- + // SCREEN + //-------------------------------------------------------------------------- + + const SCREEN_I2C_SDA_PIN: Pin = Pin::P1_10; + const SCREEN_I2C_SCL_PIN: Pin = Pin::P1_11; + + let i2c_bus = components::i2c::I2CMuxComponent::new(&nrf52840_peripherals.nrf52.twi1, None) + .finalize(components::i2c_mux_component_static!(nrf52840::i2c::TWI)); + nrf52840_peripherals.nrf52.twi1.configure( + nrf52840::pinmux::Pinmux::new(SCREEN_I2C_SCL_PIN as u32), + nrf52840::pinmux::Pinmux::new(SCREEN_I2C_SDA_PIN as u32), + ); + nrf52840_peripherals + .nrf52 + .twi1 + .set_speed(nrf52840::i2c::Speed::K400); + + // I2C address is b011110X, and on this board D/C̅ is GND. + let ssd1306_sh1106_i2c = components::i2c::I2CComponent::new(i2c_bus, 0x3c) + .finalize(components::i2c_component_static!(nrf52840::i2c::TWI)); + + // Create the ssd1306 object for the actual screen driver. + #[cfg(feature = "screen_ssd1306")] + let ssd1306_sh1106 = components::ssd1306::Ssd1306Component::new(ssd1306_sh1106_i2c, true) + .finalize(components::ssd1306_component_static!(nrf52840::i2c::TWI)); + + #[cfg(feature = "screen_sh1106")] + let ssd1306_sh1106 = components::sh1106::Sh1106Component::new(ssd1306_sh1106_i2c, true) + .finalize(components::sh1106_component_static!(nrf52840::i2c::TWI)); + + let screen = components::screen::ScreenComponent::new( + board_kernel, + capsules_extra::screen::DRIVER_NUM, + ssd1306_sh1106, + None, + ) + .finalize(components::screen_component_static!(1032)); + + ssd1306_sh1106.init_screen(); + + //-------------------------------------------------------------------------- + // NONVOLATILE STORAGE + //-------------------------------------------------------------------------- + + // 32kB of userspace-accessible storage, page aligned: + kernel::storage_volume!(APP_STORAGE, 32); + + let nonvolatile_storage = components::nonvolatile_storage::NonvolatileStorageComponent::new( + board_kernel, + capsules_extra::nonvolatile_storage_driver::DRIVER_NUM, + &nrf52840_peripherals.nrf52.nvmc, + core::ptr::addr_of!(APP_STORAGE) as usize, + APP_STORAGE.len(), + // No kernel-writeable flash: + core::ptr::null::<()>() as usize, + 0, + ) + .finalize(components::nonvolatile_storage_component_static!( + nrf52840::nvmc::Nvmc + )); + + //-------------------------------------------------------------------------- + // PLATFORM SETUP, SCHEDULER, AND START KERNEL LOOP + //-------------------------------------------------------------------------- + + let platform = Platform { + base: base_platform, + eui64, + ieee802154, + screen, + nonvolatile_storage, + }; + + // These symbols are defined in the linker script. + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + } + + let process_management_capability = + create_capability!(capabilities::ProcessManagementCapability); + + kernel::process::load_processes( + board_kernel, + chip, + core::slice::from_raw_parts( + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, + ), + core::slice::from_raw_parts_mut( + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, + ), + &mut *addr_of_mut!(PROCESSES), + &FAULT_RESPONSE, + &process_management_capability, + ) + .unwrap_or_else(|err| { + debug!("Error loading processes!"); + debug!("{:?}", err); + }); + + board_kernel.kernel_loop( + &platform, + chip, + Some(&platform.base.ipc), + &main_loop_capability, + ); +} diff --git a/boards/weact_f401ccu6/Cargo.toml b/boards/weact_f401ccu6/Cargo.toml index 1d7546ffe8..8a725d66fa 100644 --- a/boards/weact_f401ccu6/Cargo.toml +++ b/boards/weact_f401ccu6/Cargo.toml @@ -1,13 +1,23 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "weact-f401ccu6" -version = "0.1.0" -authors = ["Tock Project Developers "] -build = "build.rs" -edition = "2021" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true [dependencies] components = { path = "../components" } cortexm4 = { path = "../../arch/cortex-m4" } -capsules = { path = "../../capsules" } kernel = { path = "../../kernel" } stm32f401cc = { path = "../../chips/stm32f401cc" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/weact_f401ccu6/Makefile b/boards/weact_f401ccu6/Makefile index a3bc51f32d..061e5f9ac2 100644 --- a/boards/weact_f401ccu6/Makefile +++ b/boards/weact_f401ccu6/Makefile @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Makefile for building the tock kernel for the WeAct STM32F401CCU6 Core Board TARGET=thumbv7em-none-eabihf PLATFORM=weact-f401ccu6 diff --git a/boards/weact_f401ccu6/build.rs b/boards/weact_f401ccu6/build.rs deleted file mode 100644 index 3051054fc6..0000000000 --- a/boards/weact_f401ccu6/build.rs +++ /dev/null @@ -1,4 +0,0 @@ -fn main() { - println!("cargo:rerun-if-changed=layout.ld"); - println!("cargo:rerun-if-changed=../kernel_layout.ld"); -} diff --git a/boards/weact_f401ccu6/layout.ld b/boards/weact_f401ccu6/layout.ld index 69bed105e8..e7affc0fff 100644 --- a/boards/weact_f401ccu6/layout.ld +++ b/boards/weact_f401ccu6/layout.ld @@ -1,17 +1,20 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + /* Memory layout for the STM32F401CCU6 - * rom = 256KB (LENGTH = 0x00040000) - * kernel = 128KB - * user = 128KB - * ram = 64KB */ + * rom = 256KiB (LENGTH = 0x00040000) + * kernel = 192KiB + * user = 64KiB + * ram = 64KiB */ MEMORY { - rom (rx) : ORIGIN = 0x08000000, LENGTH = 0x00020000 - prog (rx) : ORIGIN = 0x08020000, LENGTH = 0x00020000 + rom (rx) : ORIGIN = 0x08000000, LENGTH = 0x00030000 + prog (rx) : ORIGIN = 0x08030000, LENGTH = 0x00010000 ram (rwx) : ORIGIN = 0x20000000, LENGTH = 64K } -MPU_MIN_ALIGN = 8K; PAGE_SIZE = 2K; INCLUDE ../kernel_layout.ld diff --git a/boards/weact_f401ccu6/openocd.cfg b/boards/weact_f401ccu6/openocd.cfg index d5a2feebba..11078b3091 100644 --- a/boards/weact_f401ccu6/openocd.cfg +++ b/boards/weact_f401ccu6/openocd.cfg @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + #interface interface hla hla_layout stlink diff --git a/boards/weact_f401ccu6/src/io.rs b/boards/weact_f401ccu6/src/io.rs index c8de72bd06..2167bef221 100644 --- a/boards/weact_f401ccu6/src/io.rs +++ b/boards/weact_f401ccu6/src/io.rs @@ -1,7 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; use core::panic::PanicInfo; - -use cortexm4; +use core::ptr::addr_of; +use core::ptr::addr_of_mut; use kernel::debug; use kernel::debug::IoWrite; @@ -9,7 +13,7 @@ use kernel::hil::led; use kernel::hil::uart; use kernel::hil::uart::Configure; -use stm32f401cc; +use stm32f401cc::chip_specs::Stm32f401Specs; use stm32f401cc::gpio::PinId; use crate::CHIP; @@ -40,9 +44,11 @@ impl Write for Writer { } impl IoWrite for Writer { - fn write(&mut self, buf: &[u8]) { + fn write(&mut self, buf: &[u8]) -> usize { let rcc = stm32f401cc::rcc::Rcc::new(); - let uart = stm32f401cc::usart::Usart::new_usart2(&rcc); + let clocks: stm32f401cc::clocks::Clocks = + stm32f401cc::clocks::Clocks::new(&rcc); + let uart = stm32f401cc::usart::Usart::new_usart2(&clocks); if !self.initialized { self.initialized = true; @@ -59,32 +65,35 @@ impl IoWrite for Writer { for &c in buf { uart.send_byte(c); } + buf.len() } } /// Panic handler. #[no_mangle] #[panic_handler] -pub unsafe extern "C" fn panic_fmt(info: &PanicInfo) -> ! { +pub unsafe fn panic_fmt(info: &PanicInfo) -> ! { // On-board LED C13 is connected to PC13 // Have to reinitialize several peripherals because otherwise can't access them here. let rcc = stm32f401cc::rcc::Rcc::new(); - let syscfg = stm32f401cc::syscfg::Syscfg::new(&rcc); + let clocks: stm32f401cc::clocks::Clocks = + stm32f401cc::clocks::Clocks::new(&rcc); + let syscfg = stm32f401cc::syscfg::Syscfg::new(&clocks); let exti = stm32f401cc::exti::Exti::new(&syscfg); let pin = stm32f401cc::gpio::Pin::new(PinId::PC13, &exti); - let gpio_ports = stm32f401cc::gpio::GpioPorts::new(&rcc, &exti); + let gpio_ports = stm32f401cc::gpio::GpioPorts::new(&clocks, &exti); pin.set_ports_ref(&gpio_ports); let led = &mut led::LedLow::new(&pin); - let writer = &mut WRITER; + let writer = &mut *addr_of_mut!(WRITER); debug::panic( &mut [led], writer, info, &cortexm4::support::nop, - &PROCESSES, - &CHIP, - &PROCESS_PRINTER, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), ) } diff --git a/boards/weact_f401ccu6/src/main.rs b/boards/weact_f401ccu6/src/main.rs index 5d38da6e46..a0ce19683c 100644 --- a/boards/weact_f401ccu6/src/main.rs +++ b/boards/weact_f401ccu6/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Board file for WeAct STM32F401CCU6 Core Board //! //! - @@ -8,16 +12,18 @@ #![cfg_attr(not(doc), no_main)] #![deny(missing_docs)] -use capsules::virtual_alarm::VirtualMuxAlarm; +use core::ptr::{addr_of, addr_of_mut}; + +use capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm; use components::gpio::GpioComponent; use kernel::capabilities; use kernel::component::Component; -use kernel::dynamic_deferred_call::{DynamicDeferredCall, DynamicDeferredCallClientState}; use kernel::hil::led::LedLow; use kernel::platform::{KernelResources, SyscallDriverLookup}; use kernel::scheduler::round_robin::RoundRobinSched; use kernel::{create_capability, debug, static_init}; +use stm32f401cc::chip_specs::Stm32f401Specs; use stm32f401cc::interrupt_service::Stm32f401ccDefaultPeripherals; /// Support routines for debugging I/O. @@ -32,10 +38,12 @@ static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] static mut CHIP: Option<&'static stm32f401cc::chip::Stm32f4xx> = None; -static mut PROCESS_PRINTER: Option<&'static kernel::process::ProcessPrinterText> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; // How should the kernel respond when a process faults. -const FAULT_RESPONSE: kernel::process::PanicFaultPolicy = kernel::process::PanicFaultPolicy {}; +const FAULT_RESPONSE: capsules_system::process_policies::PanicFaultPolicy = + capsules_system::process_policies::PanicFaultPolicy {}; /// Dummy buffer that causes the linker to reserve enough space for the stack. #[no_mangle] @@ -45,20 +53,20 @@ pub static mut STACK_MEMORY: [u8; 0x2000] = [0; 0x2000]; /// A structure representing this platform that holds references to all /// capsules for this platform. struct WeactF401CC { - console: &'static capsules::console::Console<'static>, - ipc: kernel::ipc::IPC, - led: &'static capsules::led::LedDriver< + console: &'static capsules_core::console::Console<'static>, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + led: &'static capsules_core::led::LedDriver< 'static, LedLow<'static, stm32f401cc::gpio::Pin<'static>>, 1, >, - button: &'static capsules::button::Button<'static, stm32f401cc::gpio::Pin<'static>>, - adc: &'static capsules::adc::AdcVirtualized<'static>, - alarm: &'static capsules::alarm::AlarmDriver< + button: &'static capsules_core::button::Button<'static, stm32f401cc::gpio::Pin<'static>>, + adc: &'static capsules_core::adc::AdcVirtualized<'static>, + alarm: &'static capsules_core::alarm::AlarmDriver< 'static, VirtualMuxAlarm<'static, stm32f401cc::tim2::Tim2<'static>>, >, - gpio: &'static capsules::gpio::GPIO<'static, stm32f401cc::gpio::Pin<'static>>, + gpio: &'static capsules_core::gpio::GPIO<'static, stm32f401cc::gpio::Pin<'static>>, scheduler: &'static RoundRobinSched<'static>, systick: cortexm4::systick::SysTick, } @@ -70,13 +78,13 @@ impl SyscallDriverLookup for WeactF401CC { F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, { match driver_num { - capsules::console::DRIVER_NUM => f(Some(self.console)), - capsules::led::DRIVER_NUM => f(Some(self.led)), - capsules::button::DRIVER_NUM => f(Some(self.button)), - capsules::adc::DRIVER_NUM => f(Some(self.adc)), - capsules::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::button::DRIVER_NUM => f(Some(self.button)), + capsules_core::adc::DRIVER_NUM => f(Some(self.adc)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), - capsules::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), _ => f(None), } } @@ -94,7 +102,7 @@ impl KernelResources &Self::SyscallDriverLookup { - &self + self } fn syscall_filter(&self) -> &Self::SyscallFilter { &() @@ -118,11 +126,11 @@ impl KernelResources; 8], + usart2: &'static stm32f401cc::usart::Usart, ) { - use stm32f401cc::dma1::Dma1Peripheral; + use stm32f401cc::dma::Dma1Peripheral; use stm32f401cc::usart; dma.enable_clock(); @@ -204,43 +212,41 @@ unsafe fn setup_peripherals(tim2: &stm32f401cc::tim2::Tim2) { cortexm4::nvic::Nvic::new(stm32f401cc::nvic::TIM2).enable(); } -/// Statically initialize the core peripherals for the chip. +/// Main function /// /// This is in a separate, inline(never) function so that its stack frame is /// removed when this function returns. Otherwise, the stack space used for /// these static_inits is wasted. #[inline(never)] -unsafe fn get_peripherals() -> ( - &'static mut Stm32f401ccDefaultPeripherals<'static>, - &'static stm32f401cc::syscfg::Syscfg<'static>, - &'static stm32f401cc::dma1::Dma1<'static>, +unsafe fn start() -> ( + &'static kernel::Kernel, + WeactF401CC, + &'static stm32f401cc::chip::Stm32f4xx<'static, Stm32f401ccDefaultPeripherals<'static>>, ) { + stm32f401cc::init(); + // We use the default HSI 16Mhz clock let rcc = static_init!(stm32f401cc::rcc::Rcc, stm32f401cc::rcc::Rcc::new()); + let clocks = static_init!( + stm32f401cc::clocks::Clocks, + stm32f401cc::clocks::Clocks::new(rcc) + ); let syscfg = static_init!( stm32f401cc::syscfg::Syscfg, - stm32f401cc::syscfg::Syscfg::new(rcc) + stm32f401cc::syscfg::Syscfg::new(clocks) ); let exti = static_init!( stm32f401cc::exti::Exti, stm32f401cc::exti::Exti::new(syscfg) ); - let dma1 = static_init!(stm32f401cc::dma1::Dma1, stm32f401cc::dma1::Dma1::new(rcc)); + let dma1 = static_init!(stm32f401cc::dma::Dma1, stm32f401cc::dma::Dma1::new(clocks)); + let dma2 = static_init!(stm32f401cc::dma::Dma2, stm32f401cc::dma::Dma2::new(clocks)); + let peripherals = static_init!( Stm32f401ccDefaultPeripherals, - Stm32f401ccDefaultPeripherals::new(rcc, exti, dma1) + Stm32f401ccDefaultPeripherals::new(clocks, exti, dma1, dma2) ); - (peripherals, syscfg, dma1) -} - -/// Main function. -/// -/// This is called after RAM initialization is complete. -#[no_mangle] -pub unsafe fn main() { - stm32f401cc::init(); - let (peripherals, syscfg, dma1) = get_peripherals(); peripherals.init(); let base_peripherals = &peripherals.stm32f4; @@ -250,19 +256,11 @@ pub unsafe fn main() { setup_dma( dma1, - &base_peripherals.dma_streams, + &base_peripherals.dma1_streams, &base_peripherals.usart2, ); - let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&PROCESSES)); - - let dynamic_deferred_call_clients = - static_init!([DynamicDeferredCallClientState; 2], Default::default()); - let dynamic_deferred_caller = static_init!( - DynamicDeferredCall, - DynamicDeferredCall::new(dynamic_deferred_call_clients) - ); - DynamicDeferredCall::set_global_instance(dynamic_deferred_caller); + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); let chip = static_init!( stm32f401cc::chip::Stm32f4xx, @@ -274,37 +272,33 @@ pub unsafe fn main() { // Create a shared UART channel for kernel debug. base_peripherals.usart2.enable_clock(); - let uart_mux = components::console::UartMuxComponent::new( - &base_peripherals.usart2, - 115200, - dynamic_deferred_caller, - ) - .finalize(()); + let uart_mux = components::console::UartMuxComponent::new(&base_peripherals.usart2, 115200) + .finalize(components::uart_mux_component_static!()); io::WRITER.set_initialized(); // Create capabilities that the board needs to call certain protected kernel // functions. let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); - let main_loop_capability = create_capability!(capabilities::MainLoopCapability); let process_management_capability = create_capability!(capabilities::ProcessManagementCapability); // Setup the console. let console = components::console::ConsoleComponent::new( board_kernel, - capsules::console::DRIVER_NUM, + capsules_core::console::DRIVER_NUM, uart_mux, ) - .finalize(()); + .finalize(components::console_component_static!()); // Create the debugger object that handles calls to `debug!()`. - components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); // LEDs // Clock to Port A, B, C are enabled in `set_pin_primary_functions()` let gpio_ports = &base_peripherals.gpio_ports; - let led = components::led::LedsComponent::new().finalize(components::led_component_helper!( + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( LedLow<'static, stm32f401cc::gpio::Pin>, LedLow::new(gpio_ports.get_pin(stm32f401cc::gpio::PinId::PC13).unwrap()), )); @@ -312,7 +306,7 @@ pub unsafe fn main() { // BUTTONs let button = components::button::ButtonComponent::new( board_kernel, - capsules::button::DRIVER_NUM, + capsules_core::button::DRIVER_NUM, components::button_component_helper!( stm32f401cc::gpio::Pin, ( @@ -322,26 +316,26 @@ pub unsafe fn main() { ) ), ) - .finalize(components::button_component_buf!(stm32f401cc::gpio::Pin)); + .finalize(components::button_component_static!(stm32f401cc::gpio::Pin)); // ALARM let tim2 = &base_peripherals.tim2; let mux_alarm = components::alarm::AlarmMuxComponent::new(tim2).finalize( - components::alarm_mux_component_helper!(stm32f401cc::tim2::Tim2), + components::alarm_mux_component_static!(stm32f401cc::tim2::Tim2), ); let alarm = components::alarm::AlarmDriverComponent::new( board_kernel, - capsules::alarm::DRIVER_NUM, + capsules_core::alarm::DRIVER_NUM, mux_alarm, ) - .finalize(components::alarm_component_helper!(stm32f401cc::tim2::Tim2)); + .finalize(components::alarm_component_static!(stm32f401cc::tim2::Tim2)); // GPIO let gpio = GpioComponent::new( board_kernel, - capsules::gpio::DRIVER_NUM, + capsules_core::gpio::DRIVER_NUM, components::gpio_component_helper!( stm32f401cc::gpio::Pin, // 2 => gpio_ports.pins[2][13].as_ref().unwrap(), // C13 (reserved for led) @@ -380,38 +374,38 @@ pub unsafe fn main() { 46 => gpio_ports.pins[1][9].as_ref().unwrap(), // B9 ), ) - .finalize(components::gpio_component_buf!(stm32f401cc::gpio::Pin)); + .finalize(components::gpio_component_static!(stm32f401cc::gpio::Pin)); // ADC let adc_mux = components::adc::AdcMuxComponent::new(&base_peripherals.adc1) - .finalize(components::adc_mux_component_helper!(stm32f401cc::adc::Adc)); + .finalize(components::adc_mux_component_static!(stm32f401cc::adc::Adc)); let adc_channel_0 = - components::adc::AdcComponent::new(&adc_mux, stm32f401cc::adc::Channel::Channel3) - .finalize(components::adc_component_helper!(stm32f401cc::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f401cc::adc::Channel::Channel3) + .finalize(components::adc_component_static!(stm32f401cc::adc::Adc)); let adc_channel_1 = - components::adc::AdcComponent::new(&adc_mux, stm32f401cc::adc::Channel::Channel10) - .finalize(components::adc_component_helper!(stm32f401cc::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f401cc::adc::Channel::Channel10) + .finalize(components::adc_component_static!(stm32f401cc::adc::Adc)); let adc_channel_2 = - components::adc::AdcComponent::new(&adc_mux, stm32f401cc::adc::Channel::Channel13) - .finalize(components::adc_component_helper!(stm32f401cc::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f401cc::adc::Channel::Channel13) + .finalize(components::adc_component_static!(stm32f401cc::adc::Adc)); let adc_channel_3 = - components::adc::AdcComponent::new(&adc_mux, stm32f401cc::adc::Channel::Channel9) - .finalize(components::adc_component_helper!(stm32f401cc::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f401cc::adc::Channel::Channel9) + .finalize(components::adc_component_static!(stm32f401cc::adc::Adc)); let adc_channel_4 = - components::adc::AdcComponent::new(&adc_mux, stm32f401cc::adc::Channel::Channel15) - .finalize(components::adc_component_helper!(stm32f401cc::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f401cc::adc::Channel::Channel15) + .finalize(components::adc_component_static!(stm32f401cc::adc::Adc)); let adc_channel_5 = - components::adc::AdcComponent::new(&adc_mux, stm32f401cc::adc::Channel::Channel8) - .finalize(components::adc_component_helper!(stm32f401cc::adc::Adc)); + components::adc::AdcComponent::new(adc_mux, stm32f401cc::adc::Channel::Channel8) + .finalize(components::adc_component_static!(stm32f401cc::adc::Adc)); let adc_syscall = - components::adc::AdcVirtualComponent::new(board_kernel, capsules::adc::DRIVER_NUM) + components::adc::AdcVirtualComponent::new(board_kernel, capsules_core::adc::DRIVER_NUM) .finalize(components::adc_syscall_component_helper!( adc_channel_0, adc_channel_1, @@ -421,8 +415,8 @@ pub unsafe fn main() { adc_channel_5 )); - let process_printer = - components::process_printer::ProcessPrinterTextComponent::new().finalize(()); + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); PROCESS_PRINTER = Some(process_printer); // PROCESS CONSOLE @@ -431,34 +425,35 @@ pub unsafe fn main() { uart_mux, mux_alarm, process_printer, + Some(cortexm4::support::reset), ) - .finalize(components::process_console_component_helper!( + .finalize(components::process_console_component_static!( stm32f401cc::tim2::Tim2 )); let _ = process_console.start(); - let scheduler = components::sched::round_robin::RoundRobinComponent::new(&PROCESSES) - .finalize(components::rr_component_helper!(NUM_PROCS)); + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); let weact_f401cc = WeactF401CC { - console: console, + console, ipc: kernel::ipc::IPC::new( board_kernel, kernel::ipc::DRIVER_NUM, &memory_allocation_capability, ), adc: adc_syscall, - led: led, - button: button, - alarm: alarm, - gpio: gpio, + led, + button, + alarm, + gpio, scheduler, systick: cortexm4::systick::SysTick::new(), }; debug!("Initialization complete. Entering main loop"); - /// These symbols are defined in the linker script. + // These symbols are defined in the linker script. extern "C" { /// Beginning of the ROM region containing app images. static _sapps: u8; @@ -474,14 +469,14 @@ pub unsafe fn main() { board_kernel, chip, core::slice::from_raw_parts( - &_sapps as *const u8, - &_eapps as *const u8 as usize - &_sapps as *const u8 as usize, + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, ), core::slice::from_raw_parts_mut( - &mut _sappmem as *mut u8, - &_eappmem as *const u8 as usize - &_sappmem as *const u8 as usize, + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, ), - &mut PROCESSES, + &mut *addr_of_mut!(PROCESSES), &FAULT_RESPONSE, &process_management_capability, ) @@ -495,10 +490,14 @@ pub unsafe fn main() { .finalize(components::multi_alarm_test_component_buf!(stm32f401cc::tim2::Tim2)) .run();*/ - board_kernel.kernel_loop( - &weact_f401cc, - chip, - Some(&weact_f401cc.ipc), - &main_loop_capability, - ); + (board_kernel, weact_f401cc, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); } diff --git a/boards/wm1110dev/Cargo.toml b/boards/wm1110dev/Cargo.toml new file mode 100644 index 0000000000..af5bb32503 --- /dev/null +++ b/boards/wm1110dev/Cargo.toml @@ -0,0 +1,25 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +[package] +name = "wm1110dev" +version.workspace = true +authors.workspace = true +build = "../build.rs" +edition.workspace = true + +[dependencies] +cortexm4 = { path = "../../arch/cortex-m4" } +kernel = { path = "../../kernel" } +nrf52 = { path = "../../chips/nrf52" } +nrf52840 = { path = "../../chips/nrf52840" } +components = { path = "../components" } +nrf52_components = { path = "../nordic/nrf52_components" } + +capsules-core = { path = "../../capsules/core" } +capsules-extra = { path = "../../capsules/extra" } +capsules-system = { path = "../../capsules/system" } + +[lints] +workspace = true diff --git a/boards/wm1110dev/Makefile b/boards/wm1110dev/Makefile new file mode 100644 index 0000000000..615b99accd --- /dev/null +++ b/boards/wm1110dev/Makefile @@ -0,0 +1,30 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +# Makefile for building the tock kernel for the Arduino Nano 33 BLE board. + +TOCK_ARCH=cortex-m4 +TARGET=thumbv7em-none-eabi +PLATFORM=wm1110dev + +include ../Makefile.common + +ifdef PORT + FLAGS += --port $(PORT) +endif + +# Default target for installing the kernel. +.PHONY: install +install: program + +# Upload the kernel using tockloader and the tock bootloader +.PHONY: program +program: $(TOCK_ROOT_DIRECTORY)target/$(TARGET)/release/$(PLATFORM).bin + tockloader $(FLAGS) flash --address 0x10000 $< + +.PHONY: flash-bootloader +flash-bootloader: + curl -L --output /tmp/wm1110_dev-bootloader_v1.1.3.bin https://github.com/tock/tock-bootloader/releases/download/v1.1.3/wm1110_dev-bootloader_v1.1.3.bin + tockloader flash --address 0 /tmp/wm1110_dev-bootloader_v1.1.3.bin + rm /tmp/wm1110_dev-bootloader_v1.1.3.bin \ No newline at end of file diff --git a/boards/wm1110dev/README.md b/boards/wm1110dev/README.md new file mode 100644 index 0000000000..2816cc9617 --- /dev/null +++ b/boards/wm1110dev/README.md @@ -0,0 +1,68 @@ +Wio WM1110 Development Board +=================== + + + +The [Wio WM1110 Development +Board](https://www.seeedstudio.com/Wio-WM1110-Dev-Kit-p-5677.html) is a +multiradio location board for LoRa and location services based on the Nordic +nRF52840 SoC. The board includes the following hardware: + +- LIS3DHTR: 3-Axis accelerometer +- SHT41: Temperature and humidity sensor +- Semtech LR1110 + - LoRaWAN + - GNSS + - WiFi AP Scanning + + +## Getting Started + +First, follow the [Tock Getting Started guide](../../doc/Getting_Started.md). + +The WM1110 is designed to be programmed using an external JLink programmer. We +would like to avoid this requirement, so we use the [Tock +Bootloader](https://github.com/tock/tock-bootloader) which allows us to program +the board over the UART connection. However, we still require the programmer one +time to flash the bootloader. + +To flash the bootloader we must connect a JLink programmer. The easiest way is +to use an nRF52840dk board. + +### Connect the nRF52840dk to the WM1110-dev + +First we jumper the board as shown here: + + + +Pin mappings: + +| nRF52840dk | WM1110-dev | +|------------|------------| +| GND | GND | +| SWD SEL | 3V3 | +| SWD CLK | CLK | +| SWD IO | DIO | + +Make sure _both_ the nRF52840dk board and the WM1110-dev board are attached to +your computer via two USB connections. + +> Tip: If you only have one USB cable, plug it into the nRF52840dk. Then, jumper +> `3V3 (wm1110dev) <-> VDD (nrf)` and `SWD SEL (nrf) <-> VDD (nrf)`. The other +> jumpers remain the same. + +Then: + +``` +make flash-bootloader +``` + +This will use JLinkExe to flash the bootloader using the nRF52840dk's onboard +jtag hardware. + +### Using the Bootloader + +The bootloader activates when the reset button is pressed twice in quick +succession. The green LED will stay on when the bootloader is active. + +Once the bootloader is installed tockloader will work as expected. diff --git a/boards/wm1110dev/layout.ld b/boards/wm1110dev/layout.ld new file mode 100644 index 0000000000..a6f358c41c --- /dev/null +++ b/boards/wm1110dev/layout.ld @@ -0,0 +1,16 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ + +/* prog: leave 16KB at the end for nonvolatile storage. */ + +MEMORY +{ + rom (rx) : ORIGIN = 0x00010000, LENGTH = 256K + prog (rx) : ORIGIN = 0x00050000, LENGTH = 704K-16K + ram (rwx) : ORIGIN = 0x20000000, LENGTH = 256K +} + +PAGE_SIZE = 4K; + +INCLUDE ../kernel_layout.ld diff --git a/boards/wm1110dev/media/wm1110-dev-connections.png b/boards/wm1110dev/media/wm1110-dev-connections.png new file mode 100644 index 0000000000..43560b329c Binary files /dev/null and b/boards/wm1110dev/media/wm1110-dev-connections.png differ diff --git a/boards/wm1110dev/src/io.rs b/boards/wm1110dev/src/io.rs new file mode 100644 index 0000000000..2e71f817ec --- /dev/null +++ b/boards/wm1110dev/src/io.rs @@ -0,0 +1,90 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +use core::fmt::Write; +use core::panic::PanicInfo; + +use kernel::debug; +use kernel::debug::IoWrite; +use kernel::hil::led; +use kernel::hil::uart; +use nrf52840::gpio::Pin; +use nrf52840::uart::UARTE0_BASE; + +use crate::CHIP; +use crate::PROCESSES; +use crate::PROCESS_PRINTER; + +/// Writer is used by kernel::debug to panic message to the serial port. +pub struct Writer { + initialized: bool, +} + +impl Writer { + /// Indicate that USART has already been initialized. + pub fn set_initialized(&mut self) { + self.initialized = true; + } +} + +/// Global static for debug writer +pub static mut WRITER: Writer = Writer { initialized: false }; + +impl Write for Writer { + fn write_str(&mut self, s: &str) -> ::core::fmt::Result { + self.write(s.as_bytes()); + Ok(()) + } +} + +impl IoWrite for Writer { + fn write(&mut self, buf: &[u8]) -> usize { + let uart = nrf52840::uart::Uarte::new(UARTE0_BASE); + + use kernel::hil::uart::Configure; + + if !self.initialized { + self.initialized = true; + let _ = uart.configure(uart::Parameters { + baud_rate: 115200, + stop_bits: uart::StopBits::One, + parity: uart::Parity::None, + hw_flow_control: false, + width: uart::Width::Eight, + }); + } + + unsafe { + for &c in buf { + uart.send_byte(c); + while !uart.tx_ready() {} + } + } + buf.len() + } +} + +/// Default panic handler for the microbit board. +/// +/// We just use the standard default provided by the debug module in the kernel. +#[cfg(not(test))] +#[no_mangle] +#[panic_handler] +pub unsafe fn panic_fmt(pi: &PanicInfo) -> ! { + // Red Led + + use core::ptr::{addr_of, addr_of_mut}; + let led_red_pin = &nrf52840::gpio::GPIOPin::new(Pin::P0_14); + let led = &mut led::LedHigh::new(led_red_pin); + let writer = &mut *addr_of_mut!(WRITER); + debug::panic( + &mut [led], + writer, + pi, + &cortexm4::support::nop, + &*addr_of!(PROCESSES), + &*addr_of!(CHIP), + &*addr_of!(PROCESS_PRINTER), + ) +} diff --git a/boards/wm1110dev/src/main.rs b/boards/wm1110dev/src/main.rs new file mode 100644 index 0000000000..f0a22861d6 --- /dev/null +++ b/boards/wm1110dev/src/main.rs @@ -0,0 +1,547 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Tock kernel for the Wio WM1110 Development Board. +//! +//! It is based on nRF52840 SoC and Semtech LR1110. + +#![no_std] +// Disable this attribute when documenting, as a workaround for +// https://github.com/rust-lang/rust/issues/62184. +#![cfg_attr(not(doc), no_main)] +#![deny(missing_docs)] + +use core::ptr::addr_of; +use core::ptr::addr_of_mut; + +use kernel::capabilities; +use kernel::component::Component; +use kernel::hil::gpio::Configure; +use kernel::hil::gpio::Output; +use kernel::hil::led::LedHigh; +use kernel::hil::spi::SpiMaster; +use kernel::hil::time::Counter; +use kernel::platform::{KernelResources, SyscallDriverLookup}; +use kernel::scheduler::round_robin::RoundRobinSched; +#[allow(unused_imports)] +use kernel::{create_capability, debug, debug_gpio, debug_verbose, static_init}; + +use nrf52840::gpio::Pin; +use nrf52840::interrupt_service::Nrf52840DefaultPeripherals; + +// Three-color LED. +const LED_RED_PIN: Pin = Pin::P0_14; +const LED_GREEN_PIN: Pin = Pin::P0_13; + +const BUTTON_RST_PIN: Pin = Pin::P0_18; + +const GPIO_D2: Pin = Pin::P0_17; +const GPIO_D3: Pin = Pin::P0_16; +const GPIO_D4: Pin = Pin::P0_15; +const GPIO_D5: Pin = Pin::P1_09; +const GPIO_D6: Pin = Pin::P1_04; +const GPIO_D7: Pin = Pin::P1_03; + +const UART_TX_PIN: Pin = Pin::P0_24; +const UART_RX_PIN: Pin = Pin::P0_22; + +/// I2C pins for all of the sensors. +const I2C_SDA_PIN: Pin = Pin::P0_27; +const I2C_SCL_PIN: Pin = Pin::P0_26; + +// Pins for communicating with LR1110 +const SPI_CS_PIN: Pin = Pin::P1_12; +const SPI_SCK_PIN: Pin = Pin::P1_13; +const SPI_MOSI_PIN: Pin = Pin::P1_14; +const SPI_MISO_PIN: Pin = Pin::P1_15; +const RADIO_BUSY_PIN: Pin = Pin::P1_11; +const RADIO_RESET_PIN: Pin = Pin::P1_10; + +const LR_DIO9: Pin = Pin::P1_08; + +/// GPIO pin that controls VCC for the I2C bus and sensors. +const I2C_PWR: Pin = Pin::P0_07; + +const LORA_SPI_DRIVER_NUM: usize = capsules_core::driver::NUM::LoRaPhySPI as usize; +const LORA_GPIO_DRIVER_NUM: usize = capsules_core::driver::NUM::LoRaPhyGPIO as usize; + +/// UART Writer for panic!()s. +pub mod io; + +// How should the kernel respond when a process faults. For this board we choose +// to stop the app and print a notice, but not immediately panic. +const FAULT_RESPONSE: capsules_system::process_policies::StopWithDebugFaultPolicy = + capsules_system::process_policies::StopWithDebugFaultPolicy {}; + +// Number of concurrent processes this platform supports. +const NUM_PROCS: usize = 8; + +// State for loading and holding applications. +static mut PROCESSES: [Option<&'static dyn kernel::process::Process>; NUM_PROCS] = + [None; NUM_PROCS]; + +static mut CHIP: Option<&'static nrf52840::chip::NRF52> = None; +static mut PROCESS_PRINTER: Option<&'static capsules_system::process_printer::ProcessPrinterText> = + None; + +/// Dummy buffer that causes the linker to reserve enough space for the stack. +#[no_mangle] +#[link_section = ".stack_buffer"] +pub static mut STACK_MEMORY: [u8; 0x1000] = [0; 0x1000]; + +type SHT4xSensor = components::sht4x::SHT4xComponentType< + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, + capsules_core::virtualizers::virtual_i2c::I2CDevice<'static, nrf52840::i2c::TWI<'static>>, +>; +type TemperatureDriver = components::temperature::TemperatureComponentType; +type HumidityDriver = components::humidity::HumidityComponentType; +type RngDriver = components::rng::RngComponentType>; + +type NonvolatileDriver = components::nonvolatile_storage::NonvolatileStorageComponentType; + +/// Supported drivers by the platform +pub struct Platform { + console: &'static capsules_core::console::Console<'static>, + gpio: &'static capsules_core::gpio::GPIO<'static, nrf52::gpio::GPIOPin<'static>>, + led: &'static capsules_core::led::LedDriver< + 'static, + LedHigh<'static, nrf52::gpio::GPIOPin<'static>>, + 2, + >, + rng: &'static RngDriver, + ipc: kernel::ipc::IPC<{ NUM_PROCS as u8 }>, + nonvolatile_storage: &'static NonvolatileDriver, + alarm: &'static capsules_core::alarm::AlarmDriver< + 'static, + capsules_core::virtualizers::virtual_alarm::VirtualMuxAlarm< + 'static, + nrf52::rtc::Rtc<'static>, + >, + >, + temperature: &'static TemperatureDriver, + humidity: &'static HumidityDriver, + lr1110_gpio: &'static capsules_core::gpio::GPIO<'static, nrf52840::gpio::GPIOPin<'static>>, + lr1110_spi: &'static capsules_core::spi_controller::Spi< + 'static, + capsules_core::virtualizers::virtual_spi::VirtualSpiMasterDevice< + 'static, + nrf52840::spi::SPIM<'static>, + >, + >, + scheduler: &'static RoundRobinSched<'static>, + systick: cortexm4::systick::SysTick, +} + +impl SyscallDriverLookup for Platform { + fn with_driver(&self, driver_num: usize, f: F) -> R + where + F: FnOnce(Option<&dyn kernel::syscall::SyscallDriver>) -> R, + { + match driver_num { + capsules_core::console::DRIVER_NUM => f(Some(self.console)), + capsules_core::gpio::DRIVER_NUM => f(Some(self.gpio)), + capsules_core::alarm::DRIVER_NUM => f(Some(self.alarm)), + capsules_core::led::DRIVER_NUM => f(Some(self.led)), + capsules_core::rng::DRIVER_NUM => f(Some(self.rng)), + capsules_extra::nonvolatile_storage_driver::DRIVER_NUM => { + f(Some(self.nonvolatile_storage)) + } + LORA_SPI_DRIVER_NUM => f(Some(self.lr1110_spi)), + LORA_GPIO_DRIVER_NUM => f(Some(self.lr1110_gpio)), + kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), + capsules_extra::temperature::DRIVER_NUM => f(Some(self.temperature)), + capsules_extra::humidity::DRIVER_NUM => f(Some(self.humidity)), + _ => f(None), + } + } +} + +impl KernelResources>> + for Platform +{ + type SyscallDriverLookup = Self; + type SyscallFilter = (); + type ProcessFault = (); + type Scheduler = RoundRobinSched<'static>; + type SchedulerTimer = cortexm4::systick::SysTick; + type WatchDog = (); + type ContextSwitchCallback = (); + + fn syscall_driver_lookup(&self) -> &Self::SyscallDriverLookup { + self + } + fn syscall_filter(&self) -> &Self::SyscallFilter { + &() + } + fn process_fault(&self) -> &Self::ProcessFault { + &() + } + fn scheduler(&self) -> &Self::Scheduler { + self.scheduler + } + fn scheduler_timer(&self) -> &Self::SchedulerTimer { + &self.systick + } + fn watchdog(&self) -> &Self::WatchDog { + &() + } + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback { + &() + } +} + +/// This is in a separate, inline(never) function so that its stack frame is +/// removed when this function returns. Otherwise, the stack space used for +/// these static_inits is wasted. +#[inline(never)] +pub unsafe fn start() -> ( + &'static kernel::Kernel, + Platform, + &'static nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>, +) { + nrf52840::init(); + + let ieee802154_ack_buf = static_init!( + [u8; nrf52840::ieee802154_radio::ACK_BUF_SIZE], + [0; nrf52840::ieee802154_radio::ACK_BUF_SIZE] + ); + + // Initialize chip peripheral drivers + let nrf52840_peripherals = static_init!( + Nrf52840DefaultPeripherals, + Nrf52840DefaultPeripherals::new(ieee802154_ack_buf) + ); + + // set up circular peripheral dependencies + nrf52840_peripherals.init(); + let base_peripherals = &nrf52840_peripherals.nrf52; + + let board_kernel = static_init!(kernel::Kernel, kernel::Kernel::new(&*addr_of!(PROCESSES))); + + nrf52_components::startup::NrfStartupComponent::new( + false, + BUTTON_RST_PIN, + nrf52840::uicr::Regulator0Output::DEFAULT, + &base_peripherals.nvmc, + ) + .finalize(()); + + //-------------------------------------------------------------------------- + // CAPABILITIES + //-------------------------------------------------------------------------- + + // Create capabilities that the board needs to call certain protected kernel + // functions. + let process_management_capability = + create_capability!(capabilities::ProcessManagementCapability); + let memory_allocation_capability = create_capability!(capabilities::MemoryAllocationCapability); + + //-------------------------------------------------------------------------- + // DEBUG GPIO + //-------------------------------------------------------------------------- + + // Configure kernel debug GPIOs as early as possible. These are used by the + // `debug_gpio!(0, toggle)` macro. We configure these early so that the + // macro is available during most of the setup code and kernel execution. + kernel::debug::assign_gpios( + Some(&nrf52840_peripherals.gpio_port[LED_GREEN_PIN]), + Some(&nrf52840_peripherals.gpio_port[LED_RED_PIN]), + None, + ); + + //-------------------------------------------------------------------------- + // GPIO + //-------------------------------------------------------------------------- + + let gpio = components::gpio::GpioComponent::new( + board_kernel, + capsules_core::gpio::DRIVER_NUM, + components::gpio_component_helper!( + nrf52840::gpio::GPIOPin, + 2 => &nrf52840_peripherals.gpio_port[GPIO_D2], + 3 => &nrf52840_peripherals.gpio_port[GPIO_D3], + 4 => &nrf52840_peripherals.gpio_port[GPIO_D4], + 5 => &nrf52840_peripherals.gpio_port[GPIO_D5], + 6 => &nrf52840_peripherals.gpio_port[GPIO_D6], + 7 => &nrf52840_peripherals.gpio_port[GPIO_D7], + ), + ) + .finalize(components::gpio_component_static!(nrf52840::gpio::GPIOPin)); + + //-------------------------------------------------------------------------- + // LEDs + //-------------------------------------------------------------------------- + + let led = components::led::LedsComponent::new().finalize(components::led_component_static!( + LedHigh<'static, nrf52840::gpio::GPIOPin>, + LedHigh::new(&nrf52840_peripherals.gpio_port[LED_GREEN_PIN]), + LedHigh::new(&nrf52840_peripherals.gpio_port[LED_RED_PIN]), + )); + + //-------------------------------------------------------------------------- + // ALARM & TIMER + //-------------------------------------------------------------------------- + + let rtc = &base_peripherals.rtc; + let _ = rtc.start(); + + let mux_alarm = components::alarm::AlarmMuxComponent::new(rtc) + .finalize(components::alarm_mux_component_static!(nrf52::rtc::Rtc)); + let alarm = components::alarm::AlarmDriverComponent::new( + board_kernel, + capsules_core::alarm::DRIVER_NUM, + mux_alarm, + ) + .finalize(components::alarm_component_static!(nrf52::rtc::Rtc)); + + //-------------------------------------------------------------------------- + // UART & CONSOLE & DEBUG + //-------------------------------------------------------------------------- + + base_peripherals.uarte0.initialize( + nrf52::pinmux::Pinmux::new(UART_TX_PIN as u32), + nrf52::pinmux::Pinmux::new(UART_RX_PIN as u32), + None, + None, + ); + + // Create a shared UART channel for the console and for kernel debug. + let uart_mux = components::console::UartMuxComponent::new(&base_peripherals.uarte0, 115200) + .finalize(components::uart_mux_component_static!()); + + // Setup the console. + let console = components::console::ConsoleComponent::new( + board_kernel, + capsules_core::console::DRIVER_NUM, + uart_mux, + ) + .finalize(components::console_component_static!()); + + // Create the debugger object that handles calls to `debug!()`. + components::debug_writer::DebugWriterComponent::new(uart_mux) + .finalize(components::debug_writer_component_static!()); + + //-------------------------------------------------------------------------- + // SENSORS + //-------------------------------------------------------------------------- + + // Enable the power supply for the I2C bus and attached sensors. + nrf52840_peripherals.gpio_port[I2C_PWR].make_output(); + nrf52840_peripherals.gpio_port[I2C_PWR].set(); + + let mux_i2c = components::i2c::I2CMuxComponent::new(&base_peripherals.twi1, None) + .finalize(components::i2c_mux_component_static!(nrf52840::i2c::TWI)); + base_peripherals.twi1.configure( + nrf52840::pinmux::Pinmux::new(I2C_SCL_PIN as u32), + nrf52840::pinmux::Pinmux::new(I2C_SDA_PIN as u32), + ); + + let sht4x = components::sht4x::SHT4xComponent::new( + mux_i2c, + capsules_extra::sht4x::BASE_ADDR, + mux_alarm, + ) + .finalize(components::sht4x_component_static!( + nrf52::rtc::Rtc<'static>, + nrf52840::i2c::TWI + )); + + let temperature = components::temperature::TemperatureComponent::new( + board_kernel, + capsules_extra::temperature::DRIVER_NUM, + sht4x, + ) + .finalize(components::temperature_component_static!(SHT4xSensor)); + + let humidity = components::humidity::HumidityComponent::new( + board_kernel, + capsules_extra::humidity::DRIVER_NUM, + sht4x, + ) + .finalize(components::humidity_component_static!(SHT4xSensor)); + + //-------------------------------------------------------------------------- + // LoRa (SPI + GPIO) + //-------------------------------------------------------------------------- + + let mux_spi = components::spi::SpiMuxComponent::new(&base_peripherals.spim0) + .finalize(components::spi_mux_component_static!(nrf52840::spi::SPIM)); + + // Create the SPI system call capsule for accessing the LoRa radio. + let lr1110_spi = components::spi::SpiSyscallComponent::new( + board_kernel, + mux_spi, + &nrf52840_peripherals.gpio_port[SPI_CS_PIN], + LORA_SPI_DRIVER_NUM, + ) + .finalize(components::spi_syscall_component_static!( + nrf52840::spi::SPIM + )); + + base_peripherals.spim0.configure( + nrf52840::pinmux::Pinmux::new(SPI_MOSI_PIN as u32), + nrf52840::pinmux::Pinmux::new(SPI_MISO_PIN as u32), + nrf52840::pinmux::Pinmux::new(SPI_SCK_PIN as u32), + ); + + base_peripherals + .spim0 + .specify_chip_select(&nrf52840_peripherals.gpio_port[SPI_CS_PIN]) + .unwrap(); + + // Pin mappings from the original WM1110 source code. + let lr1110_gpio = components::gpio::GpioComponent::new( + board_kernel, + LORA_GPIO_DRIVER_NUM, + components::gpio_component_helper!( + nrf52840::gpio::GPIOPin, + 40 => &nrf52840_peripherals.gpio_port[LR_DIO9], + 42 => &nrf52840_peripherals.gpio_port[RADIO_RESET_PIN], + 43 => &nrf52840_peripherals.gpio_port[RADIO_BUSY_PIN], + ), + ) + .finalize(components::gpio_component_static!(nrf52840::gpio::GPIOPin)); + + //-------------------------------------------------------------------------- + // Process Console + //-------------------------------------------------------------------------- + + let process_printer = components::process_printer::ProcessPrinterTextComponent::new() + .finalize(components::process_printer_text_component_static!()); + PROCESS_PRINTER = Some(process_printer); + + let _process_console = components::process_console::ProcessConsoleComponent::new( + board_kernel, + uart_mux, + mux_alarm, + process_printer, + Some(cortexm4::support::reset), + ) + .finalize(components::process_console_component_static!( + nrf52840::rtc::Rtc + )); + + //-------------------------------------------------------------------------- + // RANDOM NUMBERS + //-------------------------------------------------------------------------- + + let rng = components::rng::RngComponent::new( + board_kernel, + capsules_core::rng::DRIVER_NUM, + &base_peripherals.trng, + ) + .finalize(components::rng_component_static!(nrf52840::trng::Trng)); + + //-------------------------------------------------------------------------- + // NONVOLATILE STORAGE + //-------------------------------------------------------------------------- + + let nonvolatile_storage = components::nonvolatile_storage::NonvolatileStorageComponent::new( + board_kernel, + capsules_extra::nonvolatile_storage_driver::DRIVER_NUM, + &base_peripherals.nvmc, + 0xFC000, // Start address for userspace accessible region + 4096 * 4, // Length of userspace accessible region (16 pages) + 0, // No kernel access + 0, + ) + .finalize(components::nonvolatile_storage_component_static!( + nrf52840::nvmc::Nvmc + )); + + //-------------------------------------------------------------------------- + // FINAL SETUP AND BOARD BOOT + //-------------------------------------------------------------------------- + + // Start all of the clocks. Low power operation will require a better + // approach than this. + nrf52_components::NrfClockComponent::new(&base_peripherals.clock).finalize(()); + + let scheduler = components::sched::round_robin::RoundRobinComponent::new(&*addr_of!(PROCESSES)) + .finalize(components::round_robin_component_static!(NUM_PROCS)); + + let platform = Platform { + console, + led, + gpio, + rng, + alarm, + nonvolatile_storage, + ipc: kernel::ipc::IPC::new( + board_kernel, + kernel::ipc::DRIVER_NUM, + &memory_allocation_capability, + ), + scheduler, + systick: cortexm4::systick::SysTick::new_with_calibration(64000000), + temperature, + humidity, + lr1110_spi, + lr1110_gpio, + }; + + let chip = static_init!( + nrf52840::chip::NRF52, + nrf52840::chip::NRF52::new(nrf52840_peripherals) + ); + CHIP = Some(chip); + + //-------------------------------------------------------------------------- + // TESTS + //-------------------------------------------------------------------------- + + //-------------------------------------------------------------------------- + // BOOT COMPLETE + //-------------------------------------------------------------------------- + + debug!("Initialization complete. Entering main loop."); + let _ = _process_console.start(); + + //-------------------------------------------------------------------------- + // PROCESSES AND MAIN LOOP + //-------------------------------------------------------------------------- + + // These symbols are defined in the linker script. + extern "C" { + /// Beginning of the ROM region containing app images. + static _sapps: u8; + /// End of the ROM region containing app images. + static _eapps: u8; + /// Beginning of the RAM region for app memory. + static mut _sappmem: u8; + /// End of the RAM region for app memory. + static _eappmem: u8; + } + + kernel::process::load_processes( + board_kernel, + chip, + core::slice::from_raw_parts( + core::ptr::addr_of!(_sapps), + core::ptr::addr_of!(_eapps) as usize - core::ptr::addr_of!(_sapps) as usize, + ), + core::slice::from_raw_parts_mut( + core::ptr::addr_of_mut!(_sappmem), + core::ptr::addr_of!(_eappmem) as usize - core::ptr::addr_of!(_sappmem) as usize, + ), + &mut *addr_of_mut!(PROCESSES), + &FAULT_RESPONSE, + &process_management_capability, + ) + .unwrap_or_else(|err| { + debug!("Error loading processes!"); + debug!("{:?}", err); + }); + + (board_kernel, platform, chip) +} + +/// Main function called after RAM initialized. +#[no_mangle] +pub unsafe fn main() { + let main_loop_capability = create_capability!(capabilities::MainLoopCapability); + + let (board_kernel, platform, chip) = start(); + board_kernel.kernel_loop(&platform, chip, Some(&platform.ipc), &main_loop_capability); +} diff --git a/capsules/Cargo.toml b/capsules/Cargo.toml deleted file mode 100644 index 65301bcf12..0000000000 --- a/capsules/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "capsules" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" - -[dependencies] -kernel = { path = "../kernel" } -enum_primitive = { path = "../libraries/enum_primitive" } -tickv = { path = "../libraries/tickv" } diff --git a/capsules/README.md b/capsules/README.md index 807479160e..c7ae653ecd 100644 --- a/capsules/README.md +++ b/capsules/README.md @@ -4,6 +4,7 @@ Tock Capsules Capsules are drivers that live in the kernel and are written in Rust. They are required to conform to Rust's type system (i.e. no `unsafe`). Capsules are platform agnostic and provide a range of features: + - Drivers for sensors or other ICs - Virtualization of hardware resources - Syscall interfaces for userland applications @@ -18,173 +19,29 @@ trait and can be used by userland applications. Others provide an internal interface that can be used by other in-kernel capsules as well as a `Driver` interface for applications. +Capsule Organization +-------------------- -List of Tock Capsules ---------------------- - -The list of Tock capsules and a brief description. - -### Sensor and other IC Drivers - -These implement a driver to setup and read various physical sensors. - -- **[ADC Microphone](src/adc_microphone.rs)**: Single ADC pin microphone. -- **[Analog Sensors](src/analog_sensor.rs)**: Single ADC pin sensors. -- **[APDS9960](src/apds9960.rs)**: Proximity sensor. -- **[FXOS8700CQ](src/fxos8700cq.rs)**: Accelerometer and magnetometer. -- **[HTS221](src/hts221.rs)**: Temperature and humidity sensor. -- **[ISL29035](src/isl29035.rs)**: Light sensor. -- **[L3GD20](src/l3gd20.rs)**: MEMS 3 axys digital gyroscope and temperature - sensor. -- **[LSM303xx Support](src/lsm303xx.rs)**: Shared files. - - **[LSM303AGR](src/lsm303agr.rs)**: 3D accelerometer and 3D magnetometer - sensor. - - **[LSM303DLHC](src/lsm303dlhc.rs)**: 3D accelerometer and 3D magnetometer - sensor. -- **[LSM6DSOXTR](src/lsm6dsoxtr.rs)**: 3D accelerometer and 3D magnetometer - sensor. -- **[LPS25HB](src/lps25hb.rs)**: Pressure sensor. -- **[MLX90614](src/mlx90614.rs)**: Infrared temperature sensor. -- **[RP2040 Temperature](src/temperature_rp2040.rs)**: Analog RP2040 temperature - sensor. -- **[SHT3x](src/sht3x.rs)**: SHT3x temperature and humidity sensor. -- **[SI7021](src/si7021.rs)**: Temperature and humidity sensor. -- **[STM32 Temperature](src/temperature_stm.rs)**: Analog STM32 temperature - sensor. -- **[TSL2561](src/tsl2561.rs)**: Light sensor. - -These drivers provide support for various ICs. - -- **[FM25CL](src/fm25cl.rs)**: FRAM chip. -- **[FT6x06](src/ft6x06.rs)**: FT6x06 touch panel. -- **[HD44780 LCD](src/hd44780.rs)**: HD44780 LCD screen. -- **[LTC294X](src/ltc294x.rs)**: LTC294X series of coulomb counters. -- **[MAX17205](src/max17205.rs)**: Battery fuel gauge. -- **[MCP230xx](src/mcp230xx.rs)**: I2C GPIO extender. -- **[MX25r6435F](src/mx25r6435f.rs)**: SPI flash chip. -- **[PCA9544A](src/pca9544a.rs)**: Multiple port I2C selector. -- **[SD Card](src/sdcard.rs)**: Support for SD cards. -- **[ST77xx](src/st77xx.rs)**: ST77xx IPS screen. - - -### Wireless - -Support for wireless radios. - -- **[nRF51822 Serialization](src/nrf51822_serialization.rs)**: Kernel support - for using the nRF51 serialization library. -- **[RF233](src/rf233.rs)**: Driver for RF233 radio. -- **[BLE Advertising](src/ble_advertising_driver.rs)**: Driver for sending BLE - advertisements. - -### Libraries - -Protocol stacks and other libraries. - -- **[IEEE 802.15.4](src/ieee802154)**: 802.15.4 networking. -- **[Networking](src/net)**: Networking stack. -- **[USB](src/usb)**: USB 2.0. -- **[Segger RTT](src/segger_rtt.rs)**: Segger RTT support. Provides `hil::uart` - interface. - - -### MCU Peripherals for Userspace - -These capsules provide a `Driver` interface for common MCU peripherals. - -- **[ADC](src/adc.rs)**: Individual and continuous samples. -- **[Alarm](src/alarm.rs)**: Oneshot and periodic timers. -- **[Analog Comparator](src/analog_comparator.rs)**: Voltage comparison. -- **[CRC](src/crc.rs)**: CRC calculation. -- **[DAC](src/dac.rs)**: Digital to analog conversion. -- **[GPIO](src/gpio.rs)**: GPIO configuring and control. -- **[I2C_MASTER](src/i2c_master.rs)**: I2C master access only. -- **[I2C_MASTER_SLAVE](src/i2c_master_slave_driver.rs)**: I2C master and slave - access. -- **[RNG](src/rng.rs)**: Random number generation. -- **[SPI Controller](src/spi_controller.rs)**: SPI controller device (SPI - master) -- **[SPI Peripheral](src/spi_peripheral.rs)**: SPI peripheral device (SPI slave) - - -### Helpful Userspace Capsules - -These provide common and better abstractions for userspace. - -- **[Ambient Light](src/ambient_light.rs)**: Query light sensors. -- **[App Flash](src/app_flash_driver.rs)**: Allow applications to write their - own flash. -- **[Button](src/button.rs)**: Detect button presses. -- **[Buzzer](src/buzzer_driver.rs)**: Simple buzzer. -- **[Console](src/console.rs)**: UART console support. -- **[CTAP](src/ctap.rs)**: Client to Authenticator Protocol (CTAP) support. -- **[Humidity](src/humidity.rs)**: Query humidity sensors. -- **[LED](src/led.rs)**: Turn on and off LEDs. -- **[LED Matrix](src/led_matrix.rs)**: Control a 2D array of LEDs. -- **[Proximity](src/proximity.rs)**: Proximity sensors. -- **[Screen](src/screen.rs)**: Displays and screens. -- **[SHA](src/sha.rs)**: SHA hashes. -- **[Sound Pressure](src/sound_pressure.rs)**: Query sound pressure levels. -- **[Temperature](src/temperature.rs)**: Query temperature sensors. -- **[Text Screen](src/text_screen.rs)**: Text-based displays. -- **[Touch](src/touch.rs)**: User touch panels. - - -### Virtualized Sensor Capsules for Userspace - -These provide virtualized (i.e. multiple applications can use them -simultaneously) support for generic sensor interfaces. - -- **[Asynchronous GPIO](src/gpio_async.rs)**: GPIO pins accessed by split-phase - calls. -- **[9DOF](src/ninedof.rs)**: 9DOF sensors (acceleration, magnetometer, - gyroscope). -- **[Nonvolatile Storage](src/nonvolatile_storage_driver.rs)**: Persistent - storage for userspace. - - -### Virtualized Hardware Resources - -These allow for multiple users of shared hardware resources in the kernel. - -- **[Virtual ADC](src/virtual_adc.rs)**: Shared single ADC channel. -- **[Virtual AES-CCM](src/virtual_aes_ccm.rs)**: Shared AES-CCM engine. -- **[Virtual Alarm](src/virtual_alarm.rs)**: Shared alarm resource. -- **[Virtual Digest](src/virtual_digest.rs)**: Shared digest resource. -- **[Virtual Flash](src/virtual_flash.rs)**: Shared flash resource. -- **[Virtual HMAC](src/virtual_hmac.rs)**: Shared HMAC resource. -- **[Virtual I2C](src/virtual_i2c.rs)**: Shared I2C and fixed addresses. -- **[Virtual PWM](src/virtual_pwm.rs)**: Shared PWM hardware. -- **[Virtual RNG](src/virtual_rng.rs)**: Shared random number generator. -- **[Virtual SHA](src/virtual_sha.rs)**: Shared SHA hashes. -- **[Virtual SPI](src/virtual_spi.rs)**: Shared SPI and fixed chip select pins. -- **[Virtual Timer](src/virtual_timer.rs)**: Shared timer. -- **[Virtual UART](src/virtual_uart.rs)**: Shared UART bus. - - -### Utility Capsules - -Other capsules that implement reusable logic. +Capsules are sub-divided into multiple crates, which can be imported and used +independently. This enables Tock to enforce different policies on a per-crate +basis, for instance whether a given crate is allowed to use external +(non-vendored) dependencies. -- **[Nonvolatile to Pages](src/nonvolatile_to_pages.rs)**: Map arbitrary reads - and writes to flash pages. -- **[HMAC](src/hmac.rs)**: Hash-based Message Authentication Code (HMAC) digest - engine. -- **[Log Storage](src/log.rs)**: Log storage abstraction on top of flash - devices. -- **[Bus Adapters](src/bus.rs)**: Generic abstraction for SPI/I2C/8080. -- **[TicKV](src/tickv.rs)**: Key-value storage. +Currently, capsules are divided into the following crates: +- [**`core`**](./core): these capsules implement functionality which are + required for most (if not all) Tock-based systems to operate. For instance, + these capsules implement basic infrastructure for interacting with timer or + alarm hardware, exposing UART hardware as console ports, etc. -### Debugging Capsules + This crate further contains virtualizers, which enable a given single + peripheral to be used by multiple clients. Virtualizers are agnostic over + their underlying peripherals; they do not implement logic specific to any + given peripheral device. -These are selectively included on a board to help with testing and debugging -various elements of Tock. + This crate stricly prohibits use of any external (non-vendored and unvetted) + dependencies. -- **[Debug Process Restart](src/debug_process_restart.rs)**: Force all processes - to enter a fault state when a button is pressed. -- **[Low-Level Debug](src/low_level_debug)**: Provides system calls for - low-level debugging tasks, such as debugging toolchain and relocation issues. -- **[Panic Button](src/panic_button.rs)**: Use a button to force a `panic!()`. -- **[Process Console](src/process_console.rs)**: Provide a UART console to - inspect the status of process and stop/start them. +- [**`extra`**](./extra): this crate contains all remaining capsules; + specifically capsules which does not fit into any the above categories and + which does not require any external dependencies. diff --git a/capsules/aes_gcm/Cargo.toml b/capsules/aes_gcm/Cargo.toml new file mode 100644 index 0000000000..e581c7e3e9 --- /dev/null +++ b/capsules/aes_gcm/Cargo.toml @@ -0,0 +1,18 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Western Digital 2023. + +[package] +name = "capsules-aes-gcm" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[dependencies] +kernel = { path = "../../kernel" } +enum_primitive = { path = "../../libraries/enum_primitive" } +tickv = { path = "../../libraries/tickv" } +ghash = "0.4.4" + +[lints] +workspace = true diff --git a/capsules/aes_gcm/README.md b/capsules/aes_gcm/README.md new file mode 100644 index 0000000000..0b87a1c5c1 --- /dev/null +++ b/capsules/aes_gcm/README.md @@ -0,0 +1,37 @@ +AES GCM Capsule +=============== + +This crate contains software support for +[AES-GCM](https://en.wikipedia.org/wiki/AES-GCM-SIV). +This capsule doesn't perform any AES operations, instead it relies on +existing implmenetations to perform the AES operations and instead manages +the operations and hashing to support GCM. + +This capsule uses the extenal +[ghash crate](https://github.com/RustCrypto/universal-hashes/tree/master/ghash) +as part of Rust-crypto to implement AES GCM on top of existing AES +implementions. + +## Cargo tree + +``` +capsules-aes-gcm v0.1.0 (/var/mnt/scratch/alistair/software/tock/tock/capsules/aes_gcm) +├── enum_primitive v0.1.0 (/var/mnt/scratch/alistair/software/tock/tock/libraries/enum_primitive) +├── ghash v0.4.4 +│ ├── opaque-debug v0.3.0 +│ └── polyval v0.5.3 +│ ├── cfg-if v1.0.0 +│ ├── cpufeatures v0.2.7 +│ ├── opaque-debug v0.3.0 +│ └── universal-hash v0.4.1 +│ ├── generic-array v0.14.7 +│ │ └── typenum v1.16.0 +│ │ [build-dependencies] +│ │ └── version_check v0.9.4 +│ └── subtle v2.4.1 +├── kernel v0.1.0 (/var/mnt/scratch/alistair/software/tock/tock/kernel) +│ ├── tock-cells v0.1.0 (/var/mnt/scratch/alistair/software/tock/tock/libraries/tock-cells) +│ ├── tock-registers v0.8.1 (/var/mnt/scratch/alistair/software/tock/tock/libraries/tock-register-interface) +│ └── tock-tbf v0.1.0 (/var/mnt/scratch/alistair/software/tock/tock/libraries/tock-tbf) +└── tickv v1.0.0 (/var/mnt/scratch/alistair/software/tock/tock/libraries/tickv) +``` \ No newline at end of file diff --git a/capsules/aes_gcm/src/aes_gcm.rs b/capsules/aes_gcm/src/aes_gcm.rs new file mode 100644 index 0000000000..3e23eeeda0 --- /dev/null +++ b/capsules/aes_gcm/src/aes_gcm.rs @@ -0,0 +1,413 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Western Digital 2023. + +//! Implements an AES-GCM implementation using the underlying +//! AES-CTR implementation. +//! +//! This capsule requires an AES-CTR implementation to support +//! AES-GCM. The implementation relies on AES-CTR, AES-CBC, AES-ECB and +//! AES-CCM to ensure that when this capsule is used it exposes +//! all of supported AES operations in a single API. + +use core::cell::Cell; +use ghash::universal_hash::NewUniversalHash; +use ghash::universal_hash::UniversalHash; +use ghash::GHash; +use ghash::Key; +use kernel::hil::symmetric_encryption; +use kernel::hil::symmetric_encryption::{ + AES128Ctr, AES128, AES128CBC, AES128CCM, AES128ECB, AES128_BLOCK_SIZE, AES128_KEY_SIZE, +}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::ErrorCode; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +enum GCMState { + Idle, + GenerateHashKey, + CtrEncrypt, +} + +pub struct Aes128Gcm<'a, A: AES128<'a> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'a>> { + aes: &'a A, + + mac: OptionalCell, + + crypt_buf: TakeCell<'static, [u8]>, + + client: OptionalCell<&'a dyn symmetric_encryption::Client<'a>>, + ccm_client: OptionalCell<&'a dyn symmetric_encryption::CCMClient>, + gcm_client: OptionalCell<&'a dyn symmetric_encryption::GCMClient>, + + state: Cell, + encrypting: Cell, + + buf: TakeCell<'static, [u8]>, + + pos: Cell<(usize, usize, usize)>, + key: Cell<[u8; AES128_KEY_SIZE]>, + iv: Cell<[u8; AES128_KEY_SIZE]>, +} + +impl<'a, A: AES128<'a> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'a>> Aes128Gcm<'a, A> { + pub fn new(aes: &'a A, crypt_buf: &'static mut [u8]) -> Aes128Gcm<'a, A> { + Aes128Gcm { + aes, + + mac: OptionalCell::empty(), + + crypt_buf: TakeCell::new(crypt_buf), + + client: OptionalCell::empty(), + ccm_client: OptionalCell::empty(), + gcm_client: OptionalCell::empty(), + + state: Cell::new(GCMState::Idle), + encrypting: Cell::new(false), + + buf: TakeCell::empty(), + pos: Cell::new((0, 0, 0)), + key: Cell::new(Default::default()), + iv: Cell::new(Default::default()), + } + } + + fn start_ctr_encrypt(&self) -> Result<(), ErrorCode> { + self.aes.set_mode_aes128ctr(self.encrypting.get())?; + + let res = AES128::set_key(self.aes, &self.key.get()); + if res != Ok(()) { + return res; + } + + self.aes.set_iv(&self.iv.get()).unwrap(); + + self.aes.start_message(); + let crypt_buf = self.crypt_buf.take().unwrap(); + let (_aad_offset, message_offset, message_len) = self.pos.get(); + + match AES128::crypt( + self.aes, + None, + crypt_buf, + message_offset, + message_offset + message_len + AES128_BLOCK_SIZE, + ) { + None => { + self.state.set(GCMState::CtrEncrypt); + Ok(()) + } + Some((res, _, crypt_buf)) => { + self.crypt_buf.replace(crypt_buf); + res + } + } + } + + fn crypt_r( + &self, + buf: &'static mut [u8], + aad_offset: usize, + message_offset: usize, + message_len: usize, + encrypting: bool, + ) -> Result<(), (ErrorCode, &'static mut [u8])> { + if self.state.get() != GCMState::Idle { + return Err((ErrorCode::BUSY, buf)); + } + + self.encrypting.set(encrypting); + + self.aes.set_mode_aes128ctr(self.encrypting.get()).unwrap(); + AES128::set_key(self.aes, &self.key.get()).unwrap(); + self.aes.set_iv(&[0; AES128_BLOCK_SIZE]).unwrap(); + + self.aes.start_message(); + let crypt_buf = self.crypt_buf.take().unwrap(); + + for i in 0..AES128_BLOCK_SIZE { + crypt_buf[i] = 0; + } + + match AES128::crypt(self.aes, None, crypt_buf, 0, AES128_BLOCK_SIZE) { + None => { + self.state.set(GCMState::GenerateHashKey); + } + Some((_res, _, crypt_buf)) => { + self.crypt_buf.replace(crypt_buf); + } + } + + self.buf.replace(buf); + self.pos.set((aad_offset, message_offset, message_len)); + Ok(()) + } +} + +impl<'a, A: AES128<'a> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'a>> + symmetric_encryption::CCMClient for Aes128Gcm<'a, A> +{ + fn crypt_done(&self, buf: &'static mut [u8], res: Result<(), ErrorCode>, tag_is_valid: bool) { + self.ccm_client.map(move |client| { + client.crypt_done(buf, res, tag_is_valid); + }); + } +} + +impl<'a, A: AES128<'a> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'a>> + symmetric_encryption::AES128GCM<'a> for Aes128Gcm<'a, A> +{ + fn set_client(&self, client: &'a dyn symmetric_encryption::GCMClient) { + self.gcm_client.set(client); + } + + fn set_key(&self, key: &[u8]) -> Result<(), ErrorCode> { + if key.len() < AES128_KEY_SIZE { + Err(ErrorCode::INVAL) + } else { + let mut new_key = [0u8; AES128_KEY_SIZE]; + new_key.copy_from_slice(key); + self.key.set(new_key); + Ok(()) + } + } + + fn set_iv(&self, nonce: &[u8]) -> Result<(), ErrorCode> { + let mut new_nonce = [0u8; AES128_KEY_SIZE]; + let len = nonce.len().min(12); + + new_nonce[0..len].copy_from_slice(&nonce[0..len]); + new_nonce[12..16].copy_from_slice(&[0, 0, 0, 1]); + + self.iv.set(new_nonce); + Ok(()) + } + + fn crypt( + &self, + buf: &'static mut [u8], + aad_offset: usize, + message_offset: usize, + message_len: usize, + encrypting: bool, + ) -> Result<(), (ErrorCode, &'static mut [u8])> { + if self.state.get() != GCMState::Idle { + return Err((ErrorCode::BUSY, buf)); + } + + let _ = self + .crypt_r(buf, aad_offset, message_offset, message_len, encrypting) + .map_err(|(ecode, _)| { + self.buf.take().map(|buf| { + self.gcm_client.map(move |client| { + client.crypt_done(buf, Err(ecode), false); + }); + }); + }); + + Ok(()) + } +} + +impl<'a, A: AES128<'a> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'a>> + symmetric_encryption::AES128<'a> for Aes128Gcm<'a, A> +{ + fn enable(&self) { + self.aes.enable(); + } + + fn disable(&self) { + self.aes.disable(); + } + + fn set_client(&'a self, client: &'a dyn symmetric_encryption::Client<'a>) { + self.client.set(client); + } + + fn set_key(&self, key: &[u8]) -> Result<(), ErrorCode> { + AES128::set_key(self.aes, key) + } + + fn set_iv(&self, iv: &[u8]) -> Result<(), ErrorCode> { + self.aes.set_iv(iv) + } + + fn start_message(&self) { + self.aes.start_message() + } + + fn crypt( + &self, + source: Option<&'static mut [u8]>, + dest: &'static mut [u8], + start_index: usize, + stop_index: usize, + ) -> Option<( + Result<(), ErrorCode>, + Option<&'static mut [u8]>, + &'static mut [u8], + )> { + AES128::crypt(self.aes, source, dest, start_index, stop_index) + } +} + +impl<'a, A: AES128<'a> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'a> + AES128CCM<'a>> + symmetric_encryption::AES128CCM<'a> for Aes128Gcm<'a, A> +{ + fn set_client(&'a self, client: &'a dyn symmetric_encryption::CCMClient) { + self.ccm_client.set(client); + } + + fn set_key(&self, key: &[u8]) -> Result<(), ErrorCode> { + AES128CCM::set_key(self.aes, key) + } + + fn set_nonce(&self, nonce: &[u8]) -> Result<(), ErrorCode> { + self.aes.set_nonce(nonce) + } + + fn crypt( + &self, + buf: &'static mut [u8], + a_off: usize, + m_off: usize, + m_len: usize, + mic_len: usize, + confidential: bool, + encrypting: bool, + ) -> Result<(), (ErrorCode, &'static mut [u8])> { + AES128CCM::crypt( + self.aes, + buf, + a_off, + m_off, + m_len, + mic_len, + confidential, + encrypting, + ) + } +} + +impl<'a, A: AES128<'a> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'a>> AES128Ctr + for Aes128Gcm<'a, A> +{ + fn set_mode_aes128ctr(&self, encrypting: bool) -> Result<(), ErrorCode> { + self.aes.set_mode_aes128ctr(encrypting) + } +} + +impl<'a, A: AES128<'a> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'a>> AES128ECB + for Aes128Gcm<'a, A> +{ + fn set_mode_aes128ecb(&self, encrypting: bool) -> Result<(), ErrorCode> { + self.aes.set_mode_aes128ecb(encrypting) + } +} + +impl<'a, A: AES128<'a> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'a>> AES128CBC + for Aes128Gcm<'a, A> +{ + fn set_mode_aes128cbc(&self, encrypting: bool) -> Result<(), ErrorCode> { + self.aes.set_mode_aes128cbc(encrypting) + } +} + +impl<'a, A: AES128<'a> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'a>> + symmetric_encryption::Client<'a> for Aes128Gcm<'a, A> +{ + fn crypt_done(&self, _: Option<&'static mut [u8]>, crypt_buf: &'static mut [u8]) { + match self.state.get() { + GCMState::Idle => unreachable!(), + GCMState::GenerateHashKey => { + let (aad_offset, message_offset, message_len) = self.pos.get(); + + let mut mac = GHash::new(Key::from_slice(&crypt_buf[0..AES128_BLOCK_SIZE])); + let buf = self.buf.take().unwrap(); + + if self.encrypting.get() { + mac.update_padded(&buf[aad_offset..message_offset]); + + crypt_buf[AES128_BLOCK_SIZE..(AES128_BLOCK_SIZE + message_len)] + .copy_from_slice(&buf[message_offset..(message_offset + message_len)]); + for i in 0..AES128_BLOCK_SIZE { + crypt_buf[i] = 0; + } + + self.mac.replace(mac); + } else { + let copy_offset = (message_offset / AES128_BLOCK_SIZE) * AES128_BLOCK_SIZE; + mac.update_padded(&buf[aad_offset..message_offset]); + mac.update_padded(&buf[message_offset..(message_offset + message_len)]); + + let associated_data_bits = ((message_offset - aad_offset) as u64) * 8; + let buffer_bits = (message_len as u64) * 8; + + let mut block = ghash::Block::default(); + block[..8].copy_from_slice(&associated_data_bits.to_be_bytes()); + block[8..].copy_from_slice(&buffer_bits.to_be_bytes()); + mac.update(&block); + + let mut tag = mac.finalize().into_bytes(); + + for i in 0..AES128_BLOCK_SIZE { + tag[i] ^= crypt_buf[copy_offset + i]; + } + + buf[0..AES128_BLOCK_SIZE].copy_from_slice(&tag); + } + self.crypt_buf.replace(crypt_buf); + self.buf.replace(buf); + + self.start_ctr_encrypt().unwrap(); + } + GCMState::CtrEncrypt => { + let buf = self.buf.take().unwrap(); + let (aad_offset, message_offset, message_len) = self.pos.get(); + let tag_offset = (message_offset / AES128_BLOCK_SIZE) * AES128_BLOCK_SIZE; + let copy_offset = (message_offset / AES128_BLOCK_SIZE).max(1) * AES128_BLOCK_SIZE; + + if self.encrypting.get() { + // Check the mac + let mut mac = self.mac.take().unwrap(); + mac.update_padded( + &crypt_buf[(message_offset + AES128_BLOCK_SIZE) + ..(message_offset + message_len + AES128_BLOCK_SIZE)], + ); + + buf[0..message_len] + .copy_from_slice(&crypt_buf[copy_offset..(copy_offset + message_len)]); + + let associated_data_bits = ((message_offset - aad_offset) as u64) * 8; + let buffer_bits = (message_len as u64) * 8; + + let mut block = ghash::Block::default(); + block[..8].copy_from_slice(&associated_data_bits.to_be_bytes()); + block[8..].copy_from_slice(&buffer_bits.to_be_bytes()); + mac.update(&block); + + let mut tag = mac.finalize().into_bytes(); + + for i in 0..AES128_BLOCK_SIZE { + tag[i] ^= crypt_buf[tag_offset + i]; + } + + buf[(message_offset + message_len) + ..(message_offset + message_len + AES128_BLOCK_SIZE)] + .copy_from_slice(&tag); + } else { + buf[0..message_len] + .copy_from_slice(&crypt_buf[copy_offset..(copy_offset + message_len)]); + } + + self.aes.disable(); + self.crypt_buf.replace(crypt_buf); + self.state.set(GCMState::Idle); + self.gcm_client.map(move |client| { + client.crypt_done(buf, Ok(()), true); + }); + } + } + } +} diff --git a/capsules/aes_gcm/src/lib.rs b/capsules/aes_gcm/src/lib.rs new file mode 100644 index 0000000000..04f922bfea --- /dev/null +++ b/capsules/aes_gcm/src/lib.rs @@ -0,0 +1,8 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Western Digital 2023. + +#![forbid(unsafe_code)] +#![no_std] + +pub mod aes_gcm; diff --git a/capsules/core/Cargo.toml b/capsules/core/Cargo.toml new file mode 100644 index 0000000000..38a24003ba --- /dev/null +++ b/capsules/core/Cargo.toml @@ -0,0 +1,17 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +[package] +name = "capsules-core" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[dependencies] +kernel = { path = "../../kernel" } +enum_primitive = { path = "../../libraries/enum_primitive" } +tickv = { path = "../../libraries/tickv" } + +[lints] +workspace = true diff --git a/capsules/core/README.md b/capsules/core/README.md new file mode 100644 index 0000000000..a632da6954 --- /dev/null +++ b/capsules/core/README.md @@ -0,0 +1,84 @@ +Core Tock Capsules +================== + +This crate contains capsules which are required for most (if not all) +Tock-based systems to operate. For instance, these capsules implement +basic infrastructure for interacting with timer or alarm hardware, +exposing UART hardware as console ports, etc. + +It further contains virtualizers, which enable a given single +peripheral to be used by multiple clients. Virtualizers are agnostic +over their underlying peripherals; they do not implement logic +specific to any given peripheral device. + +For more information on capsules, see [the top-level README](../README.md). + +The remainder of this document contains a list of capsules in this crate, along +with a short description. + +MCU Peripherals for Userspace +----------------------------- + +These capsules provide a `Driver` interface for common MCU peripherals. + +- **[ADC](src/adc.rs)**: Individual and continuous samples. +- **[Alarm](src/alarm.rs)**: Oneshot and periodic timers. +- **[GPIO](src/gpio.rs)**: GPIO configuring and control. +- **[I2C_MASTER](src/i2c_master.rs)**: I2C master access only. +- **[I2C_MASTER_SLAVE](src/i2c_master_slave_combo.rs)**: I2C master and slave + access. +- **[I2C_MASTER_SLAVE Driver](src/i2c_master_slave_driver.rs)**: I2C master and + slave userspace access. +- **[RNG](src/rng.rs)**: Random number generation. +- **[SPI Controller](src/spi_controller.rs)**: SPI controller device (SPI + master) +- **[SPI Peripheral](src/spi_peripheral.rs)**: SPI peripheral device (SPI slave) + +Helpful Userspace Capsules +-------------------------- + +These provide common and better abstractions for userspace. + +- **[Button](src/button.rs)**: Detect button presses. +- **[Console](src/console.rs)**: UART console support. +- **[Console Ordered](src/console_ordered.rs)**: UART console ordered with + kernel `debug!()` prints. +- **[LED](src/led.rs)**: Turn on and off LEDs. + +Debugging Capsules +------------------ + +These are selectively included on a board to help with testing and debugging +various elements of Tock. + +- **[Low-Level Debug](src/low_level_debug)**: Provides system calls for + low-level debugging tasks, such as debugging toolchain and relocation issues. +- **[Process Console](src/process_console.rs)**: Provide a UART console to + inspect the status of process and stop/start them. + +Virtualized Hardware Resources +------------------------------ + +These allow for multiple users of shared hardware resources in the kernel. + +- **[Virtual ADC](src/virtualizers/virtual_adc.rs)**: Shared single ADC channel. +- **[Virtual AES-CCM](src/virtualizers/virtual_aes_ccm.rs)**: Shared AES-CCM engine. +- **[Virtual Alarm](src/virtualizers/virtual_alarm.rs)**: Shared alarm resource. +- **[Virtual Flash](src/virtualizers/virtual_flash.rs)**: Shared flash resource. +- **[Virtual I2C](src/virtualizers/virtual_i2c.rs)**: Shared I2C and fixed addresses. +- **[Virtual PWM](src/virtualizers/virtual_pwm.rs)**: Shared PWM hardware. +- **[Virtual RNG](src/virtualizers/virtual_rng.rs)**: Shared random number generator. +- **[Virtual SPI](src/virtualizers/virtual_spi.rs)**: Shared SPI and fixed chip select pins. +- **[Virtual Timer](src/virtualizers/virtual_timer.rs)**: Shared timer. +- **[Virtual UART](src/virtualizers/virtual_uart.rs)**: Shared UART bus. + +Miscallenous Capsules & Infrastructure +-------------------------------------- + +These modules implement miscallenous functionality & infrastructure required by +other capsule crates or the wider Tock ecosystem. + +- **[Driver Number Assignments](src/driver.rs)**: Global driver number + assignments for userspace drivers. +- **[Stream](src/stream.rs)**: Macro-infrastructure for encoding and decoding + byte-streams. Originally developed as part of the IEEE802.15.4 network stack. diff --git a/capsules/examples/traitobj_list.rs b/capsules/core/examples/traitobj_list.rs similarity index 93% rename from capsules/examples/traitobj_list.rs rename to capsules/core/examples/traitobj_list.rs index 9792a01069..3b21a51618 100644 --- a/capsules/examples/traitobj_list.rs +++ b/capsules/core/examples/traitobj_list.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! This example capsule illustrates how to create a `List` //! of trait objects //! diff --git a/capsules/src/adc.rs b/capsules/core/src/adc.rs similarity index 89% rename from capsules/src/adc.rs rename to capsules/core/src/adc.rs index 229169e2b1..446f3d3cf8 100644 --- a/capsules/src/adc.rs +++ b/capsules/core/src/adc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Syscall driver capsules for ADC sampling. //! //! This module has two ADC syscall driver capsule implementations. @@ -14,14 +18,14 @@ //! This capsule shares the ADC with the rest of the kernel through this //! virtualizer, so allows other kernel services and capsules to use the //! ADC. It also supports multiple processes requesting ADC samples -//! concurently. However, it only supports processes requesting single +//! concurrently. However, it only supports processes requesting single //! ADC samples: they cannot sample continuously or at high speed. //! //! //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! let adc_channels = static_init!( @@ -36,13 +40,13 @@ //! ] //! ); //! let adc = static_init!( -//! capsules::adc::AdcDedicated<'static, sam4l::adc::Adc>, -//! capsules::adc::AdcDedicated::new( +//! capsules_core::adc::AdcDedicated<'static, sam4l::adc::Adc>, +//! capsules_core::adc::AdcDedicated::new( //! &mut sam4l::adc::ADC0, //! adc_channels, -//! &mut capsules::adc::ADC_BUFFER1, -//! &mut capsules::adc::ADC_BUFFER2, -//! &mut capsules::adc::ADC_BUFFER3 +//! &mut capsules_core::adc::ADC_BUFFER1, +//! &mut capsules_core::adc::ADC_BUFFER2, +//! &mut capsules_core::adc::ADC_BUFFER3 //! ) //! ); //! sam4l::adc::ADC0.set_client(adc); @@ -50,7 +54,6 @@ use core::cell::Cell; use core::cmp; -use core::convert::TryFrom; use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; use kernel::hil; @@ -61,26 +64,26 @@ use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. use crate::driver; -use crate::virtual_adc::Operation; +use crate::virtualizers::virtual_adc::Operation; pub const DRIVER_NUM: usize = driver::NUM::Adc as usize; /// Multiplexed ADC syscall driver, used by applications and capsules. /// Virtualized, and can be use by multiple applications at the same time; /// requests are queued. Does not support continuous or high-speed sampling. pub struct AdcVirtualized<'a> { - drivers: &'a [&'a dyn hil::adc::AdcChannel], + drivers: &'a [&'a dyn hil::adc::AdcChannel<'a>], apps: Grant, AllowRoCount<0>, AllowRwCount<0>>, - current_app: OptionalCell, + current_process: OptionalCell, } /// ADC syscall driver, used by applications to interact with ADC. /// Not currently virtualized: does not share the ADC with other capsules /// and only one application can use it at a time. Supports continuous and /// high speed sampling. -pub struct AdcDedicated<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> { +pub struct AdcDedicated<'a, A: hil::adc::Adc<'a> + hil::adc::AdcHighSpeed<'a>> { // ADC driver adc: &'a A, - channels: &'a [&'a
::Channel], + channels: &'a [>::Channel], // ADC state active: Cell, @@ -88,7 +91,7 @@ pub struct AdcDedicated<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> { // App state apps: Grant, AllowRoCount<0>, AllowRwCount<2>>, - appid: OptionalCell, + processid: OptionalCell, channel: Cell, // ADC buffers @@ -149,11 +152,9 @@ impl Default for AppSys { /// The size is chosen somewhat arbitrarily, but has been tested. At 175000 Hz, /// buffers need to be swapped every 70 us and copied over before the next /// swap. In testing, it seems to keep up fine. -pub static mut ADC_BUFFER1: [u16; 128] = [0; 128]; -pub static mut ADC_BUFFER2: [u16; 128] = [0; 128]; -pub static mut ADC_BUFFER3: [u16; 128] = [0; 128]; +pub const BUF_LEN: usize = 128; -impl<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> AdcDedicated<'a, A> { +impl<'a, A: hil::adc::Adc<'a> + hil::adc::AdcHighSpeed<'a>> AdcDedicated<'a, A> { /// Create a new `Adc` application interface. /// /// - `adc` - ADC driver to provide application access to @@ -163,15 +164,15 @@ impl<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> AdcDedicated<'a, A> { pub fn new( adc: &'a A, grant: Grant, AllowRoCount<0>, AllowRwCount<2>>, - channels: &'a [&'a ::Channel], + channels: &'a [>::Channel], adc_buf1: &'static mut [u16; 128], adc_buf2: &'static mut [u16; 128], adc_buf3: &'static mut [u16; 128], ) -> AdcDedicated<'a, A> { AdcDedicated { // ADC driver - adc: adc, - channels: channels, + adc, + channels, // ADC state active: Cell::new(false), @@ -179,7 +180,7 @@ impl<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> AdcDedicated<'a, A> { // App state apps: grant, - appid: OptionalCell::empty(), + processid: OptionalCell::empty(), channel: Cell::new(0), // ADC buffers @@ -257,7 +258,7 @@ impl<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> AdcDedicated<'a, A> { if channel >= self.channels.len() { return Err(ErrorCode::INVAL); } - let chan = self.channels[channel]; + let chan = &self.channels[channel]; // save state for callback self.active.set(true); @@ -291,7 +292,7 @@ impl<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> AdcDedicated<'a, A> { if channel >= self.channels.len() { return Err(ErrorCode::INVAL); } - let chan = self.channels[channel]; + let chan = &self.channels[channel]; // save state for callback self.active.set(true); @@ -328,13 +329,13 @@ impl<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> AdcDedicated<'a, A> { if channel >= self.channels.len() { return Err(ErrorCode::INVAL); } - let chan = self.channels[channel]; + let chan = &self.channels[channel]; // cannot sample a buffer without a buffer to sample into let mut app_buf_length = 0; - let exists = self.appid.map_or(false, |id| { + let exists = self.processid.map_or(false, |id| { self.apps - .enter(*id, |_, kernel_data| { + .enter(id, |_, kernel_data| { app_buf_length = kernel_data .get_readwrite_processbuffer(0) .map(|b| b.len()) @@ -345,7 +346,7 @@ impl<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> AdcDedicated<'a, A> { if err == kernel::process::Error::NoSuchApp || err == kernel::process::Error::InactiveApp { - self.appid.clear(); + self.processid.clear(); } }) .unwrap_or(false) @@ -357,9 +358,9 @@ impl<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> AdcDedicated<'a, A> { // save state for callback self.active.set(true); self.mode.set(AdcMode::SingleBuffer); - let ret = self.appid.map_or(Err(ErrorCode::NOMEM), |id| { + let ret = self.processid.map_or(Err(ErrorCode::NOMEM), |id| { self.apps - .enter(*id, |app, _| { + .enter(id, |app, _| { app.app_buf_offset.set(0); self.channel.set(channel); // start a continuous sample @@ -405,7 +406,7 @@ impl<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> AdcDedicated<'a, A> { if err == kernel::process::Error::NoSuchApp || err == kernel::process::Error::InactiveApp { - self.appid.clear(); + self.processid.clear(); } }) .unwrap_or(Err(ErrorCode::NOMEM)) @@ -414,9 +415,9 @@ impl<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> AdcDedicated<'a, A> { // failure, clear state self.active.set(false); self.mode.set(AdcMode::NoMode); - self.appid.map(|id| { + self.processid.map(|id| { self.apps - .enter(*id, |app, _| { + .enter(id, |app, _| { app.samples_remaining.set(0); app.samples_outstanding.set(0); }) @@ -424,7 +425,7 @@ impl<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> AdcDedicated<'a, A> { if err == kernel::process::Error::NoSuchApp || err == kernel::process::Error::InactiveApp { - self.appid.clear(); + self.processid.clear(); } }) }); @@ -450,14 +451,14 @@ impl<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> AdcDedicated<'a, A> { if channel >= self.channels.len() { return Err(ErrorCode::INVAL); } - let chan = self.channels[channel]; + let chan = &self.channels[channel]; // cannot continuously sample without two buffers let mut app_buf_length = 0; let mut next_app_buf_length = 0; - let exists = self.appid.map_or(false, |id| { + let exists = self.processid.map_or(false, |id| { self.apps - .enter(*id, |_, kernel_data| { + .enter(id, |_, kernel_data| { app_buf_length = kernel_data .get_readwrite_processbuffer(0) .map(|b| b.len()) @@ -472,7 +473,7 @@ impl<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> AdcDedicated<'a, A> { if err == kernel::process::Error::NoSuchApp || err == kernel::process::Error::InactiveApp { - self.appid.clear(); + self.processid.clear(); } }) .unwrap_or(false) @@ -485,9 +486,9 @@ impl<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> AdcDedicated<'a, A> { self.active.set(true); self.mode.set(AdcMode::ContinuousBuffer); - let ret = self.appid.map_or(Err(ErrorCode::NOMEM), |id| { + let ret = self.processid.map_or(Err(ErrorCode::NOMEM), |id| { self.apps - .enter(*id, |app, _| { + .enter(id, |app, _| { app.app_buf_offset.set(0); self.channel.set(channel); // start a continuous sample @@ -546,7 +547,7 @@ impl<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> AdcDedicated<'a, A> { if err == kernel::process::Error::NoSuchApp || err == kernel::process::Error::InactiveApp { - self.appid.clear(); + self.processid.clear(); } }) .unwrap_or(Err(ErrorCode::NOMEM)) @@ -555,9 +556,9 @@ impl<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> AdcDedicated<'a, A> { // failure, clear state self.active.set(false); self.mode.set(AdcMode::NoMode); - self.appid.map(|id| { + self.processid.map(|id| { self.apps - .enter(*id, |app, _| { + .enter(id, |app, _| { app.samples_remaining.set(0); app.samples_outstanding.set(0); }) @@ -565,7 +566,7 @@ impl<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> AdcDedicated<'a, A> { if err == kernel::process::Error::NoSuchApp || err == kernel::process::Error::InactiveApp { - self.appid.clear(); + self.processid.clear(); } }) }); @@ -584,9 +585,9 @@ impl<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> AdcDedicated<'a, A> { } // clean up state - self.appid.map_or(Err(ErrorCode::FAIL), |id| { + self.processid.map_or(Err(ErrorCode::FAIL), |id| { self.apps - .enter(*id, |app, _| { + .enter(id, |app, _| { self.active.set(false); self.mode.set(AdcMode::NoMode); app.app_buf_offset.set(0); @@ -615,7 +616,7 @@ impl<'a, A: hil::adc::Adc + hil::adc::AdcHighSpeed> AdcDedicated<'a, A> { if err == kernel::process::Error::NoSuchApp || err == kernel::process::Error::InactiveApp { - self.appid.clear(); + self.processid.clear(); } }) .unwrap_or(Err(ErrorCode::FAIL)) @@ -637,13 +638,13 @@ impl<'a> AdcVirtualized<'a> { /// /// - `drivers` - Virtual ADC drivers to provide application access to pub fn new( - drivers: &'a [&'a dyn hil::adc::AdcChannel], + drivers: &'a [&'a dyn hil::adc::AdcChannel<'a>], grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, ) -> AdcVirtualized<'a> { AdcVirtualized { - drivers: drivers, + drivers, apps: grant, - current_app: OptionalCell::empty(), + current_process: OptionalCell::empty(), } } @@ -652,20 +653,22 @@ impl<'a> AdcVirtualized<'a> { &self, command: Operation, channel: usize, - appid: ProcessId, + processid: ProcessId, ) -> Result<(), ErrorCode> { if channel < self.drivers.len() { - self.apps - .enter(appid, |app, _| { - if self.current_app.is_none() { - self.current_app.set(appid); - let value = self.call_driver(command, channel); - if value != Ok(()) { - self.current_app.clear(); - } - value - } else { - if app.pending_command == true { + if self.current_process.is_none() { + self.current_process.set(processid); + let r = self.call_driver(command, channel); + if r != Ok(()) { + self.current_process.clear(); + } + self.run_next_command(); + Ok(()) + } else { + match self + .apps + .enter(processid, |app, _| { + if app.pending_command { Err(ErrorCode::BUSY) } else { app.pending_command = true; @@ -673,14 +676,50 @@ impl<'a> AdcVirtualized<'a> { app.channel = channel; Ok(()) } - } - }) - .unwrap_or_else(|err| err.into()) + }) + .map_err(ErrorCode::from) + { + Err(e) => Err(e), + Ok(_) => Ok(()), + } + } } else { Err(ErrorCode::NODEVICE) } } + /// Run next command in queue, when available + fn run_next_command(&self) { + let mut command = Operation::OneSample; + let mut channel = 0; + for app in self.apps.iter() { + let processid = app.processid(); + let start_command = app.enter(|app, _| { + if app.pending_command { + app.pending_command = false; + app.command.take().map(|c| { + command = c; + }); + channel = app.channel; + self.current_process.set(processid); + true + } else { + false + } + }); + if start_command { + match self.call_driver(command, channel) { + Err(_) => { + self.current_process.clear(); + } + Ok(()) => { + break; + } + } + } + } + } + /// Request the sample from the specified channel fn call_driver(&self, command: Operation, channel: usize) -> Result<(), ErrorCode> { match command { @@ -690,7 +729,9 @@ impl<'a> AdcVirtualized<'a> { } /// Callbacks from the ADC driver -impl hil::adc::Client for AdcDedicated<'_, A> { +impl<'a, A: hil::adc::Adc<'a> + hil::adc::AdcHighSpeed<'a>> hil::adc::Client + for AdcDedicated<'a, A> +{ /// Single sample operation complete. /// /// Collects the sample and provides a callback to the application. @@ -705,9 +746,9 @@ impl hil::adc::Client for AdcDedicate // perform callback - self.appid.map(|id| { + self.processid.map(|id| { self.apps - .enter(*id, |_app, upcalls| { + .enter(id, |_app, upcalls| { calledback = true; upcalls .schedule_upcall( @@ -724,7 +765,7 @@ impl hil::adc::Client for AdcDedicate if err == kernel::process::Error::NoSuchApp || err == kernel::process::Error::InactiveApp { - self.appid.clear(); + self.processid.clear(); } }) }); @@ -732,9 +773,9 @@ impl hil::adc::Client for AdcDedicate // sample ready in continuous sampling operation, keep state // perform callback - self.appid.map(|id| { + self.processid.map(|id| { self.apps - .enter(*id, |_app, upcalls| { + .enter(id, |_app, upcalls| { calledback = true; upcalls .schedule_upcall( @@ -751,7 +792,7 @@ impl hil::adc::Client for AdcDedicate if err == kernel::process::Error::NoSuchApp || err == kernel::process::Error::InactiveApp { - self.appid.clear(); + self.processid.clear(); } }) }); @@ -770,7 +811,9 @@ impl hil::adc::Client for AdcDedicate } /// Callbacks from the High Speed ADC driver -impl hil::adc::HighSpeedClient for AdcDedicated<'_, A> { +impl<'a, A: hil::adc::Adc<'a> + hil::adc::AdcHighSpeed<'a>> hil::adc::HighSpeedClient + for AdcDedicated<'a, A> +{ /// Internal buffer has filled from a buffered sampling operation. /// Copies data over to application buffer, determines if more data is /// needed, and performs a callback to the application if ready. If @@ -795,9 +838,9 @@ impl hil::adc::HighSpeedClient for Ad || self.mode.get() == AdcMode::ContinuousBuffer) { // we did expect a buffer. Determine the current application state - self.appid.map(|id| { + self.processid.map(|id| { self.apps - .enter(*id, |app, kernel_data| { + .enter(id, |app, kernel_data| { // Get both buffers, this shouldn't ever fail since the grant was created // with enough space. The buffer still may be empty though let app_buf0 = match kernel_data.get_readwrite_processbuffer(0) { @@ -965,12 +1008,7 @@ impl hil::adc::HighSpeedClient for Ad let skip_amt = app.app_buf_offset.get() / 2; { - let app_buf; - if use0 { - app_buf = &app_buf0; - } else { - app_buf = &app_buf1; - } + let app_buf = if use0 { &app_buf0 } else { &app_buf1 }; // next we should copy bytes to the app buffer let _ = app_buf.mut_enter(|app_buf| { @@ -998,7 +1036,7 @@ impl hil::adc::HighSpeedClient for Ad let mut val = sample; for byte in chunk.iter() { byte.set((val & 0xFF) as u8); - val = val >> 8; + val >>= 8; } } }); @@ -1056,7 +1094,7 @@ impl hil::adc::HighSpeedClient for Ad if err == kernel::process::Error::NoSuchApp || err == kernel::process::Error::InactiveApp { - self.appid.clear(); + self.processid.clear(); unexpected_state = true; } }) @@ -1070,16 +1108,16 @@ impl hil::adc::HighSpeedClient for Ad // state is consistent. No callback. self.active.set(false); self.mode.set(AdcMode::NoMode); - self.appid.map(|id| { + self.processid.map(|id| { self.apps - .enter(*id, |app, _| { + .enter(id, |app, _| { app.app_buf_offset.set(0); }) .map_err(|err| { if err == kernel::process::Error::NoSuchApp || err == kernel::process::Error::InactiveApp { - self.appid.clear(); + self.processid.clear(); } }) }); @@ -1102,23 +1140,23 @@ impl hil::adc::HighSpeedClient for Ad } /// Implementations of application syscalls -impl SyscallDriver for AdcDedicated<'_, A> { +impl<'a, A: hil::adc::Adc<'a> + hil::adc::AdcHighSpeed<'a>> SyscallDriver for AdcDedicated<'a, A> { /// Method for the application to command or query this driver. /// /// - `command_num` - which command call this is /// - `data` - value sent by the application, varying uses - /// - `_appid` - application identifier, unused + /// - `_processid` - application identifier, unused fn command( &self, command_num: usize, channel: usize, frequency: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { // Return true if this app already owns the ADC capsule, if no app owns // the ADC capsule, or if the app that is marked as owning the ADC // capsule no longer exists. - let match_or_empty_or_nonexistant = self.appid.map_or(true, |owning_app| { + let match_or_empty_or_nonexistant = self.processid.map_or(true, |owning_app| { // We have recorded that an app has ownership of the ADC. // If the ADC is still active, then we need to wait for the operation @@ -1127,7 +1165,7 @@ impl SyscallDriver for AdcDedicated<' // we need to verify that that application still exists, and remove // it as owner if not. if self.active.get() { - owning_app == &appid + owning_app == processid } else { // Check the app still exists. // @@ -1137,17 +1175,20 @@ impl SyscallDriver for AdcDedicated<' // longer exists and we return `true` to signify the // "or_nonexistant" case. self.apps - .enter(*owning_app, |_, _| owning_app == &appid) + .enter(owning_app, |_, _| owning_app == processid) .unwrap_or(true) } }); if match_or_empty_or_nonexistant { - self.appid.set(appid); + self.processid.set(processid); } else { return CommandReturn::failure(ErrorCode::NOMEM); } match command_num { - // check if present + // Driver existence check + // TODO(Tock 3.0): TRD104 specifies that Command 0 should return Success, not SuccessU32, + // but this driver is unchanged since it has been stabilized. It will be brought into + // compliance as part of the next major release of Tock. See #3375. 0 => CommandReturn::success_u32(self.channels.len() as u32), // Single sample on channel @@ -1228,13 +1269,13 @@ impl SyscallDriver for AdcVirtualized<'_> { /// - `command_num` - which command call this is /// - `channel` - requested channel value /// - `_` - value sent by the application, unused - /// - `appid` - application identifier + /// - `processid` - application identifier fn command( &self, command_num: usize, channel: usize, _: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { match command_num { // This driver exists and return the number of channels @@ -1242,13 +1283,13 @@ impl SyscallDriver for AdcVirtualized<'_> { // Single sample. 1 => { - let res = self.enqueue_command(Operation::OneSample, channel, appid); + let res = self.enqueue_command(Operation::OneSample, channel, processid); if res == Ok(()) { CommandReturn::success() } else { match ErrorCode::try_from(res) { Ok(error) => CommandReturn::failure(error), - _ => panic!("ADC Syscall: invalid errior from enqueue_command"), + _ => panic!("ADC Syscall: invalid error from enqueue_command"), } } } @@ -1286,8 +1327,8 @@ impl SyscallDriver for AdcVirtualized<'_> { impl<'a> hil::adc::Client for AdcVirtualized<'a> { fn sample_ready(&self, sample: u16) { - self.current_app.take().map(|appid| { - let _ = self.apps.enter(appid, |app, upcalls| { + self.current_process.take().map(|processid| { + let _ = self.apps.enter(processid, |app, upcalls| { app.pending_command = false; let channel = app.channel; upcalls @@ -1298,5 +1339,6 @@ impl<'a> hil::adc::Client for AdcVirtualized<'a> { .ok(); }); }); + self.run_next_command(); } } diff --git a/capsules/core/src/alarm.rs b/capsules/core/src/alarm.rs new file mode 100644 index 0000000000..cbab6e5234 --- /dev/null +++ b/capsules/core/src/alarm.rs @@ -0,0 +1,1070 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Tock syscall driver capsule for Alarms, which issue callbacks when +//! a point in time has been reached. + +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::hil::time::{self, Alarm, Ticks}; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::{ErrorCode, ProcessId}; + +/// Syscall driver number. +use crate::driver; +pub const DRIVER_NUM: usize = driver::NUM::Alarm as usize; + +#[derive(Copy, Clone, Debug)] +struct Expiration { + reference: T, + dt: T, +} + +#[derive(Copy, Clone)] +pub struct AlarmData { + expiration: Option>, +} + +const ALARM_CALLBACK_NUM: usize = 0; +const NUM_UPCALLS: u8 = 1; + +impl Default for AlarmData { + fn default() -> AlarmData { + AlarmData { expiration: None } + } +} + +pub struct AlarmDriver<'a, A: Alarm<'a>> { + alarm: &'a A, + app_alarms: + Grant, UpcallCount, AllowRoCount<0>, AllowRwCount<0>>, +} + +impl<'a, A: Alarm<'a>> AlarmDriver<'a, A> { + pub const fn new( + alarm: &'a A, + grant: Grant< + AlarmData, + UpcallCount, + AllowRoCount<0>, + AllowRwCount<0>, + >, + ) -> AlarmDriver<'a, A> { + AlarmDriver { + alarm, + app_alarms: grant, + } + } + + /// Find the earliest [`Expiration`] from an iterator of expirations. + /// + /// Each [`Expiration`] value is provided as a tuple, with + /// - `UD`: an additional user-data argument returned together with the + /// [`Expiration`], should it be the earliest, and + /// - `F`: a call-back function invoked when the [`Expiration`] has already + /// expired. The callback is porivded the expiration value and a reference + /// to its user-data. + /// + /// Whether an [`Expiration`] has expired or not is determined with respect + /// to `now`. If `now` is in `[exp.reference; exp.reference + exp.dt)`, it + /// the [`Expiration`] has not yet expired. + /// + /// An expired [`Expiration`] is not a candidate for "earliest" expiration. + /// This means that this function will return `Ok(None)` if it receives an + /// empty iterator, or all [`Expiration`]s have expired. + /// + /// To stop iteration on any expired [`Expiration`], its callback can return + /// `Some(R)`. Then this function will return `Err(Expiration, UD, R)`. + /// This avoids consuming the entire iterator. + fn earliest_alarm, &UD) -> Option>( + now: A::Ticks, + expirations: impl Iterator, UD, F)>, + ) -> Result, UD)>, (Expiration, UD, R)> { + let mut earliest: Option<(Expiration, UD)> = None; + + for (exp, ud, expired_handler) in expirations { + let Expiration { + reference: exp_ref, + dt: exp_dt, + } = exp; + + // Pre-compute the absolute "end" time of this expiration (the + // point at which it should fire): + let exp_end = exp_ref.wrapping_add(exp_dt); + + // If `now` is not within `[reference, reference + dt)`, this + // alarm has expired. Call the expired handler. If it returns + // false, stop here. + if !now.within_range(exp_ref, exp_end) { + let expired_handler_res = expired_handler(exp, &ud); + if let Some(retval) = expired_handler_res { + return Err((exp, ud, retval)); + } + } + + // `exp` has not yet expired. At this point we can assume that + // `now` is within `[exp_ref, exp_end)`. Check whether it will + // expire earlier than the current `earliest`: + match &earliest { + None => { + // Do not have an earliest expiration yet, set this + // expriation as earliest: + earliest = Some((exp, ud)); + } + + Some(( + Expiration { + reference: earliest_ref, + dt: earliest_dt, + }, + _, + )) => { + // As `now` is within `[ref, end)` for both timers, we + // can check which end time is closer to `now`. We thus + // first compute the end time for the current earliest + // alarm as well ... + let earliest_end = earliest_ref.wrapping_add(*earliest_dt); + + // ... and then perform a wrapping_sub against `now` for + // both, checking which is smaller: + let exp_remain = exp_end.wrapping_sub(now); + let earliest_remain = earliest_end.wrapping_sub(now); + if exp_remain < earliest_remain { + // Our current exp expires earlier than earliest, + // replace it: + earliest = Some((exp, ud)); + } + } + } + } + + // We have computed earliest by iterating over all alarms, but have not + // found one that has already expired. As such return `false`: + Ok(earliest) + } + + /// Re-arm the timer. This must be called in response to the underlying + /// timer firing, or the set of [`Expiration`]s changing. This will iterate + /// over all [`Expiration`]s and + /// + /// - invoke upcalls for all expired app alarms, resetting them afterwards, + /// - re-arming the alarm for the next earliest [`Expiration`], or + /// - disarming the alarm if no unexpired [`Expiration`] is found. + fn process_rearm_or_callback(&self) { + // Ask the clock about a current reference once. This can incur a + // volatile read, and this may not be optimized if done in a loop: + let now = self.alarm.now(); + + let expired_handler = |expired: Expiration, process_id: &ProcessId| { + // This closure is run on every expired alarm, _after_ the `enter()` + // closure on the Grant iterator has returned. We are thus not + // risking reentrancy here. + + // Enter the app's grant again: + let _ = self.app_alarms.enter(*process_id, |alarm_state, upcalls| { + // Reset this app's alarm: + alarm_state.expiration = None; + + // Deliver the upcall: + upcalls + .schedule_upcall( + ALARM_CALLBACK_NUM, + ( + now.into_u32_left_justified() as usize, + expired.reference.wrapping_add(expired.dt).into_usize(), + 0, + ), + ) + .ok(); + }); + + // Proceed iteration across expirations: + None::<()> + }; + + // Compute the earliest alarm, and invoke the `expired_handler` for + // every expired alarm. This will issue a callback and reset the alarms + // respectively. + let res = Self::earliest_alarm( + now, + // Pass an interator of all non-None expirations: + self.app_alarms.iter().filter_map(|app| { + let process_id = app.processid(); + app.enter(|alarm_state, _upcalls| { + if let Some(exp) = alarm_state.expiration { + Some((exp, process_id, expired_handler)) + } else { + None + } + }) + }), + ); + + // Arm or disarm the alarm accordingly: + match res { + // No pending alarm, disarm: + Ok(None) => { + let _ = self.alarm.disarm(); + } + + // A future, non-expired alarm should fire: + Ok(Some((Expiration { reference, dt }, _))) => { + self.alarm.set_alarm(reference, dt); + } + + // The expired closure has requested to stop iteration. This should + // be unreachable, and hence we panic: + Err((_, _, ())) => { + unreachable!(); + } + } + } + + fn rearm_u32_left_justified_expiration( + now: A::Ticks, + reference_u32: Option, + dt_u32: u32, + expiration: &mut Option>, + ) -> u32 { + // If the underlying timer is less than 32-bit wide, userspace is able + // to provide a finer `reference` and `dt` resolution than we can + // possibly represent in the kernel. + // + // We do not want to switch back to userspace *before* the timer + // fires. As such, when userspace gives us reference and ticks values + // with a precision unrepresentible using our Ticks object, we round + // `reference` down, and `dt` up (ensuring that the timer cannot fire + // earlier than requested). + let reference_unshifted = reference_u32.map(|ref_u32| ref_u32 >> A::Ticks::u32_padding()); + + // Round dt up: + let dt_unshifted = if dt_u32 & ((1 << A::Ticks::u32_padding()) - 1) != 0 { + // By right-shifting, we would decrease the requested dt value, + // firing _before_ the time requested by userspace. Add one to + // compensate this: + (dt_u32 >> A::Ticks::u32_padding()) + 1 + } else { + // dt does not need to be shifted *or* contains no lower bits + // unrepresentable in the kernel: + dt_u32 >> A::Ticks::u32_padding() + }; + + // For timers less than 32-bit wide, we do not have to handle a + // `reference + dt` overflow specially. This is because those timers are + // conveyed to us left-justified, and as such userspace would already + // have to take care of such overflow. + // + // However, we *may* need to handle overflow when the timer is *wider* + // than 32 bit. In this case, if `reference + dt` were to overflow, we + // need to rebase our reference on the full-width `now` time. + // + // If userspace didn't give us a reference, we can skip all of this and + // simply set the unshifted dt. + let new_exp = match (reference_unshifted, A::Ticks::width() > 32) { + (Some(userspace_reference_unshifted), true) => { + // We have a userspace reference and timer is wider than 32 bit. + // + // In this case, we need to check whether the lower 32 bits of the + // timer `reference` have already wrapped, compared to the reference + // provided by userspace: + if now.into_u32() < userspace_reference_unshifted { + // The lower 32-bit of reference are smaller than the userspace + // reference. This means that the full-width timer has had an + // increment in the upper bits. We thus set the full-width + // reference to the combination of the current upper timer bits + // *minus 1*, concatenated to the user-space provided bits. + // + // Because we don't know the integer type of the Ticks object + // (just that it's larger than a u32), we: + // + // 1. subtract a full `u32::MAX + 1` to incur a downward wrap, + // effectively subtracting `1` from the upper part, + // 2. subtract the lower `u32` bits from this value, setting + // those bits to zero, + // 3. adding back the userspace-provided reference. + + // Build 1 << 32: + let bit33 = A::Ticks::from(0xffffffff).wrapping_add(A::Ticks::from(0x1)); + + // Perform step 1, subtracting 1 << 32: + let sub_1_upper = now.wrapping_sub(bit33); + + // Perform step 2, setting first 32 bit to zero: + let sub_lower = + sub_1_upper.wrapping_sub(A::Ticks::from(sub_1_upper.into_u32())); + + // Perform step 3, add back the userspace-provided reference: + let rebased_reference = + sub_lower.wrapping_add(A::Ticks::from(userspace_reference_unshifted)); + + // Finally, return the new expiration. We don't have to do + // anything special for `dt`, as it's relative: + Expiration { + reference: rebased_reference, + dt: A::Ticks::from(dt_unshifted), + } + } else { + // The lower 32-bit of reference are equal to or larger than the + // userspace reference. Thus we can rebase the reference, + // touching only the lower 32 bit, by: + // + // 1. subtract the lower `u32` bits from this value, setting + // those bits to zero, + // 2. adding back the userspace-provided reference. + + // Perform step 1, setting first 32 bit to zero: + let sub_lower = now.wrapping_sub(A::Ticks::from(now.into_u32())); + + // Perform step 2, add back the userspace-provided reference: + let rebased_reference = + sub_lower.wrapping_add(A::Ticks::from(userspace_reference_unshifted)); + + // Finally, return the new expiration. We don't have to do + // anything special for `dt`, as it's relative: + Expiration { + reference: rebased_reference, + dt: A::Ticks::from(dt_unshifted), + } + } + } + + (Some(userspace_reference_unshifted), false) => { + // We have a userspace reference and timer is (less than) 32 + // bit. Simply set to unshifted values: + Expiration { + reference: A::Ticks::from(userspace_reference_unshifted), + dt: A::Ticks::from(dt_unshifted), + } + } + + (None, _) => { + // We have no userspace reference. Use `now` as a reference: + Expiration { + reference: now, + dt: A::Ticks::from(dt_unshifted), + } + } + }; + + // Store the new expiration. We already adjusted the armed count above: + *expiration = Some(new_exp); + + // Return the time left-justified time at which the alarm will fire: + new_exp + .reference + .wrapping_add(new_exp.dt) + .into_u32_left_justified() + } +} + +impl<'a, A: Alarm<'a>> SyscallDriver for AlarmDriver<'a, A> { + /// Setup and read the alarm. + /// + /// ### `command_num` + /// + /// - `0`: Driver existence check. + /// - `1`: Return the clock frequency in Hz. + /// - `2`: Read the current clock value + /// - `3`: Stop the alarm if it is outstanding + /// - `4`: Deprecated + /// - `5`: Set an alarm to fire at a given clock value `time` relative to `now` + /// - `6`: Set an alarm to fire at a given clock value `time` relative to a provided + /// reference point. + fn command( + &self, + cmd_type: usize, + data: usize, + data2: usize, + caller_id: ProcessId, + ) -> CommandReturn { + // Returns the error code to return to the user and whether we need to + // reset which is the next active alarm. We _don't_ reset if + // - we're disabling the underlying alarm anyway, + // - the underlying alarm is currently disabled and we're enabling the first alarm, or + // - on an error (i.e. no change to the alarms). + self.app_alarms + .enter(caller_id, |td, _upcalls| { + let now = self.alarm.now(); + + match cmd_type { + // Driver check: + // + // Don't re-arm the timer: + 0 => (CommandReturn::success(), false), + + 1 => { + // Get clock frequency. We return a frequency scaled by + // the amount of padding we add to the `ticks` value + // returned in command 2 ("capture time"), such that + // userspace knows when the timer will wrap and can + // accurately determine the duration of a single tick. + // + // Don't re-arm the timer: + let scaled_freq = + ::u32_left_justified_scale_freq::(); + (CommandReturn::success_u32(scaled_freq), false) + } + 2 => { + // Capture time. We pad the underlying timer's ticks to + // wrap at exactly `(2 ** 32) - 1`. This predictable + // wrapping value allows userspace to build long running + // timers beyond `2 ** now.width()` ticks. + // + // Don't re-arm the timer: + ( + CommandReturn::success_u32(now.into_u32_left_justified()), + false, + ) + } + 3 => { + // Stop + match td.expiration { + None => { + // Request to stop when already stopped. Don't + // re-arm the timer: + (CommandReturn::failure(ErrorCode::ALREADY), false) + } + Some(_old_expiraton) => { + // Clear the expiration: + td.expiration = None; + + // Ask for the timer to be re-armed. We can't do + // this here, as it would re-enter the grant + // region: + (CommandReturn::success(), true) + } + } + } + 4 => { + // Deprecated in 2.0, used to be: set absolute expiration + // + // Don't re-arm the timer: + (CommandReturn::failure(ErrorCode::NOSUPPORT), false) + } + 5 => { + // Set relative expiration. + // + // We provided userspace a potentially padded version of + // our in-kernel Ticks object, and as such we have to + // invert that operation through a right shift. + // + // Also, we need to keep track of the currently armed + // timers. + // + // All of this is done in the following helper method: + let new_exp_left_justified = Self::rearm_u32_left_justified_expiration( + // Current time: + now, + // No userspace-provided reference: + None, + // Left-justified `dt` value: + data as u32, + // Reference to the `Option`, also used + // to update the counter of armed alarms: + &mut td.expiration, + ); + + // Report success, with the left-justified time at which + // the alarm will fire. Also ask for the timer to be + // re-armed. We can't do this here, as it would re-enter + // the grant region: + (CommandReturn::success_u32(new_exp_left_justified), true) + } + 6 => { + // Also, we need to keep track of the currently armed + // timers. + // + // All of this is done in the following helper method: + let new_exp_left_justified = Self::rearm_u32_left_justified_expiration( + // Current time: + now, + // Left-justified userspace-provided reference: + Some(data as u32), + // Left-justified `dt` value: + data2 as u32, + // Reference to the `Option`, also used + // to update the counter of armed alarms: + &mut td.expiration, + ); + + // Report success, with the left-justified time at which + // the alarm will fire. Also ask for the timer to be + // re-armed. We can't do this here, as it would re-enter + // the grant region: + (CommandReturn::success_u32(new_exp_left_justified), true) + } + + // Unknown command: + // + // Don't re-arm the timer: + _ => (CommandReturn::failure(ErrorCode::NOSUPPORT), false), + } + }) + .map_or_else( + |err| CommandReturn::failure(err.into()), + |(retval, rearm_timer)| { + if rearm_timer { + self.process_rearm_or_callback(); + } + retval + }, + ) + } + + fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { + self.app_alarms.enter(processid, |_, _| {}) + } +} + +impl<'a, A: Alarm<'a>> time::AlarmClient for AlarmDriver<'a, A> { + fn alarm(&self) { + self.process_rearm_or_callback(); + } +} + +#[cfg(test)] +mod test { + use core::cell::Cell; + use core::marker::PhantomData; + + use kernel::hil::time::{ + Alarm, AlarmClient, Freq10MHz, Frequency, Ticks, Ticks24, Ticks32, Ticks64, Time, + }; + use kernel::utilities::cells::OptionalCell; + use kernel::ErrorCode; + + use super::{AlarmDriver, Expiration}; + + struct MockAlarm<'a, T: Ticks, F: Frequency> { + current_ticks: Cell, + client: OptionalCell<&'a dyn AlarmClient>, + _frequency: PhantomData, + } + + impl<'a, T: Ticks, F: Frequency> Time for MockAlarm<'a, T, F> { + type Frequency = F; + type Ticks = T; + + fn now(&self) -> Self::Ticks { + self.current_ticks.get() + } + } + + impl<'a, T: Ticks, F: Frequency> Alarm<'a> for MockAlarm<'a, T, F> { + fn set_alarm_client(&self, client: &'a dyn AlarmClient) { + self.client.set(client); + } + + fn set_alarm(&self, _reference: Self::Ticks, _dt: Self::Ticks) { + unimplemented!() + } + + fn get_alarm(&self) -> Self::Ticks { + unimplemented!() + } + + fn disarm(&self) -> Result<(), ErrorCode> { + unimplemented!() + } + + fn is_armed(&self) -> bool { + unimplemented!() + } + + fn minimum_dt(&self) -> Self::Ticks { + unimplemented!() + } + } + + #[test] + fn test_earliest_alarm_no_alarms() { + assert!( + AlarmDriver::>::earliest_alarm( + // Now: + Ticks32::from(42_u32), + // Expirations: + <[( + Expiration, + (), + fn(_, &()) -> Option<()> + ); 0] as IntoIterator>::into_iter([]) + ) + .unwrap() + .is_none() + ) + } + + #[test] + fn test_earliest_alarm_multiple_unexpired() { + // Should never be called: + let exp_handler = |exp, id: &usize| -> Option<()> { + panic!("Alarm should not be expired: {:?}, id: {}", exp, id) + }; + + let (earliest, id) = AlarmDriver::>::earliest_alarm( + // Now: + 42_u32.into(), + // Expirations: + [ + ( + // Will expire at 52: + Expiration { + reference: 42_u32.into(), + dt: 10_u32.into(), + }, + 0, + exp_handler, + ), + ( + // Will expire at exactly 43: + Expiration { + reference: u32::MAX.into(), + dt: 44_u32.into(), + }, + 1, + exp_handler, + ), + ( + // Will expire at 44: + Expiration { + reference: 10_u32.into(), + dt: 34_u32.into(), + }, + 2, + exp_handler, + ), + ] + .into_iter(), + ) + .unwrap() + .unwrap(); + + assert!(earliest.reference.into_u32() == u32::MAX); + assert!(earliest.dt.into_u32() == 44); + assert!(id == 1); + } + + #[test] + fn test_earliest_alarm_multiple_expired() { + let exp_list: [Cell; 7] = Default::default(); + + let exp_handler = |_exp, id: &usize| -> Option<()> { + exp_list[*id].set(true); + + // Don't stop iterating on the first expired alarm: + None + }; + + let (earliest, id) = AlarmDriver::>::earliest_alarm( + // Now: + 42_u32.into(), + // Expirations: + [ + ( + // Has expired at 42 (current cycle), should fire! + Expiration { + reference: 41_u32.into(), + dt: 1_u32.into(), + }, + 0, + &exp_handler, + ), + ( + // Will expire at 52, should not fire. + Expiration { + reference: 42_u32.into(), + dt: 10_u32.into(), + }, + 1, + &exp_handler, + ), + ( + // Will expire at exactly 43, should not fire. + Expiration { + reference: u32::MAX.into(), + dt: 44_u32.into(), + }, + 2, + &exp_handler, + ), + ( + // Reference is current time, expiration in the future, + // should not fire: + Expiration { + reference: 42_u32.into(), + dt: 1_u32.into(), + }, + 3, + &exp_handler, + ), + ( + // Reference is 43 (current time + 1), interpreted as "in + // the past", should fire: + Expiration { + reference: 43_u32.into(), + dt: 1_u32.into(), + }, + 4, + &exp_handler, + ), + ( + // Reference is 0, end is at 1, in the past, should fire: + Expiration { + reference: 0_u32.into(), + dt: 1_u32.into(), + }, + 5, + &exp_handler, + ), + ( + // Reference is u32::MAX, end is at 0, in the past, should fire: + Expiration { + reference: u32::MAX.into(), + dt: 1_u32.into(), + }, + 6, + &exp_handler, + ), + ] + .into_iter(), + ) + .unwrap() + .unwrap(); + + assert!(earliest.reference.into_u32() == 41); + assert!(earliest.dt.into_u32() == 1); + assert!(id == 0); + + let mut bool_exp_list: [bool; 7] = [false; 7]; + exp_list + .into_iter() + .zip(bool_exp_list.iter_mut()) + .for_each(|(src, dst)| *dst = src.get()); + + assert!(bool_exp_list == [true, false, false, false, true, true, true]); + } + + #[test] + fn test_earliest_alarm_expired_stop() { + let exp_list: [Cell; 4] = Default::default(); + + let exp_handler = |_exp, id: &usize| -> Option<&'static str> { + exp_list[*id].set(true); + + // Stop iterating on id == 3 + if *id == 3 { + Some("stopped") + } else { + None + } + }; + + let (expired, id, expired_ret) = + AlarmDriver::>::earliest_alarm( + // Now: + 42_u32.into(), + // Expirations: + [ + ( + // Will expire at 52, should not fire. + Expiration { + reference: 42_u32.into(), + dt: 10_u32.into(), + }, + 0, + &exp_handler, + ), + ( + // Has expired at 42 (current cycle), should fire! + Expiration { + reference: 41_u32.into(), + dt: 1_u32.into(), + }, + 1, + &exp_handler, + ), + ( + // Will expire at exactly 43, should not fire. + Expiration { + reference: u32::MAX.into(), + dt: 44_u32.into(), + }, + 2, + &exp_handler, + ), + ( + // Reference is 0, end is at 1, in the past, should fire: + Expiration { + reference: 0_u32.into(), + dt: 1_u32.into(), + }, + 3, + &exp_handler, + ), + ] + .into_iter(), + ) + .err() + .unwrap(); + + assert!(expired.reference.into_u32() == 0); + assert!(expired.dt.into_u32() == 1); + assert!(id == 3); + assert!(expired_ret == "stopped"); + + let mut bool_exp_list: [bool; 4] = [false; 4]; + exp_list + .into_iter() + .zip(bool_exp_list.iter_mut()) + .for_each(|(src, dst)| *dst = src.get()); + + assert!(bool_exp_list == [false, true, false, true,]); + } + + #[test] + fn test_rearm_24bit_left_justified_noref_basic() { + let mut expiration = None; + + assert!(Ticks24::u32_padding() == 8); + + let armed_time = + AlarmDriver::>::rearm_u32_left_justified_expiration( + // Current time: + Ticks24::from(1337_u32), + // No userspace-provided reference: + None, + // Left-justified `dt` value: + 1234_u32 << Ticks24::u32_padding(), + // Reference to the `Option`, also used + // to update the counter of armed alarms: + &mut expiration, + ); + + let expiration = expiration.unwrap(); + + assert_eq!(armed_time, (1337 + 1234) << Ticks24::u32_padding()); + assert_eq!(expiration.reference.into_u32(), 1337); + assert_eq!(expiration.dt.into_u32(), 1234); + } + + #[test] + fn test_rearm_24bit_left_justified_noref_wrapping() { + let mut expiration = None; + + let armed_time = + AlarmDriver::>::rearm_u32_left_justified_expiration( + // Current time: + Ticks24::from(1337_u32), + // No userspace-provided reference: + None, + // Left-justified `dt` value (in this case, with some + // irrepresentable precision) + u32::MAX - (42 << Ticks24::u32_padding()), + // Reference to the `Option`, also used + // to update the counter of armed alarms: + &mut expiration, + ); + + let expiration = expiration.unwrap(); + + // (1337 + ((0xffffffff - (42 << 8)) >> 8) + 1) % 0x01000000 = 1295 + assert_eq!(armed_time, 1295 << Ticks24::u32_padding()); + assert_eq!(expiration.reference.into_u32(), 1337); + assert_eq!( + expiration.dt.into_u32(), + // dt is rounded up to the next representable tick: + ((u32::MAX - (42 << Ticks24::u32_padding())) >> Ticks24::u32_padding()) + 1 + ); + } + + #[test] + fn test_rearm_32bit_left_justified_noref_basic() { + let mut expiration = Some(Expiration { + reference: 0_u32.into(), + dt: 1_u32.into(), + }); + + assert!(Ticks32::u32_padding() == 0); + + let armed_time = + AlarmDriver::>::rearm_u32_left_justified_expiration( + // Current time: + Ticks32::from(1337_u32), + // No userspace-provided reference: + None, + // Left-justified `dt` value, unshifted for 32 bit: + 1234_u32, + // Reference to the `Option`, also used + // to update the counter of armed alarms: + &mut expiration, + ); + + let expiration = expiration.unwrap(); + + assert_eq!(armed_time, 1337 + 1234); + assert_eq!(expiration.reference.into_u32(), 1337); + assert_eq!(expiration.dt.into_u32(), 1234); + } + + #[test] + fn test_rearm_32bit_left_justified_noref_wrapping() { + let mut expiration = None; + + let armed_time = + AlarmDriver::>::rearm_u32_left_justified_expiration( + // Current time: + Ticks32::from(1337_u32), + // No userspace-provided reference: + None, + // Left-justified `dt` value (in this case, with some + // irrepresentable precision) + u32::MAX - 42, + // Reference to the `Option`, also used + // to update the counter of armed alarms: + &mut expiration, + ); + + let expiration = expiration.unwrap(); + + // (1337 + (0xffffffff - 42)) % 0x100000000 = 1294 + assert_eq!(armed_time, 1294); + assert_eq!(expiration.reference.into_u32(), 1337); + assert_eq!(expiration.dt.into_u32(), u32::MAX - 42); + } + + #[test] + fn test_rearm_64bit_left_justified_noref_wrapping() { + let mut expiration = Some(Expiration { + reference: 0_u32.into(), + dt: 1_u32.into(), + }); + + assert!(Ticks64::u32_padding() == 0); + + let armed_time = + AlarmDriver::>::rearm_u32_left_justified_expiration( + // Current time: + Ticks64::from(0xDEADBEEFCAFE_u64), + // No userspace-provided reference: + None, + // Left-justified `dt` value, unshifted for 32 bit: + 0xDEADC0DE_u32, + // Reference to the `Option`, also used + // to update the counter of armed alarms: + &mut expiration, + ); + + let expiration = expiration.unwrap(); + + assert_eq!(armed_time, 0x9D9D8BDC_u32); + assert_eq!(expiration.reference.into_u64(), 0xDEADBEEFCAFE_u64); + assert_eq!(expiration.dt.into_u64(), 0xDEADC0DE_u64); + } + + #[test] + fn test_rearm_64bit_left_justified_refnowrap_dtnorwap() { + let mut expiration = None; + + // reference smaller than now & 0xffffffff, reference + dt don't wrap: + let armed_time = + AlarmDriver::>::rearm_u32_left_justified_expiration( + // Current time: + Ticks64::from(0xDEADBEEFCAFE_u64), + // Userspace-provided reference, smaller than now and dt + Some(0xBEEFC0DE_u32), + // Left-justified `dt` value, unshifted for 32 bit: + 0x1BADB002_u32, + // Reference to the `Option`, also used + // to update the counter of armed alarms: + &mut expiration, + ); + + let expiration = expiration.unwrap(); + + assert_eq!(armed_time, 0xDA9D70E0_u32); // remains at 0xDEAD + assert_eq!(expiration.reference.into_u64(), 0xDEADBEEFC0DE_u64); + assert_eq!(expiration.dt.into_u64(), 0x1BADB002_u64); + } + + #[test] + fn test_rearm_64bit_left_justified_refnowrwap_dtwrap() { + let mut expiration = Some(Expiration { + reference: 0_u32.into(), + dt: 1_u32.into(), + }); + + // reference smaller than now & 0xffffffff, reference + dt wrap: + let armed_time = + AlarmDriver::>::rearm_u32_left_justified_expiration( + // Current time: + Ticks64::from(0xDEADBEEFCAFE_u64), + // Userspace-provided reference, smaller than lower 32-bit of now + Some(0x8BADF00D_u32), + // Left-justified `dt` value, unshifted for 32 bit: + 0xFEEDC0DE_u32, + // Reference to the `Option`, also used + // to update the counter of armed alarms: + &mut expiration, + ); + + let expiration = expiration.unwrap(); + + assert_eq!(armed_time, 0x8A9BB0EB_u32); // wraps t0 0x0xDEAE + assert_eq!(expiration.reference.into_u64(), 0xDEAD8BADF00D_u64); + assert_eq!(expiration.dt.into_u64(), 0xFEEDC0DE_u64); + } + + #[test] + fn test_rearm_64bit_left_justified_refwrap_dtwrap() { + let mut expiration = None; + + // reference larger than now & 0xffffffff, reference + dt wrap: + let armed_time = + AlarmDriver::>::rearm_u32_left_justified_expiration( + // Current time: + Ticks64::from(0xDEADBEEFCAFE_u64), + // Userspace-provided reference, larger than lower 32-bit of + // now, meaning that it's already past: + Some(0xCAFEB0BA_u32), + // Left-justified `dt` value, unshifted for 32 bit: + 0xFEEDC0DE_u32, + // Reference to the `Option`, also used + // to update the counter of armed alarms: + &mut expiration, + ); + + let expiration = expiration.unwrap(); + + assert_eq!(armed_time, 0xC9EC7198_u32); // wraps to 0xDEAE + assert_eq!(expiration.reference.into_u64(), 0xDEACCAFEB0BA_u64); + assert_eq!(expiration.dt.into_u64(), 0xFEEDC0DE_u64); + } + + #[test] + fn test_rearm_64bit_left_justified_refwrap_dtnowrap() { + let mut expiration = Some(Expiration { + reference: 0_u32.into(), + dt: 1_u32.into(), + }); + + // reference larger than now & 0xffffffff, reference + dt don't wrap + let armed_time = + AlarmDriver::>::rearm_u32_left_justified_expiration( + // Current time: + Ticks64::from(0xDEADBEEFCAFE_u64), + // Userspace-provided reference, larger than lower 32-bit of now + Some(0xCAFEB0BA_u32), + // Left-justified `dt` value, unshifted for 32 bit: + 0x1BADB002_u32, + // Reference to the `Option`, also used + // to update the counter of armed alarms: + &mut expiration, + ); + + let expiration = expiration.unwrap(); + + assert_eq!(armed_time, 0xE6AC60BC_u32); // remains at 0xDEAD + assert_eq!(expiration.reference.into_u64(), 0xDEACCAFEB0BA_u64); + assert_eq!(expiration.dt.into_u64(), 0x1BADB002_u64); + } +} diff --git a/capsules/src/button.rs b/capsules/core/src/button.rs similarity index 89% rename from capsules/src/button.rs rename to capsules/core/src/button.rs index 64f99fe976..742e768b42 100644 --- a/capsules/src/button.rs +++ b/capsules/core/src/button.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides userspace control of buttons on a board. //! //! This allows for much more cross platform controlling of buttons without @@ -7,15 +11,15 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! let button_pins = static_init!( //! [&'static sam4l::gpio::GPIOPin; 1], //! [&sam4l::gpio::PA[16]]); //! let button = static_init!( -//! capsules::button::Button<'static>, -//! capsules::button::Button::new(button_pins, board_kernel.create_grant(&grant_cap))); +//! capsules_core::button::Button<'static>, +//! capsules_core::button::Button::new(button_pins, board_kernel.create_grant(&grant_cap))); //! for btn in button_pins.iter() { //! btn.set_client(button); //! } @@ -32,7 +36,7 @@ //! //! #### `command_num` //! -//! - `0`: Driver check and get number of buttons on the board. +//! - `0`: Driver existence check and get number of buttons on the board. //! - `1`: Enable interrupts for a given button. This will enable both press //! and depress events. //! - `2`: Disable interrupts for a button. No affect or reliance on @@ -102,10 +106,7 @@ impl<'a, P: gpio::InterruptPin<'a>> Button<'a, P> { pin.set_floating_state(floating_state); } - Self { - pins: pins, - apps: grant, - } + Self { pins, apps: grant } } fn get_button_state(&self, pin_num: u32) -> gpio::ActivationState { @@ -134,7 +135,7 @@ impl<'a, P: gpio::InterruptPin<'a>> SyscallDriver for Button<'a, P> { /// /// ### `command_num` /// - /// - `0`: Driver check and get number of buttons on the board. + /// - `0`: Driver existence check and get number of buttons on the board. /// - `1`: Enable interrupts for a given button. This will enable both press /// and depress events. /// - `2`: Disable interrupts for a button. No affect or reliance on @@ -145,18 +146,21 @@ impl<'a, P: gpio::InterruptPin<'a>> SyscallDriver for Button<'a, P> { command_num: usize, data: usize, _: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { let pins = self.pins; match command_num { // return button count + // TODO(Tock 3.0): TRD104 specifies that Command 0 should return Success, not SuccessU32, + // but this driver is unchanged since it has been stabilized. It will be brought into + // compliance as part of the next major release of Tock. See #3375. 0 => CommandReturn::success_u32(pins.len() as u32), // enable interrupts for a button 1 => { if data < pins.len() { self.apps - .enter(appid, |cntr, _| { + .enter(processid, |cntr, _| { cntr.subscribe_map |= 1 << data; let _ = pins[data] .0 @@ -176,7 +180,7 @@ impl<'a, P: gpio::InterruptPin<'a>> SyscallDriver for Button<'a, P> { } else { let res = self .apps - .enter(appid, |cntr, _| { + .enter(processid, |cntr, _| { cntr.subscribe_map &= !(1 << data); CommandReturn::success() }) diff --git a/capsules/src/console.rs b/capsules/core/src/console.rs similarity index 72% rename from capsules/src/console.rs rename to capsules/core/src/console.rs index 5db4805f90..0077d8796b 100644 --- a/capsules/src/console.rs +++ b/capsules/core/src/console.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides userspace with access to a serial interface. //! //! Setup @@ -5,9 +9,9 @@ //! //! You need a device that provides the `hil::uart::UART` trait. //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; -//! # use capsules::console::Console; +//! # use capsules_core::console::Console; //! //! let console = static_init!( //! Console, @@ -48,18 +52,43 @@ use kernel::{ErrorCode, ProcessId}; use crate::driver; pub const DRIVER_NUM: usize = driver::NUM::Console as usize; +/// Default size for the read and write buffers used by the console. +/// Boards may pass different-size buffers if needed. +pub const DEFAULT_BUF_SIZE: usize = 64; + +/// IDs for subscribed upcalls. +mod upcall { + /// Write buffer completed callback + pub const WRITE_DONE: usize = 1; + /// Read buffer completed callback + pub const READ_DONE: usize = 2; + /// Number of upcalls. Even though we only use two, indexing starts at 0 so + /// to be able to use indices 1 and 2 we need to specify three upcalls. + pub const COUNT: u8 = 3; +} + /// Ids for read-only allow buffers mod ro_allow { + /// Readonly buffer for write buffer + /// + /// Before the allow syscall was handled by the kernel, + /// console used allow number "1", so to preserve compatibility + /// we still use allow number 1 now. pub const WRITE: usize = 1; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 2; + pub const COUNT: u8 = 2; } /// Ids for read-write allow buffers mod rw_allow { + /// Writeable buffer for read buffer + /// + /// Before the allow syscall was handled by the kernel, + /// console used allow number "1", so to preserve compatibility + /// we still use allow number 1 now. pub const READ: usize = 1; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 2; + pub const COUNT: u8 = 2; } #[derive(Default)] @@ -70,14 +99,11 @@ pub struct App { read_len: usize, } -pub static mut WRITE_BUF: [u8; 64] = [0; 64]; -pub static mut READ_BUF: [u8; 64] = [0; 64]; - pub struct Console<'a> { uart: &'a dyn uart::UartData<'a>, apps: Grant< App, - UpcallCount<3>, + UpcallCount<{ upcall::COUNT }>, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<{ rw_allow::COUNT }>, >, @@ -94,13 +120,13 @@ impl<'a> Console<'a> { rx_buffer: &'static mut [u8], grant: Grant< App, - UpcallCount<3>, + UpcallCount<{ upcall::COUNT }>, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<{ rw_allow::COUNT }>, >, ) -> Console<'a> { Console { - uart: uart, + uart, apps: grant, tx_in_progress: OptionalCell::empty(), tx_buffer: TakeCell::new(tx_buffer), @@ -112,7 +138,7 @@ impl<'a> Console<'a> { /// Internal helper function for setting up a new send transaction fn send_new( &self, - app_id: ProcessId, + processid: ProcessId, app: &mut App, kernel_data: &GrantKernelData, len: usize, @@ -122,7 +148,7 @@ impl<'a> Console<'a> { .map_or(0, |write| write.len()) .min(len); app.write_remaining = app.write_len; - self.send(app_id, app, kernel_data); + self.send(processid, app, kernel_data); Ok(()) } @@ -131,13 +157,17 @@ impl<'a> Console<'a> { /// completed. fn send_continue( &self, - app_id: ProcessId, + processid: ProcessId, app: &mut App, kernel_data: &GrantKernelData, ) -> bool { if app.write_remaining > 0 { - self.send(app_id, app, kernel_data); - true + self.send(processid, app, kernel_data); + + // The send may have errored, meaning nothing is being transmitted. + // In that case there is nothing pending and we return false. In the + // common case, this will return true. + self.tx_in_progress.is_some() } else { false } @@ -145,26 +175,33 @@ impl<'a> Console<'a> { /// Internal helper function for sending data for an existing transaction. /// Cannot fail. If can't send now, it will schedule for sending later. - fn send(&self, app_id: ProcessId, app: &mut App, kernel_data: &GrantKernelData) { + fn send(&self, processid: ProcessId, app: &mut App, kernel_data: &GrantKernelData) { if self.tx_in_progress.is_none() { - self.tx_in_progress.set(app_id); + self.tx_in_progress.set(processid); self.tx_buffer.take().map(|buffer| { - let len = kernel_data - .get_readonly_processbuffer(ro_allow::WRITE) - .map_or(0, |write| write.len()); - if app.write_remaining > len { - // A slice has changed under us and is now smaller than - // what we need to write -- just write what we can. - app.write_remaining = len; - } let transaction_len = kernel_data .get_readonly_processbuffer(ro_allow::WRITE) .and_then(|write| { write.enter(|data| { - for (i, c) in data[data.len() - app.write_remaining..data.len()] - .iter() - .enumerate() + let remaining_data = match data + .get(app.write_len - app.write_remaining..app.write_len) { + Some(remaining_data) => remaining_data, + None => { + // A slice has changed under us and is now + // smaller than what we need to write. Our + // behavior in this case is documented as + // undefined; the simplest thing we can do + // that doesn't panic is to abort the write. + // We update app.write_len so that the + // number of bytes written (which is passed + // to the write done upcall) is correct. + app.write_len -= app.write_remaining; + app.write_remaining = 0; + return 0; + } + }; + for (i, c) in remaining_data.iter().enumerate() { if buffer.len() <= i { return i; // Short circuit on partial send } @@ -175,7 +212,20 @@ impl<'a> Console<'a> { }) .unwrap_or(0); app.write_remaining -= transaction_len; - let _ = self.uart.transmit_buffer(buffer, transaction_len); + match self.uart.transmit_buffer(buffer, transaction_len) { + Err((_e, tx_buffer)) => { + // The UART didn't start, so we will not get a transmit + // done callback. Need to signal the app now. + self.tx_buffer.replace(tx_buffer); + self.tx_in_progress.clear(); + + // Go ahead and signal the application + let written = app.write_len; + app.write_len = 0; + kernel_data.schedule_upcall(1, (written, 0, 0)).ok(); + } + Ok(()) => {} + } }); } else { app.pending_write = true; @@ -185,7 +235,7 @@ impl<'a> Console<'a> { /// Internal helper function for starting a receive operation fn receive_new( &self, - app_id: ProcessId, + processid: ProcessId, app: &mut App, kernel_data: &GrantKernelData, len: usize, @@ -207,61 +257,53 @@ impl<'a> Console<'a> { } else { // Note: We have ensured above that rx_buffer is present app.read_len = read_len; - self.rx_buffer.take().map(|buffer| { - self.rx_in_progress.set(app_id); - let _ = self.uart.receive_buffer(buffer, app.read_len); - }); - Ok(()) + self.rx_buffer + .take() + .map_or(Err(ErrorCode::INVAL), |buffer| { + self.rx_in_progress.set(processid); + if let Err((e, buf)) = self.uart.receive_buffer(buffer, app.read_len) { + self.rx_buffer.replace(buf); + return Err(e); + } + Ok(()) + }) } } } impl SyscallDriver for Console<'_> { - /// Setup shared buffers. - /// - /// ### `allow_num` - /// - /// - `1`: Writeable buffer for read buffer - - /// Setup shared buffers. - /// - /// ### `allow_num` - /// - /// - `1`: Readonly buffer for write buffer - - // Setup callbacks. - // - // ### `subscribe_num` - // - // - `1`: Write buffer completed callback - // - `2`: Read buffer completed callback - /// Initiate serial transfers /// /// ### `command_num` /// - /// - `0`: Driver check. + /// - `0`: Driver existence check. /// - `1`: Transmits a buffer passed via `allow`, up to the length /// passed in `arg1` /// - `2`: Receives into a buffer passed via `allow`, up to the length /// passed in `arg1` /// - `3`: Cancel any in progress receives and return (via callback) /// what has been received so far. - fn command(&self, cmd_num: usize, arg1: usize, _: usize, appid: ProcessId) -> CommandReturn { + fn command( + &self, + cmd_num: usize, + arg1: usize, + _: usize, + processid: ProcessId, + ) -> CommandReturn { let res = self .apps - .enter(appid, |app, kernel_data| { + .enter(processid, |app, kernel_data| { match cmd_num { 0 => Ok(()), 1 => { // putstr let len = arg1; - self.send_new(appid, app, kernel_data, len) + self.send_new(processid, app, kernel_data, len) } 2 => { // getnstr let len = arg1; - self.receive_new(appid, app, kernel_data, len) + self.receive_new(processid, app, kernel_data, len) } 3 => { // Abort RX @@ -294,9 +336,9 @@ impl uart::TransmitClient for Console<'_> { // Either print more from the AppSlice or send a callback to the // application. self.tx_buffer.replace(buffer); - self.tx_in_progress.take().map(|appid| { - self.apps.enter(appid, |app, kernel_data| { - match self.send_continue(appid, app, kernel_data) { + self.tx_in_progress.take().map(|processid| { + self.apps.enter(processid, |app, kernel_data| { + match self.send_continue(processid, app, kernel_data) { true => { // Still more to send. Wait to notify the process. } @@ -304,7 +346,9 @@ impl uart::TransmitClient for Console<'_> { // Go ahead and signal the application let written = app.write_len; app.write_len = 0; - kernel_data.schedule_upcall(1, (written, 0, 0)).ok(); + kernel_data + .schedule_upcall(upcall::WRITE_DONE, (written, 0, 0)) + .ok(); } } }) @@ -314,11 +358,11 @@ impl uart::TransmitClient for Console<'_> { // see if any other applications have pending messages. if self.tx_in_progress.is_none() { for cntr in self.apps.iter() { - let appid = cntr.processid(); + let processid = cntr.processid(); let started_tx = cntr.enter(|app, kernel_data| { if app.pending_write { app.pending_write = false; - self.send_continue(appid, app, kernel_data) + self.send_continue(processid, app, kernel_data) } else { false } @@ -341,9 +385,9 @@ impl uart::ReceiveClient for Console<'_> { ) { self.rx_in_progress .take() - .map(|appid| { + .map(|processid| { self.apps - .enter(appid, |_, kernel_data| { + .enter(processid, |_, kernel_data| { // An iterator over the returned buffer yielding only the first `rx_len` // bytes let rx_buffer = buffer.iter().take(rx_len); @@ -356,7 +400,7 @@ impl uart::ReceiveClient for Console<'_> { read.mut_enter(|data| { let mut c = 0; for (a, b) in data.iter().zip(rx_buffer) { - c = c + 1; + c += 1; a.set(*b); } c @@ -402,7 +446,7 @@ impl uart::ReceiveClient for Console<'_> { kernel_data .schedule_upcall( - 2, + upcall::READ_DONE, ( kernel::errorcode::into_statuscode(ret), received_length, @@ -415,7 +459,7 @@ impl uart::ReceiveClient for Console<'_> { // Some UART error occurred kernel_data .schedule_upcall( - 2, + upcall::READ_DONE, ( kernel::errorcode::into_statuscode(Err( ErrorCode::FAIL, diff --git a/capsules/core/src/console_ordered.rs b/capsules/core/src/console_ordered.rs new file mode 100644 index 0000000000..5d133ab754 --- /dev/null +++ b/capsules/core/src/console_ordered.rs @@ -0,0 +1,588 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Provides userspace with access to a serial interface whose output +//! is in-order with respect to kernel debug!() operations. Prints to +//! the console are atomic up to particular constant length, which +//! can be set at capsule instantiation. +//! +//! Note that this capsule does *not* buffer writes in an additional +//! buffer; this is critical to ensure ordering. Instead, it pushes +//! writes into the kernel debug buffer. If there is insufficient space +//! in the buffer for the write (or an atomic block size chunk of a very +//! large write), the capsule waits and uses a retry timer. This means +//! that in-kernel debug statements can starve userspace prints, e.g., +//! if they always keep the kernel debug buffer full. +//! +//! Setup +//! ----- +//! +//! This capsule allows userspace programs to print to the kernel +//! debug log. This ensures that (as long as the writes are not +//! truncated) that kernel and userspace print operations are in +//! order. It requires a reference to an Alarm for timers to issue +//! callbacks and send more data. The three configuration constants are: +//! - ATOMIC_SIZE: the minimum block of buffer that will be sent. If +//! there is not enough space in the debug buffer to +//! send ATOMIC_SIZE bytes, the console retries later. +//! - RETRY_TIMER: if there is not enough space in the debug buffer +//! to send the next chunk of a write, the console +//! waits RETRY_TIMER ticks of the supplied alarm. +//! - WRITE_TIMER: after completing a write, the console waits +//! WRITE_TIMER ticks of the supplied alarm before +//! issuing a callback or writing more. +//! +//! RETRY_TIMER and WRITE_TIMER should be set based on the speed of +//! the underlying UART and desired load. Generally speaking, setting +//! them around 50-100 byte times is good. For example, this means on +//! a 115200 UART, setting them to 5ms (576 bits, or 72 bytes) is +//! reasonable. ATOMIC_SIZE should be at least 80 (row width +//! of a standard console). +//! +//! ```rust,ignore +//! # use kernel::static_init; +//! # use capsules_core::console_ordered::ConsoleOrdered; +//! let console = static_init!( +//! ConsoleOrdered, +//! ConsoleOrdered::new(virtual_alarm, +//! board_kernel.create_grant(capsules_core::console_ordered::DRIVER_NUM, +//! &grant_cap), +//! ATOMIC_SIZE, +//! RETRY_TIMER, +//! WRITE_TIMER)); +//! +//! ``` +//! +//! Usage +//! ----- +//! +//! The user must perform three steps in order to write a buffer: +//! +//! ```c +//! // (Optional) Set a callback to be invoked when the buffer has been written +//! subscribe(CONSOLE_DRIVER_NUM, 1, my_callback); +//! // Share the buffer from userspace with the driver +//! allow(CONSOLE_DRIVER_NUM, buffer, buffer_len_in_bytes); +//! // Initiate the transaction +//! command(CONSOLE_DRIVER_NUM, 1, len_to_write_in_bytes) +//! ``` +//! + +use core::cell::Cell; +use core::cmp; + +use kernel::debug::debug_available_len; +use kernel::debug_process_slice; + +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, GrantKernelData, UpcallCount}; +use kernel::hil::time::{Alarm, AlarmClient, ConvertTicks}; +use kernel::hil::uart; +use kernel::processbuffer::{ReadableProcessBuffer, WriteableProcessBuffer}; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::{ErrorCode, ProcessId}; + +/// Syscall driver number. +use crate::driver; +pub const DRIVER_NUM: usize = driver::NUM::Console as usize; + +/// Ids for read-only allow buffers +mod ro_allow { + /// Before the allow syscall was handled by the kernel, + /// console used allow number "1", so to preserve compatibility + /// we still use allow number 1 now. + pub const WRITE: usize = 1; + /// The number of read-allow buffers (for putstr) the kernel stores for this grant + pub const COUNT: u8 = 2; +} + +/// Ids for read-write allow buffers +mod rw_allow { + /// Before the allow syscall was handled by the kernel, + /// console used allow number "1", so to preserve compatibility + /// we still use allow number 1 now. + pub const READ: usize = 1; + /// The number of read-write allow buffers (for getstr) the kernel stores for this grant + pub const COUNT: u8 = 2; +} + +#[derive(Default)] +pub struct App { + write_position: usize, // Current write position + write_len: usize, // Length of total write + writing: bool, // Are we in the midst of a write + pending_write: bool, // Are we waiting to write + tx_counter: usize, // Used to keep order of writes + read_len: usize, // Read length + rx_counter: usize, // Used to order reads (no starvation) +} + +pub struct ConsoleOrdered<'a, A: Alarm<'a>> { + uart: &'a dyn uart::Receive<'a>, + apps: Grant< + App, + UpcallCount<3>, + AllowRoCount<{ ro_allow::COUNT }>, + AllowRwCount<{ rw_allow::COUNT }>, + >, + tx_in_progress: Cell, // If true there's an ongoing write so others must wait + tx_counter: Cell, // Sequence number for writes from different processes + alarm: &'a A, // Timer for trying to send more + + rx_counter: Cell, + rx_in_progress: OptionalCell, + rx_buffer: TakeCell<'static, [u8]>, + + atomic_size: Cell, // The maximum size write the capsule promises atomicity; + // larger writes may be broken into atomic_size chunks. + // This must be smaller than the debug buffer size or a long + // write may never print. + retry_timer: Cell, // How long the capsule will wait before retrying if there + // is insufficient space in the debug buffer (alarm ticks) + // when a write is first attempted. + write_timer: Cell, // Time to wait after a successful write into the debug buffer, + // before checking whether write more or issue a callback that + // the current write has completed (alarm ticks). +} + +impl<'a, A: Alarm<'a>> ConsoleOrdered<'a, A> { + pub fn new( + uart: &'a dyn uart::Receive<'a>, + alarm: &'a A, + rx_buffer: &'static mut [u8], + grant: Grant< + App, + UpcallCount<3>, + AllowRoCount<{ ro_allow::COUNT }>, + AllowRwCount<{ rw_allow::COUNT }>, + >, + atomic_size: usize, + retry_timer: u32, + write_timer: u32, + ) -> ConsoleOrdered<'a, A> { + ConsoleOrdered { + uart, + apps: grant, + tx_in_progress: Cell::new(false), + tx_counter: Cell::new(0), + alarm, + + rx_counter: Cell::new(0), + rx_in_progress: OptionalCell::empty(), + rx_buffer: TakeCell::new(rx_buffer), + + atomic_size: Cell::new(atomic_size), + retry_timer: Cell::new(retry_timer), + write_timer: Cell::new(write_timer), + } + } + + /// Internal helper function for starting up a new print; allocate a sequence number and + /// start the send state machine. + fn send_new( + &self, + app: &mut App, + kernel_data: &GrantKernelData, + len: usize, + ) -> Result<(), ErrorCode> { + // We are already writing + if app.writing || app.pending_write { + return Err(ErrorCode::BUSY); + } + app.write_position = 0; + app.write_len = kernel_data + .get_readonly_processbuffer(ro_allow::WRITE) + .map_or(0, |write| write.len()) + .min(len); + // We have nothing to write + if app.write_len == 0 { + return Err(ErrorCode::NOMEM); + } + // Order the prints through a global counter. + app.tx_counter = self.tx_counter.get(); + self.tx_counter.set(app.tx_counter.wrapping_add(1)); + + let debug_space_avail = debug_available_len(); + + if self.tx_in_progress.get() { + // A prior print is outstanding, enqueue + app.pending_write = true; + } else if app.write_len <= debug_space_avail { + // Space for the full write, make it + app.write_position = self.send(app, kernel_data).map_or(0, |len| len); + } else if self.atomic_size.get() <= debug_space_avail { + // Space for a partial write, make it + app.write_position = self.send(app, kernel_data).map_or(0, |len| len); + } else { + // No space even for a partial, minimum size write: enqueue + app.pending_write = true; + self.alarm.set_alarm( + self.alarm.now(), + self.alarm.ticks_from_ms(self.retry_timer.get()), + ); + } + Ok(()) + } + + /// Internal helper function for sending data. Assumes that there is enough + /// space in the debug buffer for the write. Writes longer than available + /// debug buffer space will be truncated, so callers that wish to not lose + /// data must check before calling. + fn send( + &self, + app: &mut App, + kernel_data: &GrantKernelData, + ) -> Result { + // We can ignore the Result because if the call fails, it means + // the process has terminated, so issuing a callback doesn't matter. + // If the call fails, just use the alarm to try the next client. + let res = kernel_data + .get_readonly_processbuffer(ro_allow::WRITE) + .and_then(|write| { + write.enter(|data| { + // The slice might have become shorter than the requested + // write; if so, just write what there is. + let remaining_len = app.write_len - app.write_position; + let real_write_len = cmp::min(remaining_len, debug_available_len()); + let this_write_end = app.write_position + real_write_len; + let remaining_data = match data.get(app.write_position..this_write_end) { + Some(remaining_data) => remaining_data, + None => data, + }; + + app.writing = true; + self.tx_in_progress.set(true); + if real_write_len > 0 { + let count = debug_process_slice!(remaining_data); + count + } else { + 0 + } + }) + }); + // Start a timer to signal completion of this write + // and potentially write more. + self.alarm.set_alarm( + self.alarm.now(), + self.alarm.ticks_from_ms(self.write_timer.get()), + ); + res + } + + /// Internal helper function for starting a receive operation. Processes + /// do not share reads, they take turns, with turn order monitored through + /// a sequence number. + fn receive_new( + &self, + processid: ProcessId, + app: &mut App, + kernel_data: &GrantKernelData, + len: usize, + ) -> Result<(), ErrorCode> { + if app.read_len != 0 { + // We are busy reading, don't try again + Err(ErrorCode::BUSY) + } else if len == 0 { + // Cannot read length 0 + Err(ErrorCode::INVAL) + } else if self.rx_buffer.is_none() { + // Console is busy receiving, so enqueue + app.rx_counter = self.rx_counter.get(); + self.rx_counter.set(app.rx_counter + 1); + app.read_len = len; + Ok(()) + } else { + // App can try to start a read + let read_len = kernel_data + .get_readwrite_processbuffer(rw_allow::READ) + .map_or(0, |read| read.len()) + .min(len); + if read_len > self.rx_buffer.map_or(0, |buf| buf.len()) { + // For simplicity, impose a small maximum receive length + // instead of doing incremental reads + Err(ErrorCode::INVAL) + } else { + // Note: We have ensured above that rx_buffer is present + app.read_len = read_len; + self.rx_buffer.take().map(|buffer| { + self.rx_in_progress.set(processid); + let _ = self.uart.receive_buffer(buffer, app.read_len); + }); + Ok(()) + } + } + } +} + +impl<'a, A: Alarm<'a>> AlarmClient for ConsoleOrdered<'a, A> { + fn alarm(&self) { + if self.tx_in_progress.get() { + // Clear here and set it later; if .enter fails (process + // has died) it remains cleared. + self.tx_in_progress.set(false); + + // Check if the current writer is finished; if so, issue an upcall, if not, + // try to write more. + for cntr in self.apps.iter() { + cntr.enter(|app, kernel_data| { + // This is the in-progress write + if app.writing { + if app.write_position >= app.write_len { + let _res = kernel_data.schedule_upcall(1, (app.write_len, 0, 0)); + app.writing = false; + } else { + // Still have more to write, don't allow others to jump in. + self.tx_in_progress.set(true); + + // Promise to write to the end, or the atomic write unit, whichever is smaller + let remaining_len = app.write_len - app.write_position; + let debug_space_avail = debug_available_len(); + let minimum_write = cmp::min(remaining_len, self.atomic_size.get()); + + // Write, or if there isn't space for a minimum write, retry later + if minimum_write <= debug_space_avail { + app.write_position += + self.send(app, kernel_data).map_or(0, |len| len); + } else { + self.alarm.set_alarm( + self.alarm.now(), + self.alarm.ticks_from_ms(self.retry_timer.get()), + ); + } + } + } + }); + } + } + + // There's no ongoing send, try to send the next one (process with + // lowest sequence number). + if !self.tx_in_progress.get() { + // Find if there's another writer and mark it busy. + let mut next_writer: Option = None; + let mut seqno = self.tx_counter.get(); + + // Find the process that has an outstanding write with the + // earliest sequence number, handling wraparound. + for cntr in self.apps.iter() { + let appid = cntr.processid(); + cntr.enter(|app, _| { + if app.pending_write { + // Checks wither app.tx_counter is earlier than + // seqno, with the constrain that there are < + // usize/2 processes. wrapping_sub allows this to + // handle wraparound E.g., in 8-bit arithmetic + // 0x02 - 0xff = 0x03 and so 0xff is "earlier" + // than 0x02. -pal + if seqno.wrapping_sub(app.tx_counter) < usize::MAX / 2 { + seqno = app.tx_counter; + next_writer = Some(appid); + } + } + }); + } + + next_writer.map(|pid| { + self.apps.enter(pid, |app, kernel_data| { + app.pending_write = false; + let len = app.write_len; + let _ = self.send_new(app, kernel_data, len); + }) + }); + } + } +} + +impl<'a, A: Alarm<'a>> SyscallDriver for ConsoleOrdered<'a, A> { + /// Setup shared buffers. + /// + /// ### `allow_num` + /// + /// - `0`: Readonly buffer for write buffer + + // Setup callbacks. + // + // ### `subscribe_num` + // + // - `1`: Write buffer completed callback + + /// Initiate serial transfers + /// + /// ### `command_num` + /// + /// - `0`: Driver existence check. + /// - `1`: Transmits a buffer passed via `allow`, up to the length + /// passed in `arg1` + fn command(&self, cmd_num: usize, arg1: usize, _: usize, appid: ProcessId) -> CommandReturn { + let res = self + .apps + .enter(appid, |app, kernel_data| { + match cmd_num { + 0 => Ok(()), + 1 => { + // putstr + let len = arg1; + self.send_new(app, kernel_data, len) + } + 2 => { + // getnstr + let len = arg1; + self.receive_new(appid, app, kernel_data, len) + } + 3 => { + // Abort RX + let _ = self.uart.receive_abort(); + Ok(()) + } + _ => Err(ErrorCode::NOSUPPORT), + } + }) + .map_err(ErrorCode::from); + match res { + Ok(Ok(())) => CommandReturn::success(), + Ok(Err(e)) => CommandReturn::failure(e), + Err(e) => CommandReturn::failure(e), + } + } + + fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { + self.apps.enter(processid, |_, _| {}) + } +} + +impl<'a, A: Alarm<'a>> uart::ReceiveClient for ConsoleOrdered<'a, A> { + fn received_buffer( + &self, + buffer: &'static mut [u8], + rx_len: usize, + rcode: Result<(), ErrorCode>, + error: uart::Error, + ) { + // First, handle this read, then see if there's another read to process. + self.rx_in_progress + .take() + .map(|processid| { + self.apps + .enter(processid, |app, kernel_data| { + // An iterator over the returned buffer yielding only the first `rx_len` + // bytes + let rx_buffer = buffer.iter().take(rx_len); + app.read_len = 0; // Mark that we are no longer reading. + match error { + uart::Error::None | uart::Error::Aborted => { + // Receive some bytes, signal error type and return bytes to process buffer + let count = kernel_data + .get_readwrite_processbuffer(rw_allow::READ) + .and_then(|read| { + read.mut_enter(|data| { + let mut c = 0; + for (a, b) in data.iter().zip(rx_buffer) { + c += 1; + a.set(*b); + } + c + }) + }) + .unwrap_or(-1); + + // Make sure we report the same number + // of bytes that we actually copied into + // the app's buffer. This is defensive: + // we shouldn't ever receive more bytes + // than will fit in the app buffer since + // we use the app_buffer's length when + // calling `receive()`. However, a buggy + // lower layer could return more bytes + // than we asked for, and we don't want + // to propagate that length error to + // userspace. However, we do return an + // error code so that userspace knows + // something went wrong. + // + // If count < 0 this means the buffer + // disappeared: return NOMEM. + let read_buffer_len = kernel_data + .get_readwrite_processbuffer(rw_allow::READ) + .map_or(0, |read| read.len()); + let (ret, received_length) = if count < 0 { + (Err(ErrorCode::NOMEM), 0) + } else if rx_len > read_buffer_len { + // Return `SIZE` indicating that + // some received bytes were dropped. + // We report the length that we + // actually copied into the buffer, + // but also indicate that there was + // an issue in the kernel with the + // receive. + (Err(ErrorCode::SIZE), read_buffer_len) + } else { + // This is the normal and expected + // case. + (rcode, rx_len) + }; + kernel_data + .schedule_upcall( + 2, + ( + kernel::errorcode::into_statuscode(ret), + received_length, + 0, + ), + ) + .ok(); + } + _ => { + // Some UART error occurred + kernel_data + .schedule_upcall( + 2, + ( + kernel::errorcode::into_statuscode(Err( + ErrorCode::FAIL, + )), + 0, + 0, + ), + ) + .ok(); + } + } + }) + .unwrap_or_default(); + }) + .unwrap_or_default(); + + // Whatever happens, we want to make sure to replace the rx_buffer for future transactions + self.rx_buffer.replace(buffer); + + // Find if there's another reader and if so start reading + let mut next_reader: Option = None; + let mut seqno = self.tx_counter.get(); + + for cntr in self.apps.iter() { + let appid = cntr.processid(); + cntr.enter(|app, _| { + if app.read_len != 0 { + // Checks wither app.tx_counter is earlier than + // seqno, with the constrain that there are < + // usize/2 processes. wrapping_sub allows this to + // handle wraparound E.g., in 8-bit arithmetic + // 0x02 - 0xff = 0x03 and so 0xff is "earlier" + // than 0x02. -pal + if seqno.wrapping_sub(app.rx_counter) < usize::MAX / 2 { + seqno = app.rx_counter; + next_reader = Some(appid); + } + } + }); + } + + next_reader.map(|pid| { + self.apps.enter(pid, |app, kernel_data| { + let len = app.read_len; + let _ = self.receive_new(pid, app, kernel_data, len); + }) + }); + } +} diff --git a/capsules/src/driver.rs b/capsules/core/src/driver.rs similarity index 76% rename from capsules/src/driver.rs rename to capsules/core/src/driver.rs index b198c37fa2..ec84fc45a7 100644 --- a/capsules/src/driver.rs +++ b/capsules/core/src/driver.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Mapping of capsules to their syscall driver number. use enum_primitive::cast::FromPrimitive; @@ -18,6 +22,7 @@ pub enum NUM { AnalogComparator = 0x00007, LowLevelDebug = 0x00008, ReadOnlyState = 0x00009, + Pwm = 0x00010, // Kernel Ipc = 0x10000, @@ -28,11 +33,17 @@ pub enum NUM { I2cMaster = 0x20003, UsbUser = 0x20005, I2cMasterSlave = 0x20006, + Can = 0x20007, // Radio BleAdvertising = 0x30000, Ieee802154 = 0x30001, Udp = 0x30002, + LoRaPhySPI = 0x30003, + LoRaPhyGPIO = 0x30004, + Thread = 0x30005, + Eui64 = 0x30006, + WiFiPi = 0x30007, //misc or radio? // Cryptography Rng = 0x40001, @@ -46,6 +57,7 @@ pub enum NUM { AppFlash = 0x50000, NvmStorage = 0x50001, SdCard = 0x50002, + Kv = 0x50003, // Sensors Temperature = 0x60000, @@ -54,6 +66,8 @@ pub enum NUM { NINEDOF = 0x60004, Proximity = 0x60005, SoundPressure = 0x60006, + AirQuality = 0x60007, + Pressure = 0x60008, // Sensor ICs Tsl2561 = 0x70000, @@ -76,5 +90,9 @@ pub enum NUM { Screen = 0x90001, Touch = 0x90002, TextScreen = 0x90003, + SevenSegment = 0x90004, + KeyboardHid = 0x90005, + DateTime = 0x90007, + CycleCount = 0x90008, } } diff --git a/capsules/src/gpio.rs b/capsules/core/src/gpio.rs similarity index 94% rename from capsules/src/gpio.rs rename to capsules/core/src/gpio.rs index 6937885471..c8c556b0df 100644 --- a/capsules/src/gpio.rs +++ b/capsules/core/src/gpio.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides userspace applications with access to GPIO pins. //! //! GPIOs are presented through a driver interface with synchronous commands @@ -11,7 +15,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! let gpio_pins = static_init!( @@ -21,8 +25,8 @@ //! Option<&sam4l::gpio::PB[11]>, //! Option<&sam4l::gpio::PB[12]>]); //! let gpio = static_init!( -//! capsules::gpio::GPIO<'static, sam4l::gpio::GPIOPin>, -//! capsules::gpio::GPIO::new(gpio_pins)); +//! capsules_core::gpio::GPIO<'static, sam4l::gpio::GPIOPin>, +//! capsules_core::gpio::GPIO::new(gpio_pins)); //! for maybe_pin in gpio_pins.iter() { //! if let Some(pin) = maybe_pin { //! pin.set_client(gpio); @@ -78,10 +82,7 @@ impl<'a, IP: gpio::InterruptPin<'a>> GPIO<'a, IP> { pin.set_value(i as u32); } } - Self { - pins: pins, - apps: grant, - } + Self { pins, apps: grant } } fn configure_input_pin(&self, pin_num: u32, config: usize) -> CommandReturn { @@ -109,7 +110,7 @@ impl<'a, IP: gpio::InterruptPin<'a>> GPIO<'a, IP> { } fn configure_interrupt(&self, pin_num: u32, config: usize) -> CommandReturn { - let pins = self.pins.as_ref(); + let pins = self.pins; let index = pin_num as usize; if let Some(pin) = pins[index] { match config { @@ -139,7 +140,7 @@ impl<'a, IP: gpio::InterruptPin<'a>> GPIO<'a, IP> { impl<'a, IP: gpio::InterruptPin<'a>> gpio::ClientWithValue for GPIO<'a, IP> { fn fired(&self, pin_num: u32) { // read the value of the pin - let pins = self.pins.as_ref(); + let pins = self.pins; if let Some(pin) = pins[pin_num as usize] { let pin_state = pin.read(); @@ -174,7 +175,7 @@ impl<'a, IP: gpio::InterruptPin<'a>> SyscallDriver for GPIO<'a, IP> { /// /// ### `command_num` /// - /// - `0`: Number of pins. + /// - `0`: Driver existence check. /// - `1`: Enable output on `pin`. /// - `2`: Set `pin`. /// - `3`: Clear `pin`. @@ -184,6 +185,7 @@ impl<'a, IP: gpio::InterruptPin<'a>> SyscallDriver for GPIO<'a, IP> { /// - `7`: Configure interrupt on `pin` with `irq_config` in 0x00XX00000 /// - `8`: Disable interrupt on `pin`. /// - `9`: Disable `pin`. + /// - `10`: Get number of GPIO ports supported. fn command( &self, command_num: usize, @@ -191,11 +193,11 @@ impl<'a, IP: gpio::InterruptPin<'a>> SyscallDriver for GPIO<'a, IP> { data2: usize, _: ProcessId, ) -> CommandReturn { - let pins = self.pins.as_ref(); + let pins = self.pins; let pin_index = data1; match command_num { - // number of pins - 0 => CommandReturn::success_u32(pins.len() as u32), + // Check existence. + 0 => CommandReturn::success(), // enable output 1 => { @@ -327,6 +329,9 @@ impl<'a, IP: gpio::InterruptPin<'a>> SyscallDriver for GPIO<'a, IP> { } } + // number of pins + 10 => CommandReturn::success_u32(pins.len() as u32), + // default _ => CommandReturn::failure(ErrorCode::NOSUPPORT), } diff --git a/capsules/src/i2c_master.rs b/capsules/core/src/i2c_master.rs similarity index 70% rename from capsules/src/i2c_master.rs rename to capsules/core/src/i2c_master.rs index a55ade6333..080e53d987 100644 --- a/capsules/src/i2c_master.rs +++ b/capsules/core/src/i2c_master.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for an I2C Master interface. use enum_primitive::enum_from_primitive; @@ -17,30 +21,30 @@ pub const DRIVER_NUM: usize = driver::NUM::I2cMaster as usize; mod rw_allow { pub const BUFFER: usize = 1; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 2; + pub const COUNT: u8 = 2; } #[derive(Default)] pub struct App; -pub static mut BUF: [u8; 64] = [0; 64]; +pub const BUFFER_LENGTH: usize = 64; struct Transaction { /// The buffer containing the bytes to transmit as it should be returned to /// the client - app_id: ProcessId, + processid: ProcessId, /// The total amount to transmit read_len: OptionalCell, } -pub struct I2CMasterDriver<'a, I: 'a + i2c::I2CMaster> { +pub struct I2CMasterDriver<'a, I: i2c::I2CMaster<'a>> { i2c: &'a I, buf: TakeCell<'static, [u8]>, tx: MapCell, apps: Grant, AllowRoCount<0>, AllowRwCount<{ rw_allow::COUNT }>>, } -impl<'a, I: 'a + i2c::I2CMaster> I2CMasterDriver<'a, I> { +impl<'a, I: i2c::I2CMaster<'a>> I2CMasterDriver<'a, I> { pub fn new( i2c: &'a I, buf: &'static mut [u8], @@ -56,27 +60,29 @@ impl<'a, I: 'a + i2c::I2CMaster> I2CMasterDriver<'a, I> { fn operation( &self, - app_id: ProcessId, + processid: ProcessId, kernel_data: &GrantKernelData, command: Cmd, addr: u8, - wlen: u8, - rlen: u8, + wlen: usize, + rlen: usize, ) -> Result<(), ErrorCode> { kernel_data .get_readwrite_processbuffer(rw_allow::BUFFER) .and_then(|buffer| { buffer.enter(|app_buffer| { self.buf.take().map_or(Err(ErrorCode::NOMEM), |buffer| { - app_buffer[..(wlen as usize)].copy_to_slice(&mut buffer[..(wlen as usize)]); + app_buffer[..wlen].copy_to_slice(&mut buffer[..wlen]); - let read_len: OptionalCell; - if rlen == 0 { - read_len = OptionalCell::empty(); + let read_len = if rlen == 0 { + OptionalCell::empty() } else { - read_len = OptionalCell::new(rlen as usize); - } - self.tx.put(Transaction { app_id, read_len }); + OptionalCell::new(rlen) + }; + self.tx.put(Transaction { + processid, + read_len, + }); let res = match command { Cmd::Ping => { @@ -88,7 +94,7 @@ impl<'a, I: 'a + i2c::I2CMaster> I2CMasterDriver<'a, I> { Cmd::WriteRead => self.i2c.write_read(addr, buffer, wlen, rlen), }; match res { - Ok(_) => Ok(()), + Ok(()) => Ok(()), Err((error, data)) => { self.buf.put(Some(data)); Err(error.into()) @@ -113,7 +119,7 @@ pub enum Cmd { } } -impl<'a, I: 'a + i2c::I2CMaster> SyscallDriver for I2CMasterDriver<'a, I> { +impl<'a, I: i2c::I2CMaster<'a>> SyscallDriver for I2CMasterDriver<'a, I> { /// Setup shared buffers. /// /// ### `allow_num` @@ -127,25 +133,31 @@ impl<'a, I: 'a + i2c::I2CMaster> SyscallDriver for I2CMasterDriver<'a, I> { // - `0`: Write buffer completed callback /// Initiate transfers - fn command(&self, cmd_num: usize, arg1: usize, arg2: usize, appid: ProcessId) -> CommandReturn { + fn command( + &self, + cmd_num: usize, + arg1: usize, + arg2: usize, + processid: ProcessId, + ) -> CommandReturn { if let Some(cmd) = Cmd::from_usize(cmd_num) { match cmd { Cmd::Ping => CommandReturn::success(), Cmd::Write => self .apps - .enter(appid, |_, kernel_data| { + .enter(processid, |_, kernel_data| { let addr = arg1 as u8; let write_len = arg2; - self.operation(appid, kernel_data, Cmd::Write, addr, write_len as u8, 0) + self.operation(processid, kernel_data, Cmd::Write, addr, write_len, 0) .into() }) .unwrap_or_else(|err| err.into()), Cmd::Read => self .apps - .enter(appid, |_, kernel_data| { + .enter(processid, |_, kernel_data| { let addr = arg1 as u8; let read_len = arg2; - self.operation(appid, kernel_data, Cmd::Read, addr, 0, read_len as u8) + self.operation(processid, kernel_data, Cmd::Read, addr, 0, read_len) .into() }) .unwrap_or_else(|err| err.into()), @@ -154,14 +166,14 @@ impl<'a, I: 'a + i2c::I2CMaster> SyscallDriver for I2CMasterDriver<'a, I> { let write_len = arg1 >> 8; // can extend to 24 bit write length let read_len = arg2; // can extend to 32 bit read length self.apps - .enter(appid, |_, kernel_data| { + .enter(processid, |_, kernel_data| { self.operation( - appid, + processid, kernel_data, Cmd::WriteRead, addr, - write_len as u8, - read_len as u8, + write_len, + read_len, ) .into() }) @@ -178,10 +190,10 @@ impl<'a, I: 'a + i2c::I2CMaster> SyscallDriver for I2CMasterDriver<'a, I> { } } -impl<'a, I: 'a + i2c::I2CMaster> i2c::I2CHwMasterClient for I2CMasterDriver<'a, I> { - fn command_complete(&self, buffer: &'static mut [u8], _status: Result<(), i2c::Error>) { +impl<'a, I: i2c::I2CMaster<'a>> i2c::I2CHwMasterClient for I2CMasterDriver<'a, I> { + fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) { self.tx.take().map(|tx| { - self.apps.enter(tx.app_id, |_, kernel_data| { + self.apps.enter(tx.processid, |_, kernel_data| { if let Some(read_len) = tx.read_len.take() { let _ = kernel_data .get_readwrite_processbuffer(rw_allow::BUFFER) @@ -193,7 +205,16 @@ impl<'a, I: 'a + i2c::I2CMaster> i2c::I2CHwMasterClient for I2CMasterDriver<'a, } // signal to driver that tx complete - kernel_data.schedule_upcall(0, (0, 0, 0)).ok(); + kernel_data + .schedule_upcall( + 0, + ( + kernel::errorcode::into_statuscode(status.map_err(|e| e.into())), + 0, + 0, + ), + ) + .ok(); }) }); diff --git a/capsules/core/src/i2c_master_slave_combo.rs b/capsules/core/src/i2c_master_slave_combo.rs new file mode 100644 index 0000000000..0f787b5ab6 --- /dev/null +++ b/capsules/core/src/i2c_master_slave_combo.rs @@ -0,0 +1,112 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Combines two hardware devices into a single I2C master/slave device. +//! +//! If a chip provides a separate hardware implementation for a I2C master and +//! slave device, like the Apollo3 for example, this capsule can be used to +//! combine them into a single `I2CMasterSlave` compatible implementation. +//! +//! This allows the `I2CMasterSlaveDriver` capsule to be implemented on more +//! types of hardware. + +use kernel::hil::i2c::{ + Error, I2CHwMasterClient, I2CHwSlaveClient, I2CMaster, I2CMasterSlave, I2CSlave, +}; + +pub struct I2CMasterSlaveCombo<'a, M: I2CMaster<'a>, S: I2CSlave<'a>> { + i2c_master: &'a M, + i2c_slave: &'a S, +} + +impl<'a, M: I2CMaster<'a>, S: I2CSlave<'a>> I2CMasterSlaveCombo<'a, M, S> { + pub fn new(i2c_master: &'a M, i2c_slave: &'a S) -> I2CMasterSlaveCombo<'a, M, S> { + I2CMasterSlaveCombo { + i2c_master, + i2c_slave, + } + } +} + +impl<'a, M: I2CMaster<'a>, S: I2CSlave<'a>> I2CMaster<'a> for I2CMasterSlaveCombo<'a, M, S> { + fn set_master_client(&self, master_client: &'a dyn I2CHwMasterClient) { + self.i2c_master.set_master_client(master_client) + } + + fn enable(&self) { + self.i2c_master.enable() + } + + fn disable(&self) { + self.i2c_master.disable() + } + + fn write_read( + &self, + addr: u8, + data: &'static mut [u8], + write_len: usize, + read_len: usize, + ) -> Result<(), (Error, &'static mut [u8])> { + self.i2c_master.write_read(addr, data, write_len, read_len) + } + + fn write( + &self, + addr: u8, + data: &'static mut [u8], + len: usize, + ) -> Result<(), (Error, &'static mut [u8])> { + self.i2c_master.write(addr, data, len) + } + + fn read( + &self, + addr: u8, + buffer: &'static mut [u8], + len: usize, + ) -> Result<(), (Error, &'static mut [u8])> { + self.i2c_master.read(addr, buffer, len) + } +} + +impl<'a, M: I2CMaster<'a>, S: I2CSlave<'a>> I2CSlave<'a> for I2CMasterSlaveCombo<'a, M, S> { + fn set_slave_client(&self, slave_client: &'a dyn I2CHwSlaveClient) { + self.i2c_slave.set_slave_client(slave_client); + } + + fn enable(&self) { + self.i2c_slave.enable() + } + + fn disable(&self) { + self.i2c_slave.disable() + } + + fn set_address(&self, addr: u8) -> Result<(), Error> { + self.i2c_slave.set_address(addr) + } + + fn write_receive( + &self, + data: &'static mut [u8], + max_len: usize, + ) -> Result<(), (Error, &'static mut [u8])> { + self.i2c_slave.write_receive(data, max_len) + } + + fn read_send( + &self, + data: &'static mut [u8], + max_len: usize, + ) -> Result<(), (Error, &'static mut [u8])> { + self.i2c_slave.read_send(data, max_len) + } + + fn listen(&self) { + self.i2c_slave.listen() + } +} + +impl<'a, M: I2CMaster<'a>, S: I2CSlave<'a>> I2CMasterSlave<'a> for I2CMasterSlaveCombo<'a, M, S> {} diff --git a/capsules/src/i2c_master_slave_driver.rs b/capsules/core/src/i2c_master_slave_driver.rs similarity index 90% rename from capsules/src/i2c_master_slave_driver.rs rename to capsules/core/src/i2c_master_slave_driver.rs index f1a15005c9..11573c17e7 100644 --- a/capsules/src/i2c_master_slave_driver.rs +++ b/capsules/core/src/i2c_master_slave_driver.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides both an I2C Master and I2C Slave interface to userspace. //! //! By calling `listen` this module will wait for I2C messages @@ -22,9 +26,7 @@ use kernel::{ErrorCode, ProcessId}; use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; -pub static mut BUFFER1: [u8; 256] = [0; 256]; -pub static mut BUFFER2: [u8; 256] = [0; 256]; -pub static mut BUFFER3: [u8; 256] = [0; 256]; +pub const BUFFER_LENGTH: usize = 256; /// Syscall driver number. use crate::driver; @@ -35,7 +37,7 @@ mod ro_allow { pub const MASTER_TX: usize = 0; pub const SLAVE_TX: usize = 2; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 3; + pub const COUNT: u8 = 3; } /// Ids for read-write allow buffers @@ -43,7 +45,7 @@ mod rw_allow { pub const MASTER_RX: usize = 1; pub const SLAVE_RX: usize = 3; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 4; + pub const COUNT: u8 = 4; } #[derive(Default)] @@ -56,8 +58,8 @@ enum MasterAction { WriteRead(u8), } -pub struct I2CMasterSlaveDriver<'a> { - i2c: &'a dyn hil::i2c::I2CMasterSlave, +pub struct I2CMasterSlaveDriver<'a, I: hil::i2c::I2CMasterSlave<'a>> { + i2c: &'a I, listening: Cell, master_action: Cell, // Whether we issued a write or read as master master_buffer: TakeCell<'static, [u8]>, @@ -72,9 +74,9 @@ pub struct I2CMasterSlaveDriver<'a> { >, } -impl<'a> I2CMasterSlaveDriver<'a> { +impl<'a, I: hil::i2c::I2CMasterSlave<'a>> I2CMasterSlaveDriver<'a, I> { pub fn new( - i2c: &'a dyn hil::i2c::I2CMasterSlave, + i2c: &'a I, master_buffer: &'static mut [u8], slave_buffer1: &'static mut [u8], slave_buffer2: &'static mut [u8], @@ -84,7 +86,7 @@ impl<'a> I2CMasterSlaveDriver<'a> { AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<{ rw_allow::COUNT }>, >, - ) -> I2CMasterSlaveDriver<'a> { + ) -> I2CMasterSlaveDriver<'a, I> { I2CMasterSlaveDriver { i2c, listening: Cell::new(false), @@ -98,11 +100,13 @@ impl<'a> I2CMasterSlaveDriver<'a> { } } -impl hil::i2c::I2CHwMasterClient for I2CMasterSlaveDriver<'_> { +impl<'a, I: hil::i2c::I2CMasterSlave<'a>> hil::i2c::I2CHwMasterClient + for I2CMasterSlaveDriver<'a, I> +{ fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), hil::i2c::Error>) { // Map I2C error to a number we can pass back to the application let status = kernel::errorcode::into_statuscode(match status { - Ok(_) => Ok(()), + Ok(()) => Ok(()), Err(e) => Err(e.into()), }); @@ -113,7 +117,7 @@ impl hil::i2c::I2CHwMasterClient for I2CMasterSlaveDriver<'_> { self.master_buffer.replace(buffer); self.app.map(|app| { - let _ = self.apps.enter(*app, |_, kernel_data| { + let _ = self.apps.enter(app, |_, kernel_data| { kernel_data.schedule_upcall(0, (0, status, 0)).ok(); }); }); @@ -121,7 +125,7 @@ impl hil::i2c::I2CHwMasterClient for I2CMasterSlaveDriver<'_> { MasterAction::Read(read_len) => { self.app.map(|app| { - let _ = self.apps.enter(*app, |_, kernel_data| { + let _ = self.apps.enter(app, |_, kernel_data| { // Because this (somewhat incorrectly) doesn't report // back how many bytes were read, the result of mut_enter // is ignored. Note that this requires userspace to keep @@ -151,7 +155,7 @@ impl hil::i2c::I2CHwMasterClient for I2CMasterSlaveDriver<'_> { MasterAction::WriteRead(read_len) => { self.app.map(|app| { - let _ = self.apps.enter(*app, |_, kernel_data| { + let _ = self.apps.enter(app, |_, kernel_data| { // Because this (somewhat incorrectly) doesn't report // back how many bytes were read, the result of mut_enter // is ignored. Note that this requires userspace to keep @@ -185,11 +189,13 @@ impl hil::i2c::I2CHwMasterClient for I2CMasterSlaveDriver<'_> { } } -impl hil::i2c::I2CHwSlaveClient for I2CMasterSlaveDriver<'_> { +impl<'a, I: hil::i2c::I2CMasterSlave<'a>> hil::i2c::I2CHwSlaveClient + for I2CMasterSlaveDriver<'a, I> +{ fn command_complete( &self, buffer: &'static mut [u8], - length: u8, + length: usize, transmission_type: hil::i2c::SlaveTransmissionType, ) { // Need to know if read or write @@ -200,7 +206,7 @@ impl hil::i2c::I2CHwSlaveClient for I2CMasterSlaveDriver<'_> { match transmission_type { hil::i2c::SlaveTransmissionType::Write => { self.app.map(|app| { - let _ = self.apps.enter(*app, |_, kernel_data| { + let _ = self.apps.enter(app, |_, kernel_data| { kernel_data .get_readwrite_processbuffer(rw_allow::SLAVE_RX) .and_then(|slave_rx| { @@ -214,7 +220,7 @@ impl hil::i2c::I2CHwSlaveClient for I2CMasterSlaveDriver<'_> { // userspace. The I2C syscall API should pass back lengths. // -pal 3/5/21 let buf_len = cmp::min(app_rx.len(), buffer.len()); - let read_len = cmp::min(buf_len, length as usize); + let read_len = cmp::min(buf_len, length); for (i, c) in buffer[0..read_len].iter_mut().enumerate() { app_rx[i].set(*c); @@ -226,7 +232,7 @@ impl hil::i2c::I2CHwSlaveClient for I2CMasterSlaveDriver<'_> { }) .unwrap_or(0); - kernel_data.schedule_upcall(0, (3, length as usize, 0)).ok(); + kernel_data.schedule_upcall(0, (3, length, 0)).ok(); }); }); } @@ -236,8 +242,8 @@ impl hil::i2c::I2CHwSlaveClient for I2CMasterSlaveDriver<'_> { // Notify the app that the read finished self.app.map(|app| { - let _ = self.apps.enter(*app, |_, kernel_data| { - kernel_data.schedule_upcall(0, (4, length as usize, 0)).ok(); + let _ = self.apps.enter(app, |_, kernel_data| { + kernel_data.schedule_upcall(0, (4, length, 0)).ok(); }); }); } @@ -248,7 +254,7 @@ impl hil::i2c::I2CHwSlaveClient for I2CMasterSlaveDriver<'_> { // Pass this up to the client. Not much we can do until the application // has setup a buffer to read from. self.app.map(|app| { - let _ = self.apps.enter(*app, |_, kernel_data| { + let _ = self.apps.enter(app, |_, kernel_data| { // Ask the app to setup a read buffer. The app must call // command 3 after it has setup the shared read buffer with // the correct bytes. @@ -269,7 +275,7 @@ impl hil::i2c::I2CHwSlaveClient for I2CMasterSlaveDriver<'_> { } } -impl SyscallDriver for I2CMasterSlaveDriver<'_> { +impl<'a, I: hil::i2c::I2CMasterSlave<'a>> SyscallDriver for I2CMasterSlaveDriver<'a, I> { fn command( &self, command_num: usize, @@ -278,15 +284,14 @@ impl SyscallDriver for I2CMasterSlaveDriver<'_> { process_id: ProcessId, ) -> CommandReturn { if command_num == 0 { - // Handle this first as it should be returned - // unconditionally + // Handle unconditional driver existence check. return CommandReturn::success(); } // Check if this non-virtualized driver is already in use by // some (alive) process let match_or_empty_or_nonexistant = self.app.map_or(true, |current_process| { self.apps - .enter(*current_process, |_, _| current_process == &process_id) + .enter(current_process, |_, _| current_process == process_id) .unwrap_or(true) }); if match_or_empty_or_nonexistant { @@ -330,10 +335,7 @@ impl SyscallDriver for I2CMasterSlaveDriver<'_> { hil::i2c::I2CMaster::enable(self.i2c); // TODO verify errors let _ = hil::i2c::I2CMaster::write( - self.i2c, - address, - kernel_tx, - write_len as u8, + self.i2c, address, kernel_tx, write_len, ); }); 0 @@ -375,10 +377,7 @@ impl SyscallDriver for I2CMasterSlaveDriver<'_> { hil::i2c::I2CMaster::enable(self.i2c); // TODO verify errors let _ = hil::i2c::I2CMaster::read( - self.i2c, - address, - kernel_tx, - read_len as u8, + self.i2c, address, kernel_tx, read_len, ); }); 0 @@ -435,9 +434,7 @@ impl SyscallDriver for I2CMasterSlaveDriver<'_> { // TODO verify errors let _ = hil::i2c::I2CSlave::read_send( - self.i2c, - kernel_tx, - read_len as u8, + self.i2c, kernel_tx, read_len, ); }); 0 @@ -500,11 +497,7 @@ impl SyscallDriver for I2CMasterSlaveDriver<'_> { hil::i2c::I2CMaster::enable(self.i2c); // TODO verify errors let _ = hil::i2c::I2CMaster::write_read( - self.i2c, - address, - kernel_tx, - write_len as u8, - read_len as u8, + self.i2c, address, kernel_tx, write_len, read_len, ); }); }) diff --git a/capsules/src/led.rs b/capsules/core/src/led.rs similarity index 86% rename from capsules/src/led.rs rename to capsules/core/src/led.rs index 3f84cee3ac..0e067175f6 100644 --- a/capsules/src/led.rs +++ b/capsules/core/src/led.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides userspace access to LEDs on a board. //! //! This allows for much more cross platform controlling of LEDs without having @@ -12,7 +16,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! let led_pins = static_init!( @@ -21,8 +25,8 @@ //! (&sam4l::gpio::PA[15], kernel::hil::gpio::ActivationMode::ActiveLow), // Green //! (&sam4l::gpio::PA[14], kernel::hil::gpio::ActivationMode::ActiveLow)]); // Blue //! let led = static_init!( -//! capsules::led::LED<'static, sam4l::gpio::GPIOPin>, -//! capsules::led::LED::new(led_pins)); +//! capsules_core::led::LED<'static, sam4l::gpio::GPIOPin>, +//! capsules_core::led::LED::new(led_pins)); //! ``` //! //! Syscall Interface @@ -61,18 +65,18 @@ pub const DRIVER_NUM: usize = driver::NUM::Led as usize; /// Holds the array of LEDs and implements a `Driver` interface to /// control them. pub struct LedDriver<'a, L: led::Led, const NUM_LEDS: usize> { - leds: &'a mut [&'a L; NUM_LEDS], + leds: &'a [&'a L; NUM_LEDS], } impl<'a, L: led::Led, const NUM_LEDS: usize> LedDriver<'a, L, NUM_LEDS> { - pub fn new(leds: &'a mut [&'a L; NUM_LEDS]) -> Self { + pub fn new(leds: &'a [&'a L; NUM_LEDS]) -> Self { // Initialize all LEDs and turn them off - for led in leds.iter_mut() { + for led in leds.iter() { led.init(); led.off(); } - Self { leds: leds } + Self { leds } } } @@ -92,6 +96,9 @@ impl SyscallDriver for LedDriver<'_, L, NUM_ fn command(&self, command_num: usize, data: usize, _: usize, _: ProcessId) -> CommandReturn { match command_num { // get number of LEDs + // TODO(Tock 3.0): TRD104 specifies that Command 0 should return Success, not SuccessU32, + // but this driver is unchanged since it has been stabilized. It will be brought into + // compliance as part of the next major release of Tock. See #3375. 0 => CommandReturn::success_u32(NUM_LEDS as u32), // on diff --git a/capsules/core/src/lib.rs b/capsules/core/src/lib.rs new file mode 100644 index 0000000000..3528b00f03 --- /dev/null +++ b/capsules/core/src/lib.rs @@ -0,0 +1,29 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +#![forbid(unsafe_code)] +#![no_std] + +pub mod test; + +#[macro_use] +pub mod stream; + +pub mod adc; +pub mod alarm; +pub mod button; +pub mod console; +pub mod console_ordered; +pub mod driver; +pub mod gpio; +pub mod i2c_master; +pub mod i2c_master_slave_combo; +pub mod i2c_master_slave_driver; +pub mod led; +pub mod low_level_debug; +pub mod process_console; +pub mod rng; +pub mod spi_controller; +pub mod spi_peripheral; +pub mod virtualizers; diff --git a/capsules/src/low_level_debug/README.md b/capsules/core/src/low_level_debug/README.md similarity index 79% rename from capsules/src/low_level_debug/README.md rename to capsules/core/src/low_level_debug/README.md index bb6a65d11d..e5550c1ca1 100644 --- a/capsules/src/low_level_debug/README.md +++ b/capsules/core/src/low_level_debug/README.md @@ -4,4 +4,4 @@ Low Level Debug The Low Level Debug capsule provides debugging facilities to userspace for debugging low-level issues such as miscompilation and incorrect relocation. It implements the system call API described -[here](../../../doc/syscalls/00008_low_level_debug.md). +[here](../../../../doc/syscalls/00008_low_level_debug.md). diff --git a/capsules/src/low_level_debug/fmt.rs b/capsules/core/src/low_level_debug/fmt.rs similarity index 94% rename from capsules/src/low_level_debug/fmt.rs rename to capsules/core/src/low_level_debug/fmt.rs index 08cd6e2b52..aec9360ec1 100644 --- a/capsules/src/low_level_debug/fmt.rs +++ b/capsules/core/src/low_level_debug/fmt.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + /// fmt contains formatting routines for LowLevelDebug's console messages as /// well as the buffer size necessary for the messages. use super::DebugEntry; diff --git a/capsules/src/low_level_debug/mod.rs b/capsules/core/src/low_level_debug/mod.rs similarity index 92% rename from capsules/src/low_level_debug/mod.rs rename to capsules/core/src/low_level_debug/mod.rs index cf70a4a4ca..71f64beb5b 100644 --- a/capsules/src/low_level_debug/mod.rs +++ b/capsules/core/src/low_level_debug/mod.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides low-level debugging functionality to userspace. The system call //! interface is documented in doc/syscalls/00008_low_level_debug.md. @@ -91,10 +95,10 @@ impl<'u, U: Transmit<'u>> TransmitClient for LowLevelDebug<'u, U> { } for process_grant in self.grant.iter() { - let appid = process_grant.processid(); + let processid = process_grant.processid(); let (app_num, first_entry) = process_grant.enter(|owned_app_data, _| { owned_app_data.queue.rotate_left(1); - (appid.id(), owned_app_data.queue[QUEUE_SIZE - 1].take()) + (processid.id(), owned_app_data.queue[QUEUE_SIZE - 1].take()) }); let to_print = match first_entry { None => continue, @@ -114,15 +118,15 @@ impl<'u, U: Transmit<'u>> TransmitClient for LowLevelDebug<'u, U> { impl<'u, U: Transmit<'u>> LowLevelDebug<'u, U> { // If the UART is not busy (the buffer is available), transmits the entry. // Otherwise, adds it to the app's queue. - fn push_entry(&self, entry: DebugEntry, appid: ProcessId) { + fn push_entry(&self, entry: DebugEntry, processid: ProcessId) { use DebugEntry::Dropped; if let Some(buffer) = self.buffer.take() { - self.transmit_entry(buffer, appid.id(), entry); + self.transmit_entry(buffer, processid.id(), entry); return; } - let result = self.grant.enter(appid, |borrow, _| { + let result = self.grant.enter(processid, |borrow, _| { for queue_entry in &mut borrow.queue { if queue_entry.is_none() { *queue_entry = Some(entry); diff --git a/capsules/src/process_console.rs b/capsules/core/src/process_console.rs similarity index 52% rename from capsules/src/process_console.rs rename to capsules/core/src/process_console.rs index 6612b73246..d35f22b666 100644 --- a/capsules/src/process_console.rs +++ b/capsules/core/src/process_console.rs @@ -1,111 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implements a text console over the UART that allows //! a terminal to inspect and control userspace processes. //! -//! Protocol -//! -------- -//! -//! This module provides a simple text-based console to inspect and control -//! which processes are running. The console has five commands: -//! - 'help' prints the available commands and arguments -//! - 'status' prints the current system status -//! - 'list' lists the current processes with their IDs and running state -//! - 'stop n' stops the process with name n -//! - 'start n' starts the stopped process with name n -//! - 'fault n' forces the process with name n into a fault state -//! - 'panic' causes the kernel to run the panic handler -//! - 'process n' prints the memory map of process with name n -//! - 'kernel' prints the kernel memory map -//! -//! ### `list` Command Fields: -//! -//! - `PID`: The identifier for the process. This can change if the process -//! restarts. -//! - `Name`: The process name. -//! - `Quanta`: How many times this process has exceeded its alloted time -//! quanta. -//! - `Syscalls`: The number of system calls the process has made to the kernel. -//! - `Dropped Upcalls`: How many upcalls were dropped for this process -//! because the queue was full. -//! - `Restarts`: How many times this process has crashed and been restarted by -//! the kernel. -//! - `State`: The state the process is in. -//! - `Grants`: The number of grants that have been initialized for the process -//! out of the total number of grants defined by the kernel. -//! -//! Setup -//! ----- -//! -//! You need a device that provides the `hil::uart::UART` trait. This code -//! connects a `ProcessConsole` directly up to USART0: -//! -//! ```rust -//! # use kernel::{capabilities, hil, static_init}; -//! # use capsules::process_console::ProcessConsole; -//! -//! pub struct Capability; -//! unsafe impl capabilities::ProcessManagementCapability for Capability {} -//! -//! let pconsole = static_init!( -//! ProcessConsole, -//! ProcessConsole::new(&usart::USART0, -//! 115200, -//! &mut console::WRITE_BUF, -//! &mut console::READ_BUF, -//! &mut console::COMMAND_BUF, -//! kernel, -//! Capability)); -//! hil::uart::UART::set_client(&usart::USART0, pconsole); -//! -//! pconsole.start(); -//! ``` -//! -//! Using ProcessConsole -//! -------------------- -//! -//! With this capsule properly added to a board's `main.rs` and that kernel -//! loaded to the board, make sure there is a serial connection to the board. -//! Likely, this just means connecting a USB cable from a computer to the board. -//! Next, establish a serial console connection to the board. An easy way to do -//! this is to run: -//! -//! ```shell -//! $ tockloader listen -//! ``` -//! -//! With that console open, you can issue commands. For example, to see all of -//! the processes on the board, use `list`: -//! -//! ```text -//! $ tockloader listen -//! Using "/dev/cu.usbserial-c098e513000c - Hail IoT Module - TockOS" -//! -//! Listening for serial output. -//! ProcessConsole::start -//! Starting process console -//! Initialization complete. Entering main loop -//! Hello World! -//! list -//! PID Name Quanta Syscalls Dropped Upcalls Restarts State Grants -//! 00 blink 0 113 0 0 Yielded 1/12 -//! 01 c_hello 0 8 0 0 Yielded 3/12 -//! ``` -//! -//! To get a general view of the system, use the status command: -//! -//! ```text -//! status -//! Total processes: 2 -//! Active processes: 2 -//! Timeslice expirations: 0 -//! ``` -//! -//! and you can control processes with the `start` and `stop` commands: -//! -//! ```text -//! stop blink -//! Process blink stopped -//! ``` - +//! For a more in-depth documentation check /doc/Process_Console.md use core::cell::Cell; use core::cmp; use core::fmt; @@ -113,6 +13,7 @@ use core::fmt::write; use core::str; use kernel::capabilities::ProcessManagementCapability; use kernel::hil::time::ConvertTicks; +use kernel::utilities::cells::MapCell; use kernel::utilities::cells::TakeCell; use kernel::ProcessId; @@ -120,28 +21,60 @@ use kernel::debug; use kernel::hil::time::{Alarm, AlarmClient}; use kernel::hil::uart; use kernel::introspection::KernelInfo; -use kernel::process::{ProcessPrinter, ProcessPrinterContext}; +use kernel::process::{ProcessPrinter, ProcessPrinterContext, State}; use kernel::utilities::binary_write::BinaryWrite; use kernel::ErrorCode; use kernel::Kernel; /// Buffer to hold outgoing data that is passed to the UART hardware. -pub static mut WRITE_BUF: [u8; 500] = [0; 500]; +pub const WRITE_BUF_LEN: usize = 500; /// Buffer responses are initially held in until copied to the TX buffer and /// transmitted. -pub static mut QUEUE_BUF: [u8; 300] = [0; 300]; +pub const QUEUE_BUF_LEN: usize = 300; /// Since reads are byte-by-byte, to properly echo what's typed, /// we can use a very small read buffer. -pub static mut READ_BUF: [u8; 4] = [0; 4]; +pub const READ_BUF_LEN: usize = 4; /// Commands can be up to 32 bytes long: since commands themselves are 4-5 /// characters, limiting arguments to 25 bytes or so seems fine for now. -pub static mut COMMAND_BUF: [u8; 32] = [0; 32]; +pub const COMMAND_BUF_LEN: usize = 32; +/// Default size for the history command. +pub const DEFAULT_COMMAND_HISTORY_LEN: usize = 10; + +/// List of valid commands for printing help. Consolidated as these are +/// displayed in a few different cases. +const VALID_COMMANDS_STR: &[u8] = + b"help status list stop start fault boot terminate process kernel reset panic console-start console-stop\r\n"; + +/// Escape character for ANSI escape sequences. +const ESC: u8 = b'\x1B'; + +/// End of line character. +const EOL: u8 = b'\x00'; + +/// Backspace ANSI character +const BS: u8 = b'\x08'; + +/// Delete ANSI character +const DEL: u8 = b'\x7F'; + +/// Space ANSI character +const SPACE: u8 = b'\x20'; + +/// Carriage return ANSI character +const CR: u8 = b'\x0D'; + +/// Newline ANSI character +const NLINE: u8 = b'\x0A'; + +/// Upper limit for ASCII characters +const ASCII_LIMIT: u8 = 128; /// States used for state machine to allow printing large strings asynchronously /// across multiple calls. This reduces the size of the buffer needed to print /// each section of the debug message. -#[derive(PartialEq, Eq, Copy, Clone)] +#[derive(PartialEq, Eq, Copy, Clone, Default)] enum WriterState { + #[default] Empty, KernelStart, KernelBss, @@ -159,9 +92,101 @@ enum WriterState { }, } -impl Default for WriterState { - fn default() -> Self { - WriterState::Empty +/// Key that can be part from an escape sequence. +#[derive(Copy, Clone)] +enum EscKey { + Up, + Down, + Left, + Right, + Home, + End, + Delete, +} + +/// Escape state machine to check if +/// an escape sequence has occured +#[derive(Copy, Clone)] +enum EscState { + /// This state is reached when the character is a normal + /// ANSI character, and the escape sequence is bypassed. + Bypass, + + /// This state is reached when an escape sequence + /// is completed, and the corresponding EscKey is processed. + Complete(EscKey), + + /// This state is reached when an escape sequence has + /// just started and is waiting for the next + /// character to complete the sequence. + Started, + + /// This state is reached when the escape sequence + /// starts with a bracket character '[' and is waiting + /// for the next character to determine the corresponding EscKey. + Bracket, + Bracket3, + + /// This state is reached when the current character does not match + /// any of the expected characters in the escape sequence. + /// Once entered in this state, the escape sequence cannot be processed + /// and is waiting for an ascii alphabetic character to complete + /// the unrecognized sequence. + Unrecognized, + + /// This state is reached when the escape sequence has ended with + /// an unrecognized character. This state waits for an ascii + /// alphabetic character to terminate the unrecognized sequence. + UnrecognizedDone, +} + +impl EscState { + fn next_state(self, data: u8) -> Self { + use self::{ + EscKey::{Delete, Down, End, Home, Left, Right, Up}, + EscState::{ + Bracket, Bracket3, Bypass, Complete, Started, Unrecognized, UnrecognizedDone, + }, + }; + match (self, data) { + (Bypass, ESC) | (UnrecognizedDone, ESC) | (Complete(_), ESC) => Started, + // This is a short-circuit. + // ASCII DEL and ANSI Escape Sequence "Delete" should be treated the same way. + (Bypass, DEL) | (UnrecognizedDone, DEL) | (Complete(_), DEL) => Complete(Delete), + (Bypass, _) | (UnrecognizedDone, _) | (Complete(_), _) => Bypass, + (Started, b'[') => Bracket, + (Bracket, b'A') => Complete(Up), + (Bracket, b'B') => Complete(Down), + (Bracket, b'D') => Complete(Left), + (Bracket, b'C') => Complete(Right), + (Bracket, b'H') => Complete(Home), + (Bracket, b'F') => Complete(End), + (Bracket, b'3') => Bracket3, + (Bracket3, b'~') => Complete(Delete), + _ => { + if EscState::terminator_esc_char(data) { + UnrecognizedDone + } else { + Unrecognized + } + } + } + } + + /// Checks if the escape state machine is in the middle + /// of an escape sequence + fn in_progress(&self) -> bool { + matches!(self, EscState::Bracket) || matches!(self, EscState::Bracket3) + } + + /// Checks if the escape state machine is at the start + /// of processing an escape sequence + fn has_started(&self) -> bool { + matches!(self, EscState::Started) + } + + fn terminator_esc_char(data: u8) -> bool { + data.is_ascii_alphabetic() || data == b'~' } } @@ -182,7 +207,26 @@ pub struct KernelAddresses { pub bss_end: *const u8, } -pub struct ProcessConsole<'a, A: Alarm<'a>, C: ProcessManagementCapability> { +/// Track the operational state of the process console. +#[derive(Clone, Copy, PartialEq)] +enum ProcessConsoleState { + /// The console has not been started and is not listening for UART commands. + Off, + /// The console has been started and is running normally. + Active, + /// The console has been started (i.e. it has called receive), but it is not + /// actively listening to commands or showing the prompt. This mode enables + /// the console to be installed on a board but to not interfere with a + /// console-based app. + Hibernating, +} + +pub struct ProcessConsole< + 'a, + const COMMAND_HISTORY_LEN: usize, + A: Alarm<'a>, + C: ProcessManagementCapability, +> { uart: &'a dyn uart::UartData<'a>, alarm: &'a A, process_printer: &'a dyn ProcessPrinter, @@ -191,19 +235,27 @@ pub struct ProcessConsole<'a, A: Alarm<'a>, C: ProcessManagementCapability> { queue_buffer: TakeCell<'static, [u8]>, queue_size: Cell, writer_state: Cell, - rx_in_progress: Cell, rx_buffer: TakeCell<'static, [u8]>, command_buffer: TakeCell<'static, [u8]>, command_index: Cell, + /// Operational mode the console is in. This includes if it is actively + /// responding to commands. + mode: Cell, + + /// Escape state machine in order to process an escape sequence + esc_state: Cell, + + /// Keep a history of inserted commands + command_history: MapCell>, + + /// Cursor index in the current typing command + cursor: Cell, + /// Keep the previously read byte to consider \r\n sequences /// as a single \n. previous_byte: Cell, - /// Flag to mark that the process console is active and has called receive - /// from the underlying UART. - running: Cell, - /// Internal flag that the process console should parse the command it just /// received after finishing echoing the last newline character. execute: Cell, @@ -214,11 +266,138 @@ pub struct ProcessConsole<'a, A: Alarm<'a>, C: ProcessManagementCapability> { /// Memory addresses of where the kernel is placed in memory on chip. kernel_addresses: KernelAddresses, + /// Function used to reset the device in bootloader mode + reset_function: Option !>, + /// This capsule needs to use potentially dangerous APIs related to /// processes, and requires a capability to access those APIs. capability: C, } +#[derive(Copy, Clone)] +pub struct Command { + buf: [u8; COMMAND_BUF_LEN], + len: usize, +} + +impl Command { + /// Write the buffer with the provided data. + /// If the provided data's length is smaller than the buffer length, + /// the left over bytes are not modified due to '\0' termination. + fn write(&mut self, buf: &[u8; COMMAND_BUF_LEN]) { + self.len = buf + .iter() + .position(|a| *a == EOL) + .unwrap_or(COMMAND_BUF_LEN); + + (self.buf).copy_from_slice(buf); + } + + fn insert_byte(&mut self, byte: u8, pos: usize) { + for i in (pos..self.len).rev() { + self.buf[i + 1] = self.buf[i]; + } + + if let Some(buf_byte) = self.buf.get_mut(pos) { + *buf_byte = byte; + self.len += 1; + } + } + + fn delete_byte(&mut self, pos: usize) { + for i in pos..self.len { + self.buf[i] = self.buf[i + 1]; + } + + if let Some(buf_byte) = self.buf.get_mut(self.len - 1) { + *buf_byte = EOL; + self.len -= 1; + } + } + + fn clear(&mut self) { + self.buf.iter_mut().for_each(|x| *x = EOL); + self.len = 0; + } +} + +impl Default for Command { + fn default() -> Self { + Command { + buf: [EOL; COMMAND_BUF_LEN], + len: 0, + } + } +} + +impl PartialEq<[u8; COMMAND_BUF_LEN]> for Command { + fn eq(&self, other_buf: &[u8; COMMAND_BUF_LEN]) -> bool { + self.buf + .iter() + .zip(other_buf.iter()) + .take_while(|(a, b)| **a != EOL || **b != EOL) + .all(|(a, b)| *a == *b) + } +} + +struct CommandHistory<'a, const COMMAND_HISTORY_LEN: usize> { + cmds: &'a mut [Command; COMMAND_HISTORY_LEN], + cmd_idx: usize, + cmd_is_modified: bool, +} + +impl<'a, const COMMAND_HISTORY_LEN: usize> CommandHistory<'a, COMMAND_HISTORY_LEN> { + fn new(cmds_buffer: &'a mut [Command; COMMAND_HISTORY_LEN]) -> Self { + Self { + cmds: cmds_buffer, + cmd_idx: 0, + cmd_is_modified: false, + } + } + + /// Creates an empty space in the history for the next command + fn make_space(&mut self, cmd: &[u8]) { + let mut cmd_arr = [0; COMMAND_BUF_LEN]; + cmd_arr.copy_from_slice(cmd); + + if self.cmds[1] != cmd_arr { + self.cmds.rotate_right(1); + self.cmds[0].clear(); + self.cmds[1].write(&cmd_arr); + } + } + + fn write_to_first(&mut self, cmd: &[u8]) { + let mut cmd_arr = [0; COMMAND_BUF_LEN]; + cmd_arr.copy_from_slice(cmd); + self.cmds[0].write(&cmd_arr); + } + + // Set the next index in the command history + fn next_cmd_idx(&mut self) -> Option { + if self.cmd_idx + 1 >= COMMAND_HISTORY_LEN { + None + } else if self.cmds[self.cmd_idx + 1].len == 0 { + None + } else { + self.cmd_idx += 1; + + Some(self.cmd_idx) + } + } + + // Set the previous index in the command history + fn prev_cmd_idx(&mut self) -> Option { + if self.cmd_idx > 0 { + self.cmd_idx -= 1; + + Some(self.cmd_idx) + } else { + None + } + } +} + pub struct ConsoleWriter { buf: [u8; 500], size: usize, @@ -226,7 +405,7 @@ pub struct ConsoleWriter { impl ConsoleWriter { pub fn new() -> ConsoleWriter { ConsoleWriter { - buf: [0; 500], + buf: [EOL; 500], size: 0, } } @@ -237,7 +416,7 @@ impl ConsoleWriter { impl fmt::Write for ConsoleWriter { fn write_str(&mut self, s: &str) -> fmt::Result { let curr = (s).as_bytes().len(); - self.buf[self.size..self.size + curr].copy_from_slice(&(s).as_bytes()[..]); + self.buf[self.size..self.size + curr].copy_from_slice((s).as_bytes()); self.size += curr; Ok(()) } @@ -254,7 +433,9 @@ impl BinaryWrite for ConsoleWriter { } } -impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> { +impl<'a, const COMMAND_HISTORY_LEN: usize, A: Alarm<'a>, C: ProcessManagementCapability> + ProcessConsole<'a, COMMAND_HISTORY_LEN, A, C> +{ pub fn new( uart: &'a dyn uart::UartData<'a>, alarm: &'a A, @@ -263,40 +444,56 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> rx_buffer: &'static mut [u8], queue_buffer: &'static mut [u8], cmd_buffer: &'static mut [u8], + cmd_history_buffer: &'static mut [Command; COMMAND_HISTORY_LEN], kernel: &'static Kernel, kernel_addresses: KernelAddresses, + reset_function: Option !>, capability: C, - ) -> ProcessConsole<'a, A, C> { + ) -> ProcessConsole<'a, COMMAND_HISTORY_LEN, A, C> { ProcessConsole { - uart: uart, - alarm: alarm, + uart, + alarm, process_printer, tx_in_progress: Cell::new(false), tx_buffer: TakeCell::new(tx_buffer), queue_buffer: TakeCell::new(queue_buffer), queue_size: Cell::new(0), writer_state: Cell::new(WriterState::Empty), - rx_in_progress: Cell::new(false), rx_buffer: TakeCell::new(rx_buffer), command_buffer: TakeCell::new(cmd_buffer), command_index: Cell::new(0), - - previous_byte: Cell::new(0), - - running: Cell::new(false), + mode: Cell::new(ProcessConsoleState::Off), + esc_state: Cell::new(EscState::Bypass), + command_history: MapCell::new(CommandHistory::new(cmd_history_buffer)), + cursor: Cell::new(0), + previous_byte: Cell::new(EOL), execute: Cell::new(false), - kernel: kernel, - kernel_addresses: kernel_addresses, - capability: capability, + kernel, + kernel_addresses, + reset_function, + capability, } } /// Start the process console listening for user commands. pub fn start(&self) -> Result<(), ErrorCode> { - if self.running.get() == false { + if self.mode.get() == ProcessConsoleState::Off { self.alarm .set_alarm(self.alarm.now(), self.alarm.ticks_from_ms(100)); - self.running.set(true); + self.mode.set(ProcessConsoleState::Active); + } + Ok(()) + } + + /// Start the process console listening but in a hibernated state. + /// + /// The process console will not respond to commands, but can be activated + /// with the `console-start` command. + pub fn start_hibernated(&self) -> Result<(), ErrorCode> { + if self.mode.get() == ProcessConsoleState::Off { + self.alarm + .set_alarm(self.alarm.now(), self.alarm.ticks_from_ms(100)); + self.mode.set(ProcessConsoleState::Hibernating) } Ok(()) } @@ -305,11 +502,10 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> /// message. pub fn display_welcome(&self) { // Start if not already started. - if self.running.get() == false { + if self.mode.get() == ProcessConsoleState::Off { self.rx_buffer.take().map(|buffer| { - self.rx_in_progress.set(true); let _ = self.uart.receive_buffer(buffer, 1); - self.running.set(true); + self.mode.set(ProcessConsoleState::Active); }); } @@ -319,16 +515,16 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> &mut console_writer, format_args!( "Kernel version: {}.{} (build {})\r\n", - kernel::MAJOR, - kernel::MINOR, + kernel::KERNEL_MAJOR_VERSION, + kernel::KERNEL_MINOR_VERSION, option_env!("TOCK_KERNEL_VERSION").unwrap_or("unknown"), ), ); let _ = self.write_bytes(&(console_writer.buf)[..console_writer.size]); - let _ = self.write_bytes(b"Welcome to the process console.\n"); - let _ = self - .write_bytes(b"Valid commands are: help status list stop start fault process kernel\n"); + let _ = self.write_bytes(b"Welcome to the process console.\r\n"); + let _ = self.write_bytes(b"Valid commands are: "); + let _ = self.write_bytes(VALID_COMMANDS_STR); self.prompt(); } @@ -381,7 +577,7 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> "\r\n ╔═══════════╤══════════════════════════════╗\ \r\n ║ Address │ Region Name Used (bytes) ║\ \r\n ╚{:#010X}═╪══════════════════════════════╝\ - \r\n │ Bss {:6}", + \r\n │ BSS {:6}", bss_end, bss_size ), ); @@ -482,11 +678,15 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> if new_context.is_some() { self.writer_state.replace(WriterState::ProcessPrint { - process_id: process_id, + process_id, context: new_context, }); } else { self.writer_state.replace(WriterState::Empty); + // As setting the next state here to Empty does not + // go through this match again before reading a new command, + // we have to print the prompt here. + self.prompt(); } } }); @@ -501,22 +701,39 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> let pname = process.get_process_name(); let process_id = process.processid(); + let short_id = process.short_app_id(); + let (grants_used, grants_total) = info.number_app_grant_uses(process_id, &self.capability); let mut console_writer = ConsoleWriter::new(); + + // Display process id. + let _ = write(&mut console_writer, format_args!(" {:<7?}", process_id)); + // Display short id. + match short_id { + kernel::process::ShortId::LocallyUnique => { + let _ = write( + &mut console_writer, + format_args!("{}", "Unique ",), + ); + } + kernel::process::ShortId::Fixed(id) => { + let _ = + write(&mut console_writer, format_args!("0x{:<8x} ", id)); + } + } + // Display everything else. let _ = write( &mut console_writer, format_args!( - " {:?}\t{:<20}{:6}{:10}{:17}{:10} {:?}{:5}/{}\n", - process_id, + "{:<20}{:6}{:10}{:10} {:2}/{:2} {:?}\r\n", pname, process.debug_timeslice_expiration_count(), process.debug_syscall_count(), - process.debug_dropped_upcall_count(), process.get_restart_count(), - process.get_state(), grants_used, - grants_total + grants_total, + process.get_state(), ), ); @@ -553,11 +770,30 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> Ok(s) => { let clean_str = s.trim(); - if clean_str.starts_with("help") { - let _ = self.write_bytes(b"Welcome to the process console.\n"); + // Check if the command history is enabled by the user + // and check if the command is not full of whitespaces + if COMMAND_HISTORY_LEN > 1 { + if clean_str.len() > 0 { + self.command_history.map(|ht| { + ht.make_space(command); + }); + } + } + + if clean_str.starts_with("console-start") { + self.mode.set(ProcessConsoleState::Active); + } else if self.mode.get() == ProcessConsoleState::Hibernating { + // Ignore all commands in hibernating mode. We put + // this case early so we ensure we get stuck here + // even if the user typed a valid command. + } else if clean_str.starts_with("help") { + let _ = self.write_bytes(b"Welcome to the process console.\r\n"); let _ = self.write_bytes(b"Valid commands are: "); - let _ = self - .write_bytes(b"help status list stop start fault process kernel\n"); + let _ = self.write_bytes(VALID_COMMANDS_STR); + } else if clean_str.starts_with("console-stop") { + let _ = self.write_bytes(b"Disabling the process console.\r\n"); + let _ = self.write_bytes(b"Run console-start to reactivate.\r\n"); + self.mode.set(ProcessConsoleState::Hibernating); } else if clean_str.starts_with("start") { let argument = clean_str.split_whitespace().nth(1); argument.map(|name| { @@ -569,7 +805,7 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> let mut console_writer = ConsoleWriter::new(); let _ = write( &mut console_writer, - format_args!("Process {} resumed.\n", name), + format_args!("Process {} resumed.\r\n", name), ); let _ = self.write_bytes( @@ -589,7 +825,7 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> let mut console_writer = ConsoleWriter::new(); let _ = write( &mut console_writer, - format_args!("Process {} stopped\n", proc_name), + format_args!("Process {} stopped\r\n", proc_name), ); let _ = self.write_bytes( @@ -609,7 +845,30 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> let mut console_writer = ConsoleWriter::new(); let _ = write( &mut console_writer, - format_args!("Process {} now faulted\n", proc_name), + format_args!( + "Process {} now faulted\r\n", + proc_name + ), + ); + + let _ = self.write_bytes( + &(console_writer.buf)[..console_writer.size], + ); + } + }); + }); + } else if clean_str.starts_with("terminate") { + let argument = clean_str.split_whitespace().nth(1); + argument.map(|name| { + self.kernel + .process_each_capability(&self.capability, |proc| { + let proc_name = proc.get_process_name(); + if proc_name == name { + proc.terminate(None); + let mut console_writer = ConsoleWriter::new(); + let _ = write( + &mut console_writer, + format_args!("Process {} terminated\n", proc_name), ); let _ = self.write_bytes( @@ -618,10 +877,23 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> } }); }); + } else if clean_str.starts_with("boot") { + let argument = clean_str.split_whitespace().nth(1); + argument.map(|name| { + self.kernel + .process_each_capability(&self.capability, |proc| { + let proc_name = proc.get_process_name(); + if proc_name == name + && proc.get_state() == State::Terminated + { + proc.try_restart(None); + } + }); + }); } else if clean_str.starts_with("list") { - let _ = self.write_bytes(b" PID Name Quanta "); - let _ = self.write_bytes(b"Syscalls Dropped Upcalls "); - let _ = self.write_bytes(b"Restarts State Grants\n"); + let _ = self + .write_bytes(b" PID ShortID Name Quanta "); + let _ = self.write_bytes(b"Syscalls Restarts Grants State\r\n"); // Count the number of current processes. let mut count = 0; @@ -642,7 +914,7 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> let _ = write( &mut console_writer, format_args!( - "Total processes: {}\n", + "Total processes: {}\r\n", info.number_loaded_processes(&self.capability) ), ); @@ -651,7 +923,7 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> let _ = write( &mut console_writer, format_args!( - "Active processes: {}\n", + "Active processes: {}\r\n", info.number_active_processes(&self.capability) ), ); @@ -660,7 +932,7 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> let _ = write( &mut console_writer, format_args!( - "Timeslice expirations: {}\n", + "Timeslice expirations: {}\r\n", info.timeslice_expirations(&self.capability) ), ); @@ -694,7 +966,7 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> self.writer_state.replace( WriterState::ProcessPrint { process_id: proc.processid(), - context: context, + context, }, ); } @@ -709,8 +981,8 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> &mut console_writer, format_args!( "Kernel version: {}.{} (build {})\r\n", - kernel::MAJOR, - kernel::MINOR, + kernel::KERNEL_MAJOR_VERSION, + kernel::KERNEL_MINOR_VERSION, option_env!("TOCK_KERNEL_VERSION").unwrap_or("unknown") ), ); @@ -720,10 +992,20 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> // Prints kernel memory by moving the writer to the // start state. self.writer_state.replace(WriterState::KernelStart); + } else if clean_str.starts_with("reset") { + self.reset_function.map_or_else( + || { + let _ = self.write_bytes(b"Reset function is not implemented"); + }, + |f| { + f(); + }, + ); + } else if clean_str.starts_with("panic") { + panic!("Process Console forced a kernel panic."); } else { let _ = self.write_bytes(b"Valid commands are: "); - let _ = self - .write_bytes(b"help status list stop start fault process kernel\n"); + let _ = self.write_bytes(VALID_COMMANDS_STR); } } Err(_e) => { @@ -747,7 +1029,13 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> } fn prompt(&self) { - let _ = self.write_bytes(b"tock$ "); + // Only display the prompt in active mode. + match self.mode.get() { + ProcessConsoleState::Active => { + let _ = self.write_bytes(b"tock$ "); + } + _ => {} + } } /// Start or iterate the state machine for an asynchronous write operation @@ -779,7 +1067,7 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> self.queue_buffer.map(|buf| { let size = self.queue_size.get(); let len = cmp::min(bytes.len(), buf.len() - size); - (&mut buf[size..size + len]).copy_from_slice(&bytes[..len]); + (buf[size..size + len]).copy_from_slice(&bytes[..len]); self.queue_size.set(size + len); }); Err(ErrorCode::BUSY) @@ -788,7 +1076,7 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> self.tx_buffer.take().map(|buffer| { let len = cmp::min(bytes.len(), buffer.len()); // Copy elements of `bytes` into `buffer` - (&mut buffer[..len]).copy_from_slice(&bytes[..len]); + (buffer[..len]).copy_from_slice(&bytes[..len]); let _ = self.uart.transmit_buffer(buffer, len); }); Ok(()) @@ -816,7 +1104,7 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> let txlen = cmp::min(qlen, txbuf.len()); // Copy elements of the queue into the TX buffer. - (&mut txbuf[..txlen]).copy_from_slice(&qbuf[..txlen]); + (txbuf[..txlen]).copy_from_slice(&qbuf[..txlen]); // TODO: If the queue needs to print over multiple TX // buffers, we need to shift the remaining contents of the @@ -841,18 +1129,19 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> ProcessConsole<'a, A, C> } } -impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> AlarmClient for ProcessConsole<'a, A, C> { +impl<'a, const COMMAND_HISTORY_LEN: usize, A: Alarm<'a>, C: ProcessManagementCapability> AlarmClient + for ProcessConsole<'a, COMMAND_HISTORY_LEN, A, C> +{ fn alarm(&self) { self.prompt(); self.rx_buffer.take().map(|buffer| { - self.rx_in_progress.set(true); let _ = self.uart.receive_buffer(buffer, 1); }); } } -impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> uart::TransmitClient - for ProcessConsole<'a, A, C> +impl<'a, const COMMAND_HISTORY_LEN: usize, A: Alarm<'a>, C: ProcessManagementCapability> + uart::TransmitClient for ProcessConsole<'a, COMMAND_HISTORY_LEN, A, C> { fn transmitted_buffer( &self, @@ -887,8 +1176,8 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> uart::TransmitClient } } -impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> uart::ReceiveClient - for ProcessConsole<'a, A, C> +impl<'a, const COMMAND_HISTORY_LEN: usize, A: Alarm<'a>, C: ProcessManagementCapability> + uart::ReceiveClient for ProcessConsole<'a, COMMAND_HISTORY_LEN, A, C> { fn received_buffer( &self, @@ -902,37 +1191,224 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> uart::ReceiveClient 0 => debug!("ProcessConsole had read of 0 bytes"), 1 => { self.command_buffer.map(|command| { + let esc_state = self.esc_state.get().next_state(read_buf[0]); + self.esc_state.set(esc_state); + let previous_byte = self.previous_byte.get(); self.previous_byte.set(read_buf[0]); - let index = self.command_index.get() as usize; - if read_buf[0] == ('\n' as u8) || read_buf[0] == ('\r' as u8) { - if (previous_byte == ('\n' as u8) || previous_byte == ('\r' as u8)) + let index = self.command_index.get(); + + let cursor = self.cursor.get(); + + if let EscState::Complete(key) = esc_state { + match key { + EscKey::Up | EscKey::Down if COMMAND_HISTORY_LEN >= 1 => { + self.command_history.map(|ht| { + if let Some(next_index) = if matches!(key, EscKey::Up) { + ht.next_cmd_idx() + } else { + ht.prev_cmd_idx() + } { + let next_command_len = ht.cmds[next_index].len; + + for _ in cursor..index { + let _ = self.write_byte(SPACE); + } + + // Clear the displayed command + for _ in 0..index { + let _ = self.write_bytes(&[BS, SPACE, BS]); + } + + // Display the new command + for i in 0..next_command_len { + let byte = ht.cmds[next_index].buf[i]; + let _ = self.write_byte(byte); + command[i] = byte; + } + + ht.cmd_is_modified = true; + self.command_index.set(next_command_len); + self.cursor.set(next_command_len); + command[next_command_len] = EOL; + }; + }); + } + EscKey::Left if cursor > 0 => { + let _ = self.write_byte(BS); + self.cursor.set(cursor - 1); + } + EscKey::Right if cursor < index => { + let _ = self.write_byte(command[cursor]); + self.cursor.set(cursor + 1); + } + EscKey::Home if cursor > 0 => { + for _ in 0..cursor { + let _ = self.write_byte(BS); + } + + self.cursor.set(0); + } + EscKey::End if cursor < index => { + for i in cursor..index { + let _ = self.write_byte(command[i]); + } + + self.cursor.set(index); + } + EscKey::Delete if cursor < index => { + // Move the bytes one position to left + for i in cursor..(index - 1) { + command[i] = command[i + 1]; + let _ = self.write_byte(command[i]); + } + // We don't want to write the EOL byte, but we want to copy it to the left + command[index - 1] = command[index]; + + // Now that we copied all bytes to the left, we are left over with + // a dublicate "ghost" character of the last byte, + // In case we deleted the first character, this doesn't do anything as + // the dublicate is not there. + // |abcdef -> bcdef + // abc|def -> abceff -> abcef + let _ = self.write_bytes(&[SPACE, BS]); + + // Move the cursor to last position + for _ in cursor..(index - 1) { + let _ = self.write_byte(BS); + } + + self.command_index.set(index - 1); + + // Remove the byte from the command in order + // not to permit accumulation of the text + if COMMAND_HISTORY_LEN > 1 { + self.command_history.map(|ht| { + if ht.cmd_is_modified { + // Copy the last command into the unfinished command + + ht.cmds[0].clear(); + ht.write_to_first(command); + ht.cmd_is_modified = false; + } else { + ht.cmds[0].delete_byte(cursor); + } + }); + } + } + _ => {} + }; + } else if read_buf[0] == NLINE || read_buf[0] == CR { + if (previous_byte == NLINE || previous_byte == CR) && previous_byte != read_buf[0] { - // ignore the \n or \r as it is the second byte of a \r\n sequence - // reset the sequence - self.previous_byte.set(0); + // Reset the sequence, when \r\n is received + self.previous_byte.set(EOL); } else { + self.cursor.set(0); self.execute.set(true); - let _ = self.write_bytes(&['\r' as u8, '\n' as u8]); + + let _ = self.write_bytes(&[CR, NLINE]); + + if COMMAND_HISTORY_LEN > 1 { + // Clear the unfinished command + self.command_history.map(|ht| { + ht.cmd_idx = 0; + ht.cmd_is_modified = false; + ht.cmds[0].clear(); + }); + } } - } else if read_buf[0] == ('\x08' as u8) { - if index > 0 { - // Backspace, echo and remove last byte + } else if read_buf[0] == BS { + if cursor > 0 { + // Backspace, echo and remove the byte + // preceding the cursor // Note echo is '\b \b' to erase - let _ = self.write_bytes(&['\x08' as u8, ' ' as u8, '\x08' as u8]); - command[index - 1] = '\0' as u8; + let _ = self.write_bytes(&[BS, SPACE, BS]); + + // Move the bytes one position to left + for i in (cursor - 1)..(index - 1) { + command[i] = command[i + 1]; + let _ = self.write_byte(command[i]); + } + // We don't want to write the EOL byte, but we want to copy it to the left + command[index - 1] = command[index]; + + // Now that we copied all bytes to the left, we are left over with + // a dublicate "ghost" character of the last byte, + // In case we deleted the last character, this doesn't do anything as + // the dublicate is not there. + // abcdef| -> abcdef + // abcd|ef -> abceff -> abcef + let _ = self.write_bytes(&[SPACE, BS]); + + // Move the cursor to last position + for _ in cursor..index { + let _ = self.write_byte(BS); + } + self.command_index.set(index - 1); + self.cursor.set(cursor - 1); + + // Remove the byte from the command in order + // not to permit accumulation of the text + if COMMAND_HISTORY_LEN > 1 { + self.command_history.map(|ht| { + if ht.cmd_is_modified { + // Copy the last command into the unfinished command + + ht.cmds[0].clear(); + ht.write_to_first(command); + ht.cmd_is_modified = false; + } else { + ht.cmds[0].delete_byte(cursor - 1); + } + }); + } } - } else if index < (command.len() - 1) && read_buf[0] < 128 { + } else if index < (command.len() - 1) + && read_buf[0] < ASCII_LIMIT + && !esc_state.has_started() + && !esc_state.in_progress() + { // For some reason, sometimes reads return > 127 but no error, // which causes utf-8 decoding failure, so check byte is < 128. -pal - // Echo the byte and store it + // Echo the typed byte let _ = self.write_byte(read_buf[0]); - command[index] = read_buf[0]; + + // Echo the rest of the bytes from the command + for i in cursor..index { + let _ = self.write_byte(command[i]); + } + + // Make space for the newest byte + for i in (cursor..(index + 1)).rev() { + command[i + 1] = command[i]; + } + + // Move the cursor to the last position + for _ in cursor..index { + let _ = self.write_byte(BS); + } + + command[cursor] = read_buf[0]; + self.cursor.set(cursor + 1); self.command_index.set(index + 1); - command[index + 1] = 0; + + if COMMAND_HISTORY_LEN > 1 { + self.command_history.map(|ht| { + if ht.cmd_is_modified { + // Copy the last command into the unfinished command + + ht.cmds[0].clear(); + ht.write_to_first(command); + ht.cmd_is_modified = false; + } else { + ht.cmds[0].insert_byte(read_buf[0], cursor); + } + }); + } } }); } @@ -942,7 +1418,6 @@ impl<'a, A: Alarm<'a>, C: ProcessManagementCapability> uart::ReceiveClient ), }; } - self.rx_in_progress.set(true); let _ = self.uart.receive_buffer(read_buf, 1); } } diff --git a/capsules/src/rng.rs b/capsules/core/src/rng.rs similarity index 81% rename from capsules/src/rng.rs rename to capsules/core/src/rng.rs index 83879e47dd..02acfb12b2 100644 --- a/capsules/src/rng.rs +++ b/capsules/core/src/rng.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Library of randomness structures, including a system call driver for //! userspace applications to request randomness, entropy conversion, entropy //! to randomness conversion, and synchronous random number generation. @@ -10,7 +14,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! let rng = static_init!( @@ -40,7 +44,7 @@ pub const DRIVER_NUM: usize = driver::NUM::Rng as usize; mod rw_allow { pub const BUFFER: usize = 0; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; + pub const COUNT: u8 = 1; } #[derive(Default)] @@ -49,26 +53,26 @@ pub struct App { idx: usize, } -pub struct RngDriver<'a> { - rng: &'a dyn Rng<'a>, +pub struct RngDriver<'a, R: Rng<'a>> { + rng: &'a R, apps: Grant, AllowRoCount<0>, AllowRwCount<{ rw_allow::COUNT }>>, getting_randomness: Cell, } -impl<'a> RngDriver<'a> { +impl<'a, R: Rng<'a>> RngDriver<'a, R> { pub fn new( - rng: &'a dyn Rng<'a>, + rng: &'a R, grant: Grant, AllowRoCount<0>, AllowRwCount<{ rw_allow::COUNT }>>, - ) -> RngDriver<'a> { - RngDriver { - rng: rng, + ) -> Self { + Self { + rng, apps: grant, getting_randomness: Cell::new(false), } } } -impl rng::Client for RngDriver<'_> { +impl<'a, R: Rng<'a>> rng::Client for RngDriver<'a, R> { fn randomness_available( &self, randomness: &mut dyn Iterator, @@ -153,7 +157,7 @@ impl rng::Client for RngDriver<'_> { // Check if done switched to false. If it did, then that app // didn't get enough random, so there's no way there is more for // other apps. - if done == false { + if !done { break; } } @@ -167,37 +171,43 @@ impl rng::Client for RngDriver<'_> { } } -impl<'a> SyscallDriver for RngDriver<'a> { +impl<'a, R: Rng<'a>> SyscallDriver for RngDriver<'a, R> { fn command( &self, command_num: usize, data: usize, _: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { match command_num { - 0 /* Check if exists */ => - { - CommandReturn::success() - } - - 1 /* Ask for a given number of random bytes */ => self - .apps - .enter(appid, |app, _| { - app.remaining = data; - app.idx = 0; - - // Assume that the process has a callback & slice - // set. It might die or revoke them before the - // result arrives anyways - if !self.getting_randomness.get() { - self.getting_randomness.set(true); - let _ = self.rng.get(); - } + // Driver existence check + 0 => CommandReturn::success(), + + // Ask for a given number of random bytes + 1 => { + let mut needs_get = false; + let result = self + .apps + .enter(processid, |app, _| { + app.remaining = data; + app.idx = 0; + + // Assume that the process has a callback & slice + // set. It might die or revoke them before the + // result arrives anyways + if !self.getting_randomness.get() { + self.getting_randomness.set(true); + needs_get = true; + } - CommandReturn::success() - }) - .unwrap_or_else(|err| CommandReturn::failure(err.into())), + CommandReturn::success() + }) + .unwrap_or_else(|err| CommandReturn::failure(err.into())); + if needs_get { + let _ = self.rng.get(); + } + result + } _ => CommandReturn::failure(ErrorCode::NOSUPPORT), } } @@ -207,21 +217,21 @@ impl<'a> SyscallDriver for RngDriver<'a> { } } -pub struct Entropy32ToRandom<'a> { - egen: &'a dyn Entropy32<'a>, +pub struct Entropy32ToRandom<'a, E: Entropy32<'a>> { + egen: &'a E, client: OptionalCell<&'a dyn rng::Client>, } -impl<'a> Entropy32ToRandom<'a> { - pub fn new(egen: &'a dyn Entropy32<'a>) -> Entropy32ToRandom<'a> { - Entropy32ToRandom { - egen: egen, +impl<'a, E: Entropy32<'a>> Entropy32ToRandom<'a, E> { + pub fn new(egen: &'a E) -> Self { + Self { + egen, client: OptionalCell::empty(), } } } -impl<'a> Rng<'a> for Entropy32ToRandom<'a> { +impl<'a, E: Entropy32<'a>> Rng<'a> for Entropy32ToRandom<'a, E> { fn get(&self) -> Result<(), ErrorCode> { self.egen.get() } @@ -236,7 +246,7 @@ impl<'a> Rng<'a> for Entropy32ToRandom<'a> { } } -impl entropy::Client32 for Entropy32ToRandom<'_> { +impl<'a, E: Entropy32<'a>> entropy::Client32 for Entropy32ToRandom<'a, E> { fn entropy_available( &self, entropy: &mut dyn Iterator, @@ -268,17 +278,17 @@ impl Iterator for Entropy32ToRandomIter<'_> { } } -pub struct Entropy8To32<'a> { - egen: &'a dyn Entropy8<'a>, +pub struct Entropy8To32<'a, E: Entropy8<'a>> { + egen: &'a E, client: OptionalCell<&'a dyn entropy::Client32>, count: Cell, bytes: Cell, } -impl<'a> Entropy8To32<'a> { - pub fn new(egen: &'a dyn Entropy8<'a>) -> Entropy8To32<'a> { - Entropy8To32 { - egen: egen, +impl<'a, E: Entropy8<'a>> Entropy8To32<'a, E> { + pub fn new(egen: &'a E) -> Self { + Self { + egen, client: OptionalCell::empty(), count: Cell::new(0), bytes: Cell::new(0), @@ -286,7 +296,7 @@ impl<'a> Entropy8To32<'a> { } } -impl<'a> Entropy32<'a> for Entropy8To32<'a> { +impl<'a, E: Entropy8<'a>> Entropy32<'a> for Entropy8To32<'a, E> { fn get(&self) -> Result<(), ErrorCode> { self.egen.get() } @@ -309,7 +319,7 @@ impl<'a> Entropy32<'a> for Entropy8To32<'a> { } } -impl entropy::Client8 for Entropy8To32<'_> { +impl<'a, E: Entropy8<'a>> entropy::Client8 for Entropy8To32<'a, E> { fn entropy_available( &self, entropy: &mut dyn Iterator, @@ -334,7 +344,7 @@ impl entropy::Client8 for Entropy8To32<'_> { let current = self.bytes.get(); let bits = val as u32; let result = current | (bits << (8 * count)); - count = count + 1; + count += 1; self.count.set(count); self.bytes.set(result) } @@ -348,9 +358,9 @@ impl entropy::Client8 for Entropy8To32<'_> { } } -struct Entropy8To32Iter<'a, 'b: 'a>(&'a Entropy8To32<'b>); +struct Entropy8To32Iter<'a, 'b: 'a, E: Entropy8<'b>>(&'a Entropy8To32<'b, E>); -impl Iterator for Entropy8To32Iter<'_, '_> { +impl<'a, 'b: 'a, E: Entropy8<'b>> Iterator for Entropy8To32Iter<'a, 'b, E> { type Item = u32; fn next(&mut self) -> Option { @@ -364,17 +374,17 @@ impl Iterator for Entropy8To32Iter<'_, '_> { } } -pub struct Entropy32To8<'a> { - egen: &'a dyn Entropy32<'a>, +pub struct Entropy32To8<'a, E: Entropy32<'a>> { + egen: &'a E, client: OptionalCell<&'a dyn entropy::Client8>, entropy: Cell, bytes_consumed: Cell, } -impl<'a> Entropy32To8<'a> { - pub fn new(egen: &'a dyn Entropy32<'a>) -> Entropy32To8<'a> { - Entropy32To8 { - egen: egen, +impl<'a, E: Entropy32<'a>> Entropy32To8<'a, E> { + pub fn new(egen: &'a E) -> Self { + Self { + egen, client: OptionalCell::empty(), entropy: Cell::new(0), bytes_consumed: Cell::new(0), @@ -382,7 +392,7 @@ impl<'a> Entropy32To8<'a> { } } -impl<'a> Entropy8<'a> for Entropy32To8<'a> { +impl<'a, E: Entropy32<'a>> Entropy8<'a> for Entropy32To8<'a, E> { fn get(&self) -> Result<(), ErrorCode> { self.egen.get() } @@ -405,7 +415,7 @@ impl<'a> Entropy8<'a> for Entropy32To8<'a> { } } -impl entropy::Client32 for Entropy32To8<'_> { +impl<'a, E: Entropy32<'a>> entropy::Client32 for Entropy32To8<'a, E> { fn entropy_available( &self, entropy: &mut dyn Iterator, @@ -413,7 +423,7 @@ impl entropy::Client32 for Entropy32To8<'_> { ) -> entropy::Continue { self.client.map_or(entropy::Continue::Done, |client| { if error != Ok(()) { - client.entropy_available(&mut Entropy32To8Iter(self), error) + client.entropy_available(&mut Entropy32To8Iter::(self), error) } else { let r = entropy.next(); match r { @@ -429,9 +439,9 @@ impl entropy::Client32 for Entropy32To8<'_> { } } -struct Entropy32To8Iter<'a, 'b: 'a>(&'a Entropy32To8<'b>); +struct Entropy32To8Iter<'a, 'b: 'a, E: Entropy32<'b>>(&'a Entropy32To8<'b, E>); -impl Iterator for Entropy32To8Iter<'_, '_> { +impl<'a, 'b: 'a, E: Entropy32<'b>> Iterator for Entropy32To8Iter<'a, 'b, E> { type Item = u8; fn next(&mut self) -> Option { @@ -450,22 +460,22 @@ impl Iterator for Entropy32To8Iter<'_, '_> { } } -pub struct SynchronousRandom<'a> { - rgen: &'a dyn Rng<'a>, +pub struct SynchronousRandom<'a, R: Rng<'a>> { + rgen: &'a R, seed: Cell, } #[allow(dead_code)] -impl<'a> SynchronousRandom<'a> { - fn new(rgen: &'a dyn Rng<'a>) -> SynchronousRandom { - SynchronousRandom { - rgen: rgen, +impl<'a, R: Rng<'a>> SynchronousRandom<'a, R> { + fn new(rgen: &'a R) -> Self { + Self { + rgen, seed: Cell::new(0), } } } -impl<'a> Random<'a> for SynchronousRandom<'a> { +impl<'a, R: Rng<'a>> Random<'a> for SynchronousRandom<'a, R> { fn initialize(&'a self) { self.rgen.set_client(self); let _ = self.rgen.get(); @@ -491,7 +501,7 @@ impl<'a> Random<'a> for SynchronousRandom<'a> { } } -impl Client for SynchronousRandom<'_> { +impl<'a, R: Rng<'a>> Client for SynchronousRandom<'a, R> { fn randomness_available( &self, randomness: &mut dyn Iterator, diff --git a/capsules/src/spi_controller.rs b/capsules/core/src/spi_controller.rs similarity index 56% rename from capsules/src/spi_controller.rs rename to capsules/core/src/spi_controller.rs index 3eb3a9a5de..013618c5ae 100644 --- a/capsules/src/spi_controller.rs +++ b/capsules/core/src/spi_controller.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides userspace applications with the ability to communicate over the SPI //! bus. @@ -21,14 +25,14 @@ pub const DRIVER_NUM: usize = driver::NUM::Spi as usize; mod ro_allow { pub const WRITE: usize = 0; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; + pub const COUNT: u8 = 1; } /// Ids for read-write allow buffers mod rw_allow { pub const READ: usize = 0; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; + pub const COUNT: u8 = 1; } /// Suggested length for the Spi read and write buffer @@ -50,7 +54,7 @@ pub struct App { index: usize, } -pub struct Spi<'a, S: SpiMasterDevice> { +pub struct Spi<'a, S: SpiMasterDevice<'a>> { spi_master: &'a S, busy: Cell, kernel_read: TakeCell<'static, [u8]>, @@ -63,9 +67,16 @@ pub struct Spi<'a, S: SpiMasterDevice> { AllowRwCount<{ rw_allow::COUNT }>, >, current_process: OptionalCell, + command: Cell, +} + +#[derive(Debug, Clone, Copy)] +enum UserCommand { + ReadBytes, + InplaceReadWriteBytes, } -impl<'a, S: SpiMasterDevice> Spi<'a, S> { +impl<'a, S: SpiMasterDevice<'a>> Spi<'a, S> { pub fn new( spi_master: &'a S, grants: Grant< @@ -76,17 +87,18 @@ impl<'a, S: SpiMasterDevice> Spi<'a, S> { >, ) -> Spi<'a, S> { Spi { - spi_master: spi_master, + spi_master, busy: Cell::new(false), kernel_len: Cell::new(0), kernel_read: TakeCell::empty(), kernel_write: TakeCell::empty(), grants, current_process: OptionalCell::empty(), + command: Cell::new(UserCommand::ReadBytes), } } - pub fn config_buffers(&mut self, read: &'static mut [u8], write: &'static mut [u8]) { + pub fn config_buffers(&self, read: &'static mut [u8], write: &'static mut [u8]) { let len = cmp::min(read.len(), write.len()); self.kernel_len.set(len); self.kernel_read.replace(read); @@ -116,16 +128,60 @@ impl<'a, S: SpiMasterDevice> Spi<'a, S> { app.index = start + tmp_len; tmp_len }); + + let rlen = kernel_data + .get_readwrite_processbuffer(rw_allow::READ) + .map_or(0, |read| read.len()); + // TODO verify SPI return value - let _ = self.spi_master.read_write_bytes( - self.kernel_write.take().unwrap(), - self.kernel_read.take(), - write_len, - ); + let _ = if rlen == 0 { + self.spi_master + .read_write_bytes(self.kernel_write.take().unwrap(), None, write_len) + } else if write_len == 0 { + let read_len = self + .kernel_write + .map_or(0, |kwbuf| match self.command.get() { + UserCommand::ReadBytes => { + kwbuf.fill(0xFF); + + cmp::min(kwbuf.len(), rlen) + } + UserCommand::InplaceReadWriteBytes => kernel_data + .get_readwrite_processbuffer(rw_allow::READ) + .and_then(|read| { + read.mut_enter(|src| { + let length = cmp::min(kwbuf.len(), rlen); + + let start = app.index; + let end = cmp::min(app.index + length, src.len()); + + for (i, c) in src[start..end].iter().enumerate() { + kwbuf[i] = c.get(); + } + + length + }) + }) + .unwrap_or(0), + }); + app.index += read_len; + self.spi_master.read_write_bytes( + self.kernel_write.take().unwrap(), + self.kernel_read.take(), + read_len, + ) + } else { + self.spi_master.read_write_bytes( + self.kernel_write.take().unwrap(), + self.kernel_read.take(), + write_len, + ) + }; } } -impl<'a, S: SpiMasterDevice> SyscallDriver for Spi<'a, S> { +impl<'a, S: SpiMasterDevice<'a>> SyscallDriver for Spi<'a, S> { + // 0: driver existence check // 2: read/write buffers // - requires write buffer registered with allow // - read buffer optional @@ -152,6 +208,11 @@ impl<'a, S: SpiMasterDevice> SyscallDriver for Spi<'a, S> { // 10: get clock polarity on current peripheral // - 0 is idle low // - non-zero is idle high + // 11: read buffers + // - read buffer required + // 12: inplace read/write buffers + // - requires read buffer registered with allow + // - write buffer not supported // // x: lock spi // - if you perform an operation without the lock, @@ -170,14 +231,14 @@ impl<'a, S: SpiMasterDevice> SyscallDriver for Spi<'a, S> { process_id: ProcessId, ) -> CommandReturn { if command_num == 0 { - // Handle this first as it should be returned unconditionally. + // Handle unconditional driver existence check. return CommandReturn::success(); } // Check if this driver is free, or already dedicated to this process. let match_or_empty_or_nonexistant = self.current_process.map_or(true, |current_process| { self.grants - .enter(*current_process, |_, _| current_process == &process_id) + .enter(current_process, |_, _| current_process == process_id) .unwrap_or(true) }); if match_or_empty_or_nonexistant { @@ -188,83 +249,150 @@ impl<'a, S: SpiMasterDevice> SyscallDriver for Spi<'a, S> { match command_num { // No longer supported, wrap inside a read_write_bytes - 1 /* read_write_byte */ => CommandReturn::failure(ErrorCode::NOSUPPORT), - 2 /* read_write_bytes */ => { + 1 => { + // read_write_byte + CommandReturn::failure(ErrorCode::NOSUPPORT) + } + 2 => { + // read_write_bytes if self.busy.get() { return CommandReturn::failure(ErrorCode::BUSY); } - self.grants.enter(process_id, |app, kernel_data| { - // When we do a read/write, the read part is optional. - // So there are three cases: - // 1) Write and read buffers present: len is min of lengths - // 2) Only write buffer present: len is len of write - // 3) No write buffer present: no operation - let mut mlen = kernel_data - .get_readonly_processbuffer(ro_allow::WRITE) - .map_or(0, |write| write.len()); - let rlen = kernel_data - .get_readwrite_processbuffer(rw_allow::READ) - .map_or(mlen, |read| read.len()); - mlen = cmp::min(mlen, rlen); - - if mlen >= arg1 && arg1 > 0 { - app.len = arg1; - app.index = 0; - self.busy.set(true); - self.do_next_read_write(app, kernel_data); - CommandReturn::success() - } else { - /* write buffer too small, or zero length write */ - CommandReturn::failure(ErrorCode::INVAL) - } - }).unwrap_or(CommandReturn::failure(ErrorCode::FAIL)) + self.grants + .enter(process_id, |app, kernel_data| { + // When we do a read/write, the read part is optional. + // So there are three cases: + // 1) Write and read buffers present: len is min of lengths + // 2) Only write buffer present: len is len of write + // 3) No write buffer present: no operation + let wlen = kernel_data + .get_readonly_processbuffer(ro_allow::WRITE) + .map_or(0, |write| write.len()); + let rlen = kernel_data + .get_readwrite_processbuffer(rw_allow::READ) + .map_or(0, |read| read.len()); + // Note that non-shared and 0-sized read buffers both report 0 as size + let len = if rlen == 0 { wlen } else { wlen.min(rlen) }; + + if len >= arg1 && arg1 > 0 { + app.len = arg1; + app.index = 0; + self.busy.set(true); + self.do_next_read_write(app, kernel_data); + CommandReturn::success() + } else { + /* write buffer too small, or zero length write */ + CommandReturn::failure(ErrorCode::INVAL) + } + }) + .unwrap_or(CommandReturn::failure(ErrorCode::FAIL)) } - 3 /* set chip select */ => { + 3 => { + // set chip select // XXX: TODO: do nothing, for now, until we fix interface // so virtual instances can use multiple chip selects CommandReturn::failure(ErrorCode::NOSUPPORT) } - 4 /* get chip select */ => { + 4 => { + // get chip select * // XXX: We don't really know what chip select is being used // since we can't set it. Return error until set chip select // works. CommandReturn::failure(ErrorCode::NOSUPPORT) } - 5 /* set baud rate */ => { + 5 => { + // set baud rate match self.spi_master.set_rate(arg1 as u32) { Ok(()) => CommandReturn::success(), - Err (error) => CommandReturn::failure(error.into()) + Err(error) => CommandReturn::failure(error), } } - 6 /* get baud rate */ => { - CommandReturn::success_u32(self.spi_master.get_rate() as u32) + 6 => { + // get baud rate + CommandReturn::success_u32(self.spi_master.get_rate()) } - 7 /* set phase */ => { + 7 => { + // set phase match match arg1 { 0 => self.spi_master.set_phase(ClockPhase::SampleLeading), _ => self.spi_master.set_phase(ClockPhase::SampleTrailing), } { Ok(()) => CommandReturn::success(), - Err(error) => CommandReturn::failure(error.into()) + Err(error) => CommandReturn::failure(error), } } - 8 /* get phase */ => { + 8 => { + // get phase CommandReturn::success_u32(self.spi_master.get_phase() as u32) } - 9 /* set polarity */ => { + 9 => { + // set polarity match match arg1 { 0 => self.spi_master.set_polarity(ClockPolarity::IdleLow), _ => self.spi_master.set_polarity(ClockPolarity::IdleHigh), - } - { + } { Ok(()) => CommandReturn::success(), - Err(error) => CommandReturn::failure(error.into()) + Err(error) => CommandReturn::failure(error), } } - 10 /* get polarity */ => { + 10 => { + // get polarity CommandReturn::success_u32(self.spi_master.get_polarity() as u32) } - _ => CommandReturn::failure(ErrorCode::NOSUPPORT) + 11 => { + // read_bytes + // write 0xFF to the SPI bus and return the read values to + // userspace + if self.busy.get() { + return CommandReturn::failure(ErrorCode::BUSY); + } + self.grants + .enter(process_id, |app, kernel_data| { + // When we do a read, we just write 0xFF on the bus. + let rlen = kernel_data + .get_readwrite_processbuffer(rw_allow::READ) + .map_or(0, |read| read.len()); + + if rlen >= arg1 && rlen > 0 { + app.len = arg1; + app.index = 0; + self.busy.set(true); + self.command.set(UserCommand::ReadBytes); + self.do_next_read_write(app, kernel_data); + CommandReturn::success() + } else { + /* write buffer too small, or zero length write */ + CommandReturn::failure(ErrorCode::INVAL) + } + }) + .unwrap_or(CommandReturn::failure(ErrorCode::FAIL)) + } + 12 => { + // inplace read_write_bytes + if self.busy.get() { + return CommandReturn::failure(ErrorCode::BUSY); + } + self.grants + .enter(process_id, |app, kernel_data| { + let rlen = kernel_data + .get_readwrite_processbuffer(rw_allow::READ) + .map_or(0, |read| read.len()); + + if rlen >= arg1 && arg1 > 0 { + app.len = arg1; + app.index = 0; + self.busy.set(true); + self.command.set(UserCommand::InplaceReadWriteBytes); + self.do_next_read_write(app, kernel_data); + CommandReturn::success() + } else { + /* write buffer too small, or zero length write */ + CommandReturn::failure(ErrorCode::INVAL) + } + }) + .unwrap_or(CommandReturn::failure(ErrorCode::FAIL)) + } + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), } } @@ -273,7 +401,7 @@ impl<'a, S: SpiMasterDevice> SyscallDriver for Spi<'a, S> { } } -impl SpiMasterClient for Spi<'_, S> { +impl<'a, S: SpiMasterDevice<'a>> SpiMasterClient for Spi<'a, S> { fn read_write_done( &self, writebuf: &'static mut [u8], @@ -282,8 +410,8 @@ impl SpiMasterClient for Spi<'_, S> { _status: Result<(), ErrorCode>, ) { self.current_process.map(|process_id| { - let _ = self.grants.enter(*process_id, move |app, kernel_data| { - let rbuf = readbuf.map(|src| { + let _ = self.grants.enter(process_id, move |app, kernel_data| { + let rbuf = readbuf.inspect(|src| { let index = app.index; let _ = kernel_data .get_readwrite_processbuffer(rw_allow::READ) @@ -314,10 +442,11 @@ impl SpiMasterClient for Spi<'_, S> { } }) }); - src }); - self.kernel_read.put(rbuf); + if rbuf.is_some() { + self.kernel_read.put(rbuf); + } self.kernel_write.replace(writebuf); if app.index == app.len { diff --git a/capsules/src/spi_peripheral.rs b/capsules/core/src/spi_peripheral.rs similarity index 76% rename from capsules/src/spi_peripheral.rs rename to capsules/core/src/spi_peripheral.rs index d74ce91bf9..2be244922f 100644 --- a/capsules/src/spi_peripheral.rs +++ b/capsules/core/src/spi_peripheral.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides userspace applications with the ability to communicate over the SPI //! bus as a peripheral. Only supports chip select 0. @@ -21,14 +25,14 @@ pub const DRIVER_NUM: usize = driver::NUM::SpiPeripheral as usize; mod ro_allow { pub const WRITE: usize = 0; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; + pub const COUNT: u8 = 1; } /// Ids for read-write allow buffers mod rw_allow { pub const READ: usize = 0; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; + pub const COUNT: u8 = 1; } /// Suggested length for the SPI read and write buffer @@ -44,7 +48,7 @@ pub struct PeripheralApp { index: usize, } -pub struct SpiPeripheral<'a, S: SpiSlaveDevice> { +pub struct SpiPeripheral<'a, S: SpiSlaveDevice<'a>> { spi_slave: &'a S, busy: Cell, kernel_read: TakeCell<'static, [u8]>, @@ -59,7 +63,7 @@ pub struct SpiPeripheral<'a, S: SpiSlaveDevice> { current_process: OptionalCell, } -impl<'a, S: SpiSlaveDevice> SpiPeripheral<'a, S> { +impl<'a, S: SpiSlaveDevice<'a>> SpiPeripheral<'a, S> { pub fn new( spi_slave: &'a S, grants: Grant< @@ -70,7 +74,7 @@ impl<'a, S: SpiSlaveDevice> SpiPeripheral<'a, S> { >, ) -> SpiPeripheral<'a, S> { SpiPeripheral { - spi_slave: spi_slave, + spi_slave, busy: Cell::new(false), kernel_len: Cell::new(0), kernel_read: TakeCell::empty(), @@ -80,7 +84,7 @@ impl<'a, S: SpiSlaveDevice> SpiPeripheral<'a, S> { } } - pub fn config_buffers(&mut self, read: &'static mut [u8], write: &'static mut [u8]) { + pub fn config_buffers(&self, read: &'static mut [u8], write: &'static mut [u8]) { let len = cmp::min(read.len(), write.len()); self.kernel_len.set(len); self.kernel_read.replace(read); @@ -119,7 +123,7 @@ impl<'a, S: SpiSlaveDevice> SpiPeripheral<'a, S> { } } -impl SyscallDriver for SpiPeripheral<'_, S> { +impl<'a, S: SpiSlaveDevice<'a>> SyscallDriver for SpiPeripheral<'a, S> { /// Provide read/write buffers to SpiPeripheral /// /// - allow_num 0: Provides a buffer to receive transfers into. @@ -128,7 +132,7 @@ impl SyscallDriver for SpiPeripheral<'_, S> { /// /// - allow_num 0: Provides a buffer to transmit - /// - 0: check if present + /// - 0: driver existence check /// - 1: read/write buffers /// - read and write buffers optional /// - fails if arg1 (bytes to write) > @@ -166,14 +170,14 @@ impl SyscallDriver for SpiPeripheral<'_, S> { process_id: ProcessId, ) -> CommandReturn { if command_num == 0 { - // Handle this first as it should be returned unconditionally. + // Handle unconditional driver existence check. return CommandReturn::success(); } // Check if this driver is free, or already dedicated to this process. let match_or_empty_or_nonexistant = self.current_process.map_or(true, |current_process| { self.grants - .enter(*current_process, |_, _| current_process == &process_id) + .enter(current_process, |_, _| current_process == process_id) .unwrap_or(true) }); if match_or_empty_or_nonexistant { @@ -183,59 +187,74 @@ impl SyscallDriver for SpiPeripheral<'_, S> { } match command_num { - 1 /* read_write_bytes */ => { + 1 => { + // read_write_bytes if self.busy.get() { return CommandReturn::failure(ErrorCode::BUSY); } - self.grants.enter(process_id, |app, kernel_data| { - let mut mlen = kernel_data - .get_readonly_processbuffer(ro_allow::WRITE) - .map_or(0, |write| write.len()); - let rlen = kernel_data - .get_readwrite_processbuffer(rw_allow::READ) - .map_or(mlen, |read| read.len()); - mlen = cmp::min(mlen, rlen); - if mlen >= arg1 && arg1 > 0 { - app.len = arg1; - app.index = 0; - self.busy.set(true); - self.do_next_read_write(app, kernel_data); - CommandReturn::success() - } else { - CommandReturn::failure(ErrorCode::INVAL) - } - }).unwrap_or(CommandReturn::failure(ErrorCode::NOMEM)) + self.grants + .enter(process_id, |app, kernel_data| { + // When we do a read/write, the read part is optional. + // So there are three cases: + // 1) Write and read buffers present: len is min of lengths + // 2) Only write buffer present: len is len of write + // 3) No write buffer present: no operation + let wlen = kernel_data + .get_readonly_processbuffer(ro_allow::WRITE) + .map_or(0, |write| write.len()); + let rlen = kernel_data + .get_readwrite_processbuffer(rw_allow::READ) + .map_or(0, |read| read.len()); + // Note that non-shared and 0-sized read buffers both report 0 as size + let len = if rlen == 0 { wlen } else { wlen.min(rlen) }; + + if len >= arg1 && arg1 > 0 { + app.len = arg1; + app.index = 0; + self.busy.set(true); + self.do_next_read_write(app, kernel_data); + CommandReturn::success() + } else { + /* write buffer too small, or zero length write */ + CommandReturn::failure(ErrorCode::INVAL) + } + }) + .unwrap_or(CommandReturn::failure(ErrorCode::NOMEM)) } - 2 /* get chip select */ => { + 2 => { + // get chip select // Only 0 is supported CommandReturn::success_u32(0) } - 3 /* set phase */ => { + 3 => { + // set phase match match arg1 { 0 => self.spi_slave.set_phase(ClockPhase::SampleLeading), _ => self.spi_slave.set_phase(ClockPhase::SampleTrailing), } { Ok(()) => CommandReturn::success(), - Err(error) => CommandReturn::failure(error.into()) + Err(error) => CommandReturn::failure(error), } } - 4 /* get phase */ => { + 4 => { + // get phase CommandReturn::success_u32(self.spi_slave.get_phase() as u32) } - 5 /* set polarity */ => { + 5 => { + // set polarity match match arg1 { 0 => self.spi_slave.set_polarity(ClockPolarity::IdleLow), _ => self.spi_slave.set_polarity(ClockPolarity::IdleHigh), } { Ok(()) => CommandReturn::success(), - Err(error) => CommandReturn::failure(error.into()) + Err(error) => CommandReturn::failure(error), } - } - 6 /* get polarity */ => { + 6 => { + // get polarity CommandReturn::success_u32(self.spi_slave.get_polarity() as u32) } - _ => CommandReturn::failure(ErrorCode::NOSUPPORT) + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), } } @@ -244,7 +263,7 @@ impl SyscallDriver for SpiPeripheral<'_, S> { } } -impl SpiSlaveClient for SpiPeripheral<'_, S> { +impl<'a, S: SpiSlaveDevice<'a>> SpiSlaveClient for SpiPeripheral<'a, S> { fn read_write_done( &self, writebuf: Option<&'static mut [u8]>, @@ -253,8 +272,8 @@ impl SpiSlaveClient for SpiPeripheral<'_, S> { _status: Result<(), ErrorCode>, ) { self.current_process.map(|process_id| { - let _ = self.grants.enter(*process_id, move |app, kernel_data| { - let rbuf = readbuf.map(|src| { + let _ = self.grants.enter(process_id, move |app, kernel_data| { + let rbuf = readbuf.inspect(|src| { let index = app.index; let _ = kernel_data .get_readwrite_processbuffer(rw_allow::READ) @@ -284,7 +303,6 @@ impl SpiSlaveClient for SpiPeripheral<'_, S> { } }) }); - src }); self.kernel_read.put(rbuf); @@ -306,7 +324,7 @@ impl SpiSlaveClient for SpiPeripheral<'_, S> { // Simple callback for when chip has been selected fn chip_selected(&self) { self.current_process.map(|process_id| { - let _ = self.grants.enter(*process_id, move |app, kernel_data| { + let _ = self.grants.enter(process_id, move |app, kernel_data| { let len = app.len; kernel_data.schedule_upcall(1, (len, 0, 0)).ok(); }); diff --git a/capsules/core/src/stream.rs b/capsules/core/src/stream.rs new file mode 100644 index 0000000000..6f0e36ae8c --- /dev/null +++ b/capsules/core/src/stream.rs @@ -0,0 +1,304 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +#[derive(Debug)] +pub enum SResult { + // `Done(off, out)`: No errors encountered. We are currently at `off` in the + // buffer, and the previous encoder/decoder produced output `out`. + Done(usize, Output), + + // `Needed(bytes)`: Could not proceed because we needed to have `bytes` + // bytes in the buffer, but there weren't. + Needed(usize), + + // `Error(err)`: Some other error occurred. + Error(Error), +} + +impl SResult { + pub fn is_done(&self) -> bool { + match *self { + SResult::Done(_, _) => true, + _ => false, + } + } + + pub fn is_needed(&self) -> bool { + match *self { + SResult::Needed(_) => true, + _ => false, + } + } + + pub fn is_err(&self) -> bool { + match *self { + SResult::Error(_) => true, + _ => false, + } + } + + pub fn done(self) -> Option<(usize, Output)> { + match self { + SResult::Done(offset, out) => Some((offset, out)), + _ => None, + } + } + + pub fn needed(self) -> Option { + match self { + SResult::Needed(bytes) => Some(bytes), + _ => None, + } + } + + pub fn err(self) -> Option { + match self { + SResult::Error(err) => Some(err), + _ => None, + } + } +} + +/// Returns the result of encoding/decoding +#[macro_export] +macro_rules! stream_done { + ($bytes:expr, $out:expr) => { + return SResult::Done($bytes, $out) + }; + ($bytes:expr) => { + stream_done!($bytes, ()) + }; +} + +/// Returns a buffer length error if there are not enough bytes +#[macro_export] +macro_rules! stream_len_cond { + ($buf:expr, $bytes:expr) => { + if $buf.len() < $bytes { + return SResult::Needed($bytes); + } + }; +} + +/// Returns an error +#[macro_export] +macro_rules! stream_err { + ($err:expr) => { + return SResult::Error($err) + }; + () => { + stream_err!(()) + }; +} + +/// Returns an error if a condition is unmet +#[macro_export] +macro_rules! stream_cond { + ($cond:expr, $err:expr) => { + if !$cond { + return SResult::Error($err); + } + }; + ($cond:expr) => { + stream_cond!($cond, ()); + }; +} + +/// Gets the result of an `Option`, throwing a stream error if it +/// is `None` +#[macro_export] +macro_rules! stream_from_option { + ($opt:expr, $err:expr) => { + match $opt { + Some(opt) => opt, + None => stream_err!($err), + } + }; + ($opt:expr) => { + stream_from_option!($opt, ()) + }; +} + +/// Extracts the result of encoding/decoding (the new offset and the output) only +/// if no errors were encountered in encoding. This macro makes it possible to +/// handle offsets easily for the following use cases: +/// +/// `enc_try!(result, offset)`: Unwrap an already-provided result that +/// represents starting from `offset` in the buffer. +/// `enc_try!(buf, offset; encoder, args..)`: Use the encoder function, called with the +/// optionally provided arguments, on the buffer starting from `offset`. +/// `enc_try!(buf, offset; object; method, args..)`: Same as the above, but the +/// encoder function is a member method of object. +/// +/// Additionally, the offset can always be omitted from any of the above, which +/// would result in it defaulting to 0. Idiomatically, the way to combine +/// encoders is to define another encoder as follows: +/// +/// ```rust,ignore +/// # use capsules_core::{enc_try, stream_done}; +/// # use capsules_core::stream::{SResult}; +/// +/// // call a simple encoder +/// let (bytes, out1) = enc_try!(buf; encoder1); +/// /* Do something with out1 */ +/// +/// // call another encoder, but starting at the previous offset +/// let (bytes, out2) = enc_try!(buf, bytes; encoder2); +/// /* Note that bytes is shadowed. Alternatively you could mutably update a +/// variable. */ +/// +/// // subsequently, encode a struct into the buffer, with some extra arguments +/// let (bytes, out3) = enc_try!(buf, bytes; someheader; encode, 2, 5); +/// +/// // report success without returning a meaningful output +/// stream_done!(bytes); +/// ``` +/// +/// Then, using an encoder can be done simply by: +/// +/// ```rust,ignore +/// # use capsules_core::stream::SResult; +/// +/// match encoder(&mut buf) { +/// SResult::Done(off, out) => { /* celebrate */ } +/// SResult::Needed(off) => { /* get more memory? */ } +/// SResult::Error(err) => { /* give up */ } +/// } +/// ``` +#[macro_export] +macro_rules! enc_try { + ($result:expr, $offset:expr) => { + match $result { + SResult::Done(offset, out) => ($offset + offset, out), + SResult::Needed(bytes) => { return SResult::Needed($offset + bytes); } + SResult::Error(error) => { return SResult::Error(error); } + } + }; + ($result:expr) + => { enc_try!($result, 0) }; + ($buf:expr, $offset:expr; $fun:expr) + => { enc_try!($fun(&mut $buf[$offset..]), $offset) }; + ($buf:expr, $offset:expr; $fun:expr, $($args:expr),+) + => { enc_try!($fun(&mut $buf[$offset..], $($args),+), $offset) }; + ($buf:expr, $offset:expr; $object:expr; $fun:ident) + => { enc_try!($object.$fun(&mut $buf[$offset..]), $offset) }; + ($buf:expr, $offset:expr; $object:expr; $fun:ident, $($args:expr),+) + => { enc_try!($object.$fun(&mut $buf[$offset..], $($args),+), $offset) }; + ($buf:expr; $($tts:tt)+) + => { enc_try!($buf, 0; $($tts)+) }; +} + +/// This is the aforementioned version of the unwrapping macro that only returns +/// the offset. With this, it can be simpler to programmatically chain multiple +/// headers together when the outputs do not have to be collated. +#[macro_export] +macro_rules! enc_consume { + ($($tts:tt)*) => { { + let (offset, _) = enc_try!($($tts)*); + offset + } }; +} + +/// The decoding equivalent of `enc_try`. The only difference is that only an +/// immutable borrow of the buffer is required each time. +#[macro_export] +macro_rules! dec_try { + ($result:expr, $offset:expr) => { + match $result { + SResult::Done(offset, out) => ($offset + offset, out), + SResult::Needed(bytes) => { return SResult::Needed($offset + bytes); } + SResult::Error(error) => { return SResult::Error(error); } + } + }; + ($result:expr) + => { dec_try!($result, 0) }; + ($buf:expr, $offset:expr; $fun:expr) + => { dec_try!($fun(&$buf[$offset..]), $offset) }; + ($buf:expr, $offset:expr; $fun:expr, $($args:expr),+) + => { dec_try!($fun(&$buf[$offset..], $($args),+), $offset) }; + ($buf:expr, $offset:expr; $object:expr; $fun:ident) + => { dec_try!($object.$fun(&$buf[$offset..]), $offset) }; + ($buf:expr, $offset:expr; $object:expr; $fun:ident, $($args:expr),+) + => { dec_try!($object.$fun(&$buf[$offset..], $($args),+), $offset) }; + ($buf:expr; $($tts:tt)+) + => { dec_try!($buf, 0; $($tts)+) }; +} + +/// The decoding equivalent of `enc_consume` +#[macro_export] +macro_rules! dec_consume { + ($($tts:tt)*) => { { + let (offset, _) = dec_try!($($tts)*); + offset + } }; +} + +pub fn encode_u8(buf: &mut [u8], b: u8) -> SResult { + stream_len_cond!(buf, 1); + buf[0] = b; + stream_done!(1); +} + +pub fn encode_u16(buf: &mut [u8], b: u16) -> SResult { + stream_len_cond!(buf, 2); + buf[0] = (b >> 8) as u8; + buf[1] = b as u8; + stream_done!(2); +} + +pub fn encode_u32(buf: &mut [u8], b: u32) -> SResult { + stream_len_cond!(buf, 4); + buf[0] = (b >> 24) as u8; + buf[1] = (b >> 16) as u8; + buf[2] = (b >> 8) as u8; + buf[3] = b as u8; + stream_done!(4); +} + +pub fn encode_bytes(buf: &mut [u8], bs: &[u8]) -> SResult { + stream_len_cond!(buf, bs.len()); + buf[..bs.len()].copy_from_slice(bs); + stream_done!(bs.len()); +} + +// This function assumes that the host is little-endian +pub fn encode_bytes_be(buf: &mut [u8], bs: &[u8]) -> SResult { + stream_len_cond!(buf, bs.len()); + for (i, b) in bs.iter().rev().enumerate() { + buf[i] = *b; + } + stream_done!(bs.len()); +} + +pub fn decode_u8(buf: &[u8]) -> SResult { + stream_len_cond!(buf, 1); + stream_done!(1, buf[0]); +} + +pub fn decode_u16(buf: &[u8]) -> SResult { + stream_len_cond!(buf, 2); + stream_done!(2, (buf[0] as u16) << 8 | (buf[1] as u16)); +} + +pub fn decode_u32(buf: &[u8]) -> SResult { + stream_len_cond!(buf, 4); + let b = (buf[0] as u32) << 24 | (buf[1] as u32) << 16 | (buf[2] as u32) << 8 | (buf[3] as u32); + stream_done!(4, b); +} + +pub fn decode_bytes(buf: &[u8], out: &mut [u8]) -> SResult { + stream_len_cond!(buf, out.len()); + let len = out.len(); + out.copy_from_slice(&buf[..len]); + stream_done!(out.len()); +} + +// This function assumes that the host is little-endian +pub fn decode_bytes_be(buf: &[u8], out: &mut [u8]) -> SResult { + stream_len_cond!(buf, out.len()); + for (i, b) in buf[..out.len()].iter().rev().enumerate() { + out[i] = *b; + } + stream_done!(out.len()); +} diff --git a/capsules/src/test/alarm.rs b/capsules/core/src/test/alarm.rs similarity index 88% rename from capsules/src/test/alarm.rs rename to capsules/core/src/test/alarm.rs index afea384e1e..3b94c2c5bb 100644 --- a/capsules/src/test/alarm.rs +++ b/capsules/core/src/test/alarm.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test that an Alarm implementation is working. Depends on a working //! UART and debug! macro. //! @@ -15,7 +19,7 @@ pub struct TestAlarm<'a, A: Alarm<'a>> { impl<'a, A: Alarm<'a>> TestAlarm<'a, A> { pub fn new(alarm: &'a A) -> TestAlarm<'a, A> { TestAlarm { - alarm: alarm, + alarm, ms: Cell::new(0), } } diff --git a/capsules/src/test/alarm_edge_cases.rs b/capsules/core/src/test/alarm_edge_cases.rs similarity index 90% rename from capsules/src/test/alarm_edge_cases.rs rename to capsules/core/src/test/alarm_edge_cases.rs index e07c3a9f76..08a6926aee 100644 --- a/capsules/src/test/alarm_edge_cases.rs +++ b/capsules/core/src/test/alarm_edge_cases.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test that an Alarm implementation is working by trying a few edge //! cases on the delay, including delays of 1 and 0 delays. Depends //! on a working UART and debug! macro. @@ -17,7 +21,7 @@ pub struct TestAlarmEdgeCases<'a, A: 'a> { impl<'a, A: Alarm<'a>> TestAlarmEdgeCases<'a, A> { pub fn new(alarm: &'a A) -> TestAlarmEdgeCases<'a, A> { TestAlarmEdgeCases { - alarm: alarm, + alarm, counter: Cell::new(0), alarms: [ 100, 200, 25, 25, 25, 25, 500, 0, 448, 15, 19, 1, 0, 33, 5, 1000, 27, 1, 0, 1, diff --git a/capsules/core/src/test/capsule_test.rs b/capsules/core/src/test/capsule_test.rs new file mode 100644 index 0000000000..7bc59b9b22 --- /dev/null +++ b/capsules/core/src/test/capsule_test.rs @@ -0,0 +1,70 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Interface for running capsule tests. +//! +//! As Tock capsules are asynchronous, it is difficult for a test runner to +//! determine when a test has finished. This interface provides a `done()` +//! callback used by the test implementation to notify when the test has +//! completed. +//! +//! A simple example of a test capsule using this interface: +//! +//! ```rust,ignore +//! pub struct TestSensorX { +//! client: OptionalCell<&'static dyn CapsuleTestClient>, +//! } +//! +//! impl TestSensorX { +//! pub fn new() -> Self { +//! TestHmacSha256 { +//! client: OptionalCell::empty(), +//! } +//! } +//! } +//! +//! impl CapsuleTest for TestSensorX { +//! fn set_client(&self, client: &'static dyn CapsuleTestClient) { +//! self.client.set(client); +//! } +//! } +//! +//! impl AsyncClient for TestSensorX { +//! fn operation_complete(&self) { +//! // Test has finished at this point. +//! self.client.map(|client| { +//! client.done(Ok(())); +//! }); +//! } +//! } +//! ``` + +use kernel::ErrorCode; + +/// Errors for the result of a failed test. +pub enum CapsuleTestError { + /// The test computed some result (e.g., a checksum or hash) and the result + /// is not correct (e.g., it doesn't match the intended value, say the + /// correct checksum or hash). + IncorrectResult, + + /// An error occurred while running the test, and the resulting `ErrorCode` + /// is provided. + ErrorCode(ErrorCode), +} + +/// Client for receiving test done events. +pub trait CapsuleTestClient { + /// Called when the test is finished. If the test was successful, `result` + /// is `Ok(())`. If the test failed, `result` is `Err()` with a suitable + /// error. + fn done(&'static self, result: Result<(), CapsuleTestError>); +} + +/// Identify a test as a capsule test. This is only used for setting the client +/// for test complete callbacks. +pub trait CapsuleTest { + /// Set the client for the done callback. + fn set_client(&self, client: &'static dyn CapsuleTestClient); +} diff --git a/capsules/src/test/double_grant_entry.rs b/capsules/core/src/test/double_grant_entry.rs similarity index 91% rename from capsules/src/test/double_grant_entry.rs rename to capsules/core/src/test/double_grant_entry.rs index 09dd904e31..7c7d8e4bed 100644 --- a/capsules/src/test/double_grant_entry.rs +++ b/capsules/core/src/test/double_grant_entry.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test that tries to enter a grant twice. //! //! This must fail or Tock allows multiple mutable references to the same memory @@ -83,7 +87,13 @@ impl TestGrantDoubleEntry { } impl SyscallDriver for TestGrantDoubleEntry { - fn command(&self, command_num: usize, _: usize, _: usize, appid: ProcessId) -> CommandReturn { + fn command( + &self, + command_num: usize, + _: usize, + _: usize, + processid: ProcessId, + ) -> CommandReturn { match command_num { 0 => CommandReturn::success(), @@ -97,7 +107,7 @@ impl SyscallDriver for TestGrantDoubleEntry { // Enter the grant for the app. let err = self .grant - .enter(appid, |appgrant, _| { + .enter(processid, |appgrant, _| { // We can now change the state of the app's grant // region. appgrant.pending = true; @@ -131,12 +141,12 @@ impl SyscallDriver for TestGrantDoubleEntry { let mut found_pending = false; // Make sure the grant is allocated. - let _ = self.grant.enter(appid, |appgrant, _| { + let _ = self.grant.enter(processid, |appgrant, _| { appgrant.pending = false; }); for app in self.grant.iter() { - let _ = self.grant.enter(appid, |appgrant, _| { + let _ = self.grant.enter(processid, |appgrant, _| { // Mark the field. appgrant.pending = true; @@ -165,10 +175,10 @@ impl SyscallDriver for TestGrantDoubleEntry { // entered the same grant twice. let mut found_pending = false; - let _ = self.grant.enter(appid, |appgrant, _| { + let _ = self.grant.enter(processid, |appgrant, _| { appgrant.pending = true; - let _ = self.grant.enter(appid, |appgrant2, _| { + let _ = self.grant.enter(processid, |appgrant2, _| { if appgrant2.pending { found_pending = true; } diff --git a/capsules/core/src/test/mod.rs b/capsules/core/src/test/mod.rs new file mode 100644 index 0000000000..ab9f77be99 --- /dev/null +++ b/capsules/core/src/test/mod.rs @@ -0,0 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +pub mod alarm; +pub mod alarm_edge_cases; +pub mod capsule_test; +pub mod double_grant_entry; +pub mod random_alarm; +pub mod random_timer; +pub mod rng; +pub mod virtual_rng; +pub mod virtual_uart; diff --git a/capsules/src/test/random_alarm.rs b/capsules/core/src/test/random_alarm.rs similarity index 93% rename from capsules/src/test/random_alarm.rs rename to capsules/core/src/test/random_alarm.rs index b5f1536132..a704cf6c1a 100644 --- a/capsules/src/test/random_alarm.rs +++ b/capsules/core/src/test/random_alarm.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test that an Alarm implementation is working by trying a few edge //! cases on the delay, including delays of 1 and 0 delays. Depends //! on a working UART and debug! macro. @@ -21,7 +25,7 @@ pub struct TestRandomAlarm<'a, A: Alarm<'a>> { impl<'a, A: Alarm<'a>> TestRandomAlarm<'a, A> { pub fn new(alarm: &'a A, value: usize, ch: char, print_output: bool) -> TestRandomAlarm<'a, A> { TestRandomAlarm { - alarm: alarm, + alarm, counter: Cell::new(value), expected: Cell::new(alarm.ticks_from_seconds(0)), _id: ch, diff --git a/capsules/src/test/random_timer.rs b/capsules/core/src/test/random_timer.rs similarity index 90% rename from capsules/src/test/random_timer.rs rename to capsules/core/src/test/random_timer.rs index ca1b7e2b4b..e424c73014 100644 --- a/capsules/src/test/random_timer.rs +++ b/capsules/core/src/test/random_timer.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test that a Timer implementation is working by trying a few edge //! cases on the interval, including intervals of 1 and 0. Depends //! on a working UART and debug! macro. Tries repeating as well as @@ -22,7 +26,7 @@ pub struct TestRandomTimer<'a, T: 'a> { impl<'a, T: Timer<'a>> TestRandomTimer<'a, T> { pub fn new(timer: &'a T, value: usize, ch: char) -> TestRandomTimer<'a, T> { TestRandomTimer { - timer: timer, + timer, interval: Cell::new(0), counter: Cell::new(0), iv: Cell::new(value as u32), @@ -41,7 +45,7 @@ impl<'a, T: Timer<'a>> TestRandomTimer<'a, T> { let counter = self.counter.get(); if counter == 0 { - let mut us: u32 = ((iv * 745939) % 115843) as u32; + let mut us: u32 = (iv * 745939) % 115843; if us % 11 == 0 { // Try delays of zero in 1 of 11 cases us = 0; diff --git a/capsules/src/test/rng.rs b/capsules/core/src/test/rng.rs similarity index 95% rename from capsules/src/test/rng.rs rename to capsules/core/src/test/rng.rs index bb91cda848..4c10f9fab8 100644 --- a/capsules/src/test/rng.rs +++ b/capsules/core/src/test/rng.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test entropy and random number generators. Usually, to test the //! full library, these generators should be through two layers of //! translation for entropy then converted to randomness. For example, @@ -20,7 +24,7 @@ pub struct TestRandom<'a> { impl<'a> TestRandom<'a> { pub fn new(random: &'a dyn rng::Random<'a>) -> TestRandom<'a> { - TestRandom { random: random } + TestRandom { random } } pub fn run(&self) { @@ -42,7 +46,7 @@ pub struct TestRng<'a> { impl<'a> TestRng<'a> { pub fn new(rng: &'a dyn rng::Rng<'a>) -> TestRng<'a> { TestRng { - rng: rng, + rng, pool: Cell::new([0xeeeeeeee; ELEMENTS]), count: Cell::new(0), } @@ -76,7 +80,7 @@ impl<'a> rng::Client for TestRng<'a> { let mut pool = self.pool.get(); let mut count = self.count.get(); pool[count] = data; - count = count + 1; + count += 1; self.pool.set(pool); self.count.set(count); @@ -105,7 +109,7 @@ pub struct TestEntropy32<'a> { impl<'a> TestEntropy32<'a> { pub fn new(egen: &'a dyn entropy::Entropy32<'a>) -> TestEntropy32<'a> { TestEntropy32 { - egen: egen, + egen, pool: Cell::new([0xeeeeeeee; ELEMENTS]), count: Cell::new(0), } @@ -139,7 +143,7 @@ impl<'a> entropy::Client32 for TestEntropy32<'a> { let mut pool = self.pool.get(); let mut count = self.count.get(); pool[count] = data; - count = count + 1; + count += 1; self.pool.set(pool); self.count.set(count); @@ -168,7 +172,7 @@ pub struct TestEntropy8<'a> { impl<'a> TestEntropy8<'a> { pub fn new(egen: &'a dyn entropy::Entropy8<'a>) -> TestEntropy8<'a> { TestEntropy8 { - egen: egen, + egen, pool: Cell::new([0xee; ELEMENTS]), count: Cell::new(0), } @@ -202,7 +206,7 @@ impl<'a> entropy::Client8 for TestEntropy8<'a> { let mut pool = self.pool.get(); let mut count = self.count.get(); pool[count] = data; - count = count + 1; + count += 1; self.pool.set(pool); self.count.set(count); diff --git a/capsules/src/test/virtual_rng.rs b/capsules/core/src/test/virtual_rng.rs similarity index 87% rename from capsules/src/test/virtual_rng.rs rename to capsules/core/src/test/virtual_rng.rs index bac23977a8..2f8518c1a8 100644 --- a/capsules/src/test/virtual_rng.rs +++ b/capsules/core/src/test/virtual_rng.rs @@ -1,8 +1,12 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test virtual rng for a single device //! Gets a specified number of random numbers by making sequential calls to get() //! Full test harness for this can be found in nano33ble/test/virtual_rng_test -use crate::virtual_rng::VirtualRngMasterDevice; +use crate::virtualizers::virtual_rng::VirtualRngMasterDevice; use core::cell::Cell; @@ -22,8 +26,8 @@ pub struct TestRng<'a> { impl<'a> TestRng<'a> { pub fn new(device_id: usize, device: &'a VirtualRngMasterDevice<'a>) -> TestRng<'a> { TestRng { - device_id: device_id, - device: device, + device_id, + device, num_requests: Cell::new(NUM_REQUESTS), } } diff --git a/capsules/src/test/virtual_uart.rs b/capsules/core/src/test/virtual_uart.rs similarity index 86% rename from capsules/src/test/virtual_uart.rs rename to capsules/core/src/test/virtual_uart.rs index 4217241437..9d533b1948 100644 --- a/capsules/src/test/virtual_uart.rs +++ b/capsules/core/src/test/virtual_uart.rs @@ -1,6 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test reception on the virtualized UART: best if multiple Tests are //! instantiated and tested in parallel. -use crate::virtual_uart::UartDevice; +use crate::virtualizers::virtual_uart::UartDevice; use kernel::debug; use kernel::hil::uart; @@ -16,7 +20,7 @@ pub struct TestVirtualUartReceive { impl TestVirtualUartReceive { pub fn new(device: &'static UartDevice<'static>, buffer: &'static mut [u8]) -> Self { TestVirtualUartReceive { - device: device, + device, buffer: TakeCell::new(buffer), } } diff --git a/capsules/core/src/virtualizers/mod.rs b/capsules/core/src/virtualizers/mod.rs new file mode 100644 index 0000000000..348ace8cd5 --- /dev/null +++ b/capsules/core/src/virtualizers/mod.rs @@ -0,0 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +pub mod virtual_adc; +pub mod virtual_aes_ccm; +pub mod virtual_alarm; +pub mod virtual_flash; +pub mod virtual_i2c; +pub mod virtual_pwm; +pub mod virtual_rng; +pub mod virtual_spi; +pub mod virtual_timer; +pub mod virtual_uart; diff --git a/capsules/src/virtual_adc.rs b/capsules/core/src/virtualizers/virtual_adc.rs similarity index 82% rename from capsules/src/virtual_adc.rs rename to capsules/core/src/virtualizers/virtual_adc.rs index f382b3c1f5..4ae33aac9e 100644 --- a/capsules/src/virtual_adc.rs +++ b/capsules/core/src/virtualizers/virtual_adc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Virtual ADC Capsule //! //! Support Single Sample for now. @@ -8,13 +12,13 @@ use kernel::utilities::cells::OptionalCell; use kernel::ErrorCode; /// ADC Mux -pub struct MuxAdc<'a, A: hil::adc::Adc> { +pub struct MuxAdc<'a, A: hil::adc::Adc<'a>> { adc: &'a A, devices: List<'a, AdcDevice<'a, A>>, inflight: OptionalCell<&'a AdcDevice<'a, A>>, } -impl<'a, A: hil::adc::Adc> hil::adc::Client for MuxAdc<'a, A> { +impl<'a, A: hil::adc::Adc<'a>> hil::adc::Client for MuxAdc<'a, A> { fn sample_ready(&self, sample: u16) { self.inflight.take().map(|inflight| { for node in self.devices.iter() { @@ -31,10 +35,10 @@ impl<'a, A: hil::adc::Adc> hil::adc::Client for MuxAdc<'a, A> { } } -impl<'a, A: hil::adc::Adc> MuxAdc<'a, A> { +impl<'a, A: hil::adc::Adc<'a>> MuxAdc<'a, A> { pub const fn new(adc: &'a A) -> MuxAdc<'a, A> { MuxAdc { - adc: adc, + adc, devices: List::new(), inflight: OptionalCell::empty(), } @@ -74,7 +78,7 @@ pub(crate) enum Operation { } /// Virtual ADC device -pub struct AdcDevice<'a, A: hil::adc::Adc> { +pub struct AdcDevice<'a, A: hil::adc::Adc<'a>> { mux: &'a MuxAdc<'a, A>, channel: A::Channel, operation: OptionalCell, @@ -82,11 +86,11 @@ pub struct AdcDevice<'a, A: hil::adc::Adc> { client: OptionalCell<&'a dyn hil::adc::Client>, } -impl<'a, A: hil::adc::Adc> AdcDevice<'a, A> { +impl<'a, A: hil::adc::Adc<'a>> AdcDevice<'a, A> { pub const fn new(mux: &'a MuxAdc<'a, A>, channel: A::Channel) -> AdcDevice<'a, A> { let adc_user = AdcDevice { - mux: mux, - channel: channel, + mux, + channel, operation: OptionalCell::empty(), next: ListLink::empty(), client: OptionalCell::empty(), @@ -99,13 +103,13 @@ impl<'a, A: hil::adc::Adc> AdcDevice<'a, A> { } } -impl<'a, A: hil::adc::Adc> ListNode<'a, AdcDevice<'a, A>> for AdcDevice<'a, A> { +impl<'a, A: hil::adc::Adc<'a>> ListNode<'a, AdcDevice<'a, A>> for AdcDevice<'a, A> { fn next(&'a self) -> &'a ListLink<'a, AdcDevice<'a, A>> { &self.next } } -impl hil::adc::AdcChannel for AdcDevice<'_, A> { +impl<'a, A: hil::adc::Adc<'a>> hil::adc::AdcChannel<'a> for AdcDevice<'a, A> { fn sample(&self) -> Result<(), ErrorCode> { self.operation.set(Operation::OneSample); self.mux.do_next_op(); @@ -129,7 +133,7 @@ impl hil::adc::AdcChannel for AdcDevice<'_, A> { fn get_voltage_reference_mv(&self) -> Option { self.mux.get_voltage_reference_mv() } - fn set_client(&self, client: &'static dyn hil::adc::Client) { + fn set_client(&self, client: &'a dyn hil::adc::Client) { self.client.set(client); } } diff --git a/capsules/src/virtual_aes_ccm.rs b/capsules/core/src/virtualizers/virtual_aes_ccm.rs similarity index 94% rename from capsules/src/virtual_aes_ccm.rs rename to capsules/core/src/virtualizers/virtual_aes_ccm.rs index ff50def4f3..2024a3b099 100644 --- a/capsules/src/virtual_aes_ccm.rs +++ b/capsules/core/src/virtualizers/virtual_aes_ccm.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implements and virtualizes AES-CCM* encryption/decryption/authentication using an underlying //! AES-CBC and AES-CTR implementation. //! @@ -39,10 +43,10 @@ //! Usage //! ----- //! -//! ```rust -//! # use capsules::test::aes_ccm::Test; -//! # use capsules::virtual_aes_ccm; -//! # use kernel::common::dynamic_deferred_call::DynamicDeferredCall; +//! ```rust,ignore +//! # use capsules_core::test::aes_ccm::Test; +//! # use capsules_core::virtual_aes_ccm; +//! # use kernel::common::deferred_call::DeferredCallClient; //! # use kernel::hil::symmetric_encryption::{AES128, AES128CCM, AES128_BLOCK_SIZE}; //! # use kernel::static_init; //! # use sam4l::aes::{Aes, AES}; @@ -50,12 +54,8 @@ //! type AESCCMCLIENT = virtual_aes_ccm::VirtualAES128CCM<'static, AESCCMMUX>; //! // mux //! let ccm_mux = static_init!(AESCCMMUX, virtual_aes_ccm::MuxAES128CCM::new(&AES)); +//! ccm_mux.register(); //! AES.set_client(ccm_mux); -//! ccm_mux.initialize_callback_handle( -//! dynamic_deferred_caller -//! .register(ccm_mux) -//! .unwrap(), // Unwrap fail = no deferred call slot available for ccm mux -//! ); //! const CRYPT_SIZE: usize = 7 * AES128_BLOCK_SIZE; //! let crypt_buf1 = static_init!([u8; CRYPT_SIZE], [0x00; CRYPT_SIZE]); //! let ccm_client1 = static_init!( @@ -84,9 +84,7 @@ use core::cell::Cell; use kernel::collections::list::{List, ListLink, ListNode}; use kernel::debug; -use kernel::dynamic_deferred_call::{ - DeferredCallHandle, DynamicDeferredCall, DynamicDeferredCallClient, -}; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; use kernel::hil::symmetric_encryption; use kernel::hil::symmetric_encryption::{ AES128Ctr, AES128, AES128CBC, AES128ECB, AES128_BLOCK_SIZE, AES128_KEY_SIZE, CCM_NONCE_LENGTH, @@ -94,8 +92,8 @@ use kernel::hil::symmetric_encryption::{ use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::ErrorCode; -use crate::net::stream::SResult; -use crate::net::stream::{encode_bytes, encode_u16}; +use crate::stream::SResult; +use crate::stream::{encode_bytes, encode_u16}; #[derive(Copy, Clone, Eq, PartialEq, Debug)] enum CCMState { @@ -126,13 +124,13 @@ impl CryptFunctionParameters { encrypting: bool, ) -> CryptFunctionParameters { CryptFunctionParameters { - buf: buf, - a_off: a_off, - m_off: m_off, - m_len: m_len, - mic_len: mic_len, - confidential: confidential, - encrypting: encrypting, + buf, + a_off, + m_off, + m_len, + mic_len, + confidential, + encrypting, } } } @@ -142,41 +140,27 @@ pub struct MuxAES128CCM<'a, A: AES128<'a> + AES128Ctr + AES128CBC + AES128ECB> { client: OptionalCell<&'a dyn symmetric_encryption::Client<'a>>, ccm_clients: List<'a, VirtualAES128CCM<'a, A>>, inflight: OptionalCell<&'a VirtualAES128CCM<'a, A>>, - deferred_caller: &'a DynamicDeferredCall, - handle: OptionalCell, + deferred_call: DeferredCall, } impl<'a, A: AES128<'a> + AES128Ctr + AES128CBC + AES128ECB> MuxAES128CCM<'a, A> { - pub fn new(aes: &'a A, deferred_caller: &'a DynamicDeferredCall) -> MuxAES128CCM<'a, A> { + pub fn new(aes: &'a A) -> Self { aes.enable(); // enable the hardware, in case it's forgotten elsewhere - MuxAES128CCM { - aes: aes, + Self { + aes, client: OptionalCell::empty(), ccm_clients: List::new(), inflight: OptionalCell::empty(), - deferred_caller: deferred_caller, - handle: OptionalCell::empty(), + deferred_call: DeferredCall::new(), } } - /// inorder to receive callbacks correctly, please call - /// ```rust - /// mux.initialize_callback_handle( - /// dynamic_deferred_caller.register(mux) - /// .unwrap() // Unwrap fail = no deferred call slot available for ccm mux - /// ); - /// ``` - /// after the creation of the mux - pub fn initialize_callback_handle(&self, handle: DeferredCallHandle) { - self.handle.replace(handle); - } - /// Asynchronously executes the next operation, if any. Used by calls /// to trigger do_next_op such that it will execute after the call /// returns. /// See virtual_uart::MuxUart<'a>::do_next_op_async fn do_next_op_async(&self) { - self.handle.map(|handle| self.deferred_caller.set(*handle)); + self.deferred_call.set(); } fn do_next_op(&self) { @@ -214,12 +198,16 @@ impl<'a, A: AES128<'a> + AES128Ctr + AES128CBC + AES128ECB> MuxAES128CCM<'a, A> } } -impl<'a, A: AES128<'a> + AES128Ctr + AES128CBC + AES128ECB> DynamicDeferredCallClient +impl<'a, A: AES128<'a> + AES128Ctr + AES128CBC + AES128ECB> DeferredCallClient for MuxAES128CCM<'a, A> { - fn call(&self, _handle: DeferredCallHandle) { + fn handle_deferred_call(&self) { self.do_next_op(); } + + fn register(&'static self) { + self.deferred_call.register(self); + } } impl<'a, A: AES128<'a> + AES128Ctr + AES128CBC + AES128ECB> symmetric_encryption::Client<'a> @@ -271,8 +259,8 @@ impl<'a, A: AES128<'a> + AES128Ctr + AES128CBC + AES128ECB> VirtualAES128CCM<'a, crypt_buf: &'static mut [u8], ) -> VirtualAES128CCM<'a, A> { VirtualAES128CCM { - mux: mux, - aes: &mux.aes, + mux, + aes: mux.aes, next: ListLink::empty(), crypt_buf: TakeCell::new(crypt_buf), crypt_auth_len: Cell::new(0), @@ -312,7 +300,7 @@ impl<'a, A: AES128<'a> + AES128Ctr + AES128CBC + AES128ECB> VirtualAES128CCM<'a, SResult::Needed(_) => { return Err(ErrorCode::NOMEM); } - SResult::Error(_) => { + SResult::Error(()) => { return Err(ErrorCode::FAIL); } }; @@ -375,7 +363,7 @@ impl<'a, A: AES128<'a> + AES128Ctr + AES128CBC + AES128ECB> VirtualAES128CCM<'a, // encoding of a_len: if a_data.len() == 0 { // L(a) is empty, and the Adata flag is zero - } else if a_data.len() < 0xff00 as usize { + } else if a_data.len() < 0xff00_usize { // L(a) is l(a) in 2 bytes of little-endian off = enc_consume!(buf, off; encode_u16, (a_data.len() as u16).to_le()); diff --git a/capsules/src/virtual_alarm.rs b/capsules/core/src/virtualizers/virtual_alarm.rs similarity index 68% rename from capsules/src/virtual_alarm.rs rename to capsules/core/src/virtualizers/virtual_alarm.rs index d6f93c0e35..70581e8f53 100644 --- a/capsules/src/virtual_alarm.rs +++ b/capsules/core/src/virtualizers/virtual_alarm.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Virtualize the Alarm interface to enable multiple users of an underlying //! alarm hardware peripheral. @@ -85,10 +89,6 @@ impl<'a, A: Alarm<'a>> Time for VirtualMuxAlarm<'a, A> { impl<'a, A: Alarm<'a>> Alarm<'a> for VirtualMuxAlarm<'a, A> { fn set_alarm_client(&self, client: &'a dyn time::AlarmClient) { - // Reset the alarm state: should it do this? Does not seem - // to be semantically correct. What if you just wanted to - // change the callback. Keeping it but skeptical. -pal - self.armed.set(false); self.client.set(client); } @@ -147,7 +147,7 @@ impl<'a, A: Alarm<'a>> Alarm<'a> for VirtualMuxAlarm<'a, A> { if enabled == 0 { //debug!("virtual_alarm: first alarm: set it."); self.mux.set_alarm(reference, dt); - } else if self.mux.firing.get() == false { + } else if !self.mux.firing.get() { // If firing is true, the mux will scan all the alarms after // firing and pick the soonest one so do not need to modify the // mux. Otherwise, this is an alarm @@ -217,7 +217,7 @@ impl<'a, A: Alarm<'a>> MuxAlarm<'a, A> { MuxAlarm { virtual_alarms: List::new(), enabled: Cell::new(0), - alarm: alarm, + alarm, firing: Cell::new(false), next_tick_vals: Cell::new(None), } @@ -238,8 +238,6 @@ impl<'a, A: Alarm<'a>> time::AlarmClient for MuxAlarm<'a, A> { /// When the underlying alarm has fired, we have to multiplex this event back to the virtual /// alarms that should now fire. fn alarm(&self) { - let now = self.alarm.now(); - let half_max = A::Ticks::half_max_value(); // Check whether to fire each alarm. At this level, alarms are one-shot, // so a repeating client will set it again in the alarm() callback. self.firing.set(true); @@ -247,6 +245,10 @@ impl<'a, A: Alarm<'a>> time::AlarmClient for MuxAlarm<'a, A> { .iter() .filter(|cur| { let dt_ref = cur.dt_reference.get(); + // It is very important to get the current now time as the reference could have been + // set from now in the previous for_each iteration. We rely on the reference always + // being in the past when compared to now. + let now = self.alarm.now(); cur.armed.get() && !now.within_range(dt_ref.reference, dt_ref.reference_plus_dt()) }) .for_each(|cur| { @@ -256,7 +258,7 @@ impl<'a, A: Alarm<'a>> time::AlarmClient for MuxAlarm<'a, A> { // remaining time. cur.dt_reference.set(TickDtReference { reference: dt_ref.reference_plus_dt(), - dt: half_max, + dt: A::Ticks::half_max_value(), extended: false, }); } else { @@ -271,11 +273,24 @@ impl<'a, A: Alarm<'a>> time::AlarmClient for MuxAlarm<'a, A> { // Find the soonest alarm client (if any) and set the "next" underlying // alarm based on it. This needs to happen after firing all expired // alarms since those may have reset new alarms. + let now = self.alarm.now(); let next = self .virtual_alarms .iter() .filter(|cur| cur.armed.get()) - .min_by_key(|cur| cur.dt_reference.get().reference_plus_dt().wrapping_sub(now)); + .min_by_key(|cur| { + let when = cur.dt_reference.get(); + // If the alarm has already expired, then it should be + // considered as the earliest possible (0 ticks), so it + // will trigger as soon as possible. This can happen + // if the alarm expired *after* it was examined in the + // above loop. + if !now.within_range(when.reference, when.reference_plus_dt()) { + A::Ticks::from(0u32) + } else { + when.reference_plus_dt().wrapping_sub(now) + } + }); // Set the alarm. if let Some(valrm) = next { @@ -311,19 +326,53 @@ mod tests { } } + /// The emulated delay from when hardware timer to when kernel loop will + /// run to check if alarms have fired or not. + pub fn hardware_delay(&self) -> Ticks32 { + Ticks32::from(10) + } + /// Fast forwards time to the next time we would fire an alarm and call client. Returns if /// alarm is still armed after triggering client - fn trigger_next_alarm(&self) -> bool { - let hardware_delay = Ticks32::from(10); + pub fn trigger_next_alarm(&self) -> bool { + if !self.is_armed() { + return false; + } self.now.set( self.reference .get() .wrapping_add(self.dt.get()) - .wrapping_add(hardware_delay), + .wrapping_add(self.hardware_delay()), ); self.client.map(|c| c.alarm()); self.is_armed() } + + /// Runs for the specified number of ticks as long as there are alarms armed. + pub fn run_for_ticks(&self, left: Ticks32) { + let final_now = self.now.get().wrapping_add(left); + let mut left = left.into_u32(); + + while self.is_armed() { + // Ensure that we have enough remaining ticks to handle the next alarm. Reference is + // always in the past, so we need to figure out the difference between the reference + // and now to discount the DT the alarm needs to wait by. + let ticks_from_reference = self.now.get().wrapping_sub(self.reference.get()); + let dt = self + .dt + .get() + .into_u32() + .saturating_sub(ticks_from_reference.into_u32()); + if dt <= left { + left -= dt; + self.trigger_next_alarm(); + } else { + break; + } + } + // Ensure that we ate up all of the time we were suppose to run for + self.now.set(final_now); + } } impl Time for FakeAlarm<'_> { @@ -331,7 +380,10 @@ mod tests { type Frequency = Freq1KHz; fn now(&self) -> Ticks32 { - self.now.get() + // Every time we get now, it needs to increment to represent a free running timer + let new_now = Ticks32::from(self.now.get().into_u32() + 1); + self.now.set(new_now); + new_now } } @@ -432,4 +484,101 @@ mod tests { assert_eq!(client.count(), 3); } + + struct SetAlarmClient<'a> { + alarm: &'a VirtualMuxAlarm<'a, FakeAlarm<'a>>, + dt: u32, + } + + impl<'a> SetAlarmClient<'a> { + fn new(alarm: &'a VirtualMuxAlarm<'a, FakeAlarm<'a>>, dt: u32) -> Self { + Self { alarm, dt } + } + } + + impl AlarmClient for SetAlarmClient<'_> { + fn alarm(&self) { + self.alarm.set_alarm(self.alarm.now(), self.dt.into()); + } + } + + #[test] + fn test_second_alarm_set_during_first_alarm_firing() { + let alarm = FakeAlarm::new(); + let mux = MuxAlarm::new(&alarm); + alarm.set_alarm_client(&mux); + + // It is important that 0 is setup last so it is first in the linked list + let v_alarms = &[VirtualMuxAlarm::new(&mux), VirtualMuxAlarm::new(&mux)]; + v_alarms[1].setup(); + v_alarms[0].setup(); + + let set_v1_alarm = SetAlarmClient::new(&v_alarms[1], 100); + v_alarms[0].set_alarm_client(&set_v1_alarm); + + let counter = ClientCounter::new(); + v_alarms[1].set_alarm_client(&counter); + + // Set the first alarm for 10 ticks in the future. This should then set the second alarm, + // but not call fired for the second alarm until the timer gets to 100 + v_alarms[0].set_alarm(0.into(), 10.into()); + let still_armed = alarm.trigger_next_alarm(); + + // Second alarm should not have triggered yet + assert!(alarm.now().into_u32() < 100); + assert_eq!(counter.count(), 0); + assert!(still_armed); + + let still_armed = alarm.trigger_next_alarm(); + + assert!(alarm.now().into_u32() > 100); + assert_eq!(counter.count(), 1); + assert!(!still_armed); + } + + #[test] + fn test_quick_alarms_not_skipped() { + let alarm = FakeAlarm::new(); + let client = ClientCounter::new(); + + let mux = MuxAlarm::new(&alarm); + alarm.set_alarm_client(&mux); + + let v_alarms = &[ + VirtualMuxAlarm::new(&mux), + VirtualMuxAlarm::new(&mux), + VirtualMuxAlarm::new(&mux), + VirtualMuxAlarm::new(&mux), + VirtualMuxAlarm::new(&mux), + VirtualMuxAlarm::new(&mux), + ]; + + // Precalculated the now and dt for all alarms. The DT should be large enough that the + // initial check for firing is not true, but after evaluating all alarms, they would all + // be firing. This happens since time "progresses" every time now() is called, which + // emulates the clock progressing in real time. + let now = alarm.now(); + let dt = alarm + .hardware_delay() + .wrapping_add(Ticks32::from(v_alarms.len() as u32)); + + for v in v_alarms { + v.setup(); + v.set_alarm_client(&client); + let _ = v.set_alarm(now, dt); + } + + // Set one alarm to trigger immediately (at the hardware delay) and the other alarm to + // trigger in the future by some large degree + let _ = v_alarms[0].set_alarm(now, 0.into()); + let _ = v_alarms[1].set_alarm(now, 1_000.into()); + + // Run the alarm long enough for every alarm but the longer alarm to fire, and all other + // alarms should have fired once + alarm.run_for_ticks(Ticks32::from(750)); + assert_eq!(client.count(), v_alarms.len() - 1); + // Run the alarm long enough for the longer alarm to fire as well and verify count + alarm.run_for_ticks(Ticks32::from(750)); + assert_eq!(client.count(), v_alarms.len()); + } } diff --git a/capsules/src/virtual_flash.rs b/capsules/core/src/virtualizers/virtual_flash.rs similarity index 79% rename from capsules/src/virtual_flash.rs rename to capsules/core/src/virtualizers/virtual_flash.rs index a4310792c8..610a9ad21d 100644 --- a/capsules/src/virtual_flash.rs +++ b/capsules/core/src/virtualizers/virtual_flash.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Virtualize writing flash. //! //! `MuxFlash` provides shared access to a flash interface from multiple clients @@ -11,19 +15,19 @@ //! Usage //! ----- //! -//! ``` +//! ```rust,ignore //! # use kernel::{hil, static_init}; //! //! // Create the mux. //! let mux_flash = static_init!( -//! capsules::virtual_flash::MuxFlash<'static, sam4l::flashcalw::FLASHCALW>, -//! capsules::virtual_flash::MuxFlash::new(&sam4l::flashcalw::FLASH_CONTROLLER)); +//! capsules_core::virtual_flash::MuxFlash<'static, sam4l::flashcalw::FLASHCALW>, +//! capsules_core::virtual_flash::MuxFlash::new(&sam4l::flashcalw::FLASH_CONTROLLER)); //! hil::flash::HasClient::set_client(&sam4l::flashcalw::FLASH_CONTROLLER, mux_flash); //! //! // Everything that then uses the virtualized flash must use one of these. //! let virtual_flash = static_init!( -//! capsules::virtual_flash::FlashUser<'static, sam4l::flashcalw::FLASHCALW>, -//! capsules::virtual_flash::FlashUser::new(mux_flash)); +//! capsules_core::virtual_flash::FlashUser<'static, sam4l::flashcalw::FLASHCALW>, +//! capsules_core::virtual_flash::FlashUser::new(mux_flash)); //! ``` use core::cell::Cell; @@ -43,23 +47,31 @@ pub struct MuxFlash<'a, F: hil::flash::Flash + 'static> { } impl hil::flash::Client for MuxFlash<'_, F> { - fn read_complete(&self, pagebuffer: &'static mut F::Page, error: hil::flash::Error) { + fn read_complete( + &self, + pagebuffer: &'static mut F::Page, + result: Result<(), hil::flash::Error>, + ) { self.inflight.take().map(move |user| { - user.read_complete(pagebuffer, error); + user.read_complete(pagebuffer, result); }); self.do_next_op(); } - fn write_complete(&self, pagebuffer: &'static mut F::Page, error: hil::flash::Error) { + fn write_complete( + &self, + pagebuffer: &'static mut F::Page, + result: Result<(), hil::flash::Error>, + ) { self.inflight.take().map(move |user| { - user.write_complete(pagebuffer, error); + user.write_complete(pagebuffer, result); }); self.do_next_op(); } - fn erase_complete(&self, error: hil::flash::Error) { + fn erase_complete(&self, result: Result<(), hil::flash::Error>) { self.inflight.take().map(move |user| { - user.erase_complete(error); + user.erase_complete(result); }); self.do_next_op(); } @@ -68,7 +80,7 @@ impl hil::flash::Client for MuxFlash<'_, F> { impl<'a, F: hil::flash::Flash> MuxFlash<'a, F> { pub const fn new(flash: &'a F) -> MuxFlash<'a, F> { MuxFlash { - flash: flash, + flash, users: List::new(), inflight: OptionalCell::empty(), } @@ -140,9 +152,9 @@ pub struct FlashUser<'a, F: hil::flash::Flash + 'static> { } impl<'a, F: hil::flash::Flash> FlashUser<'a, F> { - pub const fn new(mux: &'a MuxFlash<'a, F>) -> FlashUser<'a, F> { + pub fn new(mux: &'a MuxFlash<'a, F>) -> FlashUser<'a, F> { FlashUser { - mux: mux, + mux, buffer: TakeCell::empty(), operation: Cell::new(Op::Idle), next: ListLink::empty(), @@ -161,21 +173,29 @@ impl<'a, F: hil::flash::Flash, C: hil::flash::Client> hil::flash::HasClien } impl<'a, F: hil::flash::Flash> hil::flash::Client for FlashUser<'a, F> { - fn read_complete(&self, pagebuffer: &'static mut F::Page, error: hil::flash::Error) { + fn read_complete( + &self, + pagebuffer: &'static mut F::Page, + result: Result<(), hil::flash::Error>, + ) { self.client.map(move |client| { - client.read_complete(pagebuffer, error); + client.read_complete(pagebuffer, result); }); } - fn write_complete(&self, pagebuffer: &'static mut F::Page, error: hil::flash::Error) { + fn write_complete( + &self, + pagebuffer: &'static mut F::Page, + result: Result<(), hil::flash::Error>, + ) { self.client.map(move |client| { - client.write_complete(pagebuffer, error); + client.write_complete(pagebuffer, result); }); } - fn erase_complete(&self, error: hil::flash::Error) { + fn erase_complete(&self, result: Result<(), hil::flash::Error>) { self.client.map(move |client| { - client.erase_complete(error); + client.erase_complete(result); }); } } diff --git a/capsules/src/virtual_i2c.rs b/capsules/core/src/virtualizers/virtual_i2c.rs similarity index 75% rename from capsules/src/virtual_i2c.rs rename to capsules/core/src/virtualizers/virtual_i2c.rs index 9f09c84970..ed310f7a34 100644 --- a/capsules/src/virtual_i2c.rs +++ b/capsules/core/src/virtualizers/virtual_i2c.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Virtualize an I2C master bus. //! //! `MuxI2C` provides shared access to a single I2C Master Bus for multiple @@ -6,25 +10,22 @@ use core::cell::Cell; use kernel::collections::list::{List, ListLink, ListNode}; -use kernel::dynamic_deferred_call::{ - DeferredCallHandle, DynamicDeferredCall, DynamicDeferredCallClient, -}; -use kernel::hil::i2c::{self, Error, I2CClient, I2CHwMasterClient}; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; +use kernel::hil::i2c::{self, Error, I2CClient, I2CHwMasterClient, NoSMBus}; use kernel::utilities::cells::{OptionalCell, TakeCell}; - -pub struct MuxI2C<'a> { - i2c: &'a dyn i2c::I2CMaster, - smbus: Option<&'a dyn i2c::SMBusMaster>, - i2c_devices: List<'a, I2CDevice<'a>>, - smbus_devices: List<'a, SMBusDevice<'a>>, +// `NoSMBus` provides a placeholder for `SMBusMaster` in case the board doesn't have a SMBus +pub struct MuxI2C<'a, I: i2c::I2CMaster<'a>, S: i2c::SMBusMaster<'a> = NoSMBus> { + i2c: &'a I, + smbus: Option<&'a S>, + i2c_devices: List<'a, I2CDevice<'a, I, S>>, + smbus_devices: List<'a, SMBusDevice<'a, I, S>>, enabled: Cell, - i2c_inflight: OptionalCell<&'a I2CDevice<'a>>, - smbus_inflight: OptionalCell<&'a SMBusDevice<'a>>, - deferred_caller: &'a DynamicDeferredCall, - handle: OptionalCell, + i2c_inflight: OptionalCell<&'a I2CDevice<'a, I, S>>, + smbus_inflight: OptionalCell<&'a SMBusDevice<'a, I, S>>, + deferred_call: DeferredCall, } -impl I2CHwMasterClient for MuxI2C<'_> { +impl<'a, I: i2c::I2CMaster<'a>, S: i2c::SMBusMaster<'a>> I2CHwMasterClient for MuxI2C<'a, I, S> { fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), Error>) { if self.i2c_inflight.is_some() { self.i2c_inflight.take().map(move |device| { @@ -39,29 +40,20 @@ impl I2CHwMasterClient for MuxI2C<'_> { } } -impl<'a> MuxI2C<'a> { - pub const fn new( - i2c: &'a dyn i2c::I2CMaster, - smbus: Option<&'a dyn i2c::SMBusMaster>, - deferred_caller: &'a DynamicDeferredCall, - ) -> MuxI2C<'a> { - MuxI2C { - i2c: i2c, +impl<'a, I: i2c::I2CMaster<'a>, S: i2c::SMBusMaster<'a>> MuxI2C<'a, I, S> { + pub fn new(i2c: &'a I, smbus: Option<&'a S>) -> Self { + Self { + i2c, smbus, i2c_devices: List::new(), smbus_devices: List::new(), enabled: Cell::new(0), i2c_inflight: OptionalCell::empty(), smbus_inflight: OptionalCell::empty(), - deferred_caller: deferred_caller, - handle: OptionalCell::empty(), + deferred_call: DeferredCall::new(), } } - pub fn initialize_callback_handle(&self, handle: DeferredCallHandle) { - self.handle.replace(handle); - } - fn enable(&self) { let enabled = self.enabled.get(); self.enabled.set(enabled + 1); @@ -91,7 +83,7 @@ impl<'a> MuxI2C<'a> { node.buffer.take().map(|buf| { match node.operation.get() { Op::Write(len) => match self.i2c.write(node.addr, buf, len) { - Ok(_) => {} + Ok(()) => {} Err((error, buffer)) => { node.buffer.replace(buffer); node.operation.set(Op::CommandComplete(Err(error))); @@ -99,7 +91,7 @@ impl<'a> MuxI2C<'a> { } }, Op::Read(len) => match self.i2c.read(node.addr, buf, len) { - Ok(_) => {} + Ok(()) => {} Err((error, buffer)) => { node.buffer.replace(buffer); node.operation.set(Op::CommandComplete(Err(error))); @@ -108,7 +100,7 @@ impl<'a> MuxI2C<'a> { }, Op::WriteRead(wlen, rlen) => { match self.i2c.write_read(node.addr, buf, wlen, rlen) { - Ok(_) => {} + Ok(()) => {} Err((error, buffer)) => { node.buffer.replace(buffer); node.operation.set(Op::CommandComplete(Err(error))); @@ -136,7 +128,7 @@ impl<'a> MuxI2C<'a> { node.buffer.take().map(|buf| match node.operation.get() { Op::Write(len) => { match self.smbus.unwrap().smbus_write(node.addr, buf, len) { - Ok(_) => {} + Ok(()) => {} Err(e) => { node.buffer.replace(e.1); node.operation.set(Op::CommandComplete(Err(e.0))); @@ -146,7 +138,7 @@ impl<'a> MuxI2C<'a> { } Op::Read(len) => { match self.smbus.unwrap().smbus_read(node.addr, buf, len) { - Ok(_) => {} + Ok(()) => {} Err(e) => { node.buffer.replace(e.1); node.operation.set(Op::CommandComplete(Err(e.0))); @@ -160,7 +152,7 @@ impl<'a> MuxI2C<'a> { .unwrap() .smbus_write_read(node.addr, buf, wlen, rlen) { - Ok(_) => {} + Ok(()) => {} Err(e) => { node.buffer.replace(e.1); node.operation.set(Op::CommandComplete(Err(e.0))); @@ -186,43 +178,46 @@ impl<'a> MuxI2C<'a> { /// requiring a callback with an error condition; if the operation /// is executed synchronously, the callback may be reentrant (executed /// during the downcall). Please see - /// - /// https://github.com/tock/tock/issues/1496 + /// fn do_next_op_async(&self) { - self.handle.map(|handle| self.deferred_caller.set(*handle)); + self.deferred_call.set(); } } -impl<'a> DynamicDeferredCallClient for MuxI2C<'a> { - fn call(&self, _handle: DeferredCallHandle) { +impl<'a, I: i2c::I2CMaster<'a>, S: i2c::SMBusMaster<'a>> DeferredCallClient for MuxI2C<'a, I, S> { + fn handle_deferred_call(&self) { self.do_next_op(); } + + fn register(&'static self) { + self.deferred_call.register(self); + } } #[derive(Copy, Clone, PartialEq)] enum Op { Idle, - Write(u8), - Read(u8), - WriteRead(u8, u8), + Write(usize), + Read(usize), + WriteRead(usize, usize), CommandComplete(Result<(), Error>), } -pub struct I2CDevice<'a> { - mux: &'a MuxI2C<'a>, +pub struct I2CDevice<'a, I: i2c::I2CMaster<'a>, S: i2c::SMBusMaster<'a> = NoSMBus> { + mux: &'a MuxI2C<'a, I, S>, addr: u8, enabled: Cell, buffer: TakeCell<'static, [u8]>, operation: Cell, - next: ListLink<'a, I2CDevice<'a>>, + next: ListLink<'a, I2CDevice<'a, I, S>>, client: OptionalCell<&'a dyn I2CClient>, } -impl<'a> I2CDevice<'a> { - pub const fn new(mux: &'a MuxI2C<'a>, addr: u8) -> I2CDevice<'a> { +impl<'a, I: i2c::I2CMaster<'a>, S: i2c::SMBusMaster<'a>> I2CDevice<'a, I, S> { + pub fn new(mux: &'a MuxI2C<'a, I, S>, addr: u8) -> I2CDevice<'a, I, S> { I2CDevice { - mux: mux, - addr: addr, + mux, + addr, enabled: Cell::new(false), buffer: TakeCell::empty(), operation: Cell::new(Op::Idle), @@ -237,7 +232,7 @@ impl<'a> I2CDevice<'a> { } } -impl I2CClient for I2CDevice<'_> { +impl<'a, I: i2c::I2CMaster<'a>, S: i2c::SMBusMaster<'a>> I2CClient for I2CDevice<'a, I, S> { fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), Error>) { self.client.map(move |client| { client.command_complete(buffer, status); @@ -245,13 +240,15 @@ impl I2CClient for I2CDevice<'_> { } } -impl<'a> ListNode<'a, I2CDevice<'a>> for I2CDevice<'a> { - fn next(&'a self) -> &'a ListLink<'a, I2CDevice<'a>> { +impl<'a, I: i2c::I2CMaster<'a>, S: i2c::SMBusMaster<'a>> ListNode<'a, I2CDevice<'a, I, S>> + for I2CDevice<'a, I, S> +{ + fn next(&'a self) -> &'a ListLink<'a, I2CDevice<'a, I, S>> { &self.next } } -impl i2c::I2CDevice for I2CDevice<'_> { +impl<'a, I: i2c::I2CMaster<'a>> i2c::I2CDevice for I2CDevice<'a, I> { fn enable(&self) { if !self.enabled.get() { self.enabled.set(true); @@ -269,8 +266,8 @@ impl i2c::I2CDevice for I2CDevice<'_> { fn write_read( &self, data: &'static mut [u8], - write_len: u8, - read_len: u8, + write_len: usize, + read_len: usize, ) -> Result<(), (Error, &'static mut [u8])> { if self.operation.get() == Op::Idle { self.buffer.replace(data); @@ -282,7 +279,7 @@ impl i2c::I2CDevice for I2CDevice<'_> { } } - fn write(&self, data: &'static mut [u8], len: u8) -> Result<(), (Error, &'static mut [u8])> { + fn write(&self, data: &'static mut [u8], len: usize) -> Result<(), (Error, &'static mut [u8])> { if self.operation.get() == Op::Idle { self.buffer.replace(data); self.operation.set(Op::Write(len)); @@ -293,7 +290,11 @@ impl i2c::I2CDevice for I2CDevice<'_> { } } - fn read(&self, buffer: &'static mut [u8], len: u8) -> Result<(), (Error, &'static mut [u8])> { + fn read( + &self, + buffer: &'static mut [u8], + len: usize, + ) -> Result<(), (Error, &'static mut [u8])> { if self.operation.get() == Op::Idle { self.buffer.replace(buffer); self.operation.set(Op::Read(len)); @@ -305,25 +306,25 @@ impl i2c::I2CDevice for I2CDevice<'_> { } } -pub struct SMBusDevice<'a> { - mux: &'a MuxI2C<'a>, +pub struct SMBusDevice<'a, I: i2c::I2CMaster<'a>, S: i2c::SMBusMaster<'a>> { + mux: &'a MuxI2C<'a, I, S>, addr: u8, enabled: Cell, buffer: TakeCell<'static, [u8]>, operation: Cell, - next: ListLink<'a, SMBusDevice<'a>>, + next: ListLink<'a, SMBusDevice<'a, I, S>>, client: OptionalCell<&'a dyn I2CClient>, } -impl<'a> SMBusDevice<'a> { - pub fn new(mux: &'a MuxI2C<'a>, addr: u8) -> SMBusDevice<'a> { +impl<'a, I: i2c::I2CMaster<'a>, S: i2c::SMBusMaster<'a>> SMBusDevice<'a, I, S> { + pub fn new(mux: &'a MuxI2C<'a, I, S>, addr: u8) -> SMBusDevice<'a, I, S> { if mux.smbus.is_none() { panic!("There is no SMBus to attach to"); } SMBusDevice { - mux: mux, - addr: addr, + mux, + addr, enabled: Cell::new(false), buffer: TakeCell::empty(), operation: Cell::new(Op::Idle), @@ -338,7 +339,7 @@ impl<'a> SMBusDevice<'a> { } } -impl<'a> I2CClient for SMBusDevice<'a> { +impl<'a, I: i2c::I2CMaster<'a>, S: i2c::SMBusMaster<'a>> I2CClient for SMBusDevice<'a, I, S> { fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), Error>) { self.client.map(move |client| { client.command_complete(buffer, status); @@ -346,13 +347,15 @@ impl<'a> I2CClient for SMBusDevice<'a> { } } -impl<'a> ListNode<'a, SMBusDevice<'a>> for SMBusDevice<'a> { - fn next(&'a self) -> &'a ListLink<'a, SMBusDevice<'a>> { +impl<'a, I: i2c::I2CMaster<'a>, S: i2c::SMBusMaster<'a>> ListNode<'a, SMBusDevice<'a, I, S>> + for SMBusDevice<'a, I, S> +{ + fn next(&'a self) -> &'a ListLink<'a, SMBusDevice<'a, I, S>> { &self.next } } -impl<'a> i2c::I2CDevice for SMBusDevice<'a> { +impl<'a, I: i2c::I2CMaster<'a>, S: i2c::SMBusMaster<'a>> i2c::I2CDevice for SMBusDevice<'a, I, S> { fn enable(&self) { if !self.enabled.get() { self.enabled.set(true); @@ -370,8 +373,8 @@ impl<'a> i2c::I2CDevice for SMBusDevice<'a> { fn write_read( &self, data: &'static mut [u8], - write_len: u8, - read_len: u8, + write_len: usize, + read_len: usize, ) -> Result<(), (Error, &'static mut [u8])> { if self.operation.get() == Op::Idle { self.buffer.replace(data); @@ -383,7 +386,7 @@ impl<'a> i2c::I2CDevice for SMBusDevice<'a> { } } - fn write(&self, data: &'static mut [u8], len: u8) -> Result<(), (Error, &'static mut [u8])> { + fn write(&self, data: &'static mut [u8], len: usize) -> Result<(), (Error, &'static mut [u8])> { if self.operation.get() == Op::Idle { self.buffer.replace(data); self.operation.set(Op::Write(len)); @@ -394,7 +397,11 @@ impl<'a> i2c::I2CDevice for SMBusDevice<'a> { } } - fn read(&self, buffer: &'static mut [u8], len: u8) -> Result<(), (Error, &'static mut [u8])> { + fn read( + &self, + buffer: &'static mut [u8], + len: usize, + ) -> Result<(), (Error, &'static mut [u8])> { if self.operation.get() == Op::Idle { self.buffer.replace(buffer); self.operation.set(Op::Read(len)); @@ -406,12 +413,14 @@ impl<'a> i2c::I2CDevice for SMBusDevice<'a> { } } -impl<'a> i2c::SMBusDevice for SMBusDevice<'a> { +impl<'a, I: i2c::I2CMaster<'a>, S: i2c::SMBusMaster<'a>> i2c::SMBusDevice + for SMBusDevice<'a, I, S> +{ fn smbus_write_read( &self, data: &'static mut [u8], - write_len: u8, - read_len: u8, + write_len: usize, + read_len: usize, ) -> Result<(), (Error, &'static mut [u8])> { if self.operation.get() == Op::Idle { self.buffer.replace(data); @@ -426,7 +435,7 @@ impl<'a> i2c::SMBusDevice for SMBusDevice<'a> { fn smbus_write( &self, data: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (Error, &'static mut [u8])> { if self.operation.get() == Op::Idle { self.buffer.replace(data); @@ -441,7 +450,7 @@ impl<'a> i2c::SMBusDevice for SMBusDevice<'a> { fn smbus_read( &self, buffer: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (Error, &'static mut [u8])> { if self.operation.get() == Op::Idle { self.buffer.replace(buffer); diff --git a/capsules/src/virtual_pwm.rs b/capsules/core/src/virtualizers/virtual_pwm.rs similarity index 89% rename from capsules/src/virtual_pwm.rs rename to capsules/core/src/virtualizers/virtual_pwm.rs index 28275fc0e2..e5734191f3 100644 --- a/capsules/src/virtual_pwm.rs +++ b/capsules/core/src/virtualizers/virtual_pwm.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Virtualize a PWM interface. //! //! `MuxPwm` provides shared access to a single PWM interface for multiple @@ -6,16 +10,16 @@ //! Usage //! ----- //! -//! ``` +//! ```rust,ignore //! # use kernel::static_init; //! //! let mux_pwm = static_init!( -//! capsules::virtual_pwm::MuxPwm<'static, nrf52::pwm::Pwm>, -//! capsules::virtual_pwm::MuxPwm::new(&base_peripherals.pwm0) +//! capsules_core::virtual_pwm::MuxPwm<'static, nrf52::pwm::Pwm>, +//! capsules_core::virtual_pwm::MuxPwm::new(&base_peripherals.pwm0) //! ); //! let virtual_pwm_buzzer = static_init!( -//! capsules::virtual_pwm::PwmPinUser<'static, nrf52::pwm::Pwm>, -//! capsules::virtual_pwm::PwmPinUser::new(mux_pwm, nrf5x::pinmux::Pinmux::new(31)) +//! capsules_core::virtual_pwm::PwmPinUser<'static, nrf52::pwm::Pwm>, +//! capsules_core::virtual_pwm::PwmPinUser::new(mux_pwm, nrf5x::pinmux::Pinmux::new(31)) //! ); //! virtual_pwm_buzzer.add_to_mux(); //! ``` @@ -34,7 +38,7 @@ pub struct MuxPwm<'a, P: hil::pwm::Pwm> { impl<'a, P: hil::pwm::Pwm> MuxPwm<'a, P> { pub const fn new(pwm: &'a P) -> MuxPwm<'a, P> { MuxPwm { - pwm: pwm, + pwm, devices: List::new(), inflight: OptionalCell::empty(), } @@ -114,8 +118,8 @@ pub struct PwmPinUser<'a, P: hil::pwm::Pwm> { impl<'a, P: hil::pwm::Pwm> PwmPinUser<'a, P> { pub const fn new(mux: &'a MuxPwm<'a, P>, pin: P::Pin) -> PwmPinUser<'a, P> { PwmPinUser { - mux: mux, - pin: pin, + mux, + pin, operation: OptionalCell::empty(), next: ListLink::empty(), } diff --git a/capsules/src/virtual_rng.rs b/capsules/core/src/virtualizers/virtual_rng.rs similarity index 94% rename from capsules/src/virtual_rng.rs rename to capsules/core/src/virtualizers/virtual_rng.rs index f45f0cd0c7..156dc482fe 100644 --- a/capsules/src/virtual_rng.rs +++ b/capsules/core/src/virtualizers/virtual_rng.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + // Virtualizer for the RNG use core::cell::Cell; use kernel::collections::list::{List, ListLink, ListNode}; @@ -21,7 +25,7 @@ pub struct MuxRngMaster<'a> { impl<'a> MuxRngMaster<'a> { pub const fn new(rng: &'a dyn Rng<'a>) -> MuxRngMaster<'a> { MuxRngMaster { - rng: rng, + rng, devices: List::new(), inflight: OptionalCell::empty(), } @@ -106,7 +110,7 @@ impl<'a> ListNode<'a, VirtualRngMasterDevice<'a>> for VirtualRngMasterDevice<'a> impl<'a> VirtualRngMasterDevice<'a> { pub const fn new(mux: &'a MuxRngMaster<'a>) -> VirtualRngMasterDevice<'a> { VirtualRngMasterDevice { - mux: mux, + mux, next: ListLink::empty(), client: OptionalCell::empty(), operation: Cell::new(Op::Idle), @@ -117,7 +121,7 @@ impl<'a> VirtualRngMasterDevice<'a> { impl<'a> PartialEq> for VirtualRngMasterDevice<'a> { fn eq(&self, other: &VirtualRngMasterDevice<'a>) -> bool { // Check whether two rng devices point to the same device - self as *const VirtualRngMasterDevice<'a> == other as *const VirtualRngMasterDevice<'a> + core::ptr::eq(self, other) } } @@ -138,7 +142,7 @@ impl<'a> Rng<'a> for VirtualRngMasterDevice<'a> { }, |current_node| { // Find if current device is the one in flight or not - if *current_node == self { + if current_node == self { self.mux.rng.cancel() } else { Ok(()) @@ -148,7 +152,7 @@ impl<'a> Rng<'a> for VirtualRngMasterDevice<'a> { } fn set_client(&'a self, client: &'a dyn Client) { - self.mux.devices.push_head(&self); + self.mux.devices.push_head(self); // Set client to handle callbacks for current device self.client.set(client); diff --git a/capsules/src/virtual_spi.rs b/capsules/core/src/virtualizers/virtual_spi.rs similarity index 84% rename from capsules/src/virtual_spi.rs rename to capsules/core/src/virtualizers/virtual_spi.rs index 5ed4a2d289..68ca833b9d 100644 --- a/capsules/src/virtual_spi.rs +++ b/capsules/core/src/virtualizers/virtual_spi.rs @@ -1,10 +1,12 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Virtualize a SPI master bus to enable multiple users of the SPI bus. use core::cell::Cell; use kernel::collections::list::{List, ListLink, ListNode}; -use kernel::dynamic_deferred_call::{ - DeferredCallHandle, DynamicDeferredCall, DynamicDeferredCallClient, -}; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; use kernel::hil; use kernel::hil::spi::SpiMasterClient; use kernel::utilities::cells::{OptionalCell, TakeCell}; @@ -12,15 +14,14 @@ use kernel::ErrorCode; /// The Mux struct manages multiple Spi clients. Each client may have /// at most one outstanding Spi request. -pub struct MuxSpiMaster<'a, Spi: hil::spi::SpiMaster> { +pub struct MuxSpiMaster<'a, Spi: hil::spi::SpiMaster<'a>> { spi: &'a Spi, devices: List<'a, VirtualSpiMasterDevice<'a, Spi>>, inflight: OptionalCell<&'a VirtualSpiMasterDevice<'a, Spi>>, - deferred_caller: &'a DynamicDeferredCall, - handle: OptionalCell, + deferred_call: DeferredCall, } -impl hil::spi::SpiMasterClient for MuxSpiMaster<'_, Spi> { +impl<'a, Spi: hil::spi::SpiMaster<'a>> hil::spi::SpiMasterClient for MuxSpiMaster<'a, Spi> { fn read_write_done( &self, write_buffer: &'static mut [u8], @@ -40,17 +41,13 @@ impl hil::spi::SpiMasterClient for MuxSpiMaster<'_, Sp } } -impl<'a, Spi: hil::spi::SpiMaster> MuxSpiMaster<'a, Spi> { - pub const fn new( - spi: &'a Spi, - deferred_caller: &'a DynamicDeferredCall, - ) -> MuxSpiMaster<'a, Spi> { - MuxSpiMaster { - spi: spi, +impl<'a, Spi: hil::spi::SpiMaster<'a>> MuxSpiMaster<'a, Spi> { + pub fn new(spi: &'a Spi) -> Self { + Self { + spi, devices: List::new(), inflight: OptionalCell::empty(), - deferred_caller: deferred_caller, - handle: OptionalCell::empty(), + deferred_call: DeferredCall::new(), } } @@ -122,27 +119,26 @@ impl<'a, Spi: hil::spi::SpiMaster> MuxSpiMaster<'a, Spi> { } } - pub fn initialize_callback_handle(&self, handle: DeferredCallHandle) { - self.handle.replace(handle); - } - /// Asynchronously executes the next operation, if any. Used by calls /// to trigger do_next_op such that it will execute after the call /// returns. This is important in case the operation triggers an error, /// requiring a callback with an error condition; if the operation /// is executed synchronously, the callback may be reentrant (executed /// during the downcall). Please see - /// - /// https://github.com/tock/tock/issues/1496 + /// fn do_next_op_async(&self) { - self.handle.map(|handle| self.deferred_caller.set(*handle)); + self.deferred_call.set(); } } -impl<'a, Spi: hil::spi::SpiMaster> DynamicDeferredCallClient for MuxSpiMaster<'a, Spi> { - fn call(&self, _handle: DeferredCallHandle) { +impl<'a, Spi: hil::spi::SpiMaster<'a>> DeferredCallClient for MuxSpiMaster<'a, Spi> { + fn handle_deferred_call(&self) { self.do_next_op(); } + + fn register(&'static self) { + self.deferred_call.register(self); + } } #[derive(Copy, Clone, PartialEq)] @@ -154,7 +150,7 @@ enum Op { // Structure used to store the SPI configuration of a client/virtual device, // so it can restored on each operation. -struct SpiConfiguration { +struct SpiConfiguration<'a, Spi: hil::spi::SpiMaster<'a>> { chip_select: Spi::ChipSelect, polarity: hil::spi::ClockPolarity, phase: hil::spi::ClockPhase, @@ -164,16 +160,16 @@ struct SpiConfiguration { // Have to do this manually because otherwise the Copy and Clone are parameterized // by Spi::ChipSelect and don't work for Cells. // https://stackoverflow.com/questions/63132174/how-do-i-fix-the-method-clone-exists-but-the-following-trait-bounds-were-not -impl Copy for SpiConfiguration {} -impl Clone for SpiConfiguration { - fn clone(&self) -> SpiConfiguration { +impl<'a, Spi: hil::spi::SpiMaster<'a>> Copy for SpiConfiguration<'a, Spi> {} +impl<'a, Spi: hil::spi::SpiMaster<'a>> Clone for SpiConfiguration<'a, Spi> { + fn clone(&self) -> SpiConfiguration<'a, Spi> { *self } } -pub struct VirtualSpiMasterDevice<'a, Spi: hil::spi::SpiMaster> { +pub struct VirtualSpiMasterDevice<'a, Spi: hil::spi::SpiMaster<'a>> { mux: &'a MuxSpiMaster<'a, Spi>, - configuration: Cell>, + configuration: Cell>, txbuffer: TakeCell<'static, [u8]>, rxbuffer: TakeCell<'static, [u8]>, operation: Cell, @@ -181,15 +177,15 @@ pub struct VirtualSpiMasterDevice<'a, Spi: hil::spi::SpiMaster> { client: OptionalCell<&'a dyn hil::spi::SpiMasterClient>, } -impl<'a, Spi: hil::spi::SpiMaster> VirtualSpiMasterDevice<'a, Spi> { - pub const fn new( +impl<'a, Spi: hil::spi::SpiMaster<'a>> VirtualSpiMasterDevice<'a, Spi> { + pub fn new( mux: &'a MuxSpiMaster<'a, Spi>, chip_select: Spi::ChipSelect, ) -> VirtualSpiMasterDevice<'a, Spi> { VirtualSpiMasterDevice { - mux: mux, + mux, configuration: Cell::new(SpiConfiguration { - chip_select: chip_select, + chip_select, polarity: hil::spi::ClockPolarity::IdleLow, phase: hil::spi::ClockPhase::SampleLeading, rate: 100_000, @@ -208,7 +204,9 @@ impl<'a, Spi: hil::spi::SpiMaster> VirtualSpiMasterDevice<'a, Spi> { } } -impl hil::spi::SpiMasterClient for VirtualSpiMasterDevice<'_, Spi> { +impl<'a, Spi: hil::spi::SpiMaster<'a>> hil::spi::SpiMasterClient + for VirtualSpiMasterDevice<'a, Spi> +{ fn read_write_done( &self, write_buffer: &'static mut [u8], @@ -222,7 +220,7 @@ impl hil::spi::SpiMasterClient for VirtualSpiMasterDev } } -impl<'a, Spi: hil::spi::SpiMaster> ListNode<'a, VirtualSpiMasterDevice<'a, Spi>> +impl<'a, Spi: hil::spi::SpiMaster<'a>> ListNode<'a, VirtualSpiMasterDevice<'a, Spi>> for VirtualSpiMasterDevice<'a, Spi> { fn next(&'a self) -> &'a ListLink<'a, VirtualSpiMasterDevice<'a, Spi>> { @@ -230,7 +228,9 @@ impl<'a, Spi: hil::spi::SpiMaster> ListNode<'a, VirtualSpiMasterDevice<'a, Spi>> } } -impl<'a, Spi: hil::spi::SpiMaster> hil::spi::SpiMasterDevice for VirtualSpiMasterDevice<'a, Spi> { +impl<'a, Spi: hil::spi::SpiMaster<'a>> hil::spi::SpiMasterDevice<'a> + for VirtualSpiMasterDevice<'a, Spi> +{ fn set_client(&self, client: &'a dyn SpiMasterClient) { self.client.set(client); } @@ -316,21 +316,21 @@ impl<'a, Spi: hil::spi::SpiMaster> hil::spi::SpiMasterDevice for VirtualSpiMaste } } -pub struct SpiSlaveDevice<'a, Spi: hil::spi::SpiSlave> { +pub struct SpiSlaveDevice<'a, Spi: hil::spi::SpiSlave<'a>> { spi: &'a Spi, client: OptionalCell<&'a dyn hil::spi::SpiSlaveClient>, } -impl<'a, Spi: hil::spi::SpiSlave> SpiSlaveDevice<'a, Spi> { +impl<'a, Spi: hil::spi::SpiSlave<'a>> SpiSlaveDevice<'a, Spi> { pub const fn new(spi: &'a Spi) -> SpiSlaveDevice<'a, Spi> { SpiSlaveDevice { - spi: spi, + spi, client: OptionalCell::empty(), } } } -impl hil::spi::SpiSlaveClient for SpiSlaveDevice<'_, Spi> { +impl<'a, Spi: hil::spi::SpiSlave<'a>> hil::spi::SpiSlaveClient for SpiSlaveDevice<'a, Spi> { fn read_write_done( &self, write_buffer: Option<&'static mut [u8]>, @@ -350,7 +350,7 @@ impl hil::spi::SpiSlaveClient for SpiSlaveDevice<'_, Sp } } -impl<'a, Spi: hil::spi::SpiSlave> hil::spi::SpiSlaveDevice for SpiSlaveDevice<'a, Spi> { +impl<'a, Spi: hil::spi::SpiSlave<'a>> hil::spi::SpiSlaveDevice<'a> for SpiSlaveDevice<'a, Spi> { fn set_client(&self, client: &'a dyn hil::spi::SpiSlaveClient) { self.client.set(client); } diff --git a/capsules/src/virtual_timer.rs b/capsules/core/src/virtualizers/virtual_timer.rs similarity index 96% rename from capsules/src/virtual_timer.rs rename to capsules/core/src/virtualizers/virtual_timer.rs index 4ab4997133..bfe12680be 100644 --- a/capsules/src/virtual_timer.rs +++ b/capsules/core/src/virtualizers/virtual_timer.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provide multiple Timers on top of a single underlying Alarm. use core::cell::Cell; @@ -8,7 +12,7 @@ use kernel::hil::time::{self, Alarm, Ticks, Time, Timer}; use kernel::utilities::cells::{NumericCellExt, OptionalCell}; use kernel::ErrorCode; -use crate::virtual_alarm::VirtualMuxAlarm; +use crate::virtualizers::virtual_alarm::VirtualMuxAlarm; #[derive(Copy, Clone, Debug, PartialEq)] enum Mode { @@ -58,7 +62,7 @@ impl<'a, A: Alarm<'a>> VirtualTimer<'a, A> { /// Call this method immediately after new() to link this to the mux, otherwise timers won't /// fire pub fn setup(&'a self) { - self.mux.timers.push_head(&self); + self.mux.timers.push_head(self); } // Start a new timer, configuring its mode and adjusting the underlying alarm if needed. @@ -193,7 +197,7 @@ impl<'a, A: Alarm<'a>> MuxTimer<'a, A> { MuxTimer { timers: List::new(), enabled: Cell::new(0), - alarm: alarm, + alarm, } } diff --git a/capsules/src/virtual_uart.rs b/capsules/core/src/virtualizers/virtual_uart.rs similarity index 81% rename from capsules/src/virtual_uart.rs rename to capsules/core/src/virtualizers/virtual_uart.rs index b8b96f9cc5..46b84413b5 100644 --- a/capsules/src/virtual_uart.rs +++ b/capsules/core/src/virtualizers/virtual_uart.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Virtualize a UART bus. //! //! This allows multiple Tock capsules to use the same UART bus. This is likely @@ -13,14 +17,14 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::{hil, static_init}; -//! # use capsules::virtual_uart::{MuxUart, UartDevice}; +//! # use capsules_core::virtual_uart::{MuxUart, UartDevice}; //! //! // Create a shared UART channel for the console and for kernel debug. //! let uart_mux = static_init!( //! MuxUart<'static>, -//! MuxUart::new(&sam4l::usart::USART0, &mut capsules::virtual_uart::RX_BUF) +//! MuxUart::new(&sam4l::usart::USART0, &mut capsules_core::virtual_uart::RX_BUF) //! ); //! hil::uart::UART::set_receive_client(&sam4l::usart::USART0, uart_mux); //! hil::uart::UART::set_transmit_client(&sam4l::usart::USART0, uart_mux); @@ -29,11 +33,11 @@ //! let console_uart = static_init!(UartDevice, UartDevice::new(uart_mux, true)); //! console_uart.setup(); // This is important! //! let console = static_init!( -//! capsules::console::Console<'static>, -//! capsules::console::Console::new( +//! capsules_core::console::Console<'static>, +//! capsules_core::console::Console::new( //! console_uart, -//! &mut capsules::console::WRITE_BUF, -//! &mut capsules::console::READ_BUF, +//! &mut capsules_core::console::WRITE_BUF, +//! &mut capsules_core::console::READ_BUF, //! board_kernel.create_grant(&grant_cap) //! ) //! ); @@ -45,15 +49,12 @@ use core::cell::Cell; use core::cmp; use kernel::collections::list::{List, ListLink, ListNode}; -use kernel::dynamic_deferred_call::{ - DeferredCallHandle, DynamicDeferredCall, DynamicDeferredCallClient, -}; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; use kernel::hil::uart; use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::ErrorCode; -const RX_BUF_LEN: usize = 64; -pub static mut RX_BUF: [u8; RX_BUF_LEN] = [0; RX_BUF_LEN]; +pub const RX_BUF_LEN: usize = 64; pub struct MuxUart<'a> { uart: &'a dyn uart::Uart<'a>, @@ -62,8 +63,7 @@ pub struct MuxUart<'a> { inflight: OptionalCell<&'a UartDevice<'a>>, buffer: TakeCell<'static, [u8]>, completing_read: Cell, - deferred_caller: &'a DynamicDeferredCall, - handle: OptionalCell, + deferred_call: DeferredCall, } impl<'a> uart::TransmitClient for MuxUart<'a> { @@ -118,9 +118,7 @@ impl<'a> uart::ReceiveClient for MuxUart<'a> { || state == UartDeviceReceiveState::Aborting { // debug!("Have {} bytes, copying in bytes {}-{}, {} remain", rx_len, position, position + len, remaining); - for i in 0..len { - rxbuf[position + i] = buffer[i]; - } + rxbuf[position..(len + position)].copy_from_slice(&buffer[..len]); } device.rx_position.set(position + len); device.rx_buffer.replace(rxbuf); @@ -184,27 +182,44 @@ impl<'a> uart::ReceiveClient for MuxUart<'a> { // we just received, or if a new receive has been started, we start the // underlying UART receive again. if read_pending { - self.start_receive(next_read_len); + if let Err((e, buf)) = self.start_receive(next_read_len) { + self.buffer.replace(buf); + + // Report the error to all devices + self.devices.iter().for_each(|device| { + if device.receiver { + device.rx_buffer.take().map(|rxbuf| { + let state = device.state.get(); + let position = device.rx_position.get(); + + if state == UartDeviceReceiveState::Receiving { + device.state.set(UartDeviceReceiveState::Idle); + + device.received_buffer( + rxbuf, + position, + Err(e), + uart::Error::Aborted, + ); + } + }); + } + }); + } } } } impl<'a> MuxUart<'a> { - pub fn new( - uart: &'a dyn uart::Uart<'a>, - buffer: &'static mut [u8], - speed: u32, - deferred_caller: &'a DynamicDeferredCall, - ) -> MuxUart<'a> { + pub fn new(uart: &'a dyn uart::Uart<'a>, buffer: &'static mut [u8], speed: u32) -> MuxUart<'a> { MuxUart { - uart: uart, - speed: speed, + uart, + speed, devices: List::new(), inflight: OptionalCell::empty(), buffer: TakeCell::new(buffer), completing_read: Cell::new(false), - deferred_caller: deferred_caller, - handle: OptionalCell::empty(), + deferred_call: DeferredCall::new(), } } @@ -218,28 +233,25 @@ impl<'a> MuxUart<'a> { }); } - pub fn initialize_callback_handle(&self, handle: DeferredCallHandle) { - self.handle.replace(handle); - } - fn do_next_op(&self) { if self.inflight.is_none() { let mnode = self.devices.iter().find(|node| node.operation.is_some()); mnode.map(|node| { node.tx_buffer.take().map(|buf| { - node.operation.map(move |op| match op { - Operation::Transmit { len } => { - let _ = self.uart.transmit_buffer(buf, *len).map_err( - move |(ecode, buf)| { - node.tx_client.map(move |client| { - node.transmitting.set(false); - client.transmitted_buffer(buf, 0, Err(ecode)); - }); - }, - ); - } + node.operation.take().map(move |op| match op { + Operation::Transmit { len } => match self.uart.transmit_buffer(buf, len) { + Ok(()) => { + self.inflight.set(node); + } + Err((ecode, buf)) => { + node.tx_client.map(move |client| { + node.transmitting.set(false); + client.transmitted_buffer(buf, 0, Err(ecode)); + }); + } + }, Operation::TransmitWord { word } => { - let rcode = self.uart.transmit_word(*word); + let rcode = self.uart.transmit_word(word); if rcode != Ok(()) { node.tx_client.map(|client| { node.transmitting.set(false); @@ -249,8 +261,6 @@ impl<'a> MuxUart<'a> { } }); }); - node.operation.clear(); - self.inflight.set(node); }); } } @@ -266,27 +276,27 @@ impl<'a> MuxUart<'a> { /// 2. We are in the midst of a read: abort so we can start a new read now /// (return true) /// 3. We are idle: start reading (return false) - fn start_receive(&self, rx_len: usize) -> bool { + fn start_receive(&self, rx_len: usize) -> Result { self.buffer.take().map_or_else( || { // No rxbuf which means a read is ongoing if self.completing_read.get() { // Case (1). Do nothing here, `received_buffer()` handler // will call start_receive when ready. - false + Ok(false) } else { // Case (2). Stop the previous read so we can use the // `received_buffer()` handler to recalculate the minimum // length for a read. let _ = self.uart.receive_abort(); - true + Ok(true) } }, |rxbuf| { // Case (3). No ongoing receive calls, we can start one now. let len = cmp::min(rx_len, rxbuf.len()); - let _ = self.uart.receive_buffer(rxbuf, len); - false + self.uart.receive_buffer(rxbuf, len)?; + Ok(false) }, ) } @@ -297,17 +307,20 @@ impl<'a> MuxUart<'a> { /// requiring a callback with an error condition; if the operation /// is executed synchronously, the callback may be reentrant (executed /// during the downcall). Please see - /// - /// https://github.com/tock/tock/issues/1496 + /// fn do_next_op_async(&self) { - self.handle.map(|handle| self.deferred_caller.set(*handle)); + self.deferred_call.set(); } } -impl<'a> DynamicDeferredCallClient for MuxUart<'a> { - fn call(&self, _handle: DeferredCallHandle) { +impl DeferredCallClient for MuxUart<'_> { + fn handle_deferred_call(&self) { self.do_next_op(); } + + fn register(&'static self) { + self.deferred_call.register(self); + } } #[derive(Copy, Clone, PartialEq)] @@ -339,11 +352,11 @@ pub struct UartDevice<'a> { } impl<'a> UartDevice<'a> { - pub const fn new(mux: &'a MuxUart<'a>, receiver: bool) -> UartDevice<'a> { + pub fn new(mux: &'a MuxUart<'a>, receiver: bool) -> UartDevice<'a> { UartDevice { state: Cell::new(UartDeviceReceiveState::Idle), - mux: mux, - receiver: receiver, + mux, + receiver, tx_buffer: TakeCell::empty(), transmitting: Cell::new(false), rx_buffer: TakeCell::empty(), @@ -418,7 +431,9 @@ impl<'a> uart::Transmit<'a> for UartDevice<'a> { tx_data: &'static mut [u8], tx_len: usize, ) -> Result<(), (ErrorCode, &'static mut [u8])> { - if self.transmitting.get() { + if tx_len == 0 { + Err((ErrorCode::SIZE, tx_data)) + } else if self.transmitting.get() { Err((ErrorCode::BUSY, tx_data)) } else { self.tx_buffer.replace(tx_data); @@ -434,7 +449,7 @@ impl<'a> uart::Transmit<'a> for UartDevice<'a> { Err(ErrorCode::BUSY) } else { self.transmitting.set(true); - self.operation.set(Operation::TransmitWord { word: word }); + self.operation.set(Operation::TransmitWord { word }); self.mux.do_next_op_async(); Ok(()) } @@ -461,7 +476,7 @@ impl<'a> uart::Receive<'a> for UartDevice<'a> { self.rx_len.set(rx_len); self.rx_position.set(0); self.state.set(UartDeviceReceiveState::Idle); - self.mux.start_receive(rx_len); + self.mux.start_receive(rx_len)?; self.state.set(UartDeviceReceiveState::Receiving); Ok(()) } diff --git a/capsules/extra/Cargo.toml b/capsules/extra/Cargo.toml new file mode 100644 index 0000000000..42eeb39e3d --- /dev/null +++ b/capsules/extra/Cargo.toml @@ -0,0 +1,18 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +[package] +name = "capsules-extra" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[dependencies] +kernel = { path = "../../kernel" } +enum_primitive = { path = "../../libraries/enum_primitive" } +tickv = { path = "../../libraries/tickv" } +capsules-core = { path = "../core" } + +[lints] +workspace = true diff --git a/capsules/extra/README.md b/capsules/extra/README.md new file mode 100644 index 0000000000..c30dd764f1 --- /dev/null +++ b/capsules/extra/README.md @@ -0,0 +1,183 @@ +"Extra" Tock Capsules +===================== + +This crate contains miscellaneous capsules which do not fit into any other, more +specific category, and which do not require any external (non-vendored and +unvetted) dependencies. + +For more information on capsules, see [the top-level README](../README.md). + +The remainder of this document contains a list of capsules in this crate, along +with a short description. + +Sensor and other IC Drivers +--------------------------- + +These implement a driver to setup and read various physical sensors. + +- **[ADC Microphone](src/adc_microphone.rs)**: Single ADC pin microphone. +- **[Analog Sensors](src/analog_sensor.rs)**: Single ADC pin sensors. +- **[APDS9960](src/apds9960.rs)**: Proximity sensor. +- **[BME280](src/bme280.rs)**: Humidity and air pressure sensor. +- **[BMM150](src/bmm150.rs)**: Geomagnetic sensor. +- **[BMP280](src/bmp280.rs)**: Temperature (and air pressure) sensor. +- **[CCS811](src/ccs811.rs)**: VOC gas sensor. +- **[FXOS8700CQ](src/fxos8700cq.rs)**: Accelerometer and magnetometer. +- **[HS3003](src/hs3003.rs)**: Temperature and humidity sensor. +- **[HTS221](src/hts221.rs)**: Temperature and humidity sensor. +- **[ISL29035](src/isl29035.rs)**: Light sensor. +- **[L3GD20](src/l3gd20.rs)**: MEMS 3 axys digital gyroscope and temperature + sensor. +- **[LSM303xx Support](src/lsm303xx.rs)**: Shared files. + - **[LSM303AGR](src/lsm303agr.rs)**: 3D accelerometer and 3D magnetometer + sensor. + - **[LSM303DLHC](src/lsm303dlhc.rs)**: 3D accelerometer and 3D magnetometer + sensor. +- **[LSM6DSOXTR](src/lsm6dsoxtr.rs)**: 3D accelerometer and 3D magnetometer + sensor. +- **[LPS22HB](src/lps22hb.rs)**: Pressure sensor. +- **[LPS25HB](src/lps25hb.rs)**: Pressure sensor. +- **[MLX90614](src/mlx90614.rs)**: Infrared temperature sensor. +- **[RP2040 Temperature](src/temperature_rp2040.rs)**: Analog RP2040 temperature + sensor. +- **[SHT3x](src/sht3x.rs)**: Temperature and humidity sensor. +- **[SHT4x](src/sht4x.rs)**: Temperature and humidity sensor. +- **[SI7021](src/si7021.rs)**: Temperature and humidity sensor. +- **[STM32 Temperature](src/temperature_stm.rs)**: Analog STM32 temperature + sensor. +- **[TSL2561](src/tsl2561.rs)**: Light sensor. + +These drivers provide support for various ICs. + +- **[AT24C32/64](src/at24c_eeprom.rs)**: EEPROM chip. +- **[FM25CL](src/fm25cl.rs)**: FRAM chip. +- **[FT6x06](src/ft6x06.rs)**: FT6x06 touch panel. +- **[HD44780 LCD](src/hd44780.rs)**: HD44780 LCD screen. +- **[LPM013M126](src/lpm013m126.rs)**: LPM013M126 LCD screen. +- **[LTC294X](src/ltc294x.rs)**: LTC294X series of coulomb counters. +- **[MAX17205](src/max17205.rs)**: Battery fuel gauge. +- **[MCP230xx](src/mcp230xx.rs)**: I2C GPIO extender. +- **[MX25r6435F](src/mx25r6435f.rs)**: SPI flash chip. +- **[PCA9544A](src/pca9544a.rs)**: Multiple port I2C selector. +- **[SD Card](src/sdcard.rs)**: Support for SD cards. +- **[Seven Segment Display](src/seven_segment.rs)**: Seven segment displays. +- **[SH1106](src/sh1106.rs)**: SH1106 OLED screen driver. +- **[SSD1306](src/ssd1306.rs)**: SSD1306 OLED screen driver. +- **[ST77xx](src/st77xx.rs)**: ST77xx IPS screen. + + +Wireless +-------- + +Support for wireless radios. + +- **[nRF51822 Serialization](src/nrf51822_serialization.rs)**: Kernel support + for using the nRF51 serialization library. +- **[RF233](src/rf233.rs)**: Driver for RF233 radio. +- **[BLE Advertising](src/ble_advertising_driver.rs)**: Driver for sending BLE + advertisements. +- **[LoRa Phy]**: Support for exposing Semtech devices to userspace + See the lora_things_plus board for an example + + +Libraries +--------- + +Protocol stacks and other libraries. + +- **[IEEE 802.15.4](src/ieee802154)**: 802.15.4 networking. +- **[Networking](src/net)**: Networking stack. +- **[USB](src/usb)**: USB 2.0. +- **[Segger RTT](src/segger_rtt.rs)**: Segger RTT support. Provides `hil::uart` + interface. +- **[Symmetric Cryptography](src/symmetric_encryption)**: Symmetric + encryption. +- **[Public Key Cryptography](src/public_key_crypto)**: Asymmetric + encryption. + + +MCU Peripherals for Userspace +----------------------------- + +These capsules provide a `Driver` interface for common MCU peripherals. + +- **[Analog Comparator](src/analog_comparator.rs)**: Voltage comparison. +- **[CRC](src/crc.rs)**: CRC calculation. +- **[DAC](src/dac.rs)**: Digital to analog conversion. +- **[CAN](src/can.rs)**: CAN communication. + + +Helpful Userspace Capsules +-------------------------- + +These provide common and better abstractions for userspace. + +- **[Air Quality](src/air_quality.rs)**: Query air quality sensors. +- **[Ambient Light](src/ambient_light.rs)**: Query light sensors. +- **[App Flash](src/app_flash_driver.rs)**: Allow applications to write their + own flash. +- **[Buzzer](src/buzzer_driver.rs)**: Simple buzzer. +- **[Date-Time](src/date_time.rs)**: Real time clock date/time support. +- **[EUI64](src/eui64.rs)**: Query device's extended unique ID. +- **[HMAC](src/hmac.rs)**: Hash-based Message Authentication Code support. +- **[Humidity](src/humidity.rs)**: Query humidity sensors. +- **[Key-Value Store](src/kv_driver.rs)**: Store key-value data. +- **[LED Matrix](src/led_matrix.rs)**: Control a 2D array of LEDs. +- **[Pressure](src/pressure.rs)**: Pressure sensors. +- **[Proximity](src/proximity.rs)**: Proximity sensors. +- **[PWM](src/pwm.rs)**: Pulse-width modulation support. +- **[Read Only State](src/read_only_state.rs)**: Read-only state sharing. +- **[Screen](src/screen.rs)**: Displays and screens. +- **[Screen Shared](src/screen_shared.rs)**: App-specific screen windows. +- **[SHA](src/sha.rs)**: SHA hashes. +- **[Sound Pressure](src/sound_pressure.rs)**: Query sound pressure levels. +- **[Temperature](src/temperature.rs)**: Query temperature sensors. +- **[Text Screen](src/text_screen.rs)**: Text-based displays. +- **[Touch](src/touch.rs)**: User touch panels. + + +Virtualized Sensor Capsules for Userspace +----------------------------------------- + +These provide virtualized (i.e. multiple applications can use them +simultaneously) support for generic sensor interfaces. + +- **[Asynchronous GPIO](src/gpio_async.rs)**: GPIO pins accessed by split-phase + calls. +- **[9DOF](src/ninedof.rs)**: 9DOF sensors (acceleration, magnetometer, + gyroscope). +- **[Nonvolatile Storage](src/nonvolatile_storage_driver.rs)**: Persistent + storage for userspace. + + +Utility Capsules +---------------- + +Other capsules that implement reusable logic. + +- **[Bus Adapters](src/bus.rs)**: Generic abstraction for SPI/I2C/8080. +- **[Buzzer PWM](src/buzzer_pwm.rs)**: Buzzer with a PWM pin. +- **[HMAC-SHA256](src/hmac_sha256.rs)**: HMAC using SHA-256. +- **[Key-Value Store with Permissions](src/kv_store_permissions.rs)**: Key-value + interface that requires read/write permissions. +- **[Log Storage](src/log.rs)**: Log storage abstraction on flash devices. +- **[Nonvolatile to Pages](src/nonvolatile_to_pages.rs)**: Map arbitrary reads + and writes to flash pages. +- **[SHA256](src/sha256.rs)**: SHA256 software hash. +- **[SipHash](src/sip_hash.rs)**: SipHash software hash. +- **[TicKV](src/tickv.rs)**: Key-value storage. +- **[TicKV KV Store](src/tickv_kv_store.rs)**: Provide `hil::kv::KV` with TickV. +- **[Virtual KV](src/virtual_kv.rs)**: Virtualize access to KV with permissions. + + +Debugging Capsules +------------------ + +These are selectively included on a board to help with testing and debugging +various elements of Tock. + +- **[Cycle Counter](src/cycle_count.rs)**: Start, stop, reset, and read a hardware cycle + counter from userspace. +- **[Debug Process Restart](src/debug_process_restart.rs)**: Force all processes + to enter a fault state when a button is pressed. +- **[Panic Button](src/panic_button.rs)**: Use a button to force a `panic!()`. diff --git a/capsules/src/adc_microphone.rs b/capsules/extra/src/adc_microphone.rs similarity index 92% rename from capsules/src/adc_microphone.rs rename to capsules/extra/src/adc_microphone.rs index d9149cd421..82a8a29042 100644 --- a/capsules/src/adc_microphone.rs +++ b/capsules/extra/src/adc_microphone.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::cell::Cell; use kernel::hil::adc; @@ -14,7 +18,7 @@ enum State { } pub struct AdcMicrophone<'a, P: gpio::Pin> { - adc: &'a dyn adc::AdcChannel, + adc: &'a dyn adc::AdcChannel<'a>, enable_pin: Option<&'a P>, spl_client: OptionalCell<&'a dyn SoundPressureClient>, spl_buffer: TakeCell<'a, [u16]>, @@ -24,7 +28,7 @@ pub struct AdcMicrophone<'a, P: gpio::Pin> { impl<'a, P: gpio::Pin> AdcMicrophone<'a, P> { pub fn new( - adc: &'a dyn adc::AdcChannel, + adc: &'a dyn adc::AdcChannel<'a>, enable_pin: Option<&'a P>, spl_buffer: &'a mut [u16], ) -> AdcMicrophone<'a, P> { @@ -46,7 +50,7 @@ impl<'a, P: gpio::Pin> AdcMicrophone<'a, P> { .iter() .map(|v| if *v > avg { v - avg } else { 0 }) .fold(0, |a, v| if a > v { a } else { v }); - let mut conv = (max as f32) / (((1 << 15) - 1) as f32) * 9 as f32; + let mut conv = (max as f32) / (((1 << 15) - 1) as f32) * 9_f32; conv = 20f32 * math::log10(conv / 0.00002f32); conv as u8 }); diff --git a/capsules/extra/src/air_quality.rs b/capsules/extra/src/air_quality.rs new file mode 100644 index 0000000000..974a0c0a78 --- /dev/null +++ b/capsules/extra/src/air_quality.rs @@ -0,0 +1,159 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Provides userspace with access to air quality sensors. +//! +//! Usage +//! ----- +//! +//! You need a device that provides the `hil::sensors::AirQualityDriver` trait. +//! +//! ```rust,ignore +//! # use kernel::static_init; +//! +//! let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); +//! let grant_temperature = board_kernel.create_grant(&grant_cap); +//! +//! let temp = static_init!( +//! capsules::temperature::AirQualitySensor<'static>, +//! capsules::temperature::AirQualitySensor::new(si7021, +//! board_kernel.create_grant(&grant_cap))); +//! +//! kernel::hil::sensors::AirQualityDriver::set_client(si7021, temp); +//! ``` + +use core::cell::Cell; +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::hil; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::{ErrorCode, ProcessId}; + +/// Syscall driver number. +use capsules_core::driver; +pub const DRIVER_NUM: usize = driver::NUM::AirQuality as usize; + +#[derive(Clone, Copy, PartialEq, Default)] +enum Operation { + #[default] + None, + CO2, + TVOC, +} + +#[derive(Default)] +pub struct App { + operation: Operation, +} + +pub struct AirQualitySensor<'a> { + driver: &'a dyn hil::sensors::AirQualityDriver<'a>, + apps: Grant, AllowRoCount<0>, AllowRwCount<0>>, + busy: Cell, +} + +impl<'a> AirQualitySensor<'a> { + pub fn new( + driver: &'a dyn hil::sensors::AirQualityDriver<'a>, + grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, + ) -> AirQualitySensor<'a> { + AirQualitySensor { + driver, + apps: grant, + busy: Cell::new(false), + } + } + + fn enqueue_command(&self, processid: ProcessId, op: Operation) -> CommandReturn { + self.apps + .enter(processid, |app, _| { + if !self.busy.get() { + self.busy.set(true); + app.operation = op; + + let rcode = match op { + Operation::None => Err(ErrorCode::FAIL), + Operation::CO2 => self.driver.read_co2(), + Operation::TVOC => self.driver.read_tvoc(), + }; + let eres = ErrorCode::try_from(rcode); + + match eres { + Ok(ecode) => CommandReturn::failure(ecode), + _ => CommandReturn::success(), + } + } else { + CommandReturn::failure(ErrorCode::BUSY) + } + }) + .unwrap_or_else(|err| CommandReturn::failure(err.into())) + } +} + +impl hil::sensors::AirQualityClient for AirQualitySensor<'_> { + fn environment_specified(&self, _result: Result<(), ErrorCode>) { + unimplemented!(); + } + + fn co2_data_available(&self, value: Result) { + for cntr in self.apps.iter() { + cntr.enter(|app, upcalls| { + if app.operation == Operation::CO2 { + value + .map(|co2| { + self.busy.set(false); + app.operation = Operation::None; + upcalls.schedule_upcall(0, (co2 as usize, 0, 0)).ok(); + }) + .ok(); + } + }); + } + } + + fn tvoc_data_available(&self, value: Result) { + for cntr in self.apps.iter() { + cntr.enter(|app, upcalls| { + if app.operation == Operation::TVOC { + value + .map(|tvoc| { + self.busy.set(false); + app.operation = Operation::None; + upcalls.schedule_upcall(0, (tvoc as usize, 0, 0)).ok(); + }) + .ok(); + } + }); + } + } +} + +impl SyscallDriver for AirQualitySensor<'_> { + fn command( + &self, + command_num: usize, + _: usize, + _: usize, + processid: ProcessId, + ) -> CommandReturn { + match command_num { + // check whether the driver exists!! + 0 => CommandReturn::success(), + + // specify temperature and humidity (TODO) + 1 => CommandReturn::failure(ErrorCode::NOSUPPORT), + + // read CO2 + 2 => self.enqueue_command(processid, Operation::CO2), + + // read TVOC + 3 => self.enqueue_command(processid, Operation::TVOC), + + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { + self.apps.enter(processid, |_, _| {}) + } +} diff --git a/capsules/src/ambient_light.rs b/capsules/extra/src/ambient_light.rs similarity index 67% rename from capsules/src/ambient_light.rs rename to capsules/extra/src/ambient_light.rs index 782d2cf133..e6961ec3b1 100644 --- a/capsules/src/ambient_light.rs +++ b/capsules/extra/src/ambient_light.rs @@ -1,8 +1,12 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Shared userland driver for light sensors. //! //! You need a device that provides the `hil::sensors::AmbientLight` trait. //! -//! ```rust +//! ```rust,ignore //! # use kernel::{hil, static_init}; //! //! let light = static_init!( @@ -20,9 +24,20 @@ use kernel::syscall::{CommandReturn, SyscallDriver}; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::AmbientLight as usize; +/// IDs for subscribed upcalls. +mod upcall { + /// Subscribe to light intensity readings. + /// + /// The callback signature is `fn(lux: usize)`, where `lux` is the light + /// intensity in lux (lx). + pub const LIGHT_INTENSITY: usize = 0; + /// Number of upcalls. + pub const COUNT: u8 = 1; +} + /// Per-process metadata #[derive(Default)] pub struct App { @@ -32,24 +47,24 @@ pub struct App { pub struct AmbientLight<'a> { sensor: &'a dyn hil::sensors::AmbientLight<'a>, command_pending: Cell, - apps: Grant, AllowRoCount<0>, AllowRwCount<0>>, + apps: Grant, AllowRoCount<0>, AllowRwCount<0>>, } impl<'a> AmbientLight<'a> { pub fn new( sensor: &'a dyn hil::sensors::AmbientLight<'a>, - grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, + grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, ) -> AmbientLight { AmbientLight { - sensor: sensor, + sensor, command_pending: Cell::new(false), apps: grant, } } - fn enqueue_sensor_reading(&self, appid: ProcessId) -> Result<(), ErrorCode> { + fn enqueue_sensor_reading(&self, processid: ProcessId) -> Result<(), ErrorCode> { self.apps - .enter(appid, |app, _| { + .enter(processid, |app, _| { if app.pending { Err(ErrorCode::NOMEM) } else { @@ -66,13 +81,6 @@ impl<'a> AmbientLight<'a> { } impl SyscallDriver for AmbientLight<'_> { - // Subscribe to light intensity readings - // - // ### `subscribe` - // - // - `0`: Subscribe to light intensity readings. The callback signature is - // `fn(lux: usize)`, where `lux` is the light intensity in lux (lx). - /// Initiate light intensity readings /// /// Sensor readings are coalesced if processes request them concurrently. If @@ -84,14 +92,20 @@ impl SyscallDriver for AmbientLight<'_> { /// /// - `0`: Check driver presence /// - `1`: Start a light sensor reading - fn command(&self, command_num: usize, _: usize, _: usize, appid: ProcessId) -> CommandReturn { + fn command( + &self, + command_num: usize, + _: usize, + _: usize, + processid: ProcessId, + ) -> CommandReturn { match command_num { - 0 /* check if present */ => CommandReturn::success(), + 0 => CommandReturn::success(), 1 => { - let _ = self.enqueue_sensor_reading(appid); + let _ = self.enqueue_sensor_reading(processid); CommandReturn::success() } - _ => CommandReturn::failure(ErrorCode::NOSUPPORT) + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), } } @@ -106,7 +120,9 @@ impl hil::sensors::AmbientLightClient for AmbientLight<'_> { self.apps.each(|_, app, upcalls| { if app.pending { app.pending = false; - upcalls.schedule_upcall(0, (lux, 0, 0)).ok(); + upcalls + .schedule_upcall(upcall::LIGHT_INTENSITY, (lux, 0, 0)) + .ok(); } }); } diff --git a/capsules/src/analog_comparator.rs b/capsules/extra/src/analog_comparator.rs similarity index 88% rename from capsules/src/analog_comparator.rs rename to capsules/extra/src/analog_comparator.rs index d775b4b192..f39c5900c7 100644 --- a/capsules/src/analog_comparator.rs +++ b/capsules/extra/src/analog_comparator.rs @@ -1,9 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides userspace access to the analog comparators on a board. //! //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! let ac_channels = static_init!( @@ -34,7 +38,7 @@ // Last modified August 9th, 2018 /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::AnalogComparator as usize; use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; @@ -114,7 +118,7 @@ impl<'a, A: hil::analog_comparator::AnalogComparator<'a>> SyscallDriver /// /// ### `command_num` /// - /// - `0`: Driver check. + /// - `0`: Driver existence check. /// - `1`: Perform a simple comparison. /// Input x chooses the desired comparator ACx (e.g. 0 or 1 for /// hail, 0-3 for imix) @@ -124,26 +128,27 @@ impl<'a, A: hil::analog_comparator::AnalogComparator<'a>> SyscallDriver /// - `3`: Stop interrupt-based comparisons. /// Input x chooses the desired comparator ACx (e.g. 0 or 1 for /// hail, 0-3 for imix) + /// - `4`: Get number of channels. fn command( &self, command_num: usize, channel: usize, _: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { if command_num == 0 { - // Handle this first as it should be returned unconditionally. - return CommandReturn::success_u32(self.channels.len() as u32); + // Handle unconditional driver existence check. + return CommandReturn::success(); } // Check if this driver is free, or already dedicated to this process. let match_or_empty_or_nonexistant = self.current_process.map_or(true, |current_process| { self.grants - .enter(*current_process, |_, _| current_process == &appid) + .enter(current_process, |_, _| current_process == processid) .unwrap_or(true) }); if match_or_empty_or_nonexistant { - self.current_process.set(appid); + self.current_process.set(processid); } else { return CommandReturn::failure(ErrorCode::NOMEM); } @@ -160,6 +165,8 @@ impl<'a, A: hil::analog_comparator::AnalogComparator<'a>> SyscallDriver 3 => self.stop_comparing(channel).into(), + 4 => CommandReturn::success_u32(self.channels.len() as u32), + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), } } @@ -174,8 +181,8 @@ impl<'a, A: hil::analog_comparator::AnalogComparator<'a>> hil::analog_comparator { /// Upcall to userland, signaling the application fn fired(&self, channel: usize) { - self.current_process.map(|appid| { - let _ = self.grants.enter(*appid, |_app, upcalls| { + self.current_process.map(|processid| { + let _ = self.grants.enter(processid, |_app, upcalls| { upcalls.schedule_upcall(0, (channel, 0, 0)).ok(); }); }); diff --git a/capsules/src/analog_sensor.rs b/capsules/extra/src/analog_sensor.rs similarity index 62% rename from capsules/src/analog_sensor.rs rename to capsules/extra/src/analog_sensor.rs index f57a018a20..1c07586fc0 100644 --- a/capsules/src/analog_sensor.rs +++ b/capsules/extra/src/analog_sensor.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Capsule for analog sensors. //! //! This capsule provides the sensor HIL interfaces for sensors which only need @@ -15,30 +19,30 @@ pub enum AnalogLightSensorType { LightDependentResistor, } -pub struct AnalogLightSensor<'a, A: hil::adc::Adc> { +pub struct AnalogLightSensor<'a, A: hil::adc::Adc<'a>> { adc: &'a A, - channel: &'a ::Channel, + channel: &'a >::Channel, sensor_type: AnalogLightSensorType, client: OptionalCell<&'a dyn hil::sensors::AmbientLightClient>, } -impl<'a, A: hil::adc::Adc> AnalogLightSensor<'a, A> { +impl<'a, A: hil::adc::Adc<'a>> AnalogLightSensor<'a, A> { pub fn new( adc: &'a A, - channel: &'a ::Channel, + channel: &'a >::Channel, sensor_type: AnalogLightSensorType, ) -> AnalogLightSensor<'a, A> { AnalogLightSensor { - adc: adc, - channel: channel, - sensor_type: sensor_type, + adc, + channel, + sensor_type, client: OptionalCell::empty(), } } } /// Callbacks from the ADC driver -impl hil::adc::Client for AnalogLightSensor<'_, A> { +impl<'a, A: hil::adc::Adc<'a>> hil::adc::Client for AnalogLightSensor<'a, A> { fn sample_ready(&self, sample: u16) { // TODO: calculate the actual light reading. let measurement: usize = match self.sensor_type { @@ -51,7 +55,7 @@ impl hil::adc::Client for AnalogLightSensor<'_, A> { } } -impl<'a, A: hil::adc::Adc> hil::sensors::AmbientLight<'a> for AnalogLightSensor<'a, A> { +impl<'a, A: hil::adc::Adc<'a>> hil::sensors::AmbientLight<'a> for AnalogLightSensor<'a, A> { fn set_client(&self, client: &'a dyn hil::sensors::AmbientLightClient) { self.client.set(client); } @@ -67,47 +71,52 @@ pub enum AnalogTemperatureSensorType { MicrochipMcp9700, } -pub struct AnalogTemperatureSensor<'a, A: hil::adc::Adc> { +pub struct AnalogTemperatureSensor<'a, A: hil::adc::Adc<'a>> { adc: &'a A, - channel: &'a ::Channel, + channel: &'a >::Channel, sensor_type: AnalogTemperatureSensorType, client: OptionalCell<&'a dyn hil::sensors::TemperatureClient>, } -impl<'a, A: hil::adc::Adc> AnalogTemperatureSensor<'a, A> { +impl<'a, A: hil::adc::Adc<'a>> AnalogTemperatureSensor<'a, A> { pub fn new( adc: &'a A, - channel: &'a ::Channel, + channel: &'a >::Channel, sensor_type: AnalogLightSensorType, ) -> AnalogLightSensor<'a, A> { AnalogLightSensor { - adc: adc, - channel: channel, - sensor_type: sensor_type, + adc, + channel, + sensor_type, client: OptionalCell::empty(), } } } /// Callbacks from the ADC driver -impl hil::adc::Client for AnalogTemperatureSensor<'_, A> { +impl<'a, A: hil::adc::Adc<'a>> hil::adc::Client for AnalogTemperatureSensor<'a, A> { fn sample_ready(&self, sample: u16) { // TODO: calculate the actual temperature reading. - let measurement: usize = match self.sensor_type { + let measurement = match self.sensor_type { // 𝑉out = 500𝑚𝑉 + 10𝑚𝑉/C ∗ 𝑇A AnalogTemperatureSensorType::MicrochipMcp9700 => { - let ref_mv = self.adc.get_voltage_reference_mv().unwrap_or(3300); - // reading_mv = (ADC / (2^16-1)) * ref_voltage - let reading_mv = (sample as usize * ref_mv) / 65535; - // need 0.01°C - (reading_mv - 500) * 10 + self.adc + .get_voltage_reference_mv() + .map_or(Err(ErrorCode::FAIL), |ref_mv| { + // reading_mv = (ADC / (2^16-1)) * ref_voltage + let reading_mv = (sample as usize * ref_mv) / 65535; + // need 0.01°C + Ok((reading_mv as i32 - 500) * 10) + }) } }; self.client.map(|client| client.callback(measurement)); } } -impl<'a, A: hil::adc::Adc> hil::sensors::TemperatureDriver<'a> for AnalogTemperatureSensor<'a, A> { +impl<'a, A: hil::adc::Adc<'a>> hil::sensors::TemperatureDriver<'a> + for AnalogTemperatureSensor<'a, A> +{ fn set_client(&self, client: &'a dyn hil::sensors::TemperatureClient) { self.client.set(client); } diff --git a/capsules/src/apds9960.rs b/capsules/extra/src/apds9960.rs similarity index 92% rename from capsules/src/apds9960.rs rename to capsules/extra/src/apds9960.rs index ac8083e479..191be9c3b0 100644 --- a/capsules/src/apds9960.rs +++ b/capsules/extra/src/apds9960.rs @@ -1,43 +1,46 @@ -//! Proximity SyscallDriver for the Adafruit APDS9960 gesture/ambient light/proximity sensor. +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Proximity SyscallDriver for the Adafruit APDS9960 gesture/ambient +//! light/proximity sensor. //! -//! <-- Datasheet +//! Datasheet: +//! //! -//! > The APDS-9960 device features advanced Gesture detection, Proximity detection, Digital Ambient Light Sense -//! > (ALS) and Color Sense (RGBC). The slim modular package, -//! > L 3.94 x W 2.36 x H 1.35 mm, incorporates an IR LED and -//! > factory calibrated LED driver for drop-in compatibility -//! > with existing footprints +//! > The APDS-9960 device features advanced Gesture detection, Proximity +//! > detection, Digital Ambient Light Sense (ALS) and Color Sense (RGBC). The +//! > slim modular package, L 3.94 x W 2.36 x H 1.35 mm, incorporates an IR LED +//! > and factory calibrated LED driver for drop-in compatibility with existing +//! > footprints //! //! Usage //! ----- //! -//! ```rust -//! # use kernel::static_init; -//! +//! ```rust,ignore //! let apds9960_i2c = static_init!( //! capsules::virtual_i2c::I2CDevice, //! capsules::virtual_i2c::I2CDevice::new(sensors_i2c_bus, 0x39) -//!); +//! ); //! -//!let apds9960 = static_init!( +//! let apds9960 = static_init!( //! capsules::apds9960::APDS9960<'static>, //! capsules::apds9960::APDS9960::new( //! apds9960_i2c, //! &nrf52840::gpio::PORT[APDS9960_PIN], //! &mut capsules::apds9960::BUFFER //! ) -//!); -//!apds9960_i2c.set_client(apds9960); -//!nrf52840::gpio::PORT[APDS9960_PIN].set_client(apds9960); - -//!let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); +//! ); +//! apds9960_i2c.set_client(apds9960); +//! nrf52840::gpio::PORT[APDS9960_PIN].set_client(apds9960); //! -//!let proximity = static_init!( +//! let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); +//! +//! let proximity = static_init!( //! capsules::proximity::ProximitySensor<'static>, //! capsules::proximity::ProximitySensor::new(apds9960 , board_kernel.create_grant(&grant_cap))); - -//!kernel::hil::sensors::ProximityDriver::set_client(apds9960, proximity); //! +//! kernel::hil::sensors::ProximityDriver::set_client(apds9960, proximity); //! ``` use core::cell::Cell; @@ -47,7 +50,7 @@ use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::ErrorCode; // I2C Buffer of 16 bytes -pub static mut BUFFER: [u8; 16] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 175]; +pub const BUF_LEN: usize = 16; // BUFFER Layout: [0,... , 12 , 13 , 14 , 15] // ^take_meas() callback stored ^take_meas_int callback stored ^low thresh ^high thresh @@ -107,24 +110,24 @@ enum State { Done, // Final state for take_measurement() state sequence } -pub struct APDS9960<'a> { - i2c: &'a dyn i2c::I2CDevice, +pub struct APDS9960<'a, I: i2c::I2CDevice> { + i2c: &'a I, interrupt_pin: &'a dyn gpio::InterruptPin<'a>, prox_callback: OptionalCell<&'a dyn kernel::hil::sensors::ProximityClient>, state: Cell, buffer: TakeCell<'static, [u8]>, } -impl<'a> APDS9960<'a> { +impl<'a, I: i2c::I2CDevice> APDS9960<'a, I> { pub fn new( - i2c: &'a dyn i2c::I2CDevice, + i2c: &'a I, interrupt_pin: &'a dyn gpio::InterruptPin<'a>, buffer: &'static mut [u8], - ) -> APDS9960<'a> { + ) -> APDS9960<'a, I> { // setup and return struct APDS9960 { - i2c: i2c, - interrupt_pin: interrupt_pin, + i2c, + interrupt_pin, prox_callback: OptionalCell::empty(), state: Cell::new(State::Idle), buffer: TakeCell::new(buffer), @@ -170,7 +173,7 @@ impl<'a> APDS9960<'a> { } buffer[0] = Registers::PROXPULSEREG as u8; - buffer[1] = (length << 6 | count) as u8; + buffer[1] = length << 6 | count; match self.i2c.write(buffer, 2) { Ok(()) => { @@ -200,7 +203,7 @@ impl<'a> APDS9960<'a> { } buffer[0] = Registers::CONTROLREG1 as u8; - buffer[1] = (ldrive << 6) as u8; + buffer[1] = ldrive << 6; match self.i2c.write(buffer, 2) { Ok(()) => { @@ -292,7 +295,7 @@ impl<'a> APDS9960<'a> { } } -impl i2c::I2CClient for APDS9960<'_> { +impl i2c::I2CClient for APDS9960<'_, I> { fn command_complete(&self, buffer: &'static mut [u8], _status: Result<(), i2c::Error>) { match self.state.get() { State::ReadId => { @@ -409,7 +412,7 @@ impl i2c::I2CClient for APDS9960<'_> { // Deactivate the device buffer[0] = Registers::ENABLE as u8; - buffer[1] = 0 as u8; + buffer[1] = 0_u8; match self.i2c.write(buffer, 2) { Ok(()) => { @@ -430,7 +433,7 @@ impl i2c::I2CClient for APDS9960<'_> { self.i2c.disable(); self.state.set(State::Idle); - self.prox_callback.map(|cb| cb.callback(prox_data as u8)); + self.prox_callback.map(|cb| cb.callback(prox_data)); } State::TakeMeasurement1 => { // Read status reg @@ -507,7 +510,7 @@ impl i2c::I2CClient for APDS9960<'_> { self.i2c.disable(); self.state.set(State::Idle); - self.prox_callback.map(|cb| cb.callback(prox_data as u8)); + self.prox_callback.map(|cb| cb.callback(prox_data)); } State::SetPulse => { @@ -529,7 +532,7 @@ impl i2c::I2CClient for APDS9960<'_> { } /// Interrupt Service Routine -impl gpio::Client for APDS9960<'_> { +impl gpio::Client for APDS9960<'_, I> { fn fired(&self) { self.buffer.take().map(|buffer| { // Read value in PDATA reg @@ -551,7 +554,7 @@ impl gpio::Client for APDS9960<'_> { } /// Proximity Driver Trait Implementation -impl<'a> kernel::hil::sensors::ProximityDriver<'a> for APDS9960<'a> { +impl<'a, I: i2c::I2CDevice> kernel::hil::sensors::ProximityDriver<'a> for APDS9960<'a, I> { fn read_proximity(&self) -> Result<(), ErrorCode> { self.take_measurement() } diff --git a/capsules/src/app_flash_driver.rs b/capsules/extra/src/app_flash_driver.rs similarity index 75% rename from capsules/src/app_flash_driver.rs rename to capsules/extra/src/app_flash_driver.rs index bc6115e08f..0ddeae7800 100644 --- a/capsules/src/app_flash_driver.rs +++ b/capsules/extra/src/app_flash_driver.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! This allows multiple apps to write their own flash region. //! //! All write requests from userland are checked to ensure that they are only @@ -12,14 +16,14 @@ //! Usage //! ----- //! -//! ``` +//! ```rust,ignore //! # use kernel::static_init; //! -//! pub static mut APP_FLASH_BUFFER: [u8; 512] = [0; 512]; +//! let app_flash_buffer = static_init!([u8; 512], [0; 512]); //! let app_flash = static_init!( //! capsules::app_flash_driver::AppFlash<'static>, //! capsules::app_flash_driver::AppFlash::new(nv_to_page, -//! board_kernel.create_grant(&grant_cap), &mut APP_FLASH_BUFFER)); +//! board_kernel.create_grant(&grant_cap), app_flash_buffer)); //! ``` use core::cmp; @@ -32,14 +36,23 @@ use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::AppFlash as usize; +/// IDs for subscribed upcalls. +mod upcall { + /// `write_done` callback. + pub const WRITE_DONE: usize = 0; + /// Number of upcalls. + pub const COUNT: u8 = 1; +} + /// Ids for read-only allow buffers mod ro_allow { + /// Set write buffer. This entire buffer will be written to flash. pub const BUFFER: usize = 0; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; + pub const COUNT: u8 = 1; } #[derive(Default)] @@ -49,20 +62,30 @@ pub struct App { } pub struct AppFlash<'a> { - driver: &'a dyn hil::nonvolatile_storage::NonvolatileStorage<'static>, - apps: Grant, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>, + driver: &'a dyn hil::nonvolatile_storage::NonvolatileStorage<'a>, + apps: Grant< + App, + UpcallCount<{ upcall::COUNT }>, + AllowRoCount<{ ro_allow::COUNT }>, + AllowRwCount<0>, + >, current_app: OptionalCell, buffer: TakeCell<'static, [u8]>, } impl<'a> AppFlash<'a> { pub fn new( - driver: &'a dyn hil::nonvolatile_storage::NonvolatileStorage<'static>, - grant: Grant, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>, + driver: &'a dyn hil::nonvolatile_storage::NonvolatileStorage<'a>, + grant: Grant< + App, + UpcallCount<{ upcall::COUNT }>, + AllowRoCount<{ ro_allow::COUNT }>, + AllowRwCount<0>, + >, buffer: &'static mut [u8], ) -> AppFlash<'a> { AppFlash { - driver: driver, + driver, apps: grant, current_app: OptionalCell::empty(), buffer: TakeCell::new(buffer), @@ -72,14 +95,14 @@ impl<'a> AppFlash<'a> { // Check to see if we are doing something. If not, go ahead and do this // command. If so, this is queued and will be run when the pending command // completes. - fn enqueue_write(&self, flash_address: usize, appid: ProcessId) -> Result<(), ErrorCode> { + fn enqueue_write(&self, flash_address: usize, processid: ProcessId) -> Result<(), ErrorCode> { self.apps - .enter(appid, |app, kernel_data| { + .enter(processid, |app, kernel_data| { // Check that this is a valid range in the app's flash. let flash_length = kernel_data .get_readonly_processbuffer(ro_allow::BUFFER) .map_or(0, |buffer| buffer.len()); - let (app_flash_start, app_flash_end) = appid.get_editable_flash_range(); + let (app_flash_start, app_flash_end) = processid.get_editable_flash_range(); if flash_address < app_flash_start || flash_address >= app_flash_end || flash_address + flash_length >= app_flash_end @@ -88,7 +111,7 @@ impl<'a> AppFlash<'a> { } if self.current_app.is_none() { - self.current_app.set(appid); + self.current_app.set(processid); kernel_data .get_readonly_processbuffer(ro_allow::BUFFER) @@ -100,9 +123,7 @@ impl<'a> AppFlash<'a> { .map_or(Err(ErrorCode::RESERVE), |buffer| { let length = cmp::min(buffer.len(), app_buffer.len()); let d = &app_buffer[0..length]; - for (i, c) in - buffer.as_mut()[0..length].iter_mut().enumerate() - { + for (i, c) in buffer[0..length].iter_mut().enumerate() { *c = d[i].get(); } @@ -113,7 +134,7 @@ impl<'a> AppFlash<'a> { .unwrap_or(Err(ErrorCode::RESERVE)) } else { // Queue this request for later. - if app.pending_command == true { + if app.pending_command { Err(ErrorCode::NOMEM) } else { app.pending_command = true; @@ -126,7 +147,7 @@ impl<'a> AppFlash<'a> { } } -impl hil::nonvolatile_storage::NonvolatileStorageClient<'static> for AppFlash<'_> { +impl hil::nonvolatile_storage::NonvolatileStorageClient for AppFlash<'_> { fn read_done(&self, _buffer: &'static mut [u8], _length: usize) {} fn write_done(&self, buffer: &'static mut [u8], _length: usize) { @@ -134,19 +155,19 @@ impl hil::nonvolatile_storage::NonvolatileStorageClient<'static> for AppFlash<'_ self.buffer.replace(buffer); // Notify the current application that the command finished. - self.current_app.take().map(|appid| { - let _ = self.apps.enter(appid, |_app, upcalls| { - upcalls.schedule_upcall(0, (0, 0, 0)).ok(); + self.current_app.take().map(|processid| { + let _ = self.apps.enter(processid, |_app, upcalls| { + upcalls.schedule_upcall(upcall::WRITE_DONE, (0, 0, 0)).ok(); }); }); // Check if there are any pending events. for cntr in self.apps.iter() { - let appid = cntr.processid(); + let processid = cntr.processid(); let started_command = cntr.enter(|app, kernel_data| { if app.pending_command { app.pending_command = false; - self.current_app.set(appid); + self.current_app.set(processid); let flash_address = app.flash_address; kernel_data @@ -160,9 +181,7 @@ impl hil::nonvolatile_storage::NonvolatileStorageClient<'static> for AppFlash<'_ // Copy contents to internal buffer and write it. let length = cmp::min(buffer.len(), app_buffer.len()); let d = &app_buffer[0..length]; - for (i, c) in - buffer.as_mut()[0..length].iter_mut().enumerate() - { + for (i, c) in buffer[0..length].iter_mut().enumerate() { *c = d[i].get(); } @@ -190,40 +209,27 @@ impl hil::nonvolatile_storage::NonvolatileStorageClient<'static> for AppFlash<'_ } impl SyscallDriver for AppFlash<'_> { - /// Setup buffer to write from. - /// - /// ### `allow_num` - /// - /// - `0`: Set write buffer. This entire buffer will be written to flash. - - // Setup callbacks. - // - // ### `subscribe_num` - // - // - `0`: Set a write_done callback. - /// App flash control. /// /// ### `command_num` /// - /// - `0`: Driver check. + /// - `0`: Driver existence check. /// - `1`: Write the memory from the `allow` buffer to the address in flash. fn command( &self, command_num: usize, arg1: usize, _: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { match command_num { - 0 /* This driver exists. */ => { - CommandReturn::success() - } + 0 => CommandReturn::success(), - 1 /* Write to flash from the allowed buffer */ => { + 1 => { + // Write to flash from the allowed buffer let flash_address = arg1; - let res = self.enqueue_write(flash_address, appid); + let res = self.enqueue_write(flash_address, processid); match res { Ok(()) => CommandReturn::success(), @@ -231,7 +237,7 @@ impl SyscallDriver for AppFlash<'_> { } } - _ /* Unknown command num */ => CommandReturn::failure(ErrorCode::NOSUPPORT), + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), } } diff --git a/capsules/extra/src/at24c_eeprom.rs b/capsules/extra/src/at24c_eeprom.rs new file mode 100644 index 0000000000..002fee5946 --- /dev/null +++ b/capsules/extra/src/at24c_eeprom.rs @@ -0,0 +1,255 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Driver for the AT24C32/64 EEPROM memory. Built on top of the I2C interface. +//! Provides interface for the NonvolatileToPages driver. +//! +//! Datasheet: +//! +//! +//! > The AT24C32/64 provides 32,768/65,536 bits of serial electrically erasable and programmable +//! > read only memory (EEPROM) organized as 4096/8192 words of 8 bits each. The device’s cascadable +//! > feature allows up to 8 devices to share a common 2- wire bus. The device is optimized for use +//! > in many industrial and commercial applications where low power and low voltage operation are +//! > essential. The AT24C32/64 is available in space saving 8-pin JEDEC PDIP, 8-pin JEDEC SOIC, +//! > 8-pin EIAJ SOIC, and 8-pin TSSOP (AT24C64) packages and is accessed via a 2-wire serial +//! > interface. +//! +//! Usage +//! ----- +//! +//! ```rust,ignore +//! let i2cmux = I2CMuxComponent::new(i2c0, None).finalize(components::i2c_mux_component_static!()); +//! +//! let at24c_buffer = static_init!([u8; 34], [0; 34]); +//! +//! let at24c_i2c_device = static_init!(I2CDevice, I2CDevice::new(i2cmux, 0x50)); +//! let at24c_capsule = static_init!(capsules_extra::at24c_eeprom::AT24C,capsules_extra::at24c_eeprom::AT24C::new( +//! at24c_i2c_device, +//! at24c_buffer, +//! ) ); +//! at24c_i2c_device.set_client(at24c_capsule); +//! +//! let nonvolatile_storage = components::nonvolatile_storage::NonvolatileStorageComponent::new( +//! board_kernel, +//! capsules_extra::nonvolatile_storage_driver::DRIVER_NUM, +//! at24c_capsule, +//! 0x0, +//! 0x10000, +//! 0x0, +//! 0x0, +//! ).finalize(components::nonvolatile_storage_component_static!(capsules_extra::at24c_eeprom::AT24C)); +//! ``` + +use core::cell::Cell; +use core::cmp; + +use kernel::hil::i2c::{Error, I2CClient, I2CDevice}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::{hil, ErrorCode}; + +const PAGE_SIZE: usize = 32; + +pub struct EEPROMPage(pub [u8; PAGE_SIZE]); + +impl Default for EEPROMPage { + fn default() -> Self { + Self([0; PAGE_SIZE]) + } +} + +impl AsMut<[u8]> for EEPROMPage { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } +} + +#[derive(Copy, Clone, Debug)] +enum State { + Idle, + Reading, + Writing, + Erasing, +} + +pub struct AT24C<'a> { + i2c: &'a dyn I2CDevice, + buffer: TakeCell<'static, [u8]>, + client_page: TakeCell<'a, EEPROMPage>, + flash_client: OptionalCell<&'a dyn hil::flash::Client>>, + state: Cell, +} + +impl<'a> AT24C<'a> { + pub fn new(i2c: &'a dyn I2CDevice, buffer: &'static mut [u8]) -> Self { + Self { + i2c, + buffer: TakeCell::new(buffer), + client_page: TakeCell::empty(), + flash_client: OptionalCell::empty(), + state: Cell::new(State::Idle), + } + } + + fn read_sector( + &self, + page_number: usize, + buf: &'static mut EEPROMPage, + ) -> Result<(), (ErrorCode, &'static mut EEPROMPage)> { + let address = page_number * PAGE_SIZE; + if let Some(rxbuffer) = self.buffer.take() { + rxbuffer[0] = ((address >> 8) & 0x00ff) as u8; + rxbuffer[1] = (address & 0x00ff) as u8; + + self.i2c.enable(); + self.state.set(State::Reading); + if let Err((error, local_buffer)) = self.i2c.write_read(rxbuffer, 2, PAGE_SIZE) { + self.buffer.replace(local_buffer); + self.i2c.disable(); + Err((error.into(), buf)) + } else { + self.client_page.replace(buf); + Ok(()) + } + } else { + Err((ErrorCode::RESERVE, buf)) + } + } + + fn write_sector( + &self, + page_number: usize, + buf: &'static mut EEPROMPage, + ) -> Result<(), (ErrorCode, &'static mut EEPROMPage)> { + let address = page_number * PAGE_SIZE; + // Schedule page write and do first + if let Some(txbuffer) = self.buffer.take() { + txbuffer[0] = ((address >> 8) & 0x00ff) as u8; + txbuffer[1] = (address & 0x00ff) as u8; + + let write_len = cmp::min(txbuffer.len() - 2, buf.0.len()); + + txbuffer[2..(write_len + 2)].copy_from_slice(&buf.0[..write_len]); + + self.i2c.enable(); + self.state.set(State::Writing); + if let Err((error, txbuffer)) = self.i2c.write(txbuffer, write_len + 2) { + self.buffer.replace(txbuffer); + self.i2c.disable(); + Err((error.into(), buf)) + } else { + self.client_page.replace(buf); + Ok(()) + } + } else { + Err((ErrorCode::RESERVE, buf)) + } + } + + fn erase_sector(&self, page_number: usize) -> Result<(), ErrorCode> { + let address = page_number * PAGE_SIZE; + // Schedule page write and do first + if let Some(txbuffer) = self.buffer.take() { + txbuffer[0] = ((address >> 8) & 0x00ff) as u8; + txbuffer[1] = (address & 0x00ff) as u8; + + let write_len = cmp::min(txbuffer.len() - 2, PAGE_SIZE); + + for i in 0..write_len { + txbuffer[i + 2] = 0; + } + + self.i2c.enable(); + self.state.set(State::Erasing); + if let Err((error, txbuffer)) = self.i2c.write(txbuffer, write_len + 2) { + self.buffer.replace(txbuffer); + self.i2c.disable(); + Err(error.into()) + } else { + Ok(()) + } + } else { + Err(ErrorCode::RESERVE) + } + } +} + +impl I2CClient for AT24C<'static> { + fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), Error>) { + match self.state.get() { + State::Reading => { + self.state.set(State::Idle); + self.i2c.disable(); + if let Some(client_page) = self.client_page.take() { + client_page.0[..PAGE_SIZE].copy_from_slice(&buffer[..PAGE_SIZE]); + self.buffer.replace(buffer); + self.flash_client.map(|client| { + if status.is_err() { + client.read_complete(client_page, Err(hil::flash::Error::FlashError)); + } else { + client.read_complete(client_page, Ok(())); + } + }); + } + } + State::Writing => { + self.state.set(State::Idle); + self.buffer.replace(buffer); + self.i2c.disable(); + self.flash_client.map(|client| { + if let Some(client_page) = self.client_page.take() { + if status.is_err() { + client.write_complete(client_page, Err(hil::flash::Error::FlashError)); + } else { + client.write_complete(client_page, Ok(())); + } + } + }); + } + State::Erasing => { + self.state.set(State::Idle); + self.buffer.replace(buffer); + self.i2c.disable(); + self.flash_client.map(move |client| { + if status.is_err() { + client.erase_complete(Err(hil::flash::Error::FlashError)); + } else { + client.erase_complete(Ok(())); + } + }); + } + State::Idle => {} + } + } +} + +impl<'a> hil::flash::Flash for AT24C<'a> { + type Page = EEPROMPage; + + fn read_page( + &self, + page_number: usize, + buf: &'static mut Self::Page, + ) -> Result<(), (ErrorCode, &'static mut Self::Page)> { + self.read_sector(page_number, buf) + } + + fn write_page( + &self, + page_number: usize, + buf: &'static mut Self::Page, + ) -> Result<(), (ErrorCode, &'static mut Self::Page)> { + self.write_sector(page_number, buf) + } + + fn erase_page(&self, page_number: usize) -> Result<(), ErrorCode> { + self.erase_sector(page_number) + } +} + +impl<'a, C: hil::flash::Client> hil::flash::HasClient<'a, C> for AT24C<'a> { + fn set_client(&'a self, client: &'a C) { + self.flash_client.set(client); + } +} diff --git a/capsules/src/ble_advertising_driver.rs b/capsules/extra/src/ble_advertising_driver.rs similarity index 91% rename from capsules/src/ble_advertising_driver.rs rename to capsules/extra/src/ble_advertising_driver.rs index 313474a166..106a7811bd 100644 --- a/capsules/src/ble_advertising_driver.rs +++ b/capsules/extra/src/ble_advertising_driver.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Bluetooth Low Energy Advertising Driver //! //! A system call driver that exposes the Bluetooth Low Energy advertising @@ -63,7 +67,7 @@ //! You need a device that provides the `kernel::BleAdvertisementDriver` trait along with a virtual //! timer to perform events and not block the entire kernel //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! # use capsules::virtual_alarm::VirtualMuxAlarm; //! @@ -116,28 +120,25 @@ use kernel::utilities::copy_slice::CopyOrErr; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::BleAdvertising as usize; /// Ids for read-only allow buffers mod ro_allow { pub const ADV_DATA: usize = 0; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; + pub const COUNT: u8 = 1; } /// Ids for read-write allow buffers mod rw_allow { pub const SCAN_BUFFER: usize = 0; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; + pub const COUNT: u8 = 1; } -/// Advertisement Buffer -pub static mut BUF: [u8; PACKET_LENGTH] = [0; PACKET_LENGTH]; - const PACKET_ADDR_LEN: usize = 6; -const PACKET_LENGTH: usize = 39; +pub const PACKET_LENGTH: usize = 39; const ADV_HEADER_TXADD_OFFSET: usize = 6; #[derive(PartialEq, Debug)] @@ -233,13 +234,13 @@ impl App { // Byte 2-5 random // Byte 6 0xf0 // FIXME: For now use ProcessId as "randomness" - fn generate_random_address(&mut self, appid: kernel::ProcessId) -> Result<(), ErrorCode> { + fn generate_random_address(&mut self, processid: kernel::ProcessId) -> Result<(), ErrorCode> { self.address = [ 0xf0, - (appid.id() & 0xff) as u8, - ((appid.id() << 8) & 0xff) as u8, - ((appid.id() << 16) & 0xff) as u8, - ((appid.id() << 24) & 0xff) as u8, + (processid.id() & 0xff) as u8, + ((processid.id() << 8) & 0xff) as u8, + ((processid.id() << 16) & 0xff) as u8, + ((processid.id() << 24) & 0xff) as u8, 0xf0, ]; Ok(()) @@ -247,7 +248,7 @@ impl App { fn send_advertisement<'a, B, A>( &mut self, - appid: kernel::ProcessId, + processid: kernel::ProcessId, kernel_data: &GrantKernelData, ble: &BLE<'a, B, A>, channel: RadioChannel, @@ -257,7 +258,7 @@ impl App { A: kernel::hil::time::Alarm<'a>, { // Ensure we have an address set before advertisement - self.generate_random_address(appid)?; + self.generate_random_address(processid)?; kernel_data .get_readonly_processbuffer(ro_allow::ADV_DATA) .and_then(|adv_data| { @@ -357,11 +358,11 @@ where alarm: &'a A, ) -> BLE<'a, B, A> { BLE { - radio: radio, + radio, busy: Cell::new(false), app: container, kernel_tx: kernel::utilities::cells::TakeCell::new(tx_buf), - alarm: alarm, + alarm, sending_app: OptionalCell::empty(), receiving_app: OptionalCell::empty(), } @@ -376,9 +377,9 @@ where // likely be chosen. fn reset_active_alarm(&self) { let now = self.alarm.now(); - let mut next_ref = u32::max_value(); - let mut next_dt = u32::max_value(); - let mut next_dist = u32::max_value(); + let mut next_ref = u32::MAX; + let mut next_dt = u32::MAX; + let mut next_dist = u32::MAX; for app in self.app.iter() { app.enter(|app, _| match app.alarm_data.expiration { Expiration::Enabled(reference, dt) => { @@ -393,7 +394,7 @@ where Expiration::Disabled => {} }); } - if next_ref != u32::max_value() { + if next_ref != u32::MAX { self.alarm .set_alarm(A::Ticks::from(next_ref), A::Ticks::from(next_dt)); } @@ -421,7 +422,7 @@ where fn alarm(&self) { let now = self.alarm.now(); - self.app.each(|appid, app, kernel_data| { + self.app.each(|processid, app, kernel_data| { if let Expiration::Enabled(reference, dt) = app.alarm_data.expiration { let exp = A::Ticks::from(reference.wrapping_add(dt)); let t0 = A::Ticks::from(reference); @@ -432,7 +433,7 @@ where // operation at the appropriate time. Instead, reschedule the // operation for later. This is _kind_ of simulating actual // on-air interference. 3 seems like a small number of ticks. - debug!("BLE: operation delayed for app {:?}", appid); + debug!("BLE: operation delayed for app {:?}", processid); app.set_next_alarm::(self.alarm.now().into_u32()); return; } @@ -444,12 +445,12 @@ where self.busy.set(true); app.process_status = Some(BLEState::Advertising(RadioChannel::AdvertisingChannel37)); - self.sending_app.set(appid); + self.sending_app.set(processid); let _ = self.radio.set_tx_power(app.tx_power); let _ = app.send_advertisement( - appid, + processid, kernel_data, - &self, + self, RadioChannel::AdvertisingChannel37, ); } @@ -457,12 +458,15 @@ where self.busy.set(true); app.process_status = Some(BLEState::Scanning(RadioChannel::AdvertisingChannel37)); - self.receiving_app.set(appid); + self.receiving_app.set(processid); let _ = self.radio.set_tx_power(app.tx_power); self.radio .receive_advertisement(RadioChannel::AdvertisingChannel37); } - _ => debug!("app: {:?} \t invalid state {:?}", appid, app.process_status), + _ => debug!( + "app: {:?} \t invalid state {:?}", + processid, app.process_status + ), } } } @@ -478,8 +482,8 @@ where A: kernel::hil::time::Alarm<'a>, { fn receive_event(&self, buf: &'static mut [u8], len: u8, result: Result<(), ErrorCode>) { - self.receiving_app.map(|appid| { - let _ = self.app.enter(*appid, |app, kernel_data| { + self.receiving_app.map(|processid| { + let _ = self.app.enter(processid, |app, kernel_data| { // Validate the received data, because ordinary BLE packets can be bigger than 39 // bytes. Thus, we need to check for that! // Moreover, we use the packet header to find size but the radio reads maximum @@ -517,7 +521,7 @@ where Some(BLEState::Scanning(RadioChannel::AdvertisingChannel37)) => { app.process_status = Some(BLEState::Scanning(RadioChannel::AdvertisingChannel38)); - self.receiving_app.set(*appid); + self.receiving_app.set(processid); let _ = self.radio.set_tx_power(app.tx_power); self.radio .receive_advertisement(RadioChannel::AdvertisingChannel38); @@ -525,7 +529,7 @@ where Some(BLEState::Scanning(RadioChannel::AdvertisingChannel38)) => { app.process_status = Some(BLEState::Scanning(RadioChannel::AdvertisingChannel39)); - self.receiving_app.set(*appid); + self.receiving_app.set(processid); self.radio .receive_advertisement(RadioChannel::AdvertisingChannel39); } @@ -553,18 +557,18 @@ where // re-transmissions for invalid CRCs fn transmit_event(&self, buf: &'static mut [u8], _crc_ok: Result<(), ErrorCode>) { self.kernel_tx.replace(buf); - self.sending_app.map(|appid| { - let _ = self.app.enter(*appid, |app, kernel_data| { + self.sending_app.map(|processid| { + let _ = self.app.enter(processid, |app, kernel_data| { match app.process_status { Some(BLEState::Advertising(RadioChannel::AdvertisingChannel37)) => { app.process_status = Some(BLEState::Advertising(RadioChannel::AdvertisingChannel38)); - self.sending_app.set(*appid); + self.sending_app.set(processid); let _ = self.radio.set_tx_power(app.tx_power); let _ = app.send_advertisement( - *appid, + processid, kernel_data, - &self, + self, RadioChannel::AdvertisingChannel38, ); } @@ -572,11 +576,11 @@ where Some(BLEState::Advertising(RadioChannel::AdvertisingChannel38)) => { app.process_status = Some(BLEState::Advertising(RadioChannel::AdvertisingChannel39)); - self.sending_app.set(*appid); + self.sending_app.set(processid); let _ = app.send_advertisement( - *appid, + processid, kernel_data, - &self, + self, RadioChannel::AdvertisingChannel39, ); } @@ -606,13 +610,13 @@ where command_num: usize, data: usize, interval: usize, - appid: kernel::ProcessId, + processid: kernel::ProcessId, ) -> CommandReturn { match command_num { // Start periodic advertisements 0 => { self.app - .enter(appid, |app, _| { + .enter(processid, |app, _| { if let Some(BLEState::Idle) = app.process_status { let pdu_type = data as AdvPduType; match pdu_type { @@ -633,12 +637,12 @@ where .map_or_else( |err| CommandReturn::failure(err.into()), |res| match res { - Ok(_) => { + Ok(()) => { // must be called outside closure passed to grant region! self.reset_active_alarm(); CommandReturn::success() } - Err(e) => CommandReturn::failure(e.into()), + Err(e) => CommandReturn::failure(e), }, ) } @@ -646,7 +650,7 @@ where // Stop periodic advertisements or passive scanning 1 => self .app - .enter(appid, |app, _| match app.process_status { + .enter(processid, |app, _| match app.process_status { Some(BLEState::AdvertisingIdle) | Some(BLEState::ScanningIdle) => { app.process_status = Some(BLEState::Idle); CommandReturn::success() @@ -664,7 +668,7 @@ where // data - Transmitting power in dBm 2 => { self.app - .enter(appid, |app, _| { + .enter(processid, |app, _| { if app.process_status != Some(BLEState::ScanningIdle) && app.process_status != Some(BLEState::AdvertisingIdle) { @@ -689,7 +693,7 @@ where // Passive scanning mode 5 => { self.app - .enter(appid, |app, _| { + .enter(processid, |app, _| { if let Some(BLEState::Idle) = app.process_status { app.process_status = Some(BLEState::ScanningIdle); app.set_next_alarm::(self.alarm.now().into_u32()); @@ -701,19 +705,18 @@ where .map_or_else( |err| err.into(), |res| match res { - Ok(_) => { + Ok(()) => { // must be called outside closure passed to grant region! self.reset_active_alarm(); CommandReturn::success() } - Err(e) => CommandReturn::failure(e.into()), + Err(e) => CommandReturn::failure(e), }, ) } _ => CommandReturn::failure(ErrorCode::NOSUPPORT), } - .into() } fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { diff --git a/capsules/extra/src/bme280.rs b/capsules/extra/src/bme280.rs new file mode 100644 index 0000000000..ffacf8e616 --- /dev/null +++ b/capsules/extra/src/bme280.rs @@ -0,0 +1,350 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! SyscallDriver for the Bosch BME280 Combined humidity and pressure +//! sensor using the I2C bus. +//! +//! +//! + +use core::cell::Cell; +use kernel::hil::i2c::{self, I2CClient, I2CDevice}; +use kernel::hil::sensors::{HumidityClient, HumidityDriver, TemperatureClient, TemperatureDriver}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::ErrorCode; + +const HUM_MSB: u8 = 0xFD; +const TEMP_MSB: u8 = 0xFA; +#[allow(dead_code)] +const PRESS_MSB: u8 = 0xF7; +#[allow(dead_code)] +const CONFIG: u8 = 0xF5; +const CTRL_MEAS: u8 = 0xF4; +#[allow(dead_code)] +const STATUS: u8 = 0xF3; +const CTRL_HUM: u8 = 0xF2; +#[allow(dead_code)] +const CALIB41: u8 = 0xF0; +const CALIB26: u8 = 0xE1; +#[allow(dead_code)] +const RESET: u8 = 0xE0; +const ID: u8 = 0xD0; +#[allow(dead_code)] +const CALIB25: u8 = 0xA1; +const CALIB00: u8 = 0x88; + +#[derive(Clone, Copy, PartialEq)] +enum DeviceState { + Identify, + CalibrationLow, + CalibrationHigh, + Probe, + Start, + Normal, +} + +#[allow(dead_code)] +#[derive(Clone, Copy, PartialEq)] +enum Operation { + None, + Temp, + Pressure, + Humidity, +} + +#[derive(Clone, Copy, PartialEq, Default)] +struct CalibrationData { + temp1: u16, + temp2: u16, + temp3: u16, + + press1: u16, + press2: u16, + press3: u16, + press4: u16, + press5: u16, + press6: u16, + press7: u16, + press8: u16, + press9: u16, + + hum1: u16, + hum2: u16, + hum3: u16, + hum4: u16, + hum5: u16, + hum6: u16, +} + +pub struct Bme280<'a, I: I2CDevice> { + buffer: TakeCell<'static, [u8]>, + i2c: &'a I, + calibration: Cell, + temperature_client: OptionalCell<&'a dyn TemperatureClient>, + humidity_client: OptionalCell<&'a dyn HumidityClient>, + state: Cell, + op: Cell, + t_fine: Cell, +} + +impl<'a, I: I2CDevice> Bme280<'a, I> { + pub fn new(i2c: &'a I, buffer: &'static mut [u8]) -> Self { + Bme280 { + buffer: TakeCell::new(buffer), + i2c, + calibration: Cell::new(CalibrationData::default()), + temperature_client: OptionalCell::empty(), + humidity_client: OptionalCell::empty(), + state: Cell::new(DeviceState::Identify), + op: Cell::new(Operation::None), + t_fine: Cell::new(0), + } + } + + pub fn startup(&self) { + self.buffer.take().map(|buffer| { + if self.state.get() == DeviceState::Identify { + // Read the ID buffer + buffer[0] = ID; + self.i2c.write_read(buffer, 1, 1).unwrap(); + } + }); + } +} + +impl<'a, I: I2CDevice> TemperatureDriver<'a> for Bme280<'a, I> { + fn set_client(&self, client: &'a dyn TemperatureClient) { + self.temperature_client.set(client); + } + + fn read_temperature(&self) -> Result<(), ErrorCode> { + if self.state.get() != DeviceState::Normal { + return Err(ErrorCode::BUSY); + } + + if self.op.get() != Operation::None { + return Err(ErrorCode::BUSY); + } + + self.buffer.take().map(|buffer| { + buffer[0] = TEMP_MSB; + + self.op.set(Operation::Temp); + self.i2c.write_read(buffer, 1, 3).unwrap(); + }); + + Ok(()) + } +} + +impl<'a, I: I2CDevice> HumidityDriver<'a> for Bme280<'a, I> { + fn set_client(&self, client: &'a dyn HumidityClient) { + self.humidity_client.set(client); + } + + fn read_humidity(&self) -> Result<(), ErrorCode> { + if self.state.get() != DeviceState::Normal { + return Err(ErrorCode::BUSY); + } + + if self.op.get() != Operation::None { + return Err(ErrorCode::BUSY); + } + + self.buffer.take().map(|buffer| { + buffer[0] = HUM_MSB; + + self.op.set(Operation::Humidity); + self.i2c.write_read(buffer, 1, 3).unwrap(); + }); + + Ok(()) + } +} + +impl<'a, I: I2CDevice> I2CClient for Bme280<'a, I> { + fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) { + if let Err(i2c_err) = status { + // We have no way to report an error, so just return a bogus value + match self.op.get() { + Operation::None => (), + Operation::Temp => { + self.temperature_client + .map(|client| client.callback(Err(i2c_err.into()))); + } + Operation::Pressure => { + unimplemented!(); + } + Operation::Humidity => { + self.humidity_client.map(|client| client.callback(0)); + } + } + self.buffer.replace(buffer); + self.op.set(Operation::None); + return; + } + + match self.state.get() { + DeviceState::Identify => { + if buffer[0] != 0x60 { + // We don't have the correct ID, this isn't the correct device + // Just stop here + self.buffer.replace(buffer); + return; + } + + buffer[0] = CALIB00; + self.i2c.write_read(buffer, 1, 26).unwrap(); + self.state.set(DeviceState::CalibrationLow); + } + DeviceState::CalibrationLow => { + let mut calib = self.calibration.take(); + calib.temp1 = buffer[0] as u16 | (buffer[1] as u16) << 8; + calib.temp2 = buffer[2] as u16 | (buffer[3] as u16) << 8; + calib.temp3 = buffer[4] as u16 | (buffer[5] as u16) << 8; + calib.press1 = buffer[6] as u16 | (buffer[7] as u16) << 8; + calib.press2 = buffer[8] as u16 | (buffer[9] as u16) << 8; + calib.press3 = buffer[10] as u16 | (buffer[11] as u16) << 8; + calib.press4 = buffer[12] as u16 | (buffer[13] as u16) << 8; + calib.press5 = buffer[14] as u16 | (buffer[15] as u16) << 8; + calib.press6 = buffer[16] as u16 | (buffer[17] as u16) << 8; + calib.press7 = buffer[18] as u16 | (buffer[19] as u16) << 8; + calib.press8 = buffer[20] as u16 | (buffer[21] as u16) << 8; + calib.press9 = buffer[22] as u16 | (buffer[23] as u16) << 8; + calib.hum1 = buffer[25] as u16; + self.calibration.set(calib); + + if calib.temp1 == 0 || calib.temp2 == 0 || calib.temp3 == 0 { + // We received stale calibration data, let's try again + + buffer[0] = CALIB00; + self.i2c.write_read(buffer, 1, 26).unwrap(); + self.state.set(DeviceState::CalibrationLow); + return; + } + + buffer[0] = CALIB26; + self.i2c.write_read(buffer, 1, 8).unwrap(); + + self.state.set(DeviceState::CalibrationHigh); + } + DeviceState::CalibrationHigh => { + let mut calib = self.calibration.take(); + calib.hum2 = buffer[0] as u16 | (buffer[1] as u16) << 8; + calib.hum3 = buffer[3] as u16; + calib.hum4 = buffer[4] as u16 | (buffer[5] as u16) << 4; + calib.hum5 = (buffer[6] as u16 >> 4) | (buffer[7] as u16) << 4; + calib.hum6 = buffer[8] as u16; + self.calibration.set(calib); + + buffer[0] = CTRL_MEAS; + self.i2c.write_read(buffer, 1, 1).unwrap(); + self.state.set(DeviceState::Probe); + } + DeviceState::Probe => { + if buffer[0] & 0x11 == 0 { + // We are in sleep mode, setup the device + // Set oversampling to 1 + buffer[0] = CTRL_HUM; + buffer[1] = 1; + self.i2c.write_read(buffer, 2, 1).unwrap(); + + self.state.set(DeviceState::Start); + } else { + // Everything is already setup, just start + self.state.set(DeviceState::Normal); + self.buffer.replace(buffer); + } + } + DeviceState::Start => { + // Set the mode to normal and set oversampling to 1 + buffer[0] = CTRL_MEAS; + buffer[1] = 0x11 | 1 << 5 | 1 << 2; + self.i2c.write(buffer, 2).unwrap(); + + self.state.set(DeviceState::Normal); + } + DeviceState::Normal => { + match self.op.get() { + Operation::None => (), + Operation::Temp => { + let calib = self.calibration.get(); + + // note: per datasheet, measurement is 20-bit two's complement + let adc_temperature: i32 = ((((buffer[0] as usize) << 12 + | (buffer[1] as usize) << 4 + | (((buffer[2] as usize) >> 4) & 0x0F)) + << 12) as i32) + >> 12; // ensure sign extension + + if adc_temperature == 0 { + // We got a misread, try again + self.buffer.replace(buffer); + self.op.set(Operation::None); + let _ = self.read_temperature(); + return; + } + + let var1 = (((adc_temperature >> 3) - ((calib.temp1 as i32) << 1)) + * (calib.temp2 as i32)) + >> 11; + let var2 = (((((adc_temperature >> 4) - (calib.temp1 as i32)) + * ((adc_temperature >> 4) - (calib.temp1 as i32))) + >> 12) + * (calib.temp3 as i32)) + >> 14; + + self.t_fine.set(var1 + var2); + + let temperature = (self.t_fine.get() * 5 + 128) >> 8; + + self.temperature_client + .map(|client| client.callback(Ok(temperature))); + } + Operation::Pressure => { + unimplemented!(); + } + Operation::Humidity => { + let calib = self.calibration.get(); + let adc_hum = (((buffer[0] as u32) << 8) | (buffer[1] as u32)) as i32; + + if adc_hum == 0 { + // We got a misread, try again + self.buffer.replace(buffer); + self.op.set(Operation::None); + let _ = self.read_humidity(); + return; + } + + let t_fine_offset = self.t_fine.get() - 76800; + + // This is straight from the datasheet + let var1 = ((((adc_hum << 14) + - ((calib.hum4 as i32) << 20) + - ((calib.hum5 as i32) * t_fine_offset)) + + 16384) + >> 15) + * (((((((t_fine_offset * (calib.hum6 as i32)) >> 10) + * (((t_fine_offset * (calib.hum3 as i32)) >> 11) + 32768)) + >> 10) + + 2097152) + * (calib.hum2 as i32) + + 8192) + >> 14); + let var2 = var1 + - (((((var1 >> 15) * (var1 >> 15)) >> 7) * (calib.hum1 as i32)) >> 4); + + let var6 = if var2 > 419430400 { 419430400 } else { var2 }; + + let hum = ((var6 >> 12) / 1024) as usize; + + self.humidity_client.map(|client| client.callback(hum)); + } + } + self.buffer.replace(buffer); + self.op.set(Operation::None); + } + } + } +} diff --git a/capsules/extra/src/bmm150.rs b/capsules/extra/src/bmm150.rs new file mode 100644 index 0000000000..d7cb5f1238 --- /dev/null +++ b/capsules/extra/src/bmm150.rs @@ -0,0 +1,202 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! SyscallDriver for the Bosch BMM150 geomagnetic sensor. +//! +//! +//! +//! > The BMM150 is a standalone geomagnetic sensor for consumer +//! > market applications. It allows measurements of the magnetic +//! > field in three perpendicular axes. Based on Bosch’s proprietary +//! > FlipCore technology, performance and features of BMM150 are +//! > carefully tuned and perfectly match the demanding requirements of +//! > all 3-axis mobile applications such as electronic compass, navigation +//! > or augmented reality. +//! +//! //! Driver Semantics +//! ---------------- +//! +//! This driver exposes the BMM150's functionality via the [NineDof] and +//! [NineDofClient] HIL interfaces. If gyroscope or accelerometer data is +//! requested, the driver will return a ErrorCode. +//! +//! //! Usage +//! ----- +//! +//! ```rust,ignore +//! # use kernel::static_init; +//! +//! let bmm150_i2c = static_init!( +//! capsules::virtual_i2c::I2CDevice, +//! capsules::virtual_i2c::I2CDevice::new(i2c_bus, 0x10)); +//! let bmm150 = static_init!( +//! capsules::bmm150::BMM150<'static>, +//! capsules::bmm150::BMM150::new(bmm150_i2c, +//! &mut capsules::BMM150::BUFFER)); +//! bmm150_i2c.set_client(bmm150); +//! ``` + +use core::cell::Cell; +use kernel::hil::i2c::{self, I2CClient, I2CDevice}; +use kernel::hil::sensors::{NineDof, NineDofClient}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::ErrorCode; + +#[allow(dead_code)] +enum Registers { + ChipID = 0x40, + DATAxLsb = 0x42, + DATAxMsb = 0x43, + DATAyLsb = 0x44, + DATAyMsb = 0x45, + DATAzLsb = 0x46, + DATAzMsb = 0x47, + RHALLlsb = 0x48, + RHALLmsb = 0x49, + INTST = 0x4A, + CTRL1 = 0x4B, + CTRL2 = 0x4C, + CTRL3 = 0x4D, + CTRL4 = 0x4E, + LoThres = 0x4F, + HiThres = 0x50, + REPXY = 0x51, + REPZ = 0x52, +} + +pub struct BMM150<'a, I: I2CDevice> { + buffer: TakeCell<'static, [u8]>, + i2c: &'a I, + ninedof_client: OptionalCell<&'a dyn NineDofClient>, + state: Cell, +} + +impl<'a, I: I2CDevice> BMM150<'a, I> { + pub fn new(buffer: &'static mut [u8], i2c: &'a I) -> BMM150<'a, I> { + BMM150 { + buffer: TakeCell::new(buffer), + i2c, + ninedof_client: OptionalCell::empty(), + state: Cell::new(State::Suspend), + } + } + + pub fn start_measurement(&self) -> Result<(), ErrorCode> { + self.buffer + .take() + .map(|buffer| { + self.i2c.enable(); + match self.state.get() { + State::Suspend => { + buffer[0] = Registers::CTRL1 as u8; + buffer[1] = 0x1_u8; + + if let Err((_error, buffer)) = self.i2c.write(buffer, 2) { + self.buffer.replace(buffer); + self.i2c.disable(); + } else { + self.state.set(State::PowerOn); + } + } + State::Sleep => { + buffer[0] = Registers::CTRL2 as u8; + buffer[1] = 0x3A_u8; + + if let Err((_error, buffer)) = self.i2c.write(buffer, 2) { + self.buffer.replace(buffer); + self.i2c.disable(); + } else { + self.state.set(State::InitializeReading); + } + } + _ => {} + } + }) + .ok_or(ErrorCode::FAIL) + } +} + +impl<'a, I: i2c::I2CDevice> NineDof<'a> for BMM150<'a, I> { + fn set_client(&self, client: &'a dyn NineDofClient) { + self.ninedof_client.set(client); + } + + fn read_magnetometer(&self) -> Result<(), ErrorCode> { + self.start_measurement() + } +} + +#[derive(Clone, Copy, Debug)] +enum State { + Suspend, + Sleep, + PowerOn, + InitializeReading, + ReadMeasurement, + Read, +} + +impl<'a, I: I2CDevice> I2CClient for BMM150<'a, I> { + fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) { + if let Err(i2c_err) = status { + self.state.set(State::Sleep); + self.buffer.replace(buffer); + self.ninedof_client + .map(|client| client.callback(i2c_err as usize, 0, 0)); + return; + } + + match self.state.get() { + State::PowerOn => { + buffer[0] = Registers::CTRL2 as u8; + buffer[1] = 0x3A_u8; + + if let Err((error, buffer)) = self.i2c.write(buffer, 2) { + self.buffer.replace(buffer); + self.i2c.disable(); + self.ninedof_client + .map(|client| client.callback(error as usize, 0, 0)); + } else { + self.state.set(State::InitializeReading); + } + } + State::InitializeReading => { + buffer[0] = Registers::DATAxLsb as u8; + + if let Err((i2c_err, buffer)) = self.i2c.write(buffer, 1) { + self.state.set(State::Sleep); + self.buffer.replace(buffer); + self.ninedof_client + .map(|client| client.callback(i2c_err as usize, 0, 0)); + } else { + self.state.set(State::ReadMeasurement); + } + } + State::ReadMeasurement => { + if let Err((i2c_err, buffer)) = self.i2c.read(buffer, 8) { + self.state.set(State::Sleep); + self.buffer.replace(buffer); + self.ninedof_client + .map(|client| client.callback(i2c_err as usize, 0, 0)); + } else { + self.state.set(State::Read); + } + } + State::Read => { + let x_axis = ((buffer[1] as i16) << 5) | ((buffer[0] as i16) >> 3); + let y_axis = ((buffer[3] as i16) << 5) | ((buffer[2] as i16) >> 3); + let z_axis = ((buffer[5] as i16) << 7) | ((buffer[4] as i16) >> 1); + + self.state.set(State::Sleep); + self.buffer.replace(buffer); + self.i2c.disable(); + self.ninedof_client.map(|client| { + client.callback(x_axis as usize, y_axis as usize, z_axis as usize) + }); + } + State::Sleep => {} // should never happen + State::Suspend => {} // should never happen + } + } +} diff --git a/capsules/extra/src/bmp280.rs b/capsules/extra/src/bmp280.rs new file mode 100644 index 0000000000..f3b7ffcd53 --- /dev/null +++ b/capsules/extra/src/bmp280.rs @@ -0,0 +1,486 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! hil driver for Bmp280 Temperature and Pressure Sensor +//! +//! Written by Dorota +//! +//! Based off the SHT3x code. +//! +//! Not implemented: pressure + +use core::cell::Cell; +use kernel::debug; +use kernel::hil; +use kernel::hil::i2c; +use kernel::hil::time::{Alarm, ConvertTicks}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::ErrorCode; + +pub static BASE_ADDR: u8 = 0x76; + +/// Currently sized enough for temperature readings only. +pub const BUFFER_SIZE: usize = 6; + +#[allow(non_camel_case_types)] +#[allow(dead_code)] +enum Register { + /// First register of calibration data. + /// Each register is 2 bytes long. + DIG_T1 = 0x88, + DIG_T2 = 0x8a, + DIG_T3 = 0x8c, + ID = 0xd0, + RESET = 0xe0, + // measuring: [3] + // im_update: [0] + STATUS = 0xf3, + // osrs_t: [7:5] + // osrs_p: [4:2] + // mode: [1:0] + CTRL_MEAS = 0xf4, + // t_sb: [7:5] + // filter: [4:2] + // spi3w_en: [0] + CONFIG = 0xf5, + PRESS_MSB = 0xf7, + PRESS_LSB = 0xf8, + // xlsb: [7:4] + PRESS_XLSB = 0xf9, + TEMP_MSB = 0xfa, + TEMP_LSB = 0xfb, + // xlsb: [7:4] + TEMP_XLSB = 0xfc, +} + +#[derive(Clone, Copy, PartialEq, Debug)] +struct CalibrationData { + dig_t1: u16, + dig_t2: i16, + dig_t3: i16, + // TODO: pressure calibration +} + +/// CAUTION: calibration data puts least significant byte in the lowest address, +/// readouts do the opposite. +fn twobyte(lsb: u8, msb: u8) -> u16 { + u16::from_be_bytes([msb, lsb]) +} + +impl CalibrationData { + fn new(i2c_raw: &[u8]) -> Self { + CalibrationData { + dig_t1: twobyte(i2c_raw[0], i2c_raw[1]), + dig_t2: twobyte(i2c_raw[2], i2c_raw[3]) as i16, + dig_t3: twobyte(i2c_raw[4], i2c_raw[5]) as i16, + } + } + + fn temp_from_raw(&self, temp: i32) -> i32 { + let dig_t1 = self.dig_t1 as i32; // same, 16-bits + let dig_t2 = self.dig_t2 as i32; // same, 16-bits + let dig_t3 = self.dig_t3 as i32; // same, 16-bits + // From the datasheet + let var1 = (((temp >> 3) - (dig_t1 << 1)) * dig_t2) >> 11; + let a = (temp >> 4) - dig_t1; + let var2 = (((a * a) >> 12) * dig_t3) >> 14; + let t_fine = var1 + var2; + ((t_fine * 5) + 128) >> 8 + } +} + +/// Internal state. +/// Each state can lead to the next on in order of appearance. +#[derive(Clone, Copy, PartialEq, Debug)] +enum State { + Uninitialized, + InitId, + /// It's not guaranteed that the MCU reset is the same as device power-on, + /// so an explicit reset is necessary. + InitResetting, + InitWaitingReady, + InitReadingCalibration, + + Idle(CalibrationData), + + // States related to sample readout + /// One-shot mode request sent + Configuring(CalibrationData), + /// Sampling takes milliseconds, so spend most of that time sleeping. + WaitingForAlarm(CalibrationData), + /// Polling for the readout to become ready. + Waiting(CalibrationData), + /// Waiting for readout to return. + /// This state can also lead back to Idle. + Reading(CalibrationData), + + /// Reset cannot be attempted. + /// This is because reset failed before, or because the ID is mismatched. + IrrecoverableError, + /// Irrecoverable. Currently only when init fails. + /// Reset will clear this. + Error, + /// An unexpected, irrecoverable situation was encountered, + /// and the driver is giving up. + /// Reset clears this. + Bug, +} + +impl State { + /// Changes state to one denoting this driver is buggy. + fn to_bug(self) -> Self { + match self { + // A bug does not override the device not being present. + State::IrrecoverableError => State::IrrecoverableError, + _ => State::Bug, + } + } +} + +/// Complies with the reading and writing protocol used by the sensor. +struct I2cWrapper<'a, I: i2c::I2CDevice> { + i2c: &'a I, +} + +impl<'a, I: i2c::I2CDevice> I2cWrapper<'a, I> { + fn write( + &self, + buffer: &'static mut [u8], + addr: Register, + data: [u8; COUNT], + ) -> Result<(), (i2c::Error, &'static mut [u8])> { + buffer[0] = addr as u8; + buffer[1..][..COUNT].copy_from_slice(&data); + self.i2c.enable(); + self.i2c.write(buffer, COUNT + 1) + } + + /// Requests a read into buffer. + /// Parse the result using `parse_read`. + fn read( + &self, + buffer: &'static mut [u8], + addr: Register, + count: usize, + ) -> Result<(), (i2c::Error, &'static mut [u8])> { + buffer[0] = addr as u8; + self.i2c.enable(); + self.i2c.write_read(buffer, 1, count) + } + + fn disable(&self) { + self.i2c.disable() + } + + fn parse_read(buffer: &[u8], count: u8) -> &[u8] { + &buffer[..(count as usize)] + } +} + +pub struct Bmp280<'a, A: Alarm<'a>, I: i2c::I2CDevice> { + i2c: I2cWrapper<'a, I>, + temperature_client: OptionalCell<&'a dyn hil::sensors::TemperatureClient>, + // This might be better as a `RefCell`, + // because `State` is multiple bytes due to the `CalibrationData`. + // `Cell` requires Copy, which might get expensive, while `RefCell` doesn't. + // It's probably not a good idea to split `CalibrationData` + // into a separate place, because it will make state more duplicated. + state: Cell, + /// Stores i2c commands + buffer: TakeCell<'static, [u8]>, + /// Needed to wait for readout completion, which can take milliseconds. + /// It's possible to implement this without an alarm with busy polling, but that's wasteful. + alarm: &'a A, +} + +impl<'a, A: Alarm<'a>, I: i2c::I2CDevice> Bmp280<'a, A, I> { + pub fn new(i2c: &'a I, buffer: &'static mut [u8], alarm: &'a A) -> Self { + Self { + i2c: I2cWrapper { i2c }, + temperature_client: OptionalCell::empty(), + state: Cell::new(State::Uninitialized), + buffer: TakeCell::new(buffer), + alarm, + } + } + + /// Resets the device and brings it into a known state. + pub fn begin_reset(&self) -> Result<(), ErrorCode> { + self.buffer + .take() + .map_or(Err(ErrorCode::NOMEM), |buffer| match self.state.get() { + State::Uninitialized | State::Error | State::Bug => { + let (ret, new_state) = match self.i2c.read(buffer, Register::ID, 1) { + Ok(()) => (Ok(()), State::InitId), + Err((_e, buffer)) => { + self.i2c.disable(); + self.buffer.replace(buffer); + (Err(ErrorCode::FAIL), State::IrrecoverableError) + } + }; + self.state.set(new_state); + ret + } + State::IrrecoverableError => Err(ErrorCode::NODEVICE), + _ => Err(ErrorCode::ALREADY), + }) + } + + pub fn read_temperature(&self) -> Result<(), ErrorCode> { + match self.state.get() { + // Actually, the sensor might be on, just in default state. + State::Uninitialized => Err(ErrorCode::OFF), + State::InitId + | State::InitResetting + | State::InitWaitingReady + | State::InitReadingCalibration => Err(ErrorCode::BUSY), + State::Idle(calibration) => { + self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| { + // todo: use bitfield crate + // forced mode, oversampling 1 + let val = 0b00100001; + let (ret, new_state) = match self.i2c.write(buffer, Register::CTRL_MEAS, [val]) + { + Ok(()) => (Ok(()), State::Configuring(calibration)), + Err((_e, buffer)) => { + self.i2c.disable(); + self.buffer.replace(buffer); + (Err(ErrorCode::FAIL), State::Idle(calibration)) + } + }; + self.state.set(new_state); + ret + }) + } + State::Configuring(_) + | State::WaitingForAlarm(_) + | State::Waiting(_) + | State::Reading(_) => Err(ErrorCode::BUSY), + State::Error | State::Bug => Err(ErrorCode::FAIL), + State::IrrecoverableError => Err(ErrorCode::NODEVICE), + } + } + + fn handle_alarm(&self) { + match self.state.get() { + State::WaitingForAlarm(calibration) => self.buffer.take().map_or_else( + || { + debug!("BMP280 No buffer available!"); + self.state.set(State::IrrecoverableError) + }, + |buffer| { + let new_state = match self.check_ready(buffer) { + Ok(()) => State::Waiting(calibration), + Err((_e, buffer)) => { + self.i2c.disable(); + self.buffer.replace(buffer); + State::Idle(calibration) + } + }; + self.state.set(new_state) + }, + ), + State::IrrecoverableError => {} + other => { + debug!("BMP280 received unexpected alarm in state {:?}", other); + self.state.set(other.to_bug()) + } + } + } + + fn arm_alarm(&self) { + // Datasheet says temp oversampling=1 makes a reading typically take 5.5ms. + // (Maximally 6.4ms). + let delay = self.alarm.ticks_from_us(6400); + self.alarm.set_alarm(self.alarm.now(), delay); + } + + fn check_ready( + &self, + buffer: &'static mut [u8], + ) -> Result<(), (i2c::Error, &'static mut [u8])> { + self.i2c.read(buffer, Register::STATUS, 1) + } +} + +enum I2cOperation { + Read { + addr: Register, + count: usize, + fail_state: State, + }, + Write { + addr: Register, + data: u8, + fail_state: State, + }, + Disable, +} + +impl I2cOperation { + fn check_ready(fail_state: State) -> Self { + Self::Read { + addr: Register::STATUS, + count: 1, + fail_state, + } + } +} + +impl<'a, A: Alarm<'a>, I: i2c::I2CDevice> i2c::I2CClient for Bmp280<'a, A, I> { + fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) { + let mut temp_readout = None; + let mut i2c_op = I2cOperation::Disable; + + let new_state = match status { + Ok(()) => match self.state.get() { + State::InitId => { + let id = I2cWrapper::::parse_read(buffer, 1); + if id[0] == 0x58 { + i2c_op = I2cOperation::Write { + addr: Register::RESET, + data: 0xb6, + fail_state: State::IrrecoverableError, + }; + State::InitResetting + } else { + State::IrrecoverableError + } + } + State::InitResetting => { + i2c_op = I2cOperation::check_ready(State::Error); + State::InitWaitingReady + } + State::InitWaitingReady => { + let waiting = I2cWrapper::::parse_read(buffer, 1)[0]; + if waiting & 0b1 == 0 { + // finished init + i2c_op = I2cOperation::Read { + addr: Register::DIG_T1, + count: 6, + fail_state: State::Error, + }; + State::InitReadingCalibration + } else { + i2c_op = I2cOperation::check_ready(State::Error); + State::InitWaitingReady + } + } + State::InitReadingCalibration => { + let data = I2cWrapper::::parse_read(buffer, 6); + let calibration = CalibrationData::new(data); + State::Idle(calibration) + } + // Readout-related states + State::Configuring(calibration) => { + self.arm_alarm(); + State::WaitingForAlarm(calibration) + } + State::Waiting(calibration) => { + let waiting_value = I2cWrapper::::parse_read(buffer, 1); + // not waiting + if waiting_value[0] & 0b1000 == 0 { + i2c_op = I2cOperation::Read { + addr: Register::TEMP_MSB, + count: 3, + fail_state: State::Idle(calibration), + }; + State::Reading(calibration) + } else { + i2c_op = I2cOperation::check_ready(State::Idle(calibration)); + State::Waiting(calibration) + } + } + State::Reading(calibration) => { + let readout = I2cWrapper::::parse_read(buffer, 3); + let msb = readout[0] as u32; + let lsb = readout[1] as u32; + let xlsb = readout[2] as u32; + let raw_temp: i32 = + ((((msb << 12) + (lsb << 4) + (xlsb >> 4)) << 12) as i32) >> 12; // ensure sign extention + temp_readout = Some(Ok(calibration.temp_from_raw(raw_temp))); + State::Idle(calibration) + } + other => { + debug!("BMP280 received unexpected i2c reply in state {:?}", other); + other.to_bug() + } + }, + Err(i2c_err) => match self.state.get() { + State::Configuring(calibration) + | State::Waiting(calibration) + | State::Reading(calibration) => { + temp_readout = Some(Err(i2c_err.into())); + State::Idle(calibration) + } + State::InitId + | State::InitResetting + | State::InitWaitingReady + | State::InitReadingCalibration => State::Error, + other => { + debug!("BMP280 received unexpected i2c reply in state {:?}", other); + other.to_bug() + } + }, + }; + + // Try enqueueing the requested i2c operation + let new_state = match i2c_op { + I2cOperation::Disable => { + self.i2c.disable(); + self.buffer.replace(buffer); + new_state + } + I2cOperation::Read { + addr, + count, + fail_state, + } => { + if let Err((_e, buffer)) = self.i2c.read(buffer, addr, count) { + self.i2c.disable(); + self.buffer.replace(buffer); + fail_state + } else { + new_state + } + } + I2cOperation::Write { + addr, + data, + fail_state, + } => { + if let Err((_e, buffer)) = self.i2c.write(buffer, addr, [data]) { + self.i2c.disable(); + self.buffer.replace(buffer); + fail_state + } else { + new_state + } + } + }; + + // Setting state before the callback, + // in case the callback wants to use the same driver again. + self.state.set(new_state); + if let Some(temp) = temp_readout { + self.temperature_client.map(|cb| cb.callback(temp)); + } + } +} + +impl<'a, A: Alarm<'a>, I: i2c::I2CDevice> hil::sensors::TemperatureDriver<'a> for Bmp280<'a, A, I> { + fn set_client(&self, client: &'a dyn hil::sensors::TemperatureClient) { + self.temperature_client.set(client) + } + + fn read_temperature(&self) -> Result<(), ErrorCode> { + self.read_temperature() + } +} + +impl<'a, A: hil::time::Alarm<'a>, I: i2c::I2CDevice> hil::time::AlarmClient for Bmp280<'a, A, I> { + fn alarm(&self) { + self.handle_alarm() + } +} diff --git a/capsules/src/bus.rs b/capsules/extra/src/bus.rs similarity index 95% rename from capsules/src/bus.rs rename to capsules/extra/src/bus.rs index b7f8d8591c..9a9203cde9 100644 --- a/capsules/src/bus.rs +++ b/capsules/extra/src/bus.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Abstraction Interface for several busses. //! Useful for devices that support multiple protocols //! @@ -5,13 +9,13 @@ //! ----- //! //! I2C example -//! ```rust +//! ```rust,ignore //! let bus = components::bus::I2CMasterBusComponent::new(i2c_mux, address) //! .finalize(components::spi_bus_component_helper!()); //! ``` //! //! SPI example -//! ```rust +//! ```rust,ignore //! let bus = //! components::bus::SpiMasterBusComponent::new().finalize(components::spi_bus_component_helper!( //! // spi type @@ -56,7 +60,7 @@ impl BusWidth { pub trait Bus<'a> { /// Set the address to write to /// - /// If the underlaying bus does not support addresses (eg UART) + /// If the underlying bus does not support addresses (eg UART) /// this function returns ENOSUPPORT fn set_addr(&self, addr_width: BusWidth, addr: usize) -> Result<(), ErrorCode>; @@ -109,7 +113,7 @@ enum BusStatus { /*********** SPI ************/ -pub struct SpiMasterBus<'a, S: SpiMasterDevice> { +pub struct SpiMasterBus<'a, S: SpiMasterDevice<'a>> { spi: &'a S, read_write_buffer: OptionalCell<&'static mut [u8]>, bus_width: Cell, @@ -118,7 +122,7 @@ pub struct SpiMasterBus<'a, S: SpiMasterDevice> { status: Cell, } -impl<'a, S: SpiMasterDevice> SpiMasterBus<'a, S> { +impl<'a, S: SpiMasterDevice<'a>> SpiMasterBus<'a, S> { pub fn new(spi: &'a S, addr_buffer: &'static mut [u8]) -> SpiMasterBus<'a, S> { SpiMasterBus { spi, @@ -144,7 +148,7 @@ impl<'a, S: SpiMasterDevice> SpiMasterBus<'a, S> { } } -impl<'a, S: SpiMasterDevice> Bus<'a> for SpiMasterBus<'a, S> { +impl<'a, S: SpiMasterDevice<'a>> Bus<'a> for SpiMasterBus<'a, S> { fn set_addr(&self, addr_width: BusWidth, addr: usize) -> Result<(), ErrorCode> { match addr_width { BusWidth::Bits8 => self @@ -227,7 +231,7 @@ impl<'a, S: SpiMasterDevice> Bus<'a> for SpiMasterBus<'a, S> { } } -impl<'a, S: SpiMasterDevice> SpiMasterClient for SpiMasterBus<'a, S> { +impl<'a, S: SpiMasterDevice<'a>> SpiMasterClient for SpiMasterBus<'a, S> { fn read_write_done( &self, write_buffer: &'static mut [u8], @@ -315,7 +319,7 @@ impl<'a, I: I2CDevice> Bus<'a> for I2CMasterBus<'a, I> { debug!("write len {}", len); self.len.set(len); self.status.set(BusStatus::Write); - match self.i2c.write(buffer, (len * bytes) as u8) { + match self.i2c.write(buffer, len * bytes) { Ok(()) => Ok(()), Err((error, buffer)) => Err((error.into(), buffer)), } @@ -336,7 +340,7 @@ impl<'a, I: I2CDevice> Bus<'a> for I2CMasterBus<'a, I> { if len & bytes < 255 && buffer.len() >= len * bytes { self.len.set(len); self.status.set(BusStatus::Read); - match self.i2c.read(buffer, (len * bytes) as u8) { + match self.i2c.read(buffer, len * bytes) { Ok(()) => Ok(()), Err((error, buffer)) => Err((error.into(), buffer)), } @@ -387,7 +391,7 @@ pub struct Bus8080Bus<'a, B: Bus8080<'static>> { impl<'a, B: Bus8080<'static>> Bus8080Bus<'a, B> { pub fn new(bus: &'a B) -> Bus8080Bus<'a, B> { Bus8080Bus { - bus: bus, + bus, client: OptionalCell::empty(), status: Cell::new(BusStatus::Idle), } diff --git a/capsules/src/buzzer_driver.rs b/capsules/extra/src/buzzer_driver.rs similarity index 51% rename from capsules/src/buzzer_driver.rs rename to capsules/extra/src/buzzer_driver.rs index 8b9d959b61..4ee820373a 100644 --- a/capsules/src/buzzer_driver.rs +++ b/capsules/extra/src/buzzer_driver.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! This provides virtualized userspace access to a buzzer. //! //! Each app can have one outstanding buzz request, and buzz requests will queue @@ -11,7 +15,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! let virtual_pwm_buzzer = static_init!( @@ -26,30 +30,50 @@ //! ); //! virtual_alarm_buzzer.setup(); //! -//! let buzzer = static_init!( -//! capsules::buzzer_driver::Buzzer< +//! let pwm_buzzer = static_init!( +//! capsules::buzzer_pwm::PwmBuzzer< //! 'static, -//! capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf5x::rtc::Rtc>>, -//! capsules::buzzer_driver::Buzzer::new( +//! capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52833::rtc::Rtc>, +//! capsules::virtual_pwm::PwmPinUser<'static, nrf52833::pwm::Pwm>, +//! >, +//! capsules::buzzer_pwm::PwmBuzzer::new( //! virtual_pwm_buzzer, //! virtual_alarm_buzzer, +//! capsules::buzzer_pwm::DEFAULT_MAX_BUZZ_TIME_MS, +//! ) +//! ); +//! +//! let buzzer_driver = static_init!( +//! capsules::buzzer_driver::Buzzer< +//! 'static, +//! capsules::buzzer_pwm::PwmBuzzer< +//! 'static, +//! capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52833::rtc::Rtc>, +//! capsules::virtual_pwm::PwmPinUser<'static, nrf52833::pwm::Pwm>, +//! >, +//! >, +//! capsules::buzzer_driver::Buzzer::new( +//! pwm_buzzer, //! capsules::buzzer_driver::DEFAULT_MAX_BUZZ_TIME_MS, -//! board_kernel.create_grant(&grant_cap)) +//! board_kernel.create_grant(capsules::buzzer_driver::DRIVER_NUM, &memory_allocation_capability) +//! ) //! ); -//! virtual_alarm_buzzer.set_client(buzzer); +//! +//! pwm_buzzer.set_client(buzzer_driver); +//! +//! virtual_alarm_buzzer.set_client(pwm_buzzer); //! ``` use core::cmp; use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; use kernel::hil; -use kernel::hil::time::Frequency; use kernel::syscall::{CommandReturn, SyscallDriver}; use kernel::utilities::cells::OptionalCell; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::Buzzer as usize; /// Standard max buzz time. @@ -68,47 +92,52 @@ pub struct App { pending_command: Option, // What command to run when the buzzer is free. } -pub struct Buzzer<'a, A: hil::time::Alarm<'a>> { - // The underlying PWM generator to make the buzzer buzz. - pwm_pin: &'a dyn hil::pwm::PwmPin, - // Alarm to stop the buzzer after some time. - alarm: &'a A, - // Per-app state. +pub struct Buzzer<'a, B: hil::buzzer::Buzzer<'a>> { + /// The service capsule buzzer. + buzzer: &'a B, + /// Per-app state. apps: Grant, AllowRoCount<0>, AllowRwCount<0>>, - // Which app is currently using the buzzer. + /// Which app is currently using the buzzer. active_app: OptionalCell, - // Max buzz time. + /// Max buzz time. max_duration_ms: usize, } -impl<'a, A: hil::time::Alarm<'a>> Buzzer<'a, A> { +impl<'a, B: hil::buzzer::Buzzer<'a>> Buzzer<'a, B> { pub fn new( - pwm_pin: &'a dyn hil::pwm::PwmPin, - alarm: &'a A, + buzzer: &'a B, max_duration_ms: usize, grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, - ) -> Buzzer<'a, A> { + ) -> Buzzer<'a, B> { Buzzer { - pwm_pin: pwm_pin, - alarm: alarm, + buzzer, apps: grant, active_app: OptionalCell::empty(), - max_duration_ms: max_duration_ms, + max_duration_ms, } } // Check so see if we are doing something. If not, go ahead and do this // command. If so, this is queued and will be run when the pending // command completes. - fn enqueue_command(&self, command: BuzzerCommand, app_id: ProcessId) -> Result<(), ErrorCode> { + fn enqueue_command( + &self, + command: BuzzerCommand, + processid: ProcessId, + ) -> Result<(), ErrorCode> { if self.active_app.is_none() { // No app is currently using the buzzer, so we just use this app. - self.active_app.set(app_id); - self.buzz(command) + self.active_app.set(processid); + match command { + BuzzerCommand::Buzz { + frequency_hz, + duration_ms, + } => self.buzzer.buzz(frequency_hz, duration_ms), + } } else { // There is an active app, so queue this request (if possible). self.apps - .enter(app_id, |app, _| { + .enter(processid, |app, _| { // Some app is using the storage, we must wait. if app.pending_command.is_some() { // No more room in the queue, nowhere to store this @@ -124,40 +153,21 @@ impl<'a, A: hil::time::Alarm<'a>> Buzzer<'a, A> { } } - fn buzz(&self, command: BuzzerCommand) -> Result<(), ErrorCode> { - match command { - BuzzerCommand::Buzz { - frequency_hz, - duration_ms, - } => { - // Start the PWM output at the specified frequency with a 50% - // duty cycle. - let ret = self - .pwm_pin - .start(frequency_hz, self.pwm_pin.get_maximum_duty_cycle() / 2); - if ret != Ok(()) { - return ret; - } - - // Now start a timer so we know when to stop the PWM. - let interval = (duration_ms as u32) * ::frequency() / 1000; - self.alarm - .set_alarm(self.alarm.now(), A::Ticks::from(interval)); - Ok(()) - } - } - } - fn check_queue(&self) { for appiter in self.apps.iter() { - let appid = appiter.processid(); + let processid = appiter.processid(); let started_command = appiter.enter(|app, _| { // If this app has a pending command let's use it. app.pending_command.take().map_or(false, |command| { // Mark this driver as being in use. - self.active_app.set(appid); + self.active_app.set(processid); // Actually make the buzz happen. - self.buzz(command) == Ok(()) + match command { + BuzzerCommand::Buzz { + frequency_hz, + duration_ms, + } => self.buzzer.buzz(frequency_hz, duration_ms) == Ok(()), + } }) }); if started_command { @@ -165,27 +175,39 @@ impl<'a, A: hil::time::Alarm<'a>> Buzzer<'a, A> { } } } + + /// For buzzing immediatelly + /// Checks whether an app is valid or not. The app is valid if + /// there is no current active_app using the driver, or if the app corresponds + /// to the current active_app. Otherwise, a different app is trying to + /// use the driver while it is already in use, therefore it is not valid. + pub fn is_valid_app(&self, processid: ProcessId) -> bool { + self.active_app + .map_or(true, |owning_app| owning_app == processid) + } } -impl<'a, A: hil::time::Alarm<'a>> hil::time::AlarmClient for Buzzer<'a, A> { - fn alarm(&self) { - // All we have to do is stop the PWM and check if there are any pending - // uses of the buzzer. - let _ = self.pwm_pin.stop(); +impl<'a, B: hil::buzzer::Buzzer<'a>> hil::buzzer::BuzzerClient for Buzzer<'a, B> { + fn buzzer_done(&self, status: Result<(), ErrorCode>) { // Mark the active app as None and see if there is a callback. - self.active_app.take().map(|app_id| { - let _ = self.apps.enter(app_id, |_app, upcalls| { - upcalls.schedule_upcall(0, (0, 0, 0)).ok(); + self.active_app.take().map(|processid| { + let _ = self.apps.enter(processid, |_app, upcalls| { + upcalls + .schedule_upcall(0, (kernel::errorcode::into_statuscode(status), 0, 0)) + .ok(); }); }); + // Remove the current app. + self.active_app.clear(); + // Check if there is anything else to do. self.check_queue(); } } /// Provide an interface for userland. -impl<'a, A: hil::time::Alarm<'a>> SyscallDriver for Buzzer<'a, A> { +impl<'a, B: hil::buzzer::Buzzer<'a>> SyscallDriver for Buzzer<'a, B> { // Setup callbacks. // // ### `subscribe_num` @@ -197,23 +219,25 @@ impl<'a, A: hil::time::Alarm<'a>> SyscallDriver for Buzzer<'a, A> { /// ### `command_num` /// /// - `0`: Return Ok(()) if this driver is included on the platform. - /// - `1`: Buzz the buzzer. `data1` is used for the frequency in hertz, and + /// - `1`: Buzz the buzzer when available. `data1` is used for the frequency in hertz, and + /// `data2` is the duration in ms. Note the duration is capped at 5000 + /// milliseconds. + /// - `2`: Buzz the buzzer immediatelly. `data1` is used for the frequency in hertz, and /// `data2` is the duration in ms. Note the duration is capped at 5000 /// milliseconds. + /// - `3`: Stop the buzzer. fn command( &self, command_num: usize, data1: usize, data2: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { match command_num { - 0 => - /* This driver exists. */ - { - CommandReturn::success() - } + // Check whether the driver exists. + 0 => CommandReturn::success(), + // Play a sound when available. 1 => { let frequency_hz = data1; let duration_ms = cmp::min(data2, self.max_duration_ms); @@ -222,11 +246,37 @@ impl<'a, A: hil::time::Alarm<'a>> SyscallDriver for Buzzer<'a, A> { frequency_hz, duration_ms, }, - appid, + processid, ) .into() } + // Play a sound immediately. + 2 => { + if !self.is_valid_app(processid) { + // A different app is trying to use the buzzer, so we return RESERVE. + CommandReturn::failure(ErrorCode::RESERVE) + } else { + // If there is no active app or the same app is trying to use the buzzer, + // we set/replace the frequency and duration. + self.active_app.set(processid); + self.buzzer.buzz(data1, data2).into() + } + } + + // Stop the current sound. + 3 => { + if !self.is_valid_app(processid) { + CommandReturn::failure(ErrorCode::RESERVE) + } else if self.active_app.is_none() { + // If there is no active app, the buzzer isn't playing, so we return OFF. + CommandReturn::failure(ErrorCode::OFF) + } else { + self.active_app.set(processid); + self.buzzer.stop().into() + } + } + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), } } diff --git a/capsules/extra/src/buzzer_pwm.rs b/capsules/extra/src/buzzer_pwm.rs new file mode 100644 index 0000000000..cab929fc1a --- /dev/null +++ b/capsules/extra/src/buzzer_pwm.rs @@ -0,0 +1,123 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Service capsule for a buzzer that uses a PWM pin. +//! +//! ## Instantiation +//! +//! Instantiate the capsule for use as a service capsule, using a virtual pwm buzzer +//! and a virtual alarm. For example: +//! +//! ```rust,ignore +//! # use kernel::static_init; +//! +//! let mux_pwm = static_init!( +//! capsules::virtual_pwm::MuxPwm<'static, nrf52833::pwm::Pwm>, +//! capsules::virtual_pwm::MuxPwm::new(&base_peripherals.pwm0) +//! ); +//! let virtual_pwm_buzzer = static_init!( +//! capsules::virtual_pwm::PwmPinUser<'static, nrf52833::pwm::Pwm>, +//! capsules::virtual_pwm::PwmPinUser::new( +//! mux_pwm, +//! nrf52833::pinmux::Pinmux::new(SPEAKER_PIN as u32) +//! ) +//! ); +//! virtual_pwm_buzzer.add_to_mux(); +//! +//! let virtual_alarm_buzzer = static_init!( +//! capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52833::rtc::Rtc>, +//! capsules::virtual_alarm::VirtualMuxAlarm::new(mux_alarm) +//! ); +//! virtual_alarm_buzzer.setup(); +//! +//! let pwm_buzzer = static_init!( +//! capsules::buzzer_pwm::PwmBuzzer< +//! 'static, +//! capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52833::rtc::Rtc>, +//! capsules::virtual_pwm::PwmPinUser<'static, nrf52833::pwm::Pwm>, +//! >, +//! capsules::buzzer_pwm::PwmBuzzer::new( +//! virtual_pwm_buzzer, +//! virtual_alarm_buzzer, +//! capsules::buzzer_pwm::DEFAULT_MAX_BUZZ_TIME_MS, +//! ) +//! ); +//! +//! pwm_buzzer.set_client(buzzer); +//! +//! virtual_alarm_buzzer.set_alarm_client(pwm_buzzer); +//! +//! ``` + +use core::cmp; + +use kernel::hil; +use kernel::hil::buzzer::BuzzerClient; +use kernel::hil::time::Frequency; +use kernel::utilities::cells::OptionalCell; +use kernel::ErrorCode; + +/// Standard max buzz time. +pub const DEFAULT_MAX_BUZZ_TIME_MS: usize = 5000; + +pub struct PwmBuzzer<'a, A: hil::time::Alarm<'a>, P: hil::pwm::PwmPin> { + /// The underlying PWM generator to make the buzzer buzz. + pwm_pin: &'a P, + /// Alarm to stop the PWM after some time. + alarm: &'a A, + /// Max buzz time. + max_duration_ms: usize, + /// The client currently using the service capsule. + client: OptionalCell<&'a dyn BuzzerClient>, +} + +impl<'a, A: hil::time::Alarm<'a>, P: hil::pwm::PwmPin> PwmBuzzer<'a, A, P> { + pub fn new(pwm_pin: &'a P, alarm: &'a A, max_duration_ms: usize) -> PwmBuzzer<'a, A, P> { + PwmBuzzer { + pwm_pin, + alarm, + client: OptionalCell::empty(), + max_duration_ms, + } + } +} + +impl<'a, A: hil::time::Alarm<'a>, P: hil::pwm::PwmPin> hil::buzzer::Buzzer<'a> + for PwmBuzzer<'a, A, P> +{ + fn set_client(&self, client: &'a dyn BuzzerClient) { + self.client.replace(client); + } + + fn buzz(&self, frequency_hz: usize, duration_ms: usize) -> Result<(), ErrorCode> { + let duration_ms_cmp = cmp::min(duration_ms, self.max_duration_ms); + self.pwm_pin + .start(frequency_hz, self.pwm_pin.get_maximum_duty_cycle() / 2)?; + + // Set an alarm for the given duration. + let interval = (duration_ms_cmp as u32) * ::frequency() / 1000; + self.alarm + .set_alarm(self.alarm.now(), A::Ticks::from(interval)); + Ok(()) + } + + fn stop(&self) -> Result<(), ErrorCode> { + // Disarm the current alarm and instantly fire another. + self.alarm.disarm()?; + // This method was used to reduce the size of the code. + self.alarm.set_alarm(self.alarm.now(), A::Ticks::from(0)); + Ok(()) + } +} + +impl<'a, A: hil::time::Alarm<'a>, P: hil::pwm::PwmPin> hil::time::AlarmClient + for PwmBuzzer<'a, A, P> +{ + fn alarm(&self) { + // Stop the pin output and signal that the buzzer has finished + // playing. + self.client + .map(|buzz_client| buzz_client.buzzer_done(self.pwm_pin.stop())); + } +} diff --git a/capsules/extra/src/can.rs b/capsules/extra/src/can.rs new file mode 100644 index 0000000000..9cb5418d93 --- /dev/null +++ b/capsules/extra/src/can.rs @@ -0,0 +1,536 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022 +// Copyright OxidOS Automotive SRL 2022 +// +// Author: Teona Severin + +//! Syscall driver capsule for CAN communication. +//! +//! This module has a CAN syscall driver capsule implementation. +//! +//! This capsule sends commands from the userspace to a driver that +//! implements the Can trait. +//! +//! The capsule shares 2 buffers with the userspace: one RO that is used +//! for transmitting messages and one RW that is used for receiving +//! messages. +//! +//! The RO buffer uses the first 4 bytes as a counter of how many messages +//! the userspace must read, at the time the upcall was sent. If the +//! userspace is slower and in the meantime there were other messages +//! that were received, the userspace reads them all and sends to the +//! capsule a new buffer that has the counter on the first 4 bytes 0. +//! Because of that, when receiving a callback from the driver regarding +//! a received message, the capsule checks the counter: +//! - if it's 0, the message will be copied to the RW buffer, the counter +//! will be incremented and an upcall will be sent +//! - if it's greater the 0, the message will be copied to the RW buffer +//! but no upcall will be done +//! +//! Usage +//! ----- +//! +//! You need a driver that implements the Can trait. +//! ```rust,ignore +//! let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); +//! let grant_can = self.board_kernel.create_grant( +//! capsules::can::CanCapsule::DRIVER_NUM, &grant_cap); +//! let can = capsules::can::CanCapsule::new( +//! can_peripheral, +//! grant_can, +//! tx_buffer, +//! rx_buffer, +//! ); +//! +//! kernel::hil::can::Controller::set_client(can_peripheral, Some(can)); +//! kernel::hil::can::Transmit::set_client(can_peripheral, Some(can)); +//! kernel::hil::can::Receive::set_client(can_peripheral, Some(can)); +//! ``` +//! + +use core::mem::size_of; + +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::hil::can; +use kernel::processbuffer::{ReadableProcessBuffer, WriteableProcessBuffer}; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::ErrorCode; +use kernel::ProcessId; + +use capsules_core::driver; +pub const DRIVER_NUM: usize = driver::NUM::Can as usize; +pub const BYTE4_MASK: usize = 0xff000000; +pub const BYTE3_MASK: usize = 0xff0000; +pub const BYTE2_MASK: usize = 0xff00; +pub const BYTE1_MASK: usize = 0xff; + +mod error_upcalls { + pub const ERROR_TX: usize = 100; + pub const ERROR_RX: usize = 101; +} + +mod up_calls { + pub const UPCALL_ENABLE: usize = 0; + pub const UPCALL_DISABLE: usize = 1; + pub const UPCALL_MESSAGE_SENT: usize = 2; + pub const UPCALL_MESSAGE_RECEIVED: usize = 3; + pub const UPCALL_RECEIVED_STOPPED: usize = 4; + pub const UPCALL_TRANSMISSION_ERROR: usize = 5; + pub const COUNT: u8 = 6; +} + +mod ro_allow { + pub const RO_ALLOW_BUFFER: usize = 0; + pub const COUNT: u8 = 1; +} + +mod rw_allow { + pub const RW_ALLOW_BUFFER: usize = 0; + pub const COUNT: u8 = 1; +} + +pub struct CanCapsule<'a, Can: can::Can> { + // CAN driver + can: &'a Can, + + // CAN buffers + can_tx: TakeCell<'static, [u8; can::STANDARD_CAN_PACKET_SIZE]>, + can_rx: TakeCell<'static, [u8; can::STANDARD_CAN_PACKET_SIZE]>, + + // Process + processes: Grant< + App, + UpcallCount<{ up_calls::COUNT }>, + AllowRoCount<{ ro_allow::COUNT }>, + AllowRwCount<{ rw_allow::COUNT }>, + >, + processid: OptionalCell, + + // Variable used to store the current state of the CAN peripheral + // during an `enable` or `disable` command. + peripheral_state: OptionalCell, +} + +#[derive(Default)] +pub struct App { + receive_index: usize, + lost_messages: u32, +} + +impl<'a, Can: can::Can> CanCapsule<'a, Can> { + pub fn new( + can: &'a Can, + grant: Grant< + App, + UpcallCount<{ up_calls::COUNT }>, + AllowRoCount<{ ro_allow::COUNT }>, + AllowRwCount<{ rw_allow::COUNT }>, + >, + can_tx: &'static mut [u8; can::STANDARD_CAN_PACKET_SIZE], + can_rx: &'static mut [u8; can::STANDARD_CAN_PACKET_SIZE], + ) -> CanCapsule<'a, Can> { + CanCapsule { + can, + can_tx: TakeCell::new(can_tx), + can_rx: TakeCell::new(can_rx), + processes: grant, + peripheral_state: OptionalCell::empty(), + processid: OptionalCell::empty(), + } + } + + fn schedule_callback(&self, callback_number: usize, data: (usize, usize, usize)) { + self.processid.map(|processid| { + let _ = self.processes.enter(processid, |_app, kernel_data| { + kernel_data + .schedule_upcall(callback_number, (data.0, data.1, data.2)) + .ok(); + }); + }); + } + + /// This function makes a copy of the buffer in the grant and sends it + /// to the low-level hardware, in order for it to be sent on the bus. + pub fn process_send_command( + &self, + processid: ProcessId, + id: can::Id, + length: usize, + ) -> Result<(), ErrorCode> { + self.processes + .enter(processid, |_, kernel_data| { + kernel_data + .get_readonly_processbuffer(ro_allow::RO_ALLOW_BUFFER) + .map_or_else( + |err| err.into(), + |buffer_ref| { + buffer_ref + .enter(|buffer| { + self.can_tx.take().map_or( + Err(ErrorCode::NOMEM), + |dest_buffer| { + for i in 0..length { + dest_buffer[i] = buffer[i].get(); + } + match self.can.send(id, dest_buffer, length) { + Ok(()) => Ok(()), + Err((err, buf)) => { + self.can_tx.replace(buf); + Err(err) + } + } + }, + ) + }) + .unwrap_or_else(|err| err.into()) + }, + ) + }) + .unwrap_or_else(|err| err.into()) + } + + pub fn is_valid_process(&self, processid: ProcessId) -> bool { + self.processid.map_or(true, |owning_process| { + self.processes + .enter(owning_process, |_, _| owning_process == processid) + .unwrap_or(true) + }) + } +} + +impl<'a, Can: can::Can> SyscallDriver for CanCapsule<'a, Can> { + fn command( + &self, + command_num: usize, + arg1: usize, + arg2: usize, + processid: ProcessId, + ) -> CommandReturn { + // This driver exists. + if command_num == 0 { + return CommandReturn::success(); + } + + // Check to see if the process or no process at all + // owns the capsule. Only one application can use the + // capsule at a time. + if !self.is_valid_process(processid) { + return CommandReturn::failure(ErrorCode::RESERVE); + } else { + self.processid.set(processid); + } + + match command_num { + // Set the bitrate + 1 => match self.can.set_bitrate(arg1 as u32) { + Ok(()) => CommandReturn::success(), + Err(err) => CommandReturn::failure(err), + }, + + // Set the operation mode (Loopback, Monitoring, etc) + 2 => { + match self.can.set_operation_mode(match arg1 { + 0 => can::OperationMode::Loopback, + 1 => can::OperationMode::Monitoring, + 2 => can::OperationMode::Freeze, + _ => can::OperationMode::Normal, + }) { + Ok(()) => CommandReturn::success(), + Err(err) => CommandReturn::failure(err), + } + } + + // Enable the peripheral + 3 => match self.can.enable() { + Ok(()) => CommandReturn::success(), + Err(err) => CommandReturn::failure(err), + }, + + // Disable the peripheral + 4 => match self.can.disable() { + Ok(()) => CommandReturn::success(), + Err(err) => CommandReturn::failure(err), + }, + + // Send a message with a 16-bit identifier + 5 => { + let id = can::Id::Standard(arg1 as u16); + self.processid + .map_or( + CommandReturn::failure(ErrorCode::BUSY), + |processid| match self.process_send_command(processid, id, arg2) { + Ok(()) => CommandReturn::success(), + Err(err) => CommandReturn::failure(err), + }, + ) + } + + // Send a message with a 32-bit identifier + 6 => { + let id = can::Id::Extended(arg1 as u32); + self.processid + .map_or( + CommandReturn::failure(ErrorCode::BUSY), + |processid| match self.process_send_command(processid, id, arg2) { + Ok(()) => CommandReturn::success(), + Err(err) => CommandReturn::failure(err), + }, + ) + } + + // Start receiving messages + 7 => { + self.can_rx + .take() + .map_or(CommandReturn::failure(ErrorCode::NOMEM), |dest_buffer| { + self.processes + .enter(processid, |_, kernel| { + match kernel.get_readwrite_processbuffer(0).map_or_else( + |err| err.into(), + |buffer_ref| { + buffer_ref + .enter(|buffer| { + // make sure that the receiving buffer can have at least + // 2 messages of 8 bytes each and 4 another bytes for the counter + if buffer.len() + >= 2 * can::STANDARD_CAN_PACKET_SIZE + + size_of::() + { + Ok(()) + } else { + Err(ErrorCode::SIZE) + } + }) + .unwrap_or_else(|err| err.into()) + }, + ) { + Ok(()) => match self.can.start_receive_process(dest_buffer) { + Ok(()) => CommandReturn::success(), + Err((err, _)) => CommandReturn::failure(err), + }, + Err(err) => CommandReturn::failure(err), + } + }) + .unwrap_or_else(|err| err.into()) + }) + } + + // Stop receiving messages + 8 => match self.can.stop_receive() { + Ok(()) => CommandReturn::success(), + Err(err) => CommandReturn::failure(err), + }, + + // Set the timing parameters + 9 => { + match self.can.set_bit_timing(can::BitTiming { + segment1: ((arg1 & BYTE4_MASK) >> 24) as u8, + segment2: ((arg1 & BYTE3_MASK) >> 16) as u8, + propagation: arg2 as u8, + sync_jump_width: ((arg1 & BYTE2_MASK) >> 8) as u32, + baud_rate_prescaler: (arg1 & BYTE1_MASK) as u32, + }) { + Ok(()) => CommandReturn::success(), + Err(err) => CommandReturn::failure(err), + } + } + + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, process_id: ProcessId) -> Result<(), kernel::process::Error> { + self.processes.enter(process_id, |_, _| {}) + } +} + +impl<'a, Can: can::Can> can::ControllerClient for CanCapsule<'a, Can> { + // This callback must be called after an `enable` or `disable` command was sent. + // It stores the new state of the peripheral. + fn state_changed(&self, state: can::State) { + self.peripheral_state.replace(state); + } + + // This callback must be called after an `enable` command was sent and after a + // `state_changed` callback was called. If there is no error and the state of + // the peripheral is Running, send to the userspace a success callback. + // If the state is different or the status is an error, send to the userspace an + // error callback. + fn enabled(&self, status: Result<(), ErrorCode>) { + match status { + Ok(()) => match self.peripheral_state.take() { + Some(can::State::Running) => { + self.schedule_callback(up_calls::UPCALL_ENABLE, (0, 0, 0)); + } + Some(can::State::Error(err)) => { + self.schedule_callback(up_calls::UPCALL_ENABLE, (err as usize, 0, 0)); + } + Some(can::State::Disabled) | None => { + self.schedule_callback( + up_calls::UPCALL_ENABLE, + (ErrorCode::OFF as usize, 0, 0), + ); + } + }, + Err(err) => { + self.peripheral_state.take(); + self.schedule_callback(up_calls::UPCALL_ENABLE, (err as usize, 0, 0)); + } + } + } + + // This callback must be called after an `disable` command was sent and after a + // `state_changed` callback was called. If there is no error and the state of + // the peripheral is Disabled, send to the userspace a success callback. + // If the state is different or the status is an error, send to the userspace an + // error callback. + fn disabled(&self, status: Result<(), ErrorCode>) { + match status { + Ok(()) => match self.peripheral_state.take() { + Some(can::State::Disabled) => { + self.schedule_callback(up_calls::UPCALL_DISABLE, (0, 0, 0)); + } + Some(can::State::Error(err)) => { + self.schedule_callback(up_calls::UPCALL_DISABLE, (err as usize, 0, 0)); + } + Some(can::State::Running) | None => { + self.schedule_callback( + up_calls::UPCALL_DISABLE, + (ErrorCode::FAIL as usize, 0, 0), + ); + } + }, + Err(err) => { + self.peripheral_state.take(); + self.schedule_callback(up_calls::UPCALL_ENABLE, (err as usize, 0, 0)); + } + } + self.processid.clear(); + } +} + +impl<'a, Can: can::Can> can::TransmitClient<{ can::STANDARD_CAN_PACKET_SIZE }> + for CanCapsule<'a, Can> +{ + // This callback is called when the hardware acknowledges that a message + // was sent. This callback also makes an upcall to the userspace. + fn transmit_complete( + &self, + status: Result<(), can::Error>, + buffer: &'static mut [u8; can::STANDARD_CAN_PACKET_SIZE], + ) { + self.can_tx.replace(buffer); + match status { + Ok(()) => self.schedule_callback(up_calls::UPCALL_MESSAGE_SENT, (0, 0, 0)), + Err(err) => { + self.schedule_callback( + up_calls::UPCALL_TRANSMISSION_ERROR, + (error_upcalls::ERROR_TX, err as usize, 0), + ); + } + } + } +} + +impl<'a, Can: can::Can> can::ReceiveClient<{ can::STANDARD_CAN_PACKET_SIZE }> + for CanCapsule<'a, Can> +{ + // This callback is called when a new message is received on any receiving + // fifo. + fn message_received( + &self, + id: can::Id, + buffer: &mut [u8; can::STANDARD_CAN_PACKET_SIZE], + len: usize, + status: Result<(), can::Error>, + ) { + let mut new_buffer = false; + let mut shared_len = 0; + match status { + Ok(()) => { + match self.processid.map_or(Err(ErrorCode::NOMEM), |processid| { + self.processes + .enter(processid, |app_data, kernel_data| { + kernel_data + .get_readwrite_processbuffer(rw_allow::RW_ALLOW_BUFFER) + .map_or_else( + |err| err.into(), + |buffer_ref| { + buffer_ref + .mut_enter(|user_buffer| { + shared_len = user_buffer.len(); + // For now, the first 4 bytes (the size of u32) represent the number + // of messages that the user has not read yet, represented as Little Endian. + // When the userspace reads the buffer, the counter will be set + // to 0 so that the capsule knows. This will be changed after + // https://github.com/tock/tock/pull/3252 and + // https://github.com/tock/tock/pull/3258 are merged. + let mut tmp_buf: [u8; size_of::()] = + [0; size_of::()]; + user_buffer[0..size_of::()] + .copy_to_slice(&mut tmp_buf); + let contor = u32::from_le_bytes(tmp_buf); + if contor == 0 { + new_buffer = true; + app_data.receive_index = size_of::(); + } + user_buffer[0..size_of::()] + .copy_from_slice(&(contor + 1).to_le_bytes()); + if app_data.receive_index + len > user_buffer.len() + { + app_data.lost_messages += 1; + Err(ErrorCode::SIZE) + } else { + let r = user_buffer[app_data.receive_index + ..app_data.receive_index + len] + .copy_from_slice_or_err(&buffer[0..len]); + if r.is_ok() { + app_data.receive_index += len; + } + r + } + }) + .unwrap_or_else(|err| err.into()) + }, + ) + }) + .unwrap_or_else(|err| err.into()) + }) { + Err(err) => self.schedule_callback( + up_calls::UPCALL_TRANSMISSION_ERROR, + (error_upcalls::ERROR_RX, err as usize, 0), + ), + Ok(()) => { + if new_buffer { + self.schedule_callback( + up_calls::UPCALL_MESSAGE_RECEIVED, + ( + 0, + shared_len, + match id { + can::Id::Standard(u16) => u16 as usize, + can::Id::Extended(u32) => u32 as usize, + }, + ), + ) + } + } + } + } + Err(err) => { + let kernel_err: ErrorCode = err.into(); + self.schedule_callback( + up_calls::UPCALL_TRANSMISSION_ERROR, + (error_upcalls::ERROR_RX, kernel_err.into(), 0), + ) + } + }; + } + + fn stopped(&self, buffer: &'static mut [u8; can::STANDARD_CAN_PACKET_SIZE]) { + self.can_rx.replace(buffer); + self.schedule_callback(up_calls::UPCALL_RECEIVED_STOPPED, (0, 0, 0)); + } +} diff --git a/capsules/extra/src/ccs811.rs b/capsules/extra/src/ccs811.rs new file mode 100644 index 0000000000..2e682a98b8 --- /dev/null +++ b/capsules/extra/src/ccs811.rs @@ -0,0 +1,324 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! SyscallDriver for the AMS CCS811 an ultra-low power digital gas sensor +//! solution which integrates a metal oxide (MOX) gas sensor to detect a wide +//! range of Volatile Organic Compounds (VOCs) for indoor air quality +//! monitoring using the I2C bus. +//! +//! +//! + +use core::cell::Cell; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; +use kernel::hil::i2c::{self, I2CClient, I2CDevice}; +use kernel::hil::sensors::{AirQualityClient, AirQualityDriver}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::ErrorCode; + +const STATUS: u8 = 0x00; +const MEAS_MODE: u8 = 0x01; +const ALG_RESULT_DATA: u8 = 0x02; +#[allow(dead_code)] +const RAW_DATA: u8 = 0x03; +#[allow(dead_code)] +const ENV_DATA: u8 = 0x05; +#[allow(dead_code)] +const NTC: u8 = 0x06; +#[allow(dead_code)] +const THRESHOLDS: u8 = 0x10; +#[allow(dead_code)] +const BASELINE: u8 = 0x11; +const HW_ID: u8 = 0x20; +#[allow(dead_code)] +const HW_VERSION: u8 = 0x21; +#[allow(dead_code)] +const FW_BOOT_VERSION: u8 = 0x23; +#[allow(dead_code)] +const FW_APP_VERSION: u8 = 0x24; +#[allow(dead_code)] +const ERROR_ID: u8 = 0xE0; +const APP_START: u8 = 0xF4; +const SW_RESET: u8 = 0xFF; + +#[derive(Clone, Copy, PartialEq)] +enum DeviceState { + Identify, + Reset, + StatusCheck, + StartApp, + Normal, +} + +#[allow(dead_code)] +#[derive(Clone, Copy, PartialEq)] +enum Operation { + None, + Setup, + SetEnv, + CO2, + TVOC, +} + +pub struct Ccs811<'a> { + buffer: TakeCell<'static, [u8]>, + i2c: &'a dyn I2CDevice, + client: OptionalCell<&'a dyn AirQualityClient>, + state: Cell, + op: Cell, + + /// Deferred caller for deferring client callbacks. + deferred_call: DeferredCall, + deferred_count: Cell, +} + +impl<'a> Ccs811<'a> { + pub fn new(i2c: &'a dyn I2CDevice, buffer: &'static mut [u8]) -> Self { + Self { + buffer: TakeCell::new(buffer), + i2c, + client: OptionalCell::empty(), + state: Cell::new(DeviceState::Identify), + op: Cell::new(Operation::Setup), + deferred_call: DeferredCall::new(), + deferred_count: Cell::new(0), + } + } + + pub fn startup(&self) { + self.buffer.take().map(|buffer| { + if self.state.get() == DeviceState::Identify { + // Read the ID buffer + buffer[0] = HW_ID; + self.i2c.write_read(buffer, 1, 1).unwrap(); + } + }); + } +} + +impl<'a> AirQualityDriver<'a> for Ccs811<'a> { + fn set_client(&self, client: &'a dyn AirQualityClient) { + self.client.set(client); + } + + fn specify_environment( + &self, + temp: Option, + humidity: Option, + ) -> Result<(), ErrorCode> { + if self.state.get() != DeviceState::Normal { + return Err(ErrorCode::BUSY); + } + + if self.op.get() != Operation::None { + return Err(ErrorCode::BUSY); + } + + self.buffer.take().map(|buffer| { + // Set the default values of 50% humidity and 25 degrees Celsius + buffer[0] = 0x05; + buffer[1] = 0x64; + buffer[2] = 0x00; + buffer[3] = 0x64; + buffer[4] = 0x00; + + // Copy in our calibration data + if let Some(hum) = humidity { + buffer[1] = hum as u8 * 2; + } + if let Some(t) = temp { + if t < -25 { + buffer[3] = 0; + } else { + buffer[3] = (t as u8 + 25) * 2; + } + } + + self.op.set(Operation::SetEnv); + self.i2c.write(buffer, 5).unwrap(); + }); + + Ok(()) + } + + fn read_co2(&self) -> Result<(), ErrorCode> { + if self.state.get() != DeviceState::Normal { + return Err(ErrorCode::BUSY); + } + + if self.op.get() != Operation::None { + return Err(ErrorCode::BUSY); + } + + self.buffer.take().map(|buffer| { + buffer[0] = ALG_RESULT_DATA; + + self.op.set(Operation::CO2); + self.i2c.write_read(buffer, 1, 6).unwrap(); + }); + + Ok(()) + } + + fn read_tvoc(&self) -> Result<(), ErrorCode> { + if self.state.get() != DeviceState::Normal { + return Err(ErrorCode::BUSY); + } + + if self.op.get() != Operation::None { + return Err(ErrorCode::BUSY); + } + + self.buffer.take().map(|buffer| { + buffer[0] = ALG_RESULT_DATA; + + self.op.set(Operation::TVOC); + self.i2c.write_read(buffer, 1, 6).unwrap(); + }); + + Ok(()) + } +} + +impl<'a> I2CClient for Ccs811<'a> { + fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) { + if status.is_err() { + match self.op.get() { + Operation::None | Operation::Setup => (), + Operation::SetEnv => { + self.client + .map(|client| client.environment_specified(Err(ErrorCode::FAIL))); + } + Operation::CO2 => { + self.client + .map(|client| client.co2_data_available(Err(ErrorCode::FAIL))); + } + Operation::TVOC => { + self.client + .map(|client| client.tvoc_data_available(Err(ErrorCode::FAIL))); + } + } + self.buffer.replace(buffer); + self.op.set(Operation::None); + return; + } + + match self.state.get() { + DeviceState::Identify => { + if buffer[0] != 0x81 { + // We don't have the correct ID, this isn't the correct device + // Just stop here + self.buffer.replace(buffer); + return; + } + + buffer[0] = SW_RESET; + buffer[1] = 0x11; + buffer[2] = 0xE5; + buffer[3] = 0x72; + buffer[4] = 0x8A; + self.i2c.write(buffer, 5).unwrap(); + self.state.set(DeviceState::Reset); + } + DeviceState::Reset => { + self.deferred_call.set(); + self.buffer.replace(buffer); + } + DeviceState::StatusCheck => { + if buffer[0] & 0x01 == 0x01 { + self.buffer.replace(buffer); + return; + } + + if buffer[0] & 0x04 == 0x04 { + self.buffer.replace(buffer); + return; + } + + buffer[0] = APP_START; + self.i2c.write(buffer, 1).unwrap(); + self.state.set(DeviceState::StartApp); + } + DeviceState::StartApp => { + buffer[0] = MEAS_MODE; + // Drive mode: 1 - Constant power mode, IAQ measurement every second + // Interrupt data ready: 0 - Interrupt generation is disabled + // Interrup Threshold: 0 - Interrupt mode operates normally + buffer[1] = (1 << 4) | (0 << 3) | (0 << 2); + self.i2c.write(buffer, 2).unwrap(); + + self.state.set(DeviceState::Normal); + } + DeviceState::Normal => { + match self.op.get() { + Operation::None => (), + Operation::Setup => { + self.buffer.replace(buffer); + self.deferred_call.set(); + return; + } + Operation::SetEnv => { + self.client + .map(|client| client.environment_specified(Ok(()))); + } + Operation::CO2 => { + let co2 = (buffer[0] as u32) << 8 | buffer[1] as u32; + let status = buffer[4]; + let _error_id = buffer[5]; + + if status & 0x01 == 0x01 { + self.client + .map(|client| client.co2_data_available(Err(ErrorCode::FAIL))); + } + + self.client.map(|client| client.co2_data_available(Ok(co2))); + } + Operation::TVOC => { + let tvoc = (buffer[2] as u32) << 8 | buffer[3] as u32; + let status = buffer[4]; + let _error_id = buffer[5]; + + if status & 0x01 == 0x01 { + self.client + .map(|client| client.tvoc_data_available(Err(ErrorCode::FAIL))); + } + + self.client + .map(|client| client.tvoc_data_available(Ok(tvoc))); + } + } + self.buffer.replace(buffer); + self.op.set(Operation::None); + } + } + } +} + +impl<'a> DeferredCallClient for Ccs811<'a> { + fn handle_deferred_call(&self) { + if self.deferred_count.get() > 1000 { + match self.state.get() { + DeviceState::Reset => { + self.buffer.take().map(|buffer| { + buffer[0] = STATUS; + self.i2c.write_read(buffer, 1, 1).unwrap(); + + self.state.set(DeviceState::StatusCheck); + }); + } + DeviceState::Normal => { + self.op.set(Operation::None); + } + _ => unreachable!(), + } + } else { + self.deferred_count.set(self.deferred_count.get() + 1); + self.deferred_call.set(); + } + } + + fn register(&'static self) { + self.deferred_call.register(self); + } +} diff --git a/capsules/src/crc.rs b/capsules/extra/src/crc.rs similarity index 96% rename from capsules/src/crc.rs rename to capsules/extra/src/crc.rs index 70fabe73ca..f8b31ac6ab 100644 --- a/capsules/src/crc.rs +++ b/capsules/extra/src/crc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides userspace access to a Crc unit. //! //! ## Instantiation @@ -7,7 +11,7 @@ //! client of the hardware implementation. For example, using the SAM4L's `CrcU` //! driver: //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! let crc_buffer = static_init!([u8; 64], [0; 64]); @@ -63,7 +67,7 @@ //! __Polynomial__: `0x04C11DB7` //! //! This algorithm uses the same polynomial as `Crc-32`, but does no post- -//! processing on the output value. It can be perfomed purely in hardware on +//! processing on the output value. It can be performed purely in hardware on //! the SAM4L. //! //! ### SAM4L-32C @@ -83,11 +87,11 @@ use kernel::processbuffer::{ReadableProcessBuffer, ReadableProcessSlice}; use kernel::syscall::{CommandReturn, SyscallDriver}; use kernel::utilities::cells::NumericCellExt; use kernel::utilities::cells::{OptionalCell, TakeCell}; -use kernel::utilities::leasable_buffer::LeasableBuffer; +use kernel::utilities::leasable_buffer::SubSliceMut; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::Crc as usize; pub const DEFAULT_CRC_BUF_LENGTH: usize = 256; @@ -95,7 +99,7 @@ pub const DEFAULT_CRC_BUF_LENGTH: usize = 256; mod ro_allow { pub const BUFFER: usize = 0; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; + pub const COUNT: u8 = 1; } /// An opaque value maintaining state for one application's request @@ -127,7 +131,7 @@ impl<'a, C: Crc<'a>> CrcDriver<'a, C> { /// /// ## Example /// - /// ```rust + /// ```rust,ignore /// capsules::crc::Crc::new(&sam4l::crccu::CrcCU, board_kernel.create_grant(&grant_cap)); /// ``` /// @@ -152,7 +156,7 @@ impl<'a, C: Crc<'a>> CrcDriver<'a, C> { kbuffer[i] = data[i].get(); } if copy_len > 0 { - let mut leasable = LeasableBuffer::new(kbuffer); + let mut leasable = SubSliceMut::new(kbuffer); leasable.slice(0..copy_len); let res = self.crc.input(leasable); match res { @@ -171,7 +175,7 @@ impl<'a, C: Crc<'a>> CrcDriver<'a, C> { // Start a new request. Return Ok(()) if one started, Err(FAIL) if not. // Issue callbacks for any requests that are invalid, either because - // they are zero-length or requested an invalid algoritm. + // they are zero-length or requested an invalid algorithm. fn next_request(&self) -> Result<(), ErrorCode> { self.app_buffer_written.set(0); for process in self.grant.iter() { @@ -362,7 +366,7 @@ impl<'a, C: Crc<'a>> SyscallDriver for CrcDriver<'a, C> { if self.current_process.is_none() { self.next_request().map_or_else( |e| CommandReturn::failure(ErrorCode::into(e)), - |_| CommandReturn::success(), + |()| CommandReturn::success(), ) } else { // Another request is ongoing. We've enqueued this one, @@ -383,13 +387,13 @@ impl<'a, C: Crc<'a>> SyscallDriver for CrcDriver<'a, C> { } impl<'a, C: Crc<'a>> Client for CrcDriver<'a, C> { - fn input_done(&self, result: Result<(), ErrorCode>, buffer: LeasableBuffer<'static, u8>) { + fn input_done(&self, result: Result<(), ErrorCode>, buffer: SubSliceMut<'static, u8>) { // A call to `input` has finished. This can mean that either // we have processed the entire buffer passed in, or it was // truncated by the CRC unit as it was too large. In the first // case, we can see whether there is more outstanding data // from the app, whereas in the latter we need to advance the - // LeasableBuffer window and pass it in again. + // SubSliceMut window and pass it in again. let mut computing = false; // There are three outcomes to this match: // - crc_buffer is not put back: input is ongoing @@ -402,7 +406,7 @@ impl<'a, C: Crc<'a>> Client for CrcDriver<'a, C> { // Put the kernel buffer back self.crc_buffer.replace(buffer.take()); self.current_process.map(|pid| { - let _res = self.grant.enter(*pid, |grant, kernel_data| { + let _res = self.grant.enter(pid, |grant, kernel_data| { // This shouldn't happen unless there's a way to clear out a request // through a system call: regardless, the request is gone, so cancel // the CRC. @@ -497,7 +501,7 @@ impl<'a, C: Crc<'a>> Client for CrcDriver<'a, C> { Err((e, returned_buffer)) => { self.crc_buffer.replace(returned_buffer.take()); self.current_process.map(|pid| { - let _res = self.grant.enter(*pid, |grant, kernel_data| { + let _res = self.grant.enter(pid, |grant, kernel_data| { grant.request = None; kernel_data .schedule_upcall( @@ -515,7 +519,7 @@ impl<'a, C: Crc<'a>> Client for CrcDriver<'a, C> { // The callback returned an error, pass it back to userspace self.crc_buffer.replace(buffer.take()); self.current_process.map(|pid| { - let _res = self.grant.enter(*pid, |grant, kernel_data| { + let _res = self.grant.enter(pid, |grant, kernel_data| { grant.request = None; kernel_data .schedule_upcall(0, (kernel::errorcode::into_statuscode(Err(e)), 0, 0)) diff --git a/capsules/extra/src/cycle_count.rs b/capsules/extra/src/cycle_count.rs new file mode 100644 index 0000000000..6af4b9f549 --- /dev/null +++ b/capsules/extra/src/cycle_count.rs @@ -0,0 +1,113 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Provides a cycle counter interface for userspace. +//! +//! Usage +//! ----- +//! +//! This capsule is intended for debug purposes. However, to ensure that use +//! by multiple apps does not lead to innacurate results, basic virtualization +//! is implemented: only the first app to start the cycle counter can start or +//! stop or reset the counter. Other apps are restricted to reading the counter +//! (which can be useful for debugging the time required by cross-process routines). + +/// Syscall driver number. +use capsules_core::driver; +pub const DRIVER_NUM: usize = driver::NUM::CycleCount as usize; + +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::utilities::cells::OptionalCell; +use kernel::{hil, ErrorCode, ProcessId}; + +#[derive(Default)] +pub struct App; + +pub struct CycleCount<'a, P: hil::hw_debug::CycleCounter> { + counters: &'a P, + apps: Grant, AllowRoCount<0>, AllowRwCount<0>>, + controlling_app: OptionalCell, +} + +impl<'a, P: hil::hw_debug::CycleCounter> CycleCount<'a, P> { + pub fn new( + counters: &'a P, + grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, + ) -> Self { + Self { + counters, + apps: grant, + controlling_app: OptionalCell::empty(), + } + } +} + +impl<'a, P: hil::hw_debug::CycleCounter> SyscallDriver for CycleCount<'a, P> { + /// Control the CycleCount system. + /// + /// ### `command_num` + /// + /// - `0`: Driver check. + /// - `1`: Start the cycle counter. + /// - `2`: Get current cycle count. + /// - `3`: Reset and stop the cycle counter. + /// - `4`: Stop the cycle counter. + fn command( + &self, + command_num: usize, + _data: usize, + _: usize, + processid: ProcessId, + ) -> CommandReturn { + let try_claim_driver = || { + let match_or_empty_or_nonexistant = + self.controlling_app.map_or(true, |controlling_app| { + self.apps + .enter(controlling_app, |_, _| controlling_app == processid) + .unwrap_or(true) + }); + if match_or_empty_or_nonexistant { + self.controlling_app.set(processid); + true + } else { + false + } + }; + match command_num { + 0 => CommandReturn::success(), + + 1 => { + if try_claim_driver() { + self.counters.start(); + CommandReturn::success() + } else { + CommandReturn::failure(ErrorCode::RESERVE) + } + } + 2 => CommandReturn::success_u64(self.counters.count()), + 3 => { + if try_claim_driver() { + self.counters.reset(); + CommandReturn::success() + } else { + CommandReturn::failure(ErrorCode::RESERVE) + } + } + 4 => { + if try_claim_driver() { + self.counters.stop(); + CommandReturn::success() + } else { + CommandReturn::failure(ErrorCode::RESERVE) + } + } + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { + self.apps.enter(processid, |_, _| {}) + } +} diff --git a/capsules/src/dac.rs b/capsules/extra/src/dac.rs similarity index 74% rename from capsules/src/dac.rs rename to capsules/extra/src/dac.rs index c2a2a303b3..f6df436a70 100644 --- a/capsules/src/dac.rs +++ b/capsules/extra/src/dac.rs @@ -1,9 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides a DAC interface for userspace. //! //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! let dac = static_init!( @@ -12,7 +16,7 @@ //! ``` /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::Dac as usize; use kernel::hil; @@ -25,7 +29,7 @@ pub struct Dac<'a> { impl<'a> Dac<'a> { pub fn new(dac: &'a dyn hil::dac::DacChannel) -> Dac<'a> { - Dac { dac: dac } + Dac { dac } } } @@ -34,15 +38,15 @@ impl SyscallDriver for Dac<'_> { /// /// ### `command_num` /// - /// - `0`: Driver check. + /// - `0`: Driver existence check. /// - `1`: Initialize and enable the DAC. /// - `2`: Set the output to `data1`, a scaled output value. fn command(&self, command_num: usize, data: usize, _: usize, _: ProcessId) -> CommandReturn { match command_num { - 0 /* check if present */ => CommandReturn::success(), + 0 => CommandReturn::success(), - // enable the dac - 1 => CommandReturn::from(self.dac.initialize()), + // enable the dac. no-op as using the dac will enable it. + 1 => CommandReturn::success(), // set the dac output 2 => CommandReturn::from(self.dac.set_value(data)), diff --git a/capsules/extra/src/date_time.rs b/capsules/extra/src/date_time.rs new file mode 100644 index 0000000000..b498cd7568 --- /dev/null +++ b/capsules/extra/src/date_time.rs @@ -0,0 +1,349 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Real Time Clock driver +//! +//! Allows handling of the current date and time +//! +//! Authors: Irina Bradu +//! Remus Rughinis +//! +//! Usage +//! ----- +//! +//! ```rust,ignore +//! let grant_dt = create_capability!(capabilities::MemoryAllocationCapability); +//! let grant_date_time = board_kernel.create_grant(capsules::date_time::DRIVER_NUM, &grant_dt); +//! +//! let date_time = static_init!( +//! capsules::date_time::DateTime<'static>, +//! capsules::date_time::DateTime::new(&peripherals.rtc, grant_date_time) +//! ); +//! kernel::hil::date_time::DateTime::set_client(&peripherals.rtc, date_time); +//! ``` +//! +//! A DateTimeValues structure can be transformed to and from a u32 tuple in the following way: +//! first number (year, month, day_of_the_month): +//! -last 5 bits store the day_of_the_month +//! -previous 4 bits store the month +//! -previous 12 bits store the year +//! second number (day_of_the_week, hour, minute, seconds): +//! -last 6 bits store the seconds +//! -previous 6 store the minute +//! -previous 5 store the hour +//! -previous 3 store the day_of_the_week + +use capsules_core::driver::NUM; +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::hil::date_time; + +use kernel::errorcode::into_statuscode; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::utilities::cells::OptionalCell; +use kernel::{ErrorCode, ProcessId}; + +pub const DRIVER_NUM: usize = NUM::DateTime as usize; + +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum DateTimeCommand { + ReadDateTime, + SetDateTime(u32, u32), +} + +#[derive(Default)] +pub struct AppData { + task: Option, +} + +pub struct DateTimeCapsule<'a, DateTime: date_time::DateTime<'a>> { + date_time: &'a DateTime, + apps: Grant, AllowRoCount<0>, AllowRwCount<0>>, + in_progress: OptionalCell, +} + +fn month_as_u32(month: date_time::Month) -> u32 { + match month { + date_time::Month::January => 1, + date_time::Month::February => 2, + date_time::Month::March => 3, + date_time::Month::April => 4, + date_time::Month::May => 5, + date_time::Month::June => 6, + date_time::Month::July => 7, + date_time::Month::August => 8, + date_time::Month::September => 9, + date_time::Month::October => 10, + date_time::Month::November => 11, + date_time::Month::December => 12, + } +} + +fn u32_as_month(month_num: u32) -> Result { + match month_num { + 1 => Ok(date_time::Month::January), + 2 => Ok(date_time::Month::February), + 3 => Ok(date_time::Month::March), + 4 => Ok(date_time::Month::April), + 5 => Ok(date_time::Month::May), + 6 => Ok(date_time::Month::June), + 7 => Ok(date_time::Month::July), + 8 => Ok(date_time::Month::August), + 9 => Ok(date_time::Month::September), + 10 => Ok(date_time::Month::October), + 11 => Ok(date_time::Month::November), + 12 => Ok(date_time::Month::December), + _ => Err(ErrorCode::INVAL), + } +} + +fn dotw_as_u32(dotw: date_time::DayOfWeek) -> u32 { + match dotw { + date_time::DayOfWeek::Sunday => 0, + date_time::DayOfWeek::Monday => 1, + date_time::DayOfWeek::Tuesday => 2, + date_time::DayOfWeek::Wednesday => 3, + date_time::DayOfWeek::Thursday => 4, + date_time::DayOfWeek::Friday => 5, + date_time::DayOfWeek::Saturday => 6, + } +} + +fn u32_as_dotw(dotw_num: u32) -> Result { + match dotw_num { + 0 => Ok(date_time::DayOfWeek::Sunday), + 1 => Ok(date_time::DayOfWeek::Monday), + 2 => Ok(date_time::DayOfWeek::Tuesday), + 3 => Ok(date_time::DayOfWeek::Wednesday), + 4 => Ok(date_time::DayOfWeek::Thursday), + 5 => Ok(date_time::DayOfWeek::Friday), + 6 => Ok(date_time::DayOfWeek::Saturday), + _ => Err(ErrorCode::INVAL), + } +} + +/// Transforms two u32 numbers into a DateTimeValues structure (year, month, dotm, dotw, hour, minute, seconds) +/// Check file documentation for details on how the u32 tuple stores data +fn date_from_u32_tuple(date: u32, time: u32) -> Result { + let month_num = date % (1 << 9) / (1 << 5); + let month_name = u32_as_month(month_num)?; + + let dotw_num = time % (1 << 20) / (1 << 17); + let dotw_name = u32_as_dotw(dotw_num)?; + + let date_result = date_time::DateTimeValues { + year: (date % (1 << 21) / (1 << 9)) as u16, + month: month_name, + day: (date % (1 << 5)) as u8, + + day_of_week: dotw_name, + hour: (time % (1 << 17) / (1 << 12)) as u8, + minute: (time % (1 << 12) / (1 << 6)) as u8, + seconds: (time % (1 << 6)) as u8, + }; + + if !(date_result.day <= 31) { + return Err(ErrorCode::INVAL); + } + if !(date_result.hour <= 24) { + return Err(ErrorCode::INVAL); + } + if !(date_result.minute <= 60) { + return Err(ErrorCode::INVAL); + } + if !(date_result.seconds <= 60) { + return Err(ErrorCode::INVAL); + } + + Ok(date_result) +} + +/// Transforms DateTimeValues structure (year, month, dotm, dotw, hour, minute, seconds) into two u32 numbers +/// Check file documentation for details on how the u32 numbers stores data +/// The two u32 numbers are returned as a tuple +fn date_as_u32_tuple(set_date: date_time::DateTimeValues) -> Result<(u32, u32), ErrorCode> { + let month = month_as_u32(set_date.month); + let dotw = dotw_as_u32(set_date.day_of_week); + + let date = + set_date.year as u32 * (1 << 9) as u32 + month * (1 << 5) as u32 + set_date.day as u32; + let time = dotw * (1 << 17) as u32 + + set_date.hour as u32 * (1 << 12) as u32 + + set_date.minute as u32 * (1 << 6) as u32 + + set_date.seconds as u32; + + Ok((date, time)) +} + +impl<'a, DateTime: date_time::DateTime<'a>> DateTimeCapsule<'a, DateTime> { + pub fn new( + date_time: &'a DateTime, + grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, + ) -> DateTimeCapsule<'a, DateTime> { + DateTimeCapsule { + date_time, + apps: grant, + in_progress: OptionalCell::empty(), + } + } + + fn call_driver(&self, command: DateTimeCommand, processid: ProcessId) -> Result<(), ErrorCode> { + match command { + DateTimeCommand::ReadDateTime => { + let date_result = self.date_time.get_date_time(); + match date_result { + Result::Ok(()) => { + self.in_progress.set(processid); + Ok(()) + } + Result::Err(e) => Err(e), + } + } + DateTimeCommand::SetDateTime(r2, r3) => { + let date = match date_from_u32_tuple(r2, r3) { + Result::Ok(d) => d, + Result::Err(e) => { + return Err(e); + } + }; + + let get_date_result = self.date_time.set_date_time(date); + + match get_date_result { + Result::Ok(()) => { + self.in_progress.set(processid); + Ok(()) + } + Result::Err(e) => Err(e), + } + } + } + } + + fn enqueue_command(&self, command: DateTimeCommand, processid: ProcessId) -> CommandReturn { + let grant_enter_res = self.apps.enter(processid, |app, _| { + if !(app.task.is_none()) { + CommandReturn::failure(ErrorCode::BUSY) + } else { + app.task = Some(command); + CommandReturn::success() + } + }); + + // If no command is currently run, run the current command + if self.in_progress.is_none() { + match grant_enter_res { + Ok(_) => match self.call_driver(command, processid) { + Ok(()) => CommandReturn::success(), + Err(e) => CommandReturn::failure(e), + }, + Err(_e) => CommandReturn::failure(ErrorCode::FAIL), + } + } else { + match grant_enter_res { + Ok(_) => CommandReturn::success(), + Err(_e) => CommandReturn::failure(ErrorCode::FAIL), + } + } + } + + fn queue_next_command(&self) { + self.apps.iter().find_map(|grant| { + let processid = grant.processid(); + grant.enter(|app, kernel| { + app.task.map_or(None, |command| { + let command_return = self.call_driver(command, processid); + match command_return { + Ok(()) => Some(()), + Err(e) => { + let upcall_status = into_statuscode(Err(e)); + kernel.schedule_upcall(0, (upcall_status, 0, 0)).ok(); + None + } + } + }) + }) + }); + } +} + +impl<'a, DateTime: date_time::DateTime<'a>> date_time::DateTimeClient + for DateTimeCapsule<'a, DateTime> +{ + fn get_date_time_done(&self, datetime: Result) { + self.in_progress.clear(); + let mut upcall_status: usize = into_statuscode(Ok(())); + let mut upcall_r1: usize = 0; + let mut upcall_r2: usize = 0; + + for cntr in self.apps.iter() { + cntr.enter(|app, upcalls| { + if app.task == Some(DateTimeCommand::ReadDateTime) { + app.task = None; + match datetime { + Result::Ok(date) => { + let (year_month_dotm, dotw_hour_min_sec) = match date_as_u32_tuple(date) + { + Result::Ok(t) => t, + Result::Err(e) => { + upcall_status = into_statuscode(Result::Err(e)); + (0, 0) + } + }; + + upcall_r1 = year_month_dotm as usize; + upcall_r2 = dotw_hour_min_sec as usize; + } + Result::Err(e) => { + upcall_status = into_statuscode(Result::Err(e)); + } + } + + upcalls + .schedule_upcall(0, (upcall_status, upcall_r1, upcall_r2)) + .ok(); + } + }); + } + + self.queue_next_command(); + } + + fn set_date_time_done(&self, result: Result<(), ErrorCode>) { + // in_progress.take() also sets the OptionalCell to None + let processid = self.in_progress.take().unwrap(); + let _enter_grant = self.apps.enter(processid, |app, upcalls| { + app.task = None; + + upcalls + .schedule_upcall(0, (into_statuscode(result), 0, 0)) + .ok(); + }); + + self.queue_next_command(); + } +} + +impl<'a, DateTime: date_time::DateTime<'a>> SyscallDriver for DateTimeCapsule<'a, DateTime> { + fn command( + &self, + command_number: usize, + r2: usize, + r3: usize, + process_id: ProcessId, + ) -> CommandReturn { + match command_number { + 0 => CommandReturn::success(), + 1 => self.enqueue_command(DateTimeCommand::ReadDateTime, process_id), + 2 => self.enqueue_command( + DateTimeCommand::SetDateTime(r2 as u32, r3 as u32), + process_id, + ), + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { + self.apps.enter(processid, |_, _| {}) + } +} diff --git a/capsules/src/debug_process_restart.rs b/capsules/extra/src/debug_process_restart.rs similarity index 91% rename from capsules/src/debug_process_restart.rs rename to capsules/extra/src/debug_process_restart.rs index 424c6299f2..ea3da9bc5e 100644 --- a/capsules/src/debug_process_restart.rs +++ b/capsules/extra/src/debug_process_restart.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Debug capsule to cause a button press to make all apps fault. //! //! This is useful for debugging that capsules and apps work when they are @@ -6,7 +10,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::{capabilities, static_init}; //! //! struct ProcessMgmtCap; @@ -51,7 +55,7 @@ impl<'a, C: ProcessManagementCapability> DebugProcessRestart<'a, C> { pin.enable_interrupts(gpio::InterruptEdge::EitherEdge); DebugProcessRestart { - kernel: kernel, + kernel, capability: cap, pin, mode, diff --git a/capsules/extra/src/eui64.rs b/capsules/extra/src/eui64.rs new file mode 100644 index 0000000000..b9b3c24570 --- /dev/null +++ b/capsules/extra/src/eui64.rs @@ -0,0 +1,41 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Provides an EUI-64 (Extended Unique Identifier) interface for userspace. + +use capsules_core::driver; +pub const DRIVER_NUM: usize = driver::NUM::Eui64 as usize; + +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::{ErrorCode, ProcessId}; + +pub struct Eui64 { + eui64: u64, +} + +impl Eui64 { + pub fn new(eui64: u64) -> Eui64 { + Eui64 { eui64 } + } +} + +impl SyscallDriver for Eui64 { + /// Control the Eui64. + /// + /// ### `command_num` + /// + /// - `0`: Driver existence check. + /// - `1`: Obtain EUI64 - providing the value within a u64 returncode. + fn command(&self, command_num: usize, _: usize, _: usize, _: ProcessId) -> CommandReturn { + match command_num { + 0 => CommandReturn::success(), + 1 => CommandReturn::success_u64(self.eui64), + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, _: ProcessId) -> Result<(), kernel::process::Error> { + Ok(()) + } +} diff --git a/capsules/src/fm25cl.rs b/capsules/extra/src/fm25cl.rs similarity index 89% rename from capsules/src/fm25cl.rs rename to capsules/extra/src/fm25cl.rs index 4eebdeced2..6b66043c27 100644 --- a/capsules/src/fm25cl.rs +++ b/capsules/extra/src/fm25cl.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for the FM25CL FRAM chip. //! //! @@ -14,7 +18,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! // Create a SPI device for this chip. @@ -46,11 +50,7 @@ use kernel::hil; use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::ErrorCode; -pub static mut TXBUFFER: [u8; 512] = [0; 512]; -pub static mut RXBUFFER: [u8; 512] = [0; 512]; - -pub static mut KERNEL_TXBUFFER: [u8; 512] = [0; 512]; -pub static mut KERNEL_RXBUFFER: [u8; 512] = [0; 512]; +pub const BUF_LEN: usize = 512; const SPI_SPEED: u32 = 4000000; @@ -89,19 +89,19 @@ pub trait FM25CLClient { fn done(&self, buffer: &'static mut [u8]); } -pub struct FM25CL<'a, S: hil::spi::SpiMasterDevice> { +pub struct FM25CL<'a, S: hil::spi::SpiMasterDevice<'a>> { spi: &'a S, state: Cell, txbuffer: TakeCell<'static, [u8]>, rxbuffer: TakeCell<'static, [u8]>, - client: OptionalCell<&'static dyn hil::nonvolatile_storage::NonvolatileStorageClient<'static>>, - client_custom: OptionalCell<&'static dyn FM25CLClient>, + client: OptionalCell<&'a dyn hil::nonvolatile_storage::NonvolatileStorageClient>, + client_custom: OptionalCell<&'a dyn FM25CLClient>, client_buffer: TakeCell<'static, [u8]>, // Store buffer and state for passing back to client client_write_address: Cell, client_write_len: Cell, } -impl<'a, S: hil::spi::SpiMasterDevice> FM25CL<'a, S> { +impl<'a, S: hil::spi::SpiMasterDevice<'a>> FM25CL<'a, S> { pub fn new( spi: &'a S, txbuffer: &'static mut [u8], @@ -109,7 +109,7 @@ impl<'a, S: hil::spi::SpiMasterDevice> FM25CL<'a, S> { ) -> FM25CL<'a, S> { // setup and return struct FM25CL { - spi: spi, + spi, state: Cell::new(State::Idle), txbuffer: TakeCell::new(txbuffer), rxbuffer: TakeCell::new(rxbuffer), @@ -121,7 +121,7 @@ impl<'a, S: hil::spi::SpiMasterDevice> FM25CL<'a, S> { } } - pub fn set_client(&self, client: &'static C) { + pub fn set_client(&self, client: &'a C) { self.client_custom.set(client); } @@ -202,7 +202,7 @@ impl<'a, S: hil::spi::SpiMasterDevice> FM25CL<'a, S> { } } -impl hil::spi::SpiMasterClient for FM25CL<'_, S> { +impl<'a, S: hil::spi::SpiMasterDevice<'a>> hil::spi::SpiMasterClient for FM25CL<'a, S> { fn read_write_done( &self, write_buffer: &'static mut [u8], @@ -237,9 +237,7 @@ impl hil::spi::SpiMasterClient for FM25CL<'_, S> { let write_len = cmp::min(write_buffer.len(), self.client_write_len.get() as usize); - for i in 0..write_len { - write_buffer[(i + 3) as usize] = buffer[i as usize]; - } + write_buffer[3..(write_len + 3)].copy_from_slice(&buffer[..write_len]); let _ = self .spi @@ -273,9 +271,8 @@ impl hil::spi::SpiMasterClient for FM25CL<'_, S> { self.client_buffer.take().map(move |buffer| { let read_len = cmp::min(buffer.len(), len); - for i in 0..(read_len - 3) { - buffer[i] = read_buffer[i + 3]; - } + buffer[..(read_len - 3)] + .copy_from_slice(&read_buffer[3..((read_len - 3) + 3)]); self.rxbuffer.replace(read_buffer); @@ -290,7 +287,7 @@ impl hil::spi::SpiMasterClient for FM25CL<'_, S> { } // Implement the custom interface that exposes chip-specific commands. -impl FM25CLCustom for FM25CL<'_, S> { +impl<'a, S: hil::spi::SpiMasterDevice<'a>> FM25CLCustom for FM25CL<'a, S> { fn read_status(&self) -> Result<(), ErrorCode> { self.configure_spi()?; @@ -315,10 +312,10 @@ impl FM25CLCustom for FM25CL<'_, S> { /// Implement the generic `NonvolatileStorage` interface common to chips that /// provide nonvolatile memory. -impl hil::nonvolatile_storage::NonvolatileStorage<'static> - for FM25CL<'_, S> +impl<'a, S: hil::spi::SpiMasterDevice<'a>> hil::nonvolatile_storage::NonvolatileStorage<'a> + for FM25CL<'a, S> { - fn set_client(&self, client: &'static dyn hil::nonvolatile_storage::NonvolatileStorageClient) { + fn set_client(&self, client: &'a dyn hil::nonvolatile_storage::NonvolatileStorageClient) { self.client.set(client); } diff --git a/capsules/src/ft6x06.rs b/capsules/extra/src/ft6x06.rs similarity index 91% rename from capsules/src/ft6x06.rs rename to capsules/extra/src/ft6x06.rs index e8321e353d..9c328e01bf 100644 --- a/capsules/src/ft6x06.rs +++ b/capsules/extra/src/ft6x06.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for the FT6x06 Touch Panel. //! //! I2C Interface @@ -7,7 +11,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! let mux_i2c = components::i2c::I2CMuxComponent::new(&stm32f4xx::i2c::I2C1) //! .finalize(components::i2c_mux_component_helper!()); //! @@ -46,8 +50,8 @@ enum_from_primitive! { } } -pub struct Ft6x06<'a> { - i2c: &'a dyn i2c::I2CDevice, +pub struct Ft6x06<'a, I: i2c::I2CDevice> { + i2c: &'a I, interrupt_pin: &'a dyn gpio::InterruptPin<'a>, touch_client: OptionalCell<&'a dyn touch::TouchClient>, gesture_client: OptionalCell<&'a dyn touch::GestureClient>, @@ -57,18 +61,18 @@ pub struct Ft6x06<'a> { events: TakeCell<'static, [TouchEvent]>, } -impl<'a> Ft6x06<'a> { +impl<'a, I: i2c::I2CDevice> Ft6x06<'a, I> { pub fn new( - i2c: &'a dyn i2c::I2CDevice, + i2c: &'a I, interrupt_pin: &'a dyn gpio::InterruptPin<'a>, buffer: &'static mut [u8], events: &'static mut [TouchEvent], - ) -> Ft6x06<'a> { + ) -> Ft6x06<'a, I> { // setup and return struct interrupt_pin.enable_interrupts(gpio::InterruptEdge::FallingEdge); Ft6x06 { - i2c: i2c, - interrupt_pin: interrupt_pin, + i2c, + interrupt_pin, touch_client: OptionalCell::empty(), gesture_client: OptionalCell::empty(), multi_touch_client: OptionalCell::empty(), @@ -79,7 +83,7 @@ impl<'a> Ft6x06<'a> { } } -impl<'a> i2c::I2CClient for Ft6x06<'a> { +impl<'a, I: i2c::I2CDevice> i2c::I2CClient for Ft6x06<'a, I> { fn command_complete(&self, buffer: &'static mut [u8], _status: Result<(), i2c::Error>) { self.num_touches.set((buffer[1] & 0x0F) as usize); self.touch_client.map(|client| { @@ -146,11 +150,11 @@ impl<'a> i2c::I2CClient for Ft6x06<'a> { x, y, id, - pressure, size, + pressure, }; }); - num_touches = num_touches + 1; + num_touches += 1; } } self.events.map(|buffer| { @@ -164,7 +168,7 @@ impl<'a> i2c::I2CClient for Ft6x06<'a> { } } -impl<'a> gpio::Client for Ft6x06<'a> { +impl<'a, I: i2c::I2CDevice> gpio::Client for Ft6x06<'a, I> { fn fired(&self) { self.buffer.take().map(|buffer| { self.interrupt_pin.disable_interrupts(); @@ -183,7 +187,7 @@ impl<'a> gpio::Client for Ft6x06<'a> { } } -impl<'a> touch::Touch<'a> for Ft6x06<'a> { +impl<'a, I: i2c::I2CDevice> touch::Touch<'a> for Ft6x06<'a, I> { fn enable(&self) -> Result<(), ErrorCode> { Ok(()) } @@ -197,13 +201,13 @@ impl<'a> touch::Touch<'a> for Ft6x06<'a> { } } -impl<'a> touch::Gesture<'a> for Ft6x06<'a> { +impl<'a, I: i2c::I2CDevice> touch::Gesture<'a> for Ft6x06<'a, I> { fn set_client(&self, client: &'a dyn touch::GestureClient) { self.gesture_client.replace(client); } } -impl<'a> touch::MultiTouch<'a> for Ft6x06<'a> { +impl<'a, I: i2c::I2CDevice> touch::MultiTouch<'a> for Ft6x06<'a, I> { fn enable(&self) -> Result<(), ErrorCode> { Ok(()) } diff --git a/capsules/src/fxos8700cq.rs b/capsules/extra/src/fxos8700cq.rs similarity index 95% rename from capsules/src/fxos8700cq.rs rename to capsules/extra/src/fxos8700cq.rs index 69581df6b4..025f117028 100644 --- a/capsules/src/fxos8700cq.rs +++ b/capsules/extra/src/fxos8700cq.rs @@ -1,6 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for the FXOS8700CQ accelerometer. //! -//! +//! //! //! The driver provides x, y, and z acceleration data to a callback function. //! It implements the `hil::sensors::NineDof` trait. @@ -8,7 +12,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! let fxos8700_i2c = static_init!(I2CDevice, I2CDevice::new(i2c_bus, 0x1e)); @@ -28,7 +32,8 @@ use kernel::hil::i2c::{Error, I2CClient, I2CDevice}; use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::ErrorCode; -pub static mut BUF: [u8; 6] = [0; 6]; +/// Recommended buffer length for this driver. +pub const BUF_LEN: usize = 6; #[allow(dead_code)] enum Registers { @@ -191,8 +196,8 @@ impl<'a> Fxos8700cq<'a> { buffer: &'static mut [u8], ) -> Fxos8700cq<'a> { Fxos8700cq { - i2c: i2c, - interrupt_pin1: interrupt_pin1, + i2c, + interrupt_pin1, state: Cell::new(State::Disabled), buffer: TakeCell::new(buffer), callback: OptionalCell::empty(), @@ -273,8 +278,8 @@ impl gpio::Client for Fxos8700cq<'_> { impl I2CClient for Fxos8700cq<'_> { fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), Error>) { // If there's an I2C error, just reset and issue a callback - // with all 0s. Otherwise, if there's no sensor attacherd, - // it's possible to have nondeterminstic behavior, where + // with all 0s. Otherwise, if there's no sensor attached, + // it's possible to have nondeterministic behavior, where // sometimes you get callbacks and sometimes you don't, based // on whether a floating interrupt line triggers. -pal 3/19/21 if status != Ok(()) { @@ -308,7 +313,7 @@ impl I2CClient for Fxos8700cq<'_> { } } State::ReadAccelWait => { - if self.interrupt_pin1.read() == false { + if !self.interrupt_pin1.read() { // Sample is already ready. self.interrupt_pin1.disable_interrupts(); buffer[0] = Registers::OutXMsb as u8; @@ -346,7 +351,7 @@ impl I2CClient for Fxos8700cq<'_> { // The callback function has no error field, // we can safely ignore the error value. - if let Err((_error, buffer)) = self.i2c.write(buffer, 3) { + if let Err((_error, buffer)) = self.i2c.write(buffer, 2) { self.state.set(State::Disabled); self.buffer.replace(buffer); self.callback.map(|cb| { diff --git a/capsules/src/gpio_async.rs b/capsules/extra/src/gpio_async.rs similarity index 92% rename from capsules/src/gpio_async.rs rename to capsules/extra/src/gpio_async.rs index f9a756a586..eedbfcb7fb 100644 --- a/capsules/src/gpio_async.rs +++ b/capsules/extra/src/gpio_async.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides userspace applications with a driver interface to asynchronous GPIO //! pins. //! @@ -7,7 +11,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! // Generate a list of ports to group into one userspace driver. @@ -32,7 +36,7 @@ use kernel::utilities::cells::OptionalCell; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::GpioAsync as usize; pub struct GPIOAsync<'a, Port: hil::gpio_async::Port> { @@ -72,7 +76,7 @@ impl<'a, Port: hil::gpio_async::Port> GPIOAsync<'a, Port> { } fn configure_input_pin(&self, port: usize, pin: usize, config: usize) -> Result<(), ErrorCode> { - let ports = self.ports.as_ref(); + let ports = self.ports; let mode = match config { 0 => hil::gpio::FloatingState::PullNone, 1 => hil::gpio::FloatingState::PullUp, @@ -83,7 +87,7 @@ impl<'a, Port: hil::gpio_async::Port> GPIOAsync<'a, Port> { } fn configure_interrupt(&self, port: usize, pin: usize, config: usize) -> Result<(), ErrorCode> { - let ports = self.ports.as_ref(); + let ports = self.ports; let mode = match config { 0 => hil::gpio::InterruptEdge::EitherEdge, 1 => hil::gpio::InterruptEdge::RisingEdge, @@ -105,7 +109,7 @@ impl hil::gpio_async::Client for GPIOAsync<'_, Port fn done(&self, value: usize) { // alert currently configuring app self.configuring_process.map(|pid| { - let _ = self.grants.enter(*pid, |_app, upcalls| { + let _ = self.grants.enter(pid, |_app, upcalls| { upcalls.schedule_upcall(0, (0, value, 0)).ok(); }); }); @@ -139,7 +143,7 @@ impl SyscallDriver for GPIOAsync<'_, Port> { /// /// ### `command_num` /// - /// - `0`: Driver check and get number of GPIO ports supported. + /// - `0`: Driver existence check. /// - `1`: Set a pin as an output. /// - `2`: Set a pin high by setting it to 1. /// - `3`: Clear a pin by setting it to 0. @@ -154,6 +158,7 @@ impl SyscallDriver for GPIOAsync<'_, Port> { /// interrupt, and 2 for a falling edge interrupt. /// - `8`: Disable an interrupt on a pin. /// - `9`: Disable a GPIO pin. + /// - `10`: Get number of GPIO ports supported. fn command( &self, command_number: usize, @@ -163,11 +168,16 @@ impl SyscallDriver for GPIOAsync<'_, Port> { ) -> CommandReturn { let port = data & 0xFFFF; let other = (data >> 16) & 0xFFFF; - let ports = self.ports.as_ref(); + let ports = self.ports; - // Special case command 0; everything else results in a process-owned, - // split-phase call. + // Driver existence check. if command_number == 0 { + CommandReturn::success(); + } + + // Special case command 10; everything else results in a process-owned, + // split-phase call. + if command_number == 10 { // How many ports return CommandReturn::success_u32(ports.len() as u32); } diff --git a/capsules/src/hd44780.rs b/capsules/extra/src/hd44780.rs similarity index 97% rename from capsules/src/hd44780.rs rename to capsules/extra/src/hd44780.rs index 78ccc803a4..8b125d6f79 100644 --- a/capsules/src/hd44780.rs +++ b/capsules/extra/src/hd44780.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for the HD44780 LCD screen. //! //! The LCD must be connected as shown here, because the pins of the LCD are @@ -23,7 +27,7 @@ //! Usage //! ----- -//! ```rust +//! ```rust,ignore //! let lcd = components::hd44780::HD44780Component::new(mux_alarm).finalize( //! components::hd44780_component_helper!( //! stm32f429zi::tim2::Tim2, @@ -79,7 +83,7 @@ static LCD_2LINE: u8 = 0x08; static LCD_1LINE: u8 = 0x00; static LCD_5X8DOTS: u8 = 0x00; -pub static mut ROW_OFFSETS: [u8; 4] = [0; 4]; +pub const BUF_LEN: usize = 4; /// The states the program can be in. #[derive(Copy, Clone, PartialEq)] @@ -166,12 +170,12 @@ impl<'a, A: Alarm<'a>> HD44780<'a, A> { data_6_pin.make_output(); data_7_pin.make_output(); let hd44780 = HD44780 { - rs_pin: rs_pin, - en_pin: en_pin, - data_4_pin: data_4_pin, - data_5_pin: data_5_pin, - data_6_pin: data_6_pin, - data_7_pin: data_7_pin, + rs_pin, + en_pin, + data_4_pin, + data_5_pin, + data_6_pin, + data_7_pin, width: Cell::new(width), height: Cell::new(height), display_function: Cell::new(LCD_4BITMODE | LCD_1LINE | LCD_5X8DOTS), @@ -179,7 +183,7 @@ impl<'a, A: Alarm<'a>> HD44780<'a, A> { display_mode: Cell::new(0), num_lines: Cell::new(0), row_offsets: TakeCell::new(row_offsets), - alarm: alarm, + alarm, lcd_status: Cell::new(LCDStatus::Idle), lcd_after_pulse_status: Cell::new(LCDStatus::Idle), lcd_after_command_status: Cell::new(LCDStatus::Idle), @@ -490,9 +494,9 @@ impl<'a, A: Alarm<'a>> HD44780<'a, A> { self.lcd_command(LCD_DISPLAYCONTROL | self.display_control.get(), next_state); } - /// `lcd_command()` is the main funcion that communicates with the device, and + /// `lcd_command()` is the main function that communicates with the device, and /// sends certain values received as arguments to the device (through - /// write_4_bits function). Due to the delays, the funcion is continued in + /// write_4_bits function). Due to the delays, the function is continued in /// the fired() function. /// /// As arguments, there are: diff --git a/capsules/src/hmac.rs b/capsules/extra/src/hmac.rs similarity index 84% rename from capsules/src/hmac.rs rename to capsules/extra/src/hmac.rs index 47182089d0..a05e878fa4 100644 --- a/capsules/src/hmac.rs +++ b/capsules/extra/src/hmac.rs @@ -1,9 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! HMAC (Hash-based Message Authentication Code). //! //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! let hmac = &earlgrey::hmac::HMAC; //! //! let mux_hmac = static_init!(MuxHmac<'static, lowrisc::hmac::Hmac>, MuxHmac::new(hmac)); @@ -23,7 +27,7 @@ //! digest::Digest::set_client(virtual_hmac_user, hmac); //! ``` -use crate::driver; +use capsules_core::driver; use kernel::errorcode::into_statuscode; /// Syscall driver number. pub const DRIVER_NUM: usize = driver::NUM::Hmac as usize; @@ -34,14 +38,14 @@ mod ro_allow { pub const DATA: usize = 1; pub const COMPARE: usize = 2; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 3; + pub const COUNT: u8 = 3; } /// Ids for read-write allow buffers mod rw_allow { pub const DEST: usize = 2; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 3; + pub const COUNT: u8 = 3; } use core::cell::Cell; @@ -51,7 +55,8 @@ use kernel::hil::digest; use kernel::processbuffer::{ReadableProcessBuffer, WriteableProcessBuffer}; use kernel::syscall::{CommandReturn, SyscallDriver}; use kernel::utilities::cells::{OptionalCell, TakeCell}; -use kernel::utilities::leasable_buffer::LeasableBuffer; +use kernel::utilities::leasable_buffer::SubSlice; +use kernel::utilities::leasable_buffer::SubSliceMut; use kernel::{ErrorCode, ProcessId}; enum ShaOperation { @@ -62,7 +67,7 @@ enum ShaOperation { // Temporary buffer to copy the keys from userspace into // -// Needs to be able to accomodate the largest key sizes, e.g. 512 +// Needs to be able to accommodate the largest key sizes, e.g. 512 const TMP_KEY_BUFFER_SIZE: usize = 512 / 8; pub struct HmacDriver<'a, H: digest::Digest<'a, L>, const L: usize> { @@ -76,7 +81,7 @@ pub struct HmacDriver<'a, H: digest::Digest<'a, L>, const L: usize> { AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<{ rw_allow::COUNT }>, >, - appid: OptionalCell, + processid: OptionalCell, data_buffer: TakeCell<'static, [u8]>, data_copied: Cell, @@ -85,7 +90,7 @@ pub struct HmacDriver<'a, H: digest::Digest<'a, L>, const L: usize> { impl< 'a, - H: digest::Digest<'a, L> + digest::HMACSha256 + digest::HMACSha384 + digest::HMACSha512, + H: digest::Digest<'a, L> + digest::HmacSha256 + digest::HmacSha384 + digest::HmacSha512, const L: usize, > HmacDriver<'a, H, L> { @@ -101,10 +106,10 @@ impl< >, ) -> HmacDriver<'a, H, L> { HmacDriver { - hmac: hmac, + hmac, active: Cell::new(false), apps: grant, - appid: OptionalCell::empty(), + processid: OptionalCell::empty(), data_buffer: TakeCell::new(data_buffer), data_copied: Cell::new(0), dest_buffer: TakeCell::new(dest_buffer), @@ -112,10 +117,10 @@ impl< } fn run(&self) -> Result<(), ErrorCode> { - self.appid.map_or(Err(ErrorCode::RESERVE), |appid| { + self.processid.map_or(Err(ErrorCode::RESERVE), |processid| { self.apps - .enter(*appid, |app, kernel_data| { - let ret = kernel_data + .enter(processid, |app, kernel_data| { + kernel_data .get_readonly_processbuffer(ro_allow::KEY) .and_then(|key| { key.enter(|k| { @@ -141,10 +146,7 @@ impl< } }) }) - .unwrap_or(Err(ErrorCode::RESERVE)); - if ret.is_err() { - return ret; - } + .unwrap_or(Err(ErrorCode::RESERVE))?; kernel_data .get_readonly_processbuffer(ro_allow::DATA) @@ -167,12 +169,12 @@ impl< }); // Add the data from the static buffer to the HMAC - let mut lease_buf = LeasableBuffer::new( + let mut lease_buf = SubSliceMut::new( self.data_buffer.take().ok_or(ErrorCode::RESERVE)?, ); lease_buf.slice(0..static_buffer_len); - if let Err(e) = self.hmac.add_data(lease_buf) { - self.data_buffer.replace(e.1); + if let Err(e) = self.hmac.add_mut_data(lease_buf) { + self.data_buffer.replace(e.1.take()); return Err(e.0); } Ok(()) @@ -191,9 +193,9 @@ impl< .hmac .run(self.dest_buffer.take().ok_or(ErrorCode::RESERVE)?) { - // Error, clear the appid and data + // Error, clear the processid and data self.hmac.clear_data(); - self.appid.clear(); + self.processid.clear(); self.dest_buffer.replace(e.1); return Err(e.0); @@ -209,9 +211,9 @@ impl< .hmac .verify(self.dest_buffer.take().ok_or(ErrorCode::RESERVE)?) { - // Error, clear the appid and data + // Error, clear the processid and data self.hmac.clear_data(); - self.appid.clear(); + self.processid.clear(); self.dest_buffer.replace(e.1); return Err(e.0); @@ -224,14 +226,14 @@ impl< for appiter in self.apps.iter() { let started_command = appiter.enter(|app, _| { // If an app is already running let it complete - if self.appid.is_some() { + if self.processid.is_some() { return true; } // If this app has a pending command let's use it. - app.pending_run_app.take().map_or(false, |appid| { + app.pending_run_app.take().map_or(false, |processid| { // Mark this driver as being in use. - self.appid.set(appid); + self.processid.set(processid); // Actually make the buzz happen. self.run() == Ok(()) }) @@ -245,19 +247,23 @@ impl< impl< 'a, - H: digest::Digest<'a, L> + digest::HMACSha256 + digest::HMACSha384 + digest::HMACSha512, + H: digest::Digest<'a, L> + digest::HmacSha256 + digest::HmacSha384 + digest::HmacSha512, const L: usize, - > digest::ClientData<'a, L> for HmacDriver<'a, H, L> + > digest::ClientData for HmacDriver<'a, H, L> { - fn add_data_done(&'a self, _result: Result<(), ErrorCode>, data: &'static mut [u8]) { - self.appid.map(move |id| { + // Because data needs to be copied from a userspace buffer into a kernel (RAM) one, + // we always pass mut data; this callback should never be invoked. + fn add_data_done(&self, _result: Result<(), ErrorCode>, _data: SubSlice<'static, u8>) {} + + fn add_mut_data_done(&self, _result: Result<(), ErrorCode>, data: SubSliceMut<'static, u8>) { + self.processid.map(move |id| { self.apps - .enter(*id, move |app, kernel_data| { + .enter(id, move |app, kernel_data| { let mut data_len = 0; let mut exit = false; let mut static_buffer_len = 0; - self.data_buffer.replace(data); + self.data_buffer.replace(data.take()); self.data_buffer.map(|buf| { let ret = kernel_data @@ -287,9 +293,9 @@ impl< .unwrap_or(Err(ErrorCode::RESERVE)); if ret == Err(ErrorCode::RESERVE) { - // No data buffer, clear the appid and data + // No data buffer, clear the processid and data self.hmac.clear_data(); - self.appid.clear(); + self.processid.clear(); exit = true; } }); @@ -305,18 +311,17 @@ impl< // Update the amount of data copied self.data_copied.set(copied_data + static_buffer_len); - let mut lease_buf = - LeasableBuffer::new(self.data_buffer.take().unwrap()); + let mut lease_buf = SubSliceMut::new(self.data_buffer.take().unwrap()); // Add the data from the static buffer to the HMAC if data_len < (copied_data + static_buffer_len) { lease_buf.slice(..(data_len - copied_data)) } - if self.hmac.add_data(lease_buf).is_err() { - // Error, clear the appid and data + if self.hmac.add_mut_data(lease_buf).is_err() { + // Error, clear the processid and data self.hmac.clear_data(); - self.appid.clear(); + self.processid.clear(); return; } @@ -368,7 +373,7 @@ impl< if err == kernel::process::Error::NoSuchApp || err == kernel::process::Error::InactiveApp { - self.appid.clear(); + self.processid.clear(); } }) }); @@ -379,14 +384,14 @@ impl< impl< 'a, - H: digest::Digest<'a, L> + digest::HMACSha256 + digest::HMACSha384 + digest::HMACSha512, + H: digest::Digest<'a, L> + digest::HmacSha256 + digest::HmacSha384 + digest::HmacSha512, const L: usize, - > digest::ClientHash<'a, L> for HmacDriver<'a, H, L> + > digest::ClientHash for HmacDriver<'a, H, L> { - fn hash_done(&'a self, result: Result<(), ErrorCode>, digest: &'static mut [u8; L]) { - self.appid.map(|id| { + fn hash_done(&self, result: Result<(), ErrorCode>, digest: &'static mut [u8; L]) { + self.processid.map(|id| { self.apps - .enter(*id, |_, kernel_data| { + .enter(id, |_, kernel_data| { self.hmac.clear_data(); let pointer = digest[0] as *mut u8; @@ -406,20 +411,20 @@ impl< }); match result { - Ok(_) => kernel_data.schedule_upcall(0, (0, pointer as usize, 0)), + Ok(()) => kernel_data.schedule_upcall(0, (0, pointer as usize, 0)), Err(e) => kernel_data .schedule_upcall(0, (into_statuscode(e.into()), pointer as usize, 0)), } .ok(); - // Clear the current appid as it has finished running - self.appid.clear(); + // Clear the current processid as it has finished running + self.processid.clear(); }) .map_err(|err| { if err == kernel::process::Error::NoSuchApp || err == kernel::process::Error::InactiveApp { - self.appid.clear(); + self.processid.clear(); } }) }); @@ -431,14 +436,14 @@ impl< impl< 'a, - H: digest::Digest<'a, L> + digest::HMACSha256 + digest::HMACSha384 + digest::HMACSha512, + H: digest::Digest<'a, L> + digest::HmacSha256 + digest::HmacSha384 + digest::HmacSha512, const L: usize, - > digest::ClientVerify<'a, L> for HmacDriver<'a, H, L> + > digest::ClientVerify for HmacDriver<'a, H, L> { - fn verification_done(&'a self, result: Result, compare: &'static mut [u8; L]) { - self.appid.map(|id| { + fn verification_done(&self, result: Result, compare: &'static mut [u8; L]) { + self.processid.map(|id| { self.apps - .enter(*id, |_app, kernel_data| { + .enter(id, |_app, kernel_data| { self.hmac.clear_data(); match result { @@ -447,14 +452,14 @@ impl< } .ok(); - // Clear the current appid as it has finished running - self.appid.clear(); + // Clear the current processid as it has finished running + self.processid.clear(); }) .map_err(|err| { if err == kernel::process::Error::NoSuchApp || err == kernel::process::Error::InactiveApp { - self.appid.clear(); + self.processid.clear(); } }) }); @@ -481,7 +486,7 @@ impl< /// the `hash_done` callback. impl< 'a, - H: digest::Digest<'a, L> + digest::HMACSha256 + digest::HMACSha384 + digest::HMACSha512, + H: digest::Digest<'a, L> + digest::HmacSha256 + digest::HmacSha384 + digest::HmacSha512, const L: usize, > SyscallDriver for HmacDriver<'a, H, L> { @@ -499,13 +504,13 @@ impl< /// above allow calls. /// /// We expect userspace not to change the value while running. If userspace - /// changes the value we have no guarentee of what is passed to the + /// changes the value we have no guarantee of what is passed to the /// hardware. This isn't a security issue, it will just prove the requesting /// app with invalid data. /// - /// The driver will take care of clearing data from the underlying impelemenation + /// The driver will take care of clearing data from the underlying implementation /// by calling the `clear_data()` function when the `hash_complete()` callback - /// is called or if an error is encounted. + /// is called or if an error is encountered. /// /// ### `command_num` /// @@ -518,9 +523,9 @@ impl< command_num: usize, data1: usize, _data2: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { - let match_or_empty_or_nonexistant = self.appid.map_or(true, |owning_app| { + let match_or_empty_or_nonexistant = self.processid.map_or(true, |owning_app| { // We have recorded that an app has ownership of the HMAC. // If the HMAC is still active, then we need to wait for the operation @@ -529,7 +534,7 @@ impl< // we need to verify that that application still exists, and remove // it as owner if not. if self.active.get() { - owning_app == &appid + owning_app == processid } else { // Check the app still exists. // @@ -539,12 +544,12 @@ impl< // longer exists and we return `true` to signify the // "or_nonexistant" case. self.apps - .enter(*owning_app, |_, _| owning_app == &appid) + .enter(owning_app, |_, _| owning_app == processid) .unwrap_or(true) } }); - let app_match = self.appid.map_or(false, |owning_app| { + let app_match = self.processid.map_or(false, |owning_app| { // We have recorded that an app has ownership of the HMAC. // If the HMAC is still active, then we need to wait for the operation @@ -553,7 +558,7 @@ impl< // we need to verify that that application still exists, and remove // it as owner if not. if self.active.get() { - owning_app == &appid + owning_app == processid } else { // Check the app still exists. // @@ -563,7 +568,7 @@ impl< // longer exists and we return `true` to signify the // "or_nonexistant" case. self.apps - .enter(*owning_app, |_, _| owning_app == &appid) + .enter(owning_app, |_, _| owning_app == processid) .unwrap_or(true) } }); @@ -573,9 +578,9 @@ impl< if match_or_empty_or_nonexistant && (command_num == 1 || command_num == 2 || command_num == 4) { - self.appid.set(appid); + self.processid.set(processid); - let _ = self.apps.enter(appid, |app, _| { + let _ = self.apps.enter(processid, |app, _| { if command_num == 1 { // run // Use key and data to compute hash @@ -596,7 +601,7 @@ impl< return if let Err(e) = self.run() { self.hmac.clear_data(); - self.appid.clear(); + self.processid.clear(); self.check_queue(); CommandReturn::failure(e) } else { @@ -605,7 +610,7 @@ impl< } self.apps - .enter(appid, |app, kernel_data| { + .enter(processid, |app, kernel_data| { match command_num { // set_algorithm 0 => { @@ -638,7 +643,7 @@ impl< CommandReturn::failure(ErrorCode::NOMEM) } else { // We can store this, so lets do it. - app.pending_run_app = Some(appid); + app.pending_run_app = Some(processid); app.op.set(Some(UserSpaceOp::Run)); CommandReturn::success() } @@ -653,7 +658,7 @@ impl< CommandReturn::failure(ErrorCode::NOMEM) } else { // We can store this, so lets do it. - app.pending_run_app = Some(appid); + app.pending_run_app = Some(processid); app.op.set(Some(UserSpaceOp::Update)); CommandReturn::success() } @@ -688,14 +693,14 @@ impl< CommandReturn::failure(ErrorCode::NOMEM) } else { // We can store this, so lets do it. - app.pending_run_app = Some(appid); + app.pending_run_app = Some(processid); app.op.set(Some(UserSpaceOp::Verify)); CommandReturn::success() } } // verify_finish - // Use key and data to compute hash and comapre it against + // Use key and data to compute hash and compare it against // the digest, useful after a update command 5 => { if app_match { diff --git a/capsules/extra/src/hmac_sha256.rs b/capsules/extra/src/hmac_sha256.rs new file mode 100644 index 0000000000..1b63c8a04e --- /dev/null +++ b/capsules/extra/src/hmac_sha256.rs @@ -0,0 +1,550 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Software implementation of HMAC-SHA256. + +use core::cell::Cell; + +use kernel::hil; +use kernel::hil::digest::DigestData; +use kernel::utilities::cells::{MapCell, OptionalCell, TakeCell}; +use kernel::utilities::leasable_buffer::SubSlice; +use kernel::utilities::leasable_buffer::SubSliceMut; +use kernel::utilities::leasable_buffer::SubSliceMutImmut; +use kernel::ErrorCode; + +#[derive(Clone, Copy, PartialEq)] +pub enum State { + Idle, + InnerHashAddKeyPending, + InnerHashAddKey, + InnerHashAddData, + InnerHash, + OuterHashAddKey, + OuterHashAddHash, + OuterHash, +} + +#[derive(Copy, Clone)] +pub enum RunMode { + Hash, + Verify, +} + +/// Value to XOR the key with on the inner hash. +const INNER_PAD_BYTE: u8 = 0x36; +/// Value to XOR the key with on the outer hash. +const OUTER_PAD_BYTE: u8 = 0x5c; + +const SHA_BLOCK_LEN_BYTES: usize = 64; +const SHA_256_OUTPUT_LEN_BYTES: usize = 32; + +pub struct HmacSha256Software<'a, S: hil::digest::Sha256 + hil::digest::DigestDataHash<'a, 32>> { + /// SHA256 hasher implementation. + sha256: &'a S, + /// The current operation for the internal state machine in this capsule. + state: Cell, + /// The current mode of operation as requested by a call to either + /// [`DigestHash::run`](kernel::hil::digest::DigestHash::run) or + /// [`DigestVerify::verify`](kernel::hil::digest::DigestVerify::verify). + mode: Cell, + /// Location to store incoming temporarily before we are able to pass it to + /// the hasher. + input_data: OptionalCell>, + /// Static buffer to store the key and to pass to the hasher. This must be + /// at least `SHA_BLOCK_LEN_BYTES` bytes. + data_buffer: TakeCell<'static, [u8]>, + /// Storage buffer to keep a copy of the key. This allows us to keep it + /// persistent if the user wants to do multiple HMACs with the same key. + key_buffer: MapCell<[u8; SHA_BLOCK_LEN_BYTES]>, + /// Holding cell for the output digest buffer while we calculate the HMAC. + digest_buffer: MapCell<&'static mut [u8; 32]>, + /// Buffer-slot used for a _verify_ operation. When not active, this + /// contains a buffer to place the current digest in. On a call to `verify`, + /// where the digest to compare to is provided in another buffer, this + /// buffer is swapped into this TakeCell. When the operation completes, we + /// swap them back and compare: + verify_buffer: MapCell<&'static mut [u8; 32]>, + /// Clients for callbacks. + // error[E0658]: cannot cast `dyn kernel::hil::digest::Client<32>` to `dyn ClientData<32>`, trait upcasting coercion is experimental + // data_client: OptionalCell<&'a dyn hil::digest::ClientData>, + // hash_client: OptionalCell<&'a dyn hil::digest::ClientHash>, + // verify_client: OptionalCell<&'a dyn hil::digest::ClientVerify>, + client: OptionalCell<&'a dyn hil::digest::Client>, +} + +impl<'a, S: hil::digest::Sha256 + hil::digest::DigestDataHash<'a, 32>> HmacSha256Software<'a, S> { + pub fn new( + sha256: &'a S, + data_buffer: &'static mut [u8], + verify_buffer: &'static mut [u8; 32], + ) -> Self { + Self { + sha256, + state: Cell::new(State::Idle), + mode: Cell::new(RunMode::Hash), + input_data: OptionalCell::empty(), + data_buffer: TakeCell::new(data_buffer), + key_buffer: MapCell::new([0; SHA_BLOCK_LEN_BYTES]), + digest_buffer: MapCell::empty(), + verify_buffer: MapCell::new(verify_buffer), + // data_client: OptionalCell::empty(), + // hash_client: OptionalCell::empty(), + // verify_client: OptionalCell::empty(), + client: OptionalCell::empty(), + } + } +} + +impl<'a, S: hil::digest::Sha256 + hil::digest::DigestDataHash<'a, 32>> + hil::digest::DigestData<'a, 32> for HmacSha256Software<'a, S> +{ + fn add_data( + &self, + data: SubSlice<'static, u8>, + ) -> Result<(), (ErrorCode, SubSlice<'static, u8>)> { + match self.state.get() { + State::InnerHashAddKeyPending => { + // We need to write the key before we write the data. + if let Some(data_buf) = self.data_buffer.take() { + self.key_buffer.map(|key_buf| { + // Copy the key XOR with inner pad (0x36). + for i in 0..64 { + data_buf[i] = key_buf[i] ^ INNER_PAD_BYTE; + } + }); + + let mut lease_buf = SubSliceMut::new(data_buf); + lease_buf.slice(0..64); + + match self.sha256.add_mut_data(lease_buf) { + Ok(()) => { + self.state.set(State::InnerHashAddKey); + // Save the incoming data to add to the hasher + // on the next iteration. + self.input_data.set(SubSliceMutImmut::Immutable(data)); + Ok(()) + } + Err((e, leased_data_buf)) => { + self.data_buffer.replace(leased_data_buf.take()); + Err((e, data)) + } + } + } else { + Err((ErrorCode::BUSY, data)) + } + } + + State::InnerHashAddData => { + // In this state the hasher is ready to take more input data so + // we can provide more input data. This is the only state after + // setting the key we can accept new data in. + self.sha256.add_data(data) + } + + State::Idle => { + // We need a key before we can accept data, so we must return + // error here. `OFF` is the closest error to this issue so we + // return that. + Err((ErrorCode::OFF, data)) + } + + _ => { + // Any other state we cannot accept new data. + Err((ErrorCode::BUSY, data)) + } + } + } + + fn add_mut_data( + &self, + data: SubSliceMut<'static, u8>, + ) -> Result<(), (ErrorCode, SubSliceMut<'static, u8>)> { + match self.state.get() { + State::InnerHashAddKeyPending => { + // We need to write the key before we write the data. + + if let Some(data_buf) = self.data_buffer.take() { + // Copy the key XOR with inner pad (0x36). + self.key_buffer.map(|key_buf| { + // Copy the key XOR with inner pad (0x36). + for i in 0..64 { + data_buf[i] = key_buf[i] ^ INNER_PAD_BYTE; + } + }); + + let mut lease_buf = SubSliceMut::new(data_buf); + lease_buf.slice(0..64); + + match self.sha256.add_mut_data(lease_buf) { + Ok(()) => { + self.state.set(State::InnerHashAddKey); + // Save the incoming data to add to the hasher + // on the next iteration. + self.input_data.set(SubSliceMutImmut::Mutable(data)); + Ok(()) + } + Err((e, leased_data_buf)) => { + self.data_buffer.replace(leased_data_buf.take()); + Err((e, data)) + } + } + } else { + Err((ErrorCode::BUSY, data)) + } + } + + State::InnerHashAddData => { + // In this state the hasher is ready to take more input data so + // we can provide more input data. This is the only state after + // setting the key we can accept new data in. + self.sha256.add_mut_data(data) + } + + State::Idle => { + // We need a key before we can accept data, so we must return + // error here. `OFF` is the closest error to this issue so we + // return that. + Err((ErrorCode::OFF, data)) + } + + _ => { + // Any other state we cannot accept new data. + Err((ErrorCode::BUSY, data)) + } + } + } + + fn clear_data(&self) { + self.state.set(State::Idle); + self.sha256.clear_data(); + } + + fn set_data_client(&'a self, _client: &'a dyn hil::digest::ClientData<32>) { + // self.data_client.set(client); + unimplemented!() + } +} + +impl<'a, S: hil::digest::Sha256 + hil::digest::DigestDataHash<'a, 32>> + hil::digest::DigestHash<'a, 32> for HmacSha256Software<'a, S> +{ + fn run( + &'a self, + digest: &'static mut [u8; 32], + ) -> Result<(), (ErrorCode, &'static mut [u8; 32])> { + // User called run, we start with the inner hash. + self.state.set(State::InnerHash); + self.mode.set(RunMode::Hash); + self.sha256.run(digest) + } + + fn set_hash_client(&'a self, _client: &'a dyn hil::digest::ClientHash<32>) { + // self.hash_client.set(client); + unimplemented!() + } +} + +impl<'a, S: hil::digest::Sha256 + hil::digest::DigestDataHash<'a, 32>> + hil::digest::DigestVerify<'a, 32> for HmacSha256Software<'a, S> +{ + fn verify( + &'a self, + compare: &'static mut [u8; 32], + ) -> Result<(), (ErrorCode, &'static mut [u8; 32])> { + // User called verify, we start with the inner hash. + self.state.set(State::InnerHash); + self.mode.set(RunMode::Verify); + + // Swap the `compare` buffer into `self.verify_buffer`, and use that to + // perform the actual digest calculation: + let digest = self.verify_buffer.replace(compare).unwrap(); + self.sha256.run(digest) + } + + fn set_verify_client(&'a self, _client: &'a dyn hil::digest::ClientVerify<32>) { + // self.verify_client.set(client); + unimplemented!() + } +} + +impl<'a, S: hil::digest::Sha256 + hil::digest::DigestDataHash<'a, 32>> + hil::digest::DigestDataHash<'a, 32> for HmacSha256Software<'a, S> +{ + fn set_client(&'a self, _client: &'a dyn hil::digest::ClientDataHash<32>) { + // self.data_client.set(client); + // self.hash_client.set(client); + unimplemented!() + } +} + +impl<'a, S: hil::digest::Sha256 + hil::digest::DigestDataHash<'a, 32>> hil::digest::Digest<'a, 32> + for HmacSha256Software<'a, S> +{ + fn set_client(&'a self, client: &'a dyn hil::digest::Client<32>) { + // self.data_client.set(client); + // self.hash_client.set(client); + // self.verify_client.set(client); + self.client.set(client); + } +} + +impl<'a, S: hil::digest::Sha256 + hil::digest::DigestDataHash<'a, 32>> hil::digest::ClientData<32> + for HmacSha256Software<'a, S> +{ + fn add_data_done(&self, result: Result<(), ErrorCode>, data: SubSlice<'static, u8>) { + // This callback is only used for the user to pass in additional data + // for the HMAC, we do not use `add_data()` internally in this capsule + // so we can just directly issue the callback. + // self.data_client.map(|client| { + self.client.map(|client| { + client.add_data_done(result, data); + }); + } + + fn add_mut_data_done(&self, result: Result<(), ErrorCode>, data: SubSliceMut<'static, u8>) { + if result.is_err() { + // self.data_client.map(|client| { + self.client.map(|client| { + client.add_mut_data_done(result, data); + }); + } else { + match self.state.get() { + State::InnerHashAddKey => { + self.data_buffer.replace(data.take()); + + // We just added the key, so we can now add the stored data. + self.input_data.take().map(|in_data| match in_data { + SubSliceMutImmut::Mutable(buffer) => { + match self.sha256.add_mut_data(buffer) { + Ok(()) => { + self.state.set(State::InnerHashAddData); + } + Err((e, leased_data_buf)) => { + self.clear_data(); + // self.data_client.map(|c| { + self.client.map(|c| { + c.add_mut_data_done(Err(e), leased_data_buf); + }); + } + } + } + SubSliceMutImmut::Immutable(buffer) => match self.sha256.add_data(buffer) { + Ok(()) => { + self.state.set(State::InnerHashAddData); + } + Err((e, leased_data_buf)) => { + self.clear_data(); + self.client.map(|c| { + c.add_data_done(Err(e), leased_data_buf); + }); + } + }, + }); + } + State::OuterHashAddKey => { + // We just added the key, now we add the result of the first + // hash. + self.digest_buffer.take().map(|digest_buf| { + let data_buf = data.take(); + + // Copy the digest result into our data buffer. We must + // use our data buffer because it does not have a fixed + // size and we can use it with `SubSliceMut`. + data_buf[..32].copy_from_slice(&digest_buf[..32]); + + let mut lease_buf = SubSliceMut::new(data_buf); + lease_buf.slice(0..32); + + match self.sha256.add_mut_data(lease_buf) { + Ok(()) => { + self.state.set(State::OuterHashAddHash); + self.digest_buffer.replace(digest_buf); + } + Err((e, leased_data_buf)) => { + self.data_buffer.replace(leased_data_buf.take()); + self.clear_data(); + // self.data_client.map(|c| { + self.client.map(|c| { + c.hash_done(Err(e), digest_buf); + }); + } + } + }); + } + State::OuterHashAddHash => { + // We've now added both the key and the result of the first + // hash, so we can run the second hash to get our HMAC. + self.data_buffer.replace(data.take()); + + self.digest_buffer + .take() + .map(|digest_buf| match self.sha256.run(digest_buf) { + Ok(()) => { + self.state.set(State::OuterHash); + } + Err((e, digest)) => { + self.clear_data(); + // self.data_client.map(|c| { + self.client.map(|c| { + c.hash_done(Err(e), digest); + }); + } + }); + } + _ => { + // In other states, we can just issue the callback like + // normal. + // self.data_client.map(|client| { + self.client.map(|client| { + client.add_mut_data_done(Ok(()), data); + }); + } + } + } + } +} + +impl<'a, S: hil::digest::Sha256 + hil::digest::DigestDataHash<'a, 32>> hil::digest::ClientHash<32> + for HmacSha256Software<'a, S> +{ + fn hash_done(&self, result: Result<(), ErrorCode>, digest: &'static mut [u8; 32]) { + let hash_done_error = |error: Result<(), ErrorCode>, + error_digest: &'static mut [u8; 32]| { + match self.mode.get() { + RunMode::Hash => { + // self.hash_client.map(|c| { + self.client.map(|c| { + c.hash_done(error, error_digest); + }) + } + RunMode::Verify => { + // Also swap back the verify_buffer, and return the original + // buffer to the client: + let compare = self.verify_buffer.replace(error_digest).unwrap(); + // self.verify_client.map(|c| { + self.client.map(|c| { + // Convert to Result + c.verification_done(error.map(|()| false), compare); + }) + } + } + }; + + if result.is_err() { + // If hashing fails, we have to propagate that error up with a + // callback. + self.clear_data(); + hash_done_error(result, digest); + } else { + match self.state.get() { + State::InnerHash => { + // Completed inner hash, now work on outer hash. + self.sha256.clear_data(); + + self.data_buffer.take().map(|data_buf| { + self.key_buffer.map(|key_buf| { + // Copy the key XOR with outer pad (0x5c). + for i in 0..64 { + data_buf[i] = key_buf[i] ^ OUTER_PAD_BYTE; + } + }); + + let mut lease_buf = SubSliceMut::new(data_buf); + lease_buf.slice(0..64); + + match self.sha256.add_mut_data(lease_buf) { + Ok(()) => { + self.state.set(State::OuterHashAddKey); + self.digest_buffer.replace(digest); + } + Err((e, leased_data_buf)) => { + // If we cannot add data, we need to replace the + // buffer and issue a callback with an error. + self.data_buffer.replace(leased_data_buf.take()); + self.clear_data(); + hash_done_error(Err(e), digest); + } + } + }); + } + + State::OuterHash => { + match self.mode.get() { + RunMode::Hash => { + // self.hash_client.map(|c| { + self.client.map(|c| { + c.hash_done(Ok(()), digest); + }); + } + + RunMode::Verify => { + let compare = self.verify_buffer.take().unwrap(); + let res = compare == digest; + self.verify_buffer.replace(digest); + // self.verify_client.map(|c| { + self.client.map(|c| { + c.verification_done(Ok(res), compare); + }); + } + } + } + _ => {} + } + } + } +} + +impl<'a, S: hil::digest::Sha256 + hil::digest::DigestDataHash<'a, 32>> hil::digest::ClientVerify<32> + for HmacSha256Software<'a, S> +{ + fn verification_done(&self, _result: Result, _compare: &'static mut [u8; 32]) { + } +} + +impl<'a, S: hil::digest::Sha256 + hil::digest::DigestDataHash<'a, 32>> hil::digest::HmacSha256 + for HmacSha256Software<'a, S> +{ + fn set_mode_hmacsha256(&self, key: &[u8]) -> Result<(), ErrorCode> { + if key.len() > SHA_BLOCK_LEN_BYTES { + // Key size must be no longer than the internal block size (which is + // 64 bytes). + Err(ErrorCode::SIZE) + } else { + self.key_buffer.map_or(Err(ErrorCode::FAIL), |key_buf| { + // Save the key in our key buffer. + for i in 0..64 { + key_buf[i] = *key.get(i).unwrap_or(&0); + } + + // Make sure our hasher is in the expected mode. + self.sha256.set_mode_sha256()?; + + // Mark that we have the key pending which we can add once we + // get additional data to add. We can't add the key in the + // underlying hash now because we don't have a callback to use, + // so we have to just store the key. We need to use the key + // again anyway, so this is ok. + self.state.set(State::InnerHashAddKeyPending); + Ok(()) + }) + } + } +} + +impl<'a, S: hil::digest::Sha256 + hil::digest::DigestDataHash<'a, 32>> hil::digest::HmacSha384 + for HmacSha256Software<'a, S> +{ + fn set_mode_hmacsha384(&self, _key: &[u8]) -> Result<(), ErrorCode> { + Err(ErrorCode::NOSUPPORT) + } +} + +impl<'a, S: hil::digest::Sha256 + hil::digest::DigestDataHash<'a, 32>> hil::digest::HmacSha512 + for HmacSha256Software<'a, S> +{ + fn set_mode_hmacsha512(&self, _key: &[u8]) -> Result<(), ErrorCode> { + Err(ErrorCode::NOSUPPORT) + } +} diff --git a/capsules/extra/src/hs3003.rs b/capsules/extra/src/hs3003.rs new file mode 100644 index 0000000000..06737a460c --- /dev/null +++ b/capsules/extra/src/hs3003.rs @@ -0,0 +1,189 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Sensor Driver for the Renesas HS3003 Temperature/Humidity sensor +//! using the I2C bus. +//! +//! +//! +//! > The HS300x (HS3001 and HS3003) series is a highly accurate, +//! > fully calibrated relative humidity and temperature sensor. +//! > The MEMS sensor features a proprietary sensor-level protection, +//! > ensuring high reliability and long-term stability. The high +//! > accuracy, fast measurement response time, and long-term stability +//! > combined with the small package size makes the HS300x series ideal +//! > for a wide number of applications ranging from portable devices to +//! > products designed for harsh environments. +//! +//! Driver Semantics +//! ---------------- +//! +//! This driver exposes the HS3003's temperature and humidity functionality via +//! the [TemperatureDriver] and [HumidityDriver] HIL interfaces. If the driver +//! receives a request for either temperature or humidity while a request for the +//! other is outstanding, both will be returned to their respective clients when +//! the I2C transaction is completed, rather than performing two separate transactions. +//! +//! Limitations +//! ----------- +//! +//! The driver uses floating point math to adjust the readings. This must be +//! done and macthes the chip's datasheet. This could decrease performance +//! on platforms that don't have hardware support for floating point math. +//! +//! Usage +//! ----- +//! +//! ```rust,ignore +//! # use kernel::static_init; +//! +//! let hs3003_i2c = static_init!( +//! capsules::virtual_i2c::I2CDevice, +//! capsules::virtual_i2c::I2CDevice::new(i2c_bus, 0x44)); +//! let hs3003 = static_init!( +//! capsules::hs3003::Hs3003<'static>, +//! capsules::hs3003::Hs3003::new(hs3003_i2c, +//! &mut capsules::hs3003::BUFFER)); +//! hs3003_i2c.set_client(hs3003); +//! ``` + +use core::cell::Cell; +use kernel::hil::i2c::{self, I2CClient, I2CDevice}; +use kernel::hil::sensors::{HumidityClient, HumidityDriver, TemperatureClient, TemperatureDriver}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::ErrorCode; + +pub struct Hs3003<'a, I: I2CDevice> { + buffer: TakeCell<'static, [u8]>, + i2c: &'a I, + temperature_client: OptionalCell<&'a dyn TemperatureClient>, + humidity_client: OptionalCell<&'a dyn HumidityClient>, + state: Cell, + pending_temperature: Cell, + pending_humidity: Cell, +} + +impl<'a, I: I2CDevice> Hs3003<'a, I> { + pub fn new(i2c: &'a I, buffer: &'static mut [u8]) -> Self { + Hs3003 { + buffer: TakeCell::new(buffer), + i2c, + temperature_client: OptionalCell::empty(), + humidity_client: OptionalCell::empty(), + state: Cell::new(State::Sleep), + pending_temperature: Cell::new(false), + pending_humidity: Cell::new(false), + } + } + + pub fn start_reading(&self) -> Result<(), ErrorCode> { + self.buffer + .take() + .map(|buffer| { + self.i2c.enable(); + match self.state.get() { + State::Sleep => { + if let Err((_error, buffer)) = self.i2c.write(buffer, 1) { + self.buffer.replace(buffer); + self.i2c.disable(); + } else { + self.state.set(State::InitiateReading); + } + } + _ => {} + } + }) + .ok_or(ErrorCode::BUSY) + } +} + +impl<'a, I: I2CDevice> TemperatureDriver<'a> for Hs3003<'a, I> { + fn set_client(&self, client: &'a dyn TemperatureClient) { + self.temperature_client.set(client); + } + + fn read_temperature(&self) -> Result<(), ErrorCode> { + self.pending_temperature.set(true); + if !self.pending_humidity.get() { + self.start_reading() + } else { + Ok(()) + } + } +} + +impl<'a, I: I2CDevice> HumidityDriver<'a> for Hs3003<'a, I> { + fn set_client(&self, client: &'a dyn HumidityClient) { + self.humidity_client.set(client); + } + + fn read_humidity(&self) -> Result<(), ErrorCode> { + self.pending_humidity.set(true); + if !self.pending_temperature.get() { + self.start_reading() + } else { + Ok(()) + } + } +} + +#[derive(Clone, Copy, Debug)] +enum State { + Sleep, + InitiateReading, + Read, +} + +impl<'a, I: I2CDevice> I2CClient for Hs3003<'a, I> { + fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) { + if let Err(i2c_err) = status { + self.state.set(State::Sleep); + self.buffer.replace(buffer); + self.temperature_client + .map(|client| client.callback(Err(i2c_err.into()))); + self.humidity_client.map(|client| client.callback(0)); + return; + } + + match self.state.get() { + State::InitiateReading => { + if let Err((i2c_err, buffer)) = self.i2c.read(buffer, 4) { + self.state.set(State::Sleep); + self.buffer.replace(buffer); + self.temperature_client + .map(|client| client.callback(Err(i2c_err.into()))); + self.humidity_client.map(|client| client.callback(0)); + } else { + self.state.set(State::Read); + } + } + State::Read => { + let humidity_raw = (((buffer[0] & 0x3F) as u16) << 8) | buffer[1] as u16; + let humidity = ((humidity_raw as f32 / ((1 << 14) - 1) as f32) * 100.0) as usize; + + let temperature_raw = ((buffer[2] as u16) << 8) | (buffer[3] as u16 >> 2); + // This operation follows the datasheet specification except dividing by 10. If its not done, + // the returned value will be in the hundreds (220 instead of 22 degrees celsius). + let temperature = ((((temperature_raw as f32 / ((1 << 14) - 1) as f32) * 165.0) + - 40.0) + / 10.0) as i32; + + self.buffer.replace(buffer); + self.i2c.disable(); + if self.pending_temperature.get() { + self.pending_temperature.set(false); + self.temperature_client + .map(|client| client.callback(Ok(temperature))); + } + if self.pending_humidity.get() { + self.pending_humidity.set(false); + self.humidity_client.map(|client| client.callback(humidity)); + } + + self.state.set(State::Sleep); + } + State::Sleep => {} // should never happen + } + } +} diff --git a/capsules/src/hts221.rs b/capsules/extra/src/hts221.rs similarity index 88% rename from capsules/src/hts221.rs rename to capsules/extra/src/hts221.rs index 8a14d26c04..071cbe085c 100644 --- a/capsules/src/hts221.rs +++ b/capsules/extra/src/hts221.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for the STMicro HTS221 relative humidity and temperature //! sensor using the I2C bus. //! @@ -51,7 +55,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! let hts221_i2c = static_init!( @@ -84,9 +88,9 @@ struct CalibrationData { humidity_intercept: f32, } -pub struct Hts221<'a> { +pub struct Hts221<'a, I: I2CDevice> { buffer: TakeCell<'static, [u8]>, - i2c: &'a dyn I2CDevice, + i2c: &'a I, temperature_client: OptionalCell<&'a dyn TemperatureClient>, humidity_client: OptionalCell<&'a dyn HumidityClient>, state: Cell, @@ -94,8 +98,8 @@ pub struct Hts221<'a> { pending_humidity: Cell, } -impl<'a> Hts221<'a> { - pub fn new(i2c: &'a dyn I2CDevice, buffer: &'static mut [u8]) -> Self { +impl<'a, I: I2CDevice> Hts221<'a, I> { + pub fn new(i2c: &'a I, buffer: &'static mut [u8]) -> Self { Hts221 { buffer: TakeCell::new(buffer), i2c, @@ -148,7 +152,7 @@ impl<'a> Hts221<'a> { } } -impl<'a> TemperatureDriver<'a> for Hts221<'a> { +impl<'a, I: I2CDevice> TemperatureDriver<'a> for Hts221<'a, I> { fn set_client(&self, client: &'a dyn TemperatureClient) { self.temperature_client.set(client); } @@ -163,7 +167,7 @@ impl<'a> TemperatureDriver<'a> for Hts221<'a> { } } -impl<'a> HumidityDriver<'a> for Hts221<'a> { +impl<'a, I: I2CDevice> HumidityDriver<'a> for Hts221<'a, I> { fn set_client(&self, client: &'a dyn HumidityClient) { self.humidity_client.set(client); } @@ -185,12 +189,12 @@ enum State { InitiateReading(CalibrationData), CheckStatus(CalibrationData), Read(CalibrationData), - Idle(CalibrationData, usize, usize), + Idle(CalibrationData, i32, usize), } -impl<'a> I2CClient for Hts221<'a> { +impl<'a, I: I2CDevice> I2CClient for Hts221<'a, I> { fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) { - if status.is_err() { + if let Err(i2c_err) = status { self.state.set(State::Idle( CalibrationData { temp_slope: 0.0, @@ -202,7 +206,8 @@ impl<'a> I2CClient for Hts221<'a> { 0, )); self.buffer.replace(buffer); - self.temperature_client.map(|client| client.callback(0)); + self.temperature_client + .map(|client| client.callback(Err(i2c_err.into()))); self.humidity_client.map(|client| client.callback(0)); return; } @@ -230,7 +235,7 @@ impl<'a> I2CClient for Hts221<'a> { buffer[1] = 1 << 2 | 1 << 7; // BDU + PD buffer[2] = 1; // ONE SHOT - if let Err((_error, buffer)) = self.i2c.write(buffer, 3) { + if let Err((error, buffer)) = self.i2c.write(buffer, 3) { self.state.set(State::Idle( CalibrationData { temp_slope: 0.0, @@ -242,7 +247,8 @@ impl<'a> I2CClient for Hts221<'a> { 0, )); self.buffer.replace(buffer); - self.temperature_client.map(|client| client.callback(0)); + self.temperature_client + .map(|client| client.callback(Err(error.into()))); self.humidity_client.map(|client| client.callback(0)); } else { self.state.set(State::InitiateReading(CalibrationData { @@ -256,7 +262,7 @@ impl<'a> I2CClient for Hts221<'a> { State::InitiateReading(calibration_data) => { buffer[0] = STATUS_REG; - if let Err((_error, buffer)) = self.i2c.write_read(buffer, 1, 1) { + if let Err((error, buffer)) = self.i2c.write_read(buffer, 1, 1) { self.state.set(State::Idle( CalibrationData { temp_slope: 0.0, @@ -268,7 +274,8 @@ impl<'a> I2CClient for Hts221<'a> { 0, )); self.buffer.replace(buffer); - self.temperature_client.map(|client| client.callback(0)); + self.temperature_client + .map(|client| client.callback(Err(error.into()))); self.humidity_client.map(|client| client.callback(0)); } else { self.state.set(State::CheckStatus(calibration_data)); @@ -278,7 +285,7 @@ impl<'a> I2CClient for Hts221<'a> { if buffer[0] & 0b11 == 0b11 { buffer[0] = REG_AUTO_INCREMENT | HUMID0_REG; - if let Err((_error, buffer)) = self.i2c.write_read(buffer, 1, 4) { + if let Err((error, buffer)) = self.i2c.write_read(buffer, 1, 4) { self.state.set(State::Idle( CalibrationData { temp_slope: 0.0, @@ -290,7 +297,8 @@ impl<'a> I2CClient for Hts221<'a> { 0, )); self.buffer.replace(buffer); - self.temperature_client.map(|client| client.callback(0)); + self.temperature_client + .map(|client| client.callback(Err(error.into()))); self.humidity_client.map(|client| client.callback(0)); } else { self.state.set(State::Read(calibration_data)); @@ -298,7 +306,7 @@ impl<'a> I2CClient for Hts221<'a> { } else { buffer[0] = STATUS_REG; - if let Err((_error, buffer)) = self.i2c.write_read(buffer, 1, 1) { + if let Err((error, buffer)) = self.i2c.write_read(buffer, 1, 1) { self.state.set(State::Idle( CalibrationData { temp_slope: 0.0, @@ -310,7 +318,8 @@ impl<'a> I2CClient for Hts221<'a> { 0, )); self.buffer.replace(buffer); - self.temperature_client.map(|client| client.callback(0)); + self.temperature_client + .map(|client| client.callback(Err(error.into()))); self.humidity_client.map(|client| client.callback(0)); } } @@ -324,7 +333,7 @@ impl<'a> I2CClient for Hts221<'a> { let temperature_raw = ((buffer[2] as i16) | ((buffer[3] as i16) << 8)) as f32; let temperature = ((temperature_raw * calibration_data.temp_slope + calibration_data.temp_intercept) - * 100.0) as usize; + * 100.0) as i32; buffer[0] = CTRL_REG1; // TODO(alevy): this is a workaround for a bug. We should be able to turn // off the the sensor between transactions, and turn it back on (as is done @@ -333,7 +342,7 @@ impl<'a> I2CClient for Hts221<'a> { // now, leave it on and waste 2uA. buffer[1] = 1 << 7; // Leave PD bit on - if let Err((_error, buffer)) = self.i2c.write(buffer, 2) { + if let Err((error, buffer)) = self.i2c.write(buffer, 2) { self.state.set(State::Idle( CalibrationData { temp_slope: 0.0, @@ -345,7 +354,8 @@ impl<'a> I2CClient for Hts221<'a> { 0, )); self.buffer.replace(buffer); - self.temperature_client.map(|client| client.callback(0)); + self.temperature_client + .map(|client| client.callback(Err(error.into()))); self.humidity_client.map(|client| client.callback(0)); } else { self.state @@ -358,7 +368,7 @@ impl<'a> I2CClient for Hts221<'a> { if self.pending_temperature.get() { self.pending_temperature.set(false); self.temperature_client - .map(|client| client.callback(temperature)); + .map(|client| client.callback(Ok(temperature))); } if self.pending_humidity.get() { self.pending_humidity.set(false); diff --git a/capsules/src/humidity.rs b/capsules/extra/src/humidity.rs similarity index 75% rename from capsules/src/humidity.rs rename to capsules/extra/src/humidity.rs index 008f99d6b6..5d15f4e027 100644 --- a/capsules/src/humidity.rs +++ b/capsules/extra/src/humidity.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides userspace with access to humidity sensors. //! //! Userspace Interface @@ -21,16 +25,15 @@ //! The `command` system call support one argument `cmd` which is used to specify the specific //! operation, currently the following cmd's are supported: //! -//! * `0`: check whether the driver exist +//! * `0`: check whether the driver exists //! * `1`: read humidity //! //! //! The possible return from the 'command' system call indicates the following: //! //! * `Ok(())`: The operation has been successful. -//! * `BUSY`: The driver is busy. -//! * `ENOSUPPORT`: Invalid `cmd`. -//! * `NOMEM`: No sufficient memory available. +//! * `NOSUPPORT`: Invalid `cmd`. +//! * `NOMEM`: Insufficient memory available. //! * `INVAL`: Invalid address of the buffer or other error. //! //! Usage @@ -38,7 +41,7 @@ //! //! You need a device that provides the `hil::sensors::HumidityDriver` trait. //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! let humidity = static_init!( @@ -56,7 +59,7 @@ use kernel::syscall::{CommandReturn, SyscallDriver}; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::Humidity as usize; #[derive(Clone, Copy, PartialEq)] @@ -70,19 +73,19 @@ pub struct App { subscribed: bool, } -pub struct HumiditySensor<'a> { - driver: &'a dyn hil::sensors::HumidityDriver<'a>, +pub struct HumiditySensor<'a, H: hil::sensors::HumidityDriver<'a>> { + driver: &'a H, apps: Grant, AllowRoCount<0>, AllowRwCount<0>>, busy: Cell, } -impl<'a> HumiditySensor<'a> { +impl<'a, H: hil::sensors::HumidityDriver<'a>> HumiditySensor<'a, H> { pub fn new( - driver: &'a dyn hil::sensors::HumidityDriver<'a>, + driver: &'a H, grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, - ) -> HumiditySensor<'a> { + ) -> HumiditySensor<'a, H> { HumiditySensor { - driver: driver, + driver, apps: grant, busy: Cell::new(false), } @@ -92,16 +95,17 @@ impl<'a> HumiditySensor<'a> { &self, command: HumidityCommand, arg1: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { self.apps - .enter(appid, |app, _| { + .enter(processid, |app, _| { + app.subscribed = true; + if !self.busy.get() { - app.subscribed = true; self.busy.set(true); self.call_driver(command, arg1) } else { - CommandReturn::failure(ErrorCode::BUSY) + CommandReturn::success() } }) .unwrap_or_else(|err| CommandReturn::failure(err.into())) @@ -115,34 +119,37 @@ impl<'a> HumiditySensor<'a> { } } -impl hil::sensors::HumidityClient for HumiditySensor<'_> { - fn callback(&self, tmp_val: usize) { +impl<'a, H: hil::sensors::HumidityDriver<'a>> hil::sensors::HumidityClient + for HumiditySensor<'a, H> +{ + fn callback(&self, humidity_val: usize) { + self.busy.set(false); + for cntr in self.apps.iter() { cntr.enter(|app, upcalls| { if app.subscribed { - self.busy.set(false); app.subscribed = false; - upcalls.schedule_upcall(0, (tmp_val, 0, 0)).ok(); + upcalls.schedule_upcall(0, (humidity_val, 0, 0)).ok(); } }); } } } -impl SyscallDriver for HumiditySensor<'_> { +impl<'a, H: hil::sensors::HumidityDriver<'a>> SyscallDriver for HumiditySensor<'a, H> { fn command( &self, command_num: usize, arg1: usize, _: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { match command_num { - // check whether the driver exist!! + // driver existence check 0 => CommandReturn::success(), // single humidity measurement - 1 => self.enqueue_command(HumidityCommand::ReadHumidity, arg1, appid), + 1 => self.enqueue_command(HumidityCommand::ReadHumidity, arg1, processid), _ => CommandReturn::failure(ErrorCode::NOSUPPORT), } diff --git a/capsules/extra/src/ieee802154/README.md b/capsules/extra/src/ieee802154/README.md new file mode 100644 index 0000000000..ba368a04bf --- /dev/null +++ b/capsules/extra/src/ieee802154/README.md @@ -0,0 +1,52 @@ +IEEE 802.15.4 Stack +=================== + +Tock supports two different implementations of an IEEE 802.15.4 stack in the +kernel. The first version implements packet framing and a MAC layer, virtualizes +the 15.4 interface, and provides a multi-programmable userspace interface. The second version +provides userspace with the ability to send/receive raw 802.15.4 frames as well as directly control the radio, and is appropriate for access by one process at a time. + +In-Kernel Stack +--------------- + +Stack overview: + +```text +┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ Syscall Interface +┌──────────────────────┐ +│ RadioDriver │ +└──────────────────────┘ +┄┄ ieee802154::device::MacDevice ┄┄ +┌──────────────────────┐ +│ VirtualMac │ +└──────────────────────┘ +┄┄ ieee802154::device::MacDevice ┄┄ +┌──────────────────────┐ +│ Framer │ +└──────────────────────┘ +┄┄ ieee802154::mac::Mac ┄┄ +┌──────────────────────┐ +│ MAC (ex: AwakeMac) │ +└──────────────────────┘ +┄┄ hil::radio::Radio ┄┄ +┌──────────────────────┐ +│ 802.15.4 Radio │ +└──────────────────────┘ +``` + + +Raw Stack +--------- + +Stack overview: + +```text +┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ Syscall Interface +┌─────────────────────────┐ +│ phy_driver::RadioDriver │ +└─────────────────────────┘ +┄┄ hil::radio::Radio ┄┄ +┌─────────────────────────┐ +│ 802.15.4 Radio │ +└─────────────────────────┘ +``` diff --git a/capsules/src/ieee802154/device.rs b/capsules/extra/src/ieee802154/device.rs similarity index 92% rename from capsules/src/ieee802154/device.rs rename to capsules/extra/src/ieee802154/device.rs index 1921defe19..a3f28c2ae1 100644 --- a/capsules/src/ieee802154/device.rs +++ b/capsules/extra/src/ieee802154/device.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! The contract satisfied by an implementation of an IEEE 802.15.4 MAC device. //! Any IEEE 802.15.4 MAC device should expose the following high-level //! functionality: @@ -87,7 +91,7 @@ pub trait TxClient { /// returned to the client here. /// - `acked`: Whether the transmission was acknowledged. /// - `result`: This is `Ok(())` if the frame was transmitted, - /// otherwise an error occured in the transmission pipeline. + /// otherwise an error occurred in the transmission pipeline. fn send_done(&self, spi_buf: &'static mut [u8], acked: bool, result: Result<(), ErrorCode>); } @@ -106,9 +110,17 @@ pub trait RxClient { /// - `header`: A fully-parsed representation of the MAC header, with the /// caveat that the auxiliary security header is still included if the frame /// was previously secured. + /// - `lqi`: The link quality indicator of the received frame. /// - `data_offset`: Offset of the data payload relative to /// `buf`, so that the payload of the frame is contained in /// `buf[data_offset..data_offset + data_len]`. /// - `data_len`: Length of the data payload - fn receive<'a>(&self, buf: &'a [u8], header: Header<'a>, data_offset: usize, data_len: usize); + fn receive<'a>( + &self, + buf: &'a [u8], + header: Header<'a>, + lqi: u8, + data_offset: usize, + data_len: usize, + ); } diff --git a/capsules/src/ieee802154/driver.rs b/capsules/extra/src/ieee802154/driver.rs similarity index 67% rename from capsules/src/ieee802154/driver.rs rename to capsules/extra/src/ieee802154/driver.rs index 4bc16492b3..a0bbf0db3d 100644 --- a/capsules/src/ieee802154/driver.rs +++ b/capsules/extra/src/ieee802154/driver.rs @@ -1,20 +1,67 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! IEEE 802.15.4 userspace interface for configuration and transmit/receive. //! //! Implements a userspace interface for sending and receiving IEEE 802.15.4 //! frames. Also provides a minimal list-based interface for managing keys and //! known link neighbors, which is needed for 802.15.4 security. +//! +//! The driver functionality can be divided into three aspects: sending +//! packets, receiving packets, and managing the 15.4 state (i.e. keys, neighbors, +//! buffers, addressing, etc). The general design and procedure for sending and +//! receiving is discussed below. +//! +//! Sending - The driver supports two modes of sending: Raw and Parse. In Raw mode, +//! the userprocess fully forms the 15.4 frame and passes it to the driver. In Parse +//! mode, the userprocess provides the payload and relevant metadata. From this +//! the driver forms the 15.4 header and secures the payload. To send a packet, +//! the userprocess issues the respective send command syscall (corresponding to +//! raw or parse mode of sending). The 15.4 capsule will then schedule an upcall, +//! upon completion of the transmission, to notify the process. +//! +//! Receiving - The driver receives 15.4 frames and passes them to the userprocess. +//! To accomplish this, the userprocess must first `allow` a read/write ring buffer +//! to the kernel. The kernel will then fill this buffer with received frames and +//! schedule an upcall upon receipt of the first packet. When handling the upcall +//! the userprocess must first `unallow` the buffer as described in section 4.4 of +//! TRD104-syscalls. After unallowing the buffer, the userprocess must then immediately +//! clear all pending/scheduled receive upcalls. This is done by either unsubscribing +//! the receive upcall or subscribing a new receive upcall. Because the userprocess +//! provides the buffer, it is responsible for adhering to this procedure. Failure +//! to comply may result in dropped or malformed packets. +//! +//! The ring buffer provided by the userprocess must be of the form: +//! +//! ```text +//! | read index | write index | user_frame 0 | user_frame 1 | ... | user_frame n | +//! ``` +//! +//! `user_frame` denotes the 15.4 frame in addition to the relevant 3 bytes of +//! metadata (offset to data payload, length of data payload, and the MIC len). The +//! capsule assumes that this is the form of the buffer. Errors or deviation in +//! the form of the provided buffer will likely result in incomplete or dropped packets. +//! +//! Because the scheduled receive upcall must be handled by the userprocess, there is +//! no guarantee as to when this will occur and if additional packets will be received +//! prior to the upcall being handled. Without a ring buffer (or some equivalent data +//! structure), the original packet will be lost. The ring buffer allows for the upcall +//! to be scheduled and for all received packets to be passed to the process. The ring +//! buffer is designed to overwrite old packets if the buffer becomes full. If the +//! userprocess notices a high number of "dropped" packets, this may be the cause. The +//! userproceess can mitigate this issue by increasing the size of the ring buffer +//! provided to the capsule. use crate::ieee802154::{device, framer}; -use crate::net::ieee802154::{AddressMode, Header, KeyId, MacAddress, PanID, SecurityLevel}; +use crate::net::ieee802154::{Header, KeyId, MacAddress, SecurityLevel}; use crate::net::stream::{decode_bytes, decode_u8, encode_bytes, encode_u8, SResult}; use core::cell::Cell; -use core::cmp::min; -use kernel::dynamic_deferred_call::{ - DeferredCallHandle, DynamicDeferredCall, DynamicDeferredCallClient, -}; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::hil::radio; use kernel::processbuffer::{ReadableProcessBuffer, WriteableProcessBuffer}; use kernel::syscall::{CommandReturn, SyscallDriver}; use kernel::utilities::cells::{MapCell, OptionalCell, TakeCell}; @@ -23,39 +70,50 @@ use kernel::{ErrorCode, ProcessId}; const MAX_NEIGHBORS: usize = 4; const MAX_KEYS: usize = 4; +const USER_FRAME_METADATA_SIZE: usize = 3; // 3B metadata (offset, len, mic_len) +const USER_FRAME_MAX_SIZE: usize = USER_FRAME_METADATA_SIZE + radio::MAX_FRAME_SIZE; // 3B metadata + 127B max payload + +/// IDs for subscribed upcalls. +mod upcall { + /// Frame is received + pub const FRAME_RECEIVED: usize = 0; + /// Frame is transmitted + pub const FRAME_TRANSMITTED: usize = 1; + /// Number of upcalls. + pub const COUNT: u8 = 2; +} + /// Ids for read-only allow buffers mod ro_allow { + /// Write buffer. Contains the frame payload to be transmitted. pub const WRITE: usize = 0; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; + pub const COUNT: u8 = 1; } /// Ids for read-write allow buffers mod rw_allow { + /// Read buffer. Will contain the received frame. pub const READ: usize = 0; + /// Config buffer. + /// + /// Used to contain miscellaneous data associated with some commands because + /// the system call parameters / return codes are not enough to convey the + /// desired information. pub const CFG: usize = 1; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 2; + pub const COUNT: u8 = 2; } -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::Ieee802154 as usize; -#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] struct DeviceDescriptor { short_addr: u16, long_addr: [u8; 8], } -impl Default for DeviceDescriptor { - fn default() -> Self { - DeviceDescriptor { - short_addr: 0, - long_addr: [0; 8], - } - } -} - /// The Key ID mode mapping expected by the userland driver #[repr(u8)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] @@ -156,14 +214,7 @@ impl KeyDescriptor { let (_, key_id) = dec_try!(buf, 1; decode_key_id); let mut key = [0u8; 16]; let off = dec_consume!(buf, 11; decode_bytes, &mut key); - stream_done!( - off, - KeyDescriptor { - level: level, - key_id: key_id, - key: key, - } - ); + stream_done!(off, KeyDescriptor { level, key_id, key }); } } @@ -172,9 +223,9 @@ pub struct App { pending_tx: Option<(u16, Option<(SecurityLevel, KeyId)>)>, } -pub struct RadioDriver<'a> { +pub struct RadioDriver<'a, M: device::MacDevice<'a>> { /// Underlying MAC device, possibly multiplexed - mac: &'a dyn device::MacDevice<'a>, + mac: &'a M, /// List of (short address, long address) pairs representing IEEE 802.15.4 /// neighbors. @@ -191,7 +242,7 @@ pub struct RadioDriver<'a> { /// Grant of apps that use this radio driver. apps: Grant< App, - UpcallCount<2>, + UpcallCount<{ upcall::COUNT }>, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<{ rw_allow::COUNT }>, >, @@ -202,31 +253,33 @@ pub struct RadioDriver<'a> { kernel_tx: TakeCell<'static, [u8]>, /// Used to ensure callbacks are delivered during upcalls - deferred_caller: &'a DynamicDeferredCall, - - /// Also used for deferred calls - handle: OptionalCell, + deferred_call: DeferredCall, /// Used to deliver callbacks to the correct app during deferred calls - saved_appid: OptionalCell, + saved_processid: OptionalCell, /// Used to save result for passing a callback from a deferred call. saved_result: OptionalCell>, + + /// Used to allow Thread to specify a key procedure for 15.4 to use for link layer encryption + backup_key_procedure: OptionalCell<&'a dyn framer::KeyProcedure>, + + /// Used to allow Thread to specify the 15.4 device procedure as used in nonce generation + backup_device_procedure: OptionalCell<&'a dyn framer::DeviceProcedure>, } -impl<'a> RadioDriver<'a> { +impl<'a, M: device::MacDevice<'a>> RadioDriver<'a, M> { pub fn new( - mac: &'a dyn device::MacDevice<'a>, + mac: &'a M, grant: Grant< App, - UpcallCount<2>, + UpcallCount<{ upcall::COUNT }>, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<{ rw_allow::COUNT }>, >, kernel_tx: &'static mut [u8], - deferred_caller: &'a DynamicDeferredCall, - ) -> RadioDriver<'a> { - RadioDriver { + ) -> Self { + Self { mac, neighbors: MapCell::new(Default::default()), num_neighbors: Cell::new(0), @@ -235,15 +288,20 @@ impl<'a> RadioDriver<'a> { apps: grant, current_app: OptionalCell::empty(), kernel_tx: TakeCell::new(kernel_tx), - deferred_caller, - saved_appid: OptionalCell::empty(), + deferred_call: DeferredCall::new(), + saved_processid: OptionalCell::empty(), saved_result: OptionalCell::empty(), - handle: OptionalCell::empty(), + backup_key_procedure: OptionalCell::empty(), + backup_device_procedure: OptionalCell::empty(), } } - pub fn initialize_callback_handle(&self, handle: DeferredCallHandle) { - self.handle.replace(handle); + pub fn set_key_procedure(&self, key_procedure: &'a dyn framer::KeyProcedure) { + self.backup_key_procedure.set(key_procedure); + } + + pub fn set_device_procedure(&self, device_procedure: &'a dyn framer::DeviceProcedure) { + self.backup_device_procedure.set(device_procedure); } // Neighbor management functions @@ -364,10 +422,10 @@ impl<'a> RadioDriver<'a> { } let mut pending_app = None; for app in self.apps.iter() { - let appid = app.processid(); + let processid = app.processid(); app.enter(|app, _| { if app.pending_tx.is_some() { - pending_app = Some(appid); + pending_app = Some(processid); } }); if pending_app.is_some() { @@ -377,26 +435,26 @@ impl<'a> RadioDriver<'a> { pending_app } - /// Performs `appid`'s pending transmission asynchronously. If the + /// Performs `processid`'s pending transmission asynchronously. If the /// transmission is not successful, the error is returned to the app via its /// `tx_callback`. Assumes that the driver is currently idle and the app has /// a pending transmission. #[inline] - fn perform_tx_async(&self, appid: ProcessId) { - let result = self.perform_tx_sync(appid); + fn perform_tx_async(&self, processid: ProcessId) { + let result = self.perform_tx_sync(processid); if result != Ok(()) { - self.saved_appid.set(appid); + self.saved_processid.set(processid); self.saved_result.set(result); - self.handle.map(|handle| self.deferred_caller.set(*handle)); + self.deferred_call.set(); } } - /// Performs `appid`'s pending transmission synchronously. The result is + /// Performs `processid`'s pending transmission synchronously. The result is /// returned immediately to the app. Assumes that the driver is currently /// idle and the app has a pending transmission. #[inline] - fn perform_tx_sync(&self, appid: ProcessId) -> Result<(), ErrorCode> { - self.apps.enter(appid, |app, kerel_data| { + fn perform_tx_sync(&self, processid: ProcessId) -> Result<(), ErrorCode> { + self.apps.enter(processid, |app, kerel_data| { let (dst_addr, security_needed) = match app.pending_tx.take() { Some(pending_tx) => pending_tx, None => { @@ -442,7 +500,7 @@ impl<'a> RadioDriver<'a> { } }); if result == Ok(()) { - self.current_app.set(appid); + self.current_app.set(processid); } result })? @@ -453,7 +511,7 @@ impl<'a> RadioDriver<'a> { #[inline] fn do_next_tx_async(&self) { self.get_next_tx_if_idle() - .map(|appid| self.perform_tx_async(appid)); + .map(|processid| self.perform_tx_async(processid)); } /// Schedule the next transmission if there is one pending. If the next @@ -462,27 +520,27 @@ impl<'a> RadioDriver<'a> { /// On the other hand, if it is some other app, then return any errors via /// callbacks. #[inline] - fn do_next_tx_sync(&self, new_appid: ProcessId) -> Result<(), ErrorCode> { - self.get_next_tx_if_idle().map_or(Ok(()), |appid| { - if appid == new_appid { - self.perform_tx_sync(appid) + fn do_next_tx_sync(&self, new_processid: ProcessId) -> Result<(), ErrorCode> { + self.get_next_tx_if_idle().map_or(Ok(()), |processid| { + if processid == new_processid { + self.perform_tx_sync(processid) } else { - self.perform_tx_async(appid); + self.perform_tx_async(processid); Ok(()) } }) } } -impl DynamicDeferredCallClient for RadioDriver<'_> { - fn call(&self, _handle: DeferredCallHandle) { +impl<'a, M: device::MacDevice<'a>> DeferredCallClient for RadioDriver<'a, M> { + fn handle_deferred_call(&self) { let _ = self .apps - .enter(self.saved_appid.unwrap_or_panic(), |_app, upcalls| { - // Unwrap fail = missing appid + .enter(self.saved_processid.unwrap_or_panic(), |_app, upcalls| { + // Unwrap fail = missing processid upcalls .schedule_upcall( - 1, + upcall::FRAME_TRANSMITTED, ( kernel::errorcode::into_statuscode( self.saved_result.unwrap_or_panic(), // Unwrap fail = missing result @@ -494,61 +552,69 @@ impl DynamicDeferredCallClient for RadioDriver<'_> { .ok(); }); } + + fn register(&'static self) { + self.deferred_call.register(self); + } } -impl framer::DeviceProcedure for RadioDriver<'_> { +impl<'a, M: device::MacDevice<'a>> framer::DeviceProcedure for RadioDriver<'a, M> { /// Gets the long address corresponding to the neighbor that matches the given /// MAC address. If no such neighbor exists, returns `None`. fn lookup_addr_long(&self, addr: MacAddress) -> Option<[u8; 8]> { - self.neighbors.and_then(|neighbors| { - neighbors[..self.num_neighbors.get()] - .iter() - .find(|neighbor| match addr { - MacAddress::Short(addr) => addr == neighbor.short_addr, - MacAddress::Long(addr) => addr == neighbor.long_addr, - }) - .map(|neighbor| neighbor.long_addr) - }) + self.neighbors + .and_then(|neighbors| { + neighbors[..self.num_neighbors.get()] + .iter() + .find(|neighbor| match addr { + MacAddress::Short(addr) => addr == neighbor.short_addr, + MacAddress::Long(addr) => addr == neighbor.long_addr, + }) + .map(|neighbor| neighbor.long_addr) + }) + .map_or_else( + // This serves the same purpose as the KeyProcedure lookup (see comment). + // This is kept as a remnant of 15.4, but should potentially be removed moving forward + // as Thread does not have a use to add a Device procedure. + || { + self.backup_device_procedure + .and_then(|procedure| procedure.lookup_addr_long(addr)) + }, + |res| Some(res), + ) } } -impl framer::KeyProcedure for RadioDriver<'_> { +impl<'a, M: device::MacDevice<'a>> framer::KeyProcedure for RadioDriver<'a, M> { /// Gets the key corresponding to the key that matches the given security /// level `level` and key ID `key_id`. If no such key matches, returns /// `None`. fn lookup_key(&self, level: SecurityLevel, key_id: KeyId) -> Option<[u8; 16]> { - self.keys.and_then(|keys| { - keys[..self.num_keys.get()] - .iter() - .find(|key| key.level == level && key.key_id == key_id) - .map(|key| key.key) - }) + self.keys + .and_then(|keys| { + keys[..self.num_keys.get()] + .iter() + .find(|key| key.level == level && key.key_id == key_id) + .map(|key| key.key) + }) + .map_or_else( + // Thread needs to add a MAC key to the 15.4 network keys so that the 15.4 framer + // can decrypt incoming Thread 15.4 frames. The backup_device_procedure was added + // so that if the lookup procedure failed to find a key here, it would check a + // "backup" procedure (Thread in this case). This is somewhat clunky and removing + // the network keys being stored in the 15.4 driver is a longer term TODO. + || { + self.backup_key_procedure.and_then(|procedure| { + // TODO: security_level / keyID are hardcoded for now + procedure.lookup_key(SecurityLevel::EncMic32, KeyId::Index(2)) + }) + }, + |res| Some(res), + ) } } -impl SyscallDriver for RadioDriver<'_> { - /// Setup buffers to read/write from. - /// - /// ### `allow_num` - /// - /// - `0`: Read buffer. Will contain the received frame. - /// - `1`: Config buffer. Used to contain miscellaneous data associated with - /// some commands because the system call parameters / return codes are - /// not enough to convey the desired information. - - /// Setup shared buffers. - /// - /// ### `allow_num` - /// - /// - `0`: Write buffer. Contains the frame payload to be transmitted. - - // Setup callbacks. - // - // ### `subscribe_num` - // - // - `0`: Setup callback for when frame is received. - // - `1`: Setup callback for when frame is transmitted. - +impl<'a, M: device::MacDevice<'a>> SyscallDriver for RadioDriver<'a, M> { /// IEEE 802.15.4 MAC device control. /// /// For some of the below commands, one 32-bit argument is not enough to @@ -561,7 +627,7 @@ impl SyscallDriver for RadioDriver<'_> { /// /// ### `command_num` /// - /// - `0`: Driver check. + /// - `0`: Driver existence check. /// - `1`: Return radio status. Ok(())/OFF = on/off. /// - `2`: Set short MAC address. /// - `3`: Set long MAC address. @@ -592,18 +658,22 @@ impl SyscallDriver for RadioDriver<'_> { /// up to 9 bytes: the key ID. /// - `23`: Get the key at an index. /// app_cfg (out): 16 bytes: the key. - /// - `24`: Add a new key with the given descripton. + /// - `24`: Add a new key with the given description. /// app_cfg (in): 1 byte: the security level + /// 1 byte: the key ID mode + /// 9 bytes: the key ID (might not use all bytes) + /// 16 bytes: the key. /// - `25`: Remove the key at an index. + /// - `26`: Transmit a frame (parse required). Take the provided payload and + /// parameters to encrypt, form headers, and transmit the frame. + /// - `28`: Set long address. + /// - `29`: Get the long MAC address. fn command( &self, command_number: usize, arg1: usize, - _: usize, - appid: ProcessId, + arg2: usize, + processid: ProcessId, ) -> CommandReturn { match command_number { 0 => CommandReturn::success(), @@ -620,7 +690,7 @@ impl SyscallDriver for RadioDriver<'_> { } 3 => self .apps - .enter(appid, |_, kernel_data| { + .enter(processid, |_, kernel_data| { kernel_data .get_readwrite_processbuffer(rw_allow::CFG) .and_then(|cfg| { @@ -656,7 +726,7 @@ impl SyscallDriver for RadioDriver<'_> { } 9 => self .apps - .enter(appid, |_, kernel_data| { + .enter(processid, |_, kernel_data| { kernel_data .get_readwrite_processbuffer(rw_allow::CFG) .and_then(|cfg| { @@ -695,7 +765,7 @@ impl SyscallDriver for RadioDriver<'_> { }), 16 => self .apps - .enter(appid, |_, kernel_data| { + .enter(processid, |_, kernel_data| { kernel_data .get_readwrite_processbuffer(rw_allow::CFG) .and_then(|cfg| { @@ -717,7 +787,7 @@ impl SyscallDriver for RadioDriver<'_> { .unwrap_or_else(|err| CommandReturn::failure(err.into())), 17 => self .apps - .enter(appid, |_, kernel_data| { + .enter(processid, |_, kernel_data| { kernel_data .get_readwrite_processbuffer(rw_allow::CFG) .and_then(|cfg| { @@ -740,7 +810,7 @@ impl SyscallDriver for RadioDriver<'_> { .unwrap_or_else(|err| CommandReturn::failure(err.into())), 18 => match self.remove_neighbor(arg1) { - Ok(_) => CommandReturn::success(), + Ok(()) => CommandReturn::success(), Err(e) => CommandReturn::failure(e), }, 19 => { @@ -758,7 +828,7 @@ impl SyscallDriver for RadioDriver<'_> { }), 22 => self .apps - .enter(appid, |_, kernel_data| { + .enter(processid, |_, kernel_data| { kernel_data .get_readwrite_processbuffer(rw_allow::CFG) .and_then(|cfg| { @@ -784,7 +854,7 @@ impl SyscallDriver for RadioDriver<'_> { .unwrap_or_else(|err| CommandReturn::failure(err.into())), 23 => self .apps - .enter(appid, |_, kernel_data| { + .enter(processid, |_, kernel_data| { kernel_data .get_readwrite_processbuffer(rw_allow::CFG) .and_then(|cfg| { @@ -806,7 +876,7 @@ impl SyscallDriver for RadioDriver<'_> { .unwrap_or_else(|err| CommandReturn::failure(err.into())), 24 => self .apps - .enter(appid, |_, kernel_data| { + .enter(processid, |_, kernel_data| { kernel_data .get_readwrite_processbuffer(rw_allow::CFG) .and_then(|cfg| { @@ -836,7 +906,7 @@ impl SyscallDriver for RadioDriver<'_> { 25 => self.remove_key(arg1).into(), 26 => { self.apps - .enter(appid, |app, kernel_data| { + .enter(processid, |app, kernel_data| { if app.pending_tx.is_some() { // Cannot support more than one pending tx per process. return Err(ErrorCode::BUSY); @@ -881,11 +951,22 @@ impl SyscallDriver for RadioDriver<'_> { .map_or_else( |err| CommandReturn::failure(err.into()), |setup_tx| match setup_tx { - Ok(_) => self.do_next_tx_sync(appid).into(), - Err(e) => CommandReturn::failure(e.into()), + Ok(()) => self.do_next_tx_sync(processid).into(), + Err(e) => CommandReturn::failure(e), }, ) } + 28 => { + let addr_upper: u64 = arg2 as u64; + let addr_lower: u64 = arg1 as u64; + let addr = addr_upper << 32 | addr_lower; + self.mac.set_address_long(addr.to_be_bytes()); + CommandReturn::success() + } + 29 => { + let addr = u64::from_be_bytes(self.mac.get_address_long()); + CommandReturn::success_u64(addr) + } _ => CommandReturn::failure(ErrorCode::NOSUPPORT), } } @@ -895,14 +976,14 @@ impl SyscallDriver for RadioDriver<'_> { } } -impl device::TxClient for RadioDriver<'_> { +impl<'a, M: device::MacDevice<'a>> device::TxClient for RadioDriver<'a, M> { fn send_done(&self, spi_buf: &'static mut [u8], acked: bool, result: Result<(), ErrorCode>) { self.kernel_tx.replace(spi_buf); - self.current_app.take().map(|appid| { - let _ = self.apps.enter(appid, |_app, upcalls| { + self.current_app.take().map(|processid| { + let _ = self.apps.enter(processid, |_app, upcalls| { upcalls .schedule_upcall( - 1, + upcall::FRAME_TRANSMITTED, ( kernel::errorcode::into_statuscode(result), acked as usize, @@ -916,46 +997,97 @@ impl device::TxClient for RadioDriver<'_> { } } -/// Encode two PAN IDs into a single usize. -#[inline] -fn encode_pans(dst_pan: &Option, src_pan: &Option) -> usize { - ((dst_pan.unwrap_or(0) as usize) << 16) | (src_pan.unwrap_or(0) as usize) -} - -/// Encodes as much as possible about an address into a single usize. -#[inline] -fn encode_address(addr: &Option) -> usize { - let short_addr_only = match *addr { - Some(MacAddress::Short(addr)) => addr as usize, - _ => 0, - }; - ((AddressMode::from(addr) as usize) << 16) | short_addr_only -} - -impl device::RxClient for RadioDriver<'_> { - fn receive<'b>(&self, buf: &'b [u8], header: Header<'b>, data_offset: usize, data_len: usize) { +impl<'a, M: device::MacDevice<'a>> device::RxClient for RadioDriver<'a, M> { + fn receive<'b>( + &self, + buf: &'b [u8], + header: Header<'b>, + lqi: u8, + data_offset: usize, + data_len: usize, + ) { self.apps.each(|_, _, kernel_data| { let read_present = kernel_data .get_readwrite_processbuffer(rw_allow::READ) .and_then(|read| { read.mut_enter(|rbuf| { - let len = min(rbuf.len(), data_offset + data_len); - // Copy the entire frame over to userland, preceded by two - // bytes: the data offset and the data length. - rbuf[..len].copy_from_slice(&buf[..len]); - rbuf[0].set(data_offset as u8); - rbuf[1].set(data_len as u8); + /////////////////////////////////////////////////////////////////////////////////////////// + // NOTE: context for the ring buffer and assumptions regarding the ring buffer + // format and usage can be found in the detailed comment at the top of this file. + // Ring buffer format: + // | read index | write index | user_frame 0 | user_frame 1 | ... | user_frame n | + // user_frame format: + // | header_len | payload_len | mic_len | 15.4 frame | + /////////////////////////////////////////////////////////////////////////////////////////// + + // 2 bytes for the readwrite buffer metadata (read / write index) + const RING_BUF_METADATA_SIZE: usize = 2; + + // Confirm the availability of the buffer. A buffer of len 0 is indicative + // of the userprocess not allocating a readwrite buffer. We must also + // confirm that the userprocess correctly formatted the buffer to be of length + // 2 + n * USER_FRAME_MAX_SIZE, where n is the number of user frames that the + // buffer can store. We combine checking the buffer's non-zero length and the + // case of the buffer being shorter than the `RING_BUF_METADATA_SIZE` as an + // invalid buffer (e.g. of length 1) may otherwise errantly pass the second + // conditional check (due to unsigned integer arithmetic). + if rbuf.len() <= RING_BUF_METADATA_SIZE + || (rbuf.len() - RING_BUF_METADATA_SIZE) % USER_FRAME_MAX_SIZE != 0 + { + // kernel::debug!("[15.4 Driver] Error - improperly formatted readwrite buffer provided"); + return false; + } + + let mic_len = header.security.map_or(0, |sec| sec.level.mic_len()); + let frame_len = data_offset + data_len + mic_len; + + let mut read_index = rbuf[0].get() as usize; + let mut write_index = rbuf[1].get() as usize; + + let max_pending_rx = + (rbuf.len() - RING_BUF_METADATA_SIZE) / USER_FRAME_MAX_SIZE; + + // confirm user modifiable metadata is valid (i.e. within bounds of the provided buffer) + if read_index >= max_pending_rx || write_index >= max_pending_rx { + // kernel::debug!("[15.4 driver] Invalid read or write index"); + return false; + } + + let offset = RING_BUF_METADATA_SIZE + (write_index * USER_FRAME_MAX_SIZE); + + // Copy the entire frame over to userland, preceded by three metadata bytes: + // the header length, the data length, and the MIC length. + rbuf[(offset + USER_FRAME_METADATA_SIZE) + ..(offset + frame_len + USER_FRAME_METADATA_SIZE)] + .copy_from_slice(&buf[..frame_len]); + + rbuf[offset].set(data_offset as u8); + rbuf[offset + 1].set(data_len as u8); + rbuf[offset + 2].set(mic_len as u8); + + // Prepare the ring buffer for the next write. The current design favors newness; + // newly received packets will begin to overwrite the oldest data in the event + // of the buffer becoming full. The read index must always point to the "oldest" + // data. If we have overwritten the oldest data, the next oldest data is now at + // the read index + 1. We must update the read index to reflect this. + write_index = (write_index + 1) % max_pending_rx; + if write_index == read_index { + read_index = (read_index + 1) % max_pending_rx; + rbuf[0].set(read_index as u8); + // kernel::debug!("[15.4 driver] Provided RX buffer is full"); + } + + // update write index metadata (we do not modify the read index + // in the recv functionality so we do not need to update this metadata) + rbuf[1].set(write_index as u8); true }) }) .unwrap_or(false); if read_present { - // Encode useful parts of the header in 3 usizes - let pans = encode_pans(&header.dst_pan, &header.src_pan); - let dst_addr = encode_address(&header.dst_addr); - let src_addr = encode_address(&header.src_addr); + // Place lqi as argument to be included in upcall. kernel_data - .schedule_upcall(0, (pans, dst_addr, src_addr)) + .schedule_upcall(upcall::FRAME_RECEIVED, (lqi as usize, 0, 0)) .ok(); } }); diff --git a/capsules/src/ieee802154/framer.rs b/capsules/extra/src/ieee802154/framer.rs similarity index 75% rename from capsules/src/ieee802154/framer.rs rename to capsules/extra/src/ieee802154/framer.rs index 91c05bdaf7..b5a8674bea 100644 --- a/capsules/src/ieee802154/framer.rs +++ b/capsules/extra/src/ieee802154/framer.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implements IEEE 802.15.4 MAC device abstraction over a 802.15.4 MAC interface. //! Allows its users to prepare and send frames in plaintext, handling 802.15.4 //! encoding and security procedures (in the future) transparently. @@ -15,7 +19,7 @@ //! `capsules::ieee802154::mac::Mac`. Suppose we have such an implementation of type //! `XMacDevice`. //! -//! ```rust +//! ```rust,ignore //! let xmac: &XMacDevice = /* ... */; //! let mac_device = static_init!( //! capsules::ieee802154::mac::Framer<'static, XMacDevice>, @@ -28,7 +32,7 @@ //! The `mac_device` device is now set up. Users of the MAC device can now //! configure the underlying radio, prepare and send frames: //! -//! ```rust +//! ```rust,ignore //! mac_device.set_pan(0xABCD); //! mac_device.set_address(0x1008); //! mac_device.config_commit(); @@ -53,7 +57,7 @@ //! You should also be able to set up the userspace driver for receiving/sending //! 802.15.4 frames: //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! let radio_capsule = static_init!( @@ -81,10 +85,11 @@ use crate::net::stream::{encode_bytes, encode_u32, encode_u8}; use core::cell::Cell; -use kernel::hil::radio; +use kernel::hil::radio::{self, LQI_SIZE}; use kernel::hil::symmetric_encryption::{CCMClient, AES128CCM}; use kernel::processbuffer::ReadableProcessSlice; use kernel::utilities::cells::{MapCell, OptionalCell}; +use kernel::utilities::leasable_buffer::SubSliceMut; use kernel::ErrorCode; /// A `Frame` wraps a static mutable byte slice and keeps just enough @@ -129,7 +134,7 @@ impl Frame { /// Calculates how much more data this frame can hold pub fn remaining_data_capacity(&self) -> usize { - self.buf.len() - radio::PSDU_OFFSET - radio::MFR_SIZE - self.info.secured_length() + self.buf.len() - self.info.secured_length() } /// Appends payload bytes into the frame if possible @@ -137,7 +142,7 @@ impl Frame { if payload.len() > self.remaining_data_capacity() { return Err(ErrorCode::NOMEM); } - let begin = radio::PSDU_OFFSET + self.info.unsecured_length(); + let begin = self.info.unsecured_length(); self.buf[begin..begin + payload.len()].copy_from_slice(payload); self.info.data_len += payload.len(); @@ -153,7 +158,7 @@ impl Frame { if payload_buf.len() > self.remaining_data_capacity() { return Err(ErrorCode::NOMEM); } - let begin = radio::PSDU_OFFSET + self.info.unsecured_length(); + let begin = self.info.unsecured_length(); payload_buf.copy_to_slice(&mut self.buf[begin..begin + payload_buf.len()]); self.info.data_len += payload_buf.len(); @@ -207,16 +212,19 @@ impl FrameInfo { (self.unsecured_length(), 0) } else { // Otherwise, a data is the header and the open payload, and - // m data is the private payload field + // m data is the private payload field; unsecured length is the end of + // private payload, length of private payload is difference between + // the offset and unsecured length ( - private_payload_offset, - self.unsecured_length() | private_payload_offset, + private_payload_offset, // m_offset + self.unsecured_length() - private_payload_offset, // m_len ) } } } -fn get_ccm_nonce(device_addr: &[u8; 8], frame_counter: u32, level: SecurityLevel) -> [u8; 13] { +/// Generate a 15.4 CCM nonce from the device address, frame counter, and SecurityLevel +pub fn get_ccm_nonce(device_addr: &[u8; 8], frame_counter: u32, level: SecurityLevel) -> [u8; 13] { let mut nonce = [0u8; 13]; let encode_ccm_nonce = |buf: &mut [u8]| { let off = enc_consume!(buf; encode_bytes, device_addr.as_ref()); @@ -248,7 +256,7 @@ pub const CRYPT_BUF_SIZE: usize = radio::MAX_MTU + 3 * 16; /// implicitly with some equivalent logic. pub trait KeyProcedure { /// Lookup the KeyDescriptor matching the provided security level and key ID - /// mode and return the key associatied with it. + /// mode and return the key associated with it. fn lookup_key(&self, level: SecurityLevel, key_id: KeyId) -> Option<[u8; 16]>; } @@ -293,16 +301,16 @@ enum RxState { /// There is no frame that has been received. Idle, /// There is a secured frame that needs to be decrypted. - ReadyToDecrypt(FrameInfo, &'static mut [u8]), + /// ReadyToDecrypt(FrameInfo, buf, lqi) + ReadyToDecrypt(FrameInfo, &'static mut [u8], u8), /// A secured frame is currently being decrypted by the decryption facility. + /// Decrypting(FrameInfo, lqi) #[allow(dead_code)] - Decrypting(FrameInfo), + Decrypting(FrameInfo, u8), /// There is an unsecured frame that needs to be re-parsed and exposed to - /// the client. + /// the client. ReadyToYield(FrameInfo, buf, lqi) #[allow(dead_code)] - ReadyToYield(FrameInfo, &'static mut [u8]), - /// The buffer containing the frame needs to be returned to the radio. - ReadyToReturn(&'static mut [u8]), + ReadyToYield(FrameInfo, &'static mut [u8], u8), } /// This struct wraps an IEEE 802.15.4 radio device `kernel::hil::radio::Radio` @@ -312,7 +320,7 @@ enum RxState { /// machines corresponding to the transmission, reception and /// encryption/decryption pipelines. See the documentation in /// `capsules/src/mac.rs` for more details. -pub struct Framer<'a, M: Mac, A: AES128CCM<'a>> { +pub struct Framer<'a, M: Mac<'a>, A: AES128CCM<'a>> { mac: &'a M, aes_ccm: &'a A, data_sequence: Cell, @@ -322,7 +330,7 @@ pub struct Framer<'a, M: Mac, A: AES128CCM<'a>> { /// DeviceDescriptor lookup procedure device_procedure: OptionalCell<&'a dyn DeviceProcedure>, - /// Transmision pipeline state. This should never be `None`, except when + /// Transmission pipeline state. This should never be `None`, except when /// transitioning between states. That is, any method that consumes the /// current state should always remember to replace it along with the /// associated state information. @@ -333,13 +341,18 @@ pub struct Framer<'a, M: Mac, A: AES128CCM<'a>> { /// `None`, except when transitioning between states. rx_state: MapCell, rx_client: OptionalCell<&'a dyn RxClient>, + crypt_buf: MapCell>, } -impl<'a, M: Mac, A: AES128CCM<'a>> Framer<'a, M, A> { - pub fn new(mac: &'a M, aes_ccm: &'a A) -> Framer<'a, M, A> { +impl<'a, M: Mac<'a>, A: AES128CCM<'a>> Framer<'a, M, A> { + pub fn new( + mac: &'a M, + aes_ccm: &'a A, + crypt_buf: SubSliceMut<'static, u8>, + ) -> Framer<'a, M, A> { Framer { - mac: mac, - aes_ccm: aes_ccm, + mac, + aes_ccm, data_sequence: Cell::new(0), key_procedure: OptionalCell::empty(), device_procedure: OptionalCell::empty(), @@ -347,6 +360,7 @@ impl<'a, M: Mac, A: AES128CCM<'a>> Framer<'a, M, A> { tx_client: OptionalCell::empty(), rx_state: MapCell::new(RxState::Idle), rx_client: OptionalCell::empty(), + crypt_buf: MapCell::new(crypt_buf), } } @@ -360,22 +374,13 @@ impl<'a, M: Mac, A: AES128CCM<'a>> Framer<'a, M, A> { self.device_procedure.set(device_procedure); } - /// Look up the key using the IEEE 802.15.4 KeyDescriptor lookup prodecure + /// Look up the key using the IEEE 802.15.4 KeyDescriptor lookup procedure /// implemented elsewhere. fn lookup_key(&self, level: SecurityLevel, key_id: KeyId) -> Option<[u8; 16]> { self.key_procedure .and_then(|key_procedure| key_procedure.lookup_key(level, key_id)) } - /// Look up the extended address of a device using the IEEE 802.15.4 - /// DeviceDescriptor lookup prodecure implemented elsewhere. - fn lookup_addr_long(&self, src_addr: Option) -> Option<[u8; 8]> { - src_addr.and_then(|addr| { - self.device_procedure - .and_then(|device_procedure| device_procedure.lookup_addr_long(addr)) - }) - } - /// IEEE 802.15.4-2015, 9.2.1, outgoing frame security procedure /// Performs the first checks in the security procedure. The rest of the /// steps are performed as part of the transmission pipeline. @@ -399,13 +404,23 @@ impl<'a, M: Mac, A: AES128CCM<'a>> Framer<'a, M, A> { } /// IEEE 802.15.4-2015, 9.2.3, incoming frame security procedure - fn incoming_frame_security(&self, buf: &'static mut [u8], frame_len: usize) -> RxState { + fn incoming_frame_security( + &self, + buf: &'static mut [u8], + frame_len: usize, + lqi: u8, + ) -> RxState { // Try to decode the MAC header. Three possible results can occur: // 1) The frame should be dropped and the buffer returned to the radio // 2) The frame is unsecured. We immediately expose the frame to the // user and queue the buffer for returning to the radio. // 3) The frame needs to be unsecured. - let result = Header::decode(&buf[radio::PSDU_OFFSET..], false) + + // The buffer containing the 15.4 packet also contains the PSDU bytes and an LQI + // byte. We only pass the 15.4 packet up the stack and slice buf accordingly. + let frame_buffer = &buf[radio::PSDU_OFFSET..(buf.len() - LQI_SIZE)]; + + let result = Header::decode(frame_buffer, false) .done() .and_then(|(data_offset, (header, mac_payload_offset))| { // Note: there is a complication here regarding the offsets. @@ -439,11 +454,18 @@ impl<'a, M: Mac, A: AES128CCM<'a>> Framer<'a, M, A> { // specifies `KeyIdMode::Source4Index`, the source // address used for the nonce is actually a constant // defined in their spec - let device_addr = match self.lookup_addr_long(header.src_addr) { - Some(addr) => addr, + let device_addr = match header.src_addr { + Some(mac) => match mac { + MacAddress::Long(val) => val, + MacAddress::Short(_) => { + kernel::debug!("[15.4] DROPPED PACKET - error only short address provided on encrypted packet."); + return None + }, + }, None => { - return None; - } + kernel::debug!("[15.4] DROPPED PACKET - Malformed, no src address provided."); + return None + }, }; // Step g, h: Check frame counter @@ -467,25 +489,34 @@ impl<'a, M: Mac, A: AES128CCM<'a>> Framer<'a, M, A> { Some(FrameInfo { frame_type: header.frame_type, - mac_payload_offset: mac_payload_offset, - data_offset: data_offset, - data_len: data_len, - mic_len: mic_len, + mac_payload_offset, + data_offset, + data_len, + mic_len, security_params: Some((security.level, key, nonce)), }) } } else { // No security needed, can yield the frame immediately + + // The buffer containing the 15.4 packet also contains the PSDU bytes and an LQI + // byte. We only pass the 15.4 packet up the stack and slice buf accordingly. + let frame_buffer = &buf[radio::PSDU_OFFSET..(buf.len() - LQI_SIZE)]; self.rx_client.map(|client| { - client.receive(&buf, header, radio::PSDU_OFFSET + data_offset, data_len); + client.receive(frame_buffer, header, lqi, data_offset, data_len); }); None } }); match result { - None => RxState::ReadyToReturn(buf), - Some(frame_info) => RxState::ReadyToDecrypt(frame_info, buf), + None => { + // The packet was not encrypted, we completed the 15.4 framer procedure, and passed the packet to the + // client. We can now return the recv buffer to the radio driver and enter framer's idle state. + self.mac.set_receive_buffer(buf); + RxState::Idle + } + Some(frame_info) => RxState::ReadyToDecrypt(frame_info, buf, lqi), } } @@ -510,6 +541,7 @@ impl<'a, M: Mac, A: AES128CCM<'a>> Framer<'a, M, A> { let (a_off, m_off) = (radio::PSDU_OFFSET, radio::PSDU_OFFSET + m_off); + // Crypto setup failed; fail sending packet and return to idle if self.aes_ccm.set_key(&key) != Ok(()) || self.aes_ccm.set_nonce(&nonce) != Ok(()) { @@ -563,58 +595,107 @@ impl<'a, M: Mac, A: AES128CCM<'a>> Framer<'a, M, A> { /// Advances the reception pipeline if it can be advanced. fn step_receive_state(&self) { self.rx_state.take().map(|state| { - let (next_state, buf) = match state { - RxState::Idle => (RxState::Idle, None), - RxState::ReadyToDecrypt(info, buf) => { + let next_state = match state { + RxState::Idle => RxState::Idle, + RxState::ReadyToDecrypt(info, buf, lqi) => { match info.security_params { None => { // `ReadyToDecrypt` should only be entered when // `security_params` is not `None`. - (RxState::Idle, Some(buf)) + RxState::Idle } Some((level, key, nonce)) => { let (m_off, m_len) = info.ccm_encrypt_ranges(); let (a_off, m_off) = (radio::PSDU_OFFSET, radio::PSDU_OFFSET + m_off); + // Crypto setup failed; fail receiving packet and return to idle if self.aes_ccm.set_key(&key) != Ok(()) || self.aes_ccm.set_nonce(&nonce) != Ok(()) { - (RxState::Idle, Some(buf)) - } else { - let res = self.aes_ccm.crypt( - buf, - a_off, - m_off, - m_len, - info.mic_len, - level.encryption_needed(), - true, + // No error is returned for the receive function because recv occurs implicitly + // Log debug statement here so that this error does not occur silently + kernel::debug!( + "[15.4 RECV FAIL] - Failed setting crypto key/nonce." ); + self.mac.set_receive_buffer(buf); + RxState::Idle + } else { + // The crypto operation requires multiple steps through the receiving pipeline and + // an unknown quanitity of time to perform decryption. Holding the 15.4 radio's + // receive buffer for this period of time is suboptimal as packets will be dropped. + // The radio driver assumes the mac.set_receive_buffer(...) function is called prior + // to returning from the framer. These constraints necessitate the creation of a seperate + // crypto buffer for the radio framer so that the framer can return the radio driver's + // receive buffer and then perform decryption using the copied packet in the crypto buffer. + let res = self.crypt_buf.take().map(|mut crypt_buf| { + crypt_buf[0..buf.len()].copy_from_slice(buf); + crypt_buf.slice(0..buf.len()); + + self.aes_ccm.crypt( + crypt_buf.take(), + a_off, + m_off, + m_len, + info.mic_len, + level.encryption_needed(), + true, + ) + }); + + // The potential scenarios include: + // - (1) Successfully transfer packet to crypto buffer and succesfully begin crypto operation + // - (2) Succesfully transfer packet to crypto buffer, but the crypto operation aes_ccm.crypt(...) + // is busy so we do not advance the reception pipeline and retry on the next iteration + // - (3) Succesfully transfer packet to crypto buffer, but the crypto operation fails for some + // unknown reason (likely due to the crypto buffer's configuration or the offset/len parameters + // passed to the function. It is not possible to decrypt the packet so we drop the packet, return + // the radio drivers recv buffer and return the framer recv state machine to idle + // - (4) The crypto buffer is empty (in use elsewhere) and we are unable to copy the received + // packet. This packet is dropped and we must return the buffer to the radio driver. This + // scenario is handled in the None case match res { - Ok(()) => (RxState::Decrypting(info), None), - Err((ErrorCode::BUSY, buf)) => { - (RxState::ReadyToDecrypt(info, buf), None) + // Scenario 1 + Some(Ok(())) => { + self.mac.set_receive_buffer(buf); + RxState::Decrypting(info, lqi) + } + // Scenario 2 + Some(Err((ErrorCode::BUSY, buf))) => { + RxState::ReadyToDecrypt(info, buf, lqi) + } + // Scenario 3 + Some(Err((_, fail_crypt_buf))) => { + self.mac.set_receive_buffer(buf); + self.crypt_buf.replace(SubSliceMut::new(fail_crypt_buf)); + RxState::Idle + } + // Scenario 4 + None => { + self.mac.set_receive_buffer(buf); + RxState::Idle } - Err((_, buf)) => (RxState::Idle, Some(buf)), } } } } } - RxState::Decrypting(info) => { + RxState::Decrypting(info, lqi) => { // This state should be advanced only by the hardware // encryption callback. - (RxState::Decrypting(info), None) + RxState::Decrypting(info, lqi) } - RxState::ReadyToYield(info, buf) => { + RxState::ReadyToYield(info, buf, lqi) => { // Between the secured and unsecured frames, the // unsecured frame length remains constant but the data // offsets may change due to the presence of PayloadIEs. // Hence, we can only use the unsecured length from the // frame info, but not the offsets. let frame_len = info.unsecured_length(); - if let Some((data_offset, (header, _))) = - Header::decode(&buf[radio::PSDU_OFFSET..], true).done() + if let Some((data_offset, (header, _))) = Header::decode( + &buf[radio::PSDU_OFFSET..(radio::PSDU_OFFSET + radio::MAX_FRAME_SIZE)], + true, + ) + .done() { // IEEE 802.15.4-2015 specifies that unsecured // frames do not have auxiliary security headers, @@ -623,30 +704,31 @@ impl<'a, M: Mac, A: AES128CCM<'a>> Framer<'a, M, A> { // This is so that it is possible to tell if the // frame was secured or unsecured, while still // always receiving the frame payload in plaintext. + // + // The buffer containing the 15.4 packet also contains + // the PSDU bytes and an LQI byte. We only pass the + // 15.4 packet up the stack and slice buf accordingly. + let frame_buffer = &buf[radio::PSDU_OFFSET..(buf.len() - LQI_SIZE)]; self.rx_client.map(|client| { client.receive( - &buf, + frame_buffer, header, - radio::PSDU_OFFSET + data_offset, + lqi, + data_offset, frame_len - data_offset, ); }); + self.crypt_buf.replace(SubSliceMut::new(buf)); } - (RxState::Idle, Some(buf)) + RxState::Idle } - RxState::ReadyToReturn(buf) => (RxState::Idle, Some(buf)), }; self.rx_state.replace(next_state); - - // Return the buffer to the radio if we are done with it. - if let Some(buf) = buf { - self.mac.set_receive_buffer(buf); - } }); } } -impl<'a, M: Mac, A: AES128CCM<'a>> MacDevice<'a> for Framer<'a, M, A> { +impl<'a, M: Mac<'a>, A: AES128CCM<'a>> MacDevice<'a> for Framer<'a, M, A> { fn set_transmit_client(&self, client: &'a dyn TxClient) { self.tx_client.set(client); } @@ -702,18 +784,26 @@ impl<'a, M: Mac, A: AES128CCM<'a>> MacDevice<'a> for Framer<'a, M, A> { // TODO: For Thread, in the case of `KeyIdMode::Source4Index`, the source // address should instead be some constant defined in their // specification. - let src_addr_long = self.get_address_long(); + let security_desc = security_needed.and_then(|(level, key_id)| { + // To decrypt the packet, we need the long addr. + // Without the long addr, we are unable to proceed + // and return None + let src_addr_long = match src_addr { + MacAddress::Long(addr) => addr, + MacAddress::Short(_) => return None, + }; + self.lookup_key(level, key_id).map(|key| { // TODO: lookup frame counter for device let frame_counter = 0; let nonce = get_ccm_nonce(&src_addr_long, frame_counter, level); ( Security { - level: level, + level, asn_in_nonce: false, frame_counter: Some(frame_counter), - key_id: key_id, + key_id, }, key, nonce, @@ -741,22 +831,22 @@ impl<'a, M: Mac, A: AES128CCM<'a>> MacDevice<'a> for Framer<'a, M, A> { dst_addr: Some(dst_addr), src_pan: Some(src_pan), src_addr: Some(src_addr), - security: security, + security, header_ies: Default::default(), header_ies_len: 0, payload_ies: Default::default(), payload_ies_len: 0, }; - match header.encode(&mut buf[radio::PSDU_OFFSET..], true).done() { + match header.encode(buf, true).done() { Some((data_offset, mac_payload_offset)) => Ok(Frame { - buf: buf, + buf, info: FrameInfo { frame_type: FrameType::Data, - mac_payload_offset: mac_payload_offset, - data_offset: data_offset, + mac_payload_offset, + data_offset, data_len: 0, - mic_len: mic_len, + mic_len, security_params: security_desc.map(|(sec, key, nonce)| (sec.level, key, nonce)), }, }), @@ -786,7 +876,7 @@ impl<'a, M: Mac, A: AES128CCM<'a>> MacDevice<'a> for Framer<'a, M, A> { } } -impl<'a, M: Mac, A: AES128CCM<'a>> radio::TxClient for Framer<'a, M, A> { +impl<'a, M: Mac<'a>, A: AES128CCM<'a>> radio::TxClient for Framer<'a, M, A> { fn send_done(&self, buf: &'static mut [u8], acked: bool, result: Result<(), ErrorCode>) { self.data_sequence.set(self.data_sequence.get() + 1); self.tx_client.map(move |client| { @@ -795,11 +885,12 @@ impl<'a, M: Mac, A: AES128CCM<'a>> radio::TxClient for Framer<'a, M, A> { } } -impl<'a, M: Mac, A: AES128CCM<'a>> radio::RxClient for Framer<'a, M, A> { +impl<'a, M: Mac<'a>, A: AES128CCM<'a>> radio::RxClient for Framer<'a, M, A> { fn receive( &self, buf: &'static mut [u8], frame_len: usize, + lqi: u8, crc_valid: bool, _: Result<(), ErrorCode>, ) { @@ -814,7 +905,7 @@ impl<'a, M: Mac, A: AES128CCM<'a>> radio::RxClient for Framer<'a, M, A> { RxState::Idle => { // We can start processing a new received frame only if // the reception pipeline is free - self.incoming_frame_security(buf, frame_len) + self.incoming_frame_security(buf, frame_len, lqi) } other_state => { // This should never occur unless something other than @@ -831,7 +922,7 @@ impl<'a, M: Mac, A: AES128CCM<'a>> radio::RxClient for Framer<'a, M, A> { } } -impl<'a, M: Mac, A: AES128CCM<'a>> radio::ConfigClient for Framer<'a, M, A> { +impl<'a, M: Mac<'a>, A: AES128CCM<'a>> radio::ConfigClient for Framer<'a, M, A> { fn config_done(&self, _: Result<(), ErrorCode>) { // The transmission pipeline is the only state machine that // waits for the configuration procedure to complete before @@ -845,7 +936,7 @@ impl<'a, M: Mac, A: AES128CCM<'a>> radio::ConfigClient for Framer<'a, M, A> { } } -impl<'a, M: Mac, A: AES128CCM<'a>> CCMClient for Framer<'a, M, A> { +impl<'a, M: Mac<'a>, A: AES128CCM<'a>> CCMClient for Framer<'a, M, A> { fn crypt_done(&self, buf: &'static mut [u8], res: Result<(), ErrorCode>, tag_is_valid: bool) { let mut tx_waiting = false; let mut rx_waiting = false; @@ -889,20 +980,22 @@ impl<'a, M: Mac, A: AES128CCM<'a>> CCMClient for Framer<'a, M, A> { // The crypto operation was from the reception pipeline. if let Some(buf) = opt_buf { self.rx_state.take().map(|state| { - let buf = buf; match state { - RxState::Decrypting(info) => { + RxState::Decrypting(info, lqi) => { let next_state = if tag_is_valid { - RxState::ReadyToYield(info, buf) + RxState::ReadyToYield(info, buf, lqi) } else { - RxState::ReadyToReturn(buf) + // The CRC tag is invalid, meaning the packet was corrupted. Drop this packet + // and reset reception pipeline + self.crypt_buf.replace(SubSliceMut::new(buf)); + RxState::Idle }; self.rx_state.replace(next_state); self.step_receive_state(); } other_state => { rx_waiting = match other_state { - RxState::ReadyToDecrypt(_, _) => true, + RxState::ReadyToDecrypt(_, _, _) => true, _ => false, }; self.rx_state.replace(other_state); diff --git a/capsules/src/ieee802154/mac.rs b/capsules/extra/src/ieee802154/mac.rs similarity index 68% rename from capsules/src/ieee802154/mac.rs rename to capsules/extra/src/ieee802154/mac.rs index 282b915abd..d3aeef207c 100644 --- a/capsules/src/ieee802154/mac.rs +++ b/capsules/extra/src/ieee802154/mac.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Specifies the interface for IEEE 802.15.4 MAC protocol layers. MAC protocols //! expose similar configuration (address, PAN, transmission power) options //! as ieee802154::device::MacDevice layers above it, but retain control over @@ -10,22 +14,20 @@ //! through each frame for transmission. use crate::net::ieee802154::{Header, MacAddress}; -use kernel::debug; -use kernel::hil::radio; +use kernel::hil::radio::{self, MAX_FRAME_SIZE, PSDU_OFFSET}; use kernel::utilities::cells::OptionalCell; use kernel::ErrorCode; -pub trait Mac { - /// Initializes the layer; may require a buffer to temporarily retaining frames to be - /// transmitted - fn initialize(&self, mac_buf: &'static mut [u8]) -> Result<(), ErrorCode>; +pub trait Mac<'a> { + /// Initializes the layer. + fn initialize(&self) -> Result<(), ErrorCode>; /// Sets the notified client for configuration changes - fn set_config_client(&self, client: &'static dyn radio::ConfigClient); + fn set_config_client(&self, client: &'a dyn radio::ConfigClient); /// Sets the notified client for transmission completions - fn set_transmit_client(&self, client: &'static dyn radio::TxClient); + fn set_transmit_client(&self, client: &'a dyn radio::TxClient); /// Sets the notified client for frame receptions - fn set_receive_client(&self, client: &'static dyn radio::RxClient); + fn set_receive_client(&self, client: &'a dyn radio::RxClient); /// Sets the buffer for packet reception fn set_receive_buffer(&self, buffer: &'static mut [u8]); @@ -46,7 +48,7 @@ pub trait Mac { /// Must be called after one or more calls to `set_*`. If /// `set_*` is called without calling `config_commit`, there is no guarantee /// that the underlying hardware configuration (addresses, pan ID) is in - /// line with this MAC protocol implementation. The specificed config_client is + /// line with this MAC protocol implementation. The specified config_client is /// notified on completed reconfiguration. fn config_commit(&self); @@ -67,25 +69,25 @@ pub trait Mac { /// implementation and the underlying radio::Radio device. Does not change the power /// state of the radio during operation. /// -pub struct AwakeMac<'a, R: radio::Radio> { +pub struct AwakeMac<'a, R: radio::Radio<'a>> { radio: &'a R, - tx_client: OptionalCell<&'static dyn radio::TxClient>, - rx_client: OptionalCell<&'static dyn radio::RxClient>, + tx_client: OptionalCell<&'a dyn radio::TxClient>, + rx_client: OptionalCell<&'a dyn radio::RxClient>, } -impl<'a, R: radio::Radio> AwakeMac<'a, R> { +impl<'a, R: radio::Radio<'a>> AwakeMac<'a, R> { pub fn new(radio: &'a R) -> AwakeMac<'a, R> { AwakeMac { - radio: radio, + radio, tx_client: OptionalCell::empty(), rx_client: OptionalCell::empty(), } } } -impl Mac for AwakeMac<'_, R> { - fn initialize(&self, _mac_buf: &'static mut [u8]) -> Result<(), ErrorCode> { +impl<'a, R: radio::Radio<'a>> Mac<'a> for AwakeMac<'a, R> { + fn initialize(&self) -> Result<(), ErrorCode> { // do nothing, extra buffer unnecessary Ok(()) } @@ -94,7 +96,7 @@ impl Mac for AwakeMac<'_, R> { self.radio.is_on() } - fn set_config_client(&self, client: &'static dyn radio::ConfigClient) { + fn set_config_client(&self, client: &'a dyn radio::ConfigClient) { self.radio.set_config_client(client) } @@ -126,11 +128,11 @@ impl Mac for AwakeMac<'_, R> { self.radio.config_commit() } - fn set_transmit_client(&self, client: &'static dyn radio::TxClient) { + fn set_transmit_client(&self, client: &'a dyn radio::TxClient) { self.tx_client.set(client); } - fn set_receive_client(&self, client: &'static dyn radio::RxClient) { + fn set_receive_client(&self, client: &'a dyn radio::RxClient) { self.rx_client.set(client); } @@ -143,11 +145,24 @@ impl Mac for AwakeMac<'_, R> { full_mac_frame: &'static mut [u8], frame_len: usize, ) -> Result<(), (ErrorCode, &'static mut [u8])> { + // We must add the PSDU_OFFSET required for the radio + // hardware. We first error check the provided arguments + // and then shift the 15.4 frame by the `PSDU_OFFSET`. + + if full_mac_frame.len() < frame_len + PSDU_OFFSET { + return Err((ErrorCode::NOMEM, full_mac_frame)); + } + + if frame_len > MAX_FRAME_SIZE { + return Err((ErrorCode::INVAL, full_mac_frame)); + } + + full_mac_frame.copy_within(0..frame_len, PSDU_OFFSET); self.radio.transmit(full_mac_frame, frame_len) } } -impl radio::TxClient for AwakeMac<'_, R> { +impl<'a, R: radio::Radio<'a>> radio::TxClient for AwakeMac<'a, R> { fn send_done(&self, buf: &'static mut [u8], acked: bool, result: Result<(), ErrorCode>) { self.tx_client.map(move |c| { c.send_done(buf, acked, result); @@ -155,11 +170,12 @@ impl radio::TxClient for AwakeMac<'_, R> { } } -impl radio::RxClient for AwakeMac<'_, R> { +impl<'a, R: radio::Radio<'a>> radio::RxClient for AwakeMac<'a, R> { fn receive( &self, buf: &'static mut [u8], frame_len: usize, + lqi: u8, crc_valid: bool, result: Result<(), ErrorCode>, ) { @@ -168,20 +184,21 @@ impl radio::RxClient for AwakeMac<'_, R> { if let Some((_, (header, _))) = Header::decode(&buf[radio::PSDU_OFFSET..], false).done() { if let Some(dst_addr) = header.dst_addr { addr_match = match dst_addr { - MacAddress::Short(addr) => addr == self.radio.get_address(), + MacAddress::Short(addr) => { + // Check if address matches radio or is set to multicast short addr 0xFFFF + (addr == self.radio.get_address()) || (addr == 0xFFFF) + } MacAddress::Long(long_addr) => long_addr == self.radio.get_address_long(), }; } } - if addr_match { - //debug!("[AwakeMAC] Rcvd a 15.4 frame addressed to this device"); + // debug!("[AwakeMAC] Rcvd a 15.4 frame addressed to this device"); self.rx_client.map(move |c| { - c.receive(buf, frame_len, crc_valid, result); + c.receive(buf, frame_len, lqi, crc_valid, result); }); } else { - debug!("[AwakeMAC] Received a packet, but not addressed to us"); - debug!("radio addr is: {:?}", self.radio.get_address()); + // debug!("[AwakeMAC] Received a packet, but not addressed to us"); self.radio.set_receive_buffer(buf); } } diff --git a/capsules/src/ieee802154/mod.rs b/capsules/extra/src/ieee802154/mod.rs similarity index 52% rename from capsules/src/ieee802154/mod.rs rename to capsules/extra/src/ieee802154/mod.rs index 56ce5ef6ac..ca8a639fe1 100644 --- a/capsules/src/ieee802154/mod.rs +++ b/capsules/extra/src/ieee802154/mod.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Support for IEEE 802.15.4. pub mod device; @@ -7,6 +11,7 @@ pub mod virtual_mac; pub mod xmac; mod driver; +pub mod phy_driver; pub use self::driver::RadioDriver; pub use self::driver::DRIVER_NUM; diff --git a/capsules/extra/src/ieee802154/phy_driver.rs b/capsules/extra/src/ieee802154/phy_driver.rs new file mode 100644 index 0000000000..e5fae4fd4d --- /dev/null +++ b/capsules/extra/src/ieee802154/phy_driver.rs @@ -0,0 +1,460 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! IEEE 802.15.4 userspace interface for configuration and transmit/receive. +//! +//! Implements a userspace interface for sending and receiving raw IEEE 802.15.4 +//! frames. +//! +//! Sending - Userspace fully forms the 15.4 frame and passes it to the driver. +//! +//! Receiving - The driver receives 15.4 frames and passes them to the process. +//! To accomplish this, the process must first `allow` a read/write ring buffer +//! to the kernel. The kernel will then fill this buffer with received frames +//! and schedule an upcall upon receipt of the first packet. +//! +//! The ring buffer provided by the process must be of the form: +//! +//! ```text +//! | read index | write index | user_frame 0 | user_frame 1 | ... | user_frame n | +//! ``` +//! +//! `user_frame` denotes the 15.4 frame in addition to the relevant 3 bytes of +//! metadata (offset to data payload, length of data payload, and the MIC len). +//! The capsule assumes that this is the form of the buffer. Errors or deviation +//! in the form of the provided buffer will likely result in incomplete or +//! dropped packets. +//! +//! Because the scheduled receive upcall must be handled by the process, there +//! is no guarantee as to when this will occur and if additional packets will be +//! received prior to the upcall being handled. Without a ring buffer (or some +//! equivalent data structure), the original packet will be lost. The ring +//! buffer allows for the upcall to be scheduled and for all received packets to +//! be passed to the process. The ring buffer is designed to overwrite old +//! packets if the buffer becomes full. If the process notices a high number of +//! "dropped" packets, this may be the cause. The process can mitigate this +//! issue by increasing the size of the ring buffer provided to the capsule. + +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::hil; +use kernel::processbuffer::{ReadableProcessBuffer, WriteableProcessBuffer}; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::{ErrorCode, ProcessId}; + +/// IDs for subscribed upcalls. +mod upcall { + /// Frame is received + pub const FRAME_RECEIVED: usize = 0; + /// Frame is transmitted + pub const FRAME_TRANSMITTED: usize = 1; + /// Number of upcalls. + pub const COUNT: u8 = 2; +} + +/// Ids for read-only allow buffers +mod ro_allow { + /// Write buffer. Contains the frame payload to be transmitted. + pub const WRITE: usize = 0; + /// The number of allow buffers the kernel stores for this grant + pub const COUNT: u8 = 1; +} + +/// Ids for read-write allow buffers +mod rw_allow { + /// Read buffer. Will contain the received frame. + pub const READ: usize = 0; + /// The number of allow buffers the kernel stores for this grant + pub const COUNT: u8 = 1; +} + +use capsules_core::driver; +pub const DRIVER_NUM: usize = driver::NUM::Ieee802154 as usize; + +#[derive(Default)] +pub struct App { + pending_tx: bool, +} + +pub struct RadioDriver<'a, R: hil::radio::Radio<'a>> { + /// Underlying radio. + radio: &'a R, + + /// Grant of apps that use this radio driver. + apps: Grant< + App, + UpcallCount<{ upcall::COUNT }>, + AllowRoCount<{ ro_allow::COUNT }>, + AllowRwCount<{ rw_allow::COUNT }>, + >, + /// ID of app whose transmission request is being processed. + current_app: OptionalCell, + + /// Buffer that stores the IEEE 802.15.4 frame to be transmitted. + kernel_tx: TakeCell<'static, [u8]>, +} + +impl<'a, R: hil::radio::Radio<'a>> RadioDriver<'a, R> { + pub fn new( + radio: &'a R, + grant: Grant< + App, + UpcallCount<{ upcall::COUNT }>, + AllowRoCount<{ ro_allow::COUNT }>, + AllowRwCount<{ rw_allow::COUNT }>, + >, + kernel_tx: &'static mut [u8], + ) -> Self { + Self { + radio, + apps: grant, + current_app: OptionalCell::empty(), + kernel_tx: TakeCell::new(kernel_tx), + } + } + + /// Performs `processid`'s pending transmission. Assumes that the driver is + /// currently idle and the app has a pending transmission. + fn perform_tx(&self, processid: ProcessId) -> Result<(), ErrorCode> { + self.apps.enter(processid, |app, kernel_data| { + app.pending_tx = false; + + self.kernel_tx.take().map_or(Err(ErrorCode::NOMEM), |kbuf| { + kernel_data + .get_readonly_processbuffer(ro_allow::WRITE) + .and_then(|write| { + write.enter(|payload| { + let frame_len = payload.len(); + let dst_start = hil::radio::PSDU_OFFSET; + let dst_end = dst_start + frame_len; + payload.copy_to_slice(&mut kbuf[dst_start..dst_end]); + + self.radio.transmit(kbuf, frame_len).map_or_else( + |(errorcode, error_buf)| { + self.kernel_tx.replace(error_buf); + Err(errorcode) + }, + |()| { + self.current_app.set(processid); + Ok(()) + }, + ) + }) + })? + }) + })? + } + + /// If the driver is currently idle and there are pending transmissions, + /// pick an app with a pending transmission and return its `ProcessId`. + fn get_next_tx_if_idle(&self) -> Option { + if self.current_app.is_some() { + return None; + } + let mut pending_app = None; + for app in self.apps.iter() { + let processid = app.processid(); + app.enter(|app, _| { + if app.pending_tx { + pending_app = Some(processid); + } + }); + if pending_app.is_some() { + break; + } + } + pending_app + } + + /// Schedule the next transmission if there is one pending. + fn do_next_tx(&self) { + self.get_next_tx_if_idle() + .map(|processid| match self.perform_tx(processid) { + Ok(()) => {} + Err(e) => { + let _ = self.apps.enter(processid, |_app, upcalls| { + let _ = upcalls.schedule_upcall( + upcall::FRAME_TRANSMITTED, + (kernel::errorcode::into_statuscode(Err(e)), 0, 0), + ); + }); + } + }); + } +} + +impl<'a, R: hil::radio::Radio<'a>> SyscallDriver for RadioDriver<'a, R> { + /// IEEE 802.15.4 low-level control. + /// + /// ### `command_num` + /// + /// - `0`: Driver existence check. + /// - `1`: Return radio status. Ok(())/OFF = on/off. + /// - `2`: Set short address. + /// - `4`: Set PAN ID. + /// - `5`: Set channel. + /// - `6`: Set transmission power. + /// - `7`: Commit any configuration changes. + /// - `8`: Get the short MAC address. + /// - `10`: Get the PAN ID. + /// - `11`: Get the channel. + /// - `12`: Get the transmission power. + /// - `27`: Transmit a frame. The frame must be stored in the write RO allow + /// buffer 0. The allowed buffer must be the length of the frame. The + /// frame includes the PDSU (i.e., the MAC payload) _without_ the MFR + /// (i.e., CRC) bytes. + /// - `28`: Set long address. + /// - `29`: Get the long MAC address. + /// - `30`: Turn the radio on. + /// - `31`: Turn the radio off. + fn command( + &self, + command_number: usize, + arg1: usize, + arg2: usize, + processid: ProcessId, + ) -> CommandReturn { + match command_number { + 0 => CommandReturn::success(), + 1 => { + if self.radio.is_on() { + CommandReturn::success() + } else { + CommandReturn::failure(ErrorCode::OFF) + } + } + 2 => { + self.radio.set_address(arg1 as u16); + CommandReturn::success() + } + 4 => { + self.radio.set_pan(arg1 as u16); + CommandReturn::success() + } + 5 => { + let channel = (arg1 as u8).try_into(); + channel.map_or(CommandReturn::failure(ErrorCode::INVAL), |chan| { + self.radio.set_channel(chan); + CommandReturn::success() + }) + } + 6 => self.radio.set_tx_power(arg1 as i8).into(), + 7 => { + self.radio.config_commit(); + CommandReturn::success() + } + 8 => { + // Guarantee that address is positive by adding 1 + let addr = self.radio.get_address(); + CommandReturn::success_u32(addr as u32 + 1) + } + 10 => { + // Guarantee that the PAN is positive by adding 1 + let pan = self.radio.get_pan(); + CommandReturn::success_u32(pan as u32 + 1) + } + 11 => { + let channel = self.radio.get_channel(); + CommandReturn::success_u32(channel as u32) + } + 12 => { + let txpower = self.radio.get_tx_power(); + CommandReturn::success_u32(txpower as u32) + } + 27 => { + self.apps + .enter(processid, |app, _| { + if app.pending_tx { + // Cannot support more than one pending TX per process. + return Err(ErrorCode::BUSY); + } + app.pending_tx = true; + Ok(()) + }) + .map_or_else( + |err| CommandReturn::failure(err.into()), + |_| { + self.do_next_tx(); + CommandReturn::success() + }, + ) + } + 28 => { + let addr_upper: u64 = arg2 as u64; + let addr_lower: u64 = arg1 as u64; + let addr = addr_upper << 32 | addr_lower; + self.radio.set_address_long(addr.to_be_bytes()); + CommandReturn::success() + } + 29 => { + let addr = u64::from_be_bytes(self.radio.get_address_long()); + CommandReturn::success_u64(addr) + } + 30 => self.radio.start().into(), + 31 => self.radio.stop().into(), + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { + self.apps.enter(processid, |_, _| {}) + } +} + +impl<'a, R: hil::radio::Radio<'a>> hil::radio::TxClient for RadioDriver<'a, R> { + fn send_done(&self, spi_buf: &'static mut [u8], acked: bool, result: Result<(), ErrorCode>) { + self.kernel_tx.replace(spi_buf); + self.current_app.take().map(|processid| { + let _ = self.apps.enter(processid, |_app, upcalls| { + upcalls + .schedule_upcall( + upcall::FRAME_TRANSMITTED, + (kernel::errorcode::into_statuscode(result), acked.into(), 0), + ) + .ok(); + }); + }); + self.do_next_tx(); + } +} + +impl<'a, R: hil::radio::Radio<'a>> hil::radio::RxClient for RadioDriver<'a, R> { + fn receive<'b>( + &self, + buf: &'static mut [u8], + frame_len: usize, + lqi: u8, + crc_valid: bool, + result: Result<(), ErrorCode>, + ) { + // Drop invalid packets or packets that had errors during reception. + if !crc_valid || result.is_err() { + // Replace the RX buffer and drop the packet. + self.radio.set_receive_buffer(buf); + return; + } + + self.apps.each(|_, _, kernel_data| { + let read_present = kernel_data + .get_readwrite_processbuffer(rw_allow::READ) + .and_then(|read| { + read.mut_enter(|rbuf| { + //////////////////////////////////////////////////////// + // NOTE: context for the ring buffer and assumptions + // regarding the ring buffer format and usage can be + // found in the detailed comment at the top of this + // file. + // + // Ring buffer format: + // | read | write | user_frame | user_frame |...| user_frame | + // | index | index | 0 | 1 | | n | + // + // user_frame format: + // | header_len | payload_len | mic_len | 15.4 frame | + // + //////////////////////////////////////////////////////// + + // 2 bytes for the readwrite buffer metadata (read and + // write index). + const RING_BUF_METADATA_SIZE: usize = 2; + + /// 3 byte metadata (offset, len, mic_len) + const USER_FRAME_METADATA_SIZE: usize = 3; + + /// 3 byte metadata + 127 byte max payload + const USER_FRAME_MAX_SIZE: usize = + USER_FRAME_METADATA_SIZE + hil::radio::MAX_FRAME_SIZE; + + // Confirm the availability of the buffer. A buffer of + // len 0 is indicative of the userprocess not allocating + // a readwrite buffer. We must also confirm that the + // userprocess correctly formatted the buffer to be of + // length 2 + n * USER_FRAME_MAX_SIZE, where n is the + // number of user frames that the buffer can store. We + // combine checking the buffer's non-zero length and the + // case of the buffer being shorter than the + // `RING_BUF_METADATA_SIZE` as an invalid buffer (e.g. + // of length 1) may otherwise errantly pass the second + // conditional check (due to unsigned integer + // arithmetic). + if rbuf.len() <= RING_BUF_METADATA_SIZE + || (rbuf.len() - RING_BUF_METADATA_SIZE) % USER_FRAME_MAX_SIZE != 0 + { + return false; + } + + let mut read_index = rbuf[0].get() as usize; + let mut write_index = rbuf[1].get() as usize; + + let max_pending_rx = + (rbuf.len() - RING_BUF_METADATA_SIZE) / USER_FRAME_MAX_SIZE; + + // Confirm user modifiable metadata is valid (i.e. + // within bounds of the provided buffer). + if read_index >= max_pending_rx || write_index >= max_pending_rx { + return false; + } + + // We don't parse the received packet, so we don't know + // how long all of the pieces are. + let mic_len = 0; + let header_len = 0; + + // Start in the buffer where we are going to write this + // incoming packet. + let offset = RING_BUF_METADATA_SIZE + (write_index * USER_FRAME_MAX_SIZE); + + // Copy the entire frame over to userland, preceded by + // three metadata bytes: the header length, the data + // length, and the MIC length. + let dst_start = offset + USER_FRAME_METADATA_SIZE; + let dst_end = dst_start + frame_len; + let src_start = hil::radio::PSDU_OFFSET; + let src_end = src_start + frame_len; + rbuf[dst_start..dst_end].copy_from_slice(&buf[src_start..src_end]); + + rbuf[offset].set(header_len as u8); + rbuf[offset + 1].set(frame_len as u8); + rbuf[offset + 2].set(mic_len as u8); + + // Prepare the ring buffer for the next write. The + // current design favors newness; newly received packets + // will begin to overwrite the oldest data in the event + // of the buffer becoming full. The read index must + // always point to the "oldest" data. If we have + // overwritten the oldest data, the next oldest data is + // now at the read index + 1. We must update the read + // index to reflect this. + write_index = (write_index + 1) % max_pending_rx; + if write_index == read_index { + read_index = (read_index + 1) % max_pending_rx; + rbuf[0].set(read_index as u8); + } + + // Update write index metadata since we have added a + // frame. + rbuf[1].set(write_index as u8); + true + }) + }) + .unwrap_or(false); + if read_present { + // Place lqi as argument to be included in upcall. + kernel_data + .schedule_upcall(upcall::FRAME_RECEIVED, (lqi as usize, 0, 0)) + .ok(); + } + }); + + self.radio.set_receive_buffer(buf); + } +} + +impl<'a, R: hil::radio::Radio<'a>> hil::radio::ConfigClient for RadioDriver<'a, R> { + fn config_done(&self, _result: Result<(), ErrorCode>) {} +} + +impl<'a, R: hil::radio::Radio<'a>> hil::radio::PowerClient for RadioDriver<'a, R> { + fn changed(&self, _on: bool) {} +} diff --git a/capsules/src/ieee802154/virtual_mac.rs b/capsules/extra/src/ieee802154/virtual_mac.rs similarity index 80% rename from capsules/src/ieee802154/virtual_mac.rs rename to capsules/extra/src/ieee802154/virtual_mac.rs index 7e436ff4a3..84aa009eb7 100644 --- a/capsules/src/ieee802154/virtual_mac.rs +++ b/capsules/extra/src/ieee802154/virtual_mac.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Virtual IEEE 802.15.4 MAC device //! //! `MuxMac` provides multiplexed access to an 802.15.4 MAC device. This enables @@ -11,7 +15,7 @@ //! Usage //! ----- //! -//! ``` +//! ```rust,ignore //! # use kernel::static_init; //! //! // Create the mux. @@ -31,8 +35,6 @@ use crate::ieee802154::{device, framer}; use crate::net::ieee802154::{Header, KeyId, MacAddress, PanID, SecurityLevel}; -use core::cell::Cell; - use kernel::collections::list::{List, ListLink, ListNode}; use kernel::utilities::cells::{MapCell, OptionalCell}; use kernel::ErrorCode; @@ -40,13 +42,13 @@ use kernel::ErrorCode; /// IEE 802.15.4 MAC device muxer that keeps a list of MAC users and sequences /// any pending transmission requests. Any received frames from the underlying /// MAC device are sent to all users. -pub struct MuxMac<'a> { - mac: &'a dyn device::MacDevice<'a>, - users: List<'a, MacUser<'a>>, - inflight: OptionalCell<&'a MacUser<'a>>, +pub struct MuxMac<'a, M: device::MacDevice<'a>> { + mac: &'a M, + users: List<'a, MacUser<'a, M>>, + inflight: OptionalCell<&'a MacUser<'a, M>>, } -impl device::TxClient for MuxMac<'_> { +impl<'a, M: device::MacDevice<'a>> device::TxClient for MuxMac<'a, M> { fn send_done(&self, spi_buf: &'static mut [u8], acked: bool, result: Result<(), ErrorCode>) { self.inflight.take().map(move |user| { user.send_done(spi_buf, acked, result); @@ -55,18 +57,25 @@ impl device::TxClient for MuxMac<'_> { } } -impl device::RxClient for MuxMac<'_> { - fn receive<'b>(&self, buf: &'b [u8], header: Header<'b>, data_offset: usize, data_len: usize) { +impl<'a, M: device::MacDevice<'a>> device::RxClient for MuxMac<'a, M> { + fn receive<'b>( + &self, + buf: &'b [u8], + header: Header<'b>, + lqi: u8, + data_offset: usize, + data_len: usize, + ) { for user in self.users.iter() { - user.receive(buf, header, data_offset, data_len); + user.receive(buf, header, lqi, data_offset, data_len); } } } -impl<'a> MuxMac<'a> { - pub const fn new(mac: &'a dyn device::MacDevice<'a>) -> MuxMac<'a> { +impl<'a, M: device::MacDevice<'a>> MuxMac<'a, M> { + pub const fn new(mac: &'a M) -> MuxMac<'a, M> { MuxMac { - mac: mac, + mac, users: List::new(), inflight: OptionalCell::empty(), } @@ -74,13 +83,13 @@ impl<'a> MuxMac<'a> { /// Registers a MAC user with this MAC mux device. Each MAC user should only /// be registered once. - pub fn add_user(&self, user: &'a MacUser<'a>) { + pub fn add_user(&self, user: &'a MacUser<'a, M>) { self.users.push_head(user); } /// Gets the next `MacUser` and operation to perform if an operation is not /// already underway. - fn get_next_op_if_idle(&self) -> Option<(&'a MacUser<'a>, Op)> { + fn get_next_op_if_idle(&self) -> Option<(&'a MacUser<'a, M>, Op)> { if self.inflight.is_some() { return None; } @@ -103,7 +112,7 @@ impl<'a> MuxMac<'a> { /// Performs a non-idle operation on a `MacUser` asynchronously: that is, if the /// transmission operation results in immediate failure, then return the /// buffer to the `MacUser` via its transmit client. - fn perform_op_async(&self, node: &'a MacUser<'a>, op: Op) { + fn perform_op_async(&self, node: &'a MacUser<'a, M>, op: Op) { if let Op::Transmit(frame) = op { match self.mac.transmit(frame) { // If Err, the transmission failed, @@ -122,7 +131,7 @@ impl<'a> MuxMac<'a> { /// the error code and the buffer immediately. fn perform_op_sync( &self, - node: &'a MacUser<'a>, + node: &'a MacUser<'a, M>, op: Op, ) -> Option> { if let Op::Transmit(frame) = op { @@ -158,10 +167,10 @@ impl<'a> MuxMac<'a> { /// device but fails immediately, return the buffer synchronously. fn do_next_op_sync( &self, - new_node: &MacUser<'a>, + new_node: &MacUser<'a, M>, ) -> Option> { self.get_next_op_if_idle().and_then(|(node, op)| { - if node as *const _ == new_node as *const _ { + if core::ptr::eq(node, new_node) { // The new node's operation is the one being scheduled, so the // operation is synchronous self.perform_op_sync(node, op) @@ -188,53 +197,60 @@ enum Op { /// all MacUsers because there is only one MAC device. For example, the MAC /// device address is shared, so calling `set_address` on one `MacUser` sets the /// MAC address for all `MacUser`s. -pub struct MacUser<'a> { - mux: &'a MuxMac<'a>, +pub struct MacUser<'a, M: device::MacDevice<'a>> { + mux: &'a MuxMac<'a, M>, operation: MapCell, - next: ListLink<'a, MacUser<'a>>, - tx_client: Cell>, - rx_client: Cell>, + next: ListLink<'a, MacUser<'a, M>>, + tx_client: OptionalCell<&'a dyn device::TxClient>, + rx_client: OptionalCell<&'a dyn device::RxClient>, } -impl<'a> MacUser<'a> { - pub const fn new(mux: &'a MuxMac<'a>) -> MacUser<'a> { - MacUser { - mux: mux, +impl<'a, M: device::MacDevice<'a>> MacUser<'a, M> { + pub const fn new(mux: &'a MuxMac<'a, M>) -> Self { + Self { + mux, operation: MapCell::new(Op::Idle), next: ListLink::empty(), - tx_client: Cell::new(None), - rx_client: Cell::new(None), + tx_client: OptionalCell::empty(), + rx_client: OptionalCell::empty(), } } } -impl MacUser<'_> { +impl<'a, M: device::MacDevice<'a>> MacUser<'a, M> { fn send_done(&self, spi_buf: &'static mut [u8], acked: bool, result: Result<(), ErrorCode>) { self.tx_client .get() .map(move |client| client.send_done(spi_buf, acked, result)); } - fn receive<'b>(&self, buf: &'b [u8], header: Header<'b>, data_offset: usize, data_len: usize) { + fn receive<'b>( + &self, + buf: &'b [u8], + header: Header<'b>, + lqi: u8, + data_offset: usize, + data_len: usize, + ) { self.rx_client .get() - .map(move |client| client.receive(buf, header, data_offset, data_len)); + .map(move |client| client.receive(buf, header, lqi, data_offset, data_len)); } } -impl<'a> ListNode<'a, MacUser<'a>> for MacUser<'a> { - fn next(&'a self) -> &'a ListLink<'a, MacUser<'a>> { +impl<'a, M: device::MacDevice<'a>> ListNode<'a, MacUser<'a, M>> for MacUser<'a, M> { + fn next(&'a self) -> &'a ListLink<'a, MacUser<'a, M>> { &self.next } } -impl<'a> device::MacDevice<'a> for MacUser<'a> { +impl<'a, M: device::MacDevice<'a>> device::MacDevice<'a> for MacUser<'a, M> { fn set_transmit_client(&self, client: &'a dyn device::TxClient) { - self.tx_client.set(Some(client)); + self.tx_client.set(client); } fn set_receive_client(&self, client: &'a dyn device::RxClient) { - self.rx_client.set(Some(client)); + self.rx_client.set(client); } fn get_address(&self) -> u16 { diff --git a/capsules/src/ieee802154/xmac.rs b/capsules/extra/src/ieee802154/xmac.rs similarity index 93% rename from capsules/src/ieee802154/xmac.rs rename to capsules/extra/src/ieee802154/xmac.rs index 65a847ba93..eb2cf0504b 100644 --- a/capsules/src/ieee802154/xmac.rs +++ b/capsules/extra/src/ieee802154/xmac.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! X-MAC protocol layer for low power 802.15.4 reception, intended primarily //! to manage an Atmel RF233 radio. //! @@ -32,7 +36,7 @@ //! a `kernel::hil::time::Alarm`, and a `kernel::hil::rng::Rng` device, the //! necessary modifications to the board configuration are shown below for `imix`s: //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! // main.rs @@ -47,7 +51,7 @@ //! // radio for transmitting preamble packets //! static mut MAC_BUF: [u8; radio::MAX_BUF_SIZE] = [0x00; radio::MAX_BUF_SIZE]; //! // ... -//! let xmac: &XMacDevice = static_init!(XMacDevice, xmac::XMac::new(rf233, alarm, rng)); +//! let xmac: &XMacDevice = static_init!(XMacDevice, xmac::XMac::new(rf233, alarm, rng, &mut MAC_BUF)); //! rng.set_client(xmac); //! alarm.set_client(xmac); //! @@ -56,7 +60,7 @@ //! rf233.set_receive_client(xmac, &mut RF233_RX_BUF); //! rf233.set_power_client(xmac); //! -//! xmac.initialize(&mut MAC_BUF); +//! xmac.initialize(); //! //! // We can now use the XMac driver to instantiate a MacDevice like a Framer //! let mac_device = static_init!( @@ -144,12 +148,12 @@ pub struct XMacHeaderInfo { // the tx/rx client, and the underlying radio driver. The transmit buffer can // also hold the actual data packet contents while preambles are being // transmitted. -pub struct XMac<'a, R: radio::Radio, A: Alarm<'a>> { +pub struct XMac<'a, R: radio::Radio<'a>, A: Alarm<'a>> { radio: &'a R, alarm: &'a A, rng: &'a dyn Rng<'a>, - tx_client: OptionalCell<&'static dyn radio::TxClient>, - rx_client: OptionalCell<&'static dyn radio::RxClient>, + tx_client: OptionalCell<&'a dyn radio::TxClient>, + rx_client: OptionalCell<&'a dyn radio::RxClient>, state: Cell, delay_sleep: Cell, @@ -164,12 +168,17 @@ pub struct XMac<'a, R: radio::Radio, A: Alarm<'a>> { rx_pending: Cell, } -impl<'a, R: radio::Radio, A: Alarm<'a>> XMac<'a, R, A> { - pub fn new(radio: &'a R, alarm: &'a A, rng: &'a dyn Rng<'a>) -> XMac<'a, R, A> { +impl<'a, R: radio::Radio<'a>, A: Alarm<'a>> XMac<'a, R, A> { + pub fn new( + radio: &'a R, + alarm: &'a A, + rng: &'a dyn Rng<'a>, + mac_buf: &'static mut [u8], + ) -> XMac<'a, R, A> { XMac { - radio: radio, - alarm: alarm, - rng: rng, + radio, + alarm, + rng, tx_client: OptionalCell::empty(), rx_client: OptionalCell::empty(), state: Cell::new(XMacState::STARTUP), @@ -179,7 +188,7 @@ impl<'a, R: radio::Radio, A: Alarm<'a>> XMac<'a, R, A> { tx_len: Cell::new(0), tx_preamble_pending: Cell::new(false), tx_preamble_seq_num: Cell::new(0), - tx_preamble_buf: TakeCell::empty(), + tx_preamble_buf: TakeCell::new(mac_buf), rx_pending: Cell::new(false), } } @@ -303,6 +312,7 @@ impl<'a, R: radio::Radio, A: Alarm<'a>> XMac<'a, R, A> { &self, buf: &'static mut [u8], len: usize, + lqi: u8, crc_valid: bool, result: Result<(), ErrorCode>, ) { @@ -310,12 +320,12 @@ impl<'a, R: radio::Radio, A: Alarm<'a>> XMac<'a, R, A> { self.sleep(); self.rx_client.map(move |c| { - c.receive(buf, len, crc_valid, result); + c.receive(buf, len, lqi, crc_valid, result); }); } } -impl<'a, R: radio::Radio, A: Alarm<'a>> rng::Client for XMac<'a, R, A> { +impl<'a, R: radio::Radio<'a>, A: Alarm<'a>> rng::Client for XMac<'a, R, A> { fn randomness_available( &self, randomness: &mut dyn Iterator, @@ -344,9 +354,8 @@ impl<'a, R: radio::Radio, A: Alarm<'a>> rng::Client for XMac<'a, R, A> { } // The vast majority of these calls pass through to the underlying radio driver. -impl<'a, R: radio::Radio, A: Alarm<'a>> Mac for XMac<'a, R, A> { - fn initialize(&self, mac_buf: &'static mut [u8]) -> Result<(), ErrorCode> { - self.tx_preamble_buf.replace(mac_buf); +impl<'a, R: radio::Radio<'a>, A: Alarm<'a>> Mac<'a> for XMac<'a, R, A> { + fn initialize(&self) -> Result<(), ErrorCode> { self.state.set(XMacState::STARTUP); Ok(()) } @@ -360,7 +369,7 @@ impl<'a, R: radio::Radio, A: Alarm<'a>> Mac for XMac<'a, R, A> { self.radio.is_on() } - fn set_config_client(&self, client: &'static dyn radio::ConfigClient) { + fn set_config_client(&self, client: &'a dyn radio::ConfigClient) { self.radio.set_config_client(client) } @@ -392,11 +401,11 @@ impl<'a, R: radio::Radio, A: Alarm<'a>> Mac for XMac<'a, R, A> { self.radio.config_commit() } - fn set_transmit_client(&self, client: &'static dyn radio::TxClient) { + fn set_transmit_client(&self, client: &'a dyn radio::TxClient) { self.tx_client.set(client); } - fn set_receive_client(&self, client: &'static dyn radio::RxClient) { + fn set_receive_client(&self, client: &'a dyn radio::RxClient) { self.rx_client.set(client); } @@ -465,7 +474,7 @@ impl<'a, R: radio::Radio, A: Alarm<'a>> Mac for XMac<'a, R, A> { // Core of the XMAC protocol - when the timer fires, the protocol state // indicates the next state/action to take. -impl<'a, R: radio::Radio, A: Alarm<'a>> time::AlarmClient for XMac<'a, R, A> { +impl<'a, R: radio::Radio<'a>, A: Alarm<'a>> time::AlarmClient for XMac<'a, R, A> { fn alarm(&self) { match self.state.get() { XMacState::SLEEP => { @@ -509,7 +518,7 @@ impl<'a, R: radio::Radio, A: Alarm<'a>> time::AlarmClient for XMac<'a, R, A> { } } -impl<'a, R: radio::Radio, A: Alarm<'a>> radio::PowerClient for XMac<'a, R, A> { +impl<'a, R: radio::Radio<'a>, A: Alarm<'a>> radio::PowerClient for XMac<'a, R, A> { fn changed(&self, on: bool) { // If the radio turns on and we're in STARTUP, then either transition to // listening for incoming preambles or start transmitting preambles if @@ -530,7 +539,7 @@ impl<'a, R: radio::Radio, A: Alarm<'a>> radio::PowerClient for XMac<'a, R, A> { } } -impl<'a, R: radio::Radio, A: Alarm<'a>> radio::TxClient for XMac<'a, R, A> { +impl<'a, R: radio::Radio<'a>, A: Alarm<'a>> radio::TxClient for XMac<'a, R, A> { fn send_done(&self, buf: &'static mut [u8], acked: bool, result: Result<(), ErrorCode>) { match self.state.get() { // Completed a data transmission to the destination node @@ -564,11 +573,12 @@ impl<'a, R: radio::Radio, A: Alarm<'a>> radio::TxClient for XMac<'a, R, A> { // destination node is receiving packets/awake while we are attempting a // transmission, we put the radio in promiscuous mode. Not a huge issue, but // we need to be wary of incoming packets not actually addressed to our node. -impl<'a, R: radio::Radio, A: Alarm<'a>> radio::RxClient for XMac<'a, R, A> { +impl<'a, R: radio::Radio<'a>, A: Alarm<'a>> radio::RxClient for XMac<'a, R, A> { fn receive( &self, buf: &'static mut [u8], frame_len: usize, + lqi: u8, crc_valid: bool, result: Result<(), ErrorCode>, ) { @@ -630,7 +640,7 @@ impl<'a, R: radio::Radio, A: Alarm<'a>> radio::RxClient for XMac<'a, R, A> { if data_received { self.rx_pending.set(false); - self.call_rx_client(buf, frame_len, crc_valid, result); + self.call_rx_client(buf, frame_len, lqi, crc_valid, result); } else { self.radio.set_receive_buffer(buf); } diff --git a/capsules/src/isl29035.rs b/capsules/extra/src/isl29035.rs similarity index 95% rename from capsules/src/isl29035.rs rename to capsules/extra/src/isl29035.rs index 42b09a04e1..87ff0ffe16 100644 --- a/capsules/src/isl29035.rs +++ b/capsules/extra/src/isl29035.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for the ISL29035 digital light sensor. //! //! @@ -12,7 +16,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! # use capsules::virtual_alarm::VirtualMuxAlarm; //! @@ -37,7 +41,8 @@ use kernel::hil::time::{self, ConvertTicks}; use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::ErrorCode; -pub static mut BUF: [u8; 3] = [0; 3]; +/// Recommended buffer length. +pub const BUF_LEN: usize = 3; #[derive(Copy, Clone, PartialEq)] enum State { @@ -59,8 +64,8 @@ pub struct Isl29035<'a, A: time::Alarm<'a>> { impl<'a, A: time::Alarm<'a>> Isl29035<'a, A> { pub fn new(i2c: &'a dyn I2CDevice, alarm: &'a A, buffer: &'static mut [u8]) -> Isl29035<'a, A> { Isl29035 { - i2c: i2c, - alarm: alarm, + i2c, + alarm, state: Cell::new(State::Disabled), buffer: TakeCell::new(buffer), client: OptionalCell::empty(), @@ -116,7 +121,7 @@ impl<'a, A: time::Alarm<'a>> time::AlarmClient for Isl29035<'a, A> { // Turn on i2c to send commands. self.i2c.enable(); - buffer[0] = 0x02 as u8; + buffer[0] = 0x02_u8; if let Err((_error, buf)) = self.i2c.write_read(buffer, 1, 2) { self.buffer.replace(buf); self.i2c.disable(); diff --git a/capsules/extra/src/kv_driver.rs b/capsules/extra/src/kv_driver.rs new file mode 100644 index 0000000000..c6832cd95d --- /dev/null +++ b/capsules/extra/src/kv_driver.rs @@ -0,0 +1,540 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! KV Store Userspace Driver. +//! +//! Provides userspace access to key-value store. Access is restricted based on +//! `StoragePermissions` so processes must have the required permissions in +//! their TBF headers to use this interface. +//! +//! ```rust,ignore +//! +===============+ +//! || Userspace || +//! +===============+ +//! +//! -----Syscall Interface----- +//! +//! +-------------------------+ +//! | KV Driver (this file) | +//! +-------------------------+ +//! +//! hil::kv::KVPermissions +//! +//! +-------------------------+ +//! | Virtualizer | +//! +-------------------------+ +//! +//! hil::kv::KVPermissions +//! +//! +-------------------------+ +//! | K-V store Permissions | +//! +-------------------------+ +//! +//! hil::kv::KV +//! +//! +-------------------------+ +//! | K-V library | +//! +-------------------------+ +//! +//! hil::flash +//! ``` + +use capsules_core::driver; +/// Syscall driver number. +pub const DRIVER_NUM: usize = driver::NUM::Kv as usize; + +use core::cmp; +use kernel::errorcode; +use kernel::grant::Grant; +use kernel::grant::{AllowRoCount, AllowRwCount, UpcallCount}; +use kernel::hil::kv; +use kernel::processbuffer::{ReadableProcessBuffer, WriteableProcessBuffer}; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::utilities::leasable_buffer::SubSliceMut; +use kernel::{ErrorCode, ProcessId}; + +/// IDs for read-only allow buffers. +mod ro_allow { + /// Key. + pub const KEY: usize = 0; + /// Input value for set/add/update. + pub const VALUE: usize = 1; + /// The number of RO allow buffers the kernel stores for this grant. + pub const COUNT: u8 = 2; +} + +/// IDs for read-write allow buffers. +mod rw_allow { + /// Output value for get. + pub const VALUE: usize = 0; + /// The number of RW allow buffers the kernel stores for this grant. + pub const COUNT: u8 = 1; +} + +/// IDs for upcalls. +mod upcalls { + /// Single upcall. + pub const VALUE: usize = 0; + /// The number of upcalls the kernel stores for this grant. + pub const COUNT: u8 = 1; +} + +#[derive(Copy, Clone, PartialEq)] +enum UserSpaceOp { + Get, + Set, + Delete, + Add, + Update, +} + +/// Contents of the grant for each app. +#[derive(Default)] +pub struct App { + op: OptionalCell, +} + +/// Capsule that provides userspace access to a key-value store. +pub struct KVStoreDriver<'a, V: kv::KVPermissions<'a>> { + /// Underlying k-v store implementation. + kv: &'a V, + /// Grant storage for each app. + apps: Grant< + App, + UpcallCount<{ upcalls::COUNT }>, + AllowRoCount<{ ro_allow::COUNT }>, + AllowRwCount<{ rw_allow::COUNT }>, + >, + /// App that is actively using the k-v store. + processid: OptionalCell, + /// Key buffer. + key_buffer: TakeCell<'static, [u8]>, + /// Value buffer. + value_buffer: TakeCell<'static, [u8]>, +} + +impl<'a, V: kv::KVPermissions<'a>> KVStoreDriver<'a, V> { + pub fn new( + kv: &'a V, + key_buffer: &'static mut [u8], + value_buffer: &'static mut [u8], + grant: Grant< + App, + UpcallCount<{ upcalls::COUNT }>, + AllowRoCount<{ ro_allow::COUNT }>, + AllowRwCount<{ rw_allow::COUNT }>, + >, + ) -> KVStoreDriver<'a, V> { + KVStoreDriver { + kv, + apps: grant, + processid: OptionalCell::empty(), + key_buffer: TakeCell::new(key_buffer), + value_buffer: TakeCell::new(value_buffer), + } + } + + fn run(&self) -> Result<(), ErrorCode> { + self.processid.map_or(Err(ErrorCode::RESERVE), |processid| { + self.apps + .enter(processid, |app, kernel_data| { + let key_len = if app.op.is_some() { + // For all operations we need to copy in the key. + kernel_data + .get_readonly_processbuffer(ro_allow::KEY) + .and_then(|buffer| { + buffer.enter(|key| { + self.key_buffer.map_or(Err(ErrorCode::NOMEM), |key_buf| { + // Error if we cannot fit the key. + if key_buf.len() < key.len() { + Err(ErrorCode::SIZE) + } else { + key.copy_to_slice(&mut key_buf[..key.len()]); + Ok(key.len()) + } + }) + }) + }) + .unwrap_or(Err(ErrorCode::RESERVE))? + } else { + 0 + }; + + match app.op.get() { + Some(UserSpaceOp::Get) => { + if let Some(Some(e)) = self.key_buffer.take().map(|key_buf| { + self.value_buffer.take().map(|val_buf| { + let perms = processid + .get_storage_permissions() + .ok_or(ErrorCode::INVAL)?; + + let mut key = SubSliceMut::new(key_buf); + key.slice(..key_len); + + let value = SubSliceMut::new(val_buf); + + if let Err((key_ret, val_ret, e)) = + self.kv.get(key, value, perms) + { + self.key_buffer.replace(key_ret.take()); + self.value_buffer.replace(val_ret.take()); + return Err(e); + } + Ok(()) + }) + }) { + return e; + } + } + Some(UserSpaceOp::Set) + | Some(UserSpaceOp::Add) + | Some(UserSpaceOp::Update) => { + let value_len = kernel_data + .get_readonly_processbuffer(ro_allow::VALUE) + .and_then(|buffer| { + buffer.enter(|value| { + self.value_buffer.map_or(Err(ErrorCode::NOMEM), |val_buf| { + // Make sure there is room for the + // Tock KV header and the value. + let header_size = self.kv.header_size(); + let remaining_space = val_buf.len() - header_size; + if remaining_space < value.len() { + Err(ErrorCode::SIZE) + } else { + value.copy_to_slice( + &mut val_buf + [header_size..(value.len() + header_size)], + ); + Ok(value.len()) + } + }) + }) + }) + .unwrap_or(Err(ErrorCode::RESERVE))?; + + if let Some(Some(e)) = self.key_buffer.take().map(|key_buf| { + self.value_buffer.take().map(|val_buf| { + let perms = processid + .get_storage_permissions() + .ok_or(ErrorCode::INVAL)?; + + let mut key = SubSliceMut::new(key_buf); + key.slice(..key_len); + + // Make sure we provide a value buffer with + // space for the tock kv header at the + // front. + let header_size = self.kv.header_size(); + let mut value = SubSliceMut::new(val_buf); + value.slice(..(value_len + header_size)); + + if let Err((key_ret, val_ret, e)) = match app.op.get() { + Some(UserSpaceOp::Set) => self.kv.set(key, value, perms), + Some(UserSpaceOp::Add) => self.kv.add(key, value, perms), + Some(UserSpaceOp::Update) => { + self.kv.update(key, value, perms) + } + _ => Ok(()), + } { + self.key_buffer.replace(key_ret.take()); + self.value_buffer.replace(val_ret.take()); + return Err(e); + } + Ok(()) + }) + }) { + return e; + } + } + Some(UserSpaceOp::Delete) => { + if let Some(e) = self.key_buffer.take().map(|key_buf| { + let perms = processid + .get_storage_permissions() + .ok_or(ErrorCode::INVAL)?; + + let mut key = SubSliceMut::new(key_buf); + key.slice(..key_len); + + if let Err((key_ret, e)) = self.kv.delete(key, perms) { + self.key_buffer.replace(key_ret.take()); + return Err(e); + } + Ok(()) + }) { + return e; + } + } + _ => {} + } + + Ok(()) + }) + .unwrap_or_else(|err| Err(err.into())) + }) + } + + fn check_queue(&self) { + // If an app is already running let it complete. + if self.processid.is_some() { + return; + } + + for appiter in self.apps.iter() { + let processid = appiter.processid(); + let has_pending_op = appiter.enter(|app, _| { + // If this app has a pending command let's use it. + app.op.is_some() + }); + let started_command = if has_pending_op { + // Mark this driver as being in use. + self.processid.set(processid); + self.run() == Ok(()) + } else { + false + }; + if started_command { + break; + } else { + self.processid.clear(); + } + } + } +} + +impl<'a, V: kv::KVPermissions<'a>> kv::KVClient for KVStoreDriver<'a, V> { + fn get_complete( + &self, + result: Result<(), ErrorCode>, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ) { + self.key_buffer.replace(key.take()); + + self.processid.map(move |id| { + self.apps.enter(id, move |app, upcalls| { + if app.op.contains(&UserSpaceOp::Get) { + app.op.clear(); + + if let Err(e) = result { + upcalls + .schedule_upcall( + upcalls::VALUE, + (errorcode::into_statuscode(e.into()), 0, 0), + ) + .ok(); + } else { + let value_len = value.len(); + let ret = upcalls + .get_readwrite_processbuffer(rw_allow::VALUE) + .and_then(|buffer| { + buffer.mut_enter(|appslice| { + let copy_len = cmp::min(value_len, appslice.len()); + appslice[..copy_len].copy_from_slice(&value[..copy_len]); + if copy_len < value_len { + Err(ErrorCode::SIZE) + } else { + Ok(()) + } + }) + }) + .unwrap_or(Err(ErrorCode::RESERVE)); + + // Signal the upcall, and return the length of the + // value. Userspace should be careful to check for an + // error and only read the portion that would fit in the + // buffer if the value was larger than the provided + // processbuffer. + upcalls + .schedule_upcall( + upcalls::VALUE, + (errorcode::into_statuscode(ret), value_len, 0), + ) + .ok(); + } + } + + self.value_buffer.replace(value.take()); + }) + }); + + // We have completed the operation so see if there is a queued operation + // to run next. + self.processid.clear(); + self.check_queue(); + } + + fn set_complete( + &self, + result: Result<(), ErrorCode>, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ) { + self.key_buffer.replace(key.take()); + self.value_buffer.replace(value.take()); + + // Signal the upcall and clear the requested op. + self.processid.map(move |id| { + self.apps.enter(id, move |app, upcalls| { + if app.op.contains(&UserSpaceOp::Set) { + app.op.clear(); + upcalls + .schedule_upcall(upcalls::VALUE, (errorcode::into_statuscode(result), 0, 0)) + .ok(); + } + }) + }); + + // We have completed the operation so see if there is a queued operation + // to run next. + self.processid.clear(); + self.check_queue(); + } + + fn add_complete( + &self, + result: Result<(), ErrorCode>, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ) { + self.key_buffer.replace(key.take()); + self.value_buffer.replace(value.take()); + + // Signal the upcall and clear the requested op. + self.processid.map(move |id| { + self.apps.enter(id, move |app, upcalls| { + if app.op.contains(&UserSpaceOp::Add) { + app.op.clear(); + upcalls + .schedule_upcall(upcalls::VALUE, (errorcode::into_statuscode(result), 0, 0)) + .ok(); + } + }) + }); + + // We have completed the operation so see if there is a queued operation + // to run next. + self.processid.clear(); + self.check_queue(); + } + + fn update_complete( + &self, + result: Result<(), ErrorCode>, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ) { + self.key_buffer.replace(key.take()); + self.value_buffer.replace(value.take()); + + // Signal the upcall and clear the requested op. + self.processid.map(move |id| { + self.apps.enter(id, move |app, upcalls| { + if app.op.contains(&UserSpaceOp::Update) { + app.op.clear(); + upcalls + .schedule_upcall(upcalls::VALUE, (errorcode::into_statuscode(result), 0, 0)) + .ok(); + } + }) + }); + + // We have completed the operation so see if there is a queued operation + // to run next. + self.processid.clear(); + self.check_queue(); + } + + fn delete_complete(&self, result: Result<(), ErrorCode>, key: SubSliceMut<'static, u8>) { + self.key_buffer.replace(key.take()); + + self.processid.map(move |id| { + self.apps.enter(id, move |app, upcalls| { + if app.op.contains(&UserSpaceOp::Delete) { + app.op.clear(); + upcalls + .schedule_upcall(upcalls::VALUE, (errorcode::into_statuscode(result), 0, 0)) + .ok(); + } + }) + }); + + // We have completed the operation so see if there is a queued operation + // to run next. + self.processid.clear(); + self.check_queue(); + } +} + +impl<'a, V: kv::KVPermissions<'a>> SyscallDriver for KVStoreDriver<'a, V> { + fn command( + &self, + command_num: usize, + _data1: usize, + _data2: usize, + processid: ProcessId, + ) -> CommandReturn { + match command_num { + // check if present + 0 => CommandReturn::success(), + + // get, set, delete, add, update + 1 | 2 | 3 | 4 | 5 => { + if self.processid.is_none() { + // Nothing is using the KV store, so we can handle this + // request. + self.processid.set(processid); + let _ = self.apps.enter(processid, |app, _| match command_num { + 1 => app.op.set(UserSpaceOp::Get), + 2 => app.op.set(UserSpaceOp::Set), + 3 => app.op.set(UserSpaceOp::Delete), + 4 => app.op.set(UserSpaceOp::Add), + 5 => app.op.set(UserSpaceOp::Update), + _ => {} + }); + let ret = self.run(); + + if let Err(e) = ret { + self.processid.clear(); + self.check_queue(); + CommandReturn::failure(e) + } else { + CommandReturn::success() + } + } else { + // There is an active app, so queue this request (if + // possible). + self.apps + .enter(processid, |app, _| { + if app.op.is_some() { + // No more room in the queue, nowhere to store + // this request. + CommandReturn::failure(ErrorCode::NOMEM) + } else { + // This app has not already queued a command so + // we can store this. + match command_num { + 1 => app.op.set(UserSpaceOp::Get), + 2 => app.op.set(UserSpaceOp::Set), + 3 => app.op.set(UserSpaceOp::Delete), + 4 => app.op.set(UserSpaceOp::Add), + 5 => app.op.set(UserSpaceOp::Update), + _ => {} + } + CommandReturn::success() + } + }) + .unwrap_or_else(|err| err.into()) + } + } + + // default + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { + self.apps.enter(processid, |_, _| {}) + } +} diff --git a/capsules/extra/src/kv_store_permissions.rs b/capsules/extra/src/kv_store_permissions.rs new file mode 100644 index 0000000000..b6b14074a1 --- /dev/null +++ b/capsules/extra/src/kv_store_permissions.rs @@ -0,0 +1,506 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Tock Key-Value store capsule with permissions. +//! +//! This capsule provides a KV interface with permissions and access control. +//! +//! ```rust,ignore +//! +-----------------------+ +//! | Capsule using K-V | +//! +-----------------------+ +//! +//! hil::kv::KVPermissions +//! +//! +-----------------------+ +//! | K-V store (this file) | +//! +-----------------------+ +//! +//! hil::kv::KV +//! +//! +-----------------------+ +//! | K-V library | +//! +-----------------------+ +//! +//! hil::flash +//! ``` + +use core::mem; +use kernel::hil::kv; +use kernel::storage_permissions::StoragePermissions; +use kernel::utilities::cells::{MapCell, OptionalCell, TakeCell}; +use kernel::utilities::leasable_buffer::SubSliceMut; +use kernel::ErrorCode; + +#[derive(Clone, Copy, PartialEq, Debug)] +enum Operation { + Get, + Set, + Add, + Update, + Delete, +} + +/// Current version of the Tock K-V header. +const HEADER_VERSION: u8 = 0; +pub const HEADER_LENGTH: usize = mem::size_of::(); + +/// This is the header used for KV stores. +#[repr(packed)] +struct KeyHeader { + version: u8, + length: u32, + write_id: u32, +} + +impl KeyHeader { + /// Create a new `KeyHeader` from a buffer + fn new_from_buf(buf: &[u8]) -> Self { + Self { + version: buf[0], + length: u32::from_le_bytes(buf[1..5].try_into().unwrap_or([0; 4])), + write_id: u32::from_le_bytes(buf[5..9].try_into().unwrap_or([0; 4])), + } + } + + /// Copy the header to `buf` + fn copy_to_buf(&self, buf: &mut [u8]) { + buf[0] = self.version; + buf[1..5].copy_from_slice(&self.length.to_le_bytes()); + buf[5..9].copy_from_slice(&self.write_id.to_le_bytes()); + } +} + +/// Key-Value store with Tock-specific extensions for permissions and access +/// control. +/// +/// Implements `KVPermissions` on top of `KV`. +pub struct KVStorePermissions<'a, K: kv::KV<'a>> { + kv: &'a K, + header_value: TakeCell<'static, [u8]>, + + client: OptionalCell<&'a dyn kv::KVClient>, + operation: OptionalCell, + + value: MapCell>, + valid_ids: OptionalCell, +} + +impl<'a, K: kv::KV<'a>> KVStorePermissions<'a, K> { + pub fn new( + kv: &'a K, + header_value: &'static mut [u8; HEADER_LENGTH], + ) -> KVStorePermissions<'a, K> { + Self { + kv, + header_value: TakeCell::new(header_value), + client: OptionalCell::empty(), + operation: OptionalCell::empty(), + value: MapCell::empty(), + valid_ids: OptionalCell::empty(), + } + } + + fn insert( + &self, + key: SubSliceMut<'static, u8>, + mut value: SubSliceMut<'static, u8>, + permissions: StoragePermissions, + operation: Operation, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + > { + let write_id = match permissions.get_write_id() { + Some(write_id) => write_id, + None => return Err((key, value, ErrorCode::INVAL)), + }; + + if self.operation.is_some() { + return Err((key, value, ErrorCode::BUSY)); + } + + // The caller must ensure there is space for the header. + if value.len() < HEADER_LENGTH { + return Err((key, value, ErrorCode::SIZE)); + } + + // Create the Tock header. + let header = KeyHeader { + version: HEADER_VERSION, + length: (value.len() - HEADER_LENGTH) as u32, + write_id, + }; + + // Copy in the header to the buffer. + header.copy_to_buf(value.as_slice()); + + self.operation.set(operation); + + match operation { + Operation::Set | Operation::Update => { + self.valid_ids.set(permissions); + + // We first read the key to see if we are allowed to overwrite it. + match self.header_value.take() { + Some(header_value) => match self.kv.get(key, SubSliceMut::new(header_value)) { + Ok(()) => { + self.value.replace(value); + Ok(()) + } + Err((key, hvalue, e)) => { + self.header_value.replace(hvalue.take()); + self.operation.clear(); + Err((key, value, e)) + } + }, + None => Err((key, value, ErrorCode::FAIL)), + } + } + + Operation::Add => { + // Since add will only succeed if the key is not already there, + // we do not have to worry about overwriting and do not need to + // check permissions. + match self.kv.add(key, value) { + Ok(()) => Ok(()), + Err((key, val, e)) => { + self.operation.clear(); + Err((key, val, e)) + } + } + } + + _ => Err((key, value, ErrorCode::FAIL)), + } + } +} + +impl<'a, K: kv::KV<'a>> kv::KVPermissions<'a> for KVStorePermissions<'a, K> { + fn set_client(&self, client: &'a dyn kv::KVClient) { + self.client.set(client); + } + + fn get( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + permissions: StoragePermissions, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + > { + if self.operation.is_some() { + return Err((key, value, ErrorCode::BUSY)); + } + + self.operation.set(Operation::Get); + self.valid_ids.set(permissions); + + match self.kv.get(key, value) { + Ok(()) => Ok(()), + Err((key, val, e)) => { + self.operation.clear(); + Err((key, val, e)) + } + } + } + + fn set( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + permissions: StoragePermissions, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + > { + self.insert(key, value, permissions, Operation::Set) + } + + fn add( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + permissions: StoragePermissions, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + > { + self.insert(key, value, permissions, Operation::Add) + } + + fn update( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + permissions: StoragePermissions, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + > { + self.insert(key, value, permissions, Operation::Update) + } + + fn delete( + &self, + key: SubSliceMut<'static, u8>, + permissions: StoragePermissions, + ) -> Result<(), (SubSliceMut<'static, u8>, ErrorCode)> { + if self.operation.is_some() { + return Err((key, ErrorCode::BUSY)); + } + + self.operation.set(Operation::Delete); + self.valid_ids.set(permissions); + + match self.header_value.take() { + Some(header_value) => match self.kv.get(key, SubSliceMut::new(header_value)) { + Ok(()) => Ok(()), + Err((key, hvalue, e)) => { + self.header_value.replace(hvalue.take()); + self.operation.clear(); + Err((key, e)) + } + }, + None => Err((key, ErrorCode::FAIL)), + } + } + + fn header_size(&self) -> usize { + HEADER_LENGTH + } +} + +impl<'a, K: kv::KV<'a>> kv::KVClient for KVStorePermissions<'a, K> { + fn get_complete( + &self, + result: Result<(), ErrorCode>, + key: SubSliceMut<'static, u8>, + mut value: SubSliceMut<'static, u8>, + ) { + self.operation.map(|op| { + match op { + Operation::Set => { + // Need to determine if we have permission to set this key. + let mut access_allowed = false; + + if result.is_ok() || result.err() == Some(ErrorCode::SIZE) { + let header = KeyHeader::new_from_buf(value.as_slice()); + + if header.version == HEADER_VERSION { + self.valid_ids.map(|perms| { + access_allowed = perms.check_write_permission(header.write_id); + }); + } + } else if result.err() == Some(ErrorCode::NOSUPPORT) { + // Key wasn't found, so we can create it fresh. + access_allowed = true; + } + + self.header_value.replace(value.take()); + + if access_allowed { + self.value + .take() + .map(|set_value| match self.kv.set(key, set_value) { + Ok(()) => {} + + Err((key, set_value, e)) => { + self.operation.clear(); + self.client.map(move |cb| { + cb.set_complete(Err(e), key, set_value); + }); + } + }); + } else { + self.operation.clear(); + self.value.take().map(|set_value| { + self.client.map(move |cb| { + cb.set_complete(Err(ErrorCode::NOSUPPORT), key, set_value); + }); + }); + } + } + Operation::Update => { + // Need to determine if we have permission to set this key. + let mut access_allowed = false; + + if result.is_ok() || result.err() == Some(ErrorCode::SIZE) { + let header = KeyHeader::new_from_buf(value.as_slice()); + + if header.version == HEADER_VERSION { + self.valid_ids.map(|perms| { + access_allowed = perms.check_write_permission(header.write_id); + }); + } + } + + self.header_value.replace(value.take()); + + if access_allowed { + self.value + .take() + .map(|set_value| match self.kv.update(key, set_value) { + Ok(()) => {} + + Err((key, set_value, e)) => { + self.operation.clear(); + self.client.map(move |cb| { + cb.update_complete(Err(e), key, set_value); + }); + } + }); + } else { + self.operation.clear(); + self.value.take().map(|set_value| { + self.client.map(move |cb| { + cb.update_complete(Err(ErrorCode::NOSUPPORT), key, set_value); + }); + }); + } + } + Operation::Delete => { + // Before we delete an object we retrieve the header to + // ensure that we have permissions to access it. In that + // case we don't need to supply a buffer long enough to + // store the full value, so a `SIZE` error code is ok and we + // can continue to remove the object. + let mut access_allowed = false; + + if result.is_ok() || result.err() == Some(ErrorCode::SIZE) { + let header = KeyHeader::new_from_buf(value.as_slice()); + + if header.version == HEADER_VERSION { + self.valid_ids.map(|perms| { + access_allowed = perms.check_write_permission(header.write_id); + }); + } + } + + self.header_value.replace(value.take()); + + if access_allowed { + match self.kv.delete(key) { + Ok(()) => {} + + Err((key, e)) => { + self.operation.clear(); + self.client.map(move |cb| { + cb.delete_complete(Err(e), key); + }); + } + } + } else { + self.operation.clear(); + self.client.map(move |cb| { + cb.delete_complete(Err(ErrorCode::NOSUPPORT), key); + }); + } + } + Operation::Get => { + self.operation.clear(); + + let mut read_allowed = false; + + if result.is_ok() || result.err() == Some(ErrorCode::SIZE) { + let header = KeyHeader::new_from_buf(value.as_slice()); + + if header.version == HEADER_VERSION { + self.valid_ids.map(|perms| { + read_allowed = perms.check_read_permission(header.write_id); + }); + + if read_allowed { + // Remove the header from the accessible portion + // of the buffer. + value.slice(HEADER_LENGTH..); + } + } + } + + if !read_allowed { + // Access denied or the header is invalid, zero the buffer. + value.as_slice().iter_mut().for_each(|m| *m = 0) + } + + self.client.map(move |cb| { + if read_allowed { + cb.get_complete(result, key, value); + } else { + // The operation failed or the caller doesn't + // have permission, just return the error for + // key not found (and an empty buffer). + cb.get_complete(Err(ErrorCode::NOSUPPORT), key, value); + } + }); + } + _ => {} + } + }); + } + + fn set_complete( + &self, + result: Result<(), ErrorCode>, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ) { + self.operation.clear(); + self.client.map(move |cb| { + cb.set_complete(result, key, value); + }); + } + + fn add_complete( + &self, + result: Result<(), ErrorCode>, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ) { + self.operation.clear(); + self.client.map(move |cb| { + cb.add_complete(result, key, value); + }); + } + + fn update_complete( + &self, + result: Result<(), ErrorCode>, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ) { + self.operation.clear(); + self.client.map(move |cb| { + cb.update_complete(result, key, value); + }); + } + + fn delete_complete(&self, result: Result<(), ErrorCode>, key: SubSliceMut<'static, u8>) { + self.operation.clear(); + self.client.map(move |cb| { + cb.delete_complete(result, key); + }); + } +} diff --git a/capsules/src/l3gd20.rs b/capsules/extra/src/l3gd20.rs similarity index 91% rename from capsules/src/l3gd20.rs rename to capsules/extra/src/l3gd20.rs index fbf04f9b64..2f6d3b288b 100644 --- a/capsules/src/l3gd20.rs +++ b/capsules/extra/src/l3gd20.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for the MEMS L3gd20Spi motion sensor, 3 axys digital output gyroscope //! and temperature sensor. //! @@ -62,7 +66,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! let mux_spi = components::spi::SpiMuxComponent::new(&stm32f3xx::spi::SPI1) //! .finalize(components::spi_mux_component_helper!(stm32f3xx::spi::Spi)); //! @@ -73,7 +77,7 @@ //! //! NineDof Example //! -//! ```rust +//! ```rust,ignore //! let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); //! let grant_ninedof = board_kernel.create_grant(&grant_cap); //! @@ -87,7 +91,7 @@ //! //! Temperature Example //! -//! ```rust +//! ```rust,ignore //! let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); //! let grant_temp = board_kernel.create_grant(&grant_cap); //! @@ -111,7 +115,7 @@ use kernel::syscall::{CommandReturn, SyscallDriver}; use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::{ErrorCode, ProcessId}; -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::L3gd20 as usize; /* Identification number */ @@ -152,8 +156,8 @@ const L3GD20_REG_INT1_DURATION: u8 = 0x38; pub const L3GD20_TX_SIZE: usize = 10; pub const L3GD20_RX_SIZE: usize = 10; -pub static mut TXBUFFER: [u8; L3GD20_TX_SIZE] = [0; L3GD20_TX_SIZE]; -pub static mut RXBUFFER: [u8; L3GD20_RX_SIZE] = [0; L3GD20_RX_SIZE]; +pub const TX_BUF_LEN: usize = L3GD20_TX_SIZE; +pub const RX_BUF_LEN: usize = L3GD20_RX_SIZE; /* Sensitivity factors, datasheet pg. 9 */ const L3GD20_SCALE_250: isize = 875; /* 8.75 mdps/digit */ @@ -180,8 +184,8 @@ enum L3gd20Status { #[derive(Default)] pub struct App {} -pub struct L3gd20Spi<'a> { - spi: &'a dyn spi::SpiMasterDevice, +pub struct L3gd20Spi<'a, S: spi::SpiMasterDevice<'a>> { + spi: &'a S, txbuffer: TakeCell<'static, [u8]>, rxbuffer: TakeCell<'static, [u8]>, status: Cell, @@ -195,16 +199,16 @@ pub struct L3gd20Spi<'a> { temperature_client: OptionalCell<&'a dyn sensors::TemperatureClient>, } -impl<'a> L3gd20Spi<'a> { +impl<'a, S: spi::SpiMasterDevice<'a>> L3gd20Spi<'a, S> { pub fn new( - spi: &'a dyn spi::SpiMasterDevice, + spi: &'a S, txbuffer: &'static mut [u8; L3GD20_TX_SIZE], rxbuffer: &'static mut [u8; L3GD20_RX_SIZE], grants: Grant, AllowRoCount<0>, AllowRwCount<0>>, - ) -> L3gd20Spi<'a> { + ) -> L3gd20Spi<'a, S> { // setup and return struct L3gd20Spi { - spi: spi, + spi, txbuffer: TakeCell::new(txbuffer), rxbuffer: TakeCell::new(rxbuffer), status: Cell::new(L3gd20Status::Idle), @@ -213,7 +217,7 @@ impl<'a> L3gd20Spi<'a> { hpf_divider: Cell::new(0), scale: Cell::new(0), current_process: OptionalCell::empty(), - grants: grants, + grants, nine_dof_client: OptionalCell::empty(), temperature_client: OptionalCell::empty(), } @@ -245,7 +249,7 @@ impl<'a> L3gd20Spi<'a> { self.hpf_enabled.set(enabled); self.txbuffer.take().map(|buf| { buf[0] = L3GD20_REG_CTRL_REG5; - buf[1] = if enabled { 1 } else { 0 } << 4; + buf[1] = u8::from(enabled) << 4; // TODO verify SPI return value let _ = self.spi.read_write_bytes(buf, None, 2); }); @@ -308,7 +312,7 @@ impl<'a> L3gd20Spi<'a> { } } -impl SyscallDriver for L3gd20Spi<'_> { +impl<'a, S: spi::SpiMasterDevice<'a>> SyscallDriver for L3gd20Spi<'a, S> { fn command( &self, command_num: usize, @@ -322,7 +326,7 @@ impl SyscallDriver for L3gd20Spi<'_> { let match_or_empty_or_nonexistent = self.current_process.map_or(true, |current_process| { self.grants - .enter(*current_process, |_, _| current_process == &process_id) + .enter(current_process, |_, _| current_process == process_id) .unwrap_or(true) }); @@ -375,7 +379,7 @@ impl SyscallDriver for L3gd20Spi<'_> { // Set High Pass Filter Mode and Divider 5 => { if self.status.get() == L3gd20Status::Idle { - let enabled = if data1 == 1 { true } else { false }; + let enabled = data1 == 1; self.enable_hpf(enabled); CommandReturn::success() } else { @@ -410,7 +414,7 @@ impl SyscallDriver for L3gd20Spi<'_> { } } -impl spi::SpiMasterClient for L3gd20Spi<'_> { +impl<'a, S: spi::SpiMasterDevice<'a>> spi::SpiMasterClient for L3gd20Spi<'a, S> { fn read_write_done( &self, write_buffer: &'static mut [u8], @@ -419,20 +423,16 @@ impl spi::SpiMasterClient for L3gd20Spi<'_> { _status: Result<(), ErrorCode>, ) { self.current_process.map(|proc_id| { - let _result = self.grants.enter(*proc_id, |_app, upcalls| { + let _result = self.grants.enter(proc_id, |_app, upcalls| { self.status.set(match self.status.get() { L3gd20Status::IsPresent => { let present = if let Some(ref buf) = read_buffer { - if buf[1] == L3GD20_WHO_AM_I { - true - } else { - false - } + buf[1] == L3GD20_WHO_AM_I } else { false }; upcalls - .schedule_upcall(0, (1, if present { 1 } else { 0 }, 0)) + .schedule_upcall(0, (1, usize::from(present), 0)) .ok(); L3gd20Status::Idle } @@ -488,17 +488,17 @@ impl spi::SpiMasterClient for L3gd20Spi<'_> { } L3gd20Status::ReadTemperature => { - let mut temperature: usize = 0; + let mut temperature = 0; let value = if let Some(ref buf) = read_buffer { if len >= 2 { - temperature = (buf[1] as i8) as usize; + temperature = buf[1] as i32; self.temperature_client.map(|client| { - client.callback(temperature * 100); + client.callback(Ok(temperature * 100)); }); true } else { self.temperature_client.map(|client| { - client.callback(0); + client.callback(Err(ErrorCode::FAIL)); }); false } @@ -506,7 +506,9 @@ impl spi::SpiMasterClient for L3gd20Spi<'_> { false }; if value { - upcalls.schedule_upcall(0, (temperature, 0, 0)).ok(); + upcalls + .schedule_upcall(0, (temperature as usize, 0, 0)) + .ok(); } else { upcalls.schedule_upcall(0, (0, 0, 0)).ok(); } @@ -527,7 +529,7 @@ impl spi::SpiMasterClient for L3gd20Spi<'_> { } } -impl<'a> sensors::NineDof<'a> for L3gd20Spi<'a> { +impl<'a, S: spi::SpiMasterDevice<'a>> sensors::NineDof<'a> for L3gd20Spi<'a, S> { fn set_client(&self, nine_dof_client: &'a dyn sensors::NineDofClient) { self.nine_dof_client.replace(nine_dof_client); } @@ -542,7 +544,7 @@ impl<'a> sensors::NineDof<'a> for L3gd20Spi<'a> { } } -impl<'a> sensors::TemperatureDriver<'a> for L3gd20Spi<'a> { +impl<'a, S: spi::SpiMasterDevice<'a>> sensors::TemperatureDriver<'a> for L3gd20Spi<'a, S> { fn set_client(&self, temperature_client: &'a dyn sensors::TemperatureClient) { self.temperature_client.replace(temperature_client); } diff --git a/capsules/src/led_matrix.rs b/capsules/extra/src/led_matrix.rs similarity index 57% rename from capsules/src/led_matrix.rs rename to capsules/extra/src/led_matrix.rs index b37174e254..eca66ac740 100644 --- a/capsules/src/led_matrix.rs +++ b/capsules/extra/src/led_matrix.rs @@ -1,98 +1,88 @@ -//! Provides userspace access to LEDs on an LED matrix. +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Service capsule for access to LEDs on a LED matrix. //! //! Usage //! ----- //! -//! ```rust -//! let buffer = static_init!([u8; 5], [0; 5]); -//! -//! let cols = static_init!( -//! [&nrf52833::gpio::GPIOPin; 5], -//! [ -//! &base_peripherals.gpio_port[LED_MATRIX_ROWS[0]], -//! &base_peripherals.gpio_port[LED_MATRIX_ROWS[1]], -//! &base_peripherals.gpio_port[LED_MATRIX_ROWS[2]], -//! &base_peripherals.gpio_port[LED_MATRIX_ROWS[3]], -//! &base_peripherals.gpio_port[LED_MATRIX_ROWS[4]] -//! ] -//! ); +//! ```rust,ignore +//! let led_matrix = components::led_matrix_component_helper!( +//! nrf52833::gpio::GPIOPin, +//! nrf52::rtc::Rtc<'static>, +//! mux_alarm, +//! @fps => 60, +//! @cols => kernel::hil::gpio::ActivationMode::ActiveLow, +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_COLS[0]], +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_COLS[1]], +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_COLS[2]], +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_COLS[3]], +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_COLS[4]], +//! @rows => kernel::hil::gpio::ActivationMode::ActiveHigh, +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_ROWS[0]], +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_ROWS[1]], +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_ROWS[2]], +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_ROWS[3]], +//! &nrf52833_peripherals.gpio_port[LED_MATRIX_ROWS[4]] //! -//! let rows = static_init!( -//! [&nrf52833::gpio::GPIOPin; 5], -//! [ -//! &base_peripherals.gpio_port[LED_MATRIX_ROWS[0]], -//! &base_peripherals.gpio_port[LED_MATRIX_ROWS[1]], -//! &base_peripherals.gpio_port[LED_MATRIX_ROWS[2]], -//! &base_peripherals.gpio_port[LED_MATRIX_ROWS[3]], -//! &base_peripherals.gpio_port[LED_MATRIX_ROWS[4]] -//! ] -//! ); +//! ) +//! .finalize(components::led_matrix_component_buf!( +//! nrf52833::gpio::GPIOPin, +//! nrf52::rtc::Rtc<'static> +//! )); //! //! let led = static_init!( -//! capsules::led_matrix::LedMatrixDriver< +//! capsules::led::LedDriver< //! 'static, -//! nrf52::gpio::GPIOPin<'static>, -//! capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, +//! capsules::led_matrix::LedMatrixLed< +//! 'static, +//! nrf52::gpio::GPIOPin<'static>, +//! capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, +//! >, +//! 25, //! >, -//! capsules::led_matrix::LedMatrixDriver::new(cols, rows, buffer, led_alarm, kernel::hil::gpio::ActivationMode::ActiveLow, kernel::hil::gpio::ActivationMode::ActiveHigh, 60) -//! ); -//! -//! led_alarm.set_alarm_client(led); -//! -//! led.init(); -//! ``` -//! -//! let single_led = static_init!( -//! capsules::led_matrix::LedMatrixLed< -//! 'static, +//! capsules::led::LedDriver::new(components::led_matrix_leds!( //! nrf52::gpio::GPIOPin<'static>, //! capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, -//! >, -//! led, -//! 1, -//! 2 +//! led_matrix, +//! (0, 0), +//! (1, 0), +//! (2, 0), +//! (3, 0), +//! (4, 0), +//! (0, 1), +//! (1, 1), +//! (2, 1), +//! (3, 1), +//! (4, 1), +//! (0, 2), +//! (1, 2), +//! (2, 2), +//! (3, 2), +//! (4, 2), +//! (0, 3), +//! (1, 3), +//! (2, 3), +//! (3, 3), +//! (4, 3), +//! (0, 4), +//! (1, 4), +//! (2, 4), +//! (3, 4), +//! (4, 4) +//! )), //! ); -//! -//! -//! Syscall Interface -//! ----------------- -//! -//! - Stability: 2 - Stable -//! -//! ### Command -//! -//! All LED operations are synchronous, so this capsule only uses the `command` -//! syscall. -//! -//! #### `command_num` -//! -//! - `0`: Return the number of LEDs on this platform. -//! - `data`: Unused. -//! - Return: Number of LEDs. -//! - `1`: Turn the LED on. -//! - `data`: The index of the LED. Starts at 0. -//! - Return: `Ok(())` if the LED index was valid, `INVAL` otherwise. -//! - `2`: Turn the LED off. -//! - `data`: The index of the LED. Starts at 0. -//! - Return: `Ok(())` if the LED index was valid, `INVAL` otherwise. -//! - `3`: Toggle the on/off state of the LED. -//! - `data`: The index of the LED. Starts at 0. -//! - Return: `Ok(())` if the LED index was valid, `INVAL` otherwise. use core::cell::Cell; -use kernel::syscall::{CommandReturn, SyscallDriver}; use kernel::utilities::cells::TakeCell; -use kernel::{ErrorCode, ProcessId}; +use kernel::ErrorCode; use kernel::hil::gpio::{ActivationMode, Pin}; use kernel::hil::led::Led; use kernel::hil::time::{Alarm, AlarmClient, ConvertTicks}; -/// Syscall driver number. -use crate::driver; -pub const DRIVER_NUM: usize = driver::NUM::Led as usize; - /// Holds the array of LEDs and implements a `Driver` interface to /// control them. pub struct LedMatrixDriver<'a, L: Pin, A: Alarm<'a>> { @@ -126,8 +116,8 @@ impl<'a, L: Pin, A: Alarm<'a>> LedMatrixDriver<'a, L, A> { rows, buffer: TakeCell::new(buffer), alarm, - col_activation: col_activation, - row_activation: row_activation, + col_activation, + row_activation, current_row: Cell::new(0), timing: (1000 / (refresh_rate * rows.len())) as u8, } @@ -208,7 +198,7 @@ impl<'a, L: Pin, A: Alarm<'a>> LedMatrixDriver<'a, L, A> { fn on_index(&self, led_index: usize) -> Result<(), ErrorCode> { if led_index < self.rows.len() * self.cols.len() { self.buffer - .map(|bits| bits[led_index / 8] = bits[led_index / 8] | (1 << (led_index % 8))); + .map(|bits| bits[led_index / 8] |= 1 << (led_index % 8)); Ok(()) } else { Err(ErrorCode::INVAL) @@ -222,7 +212,7 @@ impl<'a, L: Pin, A: Alarm<'a>> LedMatrixDriver<'a, L, A> { fn off_index(&self, led_index: usize) -> Result<(), ErrorCode> { if led_index < self.rows.len() * self.cols.len() { self.buffer - .map(|bits| bits[led_index / 8] = bits[led_index / 8] & !(1 << led_index % 8)); + .map(|bits| bits[led_index / 8] &= !(1 << (led_index % 8))); Ok(()) } else { Err(ErrorCode::INVAL) @@ -236,7 +226,7 @@ impl<'a, L: Pin, A: Alarm<'a>> LedMatrixDriver<'a, L, A> { fn toggle_index(&self, led_index: usize) -> Result<(), ErrorCode> { if led_index < self.rows.len() * self.cols.len() { self.buffer - .map(|bits| bits[led_index / 8] = bits[led_index / 8] ^ (1 << (led_index % 8))); + .map(|bits| bits[led_index / 8] ^= 1 << (led_index % 8)); Ok(()) } else { Err(ErrorCode::INVAL) @@ -264,42 +254,6 @@ impl<'a, L: Pin, A: Alarm<'a>> AlarmClient for LedMatrixDriver<'a, L, A> { } } -impl<'a, L: Pin, A: Alarm<'a>> SyscallDriver for LedMatrixDriver<'a, L, A> { - /// Control the LEDs. - /// - /// ### `command_num` - /// - /// - `0`: Returns the number of LEDs on the board. This will always be 0 or - /// greater, and therefore also allows for checking for this driver. - /// - `1`: Turn the LED at index specified by `data` on. Returns `INVAL` if - /// the LED index is not valid. - /// - `2`: Turn the LED at index specified by `data` off. Returns `INVAL` - /// if the LED index is not valid. - /// - `3`: Toggle the LED at index specified by `data` on or off. Returns - /// `INVAL` if the LED index is not valid. - fn command(&self, command_num: usize, data: usize, _: usize, _: ProcessId) -> CommandReturn { - match command_num { - // get number of LEDs - 0 => CommandReturn::success_u32((self.cols.len() * self.rows.len()) as u32), - - // on - 1 => CommandReturn::from(self.on_index(data)), - - // off - 2 => CommandReturn::from(self.off_index(data)), - - // toggle - 3 => CommandReturn::from(self.toggle_index(data)), - - // default - _ => CommandReturn::failure(ErrorCode::NOSUPPORT), - } - } - - fn allocate_grant(&self, _processid: ProcessId) -> Result<(), kernel::process::Error> { - Ok(()) - } -} // one Led from the matrix pub struct LedMatrixLed<'a, L: Pin, A: Alarm<'a>> { matrix: &'a LedMatrixDriver<'a, L, A>, @@ -312,7 +266,7 @@ impl<'a, L: Pin, A: Alarm<'a>> LedMatrixLed<'a, L, A> { if col >= matrix.cols_len() || row >= matrix.rows_len() { panic!("LED at position ({}, {}) does not exist", col, row); } - LedMatrixLed { matrix, col, row } + LedMatrixLed { matrix, row, col } } } @@ -332,9 +286,6 @@ impl<'a, L: Pin, A: Alarm<'a>> Led for LedMatrixLed<'a, L, A> { } fn read(&self) -> bool { - match self.matrix.read(self.col, self.row) { - Ok(v) => v, - Err(_) => false, - } + self.matrix.read(self.col, self.row).unwrap_or(false) } } diff --git a/capsules/src/lib.rs b/capsules/extra/src/lib.rs similarity index 62% rename from capsules/src/lib.rs rename to capsules/extra/src/lib.rs index a34f490d7c..8d169c959e 100644 --- a/capsules/src/lib.rs +++ b/capsules/extra/src/lib.rs @@ -1,48 +1,58 @@ -#![feature(const_fn_trait_bound)] +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + #![forbid(unsafe_code)] #![no_std] pub mod test; +pub mod tutorials; #[macro_use] pub mod net; -pub mod adc; pub mod adc_microphone; -pub mod alarm; +pub mod air_quality; pub mod ambient_light; pub mod analog_comparator; pub mod analog_sensor; pub mod apds9960; pub mod app_flash_driver; +pub mod at24c_eeprom; pub mod ble_advertising_driver; +pub mod bme280; +pub mod bmm150; +pub mod bmp280; pub mod bus; -pub mod button; pub mod buzzer_driver; -pub mod console; +pub mod buzzer_pwm; +pub mod can; +pub mod ccs811; pub mod crc; -pub mod ctap; +pub mod cycle_count; pub mod dac; +pub mod date_time; pub mod debug_process_restart; -pub mod driver; +pub mod eui64; pub mod fm25cl; pub mod ft6x06; pub mod fxos8700cq; -pub mod gpio; pub mod gpio_async; pub mod hd44780; pub mod hmac; +pub mod hmac_sha256; +pub mod hs3003; pub mod hts221; pub mod humidity; -pub mod i2c_master; -pub mod i2c_master_slave_driver; pub mod ieee802154; pub mod isl29035; +pub mod kv_driver; +pub mod kv_store_permissions; pub mod l3gd20; -pub mod led; pub mod led_matrix; pub mod log; -pub mod low_level_debug; +pub mod lpm013m126; +pub mod lps22hb; pub mod lps25hb; pub mod lsm303agr; pub mod lsm303dlhc; @@ -53,27 +63,34 @@ pub mod max17205; pub mod mcp230xx; pub mod mlx90614; pub mod mx25r6435f; +mod nina_w102; pub mod ninedof; pub mod nonvolatile_storage_driver; pub mod nonvolatile_to_pages; pub mod nrf51822_serialization; pub mod panic_button; pub mod pca9544a; -pub mod process_console; +pub mod pressure; pub mod proximity; +pub mod public_key_crypto; +pub mod pwm; pub mod read_only_state; pub mod rf233; pub mod rf233_const; -pub mod rng; pub mod screen; +pub mod screen_shared; pub mod sdcard; pub mod segger_rtt; +pub mod seven_segment; +pub mod sh1106; pub mod sha; +pub mod sha256; pub mod sht3x; +pub mod sht4x; pub mod si7021; +pub mod sip_hash; pub mod sound_pressure; -pub mod spi_controller; -pub mod spi_peripheral; +pub mod ssd1306; pub mod st77xx; pub mod symmetric_encryption; pub mod temperature; @@ -81,19 +98,10 @@ pub mod temperature_rp2040; pub mod temperature_stm; pub mod text_screen; pub mod tickv; +pub mod tickv_kv_store; pub mod touch; pub mod tsl2561; pub mod usb; -pub mod virtual_adc; -pub mod virtual_aes_ccm; -pub mod virtual_alarm; -pub mod virtual_digest; -pub mod virtual_flash; -pub mod virtual_hmac; -pub mod virtual_i2c; -pub mod virtual_pwm; -pub mod virtual_rng; -pub mod virtual_sha; -pub mod virtual_spi; -pub mod virtual_timer; -pub mod virtual_uart; +pub mod usb_hid_driver; +pub mod virtual_kv; +pub mod wifinina; diff --git a/capsules/src/log.rs b/capsules/extra/src/log.rs similarity index 88% rename from capsules/src/log.rs rename to capsules/extra/src/log.rs index 65e567b33f..42c09263e7 100644 --- a/capsules/src/log.rs +++ b/capsules/extra/src/log.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implements a log storage abstraction for storing persistent data in flash. //! //! Data entries can be appended to the end of a log and read back in-order. Logs may be linear @@ -7,7 +11,7 @@ //! //! Entries can be identified and seeked-to with their unique Entry IDs. Entry IDs maintain the //! ordering of the underlying entries, and an entry with a larger entry ID is newer and comes -//! after an entry with a smaller ID. IDs can also be used to determing the physical position of +//! after an entry with a smaller ID. IDs can also be used to determine the physical position of //! entries within the log's underlying storage volume - taking the ID modulo the size of the //! underlying storage volume yields the position of the entry's header relative to the start of //! the volume. Entries should not be created manually by clients, only retrieved through the @@ -38,42 +42,31 @@ //! Usage //! ----- //! -//! ``` +//! ```rust,ignore //! storage_volume!(VOLUME, 2); //! static mut PAGEBUFFER: sam4l::flashcalw::Sam4lPage = sam4l::flashcalw::Sam4lPage::new(); //! -//! let dynamic_deferred_call_clients = -//! static_init!([DynamicDeferredCallClientState; 2], Default::default()); -//! let dynamic_deferred_caller = static_init!( -//! DynamicDeferredCall, -//! DynamicDeferredCall::new(dynamic_deferred_call_clients) -//! ); -//! //! let log = static_init!( //! capsules::log::Log, //! capsules::log::Log::new( //! &VOLUME, //! &mut sam4l::flashcalw::FLASH_CONTROLLER, //! &mut PAGEBUFFER, -//! dynamic_deferred_caller, //! true //! ) //! ); +//! log.register(); //! kernel::hil::flash::HasClient::set_client(&sam4l::flashcalw::FLASH_CONTROLLER, log); -//! log.initialize_callback_handle(dynamic_deferred_caller.register(log).unwrap()); // Unwrap fail = no deferred call slot available for log storage //! //! log.set_read_client(log_storage_read_client); //! log.set_append_client(log_storage_append_client); //! ``` use core::cell::Cell; -use core::convert::TryFrom; use core::mem::size_of; use core::unreachable; -use kernel::dynamic_deferred_call::{ - DeferredCallHandle, DynamicDeferredCall, DynamicDeferredCallClient, -}; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; use kernel::hil::flash::{self, Flash}; use kernel::hil::log::{LogRead, LogReadClient, LogWrite, LogWriteClient}; use kernel::utilities::cells::{OptionalCell, TakeCell}; @@ -128,10 +121,8 @@ pub struct Log<'a, F: Flash + 'static> { /// Entry ID of next entry to append. append_entry_id: Cell, - /// Deferred caller for deferring client callbacks. - deferred_caller: &'a DynamicDeferredCall, - /// Handle for deferred caller. - handle: OptionalCell, + /// Deferred call for deferring client callbacks. + deferred_call: DeferredCall, // Note: for saving state across stack ripping. /// Client-provided buffer to write from. @@ -149,13 +140,12 @@ impl<'a, F: Flash + 'static> Log<'a, F> { volume: &'static [u8], driver: &'a F, pagebuffer: &'static mut F::Page, - deferred_caller: &'a DynamicDeferredCall, circular: bool, - ) -> Log<'a, F> { + ) -> Self { let page_size = pagebuffer.as_mut().len(); let capacity = volume.len() - PAGE_HEADER_SIZE * (volume.len() / page_size); - let log: Log<'a, F> = Log { + let log: Log<'a, F> = Self { volume, capacity, driver, @@ -168,8 +158,7 @@ impl<'a, F: Flash + 'static> Log<'a, F> { oldest_entry_id: Cell::new(PAGE_HEADER_SIZE), read_entry_id: Cell::new(PAGE_HEADER_SIZE), append_entry_id: Cell::new(PAGE_HEADER_SIZE), - deferred_caller, - handle: OptionalCell::empty(), + deferred_call: DeferredCall::new(), buffer: TakeCell::empty(), length: Cell::new(0), records_lost: Cell::new(false), @@ -228,7 +217,7 @@ impl<'a, F: Flash + 'static> Log<'a, F> { /// Reconstructs a log from flash. fn reconstruct(&self) { // Read page headers, get IDs of oldest and newest pages. - let mut oldest_page_id: EntryID = core::usize::MAX; + let mut oldest_page_id: EntryID = usize::MAX; let mut newest_page_id: EntryID = 0; for header_pos in (0..self.volume.len()).step_by(self.page_size) { let page_id = { @@ -251,7 +240,7 @@ impl<'a, F: Flash + 'static> Log<'a, F> { // Reconstruct log if at least one valid page was found (meaning oldest page ID was set to // something not usize::MAX). - if oldest_page_id != core::usize::MAX { + if oldest_page_id != usize::MAX { // Walk entries in last (newest) page to calculate last page length. let mut last_page_len = PAGE_HEADER_SIZE; loop { @@ -387,9 +376,7 @@ impl<'a, F: Flash + 'static> Log<'a, F> { // Copy data into client buffer. let data = self.get_bytes(entry_id, entry_length, pagebuffer); - for i in 0..entry_length { - buffer[i] = data[i]; - } + buffer[..entry_length].copy_from_slice(&data[..entry_length]); // Update read entry ID and return number of bytes read. self.read_entry_id.set(entry_id + entry_length); @@ -401,10 +388,8 @@ impl<'a, F: Flash + 'static> Log<'a, F> { /// Writes an entry header at the given position within a page. Must write at most /// ENTRY_HEADER_SIZE bytes. fn write_entry_header(&self, length: usize, pos: usize, pagebuffer: &mut F::Page) { - let mut offset = 0; - for byte in &length.to_ne_bytes() { + for (offset, byte) in length.to_ne_bytes().iter().enumerate() { pagebuffer.as_mut()[pos + offset] = *byte; - offset += 1; } } @@ -425,9 +410,7 @@ impl<'a, F: Flash + 'static> Log<'a, F> { page_offset += ENTRY_HEADER_SIZE; // Copy data to pagebuffer. - for offset in 0..length { - pagebuffer.as_mut()[page_offset + offset] = buffer[offset]; - } + pagebuffer.as_mut()[page_offset..(length + page_offset)].copy_from_slice(&buffer[..length]); // Increment append offset by number of bytes appended. let append_entry_id = append_entry_id + length + ENTRY_HEADER_SIZE; @@ -504,9 +487,7 @@ impl<'a, F: Flash + 'static> Log<'a, F> { // Write page header to pagebuffer. let id_bytes = append_entry_id.to_ne_bytes(); - for index in 0..id_bytes.len() { - pagebuffer.as_mut()[index] = id_bytes[index]; - } + pagebuffer.as_mut()[..id_bytes.len()].copy_from_slice(&id_bytes[..]); // Note: this is the only place where the append entry ID can cross page boundaries. self.append_entry_id.set(append_entry_id + PAGE_HEADER_SIZE); @@ -522,14 +503,9 @@ impl<'a, F: Flash + 'static> Log<'a, F> { .erase_page(self.page_number(self.oldest_entry_id.get())) } - /// Initializes a callback handle for deferred callbacks. - pub fn initialize_callback_handle(&self, handle: DeferredCallHandle) { - self.handle.replace(handle); - } - /// Defers a client callback until later. fn deferred_client_callback(&self) { - self.handle.map(|handle| self.deferred_caller.set(*handle)); + self.deferred_call.set(); } /// Resets the log state to idle and makes a client callback. The values returned by via the @@ -591,18 +567,19 @@ impl<'a, F: Flash + 'static> LogRead<'a> for Log<'a, F> { /// Read an entire log entry into a buffer, if there are any remaining. Updates the read entry /// ID to point at the next entry when done. /// Returns: - /// * Ok(()) on success. - /// * Err((Result<(), ErrorCode>, Option)) on failure. The buffer will only be `None` if the - /// error is due to a loss of the buffer. - /// Result<(), ErrorCode>s used: - /// * FAIL: reached end of log, nothing to read. - /// * BUSY: log busy with another operation, try again later. - /// * INVAL: provided client buffer is too small. - /// * CANCEL: invalid internal state, read entry ID was reset to start of log. - /// * RESERVE: client or internal pagebuffer missing. - /// * SIZE: buffer not large enough to contain entry being read. - /// Result<(), ErrorCode>s used in read_done callback: - /// * Ok(()): read succeeded. + /// * `Ok(())` on success. + /// * `Err((Result<(), ErrorCode>, Option))` on failure. The + /// buffer will only be `None` if the error is due to a loss of the + /// buffer. + /// `Result<(), ErrorCode>`s used: + /// * `FAIL`: reached end of log, nothing to read. + /// * `BUSY`: log busy with another operation, try again later. + /// * `INVAL`: provided client buffer is too small. + /// * `CANCEL`: invalid internal state, read entry ID was reset to start of log. + /// * `RESERVE`: client or internal pagebuffer missing. + /// * `SIZE`: buffer not large enough to contain entry being read. + /// `Result<(), ErrorCode>`s used in read_done callback: + /// * `Ok(())`: read succeeded. fn read( &self, buffer: &'static mut [u8], @@ -687,19 +664,19 @@ impl<'a, F: Flash + 'static> LogWrite<'a> for Log<'a, F> { /// Appends an entry onto the end of the log. Entry must fit within a page (including log /// metadata). /// Returns: - /// * Ok(()) on success. - /// * Err((Result<(), ErrorCode>, Option)) on failure. The buffer will only be `None` if the + /// * `Ok(())` on success. + /// * `Err((Result<(), ErrorCode>, Option))1 on failure. The buffer will only be `None` if the /// error is due to a loss of the buffer. - /// Result<(), ErrorCode>s used: - /// * FAIL: end of non-circular log reached, cannot append any more entries. - /// * BUSY: log busy with another operation, try again later. - /// * INVAL: provided client buffer is too small. - /// * RESERVE: client or internal pagebuffer missing. - /// * SIZE: entry too large to append to log. - /// Result<(), ErrorCode>s used in append_done callback: - /// * Ok(()): append succeeded. - /// * FAIL: write failed due to flash error. - /// * CANCEL: write failed due to reaching the end of a non-circular log. + /// `Result<(), ErrorCode>`s used: + /// * `FAIL`: end of non-circular log reached, cannot append any more entries. + /// * `BUSY`: log busy with another operation, try again later. + /// * `INVAL`: provided client buffer is too small. + /// * `RESERVE`: client or internal pagebuffer missing. + /// * `SIZE`: entry too large to append to log. + /// `Result<(), ErrorCode>`s used in append_done callback: + /// * `Ok(())`: append succeeded. + /// * `FAIL`: write failed due to flash error. + /// * `CANCEL`: write failed due to reaching the end of a non-circular log. fn append( &self, buffer: &'static mut [u8], @@ -805,16 +782,16 @@ impl<'a, F: Flash + 'static> LogWrite<'a> for Log<'a, F> { } impl<'a, F: Flash + 'static> flash::Client for Log<'a, F> { - fn read_complete(&self, _read_buffer: &'static mut F::Page, _error: flash::Error) { + fn read_complete(&self, _read_buffer: &'static mut F::Page, _result: Result<(), flash::Error>) { // Reads are made directly from the storage volume, not through the flash interface. unreachable!(); } /// If in the middle of a write operation, reset pagebuffer and finish write. If syncing, make /// successful client callback. - fn write_complete(&self, pagebuffer: &'static mut F::Page, error: flash::Error) { - match error { - flash::Error::CommandComplete => { + fn write_complete(&self, pagebuffer: &'static mut F::Page, result: Result<(), flash::Error>) { + match result.is_ok() { + true => { match self.state.get() { State::Append => { // Reset pagebuffer and finish writing on the new page. @@ -846,21 +823,25 @@ impl<'a, F: Flash + 'static> flash::Client for Log<'a, F> { _ => unreachable!(), } } - flash::Error::FlashError => { - // Make client callback with FAIL return code. - self.pagebuffer.replace(pagebuffer); - match self.state.get() { - State::Append => { - self.length.set(0); - self.records_lost.set(false); - self.error.set(Err(ErrorCode::FAIL)); - self.client_callback(); - } - State::Sync => { - self.error.set(Err(ErrorCode::FAIL)); - self.client_callback(); + false => { + match result.unwrap_err() { + flash::Error::FlashError | flash::Error::FlashMemoryProtectionError => { + // Make client callback with FAIL return code. + self.pagebuffer.replace(pagebuffer); + match self.state.get() { + State::Append => { + self.length.set(0); + self.records_lost.set(false); + self.error.set(Err(ErrorCode::FAIL)); + self.client_callback(); + } + State::Sync => { + self.error.set(Err(ErrorCode::FAIL)); + self.client_callback(); + } + _ => unreachable!(), + } } - _ => unreachable!(), } } } @@ -868,9 +849,9 @@ impl<'a, F: Flash + 'static> flash::Client for Log<'a, F> { /// Erase next page if log erase complete, else make client callback. Fails with BUSY if flash /// is busy and erase cannot be completed. - fn erase_complete(&self, error: flash::Error) { - match error { - flash::Error::CommandComplete => { + fn erase_complete(&self, result: Result<(), flash::Error>) { + match result.is_ok() { + true => { let oldest_entry_id = self.oldest_entry_id.get(); if oldest_entry_id >= self.append_entry_id.get() - self.page_size { // Erased all pages. Reset state and callback client. @@ -894,16 +875,22 @@ impl<'a, F: Flash + 'static> flash::Client for Log<'a, F> { } } } - flash::Error::FlashError => { - self.error.set(Err(ErrorCode::FAIL)); - self.client_callback(); - } + false => match result.unwrap_err() { + flash::Error::FlashError | flash::Error::FlashMemoryProtectionError => { + self.error.set(Err(ErrorCode::FAIL)); + self.client_callback(); + } + }, } } } -impl<'a, F: Flash + 'static> DynamicDeferredCallClient for Log<'a, F> { - fn call(&self, _handle: DeferredCallHandle) { +impl<'a, F: Flash + 'static> DeferredCallClient for Log<'a, F> { + fn handle_deferred_call(&self) { self.client_callback(); } + + fn register(&'static self) { + self.deferred_call.register(self); + } } diff --git a/capsules/extra/src/lpm013m126.rs b/capsules/extra/src/lpm013m126.rs new file mode 100644 index 0000000000..b7a4ba03c3 --- /dev/null +++ b/capsules/extra/src/lpm013m126.rs @@ -0,0 +1,682 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Frame buffer driver for the Japan Display LPM013M126 display +//! +//! Used in Bangle.js 2 and [Jazda](https://jazda.org). +//! The driver is configured for the above devices: +//! EXTCOM inversion is driven with EXTCOMIN. +//! +//! This driver supports monochrome mode only. +//! +//! Written by Dorota + +use core::cell::Cell; +use core::cmp; +use kernel::debug; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; +use kernel::hil::gpio::Pin; +use kernel::hil::screen::{Screen, ScreenClient, ScreenPixelFormat, ScreenRotation}; +use kernel::hil::spi::{SpiMasterClient, SpiMasterDevice}; +use kernel::hil::time::{Alarm, AlarmClient, ConvertTicks}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::utilities::leasable_buffer::SubSliceMut; +use kernel::ErrorCode; + +/// Monochrome frame buffer bytes. +/// 176 × 176 bits = 3872 bytes. +/// +/// 2 bytes for the start of each row (command header), +/// plus 2 bytes of data transfer period at the end +/// +/// 176 * 2 + 2 = 354 bytes. +pub const BUF_LEN: usize = 3872 + 354; + +/// Best Tock can do, sadly. +/// Would be better to have it offset. +type SubmitBuffer<'a> = &'a mut [u8]; + +/// Arranges frame data in a buffer +/// whose portions can be sent directly to the device. +struct FrameBuffer<'a> { + data: &'a mut [u8], +} + +impl<'a> FrameBuffer<'a> { + /// Turns a regular buffer (back) into a FrameBuffer. + /// If the buffer is fresh, and the display is initialized, + /// this *MUST* be initialized after the call to `new`. + fn new(frame_buffer: &'a mut [u8]) -> Self { + Self { data: frame_buffer } + } + + /// Initialize header bytes for each line. + fn initialize(&mut self) { + for i in 0..176 { + let line = self.get_line_mut(i); + let bytes = CommandHeader { + mode: Mode::Input1Bit, + gate_line: i, + } + .encode(); + line[..2].copy_from_slice(&bytes); + } + } + + /// Copy pixels from the buffer. The buffer may be shorter than frame. + fn blit(&mut self, buffer: &[u8], frame: &WriteFrame) { + if frame.column % 8 != 0 { + // Can't be arsed to bit shift pixels… + panic!("Horizontal offset not supported"); + } + let rows = (frame.row)..(frame.row + frame.height); + // There are 8 pixels in each row per byte. + let sources = buffer.chunks(frame.width as usize / 8); + for (i, source) in rows.zip(sources) { + let row = self.get_row_mut(i); + row[(frame.column as usize / 8)..][..(source.len())].copy_from_slice(source); + } + } + + /// Gets an entire raw line, ready to send. + fn get_line_mut(&mut self, index: u16) -> &mut [u8] { + const CMD: usize = 2; + const TRANSFER_PERIOD: usize = 2; + let line_bytes = CMD + 176 / 8; + &mut self.data[(line_bytes * index as usize)..][..line_bytes + TRANSFER_PERIOD] + } + + /// Gets pixel data. + fn get_row_mut(&mut self, index: u16) -> &mut [u8] { + let line_bytes = 176 / 8 + 2; + &mut self.data[(line_bytes * index as usize + 2)..][..(176 / 8)] + } + + /// Transform into a view of raw data for submitting to the DMA driver + fn with_raw_rows( + frame_buffer: FrameBuffer<'static>, + _start: u16, + _end: u16, + ) -> SubmitBuffer<'static> { + // HILs typically can't use offsets :/ + // Best we can do is limit length (TODO) + frame_buffer.data + } +} + +/// Modes are 6-bit, network order. +/// They use a tree-ish encoding, so only the ones in use are listed here. +#[allow(dead_code)] +#[derive(Clone, Copy)] +enum Mode { + /// Clear memory + /// bits: 0 Function, X, 1 Clear, 0 Blink off, X, X + AllClear = 0b001000, + /// Input 1-bit data + /// bits: 1 No function, X, 0 Data Update, 01 1-bit, X + Input1Bit = 0b100_01_0, +} + +/// Command header is composed of a 6-bit mode and 10 bits of address, +/// network bit order. +struct CommandHeader { + mode: Mode, + gate_line: u16, +} + +impl CommandHeader { + /// Formats header for transfer + fn encode(&self) -> [u8; 2] { + (self.gate_line | ((self.mode as u16) << 10)).to_be_bytes() + } +} + +/// Area of the screen to which data is written +#[derive(Debug, Copy, Clone)] +struct WriteFrame { + row: u16, + column: u16, + width: u16, + height: u16, +} + +/// Internal state of the driver. +/// Each state can lead to the next one in order of appearance. +#[derive(Debug, Copy, Clone)] +enum State { + /// Data structures not ready, call `setup` + Uninitialized, + + /// Display hardware is off, uninitialized. + Off, + InitializingPixelMemory, + /// COM polarity and internal latch circuits + InitializingRest, + + // Normal operation + Idle(WriteFrame), + Writing(WriteFrame), + + /// This driver is buggy. Turning off and on will try to recover it. + Bug, +} + +#[derive(Debug)] +pub enum InitError { + BufferTooSmall, +} + +pub struct Lpm013m126<'a, A: Alarm<'a>, P: Pin, S: SpiMasterDevice<'a>> { + spi: &'a S, + extcomin: &'a P, + disp: &'a P, + + state: Cell, + + /// Fields responsible for sending callbacks + /// for actions completed in software. + ready_callback: DeferredCall, + ready_callback_handler: ReadyCallbackHandler<'a, A, P, S>, + command_complete_callback: DeferredCall, + command_complete_callback_handler: CommandCompleteCallbackHandler<'a, A, P, S>, + write_complete_callback: DeferredCall, + write_complete_callback_handler: WriteCompleteCallbackHandler<'a, A, P, S>, + /// Holds the pending call parameter + write_complete_pending_call: OptionalCell>, + + /// The HIL requires updates to arbitrary rectangles. + /// The display supports only updating entire rows, + /// so edges need to be cached. + frame_buffer: OptionalCell>, + + client: OptionalCell<&'a dyn ScreenClient>, + /// Buffer for incoming pixel data, coming from the client. + /// It's not submitted directly anywhere. + buffer: TakeCell<'static, [u8]>, + + /// Needed for init and to flip the EXTCOMIN pin at regular intervals + alarm: &'a A, +} + +impl<'a, A: Alarm<'a>, P: Pin, S: SpiMasterDevice<'a>> Lpm013m126<'a, A, P, S> +where + Self: 'static, +{ + pub fn new( + spi: &'a S, + extcomin: &'a P, + disp: &'a P, + alarm: &'a A, + frame_buffer: &'static mut [u8], + ) -> Result { + if frame_buffer.len() < BUF_LEN { + Err(InitError::BufferTooSmall) + } else { + Ok(Self { + spi, + alarm, + disp, + extcomin, + ready_callback: DeferredCall::new(), + ready_callback_handler: ReadyCallbackHandler::new(), + command_complete_callback: DeferredCall::new(), + command_complete_callback_handler: CommandCompleteCallbackHandler::new(), + write_complete_callback: DeferredCall::new(), + write_complete_callback_handler: WriteCompleteCallbackHandler::new(), + write_complete_pending_call: OptionalCell::empty(), + frame_buffer: OptionalCell::new(FrameBuffer::new(frame_buffer)), + buffer: TakeCell::empty(), + client: OptionalCell::empty(), + state: Cell::new(State::Uninitialized), + }) + } + } + + /// Set up internal data structures. + /// Does not touch the hardware. + /// Idempotent. + pub fn setup(&'static self) -> Result<(), ErrorCode> { + // Needed this way to avoid exposing accessors to deferred callers. + // That would be unnecessary, no external data is needed. + // At the same time, self must be static for client registration. + match self.state.get() { + State::Uninitialized => { + self.ready_callback_handler.lpm.set(self); + self.ready_callback.register(&self.ready_callback_handler); + self.command_complete_callback_handler.lpm.set(self); + self.command_complete_callback + .register(&self.command_complete_callback_handler); + self.write_complete_callback_handler.lpm.set(self); + self.write_complete_callback + .register(&self.write_complete_callback_handler); + + self.state.set(State::Off); + Ok(()) + } + _ => Err(ErrorCode::ALREADY), + } + } + + fn initialize(&self) -> Result<(), ErrorCode> { + match self.state.get() { + State::Off | State::Bug => { + // Even if we took Pin type that implements Output, + // it's still possible that it is *not configured as a output* + // at the moment. + // To ensure outputness, output must be configured at runtime, + // even though this eliminates pins + // which don't implement Configure due to being + // simple, unconfigurable outputs. + self.extcomin.make_output(); + self.extcomin.clear(); + self.disp.make_output(); + self.disp.clear(); + + match self.frame_buffer.take() { + None => Err(ErrorCode::NOMEM), + Some(mut frame_buffer) => { + // Cheating a little: + // the frame buffer does not yet contain pixels, + // so use its beginning to send the clear command. + let buf = &mut frame_buffer.get_line_mut(0)[..2]; + buf.copy_from_slice( + &CommandHeader { + mode: Mode::AllClear, + gate_line: 0, + } + .encode(), + ); + let l = FrameBuffer::with_raw_rows(frame_buffer, 0, 1); + let res = self.spi.read_write_bytes( + l, //FrameBuffer::with_raw_rows(frame_buffer, 0, 1), + None, 2, + ); + + let (res, new_state) = match res { + Ok(()) => (Ok(()), State::InitializingPixelMemory), + Err((e, buf, _)) => { + self.frame_buffer.replace(FrameBuffer::new(buf)); + (Err(e), State::Bug) + } + }; + self.state.set(new_state); + res + } + } + } + _ => Err(ErrorCode::ALREADY), + } + } + + fn uninitialize(&self) -> Result<(), ErrorCode> { + match self.state.get() { + State::Off => Err(ErrorCode::ALREADY), + _ => { + // TODO: investigate clearing pixels asynchronously, + // like the datasheet asks. + // It seems to turn off fine without clearing, but + // perhaps the state of the buffer affects power draw when off. + + // The following stops extcomin timer. + self.alarm.disarm()?; + self.disp.clear(); + self.state.set(State::Off); + + self.ready_callback.set(); + Ok(()) + } + } + } + + fn call_write_complete(&self, ret: Result<(), ErrorCode>) { + self.write_complete_callback.set(); + self.write_complete_pending_call.set(ret); + } + + fn arm_alarm(&self) { + // Datasheet says 2Hz or more often flipping is required + // for transmissive mode. + let delay = self.alarm.ticks_from_ms(500); + self.alarm.set_alarm(self.alarm.now(), delay); + } + + fn handle_ready_callback(&self) { + self.client.map(|client| client.screen_is_ready()); + } + + fn handle_write_complete_callback(&self) { + self.client.map(|client| { + self.write_complete_pending_call.map(|pend| { + self.buffer.take().map(|buffer| { + let data = SubSliceMut::new(buffer); + client.write_complete(data, pend) + }); + }); + self.write_complete_pending_call.take(); + }); + } + + fn handle_command_complete_callback(&self) { + // Thankfully, this is the only command that results in the callback, + // so there's no danger that this will get attributed + // to a command that's not finished yet. + self.client.map(|client| client.command_complete(Ok(()))); + } +} + +impl<'a, A: Alarm<'a>, P: Pin, S: SpiMasterDevice<'a>> Screen<'a> for Lpm013m126<'a, A, P, S> +where + Self: 'static, +{ + fn get_resolution(&self) -> (usize, usize) { + (176, 176) + } + + fn get_pixel_format(&self) -> ScreenPixelFormat { + ScreenPixelFormat::Mono + } + + fn get_rotation(&self) -> ScreenRotation { + ScreenRotation::Normal + } + + fn set_write_frame( + &self, + x: usize, + y: usize, + width: usize, + height: usize, + ) -> Result<(), ErrorCode> { + let (columns, rows) = self.get_resolution(); + if y >= rows || y + height > rows || x >= columns || x + width > columns { + return Err(ErrorCode::INVAL); + } + + let frame = WriteFrame { + row: y as u16, + column: x as u16, + width: width as u16, + height: height as u16, + }; + + let mut new_state = None; + let ret = match self.state.get() { + State::Uninitialized | State::Off => Err(ErrorCode::OFF), + State::InitializingPixelMemory | State::InitializingRest => Err(ErrorCode::BUSY), + State::Idle(..) => { + new_state = Some(State::Idle(frame)); + Ok(()) + } + State::Writing(..) => { + new_state = Some(State::Writing(frame)); + Ok(()) + } + State::Bug => Err(ErrorCode::FAIL), + }; + + self.command_complete_callback.set(); + + if let Some(new_state) = new_state { + self.state.set(new_state); + } + + ret + } + + fn write( + &self, + data: SubSliceMut<'static, u8>, + _continue_write: bool, + ) -> Result<(), ErrorCode> { + let len = data.len(); + let buffer = data.take(); + + let ret = match self.state.get() { + State::Uninitialized | State::Off => Err(ErrorCode::OFF), + State::InitializingPixelMemory | State::InitializingRest => Err(ErrorCode::BUSY), + State::Idle(frame) => { + self.frame_buffer + .take() + .map_or(Err(ErrorCode::NOMEM), |mut frame_buffer| { + // TODO: reject if buffer is shorter than frame + frame_buffer.blit(&buffer[..cmp::min(buffer.len(), len)], &frame); + let send_buf = FrameBuffer::with_raw_rows( + frame_buffer, + frame.row, + frame.row + frame.height, + ); + + let sent = self.spi.read_write_bytes(send_buf, None, send_buf.len()); + let (ret, new_state) = match sent { + Ok(()) => (Ok(()), State::Writing(frame)), + Err((e, buf, _)) => { + self.frame_buffer.replace(FrameBuffer::new(buf)); + (Err(e), State::Idle(frame)) + } + }; + self.state.set(new_state); + ret + }) + } + State::Writing(..) => Err(ErrorCode::BUSY), + State::Bug => Err(ErrorCode::FAIL), + }; + + self.buffer.replace(buffer); + + match self.state.get() { + State::Writing(..) => {} + _ => self.call_write_complete(ret), + }; + + ret + } + + fn set_client(&self, client: &'a dyn ScreenClient) { + self.client.set(client); + } + + fn set_power(&self, enable: bool) -> Result<(), ErrorCode> { + let ret = if enable { + self.initialize() + } else { + self.uninitialize() + }; + + // If the device is in the desired state by now, + // then a callback needs to be sent manually. + if let Err(ErrorCode::ALREADY) = ret { + self.ready_callback.set(); + Ok(()) + } else { + ret + } + } + + fn set_brightness(&self, _brightness: u16) -> Result<(), ErrorCode> { + // TODO: add LED PWM + Err(ErrorCode::NOSUPPORT) + } + + fn set_invert(&self, _inverted: bool) -> Result<(), ErrorCode> { + Err(ErrorCode::NOSUPPORT) + } +} + +impl<'a, A: Alarm<'a>, P: Pin, S: SpiMasterDevice<'a>> AlarmClient for Lpm013m126<'a, A, P, S> +where + Self: 'static, +{ + fn alarm(&self) { + match self.state.get() { + State::InitializingRest => { + // Better flip it once too many than go out of spec + // by stretching the flip period. + self.extcomin.toggle(); + self.arm_alarm(); + let new_state = self.frame_buffer.take().map_or_else( + || { + debug!( + "LPM013M126 driver lost its frame buffer in state {:?}", + self.state.get() + ); + State::Bug + }, + |mut buffer| { + buffer.initialize(); + self.frame_buffer.replace(buffer); + State::Idle( + // The HIL doesn't specify the initial frame + WriteFrame { + row: 0, + column: 0, + width: 176, + height: 176, + }, + ) + }, + ); + + self.state.set(new_state); + + if let State::Idle(..) = new_state { + self.client.map(|client| client.screen_is_ready()); + } + } + State::Idle(..) | State::Writing(..) => { + self.extcomin.toggle(); + self.arm_alarm(); + } + other => { + debug!( + "LPM013M126 driver got alarm in unexpected state {:?}", + other + ); + self.state.set(State::Bug); + } + }; + } +} + +impl<'a, A: Alarm<'a>, P: Pin, S: SpiMasterDevice<'a>> SpiMasterClient for Lpm013m126<'a, A, P, S> { + fn read_write_done( + &self, + write_buffer: SubmitBuffer<'static>, + _read_buffer: Option<&'static mut [u8]>, + _len: usize, + status: Result<(), ErrorCode>, + ) { + self.frame_buffer.replace(FrameBuffer::new(write_buffer)); + self.state.set(match self.state.get() { + State::InitializingPixelMemory => { + // Rather than initialize them separately, wait longer and do both + // for 2 reasons: + // 1. the upper limit of waiting is only specified for both, + // 2. and state flipping code is annoying and bug-friendly. + self.disp.set(); + self.extcomin.set(); + let delay = self.alarm.ticks_from_us(200); + self.alarm.set_alarm(self.alarm.now(), delay); + State::InitializingRest + } + State::Writing(frame) => State::Idle(frame), + // can't get more buggy than buggy + other => { + debug!( + "LPM013M126 received unexpected SPI complete in state {:?}", + other + ); + State::Bug + } + }); + + // Device frame buffer is now up to date, return pixel buffer to client. + self.client.map(|client| { + self.buffer.take().map(|buf| { + let data = SubSliceMut::new(buf); + client.write_complete(data, status) + }) + }); + } +} + +// DeferredCall requires a unique client for each DeferredCall so that different callbacks +// can be distinguished. +struct ReadyCallbackHandler<'a, A: Alarm<'a>, P: Pin, S: SpiMasterDevice<'a>> { + lpm: OptionalCell<&'a Lpm013m126<'a, A, P, S>>, +} + +impl<'a, A: Alarm<'a>, P: Pin, S: SpiMasterDevice<'a>> ReadyCallbackHandler<'a, A, P, S> { + fn new() -> Self { + Self { + lpm: OptionalCell::empty(), + } + } +} + +impl<'a, A: Alarm<'a>, P: Pin, S: SpiMasterDevice<'a>> DeferredCallClient + for ReadyCallbackHandler<'a, A, P, S> +where + Self: 'static, +{ + fn handle_deferred_call(&self) { + self.lpm.map(|l| l.handle_ready_callback()); + } + + fn register(&'static self) { + self.lpm.map(|l| l.ready_callback.register(self)); + } +} + +struct CommandCompleteCallbackHandler<'a, A: Alarm<'a>, P: Pin, S: SpiMasterDevice<'a>> { + lpm: OptionalCell<&'a Lpm013m126<'a, A, P, S>>, +} + +impl<'a, A: Alarm<'a>, P: Pin, S: SpiMasterDevice<'a>> CommandCompleteCallbackHandler<'a, A, P, S> { + fn new() -> Self { + Self { + lpm: OptionalCell::empty(), + } + } +} + +impl<'a, A: Alarm<'a>, P: Pin, S: SpiMasterDevice<'a>> DeferredCallClient + for CommandCompleteCallbackHandler<'a, A, P, S> +where + Self: 'static, +{ + fn handle_deferred_call(&self) { + self.lpm.map(|l| l.handle_command_complete_callback()); + } + + fn register(&'static self) { + self.lpm.map(|l| l.command_complete_callback.register(self)); + } +} + +struct WriteCompleteCallbackHandler<'a, A: Alarm<'a>, P: Pin, S: SpiMasterDevice<'a>> { + lpm: OptionalCell<&'a Lpm013m126<'a, A, P, S>>, +} + +impl<'a, A: Alarm<'a>, P: Pin, S: SpiMasterDevice<'a>> WriteCompleteCallbackHandler<'a, A, P, S> { + fn new() -> Self { + Self { + lpm: OptionalCell::empty(), + } + } +} + +impl<'a, A: Alarm<'a>, P: Pin, S: SpiMasterDevice<'a>> DeferredCallClient + for WriteCompleteCallbackHandler<'a, A, P, S> +where + Self: 'static, +{ + fn handle_deferred_call(&self) { + self.lpm.map(|l| l.handle_write_complete_callback()); + } + + fn register(&'static self) { + self.lpm.map(|l| l.write_complete_callback.register(self)); + } +} diff --git a/capsules/extra/src/lps22hb.rs b/capsules/extra/src/lps22hb.rs new file mode 100644 index 0000000000..2074aa5782 --- /dev/null +++ b/capsules/extra/src/lps22hb.rs @@ -0,0 +1,268 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Sensor Driver for the LPS22HB MEMS nano pressure sensor +//! using the I2C bus. +//! +//! +//! +//! > The LPS22HB is an ultra-compact piezoresistive absolute +//! > pressure sensor which functions as a digital output barometer. +//! > The device comprises a sensing element and an IC interface +//! > which communicates through I2C or SPI from the sensing element +//! > to the application. +//! +//! Driver Semantics +//! ---------------- +//! +//! This driver exposes the LPS22HB's pressure functionality via +//! the [PressureDriver] HIL interface. It doesn't support handling +//! multiple concurrent pressure requests. +//! +//! Usage +//! ----- +//! +//! ```rust,ignore +//! # use kernel::static_init; +//! +//! let lps22hb_i2c = static_init!( +//! capsules::virtual_i2c::I2CDevice, +//! capsules::virtual_i2c::I2CDevice::new(i2c_bus, 0x5C)); +//! let lps22hb = static_init!( +//! capsules::lps22hb::Lps22hb<'static>, +//! capsules::lps22hb::Lps22hb::new(lps22hb_i2c, +//! &mut capsules::lps22hb::BUFFER)); +//! lps22hb_i2c.set_client(lps22hb); +//! ``` + +use core::cell::Cell; +use kernel::hil::i2c::{self, I2CClient, I2CDevice}; +use kernel::hil::sensors::{PressureClient, PressureDriver}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::ErrorCode; + +/// Syscall driver number. +use capsules_core::driver; +pub const DRIVER_NUM: usize = driver::NUM::Pressure as usize; + +/// Register values + +const REGISTER_AUTO_INCREMENT: u8 = 0x80; +const CTRL_REG1_ONE_SHOT: u8 = 0x00; + +#[allow(dead_code)] +enum Registers { + IntCfgReg = 0x0B, + ThsPL = 0x0C, + ThsPH = 0x0D, + WhoAmI = 0x0F, + CtrlReg1 = 0x10, + CtrlReg2 = 0x11, + CtrlReg3 = 0x12, + FifoCtrl = 0x14, + RefPXl = 0x15, + RefPL = 0x16, + RefPH = 0x17, + RpdsL = 0x18, + RpdsH = 0x19, + ResConf = 0x1A, + IntSourceReg = 0x25, + FifoStatus = 0x26, + StatusReg = 0x27, + PressOutXl = 0x28, + PressOutL = 0x29, + PressOutH = 0x2A, + TempOutL = 0x2B, + TempOutH = 0x2C, + LpfpRes = 0x33, +} + +pub struct Lps22hb<'a, I: I2CDevice> { + buffer: TakeCell<'static, [u8]>, + i2c_bus: &'a I, + pressure_client: OptionalCell<&'a dyn PressureClient>, + pending_pressure: Cell, + state: Cell, +} + +impl<'a, I: I2CDevice> Lps22hb<'a, I> { + pub fn new(i2c_bus: &'a I, buffer: &'static mut [u8]) -> Lps22hb<'a, I> { + Lps22hb { + buffer: TakeCell::new(buffer), + i2c_bus, + pressure_client: OptionalCell::empty(), + pending_pressure: Cell::new(false), + state: Cell::new(State::Sleep), + } + } + + fn start_measurement(&self) -> Result<(), ErrorCode> { + self.buffer + .take() + .map(|buffer| { + self.i2c_bus.enable(); + match self.state.get() { + State::Sleep => { + buffer[0] = Registers::WhoAmI as u8; + + if let Err((_error, buffer)) = self.i2c_bus.write_read(buffer, 1, 1) { + self.buffer.replace(buffer); + self.i2c_bus.disable(); + } else { + self.state.set(State::PowerOn); + } + } + State::Idle => { + buffer[0] = Registers::CtrlReg2 as u8; + buffer[1] = 0x11_u8; + + if let Err((_error, buffer)) = self.i2c_bus.write(buffer, 2) { + self.buffer.replace(buffer); + self.i2c_bus.disable(); + } else { + self.state.set(State::Status); + } + } + _ => {} + } + }) + .ok_or(ErrorCode::FAIL) + } +} + +impl<'a, I: I2CDevice> PressureDriver<'a> for Lps22hb<'a, I> { + fn set_client(&self, client: &'a dyn PressureClient) { + self.pressure_client.set(client); + } + + fn read_atmospheric_pressure(&self) -> Result<(), ErrorCode> { + if !self.pending_pressure.get() { + self.pending_pressure.set(true); + self.start_measurement() + } else { + Err(ErrorCode::BUSY) + } + } +} + +#[derive(Clone, Copy, PartialEq)] +pub enum State { + Sleep, + PowerOn, + Idle, + ConfOut, + Status, + ReadMeasurementInit, + ReadMeasurement, + GotMeasurement, +} + +impl<'a, I: I2CDevice> I2CClient for Lps22hb<'a, I> { + fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) { + if let Err(i2c_err) = status { + self.state.set(State::Idle); + self.buffer.replace(buffer); + self.pressure_client + .map(|client| client.callback(Err(i2c_err.into()))); + return; + } + + match self.state.get() { + State::PowerOn => { + if buffer[0] == 0xB1 { + buffer[0] = Registers::CtrlReg1 as u8; + buffer[1] = CTRL_REG1_ONE_SHOT; + + if let Err((i2c_err, buffer)) = self.i2c_bus.write(buffer, 2) { + self.state.set(State::Idle); + self.buffer.replace(buffer); + self.pressure_client + .map(|client| client.callback(Err(i2c_err.into()))); + } else { + self.state.set(State::ConfOut); + } + } else { + self.state.set(State::Sleep); + } + } + State::ConfOut => { + buffer[0] = Registers::CtrlReg2 as u8; + buffer[1] = 0x11_u8; + + if let Err((i2c_err, buffer)) = self.i2c_bus.write(buffer, 2) { + self.state.set(State::Idle); + self.buffer.replace(buffer); + self.pressure_client + .map(|client| client.callback(Err(i2c_err.into()))); + } else { + self.state.set(State::Status); + } + } + State::Status => { + buffer[0] = Registers::CtrlReg2 as u8; + + if let Err((i2c_err, buffer)) = self.i2c_bus.write_read(buffer, 1, 1) { + self.state.set(State::Idle); + self.buffer.replace(buffer); + self.pressure_client + .map(|client| client.callback(Err(i2c_err.into()))); + } else { + self.state.set(State::ReadMeasurementInit); + } + } + State::ReadMeasurementInit => { + if buffer[0] == 0x10 { + buffer[0] = Registers::PressOutXl as u8 | REGISTER_AUTO_INCREMENT; + + if let Err((i2c_err, buffer)) = self.i2c_bus.write(buffer, 1) { + self.state.set(State::Idle); + self.buffer.replace(buffer); + self.pressure_client + .map(|client| client.callback(Err(i2c_err.into()))); + } else { + self.state.set(State::ReadMeasurement); + } + } else { + buffer[0] = Registers::CtrlReg2 as u8; + + if let Err((i2c_err, buffer)) = self.i2c_bus.write_read(buffer, 1, 1) { + self.state.set(State::Idle); + self.buffer.replace(buffer); + self.pressure_client + .map(|client| client.callback(Err(i2c_err.into()))); + } else { + self.state.set(State::ReadMeasurementInit); + } + } + } + State::ReadMeasurement => { + if let Err((i2c_err, buffer)) = self.i2c_bus.read(buffer, 3) { + self.state.set(State::Idle); + self.buffer.replace(buffer); + self.pressure_client + .map(|client| client.callback(Err(i2c_err.into()))); + } else { + self.state.set(State::GotMeasurement); + } + } + State::GotMeasurement => { + let pressure = + (((buffer[2] as u32) << 16) | ((buffer[1] as u32) << 8) | (buffer[0] as u32)) + / 4096; + + self.buffer.replace(buffer); + self.i2c_bus.disable(); + if self.pending_pressure.get() { + self.pending_pressure.set(false); + self.pressure_client + .map(|client| client.callback(Ok(pressure))); + } + + self.state.set(State::Idle); + } + State::Sleep => {} + State::Idle => {} + } + } +} diff --git a/capsules/src/lps25hb.rs b/capsules/extra/src/lps25hb.rs similarity index 88% rename from capsules/src/lps25hb.rs rename to capsules/extra/src/lps25hb.rs index 9b934a0422..b3ed76f610 100644 --- a/capsules/src/lps25hb.rs +++ b/capsules/extra/src/lps25hb.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for the ST LPS25HB pressure sensor. //! //! @@ -5,15 +9,14 @@ //! Usage //! ----- //! -//! ```rust -//! # use kernel::static_init; -//! +//! ```rust,ignore +//! let buffer = static_init!([u8; capsules::lps25hb::BUF_LEN], [0; capsules::lps25hb::BUF_LEN]); //! let lps25hb_i2c = static_init!(I2CDevice, I2CDevice::new(i2c_bus, 0x5C)); //! let lps25hb = static_init!( //! capsules::lps25hb::LPS25HB<'static>, //! capsules::lps25hb::LPS25HB::new(lps25hb_i2c, //! &sam4l::gpio::PA[10], -//! &mut capsules::lps25hb::BUFFER)); +//! buffer)); //! lps25hb_i2c.set_client(lps25hb); //! sam4l::gpio::PA[10].set_client(lps25hb); //! ``` @@ -29,11 +32,11 @@ use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::Lps25hb as usize; -// Buffer to use for I2C messages -pub static mut BUFFER: [u8; 5] = [0; 5]; +// Expected buffer length. +pub const BUF_LEN: usize = 5; /// Register values const REGISTER_AUTO_INCREMENT: u8 = 0x80; @@ -100,8 +103,8 @@ enum State { #[derive(Default)] pub struct App {} -pub struct LPS25HB<'a> { - i2c: &'a dyn i2c::I2CDevice, +pub struct LPS25HB<'a, I: i2c::I2CDevice> { + i2c: &'a I, interrupt_pin: &'a dyn gpio::InterruptPin<'a>, state: Cell, buffer: TakeCell<'static, [u8]>, @@ -109,17 +112,17 @@ pub struct LPS25HB<'a> { owning_process: OptionalCell, } -impl<'a> LPS25HB<'a> { +impl<'a, I: i2c::I2CDevice> LPS25HB<'a, I> { pub fn new( - i2c: &'a dyn i2c::I2CDevice, + i2c: &'a I, interrupt_pin: &'a dyn gpio::InterruptPin<'a>, buffer: &'static mut [u8], apps: Grant, AllowRoCount<0>, AllowRwCount<0>>, ) -> Self { // setup and return struct Self { - i2c: i2c, - interrupt_pin: interrupt_pin, + i2c, + interrupt_pin, state: Cell::new(State::Idle), buffer: TakeCell::new(buffer), apps, @@ -172,13 +175,13 @@ impl<'a> LPS25HB<'a> { } } -impl i2c::I2CClient for LPS25HB<'_> { +impl i2c::I2CClient for LPS25HB<'_, I> { fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) { if status != Ok(()) { self.state.set(State::Idle); self.buffer.replace(buffer); self.owning_process.map(|pid| { - let _ = self.apps.enter(*pid, |_app, upcalls| { + let _ = self.apps.enter(pid, |_app, upcalls| { upcalls.schedule_upcall(0, (0, 0, 0)).ok(); }); }); @@ -204,7 +207,7 @@ impl i2c::I2CClient for LPS25HB<'_> { self.state.set(State::Idle); self.buffer.replace(buffer); self.owning_process.map(|pid| { - let _ = self.apps.enter(*pid, |_app, upcalls| { + let _ = self.apps.enter(pid, |_app, upcalls| { upcalls .schedule_upcall(0, (into_statuscode(Err(error.into())), 0, 0)) .ok(); @@ -219,7 +222,7 @@ impl i2c::I2CClient for LPS25HB<'_> { self.state.set(State::Idle); self.buffer.replace(buffer); self.owning_process.map(|pid| { - let _ = self.apps.enter(*pid, |_app, upcalls| { + let _ = self.apps.enter(pid, |_app, upcalls| { upcalls .schedule_upcall(0, (into_statuscode(Err(error.into())), 0, 0)) .ok(); @@ -238,7 +241,7 @@ impl i2c::I2CClient for LPS25HB<'_> { self.state.set(State::Idle); self.buffer.replace(buffer); self.owning_process.map(|pid| { - let _ = self.apps.enter(*pid, |_app, upcalls| { + let _ = self.apps.enter(pid, |_app, upcalls| { upcalls .schedule_upcall(0, (into_statuscode(Err(error.into())), 0, 0)) .ok(); @@ -253,7 +256,7 @@ impl i2c::I2CClient for LPS25HB<'_> { self.state.set(State::Idle); self.buffer.replace(buffer); self.owning_process.map(|pid| { - let _ = self.apps.enter(*pid, |_app, upcalls| { + let _ = self.apps.enter(pid, |_app, upcalls| { upcalls .schedule_upcall(0, (into_statuscode(Err(error.into())), 0, 0)) .ok(); @@ -264,15 +267,14 @@ impl i2c::I2CClient for LPS25HB<'_> { } } State::GotMeasurement => { - let pressure = (((buffer[2] as u32) << 16) - | ((buffer[1] as u32) << 8) - | (buffer[0] as u32)) as u32; + let pressure = + ((buffer[2] as u32) << 16) | ((buffer[1] as u32) << 8) | (buffer[0] as u32); // Returned as microbars let pressure_ubar = (pressure * 1000) / 4096; self.owning_process.map(|pid| { - let _ = self.apps.enter(*pid, |_app, upcalls| { + let _ = self.apps.enter(pid, |_app, upcalls| { upcalls .schedule_upcall(0, (pressure_ubar as usize, 0, 0)) .ok(); @@ -286,7 +288,7 @@ impl i2c::I2CClient for LPS25HB<'_> { self.state.set(State::Idle); self.buffer.replace(buffer); self.owning_process.map(|pid| { - let _ = self.apps.enter(*pid, |_app, upcalls| { + let _ = self.apps.enter(pid, |_app, upcalls| { upcalls .schedule_upcall(0, (into_statuscode(Err(error.into())), 0, 0)) .ok(); @@ -307,7 +309,7 @@ impl i2c::I2CClient for LPS25HB<'_> { } } -impl gpio::Client for LPS25HB<'_> { +impl gpio::Client for LPS25HB<'_, I> { fn fired(&self) { self.buffer.take().map(|buf| { // turn on i2c to send commands @@ -326,7 +328,7 @@ impl gpio::Client for LPS25HB<'_> { } } -impl SyscallDriver for LPS25HB<'_> { +impl SyscallDriver for LPS25HB<'_, I> { fn command( &self, command_num: usize, @@ -343,7 +345,7 @@ impl SyscallDriver for LPS25HB<'_> { // some (alive) process let match_or_empty_or_nonexistant = self.owning_process.map_or(true, |current_process| { self.apps - .enter(*current_process, |_, _| current_process == &process_id) + .enter(current_process, |_, _| current_process == process_id) .unwrap_or(true) }); if match_or_empty_or_nonexistant { diff --git a/capsules/src/lsm303agr.rs b/capsules/extra/src/lsm303agr.rs similarity index 89% rename from capsules/src/lsm303agr.rs rename to capsules/extra/src/lsm303agr.rs index e2f0c17f2e..e728c8617f 100644 --- a/capsules/src/lsm303agr.rs +++ b/capsules/extra/src/lsm303agr.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for the LSM303AGR 3D accelerometer and 3D magnetometer sensor. //! //! May be used with NineDof and Temperature @@ -12,7 +16,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! let mux_i2c = components::i2c::I2CMuxComponent::new(&stm32f3xx::i2c::I2C1) //! .finalize(components::i2c_mux_component_helper!()); //! @@ -32,7 +36,7 @@ //! //! NideDof Example //! -//! ```rust +//! ```rust,ignore //! let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); //! let grant_ninedof = board_kernel.create_grant(&grant_cap); //! @@ -55,7 +59,7 @@ //! //! Temperature Example //! -//! ```rust +//! ```rust,ignore //! let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); //! let grant_temp = board_kernel.create_grant(&grant_cap); //! @@ -91,11 +95,11 @@ use kernel::syscall::{CommandReturn, SyscallDriver}; use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::{ErrorCode, ProcessId}; -use crate::driver; use crate::lsm303xx::{ AccelerometerRegisters, Lsm303AccelDataRate, Lsm303MagnetoDataRate, Lsm303Range, Lsm303Scale, CTRL_REG1, CTRL_REG4, RANGE_FACTOR_X_Y, RANGE_FACTOR_Z, SCALE_FACTOR, }; +use capsules_core::driver; /// Syscall driver number. pub const DRIVER_NUM: usize = driver::NUM::Lsm303dlch as usize; @@ -140,10 +144,10 @@ enum State { #[derive(Default)] pub struct App {} -pub struct Lsm303agrI2C<'a> { +pub struct Lsm303agrI2C<'a, I: i2c::I2CDevice> { config_in_progress: Cell, - i2c_accelerometer: &'a dyn i2c::I2CDevice, - i2c_magnetometer: &'a dyn i2c::I2CDevice, + i2c_accelerometer: &'a I, + i2c_magnetometer: &'a I, state: Cell, accel_scale: Cell, mag_range: Cell, @@ -159,18 +163,18 @@ pub struct Lsm303agrI2C<'a> { owning_process: OptionalCell, } -impl<'a> Lsm303agrI2C<'a> { +impl<'a, I: i2c::I2CDevice> Lsm303agrI2C<'a, I> { pub fn new( - i2c_accelerometer: &'a dyn i2c::I2CDevice, - i2c_magnetometer: &'a dyn i2c::I2CDevice, + i2c_accelerometer: &'a I, + i2c_magnetometer: &'a I, buffer: &'static mut [u8], grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, - ) -> Lsm303agrI2C<'a> { + ) -> Lsm303agrI2C<'a, I> { // setup and return struct Lsm303agrI2C { config_in_progress: Cell::new(false), - i2c_accelerometer: i2c_accelerometer, - i2c_magnetometer: i2c_magnetometer, + i2c_accelerometer, + i2c_magnetometer, state: Cell::new(State::Idle), accel_scale: Cell::new(Lsm303Scale::Scale2G), mag_range: Cell::new(Lsm303Range::Range1G), @@ -401,19 +405,15 @@ impl<'a> Lsm303agrI2C<'a> { } } -impl i2c::I2CClient for Lsm303agrI2C<'_> { +impl i2c::I2CClient for Lsm303agrI2C<'_, I> { fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) { match self.state.get() { State::IsPresent => { - let present = if status == Ok(()) && buffer[0] == 60 { - true - } else { - false - }; + let present = status.is_ok() && buffer[0] == 60; self.owning_process.map(|pid| { - let _res = self.apps.enter(*pid, |_app, upcalls| { + let _res = self.apps.enter(pid, |_app, upcalls| { upcalls - .schedule_upcall(0, (if present { 1 } else { 0 }, 0, 0)) + .schedule_upcall(0, (usize::from(present), 0, 0)) .ok(); }); }); @@ -424,9 +424,9 @@ impl i2c::I2CClient for Lsm303agrI2C<'_> { State::SetPowerMode => { let set_power = status == Ok(()); self.owning_process.map(|pid| { - let _res = self.apps.enter(*pid, |_app, upcalls| { + let _res = self.apps.enter(pid, |_app, upcalls| { upcalls - .schedule_upcall(0, (if set_power { 1 } else { 0 }, 0, 0)) + .schedule_upcall(0, (usize::from(set_power), 0, 0)) .ok(); }); }); @@ -445,12 +445,9 @@ impl i2c::I2CClient for Lsm303agrI2C<'_> { State::SetScaleAndResolution => { let set_scale_and_resolution = status == Ok(()); self.owning_process.map(|pid| { - let _res = self.apps.enter(*pid, |_app, upcalls| { + let _res = self.apps.enter(pid, |_app, upcalls| { upcalls - .schedule_upcall( - 0, - (if set_scale_and_resolution { 1 } else { 0 }, 0, 0), - ) + .schedule_upcall(0, (usize::from(set_scale_and_resolution), 0, 0)) .ok(); }); }); @@ -497,7 +494,7 @@ impl i2c::I2CClient for Lsm303agrI2C<'_> { false }; self.owning_process.map(|pid| { - let _res = self.apps.enter(*pid, |_app, upcalls| { + let _res = self.apps.enter(pid, |_app, upcalls| { if values { upcalls.schedule_upcall(0, (x, y, z)).ok(); } else { @@ -512,9 +509,9 @@ impl i2c::I2CClient for Lsm303agrI2C<'_> { State::SetDataRate => { let set_magneto_data_rate = status == Ok(()); self.owning_process.map(|pid| { - let _res = self.apps.enter(*pid, |_app, upcalls| { + let _res = self.apps.enter(pid, |_app, upcalls| { upcalls - .schedule_upcall(0, (if set_magneto_data_rate { 1 } else { 0 }, 0, 0)) + .schedule_upcall(0, (usize::from(set_magneto_data_rate), 0, 0)) .ok(); }); }); @@ -530,9 +527,9 @@ impl i2c::I2CClient for Lsm303agrI2C<'_> { State::SetRange => { let set_range = status == Ok(()); self.owning_process.map(|pid| { - let _res = self.apps.enter(*pid, |_app, upcalls| { + let _res = self.apps.enter(pid, |_app, upcalls| { upcalls - .schedule_upcall(0, (if set_range { 1 } else { 0 }, 0, 0)) + .schedule_upcall(0, (usize::from(set_range), 0, 0)) .ok(); }); }); @@ -544,23 +541,17 @@ impl i2c::I2CClient for Lsm303agrI2C<'_> { self.state.set(State::Idle); } State::ReadTemperature => { - let mut temp: usize = 0; - let values = if status == Ok(()) { - temp = (buffer[1] as u16 as i16 | ((buffer[0] as i16) << 8)) as usize; - self.temperature_client.map(|client| { - client.callback((temp as i16 / 8) as usize); - }); - true - } else { - self.temperature_client.map(|client| { - client.callback(usize::MAX); - }); - false + let values = match status { + Ok(()) => Ok((buffer[1] as u16 as i16 | ((buffer[0] as i16) << 8)) as i32 / 8), + Err(i2c_err) => Err(i2c_err.into()), }; + self.temperature_client.map(|client| { + client.callback(values); + }); self.owning_process.map(|pid| { - let _res = self.apps.enter(*pid, |_app, upcalls| { - if values { - upcalls.schedule_upcall(0, (temp, 0, 0)).ok(); + let _res = self.apps.enter(pid, |_app, upcalls| { + if let Ok(temp) = values { + upcalls.schedule_upcall(0, (temp as usize, 0, 0)).ok(); } else { upcalls.schedule_upcall(0, (0, 0, 0)).ok(); } @@ -598,7 +589,7 @@ impl i2c::I2CClient for Lsm303agrI2C<'_> { false }; self.owning_process.map(|pid| { - let _res = self.apps.enter(*pid, |_app, upcalls| { + let _res = self.apps.enter(pid, |_app, upcalls| { if values { upcalls.schedule_upcall(0, (x, y, z)).ok(); } else { @@ -619,7 +610,7 @@ impl i2c::I2CClient for Lsm303agrI2C<'_> { } } -impl SyscallDriver for Lsm303agrI2C<'_> { +impl SyscallDriver for Lsm303agrI2C<'_, I> { fn command( &self, command_num: usize, @@ -635,7 +626,7 @@ impl SyscallDriver for Lsm303agrI2C<'_> { let match_or_empty_or_nonexistant = self.owning_process.map_or(true, |current_process| { self.apps - .enter(*current_process, |_, _| current_process == &process_id) + .enter(current_process, |_, _| current_process == process_id) .unwrap_or(true) }); if match_or_empty_or_nonexistant { @@ -660,8 +651,7 @@ impl SyscallDriver for Lsm303agrI2C<'_> { 2 => { if self.state.get() == State::Idle { if let Some(data_rate) = Lsm303AccelDataRate::from_usize(data1) { - match self.set_power_mode(data_rate, if data2 != 0 { true } else { false }) - { + match self.set_power_mode(data_rate, data2 != 0) { Ok(()) => CommandReturn::success(), Err(error) => CommandReturn::failure(error), } @@ -676,9 +666,7 @@ impl SyscallDriver for Lsm303agrI2C<'_> { 3 => { if self.state.get() == State::Idle { if let Some(scale) = Lsm303Scale::from_usize(data1) { - match self - .set_scale_and_resolution(scale, if data2 != 0 { true } else { false }) - { + match self.set_scale_and_resolution(scale, data2 != 0) { Ok(()) => CommandReturn::success(), Err(error) => CommandReturn::failure(error), } @@ -729,7 +717,7 @@ impl SyscallDriver for Lsm303agrI2C<'_> { } } -impl<'a> sensors::NineDof<'a> for Lsm303agrI2C<'a> { +impl<'a, I: i2c::I2CDevice> sensors::NineDof<'a> for Lsm303agrI2C<'a, I> { fn set_client(&self, nine_dof_client: &'a dyn sensors::NineDofClient) { self.nine_dof_client.replace(nine_dof_client); } @@ -743,7 +731,7 @@ impl<'a> sensors::NineDof<'a> for Lsm303agrI2C<'a> { } } -impl<'a> sensors::TemperatureDriver<'a> for Lsm303agrI2C<'a> { +impl<'a, I: i2c::I2CDevice> sensors::TemperatureDriver<'a> for Lsm303agrI2C<'a, I> { fn set_client(&self, temperature_client: &'a dyn sensors::TemperatureClient) { self.temperature_client.replace(temperature_client); } diff --git a/capsules/src/lsm303dlhc.rs b/capsules/extra/src/lsm303dlhc.rs similarity index 88% rename from capsules/src/lsm303dlhc.rs rename to capsules/extra/src/lsm303dlhc.rs index df7c259e12..6f1c4858a6 100644 --- a/capsules/src/lsm303dlhc.rs +++ b/capsules/extra/src/lsm303dlhc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for the LSM303DLHC 3D accelerometer and 3D magnetometer sensor. //! //! May be used with NineDof and Temperature @@ -11,7 +15,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! let mux_i2c = components::i2c::I2CMuxComponent::new(&stm32f3xx::i2c::I2C1) //! .finalize(components::i2c_mux_component_helper!()); //! @@ -31,7 +35,7 @@ //! //! NideDof Example //! -//! ```rust +//! ```rust,ignore //! let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); //! let grant_ninedof = board_kernel.create_grant(&grant_cap); //! @@ -54,7 +58,7 @@ //! //! Temperature Example //! -//! ```rust +//! ```rust,ignore //! let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); //! let grant_temp = board_kernel.create_grant(&grant_cap); //! @@ -95,7 +99,7 @@ use crate::lsm303xx::{ CTRL_REG1, CTRL_REG4, RANGE_FACTOR_X_Y, RANGE_FACTOR_Z, SCALE_FACTOR, }; -use crate::driver; +use capsules_core::driver; /// Syscall driver number. pub const DRIVER_NUM: usize = driver::NUM::Lsm303dlch as usize; @@ -119,7 +123,7 @@ enum_from_primitive! { } // Experimental -const TEMP_OFFSET: i8 = 17; +const TEMP_OFFSET: i32 = 17; #[derive(Clone, Copy, PartialEq)] enum State { @@ -134,10 +138,10 @@ enum State { ReadMagnetometerXYZ, } -pub struct Lsm303dlhcI2C<'a> { +pub struct Lsm303dlhcI2C<'a, I: i2c::I2CDevice> { config_in_progress: Cell, - i2c_accelerometer: &'a dyn i2c::I2CDevice, - i2c_magnetometer: &'a dyn i2c::I2CDevice, + i2c_accelerometer: &'a I, + i2c_magnetometer: &'a I, state: Cell, accel_scale: Cell, mag_range: Cell, @@ -156,18 +160,18 @@ pub struct Lsm303dlhcI2C<'a> { #[derive(Default)] pub struct App {} -impl<'a> Lsm303dlhcI2C<'a> { +impl<'a, I: i2c::I2CDevice> Lsm303dlhcI2C<'a, I> { pub fn new( - i2c_accelerometer: &'a dyn i2c::I2CDevice, - i2c_magnetometer: &'a dyn i2c::I2CDevice, + i2c_accelerometer: &'a I, + i2c_magnetometer: &'a I, buffer: &'static mut [u8], grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, - ) -> Lsm303dlhcI2C<'a> { + ) -> Lsm303dlhcI2C<'a, I> { // setup and return struct Lsm303dlhcI2C { config_in_progress: Cell::new(false), - i2c_accelerometer: i2c_accelerometer, - i2c_magnetometer: i2c_magnetometer, + i2c_accelerometer, + i2c_magnetometer, state: Cell::new(State::Idle), accel_scale: Cell::new(Lsm303Scale::Scale2G), mag_range: Cell::new(Lsm303Range::Range1G), @@ -394,20 +398,16 @@ impl<'a> Lsm303dlhcI2C<'a> { } } -impl i2c::I2CClient for Lsm303dlhcI2C<'_> { +impl i2c::I2CClient for Lsm303dlhcI2C<'_, I> { fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) { match self.state.get() { State::IsPresent => { - let present = if status == Ok(()) && buffer[0] == 60 { - true - } else { - false - }; + let present = status.is_ok() && buffer[0] == 60; self.current_process.map(|process_id| { - let _ = self.apps.enter(*process_id, |_grant, upcalls| { + let _ = self.apps.enter(process_id, |_grant, upcalls| { upcalls - .schedule_upcall(0, (if present { 1 } else { 0 }, 0, 0)) + .schedule_upcall(0, (usize::from(present), 0, 0)) .ok(); }); }); @@ -420,9 +420,9 @@ impl i2c::I2CClient for Lsm303dlhcI2C<'_> { let set_power = status == Ok(()); self.current_process.map(|process_id| { - let _ = self.apps.enter(*process_id, |_grant, upcalls| { + let _ = self.apps.enter(process_id, |_grant, upcalls| { upcalls - .schedule_upcall(0, (if set_power { 1 } else { 0 }, 0, 0)) + .schedule_upcall(0, (usize::from(set_power), 0, 0)) .ok(); }); }); @@ -441,12 +441,9 @@ impl i2c::I2CClient for Lsm303dlhcI2C<'_> { let set_scale_and_resolution = status == Ok(()); self.current_process.map(|process_id| { - let _ = self.apps.enter(*process_id, |_grant, upcalls| { + let _ = self.apps.enter(process_id, |_grant, upcalls| { upcalls - .schedule_upcall( - 0, - (if set_scale_and_resolution { 1 } else { 0 }, 0, 0), - ) + .schedule_upcall(0, (usize::from(set_scale_and_resolution), 0, 0)) .ok(); }); }); @@ -498,7 +495,7 @@ impl i2c::I2CClient for Lsm303dlhcI2C<'_> { }; self.current_process.map(|process_id| { - let _ = self.apps.enter(*process_id, |_grant, upcalls| { + let _ = self.apps.enter(process_id, |_grant, upcalls| { if values { upcalls.schedule_upcall(0, (x, y, z)).ok(); } else { @@ -515,19 +512,11 @@ impl i2c::I2CClient for Lsm303dlhcI2C<'_> { let set_temperature_and_magneto_data_rate = status == Ok(()); self.current_process.map(|process_id| { - let _ = self.apps.enter(*process_id, |_grant, upcalls| { + let _ = self.apps.enter(process_id, |_grant, upcalls| { upcalls .schedule_upcall( 0, - ( - if set_temperature_and_magneto_data_rate { - 1 - } else { - 0 - }, - 0, - 0, - ), + (usize::from(set_temperature_and_magneto_data_rate), 0, 0), ) .ok(); }); @@ -546,9 +535,9 @@ impl i2c::I2CClient for Lsm303dlhcI2C<'_> { let set_range = status == Ok(()); self.current_process.map(|process_id| { - let _ = self.apps.enter(*process_id, |_grant, upcalls| { + let _ = self.apps.enter(process_id, |_grant, upcalls| { upcalls - .schedule_upcall(0, (if set_range { 1 } else { 0 }, 0, 0)) + .schedule_upcall(0, (usize::from(set_range), 0, 0)) .ok(); }); }); @@ -561,24 +550,21 @@ impl i2c::I2CClient for Lsm303dlhcI2C<'_> { self.state.set(State::Idle); } State::ReadTemperature => { - let mut temp: usize = 0; - let values = if status == Ok(()) { - temp = ((buffer[1] as i16 | ((buffer[0] as i16) << 8)) >> 4) as usize; - self.temperature_client.map(|client| { - client.callback((temp as i16 / 8 + TEMP_OFFSET as i16) as usize); - }); - true - } else { - self.temperature_client.map(|client| { - client.callback(usize::MAX); - }); - false + let values = match status { + Ok(()) => Ok( + ((buffer[1] as i16 | ((buffer[0] as i16) << 8)) >> 4) as i32 / 8 + + TEMP_OFFSET, + ), + Err(i2c_error) => Err(i2c_error.into()), }; + self.temperature_client.map(|client| { + client.callback(values); + }); self.current_process.map(|process_id| { - let _ = self.apps.enter(*process_id, |_grant, upcalls| { - if values { - upcalls.schedule_upcall(0, (temp, 0, 0)).ok(); + let _ = self.apps.enter(process_id, |_grant, upcalls| { + if let Ok(temp) = values { + upcalls.schedule_upcall(0, (temp as usize, 0, 0)).ok(); } else { upcalls.schedule_upcall(0, (0, 0, 0)).ok(); } @@ -618,7 +604,7 @@ impl i2c::I2CClient for Lsm303dlhcI2C<'_> { }; self.current_process.map(|process_id| { - let _ = self.apps.enter(*process_id, |_grant, upcalls| { + let _ = self.apps.enter(process_id, |_grant, upcalls| { if values { upcalls.schedule_upcall(0, (x, y, z)).ok(); } else { @@ -640,7 +626,7 @@ impl i2c::I2CClient for Lsm303dlhcI2C<'_> { } } -impl SyscallDriver for Lsm303dlhcI2C<'_> { +impl SyscallDriver for Lsm303dlhcI2C<'_, I> { fn command( &self, command_num: usize, @@ -658,7 +644,7 @@ impl SyscallDriver for Lsm303dlhcI2C<'_> { // some (alive) process let match_or_empty_or_nonexistant = self.current_process.map_or(true, |current_process| { self.apps - .enter(*current_process, |_, _| current_process == &process_id) + .enter(current_process, |_, _| current_process == process_id) .unwrap_or(true) }); @@ -684,8 +670,7 @@ impl SyscallDriver for Lsm303dlhcI2C<'_> { 2 => { if self.state.get() == State::Idle { if let Some(data_rate) = Lsm303AccelDataRate::from_usize(data1) { - match self.set_power_mode(data_rate, if data2 != 0 { true } else { false }) - { + match self.set_power_mode(data_rate, data2 != 0) { Ok(()) => CommandReturn::success(), Err(error) => CommandReturn::failure(error), } @@ -700,9 +685,7 @@ impl SyscallDriver for Lsm303dlhcI2C<'_> { 3 => { if self.state.get() == State::Idle { if let Some(scale) = Lsm303Scale::from_usize(data1) { - match self - .set_scale_and_resolution(scale, if data2 != 0 { true } else { false }) - { + match self.set_scale_and_resolution(scale, data2 != 0) { Ok(()) => CommandReturn::success(), Err(error) => CommandReturn::failure(error), } @@ -717,10 +700,7 @@ impl SyscallDriver for Lsm303dlhcI2C<'_> { 4 => { if self.state.get() == State::Idle { if let Some(data_rate) = Lsm303MagnetoDataRate::from_usize(data1) { - match self.set_temperature_and_magneto_data_rate( - if data2 != 0 { true } else { false }, - data_rate, - ) { + match self.set_temperature_and_magneto_data_rate(data2 != 0, data_rate) { Ok(()) => CommandReturn::success(), Err(error) => CommandReturn::failure(error), } @@ -756,7 +736,7 @@ impl SyscallDriver for Lsm303dlhcI2C<'_> { } } -impl<'a> sensors::NineDof<'a> for Lsm303dlhcI2C<'a> { +impl<'a, I: i2c::I2CDevice> sensors::NineDof<'a> for Lsm303dlhcI2C<'a, I> { fn set_client(&self, nine_dof_client: &'a dyn sensors::NineDofClient) { self.nine_dof_client.replace(nine_dof_client); } @@ -770,7 +750,7 @@ impl<'a> sensors::NineDof<'a> for Lsm303dlhcI2C<'a> { } } -impl<'a> sensors::TemperatureDriver<'a> for Lsm303dlhcI2C<'a> { +impl<'a, I: i2c::I2CDevice> sensors::TemperatureDriver<'a> for Lsm303dlhcI2C<'a, I> { fn set_client(&self, temperature_client: &'a dyn sensors::TemperatureClient) { self.temperature_client.replace(temperature_client); } diff --git a/capsules/src/lsm303xx.rs b/capsules/extra/src/lsm303xx.rs similarity index 95% rename from capsules/src/lsm303xx.rs rename to capsules/extra/src/lsm303xx.rs index 9b8ae82051..20b4719547 100644 --- a/capsules/src/lsm303xx.rs +++ b/capsules/extra/src/lsm303xx.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! LSM303xx Sensors //! diff --git a/capsules/src/lsm6dsoxtr.rs b/capsules/extra/src/lsm6dsoxtr.rs similarity index 93% rename from capsules/src/lsm6dsoxtr.rs rename to capsules/extra/src/lsm6dsoxtr.rs index ffaca34d80..1d588c38f8 100644 --- a/capsules/src/lsm6dsoxtr.rs +++ b/capsules/extra/src/lsm6dsoxtr.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! LSM6DSOXTR Sensor //! //! Driver for the LSM6DSOXTR 3D accelerometer and 3D gyroscope sensor. @@ -11,7 +15,7 @@ //! Author: Cristiana Andrei #![allow(non_camel_case_types)] -use crate::driver; +use capsules_core::driver; use core::cell::Cell; use enum_primitive::cast::FromPrimitive; @@ -165,8 +169,8 @@ enum State { #[derive(Default)] pub struct App {} -pub struct Lsm6dsoxtrI2C<'a> { - i2c: &'a dyn i2c::I2CDevice, +pub struct Lsm6dsoxtrI2C<'a, I: i2c::I2CDevice> { + i2c: &'a I, state: Cell, config_in_progress: Cell, gyro_data_rate: Cell, @@ -183,14 +187,14 @@ pub struct Lsm6dsoxtrI2C<'a> { syscall_process: OptionalCell, } -impl<'a> Lsm6dsoxtrI2C<'a> { +impl<'a, I: i2c::I2CDevice> Lsm6dsoxtrI2C<'a, I> { pub fn new( - i2c: &'a dyn i2c::I2CDevice, + i2c: &'a I, buffer: &'static mut [u8], grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, - ) -> Lsm6dsoxtrI2C<'a> { + ) -> Lsm6dsoxtrI2C<'a, I> { Lsm6dsoxtrI2C { - i2c: i2c, + i2c, state: Cell::new(State::Idle), config_in_progress: Cell::new(false), gyro_data_rate: Cell::new(LSM6DSOXGyroDataRate::LSM6DSOX_GYRO_RATE_12_5_HZ), @@ -376,7 +380,7 @@ impl<'a> Lsm6dsoxtrI2C<'a> { } } -impl i2c::I2CClient for Lsm6dsoxtrI2C<'_> { +impl i2c::I2CClient for Lsm6dsoxtrI2C<'_, I> { fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) { match self.state.get() { State::IsPresent => { @@ -406,7 +410,7 @@ impl i2c::I2CClient for Lsm6dsoxtrI2C<'_> { 0, ( into_statuscode(status.map_err(|i2c_error| i2c_error.into())), - if self.is_present.get() { 1 } else { 0 }, + usize::from(self.is_present.get()), 0, ), ) @@ -477,22 +481,17 @@ impl i2c::I2CClient for Lsm6dsoxtrI2C<'_> { } State::ReadTemperature => { - let mut temperature: usize = 0; - - if status == Ok(()) { - self.temperature_client.map(|client| { - temperature = ((((buffer[0] as u16 + ((buffer[1] as u16) << 8)) as i16) - as isize - / (TEMP_SENSITIVITY_FACTOR as isize) - + 25) - * 100) as usize; - client.callback(temperature); - }); - } else { - self.temperature_client.map(|client| { - client.callback(0); - }); + let temperature = match status { + Ok(()) => Ok(((((buffer[0] as u16 + ((buffer[1] as u16) << 8)) as i16) + as isize + / (TEMP_SENSITIVITY_FACTOR as isize) + + 25) + * 100) as i32), + Err(i2c_error) => Err(i2c_error.into()), }; + self.temperature_client.map(|client| { + client.callback(temperature); + }); self.buffer.replace(buffer); self.i2c.disable(); self.state.set(State::Idle); @@ -521,7 +520,7 @@ impl i2c::I2CClient for Lsm6dsoxtrI2C<'_> { 0, ( into_statuscode(status.map_err(|i2c_error| i2c_error.into())), - if status == Ok(()) { 1 } else { 0 }, + usize::from(status == Ok(())), 0, ), ) @@ -542,7 +541,7 @@ impl i2c::I2CClient for Lsm6dsoxtrI2C<'_> { 0, ( into_statuscode(status.map_err(|i2c_error| i2c_error.into())), - if status == Ok(()) { 1 } else { 0 }, + usize::from(status == Ok(())), 0, ), ) @@ -554,7 +553,7 @@ impl i2c::I2CClient for Lsm6dsoxtrI2C<'_> { } } -impl SyscallDriver for Lsm6dsoxtrI2C<'_> { +impl SyscallDriver for Lsm6dsoxtrI2C<'_, I> { fn command( &self, command_num: usize, @@ -587,10 +586,7 @@ impl SyscallDriver for Lsm6dsoxtrI2C<'_> { 2 => { if self.state.get() == State::Idle { if let Some(data_rate) = LSM6DSOXAccelDataRate::from_usize(data1) { - match self.set_accelerometer_power_mode( - data_rate, - if data2 != 0 { true } else { false }, - ) { + match self.set_accelerometer_power_mode(data_rate, data2 != 0) { Ok(()) => { self.syscall_process.set(process_id); CommandReturn::success() @@ -608,10 +604,7 @@ impl SyscallDriver for Lsm6dsoxtrI2C<'_> { 3 => { if self.state.get() == State::Idle { if let Some(data_rate) = LSM6DSOXGyroDataRate::from_usize(data1) { - match self.set_gyroscope_power_mode( - data_rate, - if data2 != 0 { true } else { false }, - ) { + match self.set_gyroscope_power_mode(data_rate, data2 != 0) { Ok(()) => { self.syscall_process.set(process_id); CommandReturn::success() @@ -634,7 +627,7 @@ impl SyscallDriver for Lsm6dsoxtrI2C<'_> { } } -impl<'a> NineDof<'a> for Lsm6dsoxtrI2C<'a> { +impl<'a, I: i2c::I2CDevice> NineDof<'a> for Lsm6dsoxtrI2C<'a, I> { fn set_client(&self, nine_dof_client: &'a dyn NineDofClient) { self.nine_dof_client.replace(nine_dof_client); } @@ -648,7 +641,7 @@ impl<'a> NineDof<'a> for Lsm6dsoxtrI2C<'a> { } } -impl<'a> sensors::TemperatureDriver<'a> for Lsm6dsoxtrI2C<'a> { +impl<'a, I: i2c::I2CDevice> sensors::TemperatureDriver<'a> for Lsm6dsoxtrI2C<'a, I> { fn set_client(&self, temperature_client: &'a dyn sensors::TemperatureClient) { self.temperature_client.replace(temperature_client); } diff --git a/capsules/src/ltc294x.rs b/capsules/extra/src/ltc294x.rs similarity index 84% rename from capsules/src/ltc294x.rs rename to capsules/extra/src/ltc294x.rs index 7e9c7afa40..3f4717593b 100644 --- a/capsules/src/ltc294x.rs +++ b/capsules/extra/src/ltc294x.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for the LTC294X line of coulomb counters. //! //! - @@ -26,15 +30,16 @@ //! //! Here is a sample usage of this capsule in a board's main.rs file: //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! +//! let buffer = static_init!([u8; capsules::ltc294x::BUF_LEN], [0; capsules::ltc294x::BUF_LEN]); //! let ltc294x_i2c = static_init!( //! capsules::virtual_i2c::I2CDevice, //! capsules::virtual_i2c::I2CDevice::new(i2c_mux, 0x64)); //! let ltc294x = static_init!( //! capsules::ltc294x::LTC294X<'static>, -//! capsules::ltc294x::LTC294X::new(ltc294x_i2c, None, &mut capsules::ltc294x::BUFFER)); +//! capsules::ltc294x::LTC294X::new(ltc294x_i2c, None, buffer)); //! ltc294x_i2c.set_client(ltc294x); //! //! // Optionally create the object that provides an interface for the coulomb @@ -55,10 +60,10 @@ use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::Ltc294x as usize; -pub static mut BUFFER: [u8; 20] = [0; 20]; +pub const BUF_LEN: usize = 20; #[allow(dead_code)] enum Registers { @@ -134,8 +139,8 @@ pub trait LTC294XClient { } /// Implementation of a driver for the LTC294X coulomb counters. -pub struct LTC294X<'a> { - i2c: &'a dyn i2c::I2CDevice, +pub struct LTC294X<'a, I: i2c::I2CDevice> { + i2c: &'a I, interrupt_pin: Option<&'a dyn gpio::InterruptPin<'a>>, model: Cell, state: Cell, @@ -143,15 +148,15 @@ pub struct LTC294X<'a> { client: OptionalCell<&'static dyn LTC294XClient>, } -impl<'a> LTC294X<'a> { +impl<'a, I: i2c::I2CDevice> LTC294X<'a, I> { pub fn new( - i2c: &'a dyn i2c::I2CDevice, + i2c: &'a I, interrupt_pin: Option<&'a dyn gpio::InterruptPin<'a>>, buffer: &'static mut [u8], - ) -> LTC294X<'a> { + ) -> LTC294X<'a, I> { LTC294X { - i2c: i2c, - interrupt_pin: interrupt_pin, + i2c, + interrupt_pin, model: Cell::new(ChipModel::LTC2941), state: Cell::new(State::Idle), buffer: TakeCell::new(buffer), @@ -336,7 +341,7 @@ impl<'a> LTC294X<'a> { } } -impl i2c::I2CClient for LTC294X<'_> { +impl i2c::I2CClient for LTC294X<'_, I> { fn command_complete(&self, buffer: &'static mut [u8], _status: Result<(), i2c::Error>) { match self.state.get() { State::ReadStatus => { @@ -410,7 +415,7 @@ impl i2c::I2CClient for LTC294X<'_> { } } -impl gpio::Client for LTC294X<'_> { +impl gpio::Client for LTC294X<'_, I> { fn fired(&self) { self.client.map(|client| { client.interrupt(); @@ -418,32 +423,50 @@ impl gpio::Client for LTC294X<'_> { } } +/// IDs for subscribed upcalls. +mod upcall { + /// The callback that that is triggered when events finish and when readings + /// are ready. The first argument represents which callback was triggered. + /// + /// - `0`: Interrupt occurred from the LTC294X. + /// - `1`: Got the status. + /// - `2`: Read the charge used. + /// - `3`: `done()` was called. + /// - `4`: Read the voltage. + /// - `5`: Read the current. + pub const EVENT_FINISHED: usize = 0; + /// Number of upcalls. + pub const COUNT: u8 = 1; +} + /// Default implementation of the LTC2941 driver that provides a Driver /// interface for providing access to applications. -pub struct LTC294XDriver<'a> { - ltc294x: &'a LTC294X<'a>, - grants: Grant, AllowRoCount<0>, AllowRwCount<0>>, +pub struct LTC294XDriver<'a, I: i2c::I2CDevice> { + ltc294x: &'a LTC294X<'a, I>, + grants: Grant, AllowRoCount<0>, AllowRwCount<0>>, owning_process: OptionalCell, } -impl<'a> LTC294XDriver<'a> { +impl<'a, I: i2c::I2CDevice> LTC294XDriver<'a, I> { pub fn new( - ltc: &'a LTC294X<'a>, - grants: Grant, AllowRoCount<0>, AllowRwCount<0>>, - ) -> LTC294XDriver<'a> { + ltc: &'a LTC294X<'a, I>, + grants: Grant, AllowRoCount<0>, AllowRwCount<0>>, + ) -> LTC294XDriver<'a, I> { LTC294XDriver { ltc294x: ltc, - grants: grants, + grants, owning_process: OptionalCell::empty(), } } } -impl LTC294XClient for LTC294XDriver<'_> { +impl LTC294XClient for LTC294XDriver<'_, I> { fn interrupt(&self) { self.owning_process.map(|pid| { - let _res = self.grants.enter(*pid, |_app, upcalls| { - upcalls.schedule_upcall(0, (0, 0, 0)).ok(); + let _res = self.grants.enter(pid, |_app, upcalls| { + upcalls + .schedule_upcall(upcall::EVENT_FINISHED, (0, 0, 0)) + .ok(); }); }); } @@ -462,9 +485,12 @@ impl LTC294XClient for LTC294XDriver<'_> { | ((charge_alert_high as usize) << 3) | ((accumulated_charge_overflow as usize) << 4); self.owning_process.map(|pid| { - let _res = self.grants.enter(*pid, |_app, upcalls| { + let _res = self.grants.enter(pid, |_app, upcalls| { upcalls - .schedule_upcall(0, (1, ret, self.ltc294x.model.get() as usize)) + .schedule_upcall( + upcall::EVENT_FINISHED, + (1, ret, self.ltc294x.model.get() as usize), + ) .ok(); }); }); @@ -472,57 +498,51 @@ impl LTC294XClient for LTC294XDriver<'_> { fn charge(&self, charge: u16) { self.owning_process.map(|pid| { - let _res = self.grants.enter(*pid, |_app, upcalls| { - upcalls.schedule_upcall(0, (2, charge as usize, 0)).ok(); + let _res = self.grants.enter(pid, |_app, upcalls| { + upcalls + .schedule_upcall(upcall::EVENT_FINISHED, (2, charge as usize, 0)) + .ok(); }); }); } fn done(&self) { self.owning_process.map(|pid| { - let _res = self.grants.enter(*pid, |_app, upcalls| { - upcalls.schedule_upcall(0, (3, 0, 0)).ok(); + let _res = self.grants.enter(pid, |_app, upcalls| { + upcalls + .schedule_upcall(upcall::EVENT_FINISHED, (3, 0, 0)) + .ok(); }); }); } fn voltage(&self, voltage: u16) { self.owning_process.map(|pid| { - let _res = self.grants.enter(*pid, |_app, upcalls| { - upcalls.schedule_upcall(0, (4, voltage as usize, 0)).ok(); + let _res = self.grants.enter(pid, |_app, upcalls| { + upcalls + .schedule_upcall(upcall::EVENT_FINISHED, (4, voltage as usize, 0)) + .ok(); }); }); } fn current(&self, current: u16) { self.owning_process.map(|pid| { - let _res = self.grants.enter(*pid, |_app, upcalls| { - upcalls.schedule_upcall(0, (5, current as usize, 0)).ok(); + let _res = self.grants.enter(pid, |_app, upcalls| { + upcalls + .schedule_upcall(upcall::EVENT_FINISHED, (5, current as usize, 0)) + .ok(); }); }); } } -impl SyscallDriver for LTC294XDriver<'_> { - // Setup callbacks. - // - // ### `subscribe_num` - // - // - `0`: Set the callback that that is triggered when events finish and - // when readings are ready. The first argument represents which callback - // was triggered. - // - `0`: Interrupt occurred from the LTC294X. - // - `1`: Got the status. - // - `2`: Read the charge used. - // - `3`: `done()` was called. - // - `4`: Read the voltage. - // - `5`: Read the current. - +impl SyscallDriver for LTC294XDriver<'_, I> { /// Request operations for the LTC294X chip. /// /// ### `command_num` /// - /// - `0`: Driver check. + /// - `0`: Driver existence check. /// - `1`: Get status of the chip. /// - `2`: Configure settings of the chip. /// - `3`: Reset accumulated charge measurement to zero. @@ -550,7 +570,7 @@ impl SyscallDriver for LTC294XDriver<'_> { let match_or_empty_or_nonexistant = self.owning_process.map_or(true, |current_process| { self.grants - .enter(*current_process, |_, _| current_process == &process_id) + .enter(current_process, |_, _| current_process == process_id) .unwrap_or(true) }); if match_or_empty_or_nonexistant { diff --git a/capsules/src/max17205.rs b/capsules/extra/src/max17205.rs similarity index 86% rename from capsules/src/max17205.rs rename to capsules/extra/src/max17205.rs index f942e83ee4..014cdda460 100644 --- a/capsules/src/max17205.rs +++ b/capsules/extra/src/max17205.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for the Maxim MAX17205 fuel gauge. //! //! @@ -12,7 +16,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! // Two i2c addresses are necessary. @@ -24,10 +28,12 @@ //! let max17205_i2c_upper = static_init!( //! capsules::virtual_i2c::I2CDevice, //! capsules::virtual_i2c::I2CDevice::new(i2c_bus, 0x0B)); +//! let max17205_buffer = static_init!([u8; capsules::max17205::BUFFER_LENGTH], +//! [0; capsules::max17205::BUFFER_LENGTH]); //! let max17205 = static_init!( //! capsules::max17205::MAX17205<'static>, //! capsules::max17205::MAX17205::new(max17205_i2c_lower, max17205_i2c_upper, -//! &mut capsules::max17205::BUFFER)); +//! max17205_buffer)); //! max17205_i2c.set_client(max17205); //! //! // For userspace. @@ -46,10 +52,10 @@ use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::Max17205 as usize; -pub static mut BUFFER: [u8; 8] = [0; 8]; +pub const BUFFER_LENGTH: usize = 8; // Addresses 0x000 - 0x0FF, 0x180 - 0x1FF can be written as blocks // Addresses 0x100 - 0x17F must be written by word @@ -104,9 +110,9 @@ pub trait MAX17205Client { fn romid(&self, rid: u64, error: Result<(), ErrorCode>); } -pub struct MAX17205<'a> { - i2c_lower: &'a dyn i2c::I2CDevice, - i2c_upper: &'a dyn i2c::I2CDevice, +pub struct MAX17205<'a, I: i2c::I2CDevice> { + i2c_lower: &'a I, + i2c_upper: &'a I, state: Cell, soc: Cell, soc_mah: Cell, @@ -115,15 +121,11 @@ pub struct MAX17205<'a> { client: OptionalCell<&'static dyn MAX17205Client>, } -impl<'a> MAX17205<'a> { - pub fn new( - i2c_lower: &'a dyn i2c::I2CDevice, - i2c_upper: &'a dyn i2c::I2CDevice, - buffer: &'static mut [u8], - ) -> MAX17205<'a> { +impl<'a, I: i2c::I2CDevice> MAX17205<'a, I> { + pub fn new(i2c_lower: &'a I, i2c_upper: &'a I, buffer: &'static mut [u8]) -> MAX17205<'a, I> { MAX17205 { - i2c_lower: i2c_lower, - i2c_upper: i2c_upper, + i2c_lower, + i2c_upper, state: Cell::new(State::Idle), soc: Cell::new(0), soc_mah: Cell::new(0), @@ -199,8 +201,10 @@ impl<'a> MAX17205<'a> { fn setup_read_romid(&self) -> Result<(), ErrorCode> { self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| { self.i2c_upper.enable(); + let nrom_id = Registers::NRomID as u16; - buffer[0] = Registers::NRomID as u8; + buffer[0] = (nrom_id & 0xFF) as u8; + buffer[1] = (nrom_id >> 8) as u8; // TODO verify errors let _ = self.i2c_upper.write(buffer, 1); self.state.set(State::SetupReadRomID); @@ -210,7 +214,7 @@ impl<'a> MAX17205<'a> { } } -impl i2c::I2CClient for MAX17205<'_> { +impl i2c::I2CClient for MAX17205<'_, I> { fn command_complete(&self, buffer: &'static mut [u8], error: Result<(), i2c::Error>) { match self.state.get() { State::SetupReadStatus => { @@ -226,7 +230,7 @@ impl i2c::I2CClient for MAX17205<'_> { client.status( status, match error { - Ok(_) => Ok(()), + Ok(()) => Ok(()), Err(e) => Err(e.into()), }, ) @@ -255,7 +259,7 @@ impl i2c::I2CClient for MAX17205<'_> { self.buffer.take().map(|selfbuf| { // Get SOC mAh and percentage // Write reqcap address - selfbuf[0] = ((Registers::FullCapRep as u8) & 0xFF) as u8; + selfbuf[0] = (Registers::FullCapRep as u8) & 0xFF; // TODO verify errors let _ = self.i2c_lower.write(selfbuf, 1); @@ -277,7 +281,7 @@ impl i2c::I2CClient for MAX17205<'_> { self.soc_mah.get(), full_mah, match error { - Ok(_) => Ok(()), + Ok(()) => Ok(()), Err(e) => Err(e.into()), }, ); @@ -301,7 +305,7 @@ impl i2c::I2CClient for MAX17205<'_> { client.coulomb( coulomb, match error { - Ok(_) => Ok(()), + Ok(()) => Ok(()), Err(e) => Err(e.into()), }, ); @@ -327,7 +331,7 @@ impl i2c::I2CClient for MAX17205<'_> { // Now issue write of memory address of current // Setup read capacity self.buffer.take().map(|selfbuf| { - selfbuf[0] = ((Registers::Current as u8) & 0xFF) as u8; + selfbuf[0] = (Registers::Current as u8) & 0xFF; // TODO verify errors let _ = self.i2c_lower.write(selfbuf, 1); @@ -348,7 +352,7 @@ impl i2c::I2CClient for MAX17205<'_> { self.voltage.get(), current, match error { - Ok(_) => Ok(()), + Ok(()) => Ok(()), Err(e) => Err(e.into()), }, ) @@ -369,14 +373,14 @@ impl i2c::I2CClient for MAX17205<'_> { .iter() .take(8) .enumerate() - .fold(0u64, |rid, (i, b)| rid | ((*b as u64) << i * 8)); + .fold(0u64, |rid, (i, b)| rid | ((*b as u64) << (i * 8))); self.buffer.replace(buffer); self.client.map(|client| { client.romid( rid, match error { - Ok(_) => Ok(()), + Ok(()) => Ok(()), Err(e) => Err(e.into()), }, ) @@ -390,19 +394,27 @@ impl i2c::I2CClient for MAX17205<'_> { } } +/// IDs for subscribed upcalls. +mod upcall { + /// Callback for when all events complete or data is ready. + pub const EVENT_COMPLETE: usize = 0; + /// Number of upcalls. + pub const COUNT: u8 = 1; +} + #[derive(Default)] pub struct App {} -pub struct MAX17205Driver<'a> { - max17205: &'a MAX17205<'a>, +pub struct MAX17205Driver<'a, I: i2c::I2CDevice> { + max17205: &'a MAX17205<'a, I>, owning_process: OptionalCell, - apps: Grant, AllowRoCount<0>, AllowRwCount<0>>, + apps: Grant, AllowRoCount<0>, AllowRwCount<0>>, } -impl<'a> MAX17205Driver<'a> { +impl<'a, I: i2c::I2CDevice> MAX17205Driver<'a, I> { pub fn new( - max: &'a MAX17205, - grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, + max: &'a MAX17205, + grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, ) -> Self { Self { max17205: max, @@ -412,13 +424,13 @@ impl<'a> MAX17205Driver<'a> { } } -impl MAX17205Client for MAX17205Driver<'_> { +impl MAX17205Client for MAX17205Driver<'_, I> { fn status(&self, status: u16, error: Result<(), ErrorCode>) { self.owning_process.map(|pid| { - let _ = self.apps.enter(*pid, |_app, upcalls| { + let _ = self.apps.enter(pid, |_app, upcalls| { upcalls .schedule_upcall( - 0, + upcall::EVENT_COMPLETE, ( kernel::errorcode::into_statuscode(error), status as usize, @@ -438,10 +450,10 @@ impl MAX17205Client for MAX17205Driver<'_> { error: Result<(), ErrorCode>, ) { self.owning_process.map(|pid| { - let _ = self.apps.enter(*pid, |_app, upcalls| { + let _ = self.apps.enter(pid, |_app, upcalls| { upcalls .schedule_upcall( - 0, + upcall::EVENT_COMPLETE, ( kernel::errorcode::into_statuscode(error), percent as usize, @@ -455,10 +467,10 @@ impl MAX17205Client for MAX17205Driver<'_> { fn voltage_current(&self, voltage: u16, current: u16, error: Result<(), ErrorCode>) { self.owning_process.map(|pid| { - let _ = self.apps.enter(*pid, |_app, upcalls| { + let _ = self.apps.enter(pid, |_app, upcalls| { upcalls .schedule_upcall( - 0, + upcall::EVENT_COMPLETE, ( kernel::errorcode::into_statuscode(error), voltage as usize, @@ -472,10 +484,10 @@ impl MAX17205Client for MAX17205Driver<'_> { fn coulomb(&self, coulomb: u16, error: Result<(), ErrorCode>) { self.owning_process.map(|pid| { - let _ = self.apps.enter(*pid, |_app, upcalls| { + let _ = self.apps.enter(pid, |_app, upcalls| { upcalls .schedule_upcall( - 0, + upcall::EVENT_COMPLETE, ( kernel::errorcode::into_statuscode(error), coulomb as usize, @@ -489,10 +501,10 @@ impl MAX17205Client for MAX17205Driver<'_> { fn romid(&self, rid: u64, error: Result<(), ErrorCode>) { self.owning_process.map(|pid| { - let _ = self.apps.enter(*pid, |_app, upcalls| { + let _ = self.apps.enter(pid, |_app, upcalls| { upcalls .schedule_upcall( - 0, + upcall::EVENT_COMPLETE, ( kernel::errorcode::into_statuscode(error), (rid & 0xffffffff) as usize, @@ -505,18 +517,12 @@ impl MAX17205Client for MAX17205Driver<'_> { } } -impl SyscallDriver for MAX17205Driver<'_> { - // Setup callback. - // - // ### `subscribe_num` - // - // - `0`: Setup a callback for when all events complete or data is ready. - +impl SyscallDriver for MAX17205Driver<'_, I> { /// Setup and read the MAX17205. /// /// ### `command_num` /// - /// - `0`: Driver check. + /// - `0`: Driver existence check. /// - `1`: Read the current status of the MAX17205. /// - `2`: Read the current state of charge percent. /// - `3`: Read the current voltage and current draw. @@ -538,7 +544,7 @@ impl SyscallDriver for MAX17205Driver<'_> { // some (alive) process let match_or_empty_or_nonexistant = self.owning_process.map_or(true, |current_process| { self.apps - .enter(*current_process, |_, _| current_process == &process_id) + .enter(current_process, |_, _| current_process == process_id) .unwrap_or(true) }); if match_or_empty_or_nonexistant { diff --git a/capsules/src/mcp230xx.rs b/capsules/extra/src/mcp230xx.rs similarity index 96% rename from capsules/src/mcp230xx.rs rename to capsules/extra/src/mcp230xx.rs index f2ef889a2b..30132e4bd8 100644 --- a/capsules/src/mcp230xx.rs +++ b/capsules/extra/src/mcp230xx.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for the Microchip MCP230xx I2C GPIO extenders. //! //! - @@ -25,19 +29,21 @@ //! //! Example usage: //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! // Configure the MCP230xx. Device address 0x20. //! let mcp230xx_i2c = static_init!( //! capsules::virtual_i2c::I2CDevice, //! capsules::virtual_i2c::I2CDevice::new(i2c_mux, 0x20)); +//! let mcp230xx_buffer = static_init!([u8; capsules::mcp230xx::BUFFER_LENGTH], +//! [0; capsules::mcp230xx::BUFFER_LENGTH]); //! let mcp230xx = static_init!( //! capsules::mcp230xx::MCP230xx<'static>, //! capsules::mcp230xx::MCP230xx::new(mcp230xx_i2c, //! Some(&sam4l::gpio::PA[04]), //! None, -//! &mut capsules::mcp230xx::BUFFER, +//! mcp230xx_buffer, //! 8, // How many pins in a bank //! 1, // How many pin banks on the chip //! )); @@ -71,7 +77,7 @@ use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::ErrorCode; // Buffer to use for I2C messages -pub static mut BUFFER: [u8; 7] = [0; 7]; +pub const BUFFER_LENGTH: usize = 7; #[allow(dead_code)] #[derive(Debug)] @@ -127,8 +133,8 @@ enum PinState { Low = 0x00, } -pub struct MCP230xx<'a> { - i2c: &'a dyn hil::i2c::I2CDevice, +pub struct MCP230xx<'a, I: hil::i2c::I2CDevice> { + i2c: &'a I, state: Cell, bank_size: u8, // How many GPIO pins per bank (likely 8) number_of_banks: u8, // How many GPIO banks this extender has (likely 1 or 2) @@ -140,23 +146,23 @@ pub struct MCP230xx<'a> { client: OptionalCell<&'static dyn gpio_async::Client>, } -impl<'a> MCP230xx<'a> { +impl<'a, I: hil::i2c::I2CDevice> MCP230xx<'a, I> { pub fn new( - i2c: &'a dyn hil::i2c::I2CDevice, + i2c: &'a I, interrupt_pin_a: Option<&'a dyn gpio::InterruptValuePin<'a>>, interrupt_pin_b: Option<&'a dyn gpio::InterruptValuePin<'a>>, buffer: &'static mut [u8], bank_size: u8, number_of_banks: u8, - ) -> MCP230xx<'a> { + ) -> MCP230xx<'a, I> { MCP230xx { - i2c: i2c, + i2c, state: Cell::new(State::Idle), - bank_size: bank_size, - number_of_banks: number_of_banks, + bank_size, + number_of_banks, buffer: TakeCell::new(buffer), - interrupt_pin_a: interrupt_pin_a, - interrupt_pin_b: interrupt_pin_b, + interrupt_pin_a, + interrupt_pin_b, interrupts_enabled: Cell::new(0), interrupts_mode: Cell::new(0), client: OptionalCell::empty(), @@ -203,7 +209,7 @@ impl<'a> MCP230xx<'a> { /// and size of the bank. fn calc_register_addr(&self, register: Registers, pin_number: u8) -> u8 { if self.number_of_banks == 1 { - pin_number as u8 + pin_number } else { // Calculate an offset based on which bank this pin is in. let offset = pin_number / self.bank_size; @@ -308,7 +314,7 @@ impl<'a> MCP230xx<'a> { // we also want to set. buffer[i] = 0b00000010; // Make MCP230xx interrupt pin active high. // TODO verify errors - let _ = self.i2c.write(buffer, (i + 1) as u8); + let _ = self.i2c.write(buffer, i + 1); self.state.set(State::EnableInterruptSettings(pin_number)); Ok(()) @@ -388,7 +394,7 @@ impl<'a> MCP230xx<'a> { } } -impl hil::i2c::I2CClient for MCP230xx<'_> { +impl hil::i2c::I2CClient for MCP230xx<'_, I> { fn command_complete(&self, buffer: &'static mut [u8], _status: Result<(), hil::i2c::Error>) { match self.state.get() { State::SelectIoDir(pin_number, direction) => { @@ -550,7 +556,7 @@ impl hil::i2c::I2CClient for MCP230xx<'_> { } } -impl gpio::ClientWithValue for MCP230xx<'_> { +impl gpio::ClientWithValue for MCP230xx<'_, I> { fn fired(&self, value: u32) { if value < 2 { return; // Error, value specifies which pin A=0, B=1 @@ -570,7 +576,7 @@ impl gpio::ClientWithValue for MCP230xx<'_> { } } -impl gpio_async::Port for MCP230xx<'_> { +impl gpio_async::Port for MCP230xx<'_, I> { fn disable(&self, pin: usize) -> Result<(), ErrorCode> { // Best we can do is make this an input. self.set_direction(pin as u8, Direction::Input) diff --git a/capsules/src/mlx90614.rs b/capsules/extra/src/mlx90614.rs similarity index 80% rename from capsules/src/mlx90614.rs rename to capsules/extra/src/mlx90614.rs index c18822a425..07fc3a02bf 100644 --- a/capsules/src/mlx90614.rs +++ b/capsules/extra/src/mlx90614.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for the MLX90614 Infrared Thermometer. //! //! SMBus Interface @@ -5,7 +9,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! let mux_i2c = components::i2c::I2CMuxComponent::new(&earlgrey::i2c::I2C) //! .finalize(components::i2c_mux_component_helper!()); //! @@ -27,7 +31,7 @@ use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::utilities::registers::register_bitfields; use kernel::{ErrorCode, ProcessId}; -use crate::driver; +use capsules_core::driver; /// Syscall driver number. pub const DRIVER_NUM: usize = driver::NUM::Mlx90614 as usize; @@ -64,8 +68,8 @@ enum_from_primitive! { #[derive(Default)] pub struct App {} -pub struct Mlx90614SMBus<'a> { - smbus_temp: &'a dyn i2c::SMBusDevice, +pub struct Mlx90614SMBus<'a, S: i2c::SMBusDevice> { + smbus_temp: &'a S, temperature_client: OptionalCell<&'a dyn sensors::TemperatureClient>, buffer: TakeCell<'static, [u8]>, state: Cell, @@ -73,12 +77,12 @@ pub struct Mlx90614SMBus<'a> { owning_process: OptionalCell, } -impl<'a> Mlx90614SMBus<'_> { +impl<'a, S: i2c::SMBusDevice> Mlx90614SMBus<'a, S> { pub fn new( - smbus_temp: &'a dyn i2c::SMBusDevice, + smbus_temp: &'a S, buffer: &'static mut [u8], grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, - ) -> Mlx90614SMBus<'a> { + ) -> Mlx90614SMBus<'a, S> { Mlx90614SMBus { smbus_temp, temperature_client: OptionalCell::empty(), @@ -115,23 +119,19 @@ impl<'a> Mlx90614SMBus<'_> { } } -impl<'a> i2c::I2CClient for Mlx90614SMBus<'a> { +impl<'a, S: i2c::SMBusDevice> i2c::I2CClient for Mlx90614SMBus<'a, S> { fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) { match self.state.get() { State::Idle => { self.buffer.replace(buffer); } State::IsPresent => { - let present = if status == Ok(()) && buffer[0] == 60 { - true - } else { - false - }; + let present = status.is_ok() && buffer[0] == 60; self.owning_process.map(|pid| { - let _ = self.apps.enter(*pid, |_app, upcalls| { + let _ = self.apps.enter(pid, |_app, upcalls| { upcalls - .schedule_upcall(0, (if present { 1 } else { 0 }, 0, 0)) + .schedule_upcall(0, (usize::from(present), 0, 0)) .ok(); }); }); @@ -139,30 +139,26 @@ impl<'a> i2c::I2CClient for Mlx90614SMBus<'a> { self.state.set(State::Idle); } State::ReadAmbientTemp | State::ReadObjTemp => { - let mut temp: usize = 0; - - let values = if status == Ok(()) { + let values = match status { + Ok(()) => // Convert to centi celsius - temp = ((buffer[0] as usize | (buffer[1] as usize) << 8) * 2) - 27300; - self.temperature_client.map(|client| { - client.callback(temp as usize); - }); - true - } else { - self.temperature_client.map(|client| { - client.callback(0); - }); - false + { + Ok(((buffer[0] as usize | (buffer[1] as usize) << 8) * 2) as i32 - 27300) + } + Err(i2c_error) => Err(i2c_error.into()), }; - if values { + self.temperature_client.map(|client| { + client.callback(values); + }); + if let Ok(temp) = values { self.owning_process.map(|pid| { - let _ = self.apps.enter(*pid, |_app, upcalls| { - upcalls.schedule_upcall(0, (temp, 0, 0)).ok(); + let _ = self.apps.enter(pid, |_app, upcalls| { + upcalls.schedule_upcall(0, (temp as usize, 0, 0)).ok(); }); }); } else { self.owning_process.map(|pid| { - let _ = self.apps.enter(*pid, |_app, upcalls| { + let _ = self.apps.enter(pid, |_app, upcalls| { upcalls.schedule_upcall(0, (0, 0, 0)).ok(); }); }); @@ -174,7 +170,7 @@ impl<'a> i2c::I2CClient for Mlx90614SMBus<'a> { } } -impl<'a> SyscallDriver for Mlx90614SMBus<'a> { +impl<'a, S: i2c::SMBusDevice> SyscallDriver for Mlx90614SMBus<'a, S> { fn command( &self, command_num: usize, @@ -191,7 +187,7 @@ impl<'a> SyscallDriver for Mlx90614SMBus<'a> { // some (alive) process let match_or_empty_or_nonexistant = self.owning_process.map_or(true, |current_process| { self.apps - .enter(*current_process, |_, _| current_process == &process_id) + .enter(current_process, |_, _| current_process == process_id) .unwrap_or(true) }); if match_or_empty_or_nonexistant { @@ -239,7 +235,7 @@ impl<'a> SyscallDriver for Mlx90614SMBus<'a> { } } -impl<'a> sensors::TemperatureDriver<'a> for Mlx90614SMBus<'a> { +impl<'a, S: i2c::SMBusDevice> sensors::TemperatureDriver<'a> for Mlx90614SMBus<'a, S> { fn set_client(&self, temperature_client: &'a dyn sensors::TemperatureClient) { self.temperature_client.replace(temperature_client); } diff --git a/capsules/src/mx25r6435f.rs b/capsules/extra/src/mx25r6435f.rs similarity index 94% rename from capsules/src/mx25r6435f.rs rename to capsules/extra/src/mx25r6435f.rs index a5b7dcd38a..bcc125cd26 100644 --- a/capsules/src/mx25r6435f.rs +++ b/capsules/extra/src/mx25r6435f.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for the MX25R6435F flash chip. //! //! @@ -15,7 +19,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! # use capsules::virtual_alarm::VirtualMuxAlarm; //! @@ -59,12 +63,12 @@ use kernel::utilities::cells::OptionalCell; use kernel::utilities::cells::TakeCell; use kernel::ErrorCode; -pub static mut TXBUFFER: [u8; PAGE_SIZE as usize + 4] = [0; PAGE_SIZE as usize + 4]; -pub static mut RXBUFFER: [u8; PAGE_SIZE as usize + 4] = [0; PAGE_SIZE as usize + 4]; +pub const TX_BUF_LEN: usize = PAGE_SIZE as usize + 4; +pub const RX_BUF_LEN: usize = PAGE_SIZE as usize + 4; const SPI_SPEED: u32 = 8000000; -const SECTOR_SIZE: u32 = 4096; -const PAGE_SIZE: u32 = 256; +pub const SECTOR_SIZE: u32 = 4096; +pub const PAGE_SIZE: u32 = 256; /// This is a wrapper around a u8 array that is sized to a single page for the /// MX25R6435F. The page size is 4k because that is the smallest size that can @@ -72,18 +76,22 @@ const PAGE_SIZE: u32 = 256; /// /// An example looks like: /// -/// ``` +/// ```rust,ignore /// # use capsules::mx25r6435f::Mx25r6435fSector; /// /// static mut PAGEBUFFER: Mx25r6435fSector = Mx25r6435fSector::new(); /// ``` pub struct Mx25r6435fSector(pub [u8; SECTOR_SIZE as usize]); +impl Mx25r6435fSector { + pub const fn new() -> Self { + Self([0; SECTOR_SIZE as usize]) + } +} + impl Default for Mx25r6435fSector { fn default() -> Self { - Self { - 0: [0; SECTOR_SIZE as usize], - } + Self::new() } } @@ -167,7 +175,7 @@ enum State { pub struct MX25R6435F< 'a, - S: hil::spi::SpiMasterDevice + 'a, + S: hil::spi::SpiMasterDevice<'a> + 'a, P: hil::gpio::Pin + 'a, A: hil::time::Alarm<'a> + 'a, > { @@ -184,7 +192,7 @@ pub struct MX25R6435F< impl< 'a, - S: hil::spi::SpiMasterDevice + 'a, + S: hil::spi::SpiMasterDevice<'a> + 'a, P: hil::gpio::Pin + 'a, A: hil::time::Alarm<'a> + 'a, > MX25R6435F<'a, S, P, A> @@ -198,11 +206,11 @@ impl< hold_pin: Option<&'a P>, ) -> MX25R6435F<'a, S, P, A> { MX25R6435F { - spi: spi, - alarm: alarm, + spi, + alarm, state: Cell::new(State::Idle), - write_protect_pin: write_protect_pin, - hold_pin: hold_pin, + write_protect_pin, + hold_pin, txbuffer: TakeCell::new(txbuffer), rxbuffer: TakeCell::new(rxbuffer), client: OptionalCell::empty(), @@ -222,6 +230,8 @@ impl< ) } + /// Requests the readout of a 24-bit identification number. + /// This command will cause a debug print when succeeded. pub fn read_identification(&self) -> Result<(), ErrorCode> { self.configure_spi()?; @@ -352,7 +362,7 @@ impl< impl< 'a, - S: hil::spi::SpiMasterDevice + 'a, + S: hil::spi::SpiMasterDevice<'a> + 'a, P: hil::gpio::Pin + 'a, A: hil::time::Alarm<'a> + 'a, > hil::spi::SpiMasterClient for MX25R6435F<'a, S, P, A> @@ -369,7 +379,7 @@ impl< self.txbuffer.replace(write_buffer); read_buffer.map(|read_buffer| { debug!( - "id {:#x} {:#x} {:#x}", + "id 0x{:02x}{:02x}{:02x}", read_buffer[1], read_buffer[2], read_buffer[3] ); self.rxbuffer.replace(read_buffer); @@ -394,7 +404,7 @@ impl< self.rxbuffer.replace(read_buffer); self.client.map(move |client| { - client.read_complete(sector, hil::flash::Error::CommandComplete); + client.read_complete(sector, Ok(())); }); } else { let address = @@ -470,7 +480,7 @@ impl< self.state.set(State::Idle); self.txbuffer.replace(write_buffer); self.client.map(|client| { - client.erase_complete(hil::flash::Error::CommandComplete); + client.erase_complete(Ok(())); }); } State::WriteSectorWriteEnable { @@ -485,7 +495,7 @@ impl< self.txbuffer.replace(write_buffer); self.client.map(|client| { self.client_sector.take().map(|sector| { - client.write_complete(sector, hil::flash::Error::CommandComplete); + client.write_complete(sector, Ok(())); }); }); } else { @@ -569,7 +579,7 @@ impl< impl< 'a, - S: hil::spi::SpiMasterDevice + 'a, + S: hil::spi::SpiMasterDevice<'a> + 'a, P: hil::gpio::Pin + 'a, A: hil::time::Alarm<'a> + 'a, > hil::time::AlarmClient for MX25R6435F<'a, S, P, A> @@ -590,7 +600,7 @@ impl< impl< 'a, - S: hil::spi::SpiMasterDevice + 'a, + S: hil::spi::SpiMasterDevice<'a> + 'a, P: hil::gpio::Pin + 'a, A: hil::time::Alarm<'a> + 'a, C: hil::flash::Client, @@ -603,7 +613,7 @@ impl< impl< 'a, - S: hil::spi::SpiMasterDevice + 'a, + S: hil::spi::SpiMasterDevice<'a> + 'a, P: hil::gpio::Pin + 'a, A: hil::time::Alarm<'a> + 'a, > hil::flash::Flash for MX25R6435F<'a, S, P, A> diff --git a/capsules/src/net/frag_utils.rs b/capsules/extra/src/net/frag_utils.rs similarity index 94% rename from capsules/src/net/frag_utils.rs rename to capsules/extra/src/net/frag_utils.rs index b43a2e263e..0168ebdfa5 100644 --- a/capsules/src/net/frag_utils.rs +++ b/capsules/extra/src/net/frag_utils.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + const BITMAP_SIZE: usize = 20; pub struct Bitmap { diff --git a/capsules/src/net/icmpv6/icmpv6.rs b/capsules/extra/src/net/icmpv6/icmpv6.rs similarity index 97% rename from capsules/src/net/icmpv6/icmpv6.rs rename to capsules/extra/src/net/icmpv6/icmpv6.rs index 4e18adf528..05e8f1699f 100644 --- a/capsules/src/net/icmpv6/icmpv6.rs +++ b/capsules/extra/src/net/icmpv6/icmpv6.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! This file contains the types, structs and methods associated with the //! ICMPv6 header, including getter and setter methods and encode/decode //! functionality necessary for transmission. @@ -45,7 +49,7 @@ impl ICMP6Header { ICMP6Header { code: 0, cksum: 0, - options: options, + options, len: 0, } } diff --git a/capsules/src/net/icmpv6/icmpv6_send.rs b/capsules/extra/src/net/icmpv6/icmpv6_send.rs similarity index 91% rename from capsules/src/net/icmpv6/icmpv6_send.rs rename to capsules/extra/src/net/icmpv6/icmpv6_send.rs index 1cc69ab67a..7ed208a092 100644 --- a/capsules/src/net/icmpv6/icmpv6_send.rs +++ b/capsules/extra/src/net/icmpv6/icmpv6_send.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! This file contains the definition and implementation of a simple ICMPv6 //! sending interface. The [ICMP6Sender](trait.ICMP6Sender.html) trait provides //! an interface for an upper layer to send an ICMPv6 packet, and the @@ -14,7 +18,7 @@ use crate::net::ipv6::TransportHeader; use crate::net::network_capabilities::NetworkCapability; use kernel::utilities::cells::OptionalCell; -use kernel::utilities::leasable_buffer::LeasableBuffer; +use kernel::utilities::leasable_buffer::SubSliceMut; use kernel::ErrorCode; /// A trait for a client of an `ICMP6Sender`. @@ -66,7 +70,7 @@ pub struct ICMP6SendStruct<'a, T: IP6Sender<'a>> { impl<'a, T: IP6Sender<'a>> ICMP6SendStruct<'a, T> { pub fn new(ip_send_struct: &'a T) -> ICMP6SendStruct<'a, T> { ICMP6SendStruct { - ip_send_struct: ip_send_struct, + ip_send_struct, client: OptionalCell::empty(), } } @@ -88,7 +92,7 @@ impl<'a, T: IP6Sender<'a>> ICMP6Sender<'a> for ICMP6SendStruct<'a, T> { icmp_header.set_len(total_len as u16); let transport_header = TransportHeader::ICMP(icmp_header); self.ip_send_struct - .send_to(dest, transport_header, &LeasableBuffer::new(buf), net_cap) + .send_to(dest, transport_header, &SubSliceMut::new(buf), net_cap) } } diff --git a/capsules/src/net/icmpv6/mod.rs b/capsules/extra/src/net/icmpv6/mod.rs similarity index 63% rename from capsules/src/net/icmpv6/mod.rs rename to capsules/extra/src/net/icmpv6/mod.rs index a330f29d4c..53374f3882 100644 --- a/capsules/src/net/icmpv6/mod.rs +++ b/capsules/extra/src/net/icmpv6/mod.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + pub mod icmpv6_send; // Reexport the exports of the [`icmpv6`] module, to avoid redundant diff --git a/capsules/src/net/ieee802154.rs b/capsules/extra/src/net/ieee802154.rs similarity index 95% rename from capsules/src/net/ieee802154.rs rename to capsules/extra/src/net/ieee802154.rs index 13cfffa7ad..7cd640d8d9 100644 --- a/capsules/src/net/ieee802154.rs +++ b/capsules/extra/src/net/ieee802154.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implements IEEE 802.15.4-2015 header encoding and decoding. //! Supports the general MAC frame format, which encompasses data frames, beacon //! frames, MAC command frames, and the like. @@ -307,7 +311,8 @@ impl Security { let asn_in_nonce = (scf & security_control::ASN_IN_NONCE) != 0; // Frame counter field - let frame_counter_present = (scf & security_control::FRAME_COUNTER_SUPPRESSION) != 0; + // if frame counter suppresion is enabled, the frame counter field will not be in the header + let frame_counter_present = (scf & security_control::FRAME_COUNTER_SUPPRESSION) == 0; let (off, frame_counter) = if frame_counter_present { let (off, frame_counter_be) = dec_try!(buf, off; decode_u32); (off, Some(u32::from_be(frame_counter_be))) @@ -322,10 +327,10 @@ impl Security { stream_done!( off, Security { - level: level, - asn_in_nonce: asn_in_nonce, - frame_counter: frame_counter, - key_id: key_id, + level, + asn_in_nonce, + frame_counter, + key_id, } ); } @@ -346,19 +351,17 @@ mod ie_control { pub const TYPE: u16 = 0x8000; } -#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] pub enum HeaderIE<'a> { - Undissected { element_id: u8, content: &'a [u8] }, + Undissected { + element_id: u8, + content: &'a [u8], + }, + #[default] Termination1, Termination2, } -impl Default for HeaderIE<'_> { - fn default() -> Self { - HeaderIE::Termination1 - } -} - impl HeaderIE<'_> { pub fn is_termination(&self) -> bool { match *self { @@ -392,7 +395,7 @@ impl HeaderIE<'_> { stream_done!(off); } - pub fn decode<'b>(buf: &'b [u8]) -> SResult> { + pub fn decode(buf: &[u8]) -> SResult> { let (off, ie_ctl_be) = dec_try!(buf; decode_u16); let ie_ctl = u16::from_be(ie_ctl_be); @@ -408,8 +411,8 @@ impl HeaderIE<'_> { 0x7e => HeaderIE::Termination1, 0x7f => HeaderIE::Termination2, element_id => HeaderIE::Undissected { - element_id: element_id, - content: content, + element_id, + content, }, }; @@ -417,18 +420,16 @@ impl HeaderIE<'_> { } } -#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Debug, Default)] pub enum PayloadIE<'a> { - Undissected { group_id: u8, content: &'a [u8] }, + Undissected { + group_id: u8, + content: &'a [u8], + }, + #[default] Termination, } -impl Default for PayloadIE<'_> { - fn default() -> Self { - PayloadIE::Termination - } -} - impl PayloadIE<'_> { pub fn is_termination(&self) -> bool { match *self { @@ -458,7 +459,7 @@ impl PayloadIE<'_> { stream_done!(off); } - pub fn decode<'b>(buf: &'b [u8]) -> SResult> { + pub fn decode(buf: &[u8]) -> SResult> { let (off, ie_ctl_be) = dec_try!(buf; decode_u16); let ie_ctl = u16::from_be(ie_ctl_be); @@ -473,10 +474,7 @@ impl PayloadIE<'_> { let ie = match element_id { 0xf => PayloadIE::Termination, - group_id => PayloadIE::Undissected { - group_id: group_id, - content: content, - }, + group_id => PayloadIE::Undissected { group_id, content }, }; stream_done!(off + content_len, ie); @@ -769,20 +767,20 @@ impl Header<'_> { off, ( Header { - frame_type: frame_type, - frame_pending: frame_pending, - ack_requested: ack_requested, - version: version, - seq: seq, - dst_pan: dst_pan, - dst_addr: dst_addr, - src_pan: src_pan, - src_addr: src_addr, - security: security, - header_ies: header_ies, - header_ies_len: header_ies_len, - payload_ies: payload_ies, - payload_ies_len: payload_ies_len, + frame_type, + frame_pending, + ack_requested, + version, + seq, + dst_pan, + dst_addr, + src_pan, + src_addr, + security, + header_ies, + header_ies_len, + payload_ies, + payload_ies_len, }, mac_payload_off ) diff --git a/capsules/src/net/ipv6/ip_utils.rs b/capsules/extra/src/net/ipv6/ip_utils.rs similarity index 95% rename from capsules/src/net/ipv6/ip_utils.rs rename to capsules/extra/src/net/ipv6/ip_utils.rs index 77ea905691..a2f713e591 100644 --- a/capsules/src/net/ipv6/ip_utils.rs +++ b/capsules/extra/src/net/ipv6/ip_utils.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! This file implements various utilities used by the different components //! of the IP stack. Note that this file also contains the definition for the //! [IPAddr](struct.IPAddr.html) struct and associated helper functions. @@ -97,12 +101,12 @@ impl IPAddr { pub fn set_prefix(&mut self, prefix: &[u8], prefix_len: u8) { let full_bytes = (prefix_len / 8) as usize; let remaining = (prefix_len & 0x7) as usize; - let bytes = full_bytes + (if remaining != 0 { 1 } else { 0 }); + let bytes = full_bytes + usize::from(remaining != 0); assert!(bytes <= prefix.len() && bytes <= 16); self.0[0..full_bytes].copy_from_slice(&prefix[0..full_bytes]); if remaining != 0 { - let mask = (0xff as u8) << (8 - remaining); + let mask = 0xff_u8 << (8 - remaining); self.0[full_bytes] &= !mask; self.0[full_bytes] |= mask & prefix[full_bytes]; } @@ -176,7 +180,7 @@ pub fn compute_udp_checksum( //Finally, flip all bits sum = !sum; - sum = sum & 65535; //Remove upper 16 bits (which should be FFFF after flip) + sum &= 65535; //Remove upper 16 bits (which should be FFFF after flip) sum as u16 //Return result as u16 in host byte order */ } @@ -219,7 +223,7 @@ pub fn compute_icmp_checksum( } sum = !sum; - sum = sum & 0xffff; + sum &= 0xffff; sum as u16 } diff --git a/capsules/src/net/ipv6/ipv6.rs b/capsules/extra/src/net/ipv6/ipv6.rs similarity index 96% rename from capsules/src/net/ipv6/ipv6.rs rename to capsules/extra/src/net/ipv6/ipv6.rs index 8e5779e1b0..cf7787a4a3 100644 --- a/capsules/src/net/ipv6/ipv6.rs +++ b/capsules/extra/src/net/ipv6/ipv6.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! This file contains structs, traits, and methods associated with the IP layer //! of the networking stack. This includes the declaration and methods for the //! IP6Header, IP6Packet, and IP6Payload structs. These methods implement the @@ -74,7 +78,7 @@ use crate::net::stream::{encode_bytes, encode_u16, encode_u8}; use crate::net::tcp::TCPHeader; use crate::net::udp::UDPHeader; -use kernel::utilities::leasable_buffer::LeasableBuffer; +use kernel::utilities::leasable_buffer::SubSliceMut; use kernel::ErrorCode; pub const UDP_HDR_LEN: usize = 8; @@ -101,7 +105,7 @@ impl Default for IP6Header { version_class_flow: [version, 0, 0, 0], payload_len: 0, next_header: ip6_nh::NO_NEXT, - hop_limit: hop_limit, + hop_limit, src_addr: IPAddr::new(), dst_addr: IPAddr::new(), } @@ -269,7 +273,7 @@ impl IP6Header { udp_header.copy_from_slice(&buf[..UDP_HDR_LEN]); let checksum = match UDPHeader::decode(&udp_header).done() { Some((_offset, hdr)) => u16::from_be(compute_udp_checksum( - &self, + self, &hdr, buf.len() as u16, &buf[UDP_HDR_LEN..], @@ -288,7 +292,7 @@ impl IP6Header { let checksum = match ICMP6Header::decode(&icmp_header).done() { Some((_offset, mut hdr)) => { hdr.set_len(buf.len() as u16); - u16::from_be(compute_icmp_checksum(&self, &hdr, &buf[ICMP_HDR_LEN..])) + u16::from_be(compute_icmp_checksum(self, &hdr, &buf[ICMP_HDR_LEN..])) } None => 0xffff, //Will be dropped, as ones comp -0 checksum is invalid }; @@ -330,10 +334,7 @@ impl<'a> IPPayload<'a> { /// `header` - A `TransportHeader` for the `IPPayload` /// `payload` - A reference to a mutable buffer for the raw payload pub fn new(header: TransportHeader, payload: &'a mut [u8]) -> IPPayload<'a> { - IPPayload { - header: header, - payload: payload, - } + IPPayload { header, payload } } /// This function sets the payload for the `IPPayload`, and sets both the @@ -352,7 +353,7 @@ impl<'a> IPPayload<'a> { pub fn set_payload( &mut self, transport_header: TransportHeader, - payload: &LeasableBuffer<'static, u8>, + payload: &SubSliceMut<'static, u8>, ) -> (u8, u16) { for i in 0..payload.len() { self.payload[i] = payload[i]; @@ -378,7 +379,7 @@ impl<'a> IPPayload<'a> { /// /// # Arguments /// - /// `buf` - LeasableBuffer to write the serialized `IPPayload` to + /// `buf` - SubSliceMut to write the serialized `IPPayload` to /// `offset` - Current offset into the buffer /// /// # Return Value @@ -435,7 +436,7 @@ impl<'a> IP6Packet<'a> { pub fn new(payload: IPPayload<'a>) -> IP6Packet<'a> { IP6Packet { header: IP6Header::default(), - payload: payload, + payload, } } @@ -471,14 +472,14 @@ impl<'a> IP6Packet<'a> { TransportHeader::UDP(ref mut udp_header) => { let cksum = compute_udp_checksum( &self.header, - &udp_header, + udp_header, udp_header.get_len(), self.payload.payload, ); udp_header.set_cksum(cksum); } TransportHeader::ICMP(ref mut icmp_header) => { - let cksum = compute_icmp_checksum(&self.header, &icmp_header, self.payload.payload); + let cksum = compute_icmp_checksum(&self.header, icmp_header, self.payload.payload); icmp_header.set_cksum(cksum); } _ => { @@ -504,7 +505,7 @@ impl<'a> IP6Packet<'a> { pub fn set_payload( &mut self, transport_header: TransportHeader, - payload: &LeasableBuffer<'static, u8>, + payload: &SubSliceMut<'static, u8>, ) { let (next_header, payload_len) = self.payload.set_payload(transport_header, payload); self.header.set_next_header(next_header); diff --git a/capsules/src/net/ipv6/ipv6_recv.rs b/capsules/extra/src/net/ipv6/ipv6_recv.rs similarity index 95% rename from capsules/src/net/ipv6/ipv6_recv.rs rename to capsules/extra/src/net/ipv6/ipv6_recv.rs index 0bcad2d96d..43e75e0bc2 100644 --- a/capsules/src/net/ipv6/ipv6_recv.rs +++ b/capsules/extra/src/net/ipv6/ipv6_recv.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use crate::net::ipv6::IP6Header; use crate::net::sixlowpan::sixlowpan_state::SixlowpanRxClient; diff --git a/capsules/src/net/ipv6/ipv6_send.rs b/capsules/extra/src/net/ipv6/ipv6_send.rs similarity index 87% rename from capsules/src/net/ipv6/ipv6_send.rs rename to capsules/extra/src/net/ipv6/ipv6_send.rs index 299e2de324..9a6c55e66e 100644 --- a/capsules/src/net/ipv6/ipv6_send.rs +++ b/capsules/extra/src/net/ipv6/ipv6_send.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! This file contains the interface definition for sending an IPv6 packet. //! The [IP6Sender](trait.IP6Sender.html) trait provides an interface //! for sending IPv6 packets, while the [IP6SendClient](trait.IP6SendClient) trait @@ -22,13 +26,14 @@ use crate::net::ipv6::ip_utils::IPAddr; use crate::net::ipv6::{IP6Header, IP6Packet, TransportHeader}; use crate::net::network_capabilities::{IpVisibilityCapability, NetworkCapability}; use crate::net::sixlowpan::sixlowpan_state::TxState; +use crate::net::thread::thread_utils::{mac_from_ipv6, MULTICAST_IPV6}; use core::cell::Cell; use kernel::debug; use kernel::hil::time::{self, ConvertTicks}; use kernel::utilities::cells::{OptionalCell, TakeCell}; -use kernel::utilities::leasable_buffer::LeasableBuffer; +use kernel::utilities::leasable_buffer::SubSliceMut; use kernel::ErrorCode; /// This trait must be implemented by upper layers in order to receive @@ -85,7 +90,7 @@ pub trait IP6Sender<'a> { &self, dst: IPAddr, transport_header: TransportHeader, - payload: &LeasableBuffer<'static, u8>, + payload: &SubSliceMut<'static, u8>, net_cap: &'static NetworkCapability, ) -> Result<(), ErrorCode>; } @@ -131,18 +136,35 @@ impl<'a, A: time::Alarm<'a>> IP6Sender<'a> for IP6SendStruct<'a, A> { &self, dst: IPAddr, transport_header: TransportHeader, - payload: &LeasableBuffer<'static, u8>, + payload: &SubSliceMut<'static, u8>, net_cap: &'static NetworkCapability, ) -> Result<(), ErrorCode> { if !net_cap.remote_addr_valid(dst, self.ip_vis) { return Err(ErrorCode::FAIL); } - let _ = self.sixlowpan.init( - self.src_mac_addr, - self.dst_mac_addr, - self.radio.get_pan(), - None, - ); + + // This logic is used to update the dst mac address + // the given packet should be sent to. This complies + // with the manner in which Thread addresses packets, + // but may conflict with some other or future protocol + // that sits above and uses IPV6 + let dst_mac_addr; + if dst == MULTICAST_IPV6 { + // use short multicast ipv6 for dst mac address + dst_mac_addr = MacAddress::Short(0xFFFF) + } else if dst.0[0..8] == [0xfe, 0x80, 0, 0, 0, 0, 0, 0] { + // ipv6 address is of form fe80::MAC; use mac_from_ipv6 + // helper function to determine ipv6 to send to + dst_mac_addr = MacAddress::Long(mac_from_ipv6(dst)) + } else { + dst_mac_addr = self.dst_mac_addr; + } + + // TODO: add error handling here + let _ = self + .sixlowpan + .init(self.src_mac_addr, dst_mac_addr, self.radio.get_pan(), None); + self.init_packet(dst, transport_header, payload); let ret = self.send_next_fragment(); ret @@ -162,16 +184,16 @@ impl<'a, A: time::Alarm<'a>> IP6SendStruct<'a, A> { ) -> IP6SendStruct<'a, A> { IP6SendStruct { ip6_packet: TakeCell::new(ip6_packet), - alarm: alarm, + alarm, src_addr: Cell::new(IPAddr::new()), gateway: Cell::new(dst_mac_addr), tx_buf: TakeCell::new(tx_buf), - sixlowpan: sixlowpan, - radio: radio, - dst_mac_addr: dst_mac_addr, - src_mac_addr: src_mac_addr, + sixlowpan, + radio, + dst_mac_addr, + src_mac_addr, client: OptionalCell::empty(), - ip_vis: ip_vis, + ip_vis, } } @@ -179,7 +201,7 @@ impl<'a, A: time::Alarm<'a>> IP6SendStruct<'a, A> { &self, dst_addr: IPAddr, transport_header: TransportHeader, - payload: &LeasableBuffer<'static, u8>, + payload: &SubSliceMut<'static, u8>, ) { self.ip6_packet.map_or_else( || { diff --git a/capsules/src/net/ipv6/mod.rs b/capsules/extra/src/net/ipv6/mod.rs similarity index 69% rename from capsules/src/net/ipv6/mod.rs rename to capsules/extra/src/net/ipv6/mod.rs index 9470b10ad3..1e3c3bf376 100644 --- a/capsules/src/net/ipv6/mod.rs +++ b/capsules/extra/src/net/ipv6/mod.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + pub mod ip_utils; pub mod ipv6_recv; pub mod ipv6_send; diff --git a/capsules/src/net/mod.rs b/capsules/extra/src/net/mod.rs similarity index 61% rename from capsules/src/net/mod.rs rename to capsules/extra/src/net/mod.rs index 4c90f50f41..3b04890320 100644 --- a/capsules/src/net/mod.rs +++ b/capsules/extra/src/net/mod.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Modules for IPv6 over 6LoWPAN stack pub mod frag_utils; diff --git a/capsules/src/net/network_capabilities.rs b/capsules/extra/src/net/network_capabilities.rs similarity index 93% rename from capsules/src/net/network_capabilities.rs rename to capsules/extra/src/net/network_capabilities.rs index a661bc820d..fa88b06072 100644 --- a/capsules/src/net/network_capabilities.rs +++ b/capsules/extra/src/net/network_capabilities.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Capabilities for specifying capsule access to network resources //! //! A network capability specifies (1) with what IP addresses the holder of the @@ -45,7 +49,7 @@ impl AddrRange { let full_bytes: usize = prefix_len / 8; let remainder_bits: usize = prefix_len % 8; // initial bytes -- TODO: edge case - if &allowed_addr.0[0..full_bytes] != &addr.0[0..full_bytes] { + if allowed_addr.0[0..full_bytes] != addr.0[0..full_bytes] { false } else if remainder_bits == 0 { true //this case is necessary bc right shifting a u8 by 8 bits is UB @@ -73,7 +77,7 @@ impl PortRange { PortRange::Any => true, PortRange::NoPorts => false, PortRange::PortSet(allowed_ports) => allowed_ports.iter().any(|&p| p == port), // TODO: check refs - PortRange::Range(low, high) => (*low <= port && port <= *high), + PortRange::Range(low, high) => *low <= port && port <= *high, PortRange::Port(allowed_port) => port == *allowed_port, } } @@ -124,9 +128,9 @@ impl NetworkCapability { _create_net_cap: &dyn NetworkCapabilityCreationCapability, ) -> NetworkCapability { NetworkCapability { - remote_addrs: remote_addrs, - remote_ports: remote_ports, - local_ports: local_ports, + remote_addrs, + remote_ports, + local_ports, } } diff --git a/capsules/extra/src/net/sixlowpan/mod.rs b/capsules/extra/src/net/sixlowpan/mod.rs new file mode 100644 index 0000000000..2171900ac1 --- /dev/null +++ b/capsules/extra/src/net/sixlowpan/mod.rs @@ -0,0 +1,6 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +pub mod sixlowpan_compression; +pub mod sixlowpan_state; diff --git a/capsules/src/net/sixlowpan/sixlowpan_compression.rs b/capsules/extra/src/net/sixlowpan/sixlowpan_compression.rs similarity index 97% rename from capsules/src/net/sixlowpan/sixlowpan_compression.rs rename to capsules/extra/src/net/sixlowpan/sixlowpan_compression.rs index 0e91cfd4bc..23329030a0 100644 --- a/capsules/src/net/sixlowpan/sixlowpan_compression.rs +++ b/capsules/extra/src/net/sixlowpan/sixlowpan_compression.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use crate::net::ieee802154::MacAddress; use crate::net::ipv6::ip_utils::{compute_udp_checksum, ip6_nh, IPAddr}; use crate::net::ipv6::{IP6Header, IP6Packet, TransportHeader}; @@ -7,7 +11,6 @@ use crate::net::util::{network_slice_to_u16, u16_to_network_slice}; /// Implements the 6LoWPAN specification for sending IPv6 datagrams over /// 802.15.4 packets efficiently, as detailed in RFC 6282. use core::mem; -use core::result::Result; /// Contains bit masks and constants related to the two-byte header of the /// LoWPAN_IPHC encoding format. @@ -109,15 +112,15 @@ pub trait ContextStore { /// Computes the LoWPAN Interface Identifier from either the 16-bit short MAC or /// the IEEE EUI-64 that is derived from the 48-bit MAC. pub fn compute_iid(mac_addr: &MacAddress) -> [u8; 8] { - match mac_addr { - &MacAddress::Short(short_addr) => { + match *mac_addr { + MacAddress::Short(short_addr) => { // IID is 0000:00ff:fe00:XXXX, where XXXX is 16-bit MAC let mut iid: [u8; 8] = iphc::MAC_BASE; iid[6] = (short_addr >> 1) as u8; iid[7] = (short_addr & 0xff) as u8; iid } - &MacAddress::Long(long_addr) => { + MacAddress::Long(long_addr) => { // IID is IEEE EUI-64 with universal/local bit inverted let mut iid: [u8; 8] = long_addr; iid[0] ^= iphc::MAC_UL; @@ -188,7 +191,7 @@ pub fn compress<'a>( ip6_packet: &'a IP6Packet<'a>, src_mac_addr: MacAddress, dst_mac_addr: MacAddress, - mut buf: &mut [u8], + buf: &mut [u8], ) -> Result<(usize, usize), ()> { // Note that consumed should be constant, and equal sizeof(IP6Header) //let (mut consumed, ip6_header) = IP6Header::decode(ip6_datagram).done().ok_or(())?; @@ -222,38 +225,38 @@ pub fn compress<'a>( dst_ctx = dst_ctx.and_then(|ctx| if ctx.compress { Some(ctx) } else { None }); // Context Identifier Extension - compress_cie(&src_ctx, &dst_ctx, &mut buf, &mut written); + compress_cie(&src_ctx, &dst_ctx, buf, &mut written); // Traffic Class & Flow Label - compress_tf(&ip6_header, &mut buf, &mut written); + compress_tf(&ip6_header, buf, &mut written); // Next Header //let (mut is_nhc, mut nh_len): (bool, u8) = is_ip6_nh_compressible(ip6_packet)?; let is_nhc = ip6_header.next_header == ip6_nh::UDP; - compress_nh(&ip6_header, is_nhc, &mut buf, &mut written); + compress_nh(&ip6_header, is_nhc, buf, &mut written); // Hop Limit - compress_hl(&ip6_header, &mut buf, &mut written); + compress_hl(&ip6_header, buf, &mut written); // Source Address compress_src( &ip6_header.src_addr, &src_mac_addr, &src_ctx, - &mut buf, + buf, &mut written, ); // Destination Address if ip6_header.dst_addr.is_multicast() { - compress_multicast(&ip6_header.dst_addr, &dst_ctx, &mut buf, &mut written); + compress_multicast(&ip6_header.dst_addr, &dst_ctx, buf, &mut written); } else { compress_dst( &ip6_header.dst_addr, &dst_mac_addr, &dst_ctx, - &mut buf, + buf, &mut written, ); } @@ -272,8 +275,8 @@ pub fn compress<'a>( written += 1; // Compress ports and checksum - nhc_header |= compress_udp_ports(&udp_header, &mut buf, &mut written); - nhc_header |= compress_udp_checksum(&udp_header, &mut buf, &mut written); + nhc_header |= compress_udp_ports(&udp_header, buf, &mut written); + nhc_header |= compress_udp_checksum(&udp_header, buf, &mut written); // Write the UDP LoWPAN_NHC byte buf[udp_nh_offset] = nhc_header; @@ -617,16 +620,16 @@ pub fn decompress( let mut written: usize = mem::size_of::(); // Decompress CID and CIE fields if they exist - let (src_ctx, dst_ctx) = decompress_cie(ctx_store, iphc_header_1, &buf, &mut consumed)?; + let (src_ctx, dst_ctx) = decompress_cie(ctx_store, iphc_header_1, buf, &mut consumed)?; // Traffic Class & Flow Label - decompress_tf(&mut ip6_header, iphc_header_1, &buf, &mut consumed); + decompress_tf(&mut ip6_header, iphc_header_1, buf, &mut consumed); // Next Header - let (mut is_nhc, mut next_header) = decompress_nh(iphc_header_1, &buf, &mut consumed); + let (mut is_nhc, mut next_header) = decompress_nh(iphc_header_1, buf, &mut consumed); // Hop Limit - decompress_hl(&mut ip6_header, iphc_header_1, &buf, &mut consumed)?; + decompress_hl(&mut ip6_header, iphc_header_1, buf, &mut consumed)?; // Source Address decompress_src( @@ -634,26 +637,20 @@ pub fn decompress( iphc_header_2, &src_mac_addr, &src_ctx, - &buf, + buf, &mut consumed, )?; // Destination Address if (iphc_header_2 & iphc::MULTICAST) != 0 { - decompress_multicast( - &mut ip6_header, - iphc_header_2, - &dst_ctx, - &buf, - &mut consumed, - )?; + decompress_multicast(&mut ip6_header, iphc_header_2, &dst_ctx, buf, &mut consumed)?; } else { decompress_dst( &mut ip6_header, iphc_header_2, &dst_mac_addr, &dst_ctx, - &buf, + buf, &mut consumed, )?; } @@ -674,7 +671,7 @@ pub fn decompress( consumed += 1; // Scoped mutable borrow of out_buf - let mut next_headers: &mut [u8] = &mut out_buf[written..]; + let next_headers: &mut [u8] = &mut out_buf[written..]; match next_header { ip6_nh::IP6 => { @@ -683,7 +680,7 @@ pub fn decompress( &buf[consumed..], src_mac_addr, dst_mac_addr, - &mut next_headers, + next_headers, dgram_size, is_fragment, )?; @@ -702,7 +699,7 @@ pub fn decompress( // Decompress UDP header fields let consumed_before_port_decompress = consumed; - let (src_port, dst_port) = decompress_udp_ports(nhc_header, &buf, &mut consumed); + let (src_port, dst_port) = decompress_udp_ports(nhc_header, buf, &mut consumed); //need to add any growth from decompression to the udp length if we used the buf //len to calculate the length @@ -733,7 +730,7 @@ pub fn decompress( &next_headers[0..8], udp_length, &ip6_header, - &buf, + buf, &mut consumed, is_fragment, ); diff --git a/capsules/src/net/sixlowpan/sixlowpan_state.rs b/capsules/extra/src/net/sixlowpan/sixlowpan_state.rs similarity index 94% rename from capsules/src/net/sixlowpan/sixlowpan_state.rs rename to capsules/extra/src/net/sixlowpan/sixlowpan_state.rs index 600bd1d7d8..366cdb2cb5 100644 --- a/capsules/src/net/sixlowpan/sixlowpan_state.rs +++ b/capsules/extra/src/net/sixlowpan/sixlowpan_state.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! 6loWPAN (IPv6 over Low-Power Wireless Networks) is standard for compressing //! and fragmenting IPv6 packets over low power wireless networks, particularly //! ones with MTUs (Minimum Transmission Units) smaller than 1280 octets, like @@ -219,7 +223,7 @@ // // Issues: // -// * On imix, the reciever sometimes fails to receive a fragment. This +// * On imix, the receiver sometimes fails to receive a fragment. This // occurs below the Mac layer, and prevents the packet from being fully // reassembled. // @@ -250,7 +254,7 @@ const FRAG_TIMEOUT: u32 = 60; /// for the [Sixlowpan](struct.Sixlowpan.html) struct, and will then receive /// a callback once an IPv6 packet has been fully reassembled. pub trait SixlowpanRxClient { - fn receive<'a>(&self, buf: &'a [u8], len: usize, result: Result<(), ErrorCode>); + fn receive(&self, buf: &[u8], len: usize, result: Result<(), ErrorCode>); } pub mod lowpan_frag { @@ -338,7 +342,7 @@ impl<'a> TxState<'a> { /// global state for the entire Sixlowpan layer. pub fn new(sixlowpan: &'a dyn SixlowpanState<'a>) -> TxState<'a> { TxState { - // Externally setable fields + // Externally settable fields src_pan: Cell::new(0), dst_pan: Cell::new(0), src_mac_addr: Cell::new(MacAddress::Short(0)), @@ -351,7 +355,7 @@ impl<'a> TxState<'a> { dgram_offset: Cell::new(0), busy: Cell::new(false), - sixlowpan: sixlowpan, + sixlowpan, } } @@ -474,7 +478,7 @@ impl<'a> TxState<'a> { ) -> Result, &'static mut [u8])> { // Here, we assume that the compressed headers fit in the first MTU // fragment. This is consistent with RFC 6282. - let mut lowpan_packet = [0 as u8; radio::MAX_FRAME_SIZE as usize]; + let mut lowpan_packet = [0_u8; radio::MAX_FRAME_SIZE]; let (consumed, written) = { match sixlowpan_compression::compress( ctx_store, @@ -483,7 +487,7 @@ impl<'a> TxState<'a> { self.dst_mac_addr.get(), &mut lowpan_packet, ) { - Err(_) => return Err((Err(ErrorCode::FAIL), frame.into_buf())), + Err(()) => return Err((Err(ErrorCode::FAIL), frame.into_buf())), Ok(result) => result, } }; @@ -576,7 +580,7 @@ impl<'a> TxState<'a> { // statically allocate room on the stack. However, we do not know // how many additional headers we have until runtime. This // functionality should be fixed in the future. - let mut headers = [0 as u8; 60]; + let mut headers = [0_u8; 60]; ip6_packet.encode(&mut headers); let _ = frame.append_payload(&headers[dgram_offset..dgram_offset + headers_to_write]); payload_len -= headers_to_write; @@ -587,7 +591,7 @@ impl<'a> TxState<'a> { fn write_frag_hdr(&self, frame: &mut Frame, first_frag: bool) -> usize { if first_frag { - let mut frag_header = [0 as u8; lowpan_frag::FRAG1_HDR_SIZE]; + let mut frag_header = [0_u8; lowpan_frag::FRAG1_HDR_SIZE]; set_frag_hdr( self.dgram_size.get(), self.dgram_tag.get(), @@ -600,7 +604,7 @@ impl<'a> TxState<'a> { let _ = frame.append_payload(&frag_header); lowpan_frag::FRAG1_HDR_SIZE } else { - let mut frag_header = [0 as u8; lowpan_frag::FRAGN_HDR_SIZE]; + let mut frag_header = [0_u8; lowpan_frag::FRAGN_HDR_SIZE]; set_frag_hdr( self.dgram_size.get(), self.dgram_tag.get(), @@ -720,18 +724,18 @@ impl<'a> RxState<'a> { dgram_offset: usize, ctx_store: &dyn ContextStore, ) -> Result> { - let mut packet = self.packet.take().ok_or(Err(ErrorCode::NOMEM))?; + let packet = self.packet.take().ok_or(Err(ErrorCode::NOMEM))?; let uncompressed_len = if dgram_offset == 0 { let (consumed, written) = sixlowpan_compression::decompress( ctx_store, - &payload[0..payload_len as usize], + &payload[0..payload_len], self.src_mac_addr.get(), self.dst_mac_addr.get(), - &mut packet, + packet, dgram_size, true, ) - .map_err(|_| Err(ErrorCode::FAIL))?; + .map_err(|()| Err(ErrorCode::FAIL))?; let remaining = payload_len - consumed; packet[written..written + remaining] .copy_from_slice(&payload[consumed..consumed + remaining]); @@ -770,7 +774,7 @@ impl<'a> RxState<'a> { // and thus the packet should always be here. self.packet .map(|packet| { - client.receive(&packet, self.dgram_size.get() as usize, result); + client.receive(packet, self.dgram_size.get() as usize, result); }) .unwrap(); // Unwrap fail = Error: `packet` is None in call to end_receive. }); @@ -801,7 +805,14 @@ pub struct Sixlowpan<'a, A: time::Alarm<'a>, C: ContextStore> { // This function is called after receiving a frame impl<'a, A: time::Alarm<'a>, C: ContextStore> RxClient for Sixlowpan<'a, A, C> { - fn receive<'b>(&self, buf: &'b [u8], header: Header<'b>, data_offset: usize, data_len: usize) { + fn receive<'b>( + &self, + buf: &'b [u8], + header: Header<'b>, + _lqi: u8, + data_offset: usize, + data_len: usize, + ) { // We return if retcode is not valid, as it does not make sense to issue // a callback for an invalid frame reception // TODO: Handle the case where the addresses are None/elided - they @@ -868,8 +879,8 @@ impl<'a, A: time::Alarm<'a>, C: ContextStore> Sixlowpan<'a, A, C> { /// have an accuracy of at least 60 seconds. pub fn new(ctx_store: C, clock: &'a A) -> Sixlowpan<'a, A, C> { Sixlowpan { - ctx_store: ctx_store, - clock: clock, + ctx_store, + clock, tx_dgram_tag: Cell::new(0), rx_client: Cell::new(None), @@ -901,7 +912,7 @@ impl<'a, A: time::Alarm<'a>, C: ContextStore> Sixlowpan<'a, A, C> { dgram_offset, ) } else { - self.receive_single_packet(&packet, packet_len, src_mac_addr, dst_mac_addr) + self.receive_single_packet(packet, packet_len, src_mac_addr, dst_mac_addr) } } @@ -927,32 +938,35 @@ impl<'a, A: time::Alarm<'a>, C: ContextStore> Sixlowpan<'a, A, C> { // The packet buffer should *always* be there; in particular, // since this state is not busy, it must have the packet buffer. // Otherwise, we are in an inconsistent state and can fail. - let mut packet = state.packet.take().unwrap(); - if is_lowpan(payload) { - let decompressed = sixlowpan_compression::decompress( - &self.ctx_store, - &payload[0..payload_len as usize], - src_mac_addr, - dst_mac_addr, - &mut packet, - 0, - false, - ); - match decompressed { - Ok((consumed, written)) => { - let remaining = payload_len - consumed; - packet[written..written + remaining] - .copy_from_slice(&payload[consumed..consumed + remaining]); - // Want dgram_size to contain decompressed size of packet - state.dgram_size.set((written + remaining) as u16); - } - Err(_) => { - return (None, Err(ErrorCode::FAIL)); - } + let packet = state.packet.take().unwrap(); + + // Filter non 6LoWPAN packets and return + if !is_lowpan(payload) { + return (None, Ok(())); + } + + let decompressed = sixlowpan_compression::decompress( + &self.ctx_store, + &payload[0..payload_len], + src_mac_addr, + dst_mac_addr, + packet, + 0, + false, + ); + match decompressed { + Ok((consumed, written)) => { + let remaining = payload_len - consumed; + packet[written..written + remaining] + .copy_from_slice(&payload[consumed..consumed + remaining]); + // Want dgram_size to contain decompressed size of packet + state.dgram_size.set((written + remaining) as u16); + } + Err(()) => { + return (None, Err(ErrorCode::FAIL)); } - } else { - packet[0..payload_len].copy_from_slice(&payload[0..payload_len]); } + state.packet.replace(packet); (Some(state), Ok(())) }) diff --git a/capsules/src/net/stream.rs b/capsules/extra/src/net/stream.rs similarity index 97% rename from capsules/src/net/stream.rs rename to capsules/extra/src/net/stream.rs index 9c8d215d81..6995418ccc 100644 --- a/capsules/src/net/stream.rs +++ b/capsules/extra/src/net/stream.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + #[derive(Debug)] pub enum SResult { // `Done(off, out)`: No errors encountered. We are currently at `off` in the @@ -101,7 +105,8 @@ macro_rules! stream_cond { }; } -/// Gets the result of an Option, throwing a stream error if it is None +/// Gets the result of an `Option`, throwing a stream error if it +/// is `None` #[macro_export] macro_rules! stream_from_option { ($opt:expr, $err:expr) => { @@ -130,7 +135,7 @@ macro_rules! stream_from_option { /// would result in it defaulting to 0. Idiomatically, the way to combine /// encoders is to define another encoder as follows: /// -/// ```rust +/// ```rust,ignore /// # use capsules::{enc_try, stream_done}; /// # use capsules::net::stream::SResult; /// @@ -152,7 +157,7 @@ macro_rules! stream_from_option { /// /// Then, using an encoder can be done simply by: /// -/// ``` +/// ```rust,ignore /// # use capsules::net::stream::SResult; /// /// match encoder(&mut buf) { diff --git a/capsules/src/net/tcp.rs b/capsules/extra/src/net/tcp.rs similarity index 85% rename from capsules/src/net/tcp.rs rename to capsules/extra/src/net/tcp.rs index 835b9ee759..97462ca8d8 100644 --- a/capsules/src/net/tcp.rs +++ b/capsules/extra/src/net/tcp.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + /* Though TCP has not yet been implemented for the Tock Networking stackm this file defines the structure of the TCPHeader and TCPPacket structs so that TCPPacket can be included for clarity as part of the diff --git a/capsules/extra/src/net/thread/driver.rs b/capsules/extra/src/net/thread/driver.rs new file mode 100644 index 0000000000..16d085d266 --- /dev/null +++ b/capsules/extra/src/net/thread/driver.rs @@ -0,0 +1,742 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! This file contains the structs and methods associated with the Thread +//! networking layer. This represents a first attempt in Tock +//! to support Thread networking. The current implementation successfully +//! joins a Tock device as a child node to a Thread parent (tested using +//! OpenThread). This Thread capsule is a client to the UDP Mux. +//! The associated ThreadNetwork struct must be created in the `thread_network.rs` +//! component. +//! +//! The Userland interface is incredibly simple at this juncture. An application +//! can begin the Thread child/parent joining by issuing a syscall command +//! with the MLE/MAC key as an argument. Only one userspace application can use/join +//! the Thread network. Once a userspace application has joined the Thread network, +//! the Thread network is considered locked. After the Thread network +//! is "locked", other userspace applications attempting to join the network +//! will return a failure. This is temporary and will eventually be replaced. + +// ------------------------------------------------------------------------------ +// Current Limitations +// ------------------------------------------------------------------------------ +// (1) A majority of the TLV fields used in the parent request/child id request +// are hardcoded. Future implementations need to provide options for specifying +// varied security policies. +// (2) Current implementation joins the Thread network sucessfully and consistently +// but does not send update/heart beat messages to the parent prior to the child +// timing out. +// (3) Currently no support for sending UDP messages across Thread interface. The +// current interface is unusable for sending data. It can only be used to +// join a network. + +use crate::ieee802154::framer::{self, get_ccm_nonce}; +use crate::net::ieee802154::{KeyId, MacAddress, Security, SecurityLevel}; +use crate::net::ipv6::ip_utils::IPAddr; +use crate::net::network_capabilities::NetworkCapability; + +use crate::net::ieee802154; +use crate::net::thread::thread_utils::generate_src_ipv6; +use crate::net::thread::thread_utils::ThreadState; +use crate::net::thread::thread_utils::MULTICAST_IPV6; +use crate::net::thread::thread_utils::THREAD_PORT_NUMBER; +use crate::net::thread::thread_utils::{ + encode_cryp_data, form_child_id_req, form_parent_req, mac_from_ipv6, MleCommand, NetworkKey, + AUTH_DATA_LEN, AUX_SEC_HEADER_LENGTH, IPV6_LEN, SECURITY_SUITE_LEN, +}; +use crate::net::udp::udp_port_table::UdpPortManager; +use crate::net::udp::udp_recv::UDPRecvClient; +use crate::net::udp::udp_send::{UDPSendClient, UDPSender}; +use capsules_core::driver; + +use core::cell::Cell; + +use kernel::capabilities::UdpDriverCapability; +use kernel::errorcode::into_statuscode; +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::hil::symmetric_encryption::CCMClient; +use kernel::hil::symmetric_encryption::AES128CCM; +use kernel::hil::time; +use kernel::processbuffer::ReadableProcessBuffer; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::utilities::cells::MapCell; +use kernel::utilities::leasable_buffer::SubSliceMut; +use kernel::{ErrorCode, ProcessId}; + +const SECURITY_SUITE_ENCRYP: u8 = 0; +pub const DRIVER_NUM: usize = driver::NUM::Thread as usize; + +/// Ids for read-only allow buffers +mod ro_allow { + pub const WRITE: usize = 0; + /// The number of allow buffers the kernel stores for this grant + pub const COUNT: u8 = 1; +} + +// /// Ids for read-write allow buffers +// mod rw_allow { +// pub const READ: usize = 0; +// pub const CFG: usize = 1; +// pub const RX_CFG: usize = 2; +// /// The number of allow buffers the kernel stores for this grant +// pub const COUNT: u8 = 3; +// } + +/// IDs for subscribed upcalls. +mod upcall { + pub const JOINCOMPLETE: usize = 0; +} + +#[derive(Default)] +pub struct App {} + +#[allow(dead_code)] +pub struct ThreadNetworkDriver<'a, A: time::Alarm<'a>> { + /// UDP sender + sender: &'a dyn UDPSender<'a>, + + /// AES crypto engine for MLE encryption + aes_crypto: &'a dyn AES128CCM<'a>, + + /// Alarm for timeouts + alarm: &'a A, + + /// Grant of apps that use this thread driver. + apps: Grant, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>, + + /// mac address of device + src_mac_addr: [u8; 8], + + /// Maximum length payload that an app can transmit via this driver + max_tx_pyld_len: usize, + + /// UDP bound port table (manages kernel bindings) + port_table: &'static UdpPortManager, + + /// kernel buffer used for sending + send_buffer: MapCell>, + + /// kernel buffer used for receiving + recv_buffer: MapCell>, + + /// state machine for the Thread device + state: MapCell, + + /// UDP driver capability + driver_send_cap: &'static dyn UdpDriverCapability, + + /// Network capability + net_cap: &'static NetworkCapability, + + /// Frame counter for Thread MLE + frame_count: Cell, + + /// Stored Thread network containing mac/MLE key + networkkey: MapCell, + + /// Length of the message passed to the crypto engine + crypto_sizelock: MapCell, +} + +// Note: For now, we initialize the Thread state as empty. +// We replace the Thread state when the first userspace +// application calls the Thread capsule to initiate a Thread network. +// This serves to "lock" the Thread capsule to only one application. +// For now, Tock only supports one application using the Thread network. +// After the network is "locked" to one application, other userspace +// applications requesting to join a Thread network will fail. +impl<'a, A: time::Alarm<'a>> ThreadNetworkDriver<'a, A> { + pub fn new( + sender: &'a dyn UDPSender<'a>, + aes_crypto: &'a dyn AES128CCM<'a>, + alarm: &'a A, + grant: Grant, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>, + src_mac_addr: [u8; 8], + max_tx_pyld_len: usize, + port_table: &'static UdpPortManager, + send_buffer: SubSliceMut<'static, u8>, + recv_buffer: SubSliceMut<'static, u8>, + driver_send_cap: &'static dyn UdpDriverCapability, + net_cap: &'static NetworkCapability, + ) -> ThreadNetworkDriver<'a, A> { + ThreadNetworkDriver { + sender, + aes_crypto, + alarm, + apps: grant, + src_mac_addr, + max_tx_pyld_len, + port_table, + send_buffer: MapCell::new(send_buffer), + recv_buffer: MapCell::new(recv_buffer), + state: MapCell::empty(), + driver_send_cap, + net_cap, + frame_count: Cell::new(5), + networkkey: MapCell::empty(), + crypto_sizelock: MapCell::empty(), + } + } + + /// Takes the MLE and MAC keys and replaces the networkkey + pub fn set_networkkey(&self, mle_key: [u8; 16], mac_key: [u8; 16]) { + self.networkkey.replace(NetworkKey { mle_key, mac_key }); + } + + fn send_parent_req(&self) { + // UNCOMMENT TO DEBUG THREAD // + // kernel::debug!("[Thread] Sending parent request..."); + + // Panicking on unwrap indicates the state was taken without replacement + // (unreachable with proper state machine implementation) + let curr_state = self.state.take().unwrap(); + + match curr_state { + ThreadState::Detached => { + // A parent request can only begin from a detached state. We utilize + // helper functions to form the request and send the parent request + // to the multicast IP/Mac Address + self.state.replace(ThreadState::SendParentReq); + let parent_req_mle = form_parent_req(); + let src_ipv6 = generate_src_ipv6(&self.src_mac_addr); + self.thread_mle_send(&parent_req_mle, MULTICAST_IPV6, src_ipv6) + .err() + .map(|code| { + // Thread send failed sending parent req so we terminate and return + // to a detached state. + self.state.replace(ThreadState::Detached); + + // UNCOMMENT TO DEBUG THREAD // + // kernel::debug!( + // "[Thread] Failed sending MLE parent request - crypto operation error." + // ); + self.terminate_child_join(Err(code)); + }); + } + ThreadState::SEDActive(_, _) + | ThreadState::SendUpdate(_, _) + | ThreadState::SendUDPMsg => { + // These states constitute a device that has previously sucessfully + // joined the network. There is no need to issue a new parent request. + // Replace state, and terminate. + self.state.replace(curr_state); + self.terminate_child_join(Err(ErrorCode::ALREADY)); + } + _ => { + // All other Thread states indicate that the thread device has already + // begun the process of connecting to a parent device. Terminate the parent request. + // Replace state, and terminate. + self.state.replace(curr_state); + self.terminate_child_join(Err(ErrorCode::BUSY)); + } + }; + } + + fn thread_mle_send( + &self, + mle_buf: &[u8], + dest_addr: IPAddr, + src_addr: IPAddr, + ) -> Result<(), ErrorCode> { + // TODO: Hardcoded encryption suite and auxiliary security; add support to send encrypted/unencrypted MLE + + // We hardcode the auxiliary security for now + let security = Security { + level: SecurityLevel::EncMic32, + asn_in_nonce: false, + frame_counter: Some(self.frame_count.get()), + key_id: KeyId::Source4Index([0, 0, 0, 0], 1), + }; + + // Begin cryptographic and sending procedure for the MLE message + self.send_buffer + .take() + .map_or(Err(ErrorCode::NOMEM), |send_buffer| { + self.perform_crypt_op(src_addr, dest_addr, security, mle_buf, send_buffer.take()) + .map_err(|(code, buf)| { + // Error occured with cryptographic operation, replace buffer + // for future transmissions and return error code + self.send_buffer.replace(SubSliceMut::new(buf)); + code + }) + }) + } + + fn recv_logic(&self, sender_ip: IPAddr) -> Result<(), ErrorCode> { + // This function is called once the received MLE payload has been placed + // into the recv_buffer. The function handles the message and responds accordingly + + self.recv_buffer + .take() + .map_or(Err(ErrorCode::NOMEM), |mut recv_buf| { + if recv_buf[0] == MleCommand::ParentResponse as u8 { + // Received Parent Response -> form Child ID Request + + // UNCOMMENT TO DEBUG THREAD // + // kernel::debug!("[Thread] Received Parent Response."); + // kernel::debug!("[Thread] Sending Child ID Request..."); + + let src_ipv6 = generate_src_ipv6(&self.src_mac_addr); + + let (output, offset) = + form_child_id_req(recv_buf.as_slice(), self.frame_count.get())?; + + // Advance state machine + self.state.replace(ThreadState::SendChildIdReq(sender_ip)); + + self.thread_mle_send(&output[..offset], sender_ip, src_ipv6)?; + } else if recv_buf[0] == MleCommand::ChildIdResponse as u8 { + // Receive child id response -> advance state machine + self.state.replace(ThreadState::SEDActive( + sender_ip, + MacAddress::Long(mac_from_ipv6(sender_ip)), + )); + + // TODO: once heart beats are implemented, we will set + // the timer here (as seen below) + // let curr_time = self.alarm.now(); + // self.alarm.set_alarm( + // curr_time, + // time::ConvertTicks::ticks_from_seconds(self.alarm, 5), + // ); + } + + recv_buf.reset(); + self.recv_buffer.replace(recv_buf); + Ok(()) + }) + } + + fn terminate_child_join(&self, res: Result<(), ErrorCode>) { + // Function to schedule upcall to userland on parent request termination. Notifies + // userland of the reason for termination with the first argument. + + self.apps.each(|_, _, kernel_data| { + kernel_data + .schedule_upcall(upcall::JOINCOMPLETE, (into_statuscode(res), 0, 0)) + .ok(); + }); + } + + fn perform_crypt_op( + &self, + src_addr: IPAddr, + dst_addr: IPAddr, + security: Security, + payload: &[u8], + buf: &'static mut [u8], + ) -> Result<(), (ErrorCode, &'static mut [u8])> { + // Wrapper function for performing the AES-128CCM encryption. This function generates the nonce, + // sets the nonce/key for the crypto engine, generates the authenticated data, and initiates + // the crypto operation. + + // Note: The payload argument does not include aux sec header + + // Obtain and unwrap frame counter + let frame_counter = security.frame_counter; + if frame_counter.is_none() { + // UNCOMMENT TO DEBUG THREAD // + // kernel::debug!("[Thread] Malformed auxiliary security header"); + return Err((ErrorCode::INVAL, buf)); + } + + // Generate nonce, obtain network key and set crypto engine accordingly + let nonce = get_ccm_nonce( + &mac_from_ipv6(src_addr), + frame_counter.unwrap(), + security.level, + ); + let mle_key = self.networkkey.get(); + let mic_len = security.level.mic_len(); + match mle_key { + Some(netkey) => { + if self.aes_crypto.set_key(&netkey.mle_key).is_err() + || self.aes_crypto.set_nonce(&nonce).is_err() + { + // UNCOMMENT TO DEBUG THREAD // + // kernel::debug!("[Thread] Failure setting networkkey and/or nonce."); + return Err((ErrorCode::FAIL, buf)); + } + } + None => { + // UNCOMMENT TO DEBUG THREAD // + // kernel::debug!("[Thread] Attempt to access networkkey when no networkkey set."); + return Err((ErrorCode::NOSUPPORT, buf)); + } + } + + // Thread MLE security utilizes the AES128 CCM security used by 802.15.4 link layer security. + // Notably, there are a few minor modifications. AES128 requires authentication data (a data) + // and the secured message data (m data). Together, the a data is used to encrypt the m data + // while also generating a message integrity code (MIC). Thread subtly changes the + // a data from the 802.15.4 specification. For Thread MLE, the a data consists of a concatenation + // of the IP source address || IP destination address || auxiliary security header. It is especially important + // to note that the security control field is not included in the auxiliary security header. Likewise, + // the first byte of the payload is not included in the a data. For further information, refer to + // (Thread Spec v1.3.0 -- sect. 4.9) + // + // Because all MLE messages must be encrypted with MLE security (v1.3.0 sect 4.10), all MLE messages + // that are processed must possess an auxiliary security header of 10 bytes. The payload therefore consists of: + // + // |-----(1 byte)-----|----(10 bytes)------|---(UNKNOWN)---|--(DEPENDENT ON PROTOCOL)--| + // | SECURITY SUITE | AUX SEC HEADER | MLE | Mic | + // + // Since the aux sec header is fixed, the auth data is always 32 bytes [16 (IPV6) + 16 (IPV6) + 10 aux sec header]. + // The m data len can be determined because it is the only unknown length. + + let aux_sec_header = &mut [0u8; AUX_SEC_HEADER_LENGTH]; + Security::encode(&security, aux_sec_header); + + let m_data_len = payload.len(); + + // Encode auth data and payload into `buf` + let encode_res = encode_cryp_data(src_addr, dst_addr, aux_sec_header, payload, buf).done(); + + // Error check on result from encoding, failure likely means buf was not large enough + if encode_res.is_none() { + // UNCOMMENT TO DEBUG THREAD // + // kernel::debug!("[Thread] Error encoding cryptographic data into buffer"); + return Err((ErrorCode::FAIL, buf)); + } + + let (offset, ()) = encode_res.unwrap(); + + // GENERAL NOTE: `self.crypto_sizelock` + // This does not seem to be the most elegant solution. The `crypto_sizelock` arose from the fact + // that we must know the length of the payload when the `crypt_done` callback + // occurs in order to only send/receive the portion of the 200 byte buffer that is the message. + // The other option to `crypto_sizelock` is to only pass to the crypto engine a buffer + // that is the size of the transmission/reception. This however is flawed + // as it leads to an inability to replace/restore the 200 byte buffer. The crypto engine + // requires a reference to static memory (leading to the recv/send buf being taken). + // This is only able to be replaced when the `crypt_done` callback occurs and returns the + // buf used by the crypto engine. If a partial buffer is used, it is impossible to then replace + // the full sized buffer to the send/recv buf. Likewise, I choose to use the `crypt_sizelock` + // and pass the whole 200 byte buffer. The `crypto_sizelock` works for + // now until a more elegant solution is implemented. + + // The sizelock is empty except when a crypto operation + // is underway. If the sizelock is not empty, return error + if self.crypto_sizelock.is_some() { + // UNCOMMENT TO DEBUG THREAD // + // kernel::debug!( + // "[Thread] Error - cryptographic resources in use; crypto_sizelock occupied" + // ); + return Err((ErrorCode::BUSY, buf)); + } + + // Store the length of the payload. + self.crypto_sizelock.replace(offset + mic_len); + self.aes_crypto + .crypt(buf, 0, AUTH_DATA_LEN, m_data_len, mic_len, true, true) + } +} + +impl<'a, A: time::Alarm<'a>> framer::KeyProcedure for ThreadNetworkDriver<'a, A> { + /// Gets the key corresponding to the key that matches the given security + /// level `level` and key ID `key_id`. If no such key matches, returns + /// `None`. + // TODO: This implementation only supports one key + fn lookup_key(&self, _level: SecurityLevel, _key_id: KeyId) -> Option<[u8; 16]> { + if let Some(netkey) = self.networkkey.get() { + Some(netkey.mac_key) + } else { + None + } + } +} + +impl<'a, A: time::Alarm<'a>> framer::DeviceProcedure for ThreadNetworkDriver<'a, A> { + /// Gets the key corresponding to the key that matches the given security + /// level `level` and key ID `key_id`. If no such key matches, returns + /// `None`. + // TODO: This implementation only supports one key + fn lookup_addr_long(&self, _addr: MacAddress) -> Option<[u8; 8]> { + Some(self.src_mac_addr) + } +} + +impl<'a, A: time::Alarm<'a>> SyscallDriver for ThreadNetworkDriver<'a, A> { + /// ### `command_num` + /// - `0`: Driver Check + /// - `1`: Add a new mle/mac networkkey and initiate a parent request. + + fn command( + &self, + command_num: usize, + _arg1: usize, + _: usize, + processid: ProcessId, + ) -> CommandReturn { + match command_num { + 0 => CommandReturn::success(), + + 1 => self + .apps + .enter(processid, |_, kernel_data| { + kernel_data + .get_readonly_processbuffer(ro_allow::WRITE) + .and_then(|ro_buf| { + ro_buf.enter(|src_key| { + // check Thread state, if thread state is not empty, + // another userspace application has control of the Thread + // network and other requesting applications should fail. + if self.state.is_some() { + return CommandReturn::failure(ErrorCode::BUSY); + } + + // src key consists of the mle and mac keys; Thread + // hash is performed in userland and 32 byte hash is + // passed to thread capsule and entered as mac/mle key + // (For key generation see Thread spec v1.3.0 7.1.4) + if src_key.len() != 32 { + return CommandReturn::failure(ErrorCode::SIZE); + } + let mut mle_key = [0u8; 16]; + let mut mac_key = [0u8; 16]; + src_key[..16].copy_to_slice(&mut mle_key); + src_key[16..32].copy_to_slice(&mut mac_key); + self.set_networkkey(mle_key, mac_key); + + // Thread state begins as detached if sucessfully joined + self.state.replace(ThreadState::Detached); + CommandReturn::success() + }) + }) + .unwrap_or(CommandReturn::failure(ErrorCode::INVAL)) + }) + .map_or_else( + |err| CommandReturn::failure(err.into()), + |ok_val| { + // If no failure in saving the mle/mac key, initiate + // sending the parent request + self.send_parent_req(); + ok_val + }, + ), + + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { + self.apps.enter(processid, |_, _| {}) + } +} + +impl<'a, A: time::Alarm<'a>> UDPSendClient for ThreadNetworkDriver<'a, A> { + fn send_done(&self, _result: Result<(), ErrorCode>, mut dgram: SubSliceMut<'static, u8>) { + // TODO: handle result from send done and respond accordingly + + // Panicking on unwrap indicates the state was taken without replacement + // (unreachable with proper state machine implementation) + let curr_state = self.state.take().unwrap(); + + // Advance state machine + let next_state = match curr_state { + ThreadState::SendUpdate(dst_ip, dst_mac) => ThreadState::SEDActive(dst_ip, dst_mac), + ThreadState::SendUDPMsg => unimplemented!(), + ThreadState::SendChildIdReq(_) => ThreadState::WaitingChildRsp, + ThreadState::SendParentReq => { + // UNCOMMENT TO DEBUG THREAD // + // kernel::debug!("[Thread] Completed sending parent request to multicast IP"); + ThreadState::WaitingParentRsp + } + _ => panic!("Thread state machine diverged"), + }; + + self.frame_count.set(self.frame_count.get() + 1); + + // Replace the returned buffer and state + dgram.reset(); + self.send_buffer.replace(dgram); + self.state.replace(next_state); + } +} + +impl<'a, A: time::Alarm<'a>> time::AlarmClient for ThreadNetworkDriver<'a, A> { + // TODO: This function is mainly here as a place holder as it will be needed + // for implementing timeouts/timing for sending heartbeat messages to the parent + // node + fn alarm(&self) { + match self.state.take().unwrap() { + // TODO: Implement retries as defined in the thread spec (when timeouts occur) + ThreadState::Detached => unimplemented!("[Thread ALARM] Detached"), + ThreadState::SendParentReq => unimplemented!("[Thread ALARM] Send Parent Req"), + ThreadState::SendChildIdReq(_) => unimplemented!("[Thread ALARM] Send Child ID Req"), + ThreadState::SEDActive(_ipaddr, _mac) => { + // TODO: SEND HEARTBEAT to parent node + unimplemented!("[Thread ALARM] Send Heartbeat") + } + _ => panic!(""), + } + } +} + +impl<'a, A: time::Alarm<'a>> UDPRecvClient for ThreadNetworkDriver<'a, A> { + fn receive( + &self, + src_addr: IPAddr, + dst_addr: IPAddr, + _src_port: u16, + _dst_port: u16, + payload: &[u8], + ) { + if payload[0] != SECURITY_SUITE_ENCRYP { + // Tock's current implementation of Thread ignores all messages that do not possess MLE encryption. This + // is due to the Thread spec stating "Except for when specifically indicated, incoming + // messages that are not secured with either MLE or link-layer security SHOULD be ignored." (v.1.3.0 sect 4.10) + // UNCOMMENT TO DEBUG THREAD // + // kernel::debug!("[Thread] DROPPED PACKET - Received unencrypted MLE packet."); + } + + // decode aux security header from packet into Security data type + let sec_res = ieee802154::Security::decode(&payload[1..]).done(); + + // Guard statement for improperly formated aux sec header + if sec_res.is_none() { + // UNCOMMENT TO DEBUG THREAD // + // kernel::debug!("[Thread] DROPPED PACKET - Malformed auxiliary security header."); + return; + } + + let security = sec_res.unwrap().1; + + // Take the receive buffer and pass to the `perform_crypto_op` wrapper function. This + // initiates encoding all relevant auth data, setting crypto engine and initiating the + // crypto operation. + self.recv_buffer.take().map_or_else( + || { + // UNCOMMENT TO DEBUG THREAD // + // kernel::debug!("[Thread] DROPPED PACKET - Receive buffer not available") + }, + |recv_buf| { + self.perform_crypt_op( + src_addr, + dst_addr, + security, + &payload[SECURITY_SUITE_LEN + AUX_SEC_HEADER_LENGTH + ..payload.len() - security.level.mic_len()], + recv_buf.take(), + ) + .map_or_else( + // Error check on crypto operation. If the crypto operation + // fails, we log the error and replace the receive buffer for + // future receptions + |(_code, buf)| { + // UNCOMMENT TO DEBUG THREAD alter _code to code// + // kernel::debug!( + // "[Thread] DROPPED PACKET - Crypto Operation Error *{:?}", + // code + // ); + self.recv_buffer.replace(SubSliceMut::new(buf)); + }, + |()| (), + ) + }, + ); + } +} + +impl<'a, A: time::Alarm<'a>> CCMClient for ThreadNetworkDriver<'a, A> { + fn crypt_done(&self, buf: &'static mut [u8], _res: Result<(), ErrorCode>, _tag_is_valid: bool) { + // TODO: check validity of result/tag and handle accordingly + + // Obtain the length of the payload from the sizelock + let buf_len = self.crypto_sizelock.take().unwrap(); + + // The auth data contains the src_addr || dest_addr || aux_sec_header; + // Recover src/dst addr from the auth data + let mut src_ipv6 = [0u8; IPV6_LEN]; + let mut dst_ipv6 = [0u8; IPV6_LEN]; + src_ipv6.copy_from_slice(&buf[..IPV6_LEN]); + dst_ipv6.copy_from_slice(&buf[IPV6_LEN..(2 * IPV6_LEN)]); + + // The crypto operation requires 32 bytes (2 IPV6 addresses) as part of the auth data. We + // do not care about this data so we shift the data to overwrite this data with the aux sec header + // and payload. We shift this to an offset of 1 so that we can add the security suite field + let auth_addr_offset = AUTH_DATA_LEN - AUX_SEC_HEADER_LENGTH; + buf.copy_within(auth_addr_offset.., SECURITY_SUITE_LEN); + + // Recover the length of the mic from the security information encoded in the aux_sec_header + let mic_len = ieee802154::Security::decode(&buf[SECURITY_SUITE_LEN..]) + .done() + .unwrap() + .1 + .level + .mic_len(); + + // We hard code the security suite to `0` for now as all messages are + // assumed to be encrypted for the current implementation + buf[..SECURITY_SUITE_LEN].copy_from_slice(&[SECURITY_SUITE_ENCRYP]); + + // the assembled_buf_len is the length of: security suite || aux sec header || mle payload || mic + let assembled_buf_len = buf_len - auth_addr_offset + SECURITY_SUITE_LEN; + + // We create a new subslice that we will slice accordingly depending on if we are sending/receiving + let mut assembled_subslice = SubSliceMut::new(buf); + + // Panicking on unwrap indicates the state was taken without replacement + // (unreachable with proper state machine implementation) + let curr_state = self.state.take().unwrap(); + + match curr_state { + ThreadState::SendParentReq | ThreadState::SendChildIdReq(_) => { + //TODO: Add alarm for timeouts + + // To send, we need to send: security suite || aux sec header || mle payload || mic + // which correlates to the assembled_buf_len + assembled_subslice.slice(..assembled_buf_len); + + let dest_ipv6 = match curr_state { + // Determine destination IP depending on message type + ThreadState::SendParentReq => MULTICAST_IPV6, + ThreadState::SendChildIdReq(dst_ipv6) => dst_ipv6, + _ => unreachable!(), + }; + + // we replace the state with the current state here + // because we cannot advance the state machine until + // after the `send_done` callback is received + self.state.replace(curr_state); + + // Begin sending the transmission + self.sender + .driver_send_to( + dest_ipv6, + THREAD_PORT_NUMBER, + THREAD_PORT_NUMBER, + assembled_subslice, + self.driver_send_cap, + self.net_cap, + ) + .map_err(|buf| { + // if the sending fails prior to transmission, replace + // the buffer and pass error accordingly to terminate_child_join + // in following unwrap statement + self.send_buffer.replace(buf); + ErrorCode::FAIL + }) + .unwrap_or_else(|code| self.terminate_child_join(Err(code))); + } + ThreadState::WaitingChildRsp => { + // TODO: Receive child response + } + ThreadState::WaitingParentRsp => { + // Upon receiving messages, the receive logic only requires the MLE payload. Subsequently, + // we slice the assembled_subslice to exclude the security suite, aux sec header, and mic. + assembled_subslice + .slice(AUX_SEC_HEADER_LENGTH + SECURITY_SUITE_LEN..assembled_buf_len - mic_len); + + // Move the decrypted MLE message into the recv_buf and execute the receiving logic. Upon + // an error in `recv_logic`, joining the network fails and schedule termination upcall + self.recv_buffer.replace(assembled_subslice); + self.recv_logic(IPAddr(src_ipv6)) + .err() + .map(|code| self.terminate_child_join(Err(code))); + } + _ => (), + }; + } +} diff --git a/capsules/extra/src/net/thread/mod.rs b/capsules/extra/src/net/thread/mod.rs new file mode 100644 index 0000000000..10f2eeb086 --- /dev/null +++ b/capsules/extra/src/net/thread/mod.rs @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +pub mod driver; +pub mod thread_utils; +pub mod tlv; diff --git a/capsules/extra/src/net/thread/thread_utils.rs b/capsules/extra/src/net/thread/thread_utils.rs new file mode 100644 index 0000000000..de82257d27 --- /dev/null +++ b/capsules/extra/src/net/thread/thread_utils.rs @@ -0,0 +1,287 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +use crate::net::stream::{encode_bytes, SResult}; +use crate::net::thread::tlv::{unwrap_tlv_offset, LinkMode, MulticastResponder, Tlv}; +use crate::net::{ieee802154::MacAddress, ipv6::ip_utils::IPAddr}; +pub const THREAD_PORT_NUMBER: u16 = 19788; + +use kernel::ErrorCode; + +pub const SECURITY_SUITE_LEN: usize = 1; +pub const AUX_SEC_HEADER_LENGTH: usize = 10; +pub const AUTH_DATA_LEN: usize = 42; +pub const IPV6_LEN: usize = 16; +const PARENT_REQUEST_MLE_SIZE: usize = 21; +pub const MULTICAST_IPV6: IPAddr = IPAddr([ + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, +]); + +#[derive(Clone, Copy)] +pub struct NetworkKey { + pub mle_key: [u8; 16], + pub mac_key: [u8; 16], +} + +pub enum ThreadState { + SendParentReq, + WaitingParentRsp, + RecvParentRsp(IPAddr), + SendChildIdReq(IPAddr), + WaitingChildRsp, + RecvChildRsp(IPAddr), + SEDActive(IPAddr, MacAddress), + SendUpdate(IPAddr, MacAddress), + SendUDPMsg, + Detached, +} + +pub enum MleCommand { + LinkRequest = 0, + LinkAccept = 1, + LinkAcceptAndRequest = 2, + LinkAdvertisement = 4, + DataRequest = 7, + DataResponse = 8, + ParentRequest = 9, + ParentResponse = 10, + ChildIdRequest = 11, + ChildIdResponse = 12, + ChildUpdateRequest = 13, + ChildUpdateResponse = 14, + Announce = 15, + DiscoverRequest = 16, + DiscoveryResponse = 17, + LinkMetricsManagReq = 18, + LinkMetricsManagResp = 19, + LinkProbe = 20, +} + +/// Helper function to generate a link-local IPV6 address +/// from the device's mac address. +pub fn generate_src_ipv6(macaddr: &[u8; 8]) -> IPAddr { + // ----------------------------------------------------------------------------------------------- + // THREAD SPEC 5.2.2.4 (V1.3.0) -- A Thread Device MUST assign a link-local IPv6 address where the + // interface identifier is set to the MAC Extended Address with the universal/local bit inverted. + // ------------------------------------------------------------------------------------------------ + + let mut output: [u8; 16] = [0; 16]; + let mut lower_bytes = *macaddr; + + // The universal/local bit is the 2nd least significant bit. + // Invert by xor first byte of MAC addr with 2 + lower_bytes[0] = macaddr[0] ^ 2; + let upper_bytes: [u8; 8] = [0xfe, 0x80, 0, 0, 0, 0, 0, 0]; + + encode_bytes(&mut output[..8], &upper_bytes); + encode_bytes(&mut output[8..16], &lower_bytes); + IPAddr(output) +} + +/// Helper function to recover the mac address from +/// an IPV6 address. +pub fn mac_from_ipv6(ipv6: IPAddr) -> [u8; 8] { + // Helper function to generate the mac address from the mac address; + // reversing the tranformation used/described in `generate_src_ipv6` + let mut output: [u8; 8] = [0; 8]; + let mut lower_bytes = ipv6.0; + lower_bytes[8] ^= 2; + + encode_bytes(&mut output[..8], &lower_bytes[8..16]); + output +} + +/// Helper function to locate the challenge TLV in a received +/// MLE packet. Return the challenge to be used as a response +/// TLV in reply. +fn find_challenge(buf: &[u8]) -> Result<&[u8], ErrorCode> { + let mut index = 0; + while index < buf.len() { + let tlv_len = buf[index + 1] as usize; + if buf[index] == 3 { + return Ok(&buf[index + 2..index + 2 + tlv_len]); + } else { + index += tlv_len + 2; + } + } + Err(ErrorCode::FAIL) +} + +/// Function to encode the crypt data into a/m data +pub fn encode_cryp_data( + src_addr: IPAddr, + dst_addr: IPAddr, + aux_sec_header: &[u8; AUX_SEC_HEADER_LENGTH], + payload: &[u8], + output: &mut [u8], +) -> SResult { + // --------------AUTH DATA----------------||-- M DATA-- + // SRC IPV6 || DST IPV6 || AUX SEC HEADER || PAYLOAD + let mut off = enc_consume!(output; encode_bytes, &src_addr.0); + off = enc_consume!(output, off; encode_bytes, &dst_addr.0); + off = enc_consume!(output, off; encode_bytes, aux_sec_header); + off = enc_consume!(output, off; encode_bytes, payload); + stream_done!(off) +} + +/// This helper function creates a parent request. For now, +/// this implementation hard codes all values for the parent request +pub fn form_parent_req() -> [u8; PARENT_REQUEST_MLE_SIZE] { + // TODO: form parent request from alterable values, generate + // challenge from random number generator + let mut output = [0u8; PARENT_REQUEST_MLE_SIZE]; + let mut offset = 0; + + // Command: Parent Request // + output[0..1].copy_from_slice(&[MleCommand::ParentRequest as u8]); + offset += 1; + + // Mode TLV // + offset += unwrap_tlv_offset(Tlv::encode( + &Tlv::Mode( + LinkMode::FullNetworkDataRequired as u8 + + LinkMode::FullThreadDevice as u8 + + LinkMode::SecureDataRequests as u8 + + LinkMode::ReceiverOnWhenIdle as u8, + ), + &mut output[offset..], + )); + + // Challenge TLV // + // TODO: challenge is hardcoded currently; randomly generate number + offset += unwrap_tlv_offset(Tlv::encode( + &Tlv::Challenge([0, 0, 0, 0, 0, 0, 0, 0]), + &mut output[offset..], + )); + + // Scan Mask TLV // + offset += unwrap_tlv_offset(Tlv::encode( + &Tlv::ScanMask(MulticastResponder::Router as u8), + &mut output[offset..], + )); + + // Version TLV // + unwrap_tlv_offset(Tlv::encode(&Tlv::Version(4), &mut output[offset..])); + + output +} + +/// This helper function creates a child id request. For now, +/// this implementation hard codes many of the values +pub fn form_child_id_req( + recv_buf: &[u8], + frame_count: u32, +) -> Result<([u8; 200], usize), ErrorCode> { + let mut output: [u8; 200] = [0; 200]; + let mut offset = 0; + + /* -- Child ID Request TLVs (Thread Spec 4.5.1 (v1.3.0)) -- + Response TLV + Link-layer Frame Counter TLV + [MLE Frame Counter TLV] **optional if Link-layer frame counter is the same** + Mode TLV + Timeout TLV + Version TLV + [Address Registration TLV] + [TLV Request TLV: Address16 (Network Data and/or Route)] + [Active Timestamp TLV] + [Pending Timestamp TLV] + */ + + // Command Child ID Request // + output[0..1].copy_from_slice(&[MleCommand::ChildIdRequest as u8]); + offset += 1; + + // Response TLV // + let received_challenge_tlv: Result<&[u8], ErrorCode> = find_challenge(&recv_buf[1..]); + + if received_challenge_tlv.is_err() { + // Challenge TLV not found; malformed request + return Err(ErrorCode::FAIL); + } else { + // Encode response into output + let mut rsp_buf: [u8; 8] = [0; 8]; + rsp_buf.copy_from_slice(received_challenge_tlv.unwrap()); + rsp_buf.reverse(); // NEED TO DISCUSS BIG/LITTLE ENDIAN ASSUMPTIONS + offset += unwrap_tlv_offset(Tlv::encode(&Tlv::Response(rsp_buf), &mut output[offset..])); + } + + // Link-layer Frame Counter TLV // + offset += unwrap_tlv_offset(Tlv::encode( + &Tlv::LinkLayerFrameCounter(0), + &mut output[offset..], + )); + + // MLE Frame Counter TLV // + offset += unwrap_tlv_offset(Tlv::encode( + &Tlv::MleFrameCounter(frame_count.to_be()), + &mut output[offset..], + )); + + // Mode TLV // + offset += unwrap_tlv_offset(Tlv::encode( + &Tlv::Mode(LinkMode::FullThreadDevice as u8 + LinkMode::ReceiverOnWhenIdle as u8), + &mut output[offset..], + )); + + // Timeout TLV // + offset += unwrap_tlv_offset(Tlv::encode( + &Tlv::Timeout((10_u32).to_be()), + &mut output[offset..], + )); + + // Version TLV // + offset += unwrap_tlv_offset(Tlv::encode(&Tlv::Version(4), &mut output[offset..])); + + // TODO: hardcoded for now, but replace in future + output[offset..offset + 4].copy_from_slice(&[0x1b, 0x02, 0x00, 0x81]); + offset += 4; + + offset += unwrap_tlv_offset(Tlv::encode( + &Tlv::TlvRequest(&[0x0a, 0x0c, 0x09]), + &mut output[offset..], + )); + + Ok((output, offset)) +} + +/* +This is just here as a note for when retries are added +================================================================================================== +THREAD SPEC v1.3.0 -- section 4.5.1 +A Thread Device attempting to attach MUST first attempt to attach with the Scan Mask TLV of +the Parent Request set to only solicit responses from Routers. If no responses are received, or +there is no response with the highest link quality, this request is deemed to have failed. If it +failed, the Device MUST attempt to attach again using the same request. If it still failed, the device +MUST attempt to attach with the Scan Mask TLV of the Parent Request set to solicit responses from both +Routers and REEDs. There is no requirement on minimum link quality for +responses to this request. If no responses are received, this request is deemed to have failed. If +this Parent Request failed it MUST be retried up to three times. + +If the Thread Device is a REED and it still fails to successfully attach to a parent after all retries, +it forms a new Partition as described in Section 5.16, Thread Network Partitions in Chapter 5, +Network Layer. + +If the Thread Device is not a REED and it fails to successfully attach to a parent after all retries, +then it SHOULD first wait for a vendor-specific timeout and then attempt to attach again using a +Parent Request set to only solicit responses from Routers. If no responses are received, or if +there is no response with the highest link quality, this request is deemed to have failed. If it +failed, the Device MUST attempt to attach with the Scan Mask TLV of the Parent Request set to +solicit responses from both Routers and REEDs. There is no requirement on minimum link quality +for responses to this request. If this request still failed, the Thread Device again waits for a +vendor-specific timeout and repeats the cycle defined in this paragraph. + +SENDING/RETRYING PARENT REQUESTS THREAD SPEC v1.3.0 -- section 4.5.1 +**Attempt 1/2 considered to fail if no response received or no response with highest link quality +**Attempt 3-6 considered to fail if no response received + (Attempt 1) Send parent request with scan mask only set to routers + (Attempt 2) Repeat attempt 1 + (Attempt 3) Send parent request with scan mask set to routers and REEDs + (Attempt 4) Repeat attempt 3 + (Attempt 5) Repeat attempt 3 + (Attempt 6) Repeat attempt 3 + + + +*/ diff --git a/capsules/src/net/thread/tlv.rs b/capsules/extra/src/net/thread/tlv.rs similarity index 94% rename from capsules/src/net/thread/tlv.rs rename to capsules/extra/src/net/thread/tlv.rs index 702e48bef3..50c21e4a25 100644 --- a/capsules/src/net/thread/tlv.rs +++ b/capsules/extra/src/net/thread/tlv.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implements Type-Length-Value (TLV) encoding and decoding as outlined //! in the Thread 1.1.1 Specification. TLVs are used to serialize //! information exchanged during mesh link establishment (MLE). MLE is @@ -119,6 +123,13 @@ pub enum Tlv<'a> { PendingOperationalDataset(&'a [u8]), } +pub fn unwrap_tlv_offset(res: SResult) -> usize { + match res { + SResult::Done(val, ()) => val, + _ => 0, + } +} + impl Tlv<'_> { /// Serializes TLV data in `buf` into the format specific to the TLV /// type. @@ -192,13 +203,13 @@ impl Tlv<'_> { offset = enc_consume!(buf, offset; encode_u8, leader_router_id); stream_done!(offset) } - Tlv::NetworkData(ref network_data_tlvs) => { + Tlv::NetworkData(network_data_tlvs) => { let value_width = network_data_tlvs.len(); let mut offset = enc_consume!(buf; self; encode_tl, value_width); offset = enc_consume!(buf, offset; encode_bytes, network_data_tlvs); stream_done!(offset) } - Tlv::TlvRequest(ref tlv_codes) => { + Tlv::TlvRequest(tlv_codes) => { let value_width = tlv_codes.len(); let mut offset = enc_consume!(buf; self; encode_tl, value_width); offset = enc_consume!(buf, offset; encode_bytes, tlv_codes); @@ -271,13 +282,13 @@ impl Tlv<'_> { offset = enc_consume!(buf, offset; encode_u16, *version); stream_done!(offset) } - Tlv::ActiveOperationalDataset(ref network_mgmt_tlvs) => { + Tlv::ActiveOperationalDataset(network_mgmt_tlvs) => { let value_width = network_mgmt_tlvs.len(); let mut offset = enc_consume!(buf; self; encode_tl, value_width); offset = enc_consume!(buf, offset; encode_bytes, network_mgmt_tlvs); stream_done!(offset) } - Tlv::PendingOperationalDataset(ref network_mgmt_tlvs) => { + Tlv::PendingOperationalDataset(network_mgmt_tlvs) => { let value_width = network_mgmt_tlvs.len(); let mut offset = enc_consume!(buf; self; encode_tl, value_width); offset = enc_consume!(buf, offset; encode_bytes, network_mgmt_tlvs); @@ -345,11 +356,11 @@ impl Tlv<'_> { stream_done!( offset, Tlv::LeaderData { - partition_id: partition_id, - weighting: weighting, - data_version: data_version, - stable_data_version: stable_data_version, - leader_router_id: leader_router_id, + partition_id, + weighting, + data_version, + stable_data_version, + leader_router_id, } ) } @@ -389,15 +400,15 @@ impl Tlv<'_> { stream_done!( offset, Tlv::Connectivity { - parent_priority: parent_priority, - link_quality_3: link_quality_3, - link_quality_2: link_quality_2, - link_quality_1: link_quality_1, - leader_cost: leader_cost, - id_sequence: id_sequence, - active_routers: active_routers, - sed_buffer_size: sed_buffer_size, - sed_datagram_count: sed_datagram_count, + parent_priority, + link_quality_3, + link_quality_2, + link_quality_1, + leader_cost, + id_sequence, + active_routers, + sed_buffer_size, + sed_datagram_count, } ) } @@ -629,7 +640,7 @@ impl NetworkDataTlv<'_> { fn encode_tl(&self, buf: &mut [u8], value_width: usize, stable: bool) -> SResult { stream_len_cond!(buf, TL_WIDTH + value_width); - let stable_bit = if stable { 1u8 } else { 0u8 }; + let stable_bit = u8::from(stable); buf[0] = (NetworkDataTlvType::from(self) as u8) << 1 | stable_bit; buf[1] = value_width as u8; stream_done!(TL_WIDTH) @@ -657,9 +668,9 @@ impl NetworkDataTlv<'_> { offset + length as usize, ( NetworkDataTlv::Prefix { - domain_id: domain_id, - prefix_length_bits: prefix_length_bits, - prefix: prefix, + domain_id, + prefix_length_bits, + prefix, sub_tlvs: &buf[offset..offset + length as usize], }, stable @@ -674,8 +685,8 @@ impl NetworkDataTlv<'_> { offset, ( NetworkDataTlv::CommissioningData { - com_length: com_length, - com_data: com_data, + com_length, + com_data, }, stable ) @@ -693,11 +704,11 @@ impl NetworkDataTlv<'_> { offset + length as usize, ( NetworkDataTlv::Service { - thread_enterprise_number: thread_enterprise_number, - s_id: s_id, - s_enterprise_number: s_enterprise_number, - s_service_data_length: s_service_data_length, - s_service_data: s_service_data, + thread_enterprise_number, + s_id, + s_enterprise_number, + s_service_data_length, + s_service_data, sub_tlvs: &buf[offset..offset + length as usize], }, stable @@ -756,13 +767,13 @@ impl PrefixSubTlv<'_> { /// Prefix sub-TLV type. pub fn encode(&self, buf: &mut [u8], stable: bool) -> SResult { match *self { - PrefixSubTlv::HasRoute(ref r_border_router_16s) => { + PrefixSubTlv::HasRoute(r_border_router_16s) => { let value_width = r_border_router_16s.len(); let mut offset = enc_consume!(buf; self; encode_tl, value_width, stable); offset = enc_consume!(buf, offset; encode_bytes, r_border_router_16s); stream_done!(offset) } - PrefixSubTlv::BorderRouter(ref p_border_router_16s) => { + PrefixSubTlv::BorderRouter(p_border_router_16s) => { let value_width = p_border_router_16s.len(); let mut offset = enc_consume!(buf; self; encode_tl, value_width, stable); offset = enc_consume!(buf, offset; encode_bytes, p_border_router_16s); @@ -775,7 +786,7 @@ impl PrefixSubTlv<'_> { } => { let value_width = mem::size_of::() + mem::size_of::(); let mut offset = enc_consume!(buf; self; encode_tl, value_width, stable); - let compress_bit = if context_id_compress { 1u8 } else { 0u8 }; + let compress_bit = u8::from(context_id_compress); let first_byte = (compress_bit << 4) | (context_id & 0b1111); offset = enc_consume!(buf, offset; encode_u8, first_byte); offset = enc_consume!(buf, offset; encode_u8, context_length); @@ -786,7 +797,7 @@ impl PrefixSubTlv<'_> { fn encode_tl(&self, buf: &mut [u8], value_width: usize, stable: bool) -> SResult { stream_len_cond!(buf, TL_WIDTH + value_width); - let stable_bit = if stable { 1u8 } else { 0u8 }; + let stable_bit = u8::from(stable); buf[0] = (PrefixSubTlvType::from(self) as u8) << 1 | stable_bit; buf[1] = value_width as u8; stream_done!(TL_WIDTH) @@ -828,9 +839,9 @@ impl PrefixSubTlv<'_> { offset, ( PrefixSubTlv::SixLoWpanId { - context_id_compress: context_id_compress, - context_id: context_id, - context_length: context_length, + context_id_compress, + context_id, + context_length, }, stable ) @@ -884,7 +895,7 @@ impl HasRouteTlvValue { pub fn encode(&self, buf: &mut [u8]) -> SResult { stream_len_cond!(buf, 3); let mut offset = enc_consume!(buf, 0; encode_u16, self.r_border_router_16.to_be()); - let last_byte = ((self.r_preference & 0b11) as u8) << 6; + let last_byte = (self.r_preference & 0b11) << 6; offset = enc_consume!(buf, offset; encode_u8, last_byte); stream_done!(offset) } @@ -898,8 +909,8 @@ impl HasRouteTlvValue { stream_done!( offset, HasRouteTlvValue { - r_border_router_16: r_border_router_16, - r_preference: r_preference, + r_border_router_16, + r_preference, } ) } @@ -942,8 +953,8 @@ impl BorderRouterTlvValue { stream_done!( offset, BorderRouterTlvValue { - p_border_router_16: p_border_router_16, - p_bits: p_bits, + p_border_router_16, + p_bits, } ) } @@ -978,7 +989,7 @@ impl ServiceSubTlv { fn encode_tl(&self, buf: &mut [u8], value_width: usize, stable: bool) -> SResult { stream_len_cond!(buf, TL_WIDTH + value_width); - let stable_bit = if stable { 1u8 } else { 0u8 }; + let stable_bit = u8::from(stable); buf[0] = (ServiceSubTlvType::from(self) as u8) << 1 | stable_bit; buf[1] = value_width as u8; stream_done!(TL_WIDTH) @@ -1005,8 +1016,8 @@ impl ServiceSubTlv { offset, ( ServiceSubTlv::Server { - s_server_16: s_server_16, - s_server_data: s_server_data, + s_server_16, + s_server_data, }, stable ) @@ -1185,12 +1196,12 @@ impl NetworkManagementTlv<'_> { let value_width = timestamp_seconds.len() + mem::size_of::(); let mut offset = enc_consume!(buf; self; encode_tl, value_width); offset = enc_consume!(buf, offset; encode_bytes_be, ×tamp_seconds); - let u_bit_val = if u_bit { 1u16 } else { 0u16 }; + let u_bit_val = u16::from(u_bit); let end_bytes = (timestamp_ticks << 1) | u_bit_val; offset = enc_consume!(buf, offset; encode_u16, end_bytes.to_be()); stream_done!(offset) } - NetworkManagementTlv::CommissionerUdpPort(ref udp_port) => { + NetworkManagementTlv::CommissionerUdpPort(udp_port) => { let value_width = mem::size_of::(); let mut offset = enc_consume!(buf; self; encode_tl, value_width); offset = enc_consume!(buf, offset; encode_u16, udp_port.to_be()); @@ -1204,18 +1215,18 @@ impl NetworkManagementTlv<'_> { let value_width = timestamp_seconds.len() + mem::size_of::(); let mut offset = enc_consume!(buf; self; encode_tl, value_width); offset = enc_consume!(buf, offset; encode_bytes_be, ×tamp_seconds); - let u_bit_val = if u_bit { 1u16 } else { 0u16 }; + let u_bit_val = u16::from(u_bit); let end_bytes = (timestamp_ticks << 1) | u_bit_val; offset = enc_consume!(buf, offset; encode_u16, end_bytes.to_be()); stream_done!(offset) } - NetworkManagementTlv::DelayTimer(ref time_remaining) => { + NetworkManagementTlv::DelayTimer(time_remaining) => { let value_width = mem::size_of::(); let mut offset = enc_consume!(buf; self; encode_tl, value_width); offset = enc_consume!(buf, offset; encode_u32, time_remaining.to_be()); stream_done!(offset) } - NetworkManagementTlv::ChannelMask(ref entries) => { + NetworkManagementTlv::ChannelMask(entries) => { let value_width = entries.len(); let mut offset = enc_consume!(buf; self; encode_tl, value_width); offset = enc_consume!(buf, offset; encode_bytes, entries); @@ -1248,8 +1259,8 @@ impl NetworkManagementTlv<'_> { stream_done!( offset, NetworkManagementTlv::Channel { - channel_page: channel_page, - channel: channel, + channel_page, + channel, } ) } @@ -1320,8 +1331,8 @@ impl NetworkManagementTlv<'_> { stream_done!( offset, NetworkManagementTlv::SecurityPolicy { - rotation_time: rotation_time, - policy_bits: policy_bits, + rotation_time, + policy_bits, } ) } @@ -1332,7 +1343,7 @@ impl NetworkManagementTlv<'_> { stream_done!( offset, NetworkManagementTlv::ActiveTimestamp { - timestamp_seconds: timestamp_seconds, + timestamp_seconds, timestamp_ticks: timestamp_ticks >> 1, u_bit: (timestamp_ticks & 1u16) > 0, } @@ -1349,7 +1360,7 @@ impl NetworkManagementTlv<'_> { stream_done!( offset, NetworkManagementTlv::PendingTimestamp { - timestamp_seconds: timestamp_seconds, + timestamp_seconds, timestamp_ticks: timestamp_ticks >> 1, u_bit: (timestamp_ticks & 1u16) > 0, } @@ -1493,9 +1504,9 @@ impl ChannelMaskEntry { stream_done!( offset, ChannelMaskEntry { - channel_page: channel_page, - mask_length: mask_length, - channel_mask: channel_mask, + channel_page, + mask_length, + channel_mask, } ) } diff --git a/capsules/src/net/udp/driver.rs b/capsules/extra/src/net/udp/driver.rs similarity index 84% rename from capsules/src/net/udp/driver.rs rename to capsules/extra/src/net/udp/driver.rs index 303594d4d8..7f1789e3b8 100644 --- a/capsules/src/net/udp/driver.rs +++ b/capsules/extra/src/net/udp/driver.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! UDP userspace interface for transmit and receive. //! //! Implements a userspace interface for sending and receiving UDP messages. @@ -17,8 +21,6 @@ use crate::net::udp::udp_send::{UDPSendClient, UDPSender}; use crate::net::util::host_slice_to_u16; use core::cell::Cell; -use core::convert::TryFrom; -use core::convert::TryInto; use core::mem::size_of; use core::{cmp, mem}; @@ -28,26 +30,50 @@ use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; use kernel::processbuffer::{ReadableProcessBuffer, WriteableProcessBuffer}; use kernel::syscall::{CommandReturn, SyscallDriver}; use kernel::utilities::cells::MapCell; -use kernel::utilities::leasable_buffer::LeasableBuffer; +use kernel::utilities::leasable_buffer::SubSliceMut; use kernel::{ErrorCode, ProcessId}; -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::Udp as usize; +/// IDs for subscribed upcalls. +mod upcall { + /// Callback for when packet is received. If no port has been bound, return + /// `RESERVE` to indicate that port binding is is a prerequisite to + /// reception. + pub const PACKET_RECEIVED: usize = 0; + /// Callback for when packet is transmitted. Notably, this callback receives + /// the result of the send_done callback from udp_send.rs, which does not + /// currently pass information regarding whether packets were acked at the + /// link layer. + pub const PACKET_TRANSMITTED: usize = 1; + /// Number of upcalls. + pub const COUNT: u8 = 2; +} + /// Ids for read-only allow buffers mod ro_allow { + /// Write buffer. Contains the UDP payload to be transmitted. Returns SIZE + /// if the passed buffer is too long, and NOSUPPORT if an invalid + /// `allow_num` is passed. pub const WRITE: usize = 0; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; + pub const COUNT: u8 = 1; } /// Ids for read-write allow buffers mod rw_allow { + /// Read buffer. Will contain the received payload. pub const READ: usize = 0; + /// Config buffer. Used to contain miscellaneous data associated with some + /// commands, namely source/destination addresses and ports. pub const CFG: usize = 1; + /// Rx config buffer. Used to contain source/destination addresses and ports + /// for receives (separate from `2` because receives may be waiting for an + /// incoming packet asynchronously). pub const RX_CFG: usize = 2; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 3; + pub const COUNT: u8 = 3; } #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -99,7 +125,7 @@ pub struct UDPDriver<'a> { /// Grant of apps that use this radio driver. apps: Grant< App, - UpcallCount<2>, + UpcallCount<{ upcall::COUNT }>, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<{ rw_allow::COUNT }>, >, @@ -115,7 +141,7 @@ pub struct UDPDriver<'a> { /// UDP bound port table (manages kernel bindings) port_table: &'static UdpPortManager, - kernel_buffer: MapCell>, + kernel_buffer: MapCell>, driver_send_cap: &'static dyn UdpDriverCapability, @@ -127,27 +153,27 @@ impl<'a> UDPDriver<'a> { sender: &'a dyn UDPSender<'a>, grant: Grant< App, - UpcallCount<2>, + UpcallCount<{ upcall::COUNT }>, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<{ rw_allow::COUNT }>, >, interface_list: &'static [IPAddr], max_tx_pyld_len: usize, port_table: &'static UdpPortManager, - kernel_buffer: LeasableBuffer<'static, u8>, + kernel_buffer: SubSliceMut<'static, u8>, driver_send_cap: &'static dyn UdpDriverCapability, net_cap: &'static NetworkCapability, ) -> UDPDriver<'a> { UDPDriver { - sender: sender, + sender, apps: grant, current_app: Cell::new(None), - interface_list: interface_list, - max_tx_pyld_len: max_tx_pyld_len, - port_table: port_table, + interface_list, + max_tx_pyld_len, + port_table, kernel_buffer: MapCell::new(kernel_buffer), - driver_send_cap: driver_send_cap, - net_cap: net_cap, + driver_send_cap, + net_cap, } } @@ -160,10 +186,10 @@ impl<'a> UDPDriver<'a> { } let mut pending_app = None; for app in self.apps.iter() { - let appid = app.processid(); + let processid = app.processid(); app.enter(|app, _| { if app.pending_tx.is_some() { - pending_app = Some(appid); + pending_app = Some(processid); } }); if pending_app.is_some() { @@ -173,28 +199,31 @@ impl<'a> UDPDriver<'a> { pending_app } - /// Performs `appid`'s pending transmission asynchronously. If the + /// Performs `processid`'s pending transmission asynchronously. If the /// transmission is not successful, the error is returned to the app via its /// `tx_callback`. Assumes that the driver is currently idle and the app has /// a pending transmission. #[inline] - fn perform_tx_async(&self, appid: ProcessId) { - let result = self.perform_tx_sync(appid); + fn perform_tx_async(&self, processid: ProcessId) { + let result = self.perform_tx_sync(processid); if result != Ok(()) { - let _ = self.apps.enter(appid, |_app, upcalls| { + let _ = self.apps.enter(processid, |_app, upcalls| { upcalls - .schedule_upcall(1, (kernel::errorcode::into_statuscode(result), 0, 0)) + .schedule_upcall( + upcall::PACKET_TRANSMITTED, + (kernel::errorcode::into_statuscode(result), 0, 0), + ) .ok(); }); } } - /// Performs `appid`'s pending transmission synchronously. The result is + /// Performs `processid`'s pending transmission synchronously. The result is /// returned immediately to the app. Assumes that the driver is currently /// idle and the app has a pending transmission. #[inline] - fn perform_tx_sync(&self, appid: ProcessId) -> Result<(), ErrorCode> { - self.apps.enter(appid, |app, kernel_data| { + fn perform_tx_sync(&self, processid: ProcessId) -> Result<(), ErrorCode> { + self.apps.enter(processid, |app, kernel_data| { let addr_ports = match app.pending_tx.take() { Some(pending_tx) => pending_tx, None => { @@ -215,6 +244,7 @@ impl<'a> UDPDriver<'a> { Err(ErrorCode::NOMEM), |mut kernel_buffer| { if payload.len() > kernel_buffer.len() { + self.kernel_buffer.replace(kernel_buffer); return Err(ErrorCode::SIZE); } payload.copy_to_slice(&mut kernel_buffer[0..payload.len()]); @@ -227,7 +257,7 @@ impl<'a> UDPDriver<'a> { self.driver_send_cap, self.net_cap, ) { - Ok(_) => Ok(()), + Ok(()) => Ok(()), Err(mut buf) => { buf.reset(); self.kernel_buffer.replace(buf); @@ -240,7 +270,7 @@ impl<'a> UDPDriver<'a> { }) .unwrap_or(Err(ErrorCode::NOMEM)); if result == Ok(()) { - self.current_app.set(Some(appid)); + self.current_app.set(Some(processid)); } result })? @@ -252,7 +282,7 @@ impl<'a> UDPDriver<'a> { #[allow(dead_code)] fn do_next_tx_queued(&self) { self.get_next_tx_if_idle() - .map(|appid| self.perform_tx_async(appid)); + .map(|processid| self.perform_tx_async(processid)); } /// Schedule the next transmission if there is one pending. If the next @@ -261,17 +291,17 @@ impl<'a> UDPDriver<'a> { /// On the other hand, if it is some other app, then return any errors via /// callbacks. #[inline] - fn do_next_tx_immediate(&self, new_appid: ProcessId) -> Result { - self.get_next_tx_if_idle().map_or(Ok(0), |appid| { - if appid == new_appid { - let sync_result = self.perform_tx_sync(appid); + fn do_next_tx_immediate(&self, new_processid: ProcessId) -> Result { + self.get_next_tx_if_idle().map_or(Ok(0), |processid| { + if processid == new_processid { + let sync_result = self.perform_tx_sync(processid); if sync_result == Ok(()) { Ok(1) //Indicates packet passed to radio } else { Err(ErrorCode::try_from(sync_result).unwrap()) } } else { - self.perform_tx_async(appid); + self.perform_tx_async(processid); Ok(0) //indicates async transmission } }) @@ -292,7 +322,7 @@ impl<'a> UDPDriver<'a> { addr.0.copy_from_slice(a); let pair = UDPEndpoint { - addr: addr, + addr, port: host_slice_to_u16(p), }; Some(pair) @@ -301,42 +331,11 @@ impl<'a> UDPDriver<'a> { } impl<'a> SyscallDriver for UDPDriver<'a> { - /// Setup buffers to read/write from. - /// - /// ### `allow_num` - /// - /// - `0`: Read buffer. Will contain the received payload. - /// - `1`: Config buffer. Used to contain miscellaneous data associated with - /// some commands, namely source/destination addresses and ports. - /// - `2`: Rx config buffer. Used to contain source/destination addresses - /// and ports for receives (separate from `2` because receives may - /// be waiting for an incoming packet asynchronously). - - /// Setup shared buffers. - /// - /// ### `allow_num` - /// - /// - `0`: Write buffer. Contains the UDP payload to be transmitted. - /// Returns SIZE if the passed buffer is too long, and NOSUPPORT - /// if an invalid `allow_num` is passed. - - // Setup callbacks. - // - // ### `subscribe_num` - // - // - `0`: Setup callback for when packet is received. If no port has - // been bound, return RESERVE to indicate that port binding is - // is a prerequisite to reception. - // - `1`: Setup callback for when packet is transmitted. Notably, - // this callback receives the result of the send_done callback - // from udp_send.rs, which does not currently pass information - // regarding whether packets were acked at the link layer. - /// UDP control /// /// ### `command_num` /// - /// - `0`: Driver check. + /// - `0`: Driver existence check. /// - `1`: Get the interface list /// app_cfg (out): 16 * `n` bytes: the list of interface IPv6 addresses, length /// limited by `app_cfg` length. @@ -353,7 +352,7 @@ impl<'a> SyscallDriver for UDPDriver<'a> { /// the packet was passed to the radio. However, if Success_U32 /// is returned with value 1, this means the the packet was successfully passed /// the radio without any errors, which tells the userland application that it does - /// not need to wait for a callback to check if any errors occured while the packet + /// not need to wait for a callback to check if any errors occurred while the packet /// was being passed down to the radio. Any successful return value indicates that /// the app should wait for a send_done() callback before attempting to queue another /// packet. @@ -383,7 +382,7 @@ impl<'a> SyscallDriver for UDPDriver<'a> { command_num: usize, arg1: usize, _: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { match command_num { 0 => CommandReturn::success(), @@ -392,7 +391,7 @@ impl<'a> SyscallDriver for UDPDriver<'a> { // `arg1`: number of interfaces requested that will fit into the buffer 1 => { self.apps - .enter(appid, |_, kernel_data| { + .enter(processid, |_, kernel_data| { kernel_data .get_readwrite_processbuffer(rw_allow::CFG) .and_then(|cfg| { @@ -420,7 +419,7 @@ impl<'a> SyscallDriver for UDPDriver<'a> { 2 => { let res = self .apps - .enter(appid, |app, kernel_data| { + .enter(processid, |app, kernel_data| { if app.pending_tx.is_some() { // Cannot support more than one pending tx per process. return Err(ErrorCode::BUSY); @@ -449,7 +448,7 @@ impl<'a> SyscallDriver for UDPDriver<'a> { &tmp_cfg_buffer[..size_of::()], ), ) { - if Some(src.clone()) == app.bound_port { + if Some(src) == app.bound_port { Some([src, dst]) } else { None @@ -468,8 +467,8 @@ impl<'a> SyscallDriver for UDPDriver<'a> { }) .unwrap_or_else(|err| Err(err.into())); match res { - Ok(_) => self.do_next_tx_immediate(appid).map_or_else( - |err| CommandReturn::failure(err.into()), + Ok(()) => self.do_next_tx_immediate(processid).map_or_else( + |err| CommandReturn::failure(err), |v| CommandReturn::success_u32(v), ), Err(e) => CommandReturn::failure(e), @@ -478,7 +477,7 @@ impl<'a> SyscallDriver for UDPDriver<'a> { 3 => { let err = self .apps - .enter(appid, |app, kernel_data| { + .enter(processid, |app, kernel_data| { // Move UDPEndpoint into udp.rs? let requested_addr_opt = kernel_data .get_readwrite_processbuffer(rw_allow::RX_CFG) @@ -492,13 +491,7 @@ impl<'a> SyscallDriver for UDPDriver<'a> { cfg[mem::size_of::()..] .copy_to_slice(&mut tmp_endpoint); - if let Some(local_iface) = - self.parse_ip_port_pair(&tmp_endpoint) - { - Some(local_iface) - } else { - None - } + self.parse_ip_port_pair(&tmp_endpoint) } }) }) @@ -533,7 +526,7 @@ impl<'a> SyscallDriver for UDPDriver<'a> { CommandReturn::failure(ErrorCode::BUSY) } else { self.apps - .enter(appid, |app, _| { + .enter(processid, |app, _| { // The requested addr is free and valid app.bound_port = Some(requested_addr); CommandReturn::success() @@ -543,7 +536,7 @@ impl<'a> SyscallDriver for UDPDriver<'a> { }) } } - Err(_) => CommandReturn::failure(ErrorCode::FAIL), //error in port table + Err(()) => CommandReturn::failure(ErrorCode::FAIL), //error in port table } }) } @@ -561,14 +554,17 @@ impl<'a> SyscallDriver for UDPDriver<'a> { } impl<'a> UDPSendClient for UDPDriver<'a> { - fn send_done(&self, result: Result<(), ErrorCode>, mut dgram: LeasableBuffer<'static, u8>) { + fn send_done(&self, result: Result<(), ErrorCode>, mut dgram: SubSliceMut<'static, u8>) { // Replace the returned kernel buffer. Now we can send the next msg. dgram.reset(); self.kernel_buffer.replace(dgram); - self.current_app.get().map(|appid| { - let _ = self.apps.enter(appid, |_app, upcalls| { + self.current_app.get().map(|processid| { + let _ = self.apps.enter(processid, |_app, upcalls| { upcalls - .schedule_upcall(1, (kernel::errorcode::into_statuscode(result), 0, 0)) + .schedule_upcall( + upcall::PACKET_TRANSMITTED, + (kernel::errorcode::into_statuscode(result), 0, 0), + ) .ok(); }); }); @@ -615,7 +611,9 @@ impl<'a> UDPRecvClient for UDPDriver<'a> { addr: src_addr, port: src_port, }; - kernel_data.schedule_upcall(0, (len, 0, 0)).ok(); + kernel_data + .schedule_upcall(upcall::PACKET_RECEIVED, (len, 0, 0)) + .ok(); const CFG_LEN: usize = 2 * size_of::(); let _ = kernel_data .get_readwrite_processbuffer(rw_allow::RX_CFG) @@ -645,7 +643,7 @@ impl<'a> PortQuery for UDPDriver<'a> { for app in self.apps.iter() { app.enter(|other_app, _| { if other_app.bound_port.is_some() { - let other_addr_opt = other_app.bound_port.clone(); + let other_addr_opt = other_app.bound_port; let other_addr = other_addr_opt.unwrap(); // Unwrap fail = Missing other_addr if other_addr.port == port { port_bound = true; diff --git a/capsules/src/net/udp/mod.rs b/capsules/extra/src/net/udp/mod.rs similarity index 66% rename from capsules/src/net/udp/mod.rs rename to capsules/extra/src/net/udp/mod.rs index 654c0a4631..85dd139486 100644 --- a/capsules/src/net/udp/mod.rs +++ b/capsules/extra/src/net/udp/mod.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + pub mod driver; pub mod udp_port_table; pub mod udp_recv; diff --git a/capsules/src/net/udp/udp.rs b/capsules/extra/src/net/udp/udp.rs similarity index 94% rename from capsules/src/net/udp/udp.rs rename to capsules/extra/src/net/udp/udp.rs index 30cca5938e..1d0ef8c978 100644 --- a/capsules/src/net/udp/udp.rs +++ b/capsules/extra/src/net/udp/udp.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! This file contains the structs and methods associated with the UDP header. //! This includes getters and setters for the various header fields, as well //! as the standard encode/decode functionality required for serializing @@ -14,10 +18,10 @@ use crate::net::stream::SResult; /// for the various fields of the header, to avoid confusion with endian-ness. #[derive(Copy, Clone, Debug)] pub struct UDPHeader { - pub src_port: u16, - pub dst_port: u16, - pub len: u16, - pub cksum: u16, + src_port: u16, + dst_port: u16, + len: u16, + cksum: u16, } impl Default for UDPHeader { diff --git a/capsules/src/net/udp/udp_port_table.rs b/capsules/extra/src/net/udp/udp_port_table.rs similarity index 92% rename from capsules/src/net/udp/udp_port_table.rs rename to capsules/extra/src/net/udp/udp_port_table.rs index 5fa575e232..21f9a626b7 100644 --- a/capsules/src/net/udp/udp_port_table.rs +++ b/capsules/extra/src/net/udp/udp_port_table.rs @@ -1,18 +1,22 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! In-kernel structure for tracking UDP ports bound by capsules. //! //! When kernel capsules wish to send or receive UDP packets, the UDP sending / receiving //! capsules will only allow this if the capsule has bound to the port it wishes to -//! send from / receive on. Binding to a port is accompished via calls on the +//! send from / receive on. Binding to a port is accomplished via calls on the //! `UdpPortManager` struct defined in this file. Calls to bind on this table enforce that only -//! one capsule can be bound to a given port at any time. Once capsules succesfully bind +//! one capsule can be bound to a given port at any time. Once capsules successfully bind //! using this table, they receive back binding structures (`UdpPortBindingTx`/`UdpPortBindingRx`) //! that act as proof that the holder //! is bound to that port. These structures can only be created within this file, and calls //! to unbind must consume these structures, enforcing this invariant. //! The UDP tx/rx capsules require these bindings be passed in order to send/receive on a given -//! port. Seperate bindings are used for sending and receiving because the UdpReceiver must +//! port. Separate bindings are used for sending and receiving because the UdpReceiver must //! hold onto the binding for as long as a capsule wishes to receive packets on a port, so -//! a seperate binding must be available to enable sending packets on a port while +//! a separate binding must be available to enable sending packets on a port while //! listening on the same port. //! //! To reduce the size of data structures required for this task, a fixed size @@ -25,7 +29,7 @@ //! The files `udp_send.rs` and `udp_recv.rs` enforce that only capsules possessing //! the correct bindings can actually send / recv on a given port. //! -//! Userspace port bindings are managed seperately by the userspace UDP driver +//! Userspace port bindings are managed separately by the userspace UDP driver //! (`capsules/src/net/udp/driver.rs`), because apps can be dynamically added or //! removed. Bindings for userspace apps are stored in the grant regions of each app, //! such that removing an app automatically unbinds it. This file is able to query the @@ -92,7 +96,7 @@ impl UdpSocket { // obtain access to ports bound by other capsules fn new(idx: usize, pt: &'static UdpPortManager) -> UdpSocket { UdpSocket { - idx: idx, + idx, port_table: pt, } } @@ -122,10 +126,7 @@ pub struct UdpPortBindingTx { impl UdpPortBindingTx { fn new(idx: usize, port: u16) -> UdpPortBindingTx { - UdpPortBindingTx { - idx: idx, - port: port, - } + UdpPortBindingTx { idx, port } } pub fn get_port(&self) -> u16 { @@ -135,10 +136,7 @@ impl UdpPortBindingTx { impl UdpPortBindingRx { fn new(idx: usize, port: u16) -> UdpPortBindingRx { - UdpPortBindingRx { - idx: idx, - port: port, - } + UdpPortBindingRx { idx, port } } pub fn get_port(&self) -> u16 { @@ -156,7 +154,7 @@ impl UdpPortManager { UdpPortManager { port_array: TakeCell::new(used_kernel_ports), user_ports: OptionalCell::empty(), - udp_vis: udp_vis, + udp_vis, } } @@ -181,7 +179,7 @@ impl UdpPortManager { for i in 0..MAX_NUM_BOUND_PORTS { match table[i] { None => { - result = Ok(UdpSocket::new(i, &self)); + result = Ok(UdpSocket::new(i, self)); table[i] = Some(SocketBindingEntry::Unbound); break; } @@ -196,7 +194,7 @@ impl UdpPortManager { /// The slot in the table is only freed if the socket that is dropped is /// unbound. If the slot is bound, the socket is being dropped after a call to /// bind(), and the slot in the table should remain reserved. - fn destroy_socket(&self, socket: &mut UdpSocket) { + fn destroy_socket(&self, socket: &UdpSocket) { self.port_array.map(|table| match table[socket.idx] { Some(entry) => { if entry == SocketBindingEntry::Unbound { @@ -216,7 +214,6 @@ impl UdpPortManager { let user_bound = self .user_ports .map_or(true, |port_query| port_query.is_bound(port)); - if self.user_ports.is_none() {} if user_bound { return Ok(true); }; @@ -270,7 +267,7 @@ impl UdpPortManager { .unwrap() } } - Err(_) => Err(socket), + Err(()) => Err(socket), } } else { Err(socket) @@ -284,7 +281,7 @@ impl UdpPortManager { sender_binding: UdpPortBindingTx, receiver_binding: UdpPortBindingRx, ) -> Result { - // Verfify that the indices match up + // Verify that the indices match up if sender_binding.idx != receiver_binding.idx { return Err((sender_binding, receiver_binding)); } @@ -293,6 +290,6 @@ impl UdpPortManager { table[idx] = Some(SocketBindingEntry::Unbound); }); // Search the list and return the appropriate socket - Ok(UdpSocket::new(idx, &self)) + Ok(UdpSocket::new(idx, self)) } } diff --git a/capsules/src/net/udp/udp_recv.rs b/capsules/extra/src/net/udp/udp_recv.rs similarity index 95% rename from capsules/src/net/udp/udp_recv.rs rename to capsules/extra/src/net/udp/udp_recv.rs index 26586ac22c..0a8fc7456b 100644 --- a/capsules/src/net/udp/udp_recv.rs +++ b/capsules/extra/src/net/udp/udp_recv.rs @@ -1,7 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! This file contains the definition and implementation for the UDP reception //! interface. It follows the same virtualization model as that described in `udp_send.rs`, //! except that no queueing is needed because received packets are immediately dispatched to the -//! appropriate capsule / app. Once again, port binding for userspace apps is managed seperately +//! appropriate capsule / app. Once again, port binding for userspace apps is managed separately //! by the UDP userspace driver, which must correctly check bindings of kernel apps to ensure //! correctness when dispatching received packets to the appropriate client. @@ -110,7 +114,7 @@ pub trait UDPRecvClient { /// This struct is set as the client of the MuxUdpReceiver, and passes /// received packets up to whatever app layer client assigns itself -/// as the UDPRecvClient held by this UDPReciever. +/// as the UDPRecvClient held by this UDPReceiver. pub struct UDPReceiver<'a> { client: OptionalCell<&'a dyn UDPRecvClient>, binding: MapCell, diff --git a/capsules/src/net/udp/udp_send.rs b/capsules/extra/src/net/udp/udp_send.rs similarity index 93% rename from capsules/src/net/udp/udp_send.rs rename to capsules/extra/src/net/udp/udp_send.rs index 6314089883..b5538bf59a 100644 --- a/capsules/src/net/udp/udp_send.rs +++ b/capsules/extra/src/net/udp/udp_send.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! This file contains the definition and implementation for a virtualized UDP //! sending interface. The [UDPSender](trait.UDPSender.html) trait provides //! an interface for kernel capsules to send a UDP packet, and the @@ -28,7 +32,7 @@ use kernel::capabilities::UdpDriverCapability; use kernel::collections::list::{List, ListLink, ListNode}; use kernel::debug; use kernel::utilities::cells::{MapCell, OptionalCell}; -use kernel::utilities::leasable_buffer::LeasableBuffer; +use kernel::utilities::leasable_buffer::SubSliceMut; use kernel::ErrorCode; pub struct MuxUdpSender<'a, T: IP6Sender<'a>> { @@ -149,7 +153,7 @@ impl<'a, T: IP6Sender<'a>> IP6SendClient for MuxUdpSender<'a, T> { /// has completed sending the requested packet. Note that the /// `UDPSender::set_client` method must be called to set the client. pub trait UDPSendClient { - fn send_done(&self, result: Result<(), ErrorCode>, dgram: LeasableBuffer<'static, u8>); + fn send_done(&self, result: Result<(), ErrorCode>, dgram: SubSliceMut<'static, u8>); } /// This trait represents the bulk of the UDP functionality. The two @@ -183,9 +187,9 @@ pub trait UDPSender<'a> { dest: IPAddr, dst_port: u16, //src_port: u16, - buf: LeasableBuffer<'static, u8>, + buf: SubSliceMut<'static, u8>, net_cap: &'static NetworkCapability, - ) -> Result<(), LeasableBuffer<'static, u8>>; + ) -> Result<(), SubSliceMut<'static, u8>>; /// This function is identical to `send_to()` except that it takes in /// an explicit src_port instead of a binding. This allows it to be used @@ -205,10 +209,10 @@ pub trait UDPSender<'a> { dest: IPAddr, dst_port: u16, src_port: u16, - buf: LeasableBuffer<'static, u8>, + buf: SubSliceMut<'static, u8>, driver_send_cap: &dyn UdpDriverCapability, net_cap: &'static NetworkCapability, - ) -> Result<(), LeasableBuffer<'static, u8>>; + ) -> Result<(), SubSliceMut<'static, u8>>; /// This function constructs an IP packet from the completed `UDPHeader` /// and buffer, and sends it to the provided IP address @@ -225,9 +229,9 @@ pub trait UDPSender<'a> { &'a self, dest: IPAddr, udp_header: UDPHeader, - buf: LeasableBuffer<'static, u8>, + buf: SubSliceMut<'static, u8>, net_cap: &'static NetworkCapability, - ) -> Result<(), LeasableBuffer<'static, u8>>; + ) -> Result<(), SubSliceMut<'static, u8>>; fn get_binding(&self) -> Option; @@ -243,7 +247,7 @@ pub struct UDPSendStruct<'a, T: IP6Sender<'a>> { udp_mux_sender: &'a MuxUdpSender<'a, T>, client: OptionalCell<&'a dyn UDPSendClient>, next: ListLink<'a, UDPSendStruct<'a, T>>, - tx_buffer: MapCell>, + tx_buffer: MapCell>, next_dest: Cell, next_th: OptionalCell, binding: MapCell, @@ -268,9 +272,9 @@ impl<'a, T: IP6Sender<'a>> UDPSender<'a> for UDPSendStruct<'a, T> { &'a self, dest: IPAddr, dst_port: u16, - buf: LeasableBuffer<'static, u8>, + buf: SubSliceMut<'static, u8>, net_cap: &'static NetworkCapability, - ) -> Result<(), LeasableBuffer<'static, u8>> { + ) -> Result<(), SubSliceMut<'static, u8>> { let mut udp_header = UDPHeader::new(); udp_header.set_dst_port(dst_port); match self.binding.take() { @@ -299,10 +303,10 @@ impl<'a, T: IP6Sender<'a>> UDPSender<'a> for UDPSendStruct<'a, T> { dest: IPAddr, dst_port: u16, src_port: u16, - buf: LeasableBuffer<'static, u8>, + buf: SubSliceMut<'static, u8>, _driver_send_cap: &dyn UdpDriverCapability, net_cap: &'static NetworkCapability, - ) -> Result<(), LeasableBuffer<'static, u8>> { + ) -> Result<(), SubSliceMut<'static, u8>> { let mut udp_header = UDPHeader::new(); udp_header.set_dst_port(dst_port); udp_header.set_src_port(src_port); @@ -313,9 +317,9 @@ impl<'a, T: IP6Sender<'a>> UDPSender<'a> for UDPSendStruct<'a, T> { &'a self, dest: IPAddr, mut udp_header: UDPHeader, - buf: LeasableBuffer<'static, u8>, + buf: SubSliceMut<'static, u8>, net_cap: &'static NetworkCapability, - ) -> Result<(), LeasableBuffer<'static, u8>> { + ) -> Result<(), SubSliceMut<'static, u8>> { udp_header.set_len((buf.len() + udp_header.get_hdr_size()) as u16); let transport_header = TransportHeader::UDP(udp_header); self.tx_buffer.replace(buf); @@ -323,7 +327,7 @@ impl<'a, T: IP6Sender<'a>> UDPSender<'a> for UDPSendStruct<'a, T> { self.next_th.replace(transport_header); // th = transport header match self .udp_mux_sender - .send_to(dest, transport_header, &self, net_cap) + .send_to(dest, transport_header, self, net_cap) { Ok(()) => Ok(()), _ => Err(self.tx_buffer.take().unwrap()), @@ -349,14 +353,14 @@ impl<'a, T: IP6Sender<'a>> UDPSendStruct<'a, T> { udp_vis: &'static UdpVisibilityCapability, ) -> UDPSendStruct<'a, T> { UDPSendStruct { - udp_mux_sender: udp_mux_sender, + udp_mux_sender, client: OptionalCell::empty(), next: ListLink::empty(), tx_buffer: MapCell::empty(), next_dest: Cell::new(IPAddr::new()), next_th: OptionalCell::empty(), binding: MapCell::empty(), - udp_vis: udp_vis, + udp_vis, net_cap: OptionalCell::empty(), } } diff --git a/capsules/src/net/util.rs b/capsules/extra/src/net/util.rs similarity index 88% rename from capsules/src/net/util.rs rename to capsules/extra/src/net/util.rs index 243947d3d4..3459dfcbea 100644 --- a/capsules/src/net/util.rs +++ b/capsules/extra/src/net/util.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Utility functions used in the 6LoWPAN implementation /// Verifies that a prefix given in the form of a byte array slice is valid with @@ -9,7 +13,7 @@ pub fn verify_prefix_len(prefix: &[u8], prefix_len: u8) -> bool { let full_bytes = (prefix_len / 8) as usize; let remaining_bits = prefix_len % 8; - let bytes = full_bytes + if remaining_bits != 0 { 1 } else { 0 }; + let bytes = full_bytes + usize::from(remaining_bits != 0); if bytes > prefix.len() { return false; @@ -32,7 +36,7 @@ pub fn verify_prefix_len(prefix: &[u8], prefix_len: u8) -> bool { pub fn matches_prefix(buf1: &[u8], buf2: &[u8], prefix_len: u8) -> bool { let full_bytes = (prefix_len / 8) as usize; let remaining_bits = prefix_len % 8; - let bytes = full_bytes + if remaining_bits != 0 { 1 } else { 0 }; + let bytes = full_bytes + usize::from(remaining_bits != 0); if bytes > buf1.len() || bytes > buf2.len() { return false; diff --git a/capsules/extra/src/nina_w102.rs b/capsules/extra/src/nina_w102.rs new file mode 100644 index 0000000000..4d8bb8f5ff --- /dev/null +++ b/capsules/extra/src/nina_w102.rs @@ -0,0 +1,1366 @@ +use core::cell::Cell; + +use kernel::debug; +use kernel::hil::gpio::Pin; +use kernel::hil::spi::{SpiMaster, SpiMasterClient}; +use kernel::hil::time::AlarmClient; +use kernel::hil::time::{Alarm, ConvertTicks}; +use kernel::hil::wifinina::{self, Psk, Ssid, Station, StationClient, StationStatus}; +use kernel::hil::wifinina::{Network, Scanner, ScannerClient}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::ErrorCode; + +const START_CMD: u8 = 0xe0; +const END_CMD: u8 = 0xee; +const ERROR_CMD: u8 = 0xef; + +const POS_CMD: usize = 1; +const POS_PARAM_LEN: usize = 2; +const POS_LEN: usize = 2; +const POS_PARAM: usize = 3; + +const REPLY_FLAG: u8 = 1 << 7; + +#[repr(u8)] +#[derive(Copy, Clone, PartialEq, Debug)] +enum SockMode { + TcpMode, + UdpMode, + TlsMode, + UdpMulticastMode, + TlsBearsslMode, +} + +#[repr(u8)] +#[derive(Copy, Clone, PartialEq, Debug)] +enum TcpState { + TCPStateClosed, + TCPStateListen, + TCPStateSynSent, + TCPStateSynRcvd, + TCPStateEstablished, + TCPStateFinWait1, + TCPStateFinWait2, + TCPStateCloseWait, + TCPStateClosing, + TCPStateLastACK, + TCPStateTimeWait, +} + +#[repr(u8)] +#[derive(Copy, Clone, PartialEq, Debug)] +enum Command { + SetNetCmd = 0x10, + SetPassPhraseCmd = 0x11, + GetConnStatusCmd = 0x20, + GetIpAddressCmd = 0x21, + GetMacAddressCmd = 0x22, + ScanNetworksCmd = 0x27, + StartTcpServer = 0x28, + StartTcpClient = 0x2D, + StopTcpClient = 0x2E, + GetClientStateTCP = 0x2F, + GetIdxRSSICmd = 0x32, + StartScanNetworksCmd = 0x36, + GetFwVersion = 0x37, + SendUdpPacket = 0x39, + SendPing = 0x3E, + GetSocket = 0x3F, + InsertDataBuf = 0x46, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +enum InitStatus { + Starting, + Initialized, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +enum Status { + Idle, + Init(InitStatus), + Send(Command), + Receive(Command, usize, usize), + StartScanNetworks, + ScanNetworks, + GetConnStatus, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum ConnectionStatus { + Idle = 0, + NoSSIDAvail = 1, + ScanCompleted = 2, + Connected = 3, + ConnectFailed = 4, + ConnectionLost = 5, + Disconnected = 6, + NoShield = 255, + NoConnection, +} + +pub struct NinaW102<'a, S: SpiMaster<'a>, P: Pin, A: Alarm<'a>> { + spi: &'a S, + write_buffer: TakeCell<'static, [u8]>, + read_buffer: TakeCell<'static, [u8]>, + one_byte_read_buffer: TakeCell<'static, [u8]>, + cs: &'a P, + ready: &'a P, + reset: &'a P, + gpio0: &'a P, + alarm: &'a A, + status: Cell, + station_status: Cell, + networks: TakeCell<'static, [Network]>, + scanner_client: OptionalCell<&'a dyn ScannerClient>, + station_client: OptionalCell<&'a dyn StationClient>, + second_time: Cell, +} + +impl<'a, S: SpiMaster<'a>, P: Pin, A: Alarm<'a>> NinaW102<'a, S, P, A> { + pub fn new( + spi: &'a S, + write_buffer: &'static mut [u8], + read_buffer: &'static mut [u8], + one_byte_read_buffer: &'static mut [u8], + cs: &'a P, + ready: &'a P, + reset: &'a P, + gpio0: &'a P, + alarm: &'a A, + networks: &'static mut [Network], + ) -> Self { + cs.make_output(); + ready.make_input(); + reset.make_output(); + gpio0.make_output(); + + NinaW102 { + spi, + write_buffer: TakeCell::new(write_buffer), + read_buffer: TakeCell::new(read_buffer), + one_byte_read_buffer: TakeCell::new(one_byte_read_buffer), + cs, + ready, + reset, + gpio0, + alarm, + status: Cell::new(Status::Idle), + station_status: Cell::new(StationStatus::Disconnected), + networks: TakeCell::new(networks), + scanner_client: OptionalCell::empty(), + station_client: OptionalCell::empty(), + second_time: Cell::new(0), + } + } + + pub fn init(&self) -> Result<(), ErrorCode> { + self.cs.set(); + self.reset.clear(); + self.gpio0.set(); + self.status.set(Status::Init(InitStatus::Starting)); + + self.alarm + .set_alarm(self.alarm.now(), self.alarm.ticks_from_ms(10)); + Ok(()) + } + + pub fn number_to_connection_status(&self, status: usize) -> ConnectionStatus { + match status { + 0 => ConnectionStatus::Idle, + 1 => ConnectionStatus::NoSSIDAvail, + 2 => ConnectionStatus::ScanCompleted, + 3 => ConnectionStatus::Connected, + 4 => ConnectionStatus::ConnectFailed, + 5 => ConnectionStatus::ConnectionLost, + 6 => ConnectionStatus::Disconnected, + 255 => ConnectionStatus::NoShield, + _ => ConnectionStatus::NoConnection, + } + } + + pub fn get_firmware_version(&self) -> Result<(), ErrorCode> { + if self.status.get() == Status::Idle { + self.send_command(Command::GetFwVersion, &[]) + } else { + Err(ErrorCode::BUSY) + } + } + + pub fn scan_networks(&self) -> Result<(), ErrorCode> { + if self.status.get() == Status::Idle || self.status.get() == Status::ScanNetworks { + self.send_command(Command::ScanNetworksCmd, &[]) + } else { + Err(ErrorCode::BUSY) + } + } + + pub fn start_scan_networks(&self) -> Result<(), ErrorCode> { + if self.status.get() == Status::Idle || self.status.get() == Status::StartScanNetworks { + self.send_command(Command::StartScanNetworksCmd, &[]) + } else { + Err(ErrorCode::BUSY) + } + } + + pub fn send_ping(&self) -> Result<(), ErrorCode> { + if self.status.get() == Status::Idle { + self.send_command(Command::SendPing, &[&[172, 20, 10, 7], &[128]]) + } else { + Err(ErrorCode::BUSY) + } + } + + pub fn start_tcp_server(&self, socket: u8) -> Result<(), ErrorCode> { + if self.status.get() == Status::Idle { + self.send_command( + Command::StartTcpServer, + &[&[0x9, 0x56], &[socket], &[SockMode::UdpMode as u8]], + ) + } else { + Err(ErrorCode::BUSY) + } + } + + pub fn start_tcp_client(&self, socket: u8) -> Result<(), ErrorCode> { + if self.status.get() == Status::Idle { + // open socket connection to 172.20.10.7:3000; + // 192.168.100.175 + // &[175, 100, 168, 192], + self.send_command( + Command::StartTcpClient, + &[ + &[191, 101, 164, 144], + // &[172, 20, 10, 7], + &[0xff, 0x9b], + &[socket], + &[SockMode::UdpMode as u8], + ], + ) + } else { + Err(ErrorCode::BUSY) + } + } + + pub fn stop_tcp_client(&self, socket: u8) -> Result<(), ErrorCode> { + if self.status.get() == Status::Idle { + self.send_command(Command::StopTcpClient, &[&[socket]]) + } else { + Err(ErrorCode::BUSY) + } + } + + pub fn insert_data_buf(&self, socket: u8) -> Result<(), ErrorCode> { + if self.status.get() == Status::Idle { + // Inserting buffer "Buna!" + self.send_command( + Command::InsertDataBuf, + &[ + &[socket], + &[ + 0x42, 0x75, 0x6e, 0x61, 0x20, 0x64, 0x69, 0x6e, 0x20, 0x54, 0x6f, 0x63, + 0x6B, 0x21, 0xa, + ], + ], + ) + } else { + Err(ErrorCode::BUSY) + } + } + + pub fn get_networks_rssi(&self) -> Result<(), ErrorCode> { + if self.status.get() == Status::Idle { + self.send_command(Command::GetIdxRSSICmd, &[]) + } else { + Err(ErrorCode::BUSY) + } + } + + pub fn get_connection_status(&self) -> Result<(), ErrorCode> { + if self.status.get() == Status::Idle { + self.send_command(Command::GetConnStatusCmd, &[]) + } else { + Err(ErrorCode::BUSY) + } + } + + pub fn get_ip_address(&self) -> Result<(), ErrorCode> { + if self.status.get() == Status::Idle { + self.send_command(Command::GetIpAddressCmd, &[&[0xff]]) + } else { + Err(ErrorCode::BUSY) + } + } + + pub fn get_mac_address(&self) -> Result<(), ErrorCode> { + if self.status.get() == Status::Idle { + self.send_command(Command::GetMacAddressCmd, &[&[0xff]]) + } else { + Err(ErrorCode::BUSY) + } + } + + pub fn get_socket(&self) -> Result<(), ErrorCode> { + if self.status.get() == Status::Idle { + self.send_command(Command::GetSocket, &[]) + } else { + Err(ErrorCode::BUSY) + } + } + + pub fn get_tcp_client_state(&self, socket: u8) -> Result<(), ErrorCode> { + if self.status.get() == Status::Idle { + self.send_command(Command::GetClientStateTCP, &[&[socket]]) + } else { + Err(ErrorCode::BUSY) + } + } + + pub fn set_network(&self) -> Result<(), ErrorCode> { + if self.status.get() == Status::Idle { + self.send_command(Command::SetNetCmd, &[]) + } else { + Err(ErrorCode::BUSY) + } + } + + pub fn set_passphrase(&self, ssid: &[u8], psk: &[u8]) -> Result<(), ErrorCode> { + if self.status.get() == Status::Idle { + self.send_command(Command::SetPassPhraseCmd, &[ssid, psk]) + } else { + Err(ErrorCode::BUSY) + } + } + + pub fn send_udp_packet(&self, socket: u8) -> Result<(), ErrorCode> { + if self.status.get() == Status::Idle { + self.send_command(Command::SendUdpPacket, &[&[socket]]) + } else { + Err(ErrorCode::BUSY) + } + } + + fn wait_for_chip_ready(&self) -> Result<(), ErrorCode> { + for _i in 0..100000000 { + if !self.ready.read() { + return Ok(()); + } + } + Err(ErrorCode::BUSY) + } + + fn wait_for_chip_select(&self) -> Result<(), ErrorCode> { + self.cs.clear(); + for _i in 0..100000 { + if self.ready.read() { + return Ok(()); + } + } + self.cs.set(); + Err(ErrorCode::NOACK) + } + + fn send_command<'b>(&self, command: Command, params: &'b [&'b [u8]]) -> Result<(), ErrorCode> { + debug!("Send command"); + self.wait_for_chip_ready()?; + + self.wait_for_chip_select()?; + + self.write_buffer + .take() + .map_or(Err(ErrorCode::NOMEM), |buffer| { + buffer[0] = START_CMD; + buffer[POS_CMD] = (command as u8) & !REPLY_FLAG; + buffer[POS_PARAM_LEN] = params.len() as u8; + let mut position = 3; + for param in params { + //let mut param_bytes_pos = 0; + if command == Command::InsertDataBuf { + buffer[position] = 0; + position = position + 1; + buffer[position] = param.len() as u8; + } else { + buffer[position] = param.len() as u8; + } + position = position + 1; + for byte in *param { + buffer[position] = *byte; + position = position + 1; + } + + //position = position + 1; + } + buffer[position] = END_CMD; + + // Vedem daca e util sau nu ? + // if params.len() != 0 { + // for i in ((4 - ((position + 1) % 4)) & 3)..0 { + // position = position + 1; + // buffer[position] = 0xff; + // } + // } + + if command == Command::StartTcpServer { + position = position + 1; + buffer[position + 1]; + for i in 0..position+1 { + debug!("{:x} ", buffer[i]); + } + } + if command == Command::StartTcpClient { + debug!("StartTcpClient: chars to be written {}", position + 1); + debug!( + "{:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x}", + buffer[0], + buffer[1], + buffer[2], + buffer[3], + buffer[4], + buffer[5], + buffer[6], + buffer[7], + buffer[8], + buffer[9], + buffer[10], + buffer[11], + buffer[12], + buffer[13], + buffer[14], + buffer[15], + ); + } + if command == Command::InsertDataBuf { + // for i in 0..position + 1 { + // debug!("Byte {}: {:x}", i, buffer[i]); + // } + // debug!("InsertDataBuf: {}", position + 1); + while (position + 1) % 4 != 0 { + debug!("Aici"); + buffer[position] = 0xff; + position = position + 1; + } + debug!("InsertDataBuf: chars to be written {}", position + 1); + // debug!("{:?}", buffer); + debug!( + "{:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x} {:x}", + buffer[0], + buffer[1], + buffer[2], + buffer[3], + buffer[4], + buffer[5], + buffer[6], + buffer[7], + buffer[8], + buffer[9], + buffer[10], + buffer[11], + buffer[12], + buffer[13], + ); + } + if command == Command::SendUdpPacket { + buffer[position + 1] = 0xff; + position = position + 1; + buffer[position + 1] = 0xff; + position = position + 1; + debug!("InsertDataBuf: chars to be written {}", position + 1); + // debug!("{:?}", buffer); + debug!( + "{:x} {:x} {:x} {:x} {:x} {:x}", + buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5] + ); + } + + self.spi.release_low(); + + self.spi + .read_write_bytes(buffer, self.read_buffer.take(), position + 1) + .map_err(|(err, write_buffer, read_buffer)| { + self.write_buffer.replace(write_buffer); + read_buffer.map(|buffer| self.read_buffer.replace(buffer)); + err + })?; + + self.status.set(Status::Send(command)); + debug!("command sent: {:?}", self.status.get()); + + Ok(()) + }) + .map_err(|err| { + self.cs.set(); + err + }) + } + + fn receive_byte( + &self, + command: Command, + position: usize, + timeout: usize, + ) -> Result<(), ErrorCode> { + self.write_buffer + .take() + .map_or(Err(ErrorCode::NOMEM), |buffer| { + buffer[0] = 0xff; + + self.one_byte_read_buffer + .take() + .map_or(Err(ErrorCode::NOMEM), move |read_buffer| { + self.status.set(Status::Receive(command, position, timeout)); + self.spi.hold_low(); + self.spi + .read_write_bytes(buffer, Some(read_buffer), 1) + .map_err(|(err, write_buffer, read_buffer)| { + self.write_buffer.replace(write_buffer); + read_buffer.map(|buffer| self.one_byte_read_buffer.replace(buffer)); + err + }) + }) + }) + .map_err(|err| { + self.cs.set(); + err + }) + } + + fn receive_command(&self, command: Command) -> Result<(), ErrorCode> { + debug!("received command {:?}", command); + self.wait_for_chip_ready()?; + + self.wait_for_chip_select()?; + + self.receive_byte(command, 0, 1000) + } + + fn process_buffer(&self, command: Command) -> Result<(), ErrorCode> { + debug!("Process buffer for command **** {:?} *****", command); + // if command == Command::GetSocket { + // debug!("We get here in process buffer for Command::GetSocket!"); + // } + self.read_buffer + .map_or(Err(ErrorCode::NOMEM), |read_buffer| { + if read_buffer[0] == START_CMD { + if read_buffer[POS_CMD] == (command as u8) | REPLY_FLAG { + let param_len = read_buffer[POS_LEN]; + let mut current_position = 0; + for _parameter_index in 0..param_len { + let pos = POS_PARAM + current_position; + + if pos < read_buffer.len() { + current_position = current_position + read_buffer[pos] as usize; + } else { + break; + } + current_position = current_position + 1; + } + + let end_pos = POS_PARAM + current_position; + + if end_pos < read_buffer.len() && read_buffer[end_pos] == END_CMD { + match command { + Command::GetFwVersion => { + debug!("{:?}", core::str::from_utf8(&read_buffer[4..10])); + Ok(()) + } + Command::GetConnStatusCmd => { + let status = + self.number_to_connection_status(read_buffer[4] as usize); + if status == ConnectionStatus::Connected { + if let StationStatus::Connecting(net) = + self.station_status.get() + { + debug!("Getting here!"); + self.station_status.set(StationStatus::Connected(net)); + self.station_client.map(|client| { + client + .command_complete(Ok(self.station_status.get())) + }); + // self.get_ip_address(); + // self.start_tcp_client(0); + // self.get_socket(); + self.status.set(Status::Send(Command::SendPing)); + self.alarm.set_alarm( + self.alarm.now(), + self.alarm.ticks_from_ms(10), + ); + // self.send_ping(); + } + } else if status == ConnectionStatus::ConnectFailed { + if let StationStatus::Connecting(net) = + self.station_status.get() + { + debug!("Getting here!"); + self.station_status.set(StationStatus::Disconnected); + self.station_client.map(|client| { + client + .command_complete(Ok(self.station_status.get())) + }); + } + } else { + debug!("Getting here! {:?}", status); + self.status.set(Status::GetConnStatus); + self.alarm.set_alarm( + self.alarm.now(), + self.alarm.ticks_from_ms(2000), + ); + } + Ok(()) + } + Command::StartScanNetworksCmd => { + debug!("Starts scanning"); + self.status.set(Status::ScanNetworks); + self.alarm.set_alarm( + self.alarm.now(), + self.alarm.ticks_from_ms(2000), + ); + + Ok(()) + } + Command::ScanNetworksCmd => { + debug!("Scan networks command"); + self.networks.map(|networks| { + let mut current_position = 0; + for parameter_index in 0..param_len { + let pos = POS_PARAM + current_position; + + if pos < read_buffer.len() { + for (d, s) in networks[parameter_index as usize] + .ssid + .value + .iter_mut() + .zip( + read_buffer[pos + 1 + ..pos + + (read_buffer[pos] as usize) + + 1] + .iter(), + ) + { + *d = *s + } + networks[parameter_index as usize].ssid.len = + read_buffer[pos]; + networks[parameter_index as usize].security = None; + networks[parameter_index as usize].rssi = 0; + + current_position = + current_position + read_buffer[pos] as usize; + } else { + break; + } + current_position = current_position + 1; + } + self.scanner_client.map(|client| { + client.scan_done(Ok(&networks[0..param_len as usize])) + }); + }); + Ok(()) + } + + Command::GetIdxRSSICmd => Ok(()), + + Command::SetNetCmd => Ok(()), + + Command::SetPassPhraseCmd => { + // debug!("{}", end_pos); + // debug!( + // "{:x} {:x} {:x} {:x} {:x} {:x}", + // read_buffer[0], + // read_buffer[1], + // read_buffer[2], + // read_buffer[3], + // read_buffer[4], + // read_buffer[5] + // ); + + if read_buffer[4] == 1 { + debug!("SetPassPhraseCmd worked!"); + } else { + debug!("SetPassPhraseCmd: error!"); + } + + self.status.set(Status::Send(Command::GetConnStatusCmd)); + self.alarm.set_alarm( + self.alarm.now(), + self.alarm.ticks_from_ms(2000), + ); + // self.send_ping(); + Ok(()) + } + + Command::GetIpAddressCmd => { + let mut current_position = 0; + let mut count = 0; + for parameter_index in 0..param_len { + count = count + 1; + let pos = POS_PARAM + current_position; + let mut buf: [u8; 20] = [0; 20]; + // debug!("buffer: {:?}", read_buffer); + if pos < read_buffer.len() { + for i in 0..read_buffer[pos] { + buf[i as usize] = read_buffer[pos + i as usize + 1]; + } + debug!("Array nr {}: {:?}", count, buf); + current_position = + current_position + read_buffer[pos] as usize; + current_position = current_position + 1; + } + } + self.get_mac_address(); + // debug!( + // "Asta: IP Address {:x}:{:x}:{:x}:{:x}:{:x}:{:x}", + // read_buffer[0], + // read_buffer[1], + // read_buffer[2], + // read_buffer[3], + // read_buffer[4], + // read_buffer[5], + // ); + Ok(()) + } + + Command::GetMacAddressCmd => { + let mut current_position = 0; + let mut count = 0; + for parameter_index in 0..param_len { + count = count + 1; + let pos = POS_PARAM + current_position; + let mut buf: [u8; 20] = [0; 20]; + // debug!("buffer: {:?}", read_buffer); + if pos < read_buffer.len() { + for i in 0..read_buffer[pos] { + buf[i as usize] = read_buffer[pos + i as usize + 1]; + } + debug!( + "MAC Address {:x}:{:x}:{:x}:{:x}:{:x}:{:x}", + buf[5], buf[4], buf[3], buf[2], buf[1], buf[0], + ); + current_position = + current_position + read_buffer[pos] as usize; + current_position = current_position + 1; + } + } + self.get_ip_address(); + Ok(()) + } + Command::SendPing => { + let mut current_position = 0; + let mut count = 0; + for parameter_index in 0..param_len { + count = count + 1; + let pos = POS_PARAM + current_position; + let mut buf: [u8; 20] = [0; 20]; + // debug!("buffer: {:?}", read_buffer); + if pos < read_buffer.len() { + debug!("Num elements: {:x}", read_buffer[pos]); + for i in 0..read_buffer[pos] { + buf[i as usize] = read_buffer[pos + i as usize + 1]; + debug!("Element {}: {:x}", i, buf[i as usize]); + } + current_position = + current_position + read_buffer[pos] as usize; + current_position = current_position + 1; + } + } + self.status.set(Status::Send(Command::GetSocket)); + self.alarm + .set_alarm(self.alarm.now(), self.alarm.ticks_from_ms(100)); + self.get_socket(); + Ok(()) + } + Command::GetSocket => { + let mut current_position = 0; + let mut count = 0; + for parameter_index in 0..param_len { + count = count + 1; + let pos = POS_PARAM + current_position; + let mut buf: [u8; 20] = [0; 20]; + // debug!("buffer: {:?}", read_buffer); + if pos < read_buffer.len() { + debug!( + "GetSocket: Num elements: {:x}", + read_buffer[pos] + ); + debug!("Socket num: {:x}", read_buffer[pos + 1]); + // for i in 0..read_buffer[pos] { + // buf[i as usize] = read_buffer[pos + i as usize + 1]; + // debug!("Element {}: {:x}", i, buf[i as usize]); + // } + self.status.set(Status::Send(Command::StartTcpServer)); + self.alarm.set_alarm( + self.alarm.now(), + self.alarm.ticks_from_ms(1000), + ); + // self.start_tcp_client(read_buffer[pos + 1]); + current_position = + current_position + read_buffer[pos] as usize; + current_position = current_position + 1; + } + } + // self.get_socket(); + Ok(()) + } + Command::StartTcpClient => { + debug!( + "Process buffer for Command::StartTcpClient {:?}", + param_len + ); + // if param_len == 0 { + // self.get_tcp_client_state(0); + // } + let mut current_position = 0; + let mut count = 0; + for parameter_index in 0..param_len { + // debug!("Dar Intru aici?"); + count = count + 1; + let pos = POS_PARAM + current_position; + let mut buf: [u8; 20] = [0; 20]; + // debug!("buffer: {:?}", read_buffer); + if pos < read_buffer.len() { + debug!( + "StartTcpClient: Num elements: {:x}", + read_buffer[pos] + ); + for i in 0..read_buffer[pos] { + buf[i as usize] = read_buffer[pos + i as usize + 1]; + debug!("Element {}: {:x}", i, buf[i as usize]); + } + // self.start_tcp_client(read_buffer[pos + 1]); + current_position = + current_position + read_buffer[pos] as usize; + current_position = current_position + 1; + debug!( + "StartTcpClient end, status: {:?}", + self.status.get() + ); + // self.get_tcp_client_state(0); + } + } + self.status.set(Status::Send(Command::InsertDataBuf)); + self.alarm + .set_alarm(self.alarm.now(), self.alarm.ticks_from_ms(100)); + Ok(()) + } + Command::StartTcpServer => { + debug!( + "Process buffer for Command::StartTcpServer {:?}", + param_len + ); + // if param_len == 0 { + // self.get_tcp_client_state(0); + // } + let mut current_position = 0; + let mut count = 0; + for parameter_index in 0..param_len { + // debug!("Dar Intru aici?"); + count = count + 1; + let pos = POS_PARAM + current_position; + let mut buf: [u8; 20] = [0; 20]; + // debug!("buffer: {:?}", read_buffer); + if pos < read_buffer.len() { + debug!( + "StartTcpServer: Num elements: {:x}", + read_buffer[pos] + ); + for i in 0..read_buffer[pos] { + buf[i as usize] = read_buffer[pos + i as usize + 1]; + debug!("Element {}: {:x}", i, buf[i as usize]); + } + // self.start_tcp_client(read_buffer[pos + 1]); + current_position = + current_position + read_buffer[pos] as usize; + current_position = current_position + 1; + debug!( + "StartTcpServer end, status: {:?}", + self.status.get() + ); + // self.get_tcp_client_state(0); + } + } + self.second_time.set(1); + self.status.set(Status::Send(Command::StartTcpClient)); + self.alarm + .set_alarm(self.alarm.now(), self.alarm.ticks_from_ms(100)); + Ok(()) + } + Command::GetClientStateTCP => { + debug!( + "Process buffer for Command::GetClientStateTCP {:?}", + param_len + ); + let mut current_position = 0; + let mut count = 0; + for parameter_index in 0..param_len { + // debug!("Dar Intru aici?"); + count = count + 1; + let pos = POS_PARAM + current_position; + let mut buf: [u8; 20] = [0; 20]; + // debug!("buffer: {:?}", read_buffer); + if pos < read_buffer.len() { + debug!( + "GetClientStateTCP: Num elements: {:x}", + read_buffer[pos] + ); + for i in 0..read_buffer[pos] { + buf[i as usize] = read_buffer[pos + i as usize + 1]; + debug!("Element {}: {:x}", i, buf[i as usize]); + match buf[i as usize] { + 0 => { + debug!("{:?}", TcpState::TCPStateClosed); + self.status.set(Status::Send( + Command::GetClientStateTCP, + )); + self.alarm.set_alarm( + self.alarm.now(), + self.alarm.ticks_from_ms(1000), + ) + } + 1 => { + debug!("{:?}", TcpState::TCPStateListen) + } + 2 => { + debug!("{:?}", TcpState::TCPStateSynSent) + } + 3 => { + debug!("{:?}", TcpState::TCPStateSynRcvd) + } + 4 => { + debug!( + "{:?}", + TcpState::TCPStateEstablished + ); + self.insert_data_buf(0); + () + // self.status.set(Status::Send( + // Command::InsertDataBuf, + // )); + // self.alarm.set_alarm( + // self.alarm.now(), + // self.alarm.ticks_from_ms(2000), + // ) + } + 5 => { + debug!("{:?}", TcpState::TCPStateFinWait1) + } + 6 => { + debug!("{:?}", TcpState::TCPStateFinWait2) + } + 7 => { + debug!("{:?}", TcpState::TCPStateCloseWait) + } + 8 => { + debug!("{:?}", TcpState::TCPStateClosing) + } + 9 => { + debug!("{:?}", TcpState::TCPStateLastACK) + } + 10 => { + debug!("{:?}", TcpState::TCPStateTimeWait) + } + _ => { + debug!("Other value here!"); + } + } + } + // self.start_tcp_client(read_buffer[pos + 1]); + current_position = + current_position + read_buffer[pos] as usize; + current_position = current_position + 1; + // self.status.set(Status::Send(Command::InsertDataBuf)); + // self.alarm.set_alarm( + // self.alarm.now(), + // self.alarm.ticks_from_ms(10), + // ); + } + } + Ok(()) + } + Command::InsertDataBuf => { + let mut current_position = 0; + let mut count = 0; + for parameter_index in 0..param_len { + count = count + 1; + let pos = POS_PARAM + current_position; + let mut buf: [u8; 20] = [0; 20]; + // debug!("buffer: {:?}", read_buffer); + if pos < read_buffer.len() { + debug!( + "InsertDataBuf: Num elements: {:x}", + read_buffer[pos] + ); + for i in 0..read_buffer[pos] { + buf[i as usize] = read_buffer[pos + i as usize + 1]; + debug!("Element {}: {:x}", i, buf[i as usize]); + } + self.status.set(Status::Send(Command::SendUdpPacket)); + self.alarm.set_alarm( + self.alarm.now(), + self.alarm.ticks_from_ms(10), + ); + // self.send_udp_packet(0); + current_position = + current_position + read_buffer[pos] as usize; + current_position = current_position + 1; + } + } + Ok(()) + } + Command::SendUdpPacket => { + let mut current_position = 0; + let mut count = 0; + for parameter_index in 0..param_len { + count = count + 1; + let pos = POS_PARAM + current_position; + let mut buf: [u8; 20] = [0; 20]; + // debug!("buffer: {:?}", read_buffer); + if pos < read_buffer.len() { + debug!("Socket: {:x?}", self.get_socket()); + debug!( + "SendUdpPacket: Num elements: {:x}", + read_buffer[pos] + ); + for i in 0..read_buffer[pos] { + buf[i as usize] = read_buffer[pos + i as usize + 1]; + debug!("Element {}: {:x}", i, buf[i as usize]); + // if buf[i as usize] == 0 { + self.status + .set(Status::Send(Command::StartTcpClient)); + self.alarm.set_alarm( + self.alarm.now(), + self.alarm.ticks_from_ms(2000), + ) + // } + } + current_position = + current_position + read_buffer[pos] as usize; + current_position = current_position + 1; + } + } + Ok(()) + } + Command::StopTcpClient => { + let mut current_position = 0; + let mut count = 0; + for parameter_index in 0..param_len { + count = count + 1; + let pos = POS_PARAM + current_position; + let mut buf: [u8; 20] = [0; 20]; + // debug!("buffer: {:?}", read_buffer); + if pos < read_buffer.len() { + debug!( + "SendUdpPacket: Num elements: {:x}", + read_buffer[pos] + ); + for i in 0..read_buffer[pos] { + buf[i as usize] = read_buffer[pos + i as usize + 1]; + debug!("Element {}: {:x}", i, buf[i as usize]); + // self.status.set(Status::Send(Command::GetSocket)); + // self.alarm.set_alarm( + // self.alarm.now(), + // self.alarm.ticks_from_ms(100), + // ) + } + current_position = + current_position + read_buffer[pos] as usize; + current_position = current_position + 1; + } + } + Ok(()) + } + } + } else { + Err(ErrorCode::INVAL) + } + } else if read_buffer[POS_CMD] == ERROR_CMD { + Err(ErrorCode::FAIL) + } else { + Ok(()) + } + } else { + Err(ErrorCode::INVAL) + } + }) + } + + fn schedule_callback_error(&self, command: Command, error: ErrorCode) { + match command { + Command::StartScanNetworksCmd | Command::ScanNetworksCmd => { + self.scanner_client + .map(|client| client.scan_done(Err(error))); + } + _ => {} + } + } +} + +impl<'a, S: SpiMaster<'a>, P: Pin, A: Alarm<'a>> SpiMasterClient for NinaW102<'a, S, P, A> { + fn read_write_done( + &self, + write_buffer: &'static mut [u8], + read_buffer: Option<&'static mut [u8]>, + _len: usize, + status: Result<(), ErrorCode>, + ) { + if let Err(err) = status { + match self.status.get() { + Status::Send(command) | Status::Receive(command, _, _) => { + self.schedule_callback_error(command, err) + } + _ => {} + } + self.write_buffer.replace(write_buffer); + + // TO BE CHANGED?? + read_buffer.map(|buffer| self.one_byte_read_buffer.replace(buffer)); + + self.status.set(Status::Idle); + } else { + match self.status.get() { + Status::Send(command) => { + // if command == Command::GetConnStatusCmd { + // debug!("In read_write_done in Status::send"); + // } + self.write_buffer.replace(write_buffer); + read_buffer.map(|buffer| self.read_buffer.replace(buffer)); + if let Err(error) = self.receive_command(command) { + self.schedule_callback_error(command, error); + self.status.set(Status::Idle); + } + } + Status::Receive(command, position, timeout) => { + // if command == Command::GetSocket { + // debug!("In read_write_done in Status::send"); + // } + self.status.set(Status::Idle); + self.write_buffer.replace(write_buffer); + // debug!("In status::Receive"); + read_buffer + .map_or(Err(ErrorCode::NOMEM), |buffer| { + let byte = buffer[0]; + // if command == Command::SendPing && byte != 0xff { + // debug!( + // "Aiciii: command: {:?}, byte {:x}, position {:x}", + // command, byte, position + // ); + // } + + self.one_byte_read_buffer.replace(buffer); + if position == 0 { + if byte == START_CMD || byte == ERROR_CMD { + self.read_buffer.map(|buffer| { + buffer[0] = byte; + }); + if byte == START_CMD { + if command == Command::GetSocket + || command == Command::InsertDataBuf + { + // debug!("Intru aici la START_CMD"); + } + self.receive_byte(command, 1, 1000) + } else { + if command == Command::GetSocket + || command == Command::InsertDataBuf + { + debug!("Intru aici la err"); + // self.start_tcp_client(); + } + Ok(()) + } + } else if timeout > 0 { + // if (command == Command::GetClientStateTCP) { + // debug!("read byte {} {}", position, timeout); + // } + self.receive_byte(command, 0, timeout - 1) + } else { + if command == Command::GetSocket + || command == Command::InsertDataBuf + { + debug!("Timeout..."); + if command == Command::GetSocket { + debug!("At InsertDataBuf"); + self.status.set(Status::Send(Command::GetSocket)); + self.alarm.set_alarm( + self.alarm.now(), + self.alarm.ticks_from_ms(100), + ); + } + // } + // self.start_tcp_client(); + Ok(()) + } else { + self.cs.set(); + Err(ErrorCode::NOACK) + } + } + } else { + self.read_buffer.map(|buffer| { + buffer[position] = byte; + // if command == Command::GetIpAddressCmd { + // debug!( + // "command: {:?}, byte {:x}, position {:x}", + // command, buffer[position], position + // ); + // } + }); + if byte == END_CMD { + self.cs.set(); + self.spi.release_low(); + if command == Command::GetSocket + || command == Command::InsertDataBuf + { + // debug!("LA end!!"); + } + + self.process_buffer(command) + } else if timeout > 0 { + self.receive_byte(command, position + 1, timeout - 1) + } else { + self.cs.set(); + Err(ErrorCode::SIZE) + } + } + }) + .map_err(|error| { + self.schedule_callback_error(command, error); + self.status.set(Status::Idle); + }) + .ok(); + } + Status::Idle => { + self.write_buffer.replace(write_buffer); + read_buffer.map(|read_buffer| self.read_buffer.replace(read_buffer)); + } + + Status::ScanNetworks => {} + + _ => {} + } + } + } +} +impl<'a, S: SpiMaster<'a>, P: Pin, A: Alarm<'a>> AlarmClient for NinaW102<'a, S, P, A> { + fn alarm(&self) { + match self.status.get() { + Status::Init(init_status) => match init_status { + InitStatus::Starting => { + self.reset.set(); + self.alarm + .set_alarm(self.alarm.now(), self.alarm.ticks_from_ms(750)); + + self.status.set(Status::Init(InitStatus::Initialized)); + } + + InitStatus::Initialized => { + self.gpio0.clear(); + self.gpio0.make_input(); + self.status.set(Status::Idle); + } + }, + + // Status::Idle => { + // let _ = self.start_tcp_client(0); + // } + Status::StartScanNetworks => { + let _ = self.start_scan_networks(); + } + Status::ScanNetworks => { + // debug!("ScanNetworks status from alarm"); + let _ = self.scan_networks(); + } + + Status::GetConnStatus => { + // debug!("Status get conn"); + self.status.set(Status::Idle); + let _ = self.get_connection_status(); + } + + Status::Send(command) => match command { + Command::GetConnStatusCmd => { + self.status.set(Status::Idle); + self.get_connection_status(); + } + Command::GetSocket => { + self.status.set(Status::Idle); + self.get_socket(); + } + Command::StartTcpServer => { + self.status.set(Status::Idle); + self.start_tcp_server(0); + } + Command::StartTcpClient => { + self.status.set(Status::Idle); + self.start_tcp_client(0); + } + Command::StopTcpClient => { + self.status.set(Status::Idle); + self.stop_tcp_client(0); + } + Command::GetClientStateTCP => { + self.status.set(Status::Idle); + self.get_tcp_client_state(0); + } + Command::InsertDataBuf => { + self.status.set(Status::Idle); + self.insert_data_buf(0); + } + Command::SendUdpPacket => { + self.status.set(Status::Idle); + self.send_udp_packet(0); + } + Command::SendPing => { + self.status.set(Status::Idle); + self.send_ping(); + } + _ => { + self.status.set(Status::Idle); + } + }, + + _ => {} + } + } +} + +impl<'a, S: SpiMaster<'static>, P: Pin, A: Alarm<'static>> Scanner<'static> + for NinaW102<'static, S, P, A> +{ + fn scan(&self) -> Result<(), ErrorCode> { + debug!("Nina starts scanning"); + self.start_scan_networks() + } + + fn set_client(&self, client: &'static dyn ScannerClient) { + self.scanner_client.set(client); + } +} + +impl<'a, S: SpiMaster<'static>, P: Pin, A: Alarm<'static>> Station<'static> + for NinaW102<'static, S, P, A> +{ + // try to initiate a connection to the `Network` + fn connect(&self, ssid: Ssid, psk: Option) -> Result<(), ErrorCode> { + //if let Some(psk) = psk.unwrap() {} + self.station_status.set(StationStatus::Connecting(Network { + ssid, + rssi: 0, + security: None, + })); + self.set_passphrase( + &ssid.value[0..ssid.value.len()], + &psk.unwrap().value[0..psk.unwrap().value.len()], + ) + } + // try to disconnect from the network that it is currently connected to + fn disconnect(&self) -> Result<(), ErrorCode> { + Err(ErrorCode::NOSUPPORT) + } + + // return the status + fn get_status(&self) -> Result<(), ErrorCode> { + self.get_connection_status() + } + fn set_client(&self, client: &'static dyn StationClient) { + self.station_client.set(client); + } +} diff --git a/capsules/src/ninedof.rs b/capsules/extra/src/ninedof.rs similarity index 90% rename from capsules/src/ninedof.rs rename to capsules/extra/src/ninedof.rs index e56ed4ea07..629c78fe88 100644 --- a/capsules/src/ninedof.rs +++ b/capsules/extra/src/ninedof.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides userspace with virtualized access to 9DOF sensors. //! //! Usage @@ -5,7 +9,7 @@ //! //! You need a device that provides the `hil::sensors::NineDof` trait. //! -//! ```rust +//! ```rust,ignore //! # use kernel::{hil, static_init}; //! //! let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); @@ -25,7 +29,7 @@ use kernel::utilities::cells::OptionalCell; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::NINEDOF as usize; #[derive(Clone, Copy, PartialEq)] @@ -64,7 +68,7 @@ impl<'a> NineDof<'a> { grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, ) -> NineDof<'a> { NineDof { - drivers: drivers, + drivers, apps: grant, current_app: OptionalCell::empty(), } @@ -77,19 +81,19 @@ impl<'a> NineDof<'a> { &self, command: NineDofCommand, arg1: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { self.apps - .enter(appid, |app, _| { + .enter(processid, |app, _| { if self.current_app.is_none() { - self.current_app.set(appid); + self.current_app.set(processid); let value = self.call_driver(command, arg1); if value != Ok(()) { self.current_app.clear(); } CommandReturn::from(value) } else { - if app.pending_command == true { + if app.pending_command { CommandReturn::failure(ErrorCode::BUSY) } else { app.pending_command = true; @@ -149,8 +153,8 @@ impl hil::sensors::NineDofClient for NineDof<'_> { // the result. let mut finished_command = NineDofCommand::Exists; let mut finished_command_arg = 0; - self.current_app.take().map(|appid| { - let _ = self.apps.enter(appid, |app, upcalls| { + self.current_app.take().map(|processid| { + let _ = self.apps.enter(processid, |app, upcalls| { app.pending_command = false; finished_command = app.command; finished_command_arg = app.arg1; @@ -160,7 +164,7 @@ impl hil::sensors::NineDofClient for NineDof<'_> { // Check if there are any pending events. for cntr in self.apps.iter() { - let appid = cntr.processid(); + let processid = cntr.processid(); let started_command = cntr.enter(|app, upcalls| { if app.pending_command && app.command == finished_command @@ -173,7 +177,7 @@ impl hil::sensors::NineDofClient for NineDof<'_> { false } else if app.pending_command { app.pending_command = false; - self.current_app.set(appid); + self.current_app.set(processid); self.call_driver(app.command, app.arg1) == Ok(()) } else { false @@ -192,18 +196,18 @@ impl SyscallDriver for NineDof<'_> { command_num: usize, arg1: usize, _: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { match command_num { 0 => CommandReturn::success(), // Single acceleration reading. - 1 => self.enqueue_command(NineDofCommand::ReadAccelerometer, arg1, appid), + 1 => self.enqueue_command(NineDofCommand::ReadAccelerometer, arg1, processid), // Single magnetometer reading. - 100 => self.enqueue_command(NineDofCommand::ReadMagnetometer, arg1, appid), + 100 => self.enqueue_command(NineDofCommand::ReadMagnetometer, arg1, processid), // Single gyroscope reading. - 200 => self.enqueue_command(NineDofCommand::ReadGyroscope, arg1, appid), + 200 => self.enqueue_command(NineDofCommand::ReadGyroscope, arg1, processid), _ => CommandReturn::failure(ErrorCode::NOSUPPORT), } diff --git a/capsules/src/nonvolatile_storage_driver.rs b/capsules/extra/src/nonvolatile_storage_driver.rs similarity index 87% rename from capsules/src/nonvolatile_storage_driver.rs rename to capsules/extra/src/nonvolatile_storage_driver.rs index fba44a3bac..f691b925e0 100644 --- a/capsules/src/nonvolatile_storage_driver.rs +++ b/capsules/extra/src/nonvolatile_storage_driver.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! This provides kernel and userspace access to nonvolatile memory. //! //! This is an initial implementation that does not provide safety for @@ -36,7 +40,7 @@ //! //! Example instantiation: //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! let nonvolatile_storage = static_init!( @@ -65,24 +69,36 @@ use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::NvmStorage as usize; +/// IDs for subscribed upcalls. +mod upcall { + /// Read done callback. + pub const READ_DONE: usize = 0; + /// Write done callback. + pub const WRITE_DONE: usize = 1; + /// Number of upcalls. + pub const COUNT: u8 = 2; +} + /// Ids for read-only allow buffers mod ro_allow { + /// Setup a buffer to write bytes to the nonvolatile storage. pub const WRITE: usize = 0; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; + pub const COUNT: u8 = 1; } /// Ids for read-write allow buffers mod rw_allow { + /// Setup a buffer to read from the nonvolatile storage into. pub const READ: usize = 0; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; + pub const COUNT: u8 = 1; } -pub static mut BUFFER: [u8; 512] = [0; 512]; +pub const BUF_LEN: usize = 512; #[derive(Clone, Copy, PartialEq)] pub enum NonvolatileCommand { @@ -94,7 +110,7 @@ pub enum NonvolatileCommand { #[derive(Clone, Copy)] pub enum NonvolatileUser { - App { app_id: ProcessId }, + App { processid: ProcessId }, Kernel, } @@ -118,11 +134,11 @@ impl Default for App { pub struct NonvolatileStorage<'a> { // The underlying physical storage device. - driver: &'a dyn hil::nonvolatile_storage::NonvolatileStorage<'static>, + driver: &'a dyn hil::nonvolatile_storage::NonvolatileStorage<'a>, // Per-app state. apps: Grant< App, - UpcallCount<2>, + UpcallCount<{ upcall::COUNT }>, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<{ rw_allow::COUNT }>, >, @@ -143,8 +159,7 @@ pub struct NonvolatileStorage<'a> { // Optional client for the kernel. Only needed if the kernel intends to use // this nonvolatile storage. - kernel_client: - OptionalCell<&'static dyn hil::nonvolatile_storage::NonvolatileStorageClient<'static>>, + kernel_client: OptionalCell<&'a dyn hil::nonvolatile_storage::NonvolatileStorageClient>, // Whether the kernel is waiting for a read/write. kernel_pending_command: Cell, // Whether the kernel wanted a read/write. @@ -159,10 +174,10 @@ pub struct NonvolatileStorage<'a> { impl<'a> NonvolatileStorage<'a> { pub fn new( - driver: &'a dyn hil::nonvolatile_storage::NonvolatileStorage<'static>, + driver: &'a dyn hil::nonvolatile_storage::NonvolatileStorage<'a>, grant: Grant< App, - UpcallCount<2>, + UpcallCount<{ upcall::COUNT }>, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<{ rw_allow::COUNT }>, >, @@ -173,14 +188,14 @@ impl<'a> NonvolatileStorage<'a> { buffer: &'static mut [u8], ) -> NonvolatileStorage<'a> { NonvolatileStorage { - driver: driver, + driver, apps: grant, buffer: TakeCell::new(buffer), current_user: OptionalCell::empty(), - userspace_start_address: userspace_start_address, - userspace_length: userspace_length, - kernel_start_address: kernel_start_address, - kernel_length: kernel_length, + userspace_start_address, + userspace_length, + kernel_start_address, + kernel_length, kernel_client: OptionalCell::empty(), kernel_pending_command: Cell::new(false), kernel_command: Cell::new(NonvolatileCommand::KernelRead), @@ -198,7 +213,7 @@ impl<'a> NonvolatileStorage<'a> { command: NonvolatileCommand, offset: usize, length: usize, - app_id: Option, + processid: Option, ) -> Result<(), ErrorCode> { // Do bounds check. match command { @@ -229,9 +244,9 @@ impl<'a> NonvolatileStorage<'a> { // or from the kernel. match command { NonvolatileCommand::UserspaceRead | NonvolatileCommand::UserspaceWrite => { - app_id.map_or(Err(ErrorCode::FAIL), |appid| { + processid.map_or(Err(ErrorCode::FAIL), |processid| { self.apps - .enter(appid, |app, kernel_data| { + .enter(processid, |app, kernel_data| { // Get the length of the correct allowed buffer. let allow_buf_len = match command { NonvolatileCommand::UserspaceRead => kernel_data @@ -257,8 +272,7 @@ impl<'a> NonvolatileStorage<'a> { if self.current_user.is_none() { // No app is currently using the underlying storage. // Mark this app as active, and then execute the command. - self.current_user - .set(NonvolatileUser::App { app_id: appid }); + self.current_user.set(NonvolatileUser::App { processid }); // Need to copy bytes if this is a write! if command == NonvolatileCommand::UserspaceWrite { @@ -287,7 +301,7 @@ impl<'a> NonvolatileStorage<'a> { self.userspace_call_driver(command, offset, active_len) } else { // Some app is using the storage, we must wait. - if app.pending_command == true { + if app.pending_command { // No more room in the queue, nowhere to store this // request. Err(ErrorCode::NOMEM) @@ -325,7 +339,7 @@ impl<'a> NonvolatileStorage<'a> { _ => Err(ErrorCode::FAIL), } } else { - if self.kernel_pending_command.get() == true { + if self.kernel_pending_command.get() { Err(ErrorCode::NOMEM) } else { self.kernel_pending_command.set(true); @@ -358,7 +372,7 @@ impl<'a> NonvolatileStorage<'a> { // allowed are long enough. let active_len = cmp::min(length, buffer.len()); - // self.current_app.set(Some(appid)); + // self.current_app.set(Some(processid)); match command { NonvolatileCommand::UserspaceRead => { self.driver.read(buffer, physical_address, active_len) @@ -395,12 +409,11 @@ impl<'a> NonvolatileStorage<'a> { } else { // If the kernel is not requesting anything, check all of the apps. for cntr in self.apps.iter() { - let appid = cntr.processid(); + let processid = cntr.processid(); let started_command = cntr.enter(|app, _| { if app.pending_command { app.pending_command = false; - self.current_user - .set(NonvolatileUser::App { app_id: appid }); + self.current_user.set(NonvolatileUser::App { processid }); if let Ok(()) = self.userspace_call_driver(app.command, app.offset, app.length) { @@ -421,7 +434,7 @@ impl<'a> NonvolatileStorage<'a> { } /// This is the callback client for the underlying physical storage driver. -impl hil::nonvolatile_storage::NonvolatileStorageClient<'static> for NonvolatileStorage<'_> { +impl hil::nonvolatile_storage::NonvolatileStorageClient for NonvolatileStorage<'_> { fn read_done(&self, buffer: &'static mut [u8], length: usize) { // Switch on which user of this capsule generated this callback. self.current_user.take().map(|user| { @@ -431,8 +444,8 @@ impl hil::nonvolatile_storage::NonvolatileStorageClient<'static> for Nonvolatile client.read_done(buffer, length); }); } - NonvolatileUser::App { app_id } => { - let _ = self.apps.enter(app_id, move |_, kernel_data| { + NonvolatileUser::App { processid } => { + let _ = self.apps.enter(processid, move |_, kernel_data| { // Need to copy in the contents of the buffer let _ = kernel_data .get_readwrite_processbuffer(rw_allow::READ) @@ -440,7 +453,7 @@ impl hil::nonvolatile_storage::NonvolatileStorageClient<'static> for Nonvolatile read.mut_enter(|app_buffer| { let read_len = cmp::min(app_buffer.len(), length); - let d = &app_buffer[0..(read_len as usize)]; + let d = &app_buffer[0..read_len]; for (i, c) in buffer[0..read_len].iter().enumerate() { d[i].set(*c); } @@ -451,7 +464,9 @@ impl hil::nonvolatile_storage::NonvolatileStorageClient<'static> for Nonvolatile self.buffer.replace(buffer); // And then signal the app. - kernel_data.schedule_upcall(0, (length, 0, 0)).ok(); + kernel_data + .schedule_upcall(upcall::READ_DONE, (length, 0, 0)) + .ok(); }); } } @@ -469,13 +484,15 @@ impl hil::nonvolatile_storage::NonvolatileStorageClient<'static> for Nonvolatile client.write_done(buffer, length); }); } - NonvolatileUser::App { app_id } => { - let _ = self.apps.enter(app_id, move |_app, kernel_data| { + NonvolatileUser::App { processid } => { + let _ = self.apps.enter(processid, move |_app, kernel_data| { // Replace the buffer we used to do this write. self.buffer.replace(buffer); // And then signal the app. - kernel_data.schedule_upcall(1, (length, 0, 0)).ok(); + kernel_data + .schedule_upcall(upcall::WRITE_DONE, (length, 0, 0)) + .ok(); }); } } @@ -486,8 +503,8 @@ impl hil::nonvolatile_storage::NonvolatileStorageClient<'static> for Nonvolatile } /// Provide an interface for the kernel. -impl hil::nonvolatile_storage::NonvolatileStorage<'static> for NonvolatileStorage<'_> { - fn set_client(&self, client: &'static dyn hil::nonvolatile_storage::NonvolatileStorageClient) { +impl<'a> hil::nonvolatile_storage::NonvolatileStorage<'a> for NonvolatileStorage<'a> { + fn set_client(&self, client: &'a dyn hil::nonvolatile_storage::NonvolatileStorageClient) { self.kernel_client.set(client); } @@ -514,25 +531,6 @@ impl hil::nonvolatile_storage::NonvolatileStorage<'static> for NonvolatileStorag /// Provide an interface for userland. impl SyscallDriver for NonvolatileStorage<'_> { - /// Setup shared kernel-writable buffers. - /// - /// ### `allow_num` - /// - /// - `0`: Setup a buffer to read from the nonvolatile storage into. - - /// Setup shared kernel-readable buffers. - /// - /// ### `allow_num` - /// - /// - `0`: Setup a buffer to write bytes to the nonvolatile storage. - - // Setup callbacks. - // - // ### `subscribe_num` - // - // - `0`: Setup a read done callback. - // - `1`: Setup a write done callback. - /// Command interface. /// /// Commands are selected by the lowest 8 bits of the first argument. @@ -548,26 +546,25 @@ impl SyscallDriver for NonvolatileStorage<'_> { command_num: usize, offset: usize, length: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { match command_num { - 0 /* This driver exists. */ => { - CommandReturn::success() - } + 0 => CommandReturn::success(), - 1 /* How many bytes are accessible from userspace */ => { + 1 => { + // How many bytes are accessible from userspace // TODO: Would break on 64-bit platforms CommandReturn::success_u32(self.userspace_length as u32) - }, + } - 2 /* Issue a read command */ => { - let res = - self.enqueue_command( - NonvolatileCommand::UserspaceRead, - offset, - length, - Some(appid), - ); + 2 => { + // Issue a read command + let res = self.enqueue_command( + NonvolatileCommand::UserspaceRead, + offset, + length, + Some(processid), + ); match res { Ok(()) => CommandReturn::success(), @@ -575,14 +572,14 @@ impl SyscallDriver for NonvolatileStorage<'_> { } } - 3 /* Issue a write command */ => { - let res = - self.enqueue_command( - NonvolatileCommand::UserspaceWrite, - offset, - length, - Some(appid), - ); + 3 => { + // Issue a write command + let res = self.enqueue_command( + NonvolatileCommand::UserspaceWrite, + offset, + length, + Some(processid), + ); match res { Ok(()) => CommandReturn::success(), diff --git a/capsules/src/nonvolatile_to_pages.rs b/capsules/extra/src/nonvolatile_to_pages.rs similarity index 88% rename from capsules/src/nonvolatile_to_pages.rs rename to capsules/extra/src/nonvolatile_to_pages.rs index 919c38eefc..1bc0fa8c3f 100644 --- a/capsules/src/nonvolatile_to_pages.rs +++ b/capsules/extra/src/nonvolatile_to_pages.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Map arbitrary nonvolatile reads and writes to page operations. //! //! This splits non-page-aligned reads and writes into a series of page level @@ -20,16 +24,19 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::{hil, static_init}; -//! + //! sam4l::flashcalw::FLASH_CONTROLLER.configure(); -//! pub static mut PAGEBUFFER: sam4l::flashcalw::Sam4lPage = sam4l::flashcalw::Sam4lPage::new(); +//! let page_buffer = static_init!( +//! sam4l::flashcalw::Sam4lPage, +//! sam4l::flashcalw::Sam4lPage::default() +//! ); //! let nv_to_page = static_init!( //! capsules::nonvolatile_to_pages::NonvolatileToPages<'static, sam4l::flashcalw::FLASHCALW>, //! capsules::nonvolatile_to_pages::NonvolatileToPages::new( //! &mut sam4l::flashcalw::FLASH_CONTROLLER, -//! &mut PAGEBUFFER)); +//! page_buffer)); //! hil::flash::HasClient::set_client(&sam4l::flashcalw::FLASH_CONTROLLER, nv_to_page); //! ``` @@ -52,7 +59,7 @@ pub struct NonvolatileToPages<'a, F: hil::flash::Flash + 'static> { /// The module providing a `Flash` interface. driver: &'a F, /// Callback to the user of this capsule. - client: OptionalCell<&'static dyn hil::nonvolatile_storage::NonvolatileStorageClient<'static>>, + client: OptionalCell<&'a dyn hil::nonvolatile_storage::NonvolatileStorageClient>, /// Buffer correctly sized for the underlying flash page size. pagebuffer: TakeCell<'static, F::Page>, /// Current state of this capsule. @@ -74,7 +81,7 @@ pub struct NonvolatileToPages<'a, F: hil::flash::Flash + 'static> { impl<'a, F: hil::flash::Flash> NonvolatileToPages<'a, F> { pub fn new(driver: &'a F, buffer: &'static mut F::Page) -> NonvolatileToPages<'a, F> { NonvolatileToPages { - driver: driver, + driver, client: OptionalCell::empty(), pagebuffer: TakeCell::new(buffer), state: Cell::new(State::Idle), @@ -87,10 +94,10 @@ impl<'a, F: hil::flash::Flash> NonvolatileToPages<'a, F> { } } -impl<'a, F: hil::flash::Flash> hil::nonvolatile_storage::NonvolatileStorage<'static> +impl<'a, F: hil::flash::Flash> hil::nonvolatile_storage::NonvolatileStorage<'a> for NonvolatileToPages<'a, F> { - fn set_client(&self, client: &'static dyn hil::nonvolatile_storage::NonvolatileStorageClient) { + fn set_client(&self, client: &'a dyn hil::nonvolatile_storage::NonvolatileStorageClient) { self.client.set(client); } @@ -151,9 +158,7 @@ impl<'a, F: hil::flash::Flash> hil::nonvolatile_storage::NonvolatileStorage<'sta // page or more. // Copy data into page buffer. - for i in 0..page_size { - pagebuffer.as_mut()[i] = buffer[i]; - } + pagebuffer.as_mut()[..page_size].copy_from_slice(&buffer[..page_size]); self.buffer.replace(buffer); self.address.set(address + page_size); @@ -187,7 +192,11 @@ impl<'a, F: hil::flash::Flash> hil::nonvolatile_storage::NonvolatileStorage<'sta } impl hil::flash::Client for NonvolatileToPages<'_, F> { - fn read_complete(&self, pagebuffer: &'static mut F::Page, _error: hil::flash::Error) { + fn read_complete( + &self, + pagebuffer: &'static mut F::Page, + _result: Result<(), hil::flash::Error>, + ) { match self.state.get() { State::Read => { // OK we got a page from flash. Copy what we actually want from it @@ -202,9 +211,8 @@ impl hil::flash::Client for NonvolatileToPages<'_, F> { let buffer_index = self.buffer_index.get(); // Copy what we read from the page buffer to the user buffer. - for i in 0..len { - buffer[buffer_index + i] = pagebuffer.as_mut()[page_index + i]; - } + buffer[buffer_index..(len + buffer_index)] + .copy_from_slice(&pagebuffer.as_mut()[page_index..(len + page_index)]); // Decide if we are done. let new_len = self.remaining_length.get() - len; @@ -246,9 +254,8 @@ impl hil::flash::Client for NonvolatileToPages<'_, F> { let page_number = self.address.get() / page_size; // Copy what we read from the page buffer to the user buffer. - for i in 0..len { - pagebuffer.as_mut()[page_index + i] = buffer[buffer_index + i]; - } + pagebuffer.as_mut()[page_index..(len + page_index)] + .copy_from_slice(&buffer[buffer_index..(len + buffer_index)]); // Do the write. self.buffer.replace(buffer); @@ -264,7 +271,11 @@ impl hil::flash::Client for NonvolatileToPages<'_, F> { } } - fn write_complete(&self, pagebuffer: &'static mut F::Page, _error: hil::flash::Error) { + fn write_complete( + &self, + pagebuffer: &'static mut F::Page, + _result: Result<(), hil::flash::Error>, + ) { // After a write we could be done, need to do another write, or need to // do a read. self.buffer.take().map(move |buffer| { @@ -282,9 +293,8 @@ impl hil::flash::Client for NonvolatileToPages<'_, F> { let page_number = self.address.get() / page_size; // Copy data into page buffer. - for i in 0..page_size { - pagebuffer.as_mut()[i] = buffer[buffer_index + i]; - } + pagebuffer.as_mut()[..page_size] + .copy_from_slice(&buffer[buffer_index..(page_size + buffer_index)]); self.buffer.replace(buffer); self.remaining_length.subtract(page_size); @@ -306,5 +316,5 @@ impl hil::flash::Client for NonvolatileToPages<'_, F> { }); } - fn erase_complete(&self, _error: hil::flash::Error) {} + fn erase_complete(&self, _result: Result<(), hil::flash::Error>) {} } diff --git a/capsules/extra/src/nrf51822_serialization.rs b/capsules/extra/src/nrf51822_serialization.rs new file mode 100644 index 0000000000..f520e0de77 --- /dev/null +++ b/capsules/extra/src/nrf51822_serialization.rs @@ -0,0 +1,356 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Provides userspace with the UART API that the nRF51822 serialization library +//! requires. +//! +//! This capsule handles interfacing with the UART driver, and includes some +//! nuances that keep the Nordic BLE serialization library happy. +//! +//! Usage +//! ----- +//! +//! ```rust,ignore +//! # use kernel::{hil, static_init}; +//! # use capsules::nrf51822_serialization; +//! # use capsules::nrf51822_serialization::Nrf51822Serialization; +//! +//! let nrf_serialization = static_init!( +//! Nrf51822Serialization, +//! Nrf51822Serialization::new(&usart::USART3, +//! &mut nrf51822_serialization::WRITE_BUF, +//! &mut nrf51822_serialization::READ_BUF)); +//! hil::uart::UART::set_client(&usart::USART3, nrf_serialization); +//! ``` + +use core::cmp; + +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::hil; +use kernel::hil::uart; +use kernel::processbuffer::{ReadableProcessBuffer, WriteableProcessBuffer}; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::{ErrorCode, ProcessId}; + +/// Syscall driver number. +use capsules_core::driver; +pub const DRIVER_NUM: usize = driver::NUM::Nrf51822Serialization as usize; + +/// IDs for subscribed upcalls. +mod upcall { + /// Callback will be called when a TX finishes and when RX data is + /// available. + pub const TX_DONE_RX_READY: usize = 0; + /// Number of upcalls. + pub const COUNT: u8 = 1; +} + +/// Ids for read-only allow buffers +mod ro_allow { + /// TX buffer. + /// + /// This also sets which app is currently using this driver. Only one app + /// can control the nRF51 serialization driver. + pub const TX: usize = 0; + /// The number of allow buffers the kernel stores for this grant + pub const COUNT: u8 = 1; +} + +/// Ids for read-write allow buffers +mod rw_allow { + /// RX buffer. + /// + /// This also sets which app is currently using this driver. Only one app + /// can control the nRF51 serialization driver. + pub const RX: usize = 0; + /// The number of allow buffers the kernel stores for this grant + pub const COUNT: u8 = 1; +} + +#[derive(Default)] +pub struct App; + +// Local buffer for passing data between applications and the underlying +// transport hardware. +pub const WRITE_BUF_LEN: usize = 600; +pub const READ_BUF_LEN: usize = 600; + +// We need two resources: a UART HW driver and driver state for each +// application. +pub struct Nrf51822Serialization<'a> { + uart: &'a dyn uart::UartAdvanced<'a>, + reset_pin: &'a dyn hil::gpio::Pin, + apps: Grant< + App, + UpcallCount<{ upcall::COUNT }>, + AllowRoCount<{ ro_allow::COUNT }>, + AllowRwCount<{ rw_allow::COUNT }>, + >, + active_app: OptionalCell, + tx_buffer: TakeCell<'static, [u8]>, + rx_buffer: TakeCell<'static, [u8]>, +} + +impl<'a> Nrf51822Serialization<'a> { + pub fn new( + uart: &'a dyn uart::UartAdvanced<'a>, + grant: Grant< + App, + UpcallCount<{ upcall::COUNT }>, + AllowRoCount<{ ro_allow::COUNT }>, + AllowRwCount<{ rw_allow::COUNT }>, + >, + reset_pin: &'a dyn hil::gpio::Pin, + tx_buffer: &'static mut [u8], + rx_buffer: &'static mut [u8], + ) -> Nrf51822Serialization<'a> { + Nrf51822Serialization { + uart, + reset_pin, + apps: grant, + active_app: OptionalCell::empty(), + tx_buffer: TakeCell::new(tx_buffer), + rx_buffer: TakeCell::new(rx_buffer), + } + } + + pub fn initialize(&self) { + let _ = self.uart.configure(uart::Parameters { + baud_rate: 250000, + width: uart::Width::Eight, + stop_bits: uart::StopBits::One, + parity: uart::Parity::Even, + hw_flow_control: true, + }); + } + + pub fn reset(&self) { + self.reset_pin.make_output(); + self.reset_pin.clear(); + // minimum hold time is 200ns, ~20ns per instruction, so overshoot a bit + for _ in 0..10 { + self.reset_pin.clear(); + } + self.reset_pin.set(); + } +} + +impl SyscallDriver for Nrf51822Serialization<'_> { + /// Issue a command to the Nrf51822Serialization driver. + /// + /// ### `command_type` + /// + /// - `0`: Driver existence check. + /// - `1`: Send the allowed buffer to the nRF. + /// - `2`: Received from the nRF into the allowed buffer. + /// - `3`: Reset the nRF51822. + fn command( + &self, + command_type: usize, + arg1: usize, + _: usize, + processid: ProcessId, + ) -> CommandReturn { + match command_type { + 0 => CommandReturn::success(), + + // Send a buffer to the nRF51822 over UART. + 1 => { + self.apps + .enter(processid, |_, kernel_data| { + kernel_data + .get_readonly_processbuffer(ro_allow::TX) + .and_then(|tx| { + tx.enter(|slice| { + let write_len = slice.len(); + self.tx_buffer.take().map_or( + CommandReturn::failure(ErrorCode::FAIL), + |buffer| { + for (i, c) in slice.iter().enumerate() { + buffer[i] = c.get(); + } + // Set this as the active app for the transmit callback + self.active_app.set(processid); + let _ = self.uart.transmit_buffer(buffer, write_len); + CommandReturn::success() + }, + ) + }) + }) + .unwrap_or(CommandReturn::failure(ErrorCode::FAIL)) + }) + .unwrap_or(CommandReturn::failure(ErrorCode::FAIL)) + } + // Receive from the nRF51822 + 2 => { + let len = arg1; + + // We only allow one app to use the NRF serialization capsule + // (old legacy code, and a difficult thing to virtualize). + // However, we would like to support restarting/updating apps. + // But we don't want to allow a simultaneous app to disrupt the + // app that got to the BLE serialization first. So we have to + // find a compromise. + // + // We handle this by checking if the current active app still + // exists. If it does, we leave it alone. Otherwise, we replace + // it. + self.active_app.map_or_else( + || { + // The app is not set, handle this for the normal case. + self.rx_buffer.take().map_or( + CommandReturn::failure(ErrorCode::RESERVE), + |buffer| { + if len > buffer.len() { + CommandReturn::failure(ErrorCode::SIZE) + } else { + // Set this as the active app for the + // receive callback. + self.active_app.set(processid); + let _ = self.uart.receive_automatic(buffer, len, 250); + CommandReturn::success_u32(len as u32) + } + }, + ) + }, + |processid| { + // The app is set, check if it still exists. + if let Err(kernel::process::Error::NoSuchApp) = + self.apps.enter(processid, |_, _| {}) + { + // The app we had as active no longer exists. + self.active_app.clear(); + self.rx_buffer.take().map_or_else( + || { + // We do not have the RF buffer as it is + // currently in use by the underlying UART. + // We don't have to do anything else except + // update the active app. + self.active_app.set(processid); + CommandReturn::success_u32(len as u32) + }, + |buffer| { + if len > buffer.len() { + CommandReturn::failure(ErrorCode::SIZE) + } else { + self.active_app.set(processid); + // Use the buffer to start the receive. + let _ = self.uart.receive_automatic(buffer, len, 250); + CommandReturn::success_u32(len as u32) + } + }, + ) + } else { + // Active app exists. Return error as there can only + // be one app using this capsule. + CommandReturn::failure(ErrorCode::RESERVE) + } + }, + ) + } + + // Initialize the nRF51822 by resetting it. + 3 => { + self.reset(); + CommandReturn::success() + } + + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { + self.apps.enter(processid, |_, _| {}) + } +} + +// Callbacks from the underlying UART driver. +impl uart::TransmitClient for Nrf51822Serialization<'_> { + // Called when the UART TX has finished. + fn transmitted_buffer( + &self, + buffer: &'static mut [u8], + _tx_len: usize, + _rcode: Result<(), ErrorCode>, + ) { + self.tx_buffer.replace(buffer); + + self.active_app.map(|processid| { + let _ = self.apps.enter(processid, |_app, kernel_data| { + // Call the callback after TX has finished + kernel_data + .schedule_upcall(upcall::TX_DONE_RX_READY, (1, 0, 0)) + .ok(); + }); + }); + } + + fn transmitted_word(&self, _rcode: Result<(), ErrorCode>) {} +} + +impl uart::ReceiveClient for Nrf51822Serialization<'_> { + // Called when a buffer is received on the UART. + fn received_buffer( + &self, + buffer: &'static mut [u8], + rx_len: usize, + _rcode: Result<(), ErrorCode>, + _error: uart::Error, + ) { + self.rx_buffer.replace(buffer); + + // By default we continuously receive on UART. However, if we receive + // and the active app is no longer existent, then we stop receiving. + let mut repeat_receive = true; + + self.active_app.map(|processid| { + if let Err(_err) = self.apps.enter(processid, |_, kernel_data| { + let len = kernel_data + .get_readwrite_processbuffer(rw_allow::RX) + .and_then(|rx| { + rx.mut_enter(|rb| { + // Figure out length to copy. + let max_len = cmp::min(rx_len, rb.len()); + + // Copy over data to app buffer. + self.rx_buffer.map_or(0, |buffer| { + for idx in 0..max_len { + rb[idx].set(buffer[idx]); + } + max_len + }) + }) + }) + .unwrap_or(0); + + // Notify the serialization library in userspace about the + // received buffer. + // + // Note: This indicates how many bytes were received by + // hardware, regardless of how much space (if any) was + // available in the buffer provided by the app. + kernel_data + .schedule_upcall(upcall::TX_DONE_RX_READY, (4, rx_len, len)) + .ok(); + }) { + // The app we had as active no longer exists. Clear that and + // stop receiving. This puts us back in an idle state. A new app + // can use the BLE serialization. + self.active_app.clear(); + repeat_receive = false; + } + }); + + if repeat_receive { + // Restart the UART receive. + self.rx_buffer.take().map(|buffer| { + let len = buffer.len(); + let _ = self.uart.receive_automatic(buffer, len, 250); + }); + } + } + + fn received_word(&self, _word: u32, _rcode: Result<(), ErrorCode>, _err: uart::Error) {} +} diff --git a/capsules/src/panic_button.rs b/capsules/extra/src/panic_button.rs similarity index 88% rename from capsules/src/panic_button.rs rename to capsules/extra/src/panic_button.rs index 824c2b3941..4b0374f4d4 100644 --- a/capsules/src/panic_button.rs +++ b/capsules/extra/src/panic_button.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Debug capsule to cause a button press to trigger a kernel panic. //! //! This can be useful especially when developing or debugging console @@ -10,7 +14,7 @@ //! //! Alternatively, a low-level way of using the capsule is as follows. //! -//! ```rust +//! ```rust,ignore //! let panic_button = static_init!( //! PanicButton, //! PanicButton::new( diff --git a/capsules/src/pca9544a.rs b/capsules/extra/src/pca9544a.rs similarity index 76% rename from capsules/src/pca9544a.rs rename to capsules/extra/src/pca9544a.rs index 51ad90d87a..378e5a3470 100644 --- a/capsules/src/pca9544a.rs +++ b/capsules/extra/src/pca9544a.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for the PCA9544A I2C Selector. //! //! This chip allows for multiple I2C devices with the same addresses to @@ -16,15 +20,17 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! let pca9544a_i2c = static_init!( //! capsules::virtual_i2c::I2CDevice, //! capsules::virtual_i2c::I2CDevice::new(i2c_bus, 0x70)); +//! let pca9544a_buffer = static_init!([u8; capsules::pca9544a::BUFFER_LENGTH], +//! [0; capsules::pca9544a::BUFFER_LENGTH]); //! let pca9544a = static_init!( //! capsules::pca9544a::PCA9544A<'static>, -//! capsules::pca9544a::PCA9544A::new(pca9544a_i2c, &mut capsules::pca9544a::BUFFER)); +//! capsules::pca9544a::PCA9544A::new(pca9544a_i2c, pca9544a_buffer)); //! pca9544a_i2c.set_client(pca9544a); //! ``` @@ -37,10 +43,10 @@ use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::Pca9544a as usize; -pub static mut BUFFER: [u8; 5] = [0; 5]; +pub const BUFFER_LENGTH: usize = 5; #[derive(Clone, Copy, PartialEq)] enum State { @@ -58,22 +64,31 @@ enum ControlField { SelectedChannels, } +/// IDs for subscribed upcalls. +mod upcall { + /// Triggered when a channel is finished being selected or when the current + /// channel setup is returned. + pub const CHANNEL_DONE: usize = 0; + /// Number of upcalls. + pub const COUNT: u8 = 1; +} + #[derive(Default)] pub struct App {} -pub struct PCA9544A<'a> { - i2c: &'a dyn i2c::I2CDevice, +pub struct PCA9544A<'a, I: i2c::I2CDevice> { + i2c: &'a I, state: Cell, buffer: TakeCell<'static, [u8]>, - apps: Grant, AllowRoCount<0>, AllowRwCount<0>>, + apps: Grant, AllowRoCount<0>, AllowRwCount<0>>, owning_process: OptionalCell, } -impl<'a> PCA9544A<'a> { +impl<'a, I: i2c::I2CDevice> PCA9544A<'a, I> { pub fn new( - i2c: &'a dyn i2c::I2CDevice, + i2c: &'a I, buffer: &'static mut [u8], - grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, + grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, ) -> Self { Self { i2c, @@ -107,7 +122,7 @@ impl<'a> PCA9544A<'a> { } // TODO verify errors - let _ = self.i2c.write(buffer, index as u8); + let _ = self.i2c.write(buffer, index); self.state.set(State::Done); CommandReturn::success() @@ -138,7 +153,7 @@ impl<'a> PCA9544A<'a> { } } -impl i2c::I2CClient for PCA9544A<'_> { +impl i2c::I2CClient for PCA9544A<'_, I> { fn command_complete(&self, buffer: &'static mut [u8], _status: Result<(), i2c::Error>) { match self.state.get() { State::ReadControl(field) => { @@ -148,9 +163,12 @@ impl i2c::I2CClient for PCA9544A<'_> { }; self.owning_process.map(|pid| { - let _ = self.apps.enter(*pid, |_app, upcalls| { + let _ = self.apps.enter(pid, |_app, upcalls| { upcalls - .schedule_upcall(0, (field as usize + 1, ret as usize, 0)) + .schedule_upcall( + upcall::CHANNEL_DONE, + (field as usize + 1, ret as usize, 0), + ) .ok(); }); }); @@ -161,8 +179,10 @@ impl i2c::I2CClient for PCA9544A<'_> { } State::Done => { self.owning_process.map(|pid| { - let _ = self.apps.enter(*pid, |_app, upcalls| { - upcalls.schedule_upcall(0, (0, 0, 0)).ok(); + let _ = self.apps.enter(pid, |_app, upcalls| { + upcalls + .schedule_upcall(upcall::CHANNEL_DONE, (0, 0, 0)) + .ok(); }); }); @@ -175,19 +195,12 @@ impl i2c::I2CClient for PCA9544A<'_> { } } -impl SyscallDriver for PCA9544A<'_> { - // Setup callback for event done. - // - // ### `subscribe_num` - // - // - `0`: Upcall is triggered when a channel is finished being selected - // or when the current channel setup is returned. - +impl SyscallDriver for PCA9544A<'_, I> { /// Control the I2C selector. /// /// ### `command_num` /// - /// - `0`: Driver check. + /// - `0`: Driver existence check. /// - `1`: Choose which channels are active. /// - `2`: Disable all channels. /// - `3`: Read the list of fired interrupts. @@ -208,7 +221,7 @@ impl SyscallDriver for PCA9544A<'_> { // some (alive) process let match_or_empty_or_nonexistant = self.owning_process.map_or(true, |current_process| { self.apps - .enter(*current_process, |_, _| current_process == &process_id) + .enter(current_process, |_, _| current_process == process_id) .unwrap_or(true) }); if match_or_empty_or_nonexistant { @@ -222,16 +235,16 @@ impl SyscallDriver for PCA9544A<'_> { 0 => CommandReturn::success(), // Select channels. - 1 => self.select_channels(data as u8).into(), + 1 => self.select_channels(data as u8), // Disable all channels. - 2 => self.select_channels(0).into(), + 2 => self.select_channels(0), // Read the current interrupt fired mask. - 3 => self.read_interrupts().into(), + 3 => self.read_interrupts(), // Read the current selected channels. - 4 => self.read_selected_channels().into(), + 4 => self.read_selected_channels(), // default _ => CommandReturn::failure(ErrorCode::NOSUPPORT), diff --git a/capsules/extra/src/pressure.rs b/capsules/extra/src/pressure.rs new file mode 100644 index 0000000000..93d9cf5110 --- /dev/null +++ b/capsules/extra/src/pressure.rs @@ -0,0 +1,152 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Provides userspace with access to barometer sensors. +//! +//! Userspace Interface +//! ------------------- +//! +//! ### `subscribe` System Call +//! +//! The `subscribe` system call supports the single `subscribe_number` zero, +//! which is used to provide a callback that will return back the result of +//! a barometer sensor reading. +//! The `subscribe`call return codes indicate the following: +//! +//! * `Ok(())`: the callback been successfully been configured. +//! * `ENOSUPPORT`: Invalid allow_num. +//! * `NOMEM`: No sufficient memory available. +//! * `INVAL`: Invalid address of the buffer or other error. +//! +//! +//! ### `command` System Call +//! +//! The `command` system call support one argument `cmd` which is used to specify the specific +//! operation, currently the following cmd's are supported: +//! +//! * `0`: check whether the driver exist +//! * `1`: read the barometer +//! +//! +//! The possible return from the 'command' system call indicates the following: +//! +//! * `Ok(())`: The operation has been successful. +//! * `BUSY`: The driver is busy. +//! * `ENOSUPPORT`: Invalid `cmd`. +//! * `NOMEM`: No sufficient memory available. +//! * `INVAL`: Invalid address of the buffer or other error. +//! +//! Usage +//! ----- +//! +//! You need a device that provides the `hil::sensors::PressureDriver` trait. +//! +//! ```rust,ignore +//! # use kernel::static_init; +//! +//! let pressure = static_init!( +//! capsules::temperature::PressureSensor<'static>, +//! capsules::temperature::PressureSensor::new(si7021, +//! board_kernel.create_grant(&grant_cap))); +//! +//! kernel::hil::sensors::PressureDriver::set_client(si7021, pressure); +//! ``` + +use core::cell::Cell; + +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::hil; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::{ErrorCode, ProcessId}; + +/// Syscall driver number. +use capsules_core::driver; +pub const DRIVER_NUM: usize = driver::NUM::Pressure as usize; + +#[derive(Default)] +pub struct App { + subscribed: bool, +} + +pub struct PressureSensor<'a, T: hil::sensors::PressureDriver<'a>> { + driver: &'a T, + apps: Grant, AllowRoCount<0>, AllowRwCount<0>>, + busy: Cell, +} + +impl<'a, T: hil::sensors::PressureDriver<'a>> PressureSensor<'a, T> { + pub fn new( + driver: &'a T, + apps: Grant, AllowRoCount<0>, AllowRwCount<0>>, + ) -> PressureSensor<'a, T> { + PressureSensor { + driver, + apps, + busy: Cell::new(false), + } + } + + fn enqueue_command(&self, processid: ProcessId) -> CommandReturn { + self.apps + .enter(processid, |app, _| { + app.subscribed = true; + if !self.busy.get() { + let res = self.driver.read_atmospheric_pressure(); + if let Ok(err) = ErrorCode::try_from(res) { + CommandReturn::failure(err) + } else { + self.busy.set(true); + CommandReturn::success() + } + } else { + CommandReturn::success() + } + }) + .unwrap_or_else(|err| CommandReturn::failure(err.into())) + } +} + +impl<'a, T: hil::sensors::PressureDriver<'a>> hil::sensors::PressureClient + for PressureSensor<'a, T> +{ + fn callback(&self, pressure: Result) { + self.busy.set(false); + for cntr in self.apps.iter() { + cntr.enter(|app, upcalls| { + if app.subscribed { + app.subscribed = false; + let result = match pressure { + Ok(pressure_value) => ( + kernel::errorcode::into_statuscode(Ok(())), + pressure_value as usize, + 0, + ), + Err(err) => (kernel::errorcode::into_statuscode(Err(err)), 0, 0), + }; + upcalls.schedule_upcall(0, result).ok(); + } + }) + } + } +} + +impl<'a, T: hil::sensors::PressureDriver<'a>> SyscallDriver for PressureSensor<'a, T> { + fn command( + &self, + command_num: usize, + _: usize, + _: usize, + process_id: ProcessId, + ) -> CommandReturn { + match command_num { + 0 => CommandReturn::success(), + 1 => self.enqueue_command(process_id), + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, process_id: ProcessId) -> Result<(), kernel::process::Error> { + self.apps.enter(process_id, |_, _| {}) + } +} diff --git a/capsules/src/proximity.rs b/capsules/extra/src/proximity.rs similarity index 94% rename from capsules/src/proximity.rs rename to capsules/extra/src/proximity.rs index ae7d0a1292..d82b09ee00 100644 --- a/capsules/src/proximity.rs +++ b/capsules/extra/src/proximity.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides userspace with access to proximity sensors. //! //! Userspace Interface @@ -19,7 +23,7 @@ //! The `command` system call support one argument `cmd` which is used to specify the specific //! operation, currently the following cmd's are supported: //! -//! * `0`: check whether the driver exist +//! * `0`: driver existence check //! * `1`: read proximity //! * `2`: read proximity on interrupt //! @@ -36,7 +40,7 @@ //! You need a device that provides the `hil::sensors::ProximityDriver` trait. //! Here is an example of how to set up a proximity sensor with the apds9960 IC //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //!let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); @@ -56,7 +60,7 @@ use kernel::syscall::{CommandReturn, SyscallDriver}; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::Proximity as usize; #[derive(Default)] @@ -67,19 +71,14 @@ pub struct App { upper_proximity: u8, } -#[derive(Clone, Copy, PartialEq)] +#[derive(Clone, Copy, PartialEq, Default)] pub enum ProximityCommand { ReadProximity = 1, ReadProximityOnInterrupt = 2, + #[default] NoCommand = 3, } -impl Default for ProximityCommand { - fn default() -> Self { - ProximityCommand::NoCommand - } -} - #[derive(Default)] pub struct Thresholds { lower: u8, @@ -98,7 +97,7 @@ impl<'a> ProximitySensor<'a> { grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, ) -> ProximitySensor<'a> { ProximitySensor { - driver: driver, + driver, apps: grant, command_running: Cell::new(ProximityCommand::NoCommand), } @@ -109,11 +108,11 @@ impl<'a> ProximitySensor<'a> { command: ProximityCommand, arg1: usize, arg2: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { - // Enqueue command by saving command type, args, appid within app struct in grant region + // Enqueue command by saving command type, args, processid within app struct in grant region self.apps - .enter(appid, |app, _| { + .enter(processid, |app, _| { // Return busy if same app attempts to enqueue second command before first one is "callbacked" if app.subscribed { return CommandReturn::failure(ErrorCode::BUSY); @@ -266,9 +265,7 @@ impl hil::sensors::ProximityClient for ProximitySensor<'_> { if app.enqueued_command_type == ProximityCommand::ReadProximityOnInterrupt { // Case: ReadProximityOnInterrupt // Only callback to those apps which we expect would want to know about this threshold reading. - if ((temp_val as u8) > app.upper_proximity) - || ((temp_val as u8) < app.lower_proximity) - { + if (temp_val > app.upper_proximity) || (temp_val < app.lower_proximity) { upcalls.schedule_upcall(0, (temp_val as usize, 0, 0)).ok(); app.subscribed = false; // dequeue } @@ -296,21 +293,21 @@ impl SyscallDriver for ProximitySensor<'_> { command_num: usize, arg1: usize, arg2: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { match command_num { - // check whether the driver exist!! + // Driver existence check 0 => CommandReturn::success(), // Instantaneous proximity measurement - 1 => self.enqueue_command(ProximityCommand::ReadProximity, arg1, arg2, appid), + 1 => self.enqueue_command(ProximityCommand::ReadProximity, arg1, arg2, processid), // Upcall occurs only after interrupt is fired 2 => self.enqueue_command( ProximityCommand::ReadProximityOnInterrupt, arg1, arg2, - appid, + processid, ), _ => CommandReturn::failure(ErrorCode::NOSUPPORT), diff --git a/capsules/extra/src/public_key_crypto/mod.rs b/capsules/extra/src/public_key_crypto/mod.rs new file mode 100644 index 0000000000..2fd4b5a2d6 --- /dev/null +++ b/capsules/extra/src/public_key_crypto/mod.rs @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Provides capsules for asymmetric encryption + +pub mod rsa_keys; diff --git a/capsules/extra/src/public_key_crypto/rsa_keys.rs b/capsules/extra/src/public_key_crypto/rsa_keys.rs new file mode 100644 index 0000000000..d74aed9a77 --- /dev/null +++ b/capsules/extra/src/public_key_crypto/rsa_keys.rs @@ -0,0 +1,741 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Helper library for RSA public and private keys + +use core::cell::Cell; +use kernel::hil::public_key_crypto::keys::{ + PubKey, PubKeyMut, PubPrivKey, PubPrivKeyMut, RsaKey, RsaKeyMut, RsaPrivKey, RsaPrivKeyMut, +}; +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::mut_imut_buffer::MutImutBuffer; +use kernel::ErrorCode; + +// Copy OpenSSL and use e as 65537 +const PUBLIC_EXPONENT: u32 = 65537; + +/// A Public/Private RSA key pair +/// The key is `L` bytes long +struct RSAKeys { + public_key: OptionalCell>, + public_exponent: Cell, + private_key: OptionalCell>, +} + +impl RSAKeys { + const fn new() -> Self { + Self { + public_key: OptionalCell::empty(), + public_exponent: Cell::new(PUBLIC_EXPONENT), + private_key: OptionalCell::empty(), + } + } + + /// `public_key` is a buffer containing the public key. + /// This is the `L` byte modulus (also called `n`). + fn import_public_key( + &self, + public_key: MutImutBuffer<'static, u8>, + ) -> Result<(), (ErrorCode, MutImutBuffer<'static, u8>)> { + if public_key.len() != L { + return Err((ErrorCode::SIZE, public_key)); + } + + self.public_key.replace(public_key); + + Ok(()) + } + + fn pub_key(&self) -> Result, ErrorCode> { + if self.public_key.is_some() { + Ok(self.public_key.take().unwrap()) + } else { + Err(ErrorCode::NODEVICE) + } + } + + fn import_private_key( + &self, + private_key: MutImutBuffer<'static, u8>, + ) -> Result<(), (ErrorCode, MutImutBuffer<'static, u8>)> { + if private_key.len() != L { + return Err((ErrorCode::SIZE, private_key)); + } + + self.private_key.replace(private_key); + + Ok(()) + } + + fn priv_key(&self) -> Result, ErrorCode> { + if self.private_key.is_some() { + Ok(self.private_key.take().unwrap()) + } else { + Err(ErrorCode::NODEVICE) + } + } +} + +impl PubKey for RSAKeys { + /// `public_key` is a buffer containing the public key. + /// This is the `L` byte modulus (also called `n`). + fn import_public_key( + &self, + public_key: &'static [u8], + ) -> Result<(), (ErrorCode, &'static [u8])> { + if public_key.len() != L { + return Err((ErrorCode::SIZE, public_key)); + } + + self.public_key + .replace(MutImutBuffer::Immutable(public_key)); + + Ok(()) + } + + fn pub_key(&self) -> Result<&'static [u8], kernel::ErrorCode> { + if self.public_key.is_some() { + match self.public_key.take().unwrap() { + MutImutBuffer::Immutable(ret) => Ok(ret), + MutImutBuffer::Mutable(_ret) => unreachable!(), + } + } else { + Err(ErrorCode::NODEVICE) + } + } + + fn len(&self) -> usize { + if let Some(key) = self.public_key.take() { + let ret = key.len(); + self.public_key.set(key); + ret + } else { + 0 + } + } +} + +impl PubKeyMut for RSAKeys { + /// `public_key` is a buffer containing the public key. + /// This is the `L` byte modulus (also called `n`). + fn import_public_key( + &self, + public_key: &'static mut [u8], + ) -> Result<(), (ErrorCode, &'static mut [u8])> { + if public_key.len() != L { + return Err((ErrorCode::SIZE, public_key)); + } + + self.public_key.replace(MutImutBuffer::Mutable(public_key)); + + Ok(()) + } + + fn pub_key(&self) -> Result<&'static mut [u8], kernel::ErrorCode> { + if self.public_key.is_some() { + match self.public_key.take().unwrap() { + MutImutBuffer::Mutable(ret) => Ok(ret), + MutImutBuffer::Immutable(_ret) => unreachable!(), + } + } else { + Err(ErrorCode::NODEVICE) + } + } + + fn len(&self) -> usize { + if let Some(key) = self.public_key.take() { + let ret = key.len(); + self.public_key.set(key); + ret + } else { + 0 + } + } +} + +impl PubPrivKey for RSAKeys { + /// `private_key` is a buffer containing the private key. + /// The first `L` bytes are the private_exponent (also called `d`). + fn import_private_key( + &self, + private_key: &'static [u8], + ) -> Result<(), (ErrorCode, &'static [u8])> { + if private_key.len() != L { + return Err((ErrorCode::SIZE, private_key)); + } + + self.private_key + .replace(MutImutBuffer::Immutable(private_key)); + + Ok(()) + } + + fn priv_key(&self) -> Result<&'static [u8], ErrorCode> { + if self.private_key.is_some() { + match self.private_key.take().unwrap() { + MutImutBuffer::Immutable(ret) => Ok(ret), + MutImutBuffer::Mutable(_ret) => unreachable!(), + } + } else { + Err(ErrorCode::NODEVICE) + } + } + + fn len(&self) -> usize { + if let Some(key) = self.private_key.take() { + let ret = key.len(); + self.private_key.set(key); + ret + } else { + 0 + } + } +} + +impl PubPrivKeyMut for RSAKeys { + /// `private_key` is a buffer containing the private key. + /// The first `L` bytes are the private_exponent (also called `d`). + fn import_private_key( + &self, + private_key: &'static mut [u8], + ) -> Result<(), (ErrorCode, &'static mut [u8])> { + if private_key.len() != L { + return Err((ErrorCode::SIZE, private_key)); + } + + self.private_key + .replace(MutImutBuffer::Mutable(private_key)); + + Ok(()) + } + + fn priv_key(&self) -> Result<&'static mut [u8], ErrorCode> { + if self.private_key.is_some() { + match self.private_key.take().unwrap() { + MutImutBuffer::Mutable(ret) => Ok(ret), + MutImutBuffer::Immutable(_ret) => unreachable!(), + } + } else { + Err(ErrorCode::NODEVICE) + } + } + + fn len(&self) -> usize { + if let Some(key) = self.private_key.take() { + let ret = key.len(); + self.private_key.set(key); + ret + } else { + 0 + } + } +} + +impl RsaKey for RSAKeys { + fn map_modulus(&self, closure: &dyn Fn(&[u8])) -> Option<()> { + if let Some(public_key) = self.public_key.take() { + match public_key { + MutImutBuffer::Mutable(ref _buf) => unreachable!(), + MutImutBuffer::Immutable(buf) => { + closure(buf); + } + } + self.public_key.replace(public_key); + Some(()) + } else { + None + } + } + + fn take_modulus(&self) -> Option<&'static [u8]> { + if let Some(public_key) = self.public_key.take() { + match public_key { + MutImutBuffer::Immutable(ret) => Some(ret), + MutImutBuffer::Mutable(_ret) => unreachable!(), + } + } else { + None + } + } + + fn public_exponent(&self) -> Option { + Some(self.public_exponent.get()) + } +} + +impl RsaKeyMut for RSAKeys { + fn map_modulus(&self, closure: &dyn Fn(&mut [u8])) -> Option<()> { + if let Some(mut public_key) = self.public_key.take() { + match public_key { + MutImutBuffer::Mutable(ref mut buf) => { + closure(buf); + } + MutImutBuffer::Immutable(_buf) => unreachable!(), + } + self.public_key.replace(public_key); + Some(()) + } else { + None + } + } + + fn take_modulus(&self) -> Option<&'static mut [u8]> { + if let Some(public_key) = self.public_key.take() { + match public_key { + MutImutBuffer::Mutable(ret) => Some(ret), + MutImutBuffer::Immutable(_ret) => unreachable!(), + } + } else { + None + } + } + + fn public_exponent(&self) -> Option { + Some(self.public_exponent.get()) + } +} + +impl RsaPrivKey for RSAKeys { + fn map_exponent(&self, closure: &dyn Fn(&[u8])) -> Option<()> { + if let Some(private_key) = self.private_key.take() { + match private_key { + MutImutBuffer::Mutable(ref _buf) => unreachable!(), + MutImutBuffer::Immutable(buf) => { + closure(buf); + } + }; + self.private_key.replace(private_key); + Some(()) + } else { + None + } + } + + fn take_exponent(&self) -> Option<&'static [u8]> { + if let Some(private_key) = self.private_key.take() { + match private_key { + MutImutBuffer::Immutable(ret) => Some(ret), + MutImutBuffer::Mutable(_ret) => unreachable!(), + } + } else { + None + } + } +} + +impl RsaPrivKeyMut for RSAKeys { + fn map_exponent(&self, closure: &dyn Fn(&mut [u8])) -> Option<()> { + if let Some(mut private_key) = self.private_key.take() { + match private_key { + MutImutBuffer::Mutable(ref mut buf) => { + closure(buf); + } + MutImutBuffer::Immutable(_buf) => unreachable!(), + }; + self.private_key.replace(private_key); + Some(()) + } else { + None + } + } + + fn take_exponent(&self) -> Option<&'static mut [u8]> { + if let Some(private_key) = self.private_key.take() { + match private_key { + MutImutBuffer::Mutable(ret) => Some(ret), + MutImutBuffer::Immutable(_ret) => unreachable!(), + } + } else { + None + } + } +} + +pub struct RSA2048Keys(RSAKeys<256>); + +impl RSA2048Keys { + pub const fn new() -> RSA2048Keys { + RSA2048Keys(RSAKeys::<256>::new()) + } +} + +impl PubKey for RSA2048Keys { + fn import_public_key( + &self, + public_key: &'static [u8], + ) -> Result<(), (kernel::ErrorCode, &'static [u8])> { + let key = self + .0 + .import_public_key(MutImutBuffer::Immutable(public_key)); + + match key { + Err((e, buf)) => match buf { + MutImutBuffer::Immutable(ret) => Err((e, ret)), + MutImutBuffer::Mutable(_ret) => unreachable!(), + }, + Ok(()) => Ok(()), + } + } + + fn pub_key(&self) -> Result<&'static [u8], kernel::ErrorCode> { + match self.0.pub_key() { + Ok(buf) => match buf { + MutImutBuffer::Immutable(ret) => Ok(ret), + MutImutBuffer::Mutable(_ret) => unreachable!(), + }, + Err(e) => Err(e), + } + } + + fn len(&self) -> usize { + PubKey::len(&self.0) + } +} + +impl PubPrivKey for RSA2048Keys { + fn import_private_key( + &self, + private_key: &'static [u8], + ) -> Result<(), (kernel::ErrorCode, &'static [u8])> { + let key = self + .0 + .import_private_key(MutImutBuffer::Immutable(private_key)); + + match key { + Err((e, buf)) => match buf { + MutImutBuffer::Immutable(ret) => Err((e, ret)), + MutImutBuffer::Mutable(_ret) => unreachable!(), + }, + Ok(()) => Ok(()), + } + } + + fn priv_key(&self) -> Result<&'static [u8], kernel::ErrorCode> { + match self.0.priv_key() { + Ok(buf) => match buf { + MutImutBuffer::Immutable(ret) => Ok(ret), + MutImutBuffer::Mutable(_ret) => unreachable!(), + }, + Err(e) => Err(e), + } + } + + fn len(&self) -> usize { + PubPrivKey::len(&self.0) + } +} + +impl RsaKey for RSA2048Keys { + fn map_modulus(&self, closure: &dyn Fn(&[u8])) -> Option<()> { + RsaKey::map_modulus(&self.0, closure) + } + + fn take_modulus(&self) -> Option<&'static [u8]> { + RsaKey::take_modulus(&self.0) + } + + fn public_exponent(&self) -> Option { + RsaKey::public_exponent(&self.0) + } +} + +impl RsaPrivKey for RSA2048Keys { + fn map_exponent(&self, closure: &dyn Fn(&[u8])) -> Option<()> { + RsaPrivKey::map_exponent(&self.0, closure) + } + + fn take_exponent(&self) -> Option<&'static [u8]> { + RsaPrivKey::take_exponent(&self.0) + } +} + +pub struct RSA2048KeysMut(RSAKeys<256>); + +impl RSA2048KeysMut { + pub const fn new() -> RSA2048KeysMut { + RSA2048KeysMut(RSAKeys::<256>::new()) + } +} + +impl PubKeyMut for RSA2048KeysMut { + fn import_public_key( + &self, + public_key: &'static mut [u8], + ) -> Result<(), (kernel::ErrorCode, &'static mut [u8])> { + let key = self.0.import_public_key(MutImutBuffer::Mutable(public_key)); + + match key { + Err((e, buf)) => match buf { + MutImutBuffer::Mutable(ret) => Err((e, ret)), + MutImutBuffer::Immutable(_ret) => unreachable!(), + }, + Ok(()) => Ok(()), + } + } + + fn pub_key(&self) -> Result<&'static mut [u8], kernel::ErrorCode> { + match self.0.pub_key() { + Ok(buf) => match buf { + MutImutBuffer::Mutable(ret) => Ok(ret), + MutImutBuffer::Immutable(_ret) => unreachable!(), + }, + Err(e) => Err(e), + } + } + + fn len(&self) -> usize { + PubKey::len(&self.0) + } +} + +impl PubPrivKeyMut for RSA2048KeysMut { + fn import_private_key( + &self, + private_key: &'static mut [u8], + ) -> Result<(), (kernel::ErrorCode, &'static mut [u8])> { + let key = self + .0 + .import_private_key(MutImutBuffer::Mutable(private_key)); + + match key { + Err((e, buf)) => match buf { + MutImutBuffer::Mutable(ret) => Err((e, ret)), + MutImutBuffer::Immutable(_ret) => unreachable!(), + }, + Ok(()) => Ok(()), + } + } + + fn priv_key(&self) -> Result<&'static mut [u8], kernel::ErrorCode> { + match self.0.priv_key() { + Ok(buf) => match buf { + MutImutBuffer::Mutable(ret) => Ok(ret), + MutImutBuffer::Immutable(_ret) => unreachable!(), + }, + Err(e) => Err(e), + } + } + + fn len(&self) -> usize { + PubPrivKey::len(&self.0) + } +} + +impl RsaKeyMut for RSA2048KeysMut { + fn map_modulus(&self, closure: &dyn Fn(&mut [u8])) -> Option<()> { + RsaKeyMut::map_modulus(&self.0, closure) + } + + fn take_modulus(&self) -> Option<&'static mut [u8]> { + RsaKeyMut::take_modulus(&self.0) + } + + fn public_exponent(&self) -> Option { + RsaKeyMut::public_exponent(&self.0) + } +} + +impl RsaPrivKeyMut for RSA2048KeysMut { + fn map_exponent(&self, closure: &dyn Fn(&mut [u8])) -> Option<()> { + RsaPrivKeyMut::map_exponent(&self.0, closure) + } + + fn take_exponent(&self) -> Option<&'static mut [u8]> { + RsaPrivKeyMut::take_exponent(&self.0) + } +} + +pub struct RSA4096Keys(RSAKeys<512>); + +impl RSA4096Keys { + pub const fn new() -> RSA4096Keys { + RSA4096Keys(RSAKeys::<512>::new()) + } +} + +impl PubKey for RSA4096Keys { + fn import_public_key( + &self, + public_key: &'static [u8], + ) -> Result<(), (kernel::ErrorCode, &'static [u8])> { + let key = self + .0 + .import_public_key(MutImutBuffer::Immutable(public_key)); + + match key { + Err((e, buf)) => match buf { + MutImutBuffer::Immutable(ret) => Err((e, ret)), + MutImutBuffer::Mutable(_ret) => unreachable!(), + }, + Ok(()) => Ok(()), + } + } + + fn pub_key(&self) -> Result<&'static [u8], kernel::ErrorCode> { + match self.0.pub_key() { + Ok(buf) => match buf { + MutImutBuffer::Immutable(ret) => Ok(ret), + MutImutBuffer::Mutable(_ret) => unreachable!(), + }, + Err(e) => Err(e), + } + } + + fn len(&self) -> usize { + PubKey::len(&self.0) + } +} + +impl PubPrivKey for RSA4096Keys { + fn import_private_key( + &self, + private_key: &'static [u8], + ) -> Result<(), (kernel::ErrorCode, &'static [u8])> { + let key = self + .0 + .import_private_key(MutImutBuffer::Immutable(private_key)); + + match key { + Err((e, buf)) => match buf { + MutImutBuffer::Immutable(ret) => Err((e, ret)), + MutImutBuffer::Mutable(_ret) => unreachable!(), + }, + Ok(()) => Ok(()), + } + } + + fn priv_key(&self) -> Result<&'static [u8], kernel::ErrorCode> { + match self.0.priv_key() { + Ok(buf) => match buf { + MutImutBuffer::Immutable(ret) => Ok(ret), + MutImutBuffer::Mutable(_ret) => unreachable!(), + }, + Err(e) => Err(e), + } + } + + fn len(&self) -> usize { + PubPrivKey::len(&self.0) + } +} + +impl RsaKey for RSA4096Keys { + fn map_modulus(&self, closure: &dyn Fn(&[u8])) -> Option<()> { + RsaKey::map_modulus(&self.0, closure) + } + + fn take_modulus(&self) -> Option<&'static [u8]> { + RsaKey::take_modulus(&self.0) + } + + fn public_exponent(&self) -> Option { + RsaKey::public_exponent(&self.0) + } +} + +impl RsaPrivKey for RSA4096Keys { + fn map_exponent(&self, closure: &dyn Fn(&[u8])) -> Option<()> { + RsaPrivKey::map_exponent(&self.0, closure) + } + + fn take_exponent(&self) -> Option<&'static [u8]> { + RsaPrivKey::take_exponent(&self.0) + } +} + +pub struct RSA4096KeysMut(RSAKeys<512>); + +impl RSA4096KeysMut { + pub const fn new() -> RSA4096KeysMut { + RSA4096KeysMut(RSAKeys::<512>::new()) + } +} + +impl PubKeyMut for RSA4096KeysMut { + fn import_public_key( + &self, + public_key: &'static mut [u8], + ) -> Result<(), (kernel::ErrorCode, &'static mut [u8])> { + let key = self.0.import_public_key(MutImutBuffer::Mutable(public_key)); + + match key { + Err((e, buf)) => match buf { + MutImutBuffer::Mutable(ret) => Err((e, ret)), + MutImutBuffer::Immutable(_ret) => unreachable!(), + }, + Ok(()) => Ok(()), + } + } + + fn pub_key(&self) -> Result<&'static mut [u8], kernel::ErrorCode> { + match self.0.pub_key() { + Ok(buf) => match buf { + MutImutBuffer::Mutable(ret) => Ok(ret), + MutImutBuffer::Immutable(_ret) => unreachable!(), + }, + Err(e) => Err(e), + } + } + + fn len(&self) -> usize { + PubKey::len(&self.0) + } +} + +impl PubPrivKeyMut for RSA4096KeysMut { + fn import_private_key( + &self, + private_key: &'static mut [u8], + ) -> Result<(), (kernel::ErrorCode, &'static mut [u8])> { + let key = self + .0 + .import_private_key(MutImutBuffer::Mutable(private_key)); + + match key { + Err((e, buf)) => match buf { + MutImutBuffer::Mutable(ret) => Err((e, ret)), + MutImutBuffer::Immutable(_ret) => unreachable!(), + }, + Ok(()) => Ok(()), + } + } + + fn priv_key(&self) -> Result<&'static mut [u8], kernel::ErrorCode> { + match self.0.priv_key() { + Ok(buf) => match buf { + MutImutBuffer::Mutable(ret) => Ok(ret), + MutImutBuffer::Immutable(_ret) => unreachable!(), + }, + Err(e) => Err(e), + } + } + + fn len(&self) -> usize { + PubPrivKey::len(&self.0) + } +} + +impl RsaKeyMut for RSA4096KeysMut { + fn map_modulus(&self, closure: &dyn Fn(&mut [u8])) -> Option<()> { + RsaKeyMut::map_modulus(&self.0, closure) + } + + fn take_modulus(&self) -> Option<&'static mut [u8]> { + RsaKeyMut::take_modulus(&self.0) + } + + fn public_exponent(&self) -> Option { + RsaKeyMut::public_exponent(&self.0) + } +} + +impl RsaPrivKeyMut for RSA4096KeysMut { + fn map_exponent(&self, closure: &dyn Fn(&mut [u8])) -> Option<()> { + RsaPrivKeyMut::map_exponent(&self.0, closure) + } + + fn take_exponent(&self) -> Option<&'static mut [u8]> { + RsaPrivKeyMut::take_exponent(&self.0) + } +} diff --git a/capsules/extra/src/pwm.rs b/capsules/extra/src/pwm.rs new file mode 100644 index 0000000000..5bc2f56a36 --- /dev/null +++ b/capsules/extra/src/pwm.rs @@ -0,0 +1,166 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::hil; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::utilities::cells::OptionalCell; +use kernel::{ErrorCode, ProcessId}; + +/// Syscall driver number. +use capsules_core::driver; +pub const DRIVER_NUM: usize = driver::NUM::Pwm as usize; + +// An empty app, for potential uses in future updates of the driver +#[derive(Default)] +pub struct App; + +pub struct Pwm<'a, const NUM_PINS: usize> { + /// The usable pwm pins. + pwm_pins: &'a [&'a dyn hil::pwm::PwmPin; NUM_PINS], + /// Per-app state. + apps: Grant, AllowRoCount<0>, AllowRwCount<0>>, + /// An array of apps associated to their reserved pins. + active_process: [OptionalCell; NUM_PINS], +} + +impl<'a, const NUM_PINS: usize> Pwm<'a, NUM_PINS> { + pub fn new( + pwm_pins: &'a [&'a dyn hil::pwm::PwmPin; NUM_PINS], + grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, + ) -> Pwm<'a, NUM_PINS> { + assert!(u16::try_from(NUM_PINS).is_ok()); + const EMPTY: OptionalCell = OptionalCell::empty(); + Pwm { + pwm_pins, + apps: grant, + active_process: [EMPTY; NUM_PINS], + } + } + + pub fn claim_pin(&self, processid: ProcessId, pin: usize) -> bool { + // Attempt to get the app that is using the pin. + self.active_process[pin].map_or(true, |id| { + // If the app is empty, that means that there is no app currently using this pin, + // therefore the pin could be usable by the new app + if id == processid { + // The same app is trying to access the pin it has access to, valid + true + } else { + // An app is trying to access another app's pin, invalid + false + } + }) + } + + pub fn release_pin(&self, pin: usize) { + // Release the claimed pin so that it can now be used by another process. + self.active_process[pin].clear(); + } +} + +/// Provide an interface for userland. +impl<'a, const NUM_PINS: usize> SyscallDriver for Pwm<'a, NUM_PINS> { + /// Command interface. + /// + /// ### `command_num` + /// + /// - `0`: Driver existence check. + /// - `1`: Start the PWM pin output. First 16 bits of `data1` are used for the duty cycle, as a + /// percentage with 2 decimals, and the last 16 bits of `data1` are used for the PWM channel + /// to be controlled. `data2` is used for the frequency in hertz. For the duty cycle, 100% is + /// the max duty cycle for this pin. + /// - `2`: Stop the PWM output. + /// - `3`: Return the maximum possible frequency for this pin. + /// - `4`: Return number of PWM pins if this driver is included on the platform. + fn command( + &self, + command_num: usize, + data1: usize, + data2: usize, + processid: ProcessId, + ) -> CommandReturn { + match command_num { + // Check existence. + 0 => CommandReturn::success(), + + // Start the pwm output. + + // data1 stores the duty cycle and the pin number in the format + // +------------------+------------------+ + // | duty cycle (u16) | pwm pin (u16) | + // +------------------+------------------+ + // This format was chosen because there are only 2 parameters in the command function that can be used for storing values, + // but in this case, 3 values are needed (pin, frequency, duty cycle), so data1 stores two of these values that can be + // represented using only 16 bits. + 1 => { + let pin = data1 & ((1 << 16) - 1); + let duty_cycle = data1 >> 16; + let frequency_hz = data2; + + if pin >= NUM_PINS { + // App asked to use a pin that doesn't exist. + CommandReturn::failure(ErrorCode::INVAL) + } else { + if !self.claim_pin(processid, pin) { + // App cannot claim pin. + CommandReturn::failure(ErrorCode::RESERVE) + } else { + // App can claim pin, start pwm pin at given frequency and duty_cycle. + self.active_process[pin].set(processid); + // Duty cycle is represented as a 4 digit number, so we divide by 10000 to get the percentage of the max duty cycle. + // e.g.: a duty cycle of 60.5% is represented as 6050, so the actual value of the duty cycle is + // 6050 * max_duty_cycle / 10000 = 0.605 * max_duty_cycle + self.pwm_pins[pin] + .start( + frequency_hz, + duty_cycle * self.pwm_pins[pin].get_maximum_duty_cycle() / 10000, + ) + .into() + } + } + } + + // Stop the PWM output. + 2 => { + let pin = data1; + if pin >= NUM_PINS { + // App asked to use a pin that doesn't exist. + CommandReturn::failure(ErrorCode::INVAL) + } else { + if !self.claim_pin(processid, pin) { + // App cannot claim pin. + CommandReturn::failure(ErrorCode::RESERVE) + } else if self.active_process[pin].is_none() { + // If there is no active app, the pwm pin isn't in use. + CommandReturn::failure(ErrorCode::OFF) + } else { + // Release the pin and stop pwm output. + self.release_pin(pin); + self.pwm_pins[pin].stop().into() + } + } + } + + // Get max frequency of pin. + 3 => { + let pin = data1; + if pin >= NUM_PINS { + CommandReturn::failure(ErrorCode::INVAL) + } else { + CommandReturn::success_u32(self.pwm_pins[pin].get_maximum_frequency_hz() as u32) + } + } + + // Return number of usable PWM pins. + 4 => CommandReturn::success_u32(NUM_PINS as u32), + + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { + self.apps.enter(processid, |_, _| {}) + } +} diff --git a/capsules/src/read_only_state.rs b/capsules/extra/src/read_only_state.rs similarity index 85% rename from capsules/src/read_only_state.rs rename to capsules/extra/src/read_only_state.rs index f69916a043..4c2c78457e 100644 --- a/capsules/src/read_only_state.rs +++ b/capsules/extra/src/read_only_state.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Read Only State //! //! This capsule provides read only state to userspace applications. @@ -16,6 +20,7 @@ //! Versions are backwards compatible, that is new versions will only add //! fields, not remove existing ones or change the order. //! +//! ```text //! Version 1: //! |-------------------------| //! | Switch Count (u32) | @@ -25,7 +30,7 @@ //! | | //! | Time Ticks (u64) | //! |-------------------------| -//! +//! ``` use core::cell::Cell; use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; @@ -37,7 +42,7 @@ use kernel::syscall::{CommandReturn, SyscallDriver}; use kernel::ErrorCode; /// Syscall driver number. -pub const DRIVER_NUM: usize = crate::driver::NUM::ReadOnlyState as usize; +pub const DRIVER_NUM: usize = capsules_core::driver::NUM::ReadOnlyState as usize; const VERSION: u32 = 1; pub struct ReadOnlyStateDriver<'a, T: Time> { @@ -57,11 +62,11 @@ impl<'a, T: Time> ReadOnlyStateDriver<'a, T> { impl<'a, T: Time> ContextSwitchCallback for ReadOnlyStateDriver<'a, T> { fn context_switch_hook(&self, process: &dyn process::Process) { - let appid = process.processid(); + let processid = process.processid(); let pending_tasks = process.pending_tasks(); self.apps - .enter(appid, |app, _| { + .enter(processid, |app, _| { let count = app.count.get(); let _ = app.mem_region.mut_enter(|buf| { @@ -92,16 +97,16 @@ impl<'a, T: Time> SyscallDriver for ReadOnlyStateDriver<'a, T> { /// This should only be read by the app and written by the capsule. fn allow_userspace_readable( &self, - appid: ProcessId, + processid: ProcessId, which: usize, mut slice: UserspaceReadableProcessBuffer, ) -> Result { if which == 0 { - let res = self.apps.enter(appid, |data, _| { + let res = self.apps.enter(processid, |data, _| { core::mem::swap(&mut data.mem_region, &mut slice); }); match res { - Ok(_) => Ok(slice), + Ok(()) => Ok(slice), Err(e) => Err((slice, e.into())), } } else { @@ -113,17 +118,21 @@ impl<'a, T: Time> SyscallDriver for ReadOnlyStateDriver<'a, T> { /// /// ### `command_num` /// - /// - `0`: get version + /// - `0`: Driver existence check. + /// - `1`: Get version. fn command( &self, command_number: usize, _target_id: usize, _: usize, - _appid: ProcessId, + _processid: ProcessId, ) -> CommandReturn { match command_number { - // get version - 0 => CommandReturn::success_u32(VERSION), + // Check existence + 0 => CommandReturn::success(), + + // Get version + 1 => CommandReturn::success_u32(VERSION), // default _ => CommandReturn::failure(ErrorCode::NOSUPPORT), diff --git a/capsules/src/rf233.rs b/capsules/extra/src/rf233.rs similarity index 95% rename from capsules/src/rf233.rs rename to capsules/extra/src/rf233.rs index 09c2aa5f4a..471cb3d702 100644 --- a/capsules/src/rf233.rs +++ b/capsules/extra/src/rf233.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for sending 802.15.4 packets with an Atmel RF233. //! //! This implementation is completely non-blocking. This means that the state @@ -160,7 +164,7 @@ enum InternalState { // packet-length buffers. Since the SPI callback does not distinguish // which buffers are being used, the read_write_done callback checks // which state the stack is in and places the buffers back -// accodingly. A bug here would mean a memory leak and later panic +// accordingly. A bug here would mean a memory leak and later panic // when a buffer that should be present has been lost. // // The finite state machine is tricky for two reasons. First, the @@ -185,7 +189,9 @@ enum InternalState { // and waits for the interrupt specifying the entire packet has been // received. -pub struct RF233<'a, S: spi::SpiMasterDevice> { +pub const SPI_REGISTER_TRANSACTION_LENGTH: usize = 2; + +pub struct RF233<'a, S: spi::SpiMasterDevice<'a>> { spi: &'a S, radio_on: Cell, transmitting: Cell, @@ -205,15 +211,15 @@ pub struct RF233<'a, S: spi::SpiMasterDevice> { tx_buf: TakeCell<'static, [u8]>, rx_buf: TakeCell<'static, [u8]>, tx_len: Cell, - tx_client: OptionalCell<&'static dyn radio::TxClient>, - rx_client: OptionalCell<&'static dyn radio::RxClient>, - cfg_client: OptionalCell<&'static dyn radio::ConfigClient>, - power_client: OptionalCell<&'static dyn radio::PowerClient>, + tx_client: OptionalCell<&'a dyn radio::TxClient>, + rx_client: OptionalCell<&'a dyn radio::RxClient>, + cfg_client: OptionalCell<&'a dyn radio::ConfigClient>, + power_client: OptionalCell<&'a dyn radio::PowerClient>, addr: Cell, addr_long: Cell<[u8; 8]>, pan: Cell, tx_power: Cell, - channel: Cell, + channel: Cell, spi_rx: TakeCell<'static, [u8]>, spi_tx: TakeCell<'static, [u8]>, spi_buf: TakeCell<'static, [u8]>, @@ -276,7 +282,7 @@ fn interrupt_included(mask: u8, interrupt: InteruptFlags) -> bool { (mask & int) == int } -impl<'a, S: spi::SpiMasterDevice> spi::SpiMasterClient for RF233<'a, S> { +impl<'a, S: spi::SpiMasterDevice<'a>> spi::SpiMasterClient for RF233<'a, S> { // This function is a bit confusing because the order of the logic in the // function is different than the order of operations during transmission // and reception. @@ -484,7 +490,7 @@ impl<'a, S: spi::SpiMasterDevice> spi::SpiMasterClient for RF233<'a, S> { ); } InternalState::START_CTRL1_SET => { - let val = self.channel.get() | PHY_CC_CCA_MODE_CS_OR_ED; + let val = self.channel.get().get_channel_number() | PHY_CC_CCA_MODE_CS_OR_ED; self.state_transition_write( RF233Register::PHY_CC_CCA, val, @@ -910,7 +916,9 @@ impl<'a, S: spi::SpiMasterDevice> spi::SpiMasterClient for RF233<'a, S> { self.rx_client.map(|client| { let rbuf = self.rx_buf.take().unwrap(); let frame_len = rbuf[1] as usize - radio::MFR_SIZE; - client.receive(rbuf, frame_len, self.crc_valid.get(), Ok(())); + + // lqi is currently unimplemented for rf233 and is subsequently hardcoded to zero + client.receive(rbuf, frame_len, 0, self.crc_valid.get(), Ok(())); }); } @@ -1000,7 +1008,7 @@ impl<'a, S: spi::SpiMasterDevice> spi::SpiMasterClient for RF233<'a, S> { ); } InternalState::CONFIG_POWER_SET => { - let val = self.channel.get() | PHY_CC_CCA_MODE_CS_OR_ED; + let val = self.channel.get().get_channel_number() | PHY_CC_CCA_MODE_CS_OR_ED; self.state_transition_write( RF233Register::PHY_CC_CCA, val, @@ -1018,22 +1026,25 @@ impl<'a, S: spi::SpiMasterDevice> spi::SpiMasterClient for RF233<'a, S> { } } -impl gpio::Client for RF233<'_, S> { +impl<'a, S: spi::SpiMasterDevice<'a>> gpio::Client for RF233<'a, S> { fn fired(&self) { self.handle_interrupt(); } } -impl<'a, S: spi::SpiMasterDevice> RF233<'a, S> { +impl<'a, S: spi::SpiMasterDevice<'a>> RF233<'a, S> { pub fn new( spi: &'a S, + spi_buf: &'static mut [u8], + reg_write: &'static mut [u8; SPI_REGISTER_TRANSACTION_LENGTH], + reg_read: &'static mut [u8; SPI_REGISTER_TRANSACTION_LENGTH], reset: &'a dyn gpio::Pin, sleep: &'a dyn gpio::Pin, irq: &'a dyn gpio::InterruptPin<'a>, - channel: u8, + channel: radio::RadioChannel, ) -> RF233<'a, S> { RF233 { - spi: spi, + spi, reset_pin: reset, sleep_pin: sleep, irq_pin: irq, @@ -1061,9 +1072,9 @@ impl<'a, S: spi::SpiMasterDevice> RF233<'a, S> { pan: Cell::new(0), tx_power: Cell::new(setting_to_power(PHY_TX_PWR)), channel: Cell::new(channel), - spi_rx: TakeCell::empty(), - spi_tx: TakeCell::empty(), - spi_buf: TakeCell::empty(), + spi_rx: TakeCell::new(reg_read), + spi_tx: TakeCell::new(reg_write), + spi_buf: TakeCell::new(spi_buf), } } @@ -1074,7 +1085,7 @@ impl<'a, S: spi::SpiMasterDevice> RF233<'a, S> { // packet from being overwritten before reading it from the radio, // the driver needs to disable reception. This has to be done in the first // SPI operation. - if self.spi_busy.get() == false { + if !self.spi_busy.get() { if self.state.get() == InternalState::RX { // We've received a complete frame; need to disable // reception until we've read it out from RAM, @@ -1167,19 +1178,8 @@ impl<'a, S: spi::SpiMasterDevice> RF233<'a, S> { } } -impl radio::RadioConfig for RF233<'_, S> { - fn initialize( - &self, - buf: &'static mut [u8], - reg_write: &'static mut [u8], - reg_read: &'static mut [u8], - ) -> Result<(), ErrorCode> { - if (buf.len() < radio::MAX_BUF_SIZE || reg_read.len() != 2 || reg_write.len() != 2) { - return Err(ErrorCode::SIZE); - } - self.spi_buf.replace(buf); - self.spi_rx.replace(reg_read); - self.spi_tx.replace(reg_write); +impl<'a, S: spi::SpiMasterDevice<'a>> radio::RadioConfig<'a> for RF233<'a, S> { + fn initialize(&self) -> Result<(), ErrorCode> { Ok(()) } @@ -1250,11 +1250,11 @@ impl radio::RadioConfig for RF233<'_, S> { self.state.get() != InternalState::READY && self.state.get() != InternalState::SLEEP } - fn set_config_client(&self, client: &'static dyn radio::ConfigClient) { + fn set_config_client(&self, client: &'a dyn radio::ConfigClient) { self.cfg_client.set(client); } - fn set_power_client(&self, client: &'static dyn radio::PowerClient) { + fn set_power_client(&self, client: &'a dyn radio::PowerClient) { self.power_client.set(client); } @@ -1279,13 +1279,8 @@ impl radio::RadioConfig for RF233<'_, S> { } } - fn set_channel(&self, chan: u8) -> Result<(), ErrorCode> { - if chan >= 11 && chan <= 26 { - self.channel.set(chan); - Ok(()) - } else { - Err(ErrorCode::INVAL) - } + fn set_channel(&self, chan: radio::RadioChannel) { + self.channel.set(chan); } fn get_address(&self) -> u16 { @@ -1306,7 +1301,7 @@ impl radio::RadioConfig for RF233<'_, S> { } /// The 802.15.4 channel fn get_channel(&self) -> u8 { - self.channel.get() + self.channel.get().get_channel_number() } fn config_commit(&self) { @@ -1332,14 +1327,13 @@ impl radio::RadioConfig for RF233<'_, S> { } } -impl radio::RadioData for RF233<'_, S> { - fn set_transmit_client(&self, client: &'static dyn radio::TxClient) { +impl<'a, S: spi::SpiMasterDevice<'a>> radio::RadioData<'a> for RF233<'a, S> { + fn set_transmit_client(&self, client: &'a dyn radio::TxClient) { self.tx_client.set(client); } - fn set_receive_client(&self, client: &'static dyn radio::RxClient, buffer: &'static mut [u8]) { + fn set_receive_client(&self, client: &'a dyn radio::RxClient) { self.rx_client.set(client); - self.rx_buf.replace(buffer); } fn set_receive_buffer(&self, buffer: &'static mut [u8]) { diff --git a/capsules/src/rf233_const.rs b/capsules/extra/src/rf233_const.rs similarity index 96% rename from capsules/src/rf233_const.rs rename to capsules/extra/src/rf233_const.rs index 9ede7c3400..fa2a2c87c4 100644 --- a/capsules/src/rf233_const.rs +++ b/capsules/extra/src/rf233_const.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Support for the RF233 capsule #![allow(non_camel_case_types)] diff --git a/capsules/extra/src/screen.rs b/capsules/extra/src/screen.rs new file mode 100644 index 0000000000..c71f884ab6 --- /dev/null +++ b/capsules/extra/src/screen.rs @@ -0,0 +1,577 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Provides userspace with access to the screen. +//! +//! Usage +//! ----- +//! +//! You need a screen that provides the `hil::screen::Screen` trait. +//! +//! ```rust,ignore +//! let screen = +//! components::screen::ScreenComponent::new(board_kernel, tft).finalize(); +//! ``` + +use core::cell::Cell; + +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::hil; +use kernel::hil::screen::{ScreenPixelFormat, ScreenRotation}; +use kernel::processbuffer::ReadableProcessBuffer; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::utilities::leasable_buffer::SubSliceMut; +use kernel::{ErrorCode, ProcessId}; + +/// Syscall driver number. +use capsules_core::driver; +pub const DRIVER_NUM: usize = driver::NUM::Screen as usize; + +/// Ids for read-only allow buffers +mod ro_allow { + pub const SHARED: usize = 0; + /// The number of allow buffers the kernel stores for this grant + pub const COUNT: u8 = 1; +} + +fn screen_rotation_from(screen_rotation: usize) -> Option { + match screen_rotation { + 0 => Some(ScreenRotation::Normal), + 1 => Some(ScreenRotation::Rotated90), + 2 => Some(ScreenRotation::Rotated180), + 3 => Some(ScreenRotation::Rotated270), + _ => None, + } +} + +fn screen_pixel_format_from(screen_pixel_format: usize) -> Option { + match screen_pixel_format { + 0 => Some(ScreenPixelFormat::Mono), + 1 => Some(ScreenPixelFormat::RGB_233), + 2 => Some(ScreenPixelFormat::RGB_565), + 3 => Some(ScreenPixelFormat::RGB_888), + 4 => Some(ScreenPixelFormat::ARGB_8888), + _ => None, + } +} + +#[derive(Clone, Copy, PartialEq)] +enum ScreenCommand { + Nop, + SetBrightness(u16), + SetPower(bool), + SetInvert(bool), + SetRotation(ScreenRotation), + SetResolution { + width: usize, + height: usize, + }, + SetPixelFormat(ScreenPixelFormat), + SetWriteFrame { + x: usize, + y: usize, + width: usize, + height: usize, + }, + Write(usize), + Fill, +} + +fn pixels_in_bytes(pixels: usize, bits_per_pixel: usize) -> usize { + let bytes = pixels * bits_per_pixel / 8; + if pixels * bits_per_pixel % 8 != 0 { + bytes + 1 + } else { + bytes + } +} + +pub struct App { + pending_command: bool, + write_position: usize, + write_len: usize, + command: ScreenCommand, + width: usize, + height: usize, +} + +impl Default for App { + fn default() -> App { + App { + pending_command: false, + command: ScreenCommand::Nop, + width: 0, + height: 0, + write_len: 0, + write_position: 0, + } + } +} + +pub struct Screen<'a> { + screen: &'a dyn hil::screen::Screen<'a>, + screen_setup: Option<&'a dyn hil::screen::ScreenSetup<'a>>, + apps: Grant, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>, + current_process: OptionalCell, + pixel_format: Cell, + buffer: TakeCell<'static, [u8]>, +} + +impl<'a> Screen<'a> { + pub fn new( + screen: &'a dyn hil::screen::Screen<'a>, + screen_setup: Option<&'a dyn hil::screen::ScreenSetup<'a>>, + buffer: &'static mut [u8], + grant: Grant, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>, + ) -> Screen<'a> { + Screen { + screen, + screen_setup, + apps: grant, + current_process: OptionalCell::empty(), + pixel_format: Cell::new(screen.get_pixel_format()), + buffer: TakeCell::new(buffer), + } + } + + // Check to see if we are doing something. If not, + // go ahead and do this command. If so, this is queued + // and will be run when the pending command completes. + fn enqueue_command(&self, command: ScreenCommand, process_id: ProcessId) -> CommandReturn { + match self + .apps + .enter(process_id, |app, _| { + if app.pending_command { + CommandReturn::failure(ErrorCode::BUSY) + } else { + app.pending_command = true; + app.command = command; + app.write_position = 0; + CommandReturn::success() + } + }) + .map_err(ErrorCode::from) + { + Err(e) => CommandReturn::failure(e), + Ok(r) => { + if self.current_process.is_none() { + self.current_process.set(process_id); + let r = self.call_screen(command, process_id); + if r != Ok(()) { + self.current_process.clear(); + } + CommandReturn::from(r) + } else { + r + } + } + } + } + + fn is_len_multiple_color_depth(&self, len: usize) -> bool { + let depth = pixels_in_bytes(1, self.screen.get_pixel_format().get_bits_per_pixel()); + (len % depth) == 0 + } + + fn call_screen(&self, command: ScreenCommand, process_id: ProcessId) -> Result<(), ErrorCode> { + match command { + ScreenCommand::SetBrightness(brighness) => self.screen.set_brightness(brighness), + ScreenCommand::SetPower(enabled) => self.screen.set_power(enabled), + ScreenCommand::SetInvert(enabled) => self.screen.set_invert(enabled), + ScreenCommand::SetRotation(rotation) => { + if let Some(screen) = self.screen_setup { + screen.set_rotation(rotation) + } else { + Err(ErrorCode::NOSUPPORT) + } + } + ScreenCommand::SetResolution { width, height } => { + if let Some(screen) = self.screen_setup { + screen.set_resolution((width, height)) + } else { + Err(ErrorCode::NOSUPPORT) + } + } + ScreenCommand::SetPixelFormat(pixel_format) => { + if let Some(screen) = self.screen_setup { + screen.set_pixel_format(pixel_format) + } else { + Err(ErrorCode::NOSUPPORT) + } + } + ScreenCommand::Fill => { + match self + .apps + .enter(process_id, |app, kernel_data| { + let len = kernel_data + .get_readonly_processbuffer(ro_allow::SHARED) + .map_or(0, |shared| shared.len()); + // Ensure we have a buffer that is the correct size + if len == 0 { + Err(ErrorCode::NOMEM) + } else if !self.is_len_multiple_color_depth(len) { + Err(ErrorCode::INVAL) + } else { + app.write_position = 0; + app.write_len = pixels_in_bytes( + app.width * app.height, + self.pixel_format.get().get_bits_per_pixel(), + ); + Ok(()) + } + }) + .unwrap_or_else(|err| err.into()) + { + Err(e) => Err(e), + Ok(()) => self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| { + let len = self.fill_next_buffer_for_write(buffer); + if len > 0 { + let mut data = SubSliceMut::new(buffer); + data.slice(..len); + self.screen.write(data, false) + } else { + self.buffer.replace(buffer); + self.run_next_command(kernel::errorcode::into_statuscode(Ok(())), 0, 0); + Ok(()) + } + }), + } + } + + ScreenCommand::Write(data_len) => { + match self + .apps + .enter(process_id, |app, kernel_data| { + let len = kernel_data + .get_readonly_processbuffer(ro_allow::SHARED) + .map_or(0, |shared| shared.len()) + .min(data_len); + // Ensure we have a buffer that is the correct size + if len == 0 { + Err(ErrorCode::NOMEM) + } else if !self.is_len_multiple_color_depth(len) { + Err(ErrorCode::INVAL) + } else { + app.write_position = 0; + app.write_len = len; + Ok(()) + } + }) + .unwrap_or_else(|err| err.into()) + { + Ok(()) => self.buffer.take().map_or(Err(ErrorCode::FAIL), |buffer| { + let len = self.fill_next_buffer_for_write(buffer); + if len > 0 { + let mut data = SubSliceMut::new(buffer); + data.slice(..len); + self.screen.write(data, false) + } else { + self.buffer.replace(buffer); + self.run_next_command(kernel::errorcode::into_statuscode(Ok(())), 0, 0); + Ok(()) + } + }), + Err(e) => Err(e), + } + } + ScreenCommand::SetWriteFrame { + x, + y, + width, + height, + } => self + .apps + .enter(process_id, |app, _| { + app.write_position = 0; + app.width = width; + app.height = height; + + self.screen.set_write_frame(x, y, width, height) + }) + .unwrap_or_else(|err| err.into()), + _ => Err(ErrorCode::NOSUPPORT), + } + } + + fn schedule_callback(&self, data1: usize, data2: usize, data3: usize) { + self.current_process.take().map(|process_id| { + let _ = self.apps.enter(process_id, |app, upcalls| { + app.pending_command = false; + upcalls.schedule_upcall(0, (data1, data2, data3)).ok(); + }); + }); + } + + fn run_next_command(&self, data1: usize, data2: usize, data3: usize) { + self.schedule_callback(data1, data2, data3); + + let mut command = ScreenCommand::Nop; + + // Check if there are any pending events. + for app in self.apps.iter() { + let process_id = app.processid(); + let start_command = app.enter(|app, _| { + if app.pending_command { + app.pending_command = false; + command = app.command; + self.current_process.set(process_id); + true + } else { + false + } + }); + if start_command { + match self.call_screen(command, process_id) { + Err(err) => { + self.current_process.clear(); + self.schedule_callback(kernel::errorcode::into_statuscode(Err(err)), 0, 0); + } + Ok(()) => { + break; + } + } + } + } + } + + fn fill_next_buffer_for_write(&self, buffer: &mut [u8]) -> usize { + self.current_process.map_or(0, |process_id| { + self.apps + .enter(process_id, |app, kernel_data| { + let position = app.write_position; + let mut len = app.write_len; + if position < len { + let buffer_size = buffer.len(); + let chunk_number = position / buffer_size; + let initial_pos = chunk_number * buffer_size; + let mut pos = initial_pos; + match app.command { + ScreenCommand::Write(_) => { + let res = kernel_data + .get_readonly_processbuffer(ro_allow::SHARED) + .and_then(|shared| { + shared.enter(|s| { + let mut chunks = s.chunks(buffer_size); + if let Some(chunk) = chunks.nth(chunk_number) { + for (i, byte) in chunk.iter().enumerate() { + if pos < len { + buffer[i] = byte.get(); + pos += 1 + } else { + break; + } + } + app.write_len - initial_pos + } else { + // stop writing + 0 + } + }) + }) + .unwrap_or(0); + if res > 0 { + app.write_position = pos; + } + res + } + ScreenCommand::Fill => { + // TODO bytes per pixel + len -= position; + let bytes_per_pixel = pixels_in_bytes( + 1, + self.pixel_format.get().get_bits_per_pixel(), + ); + let mut write_len = buffer_size / bytes_per_pixel; + if write_len > len { + write_len = len + }; + app.write_position += write_len * bytes_per_pixel; + kernel_data + .get_readonly_processbuffer(ro_allow::SHARED) + .and_then(|shared| { + shared.enter(|data| { + let mut bytes = data.iter(); + // bytes per pixel + for i in 0..bytes_per_pixel { + if let Some(byte) = bytes.next() { + buffer[i] = byte.get(); + } + } + for i in 1..write_len { + // bytes per pixel + for j in 0..bytes_per_pixel { + buffer[bytes_per_pixel * i + j] = buffer[j] + } + } + write_len * bytes_per_pixel + }) + }) + .unwrap_or(0) + } + _ => 0, + } + } else { + 0 + } + }) + .unwrap_or(0) + }) + } +} + +impl<'a> hil::screen::ScreenClient for Screen<'a> { + fn command_complete(&self, r: Result<(), ErrorCode>) { + self.run_next_command(kernel::errorcode::into_statuscode(r), 0, 0); + } + + fn write_complete(&self, data: SubSliceMut<'static, u8>, r: Result<(), ErrorCode>) { + let buffer = data.take(); + let len = self.fill_next_buffer_for_write(buffer); + + if r == Ok(()) && len > 0 { + let mut data = SubSliceMut::new(buffer); + data.slice(..len); + let _ = self.screen.write(data, true); + } else { + self.buffer.replace(buffer); + self.run_next_command(kernel::errorcode::into_statuscode(r), 0, 0); + } + } + + fn screen_is_ready(&self) { + self.run_next_command(kernel::errorcode::into_statuscode(Ok(())), 0, 0); + } +} + +impl<'a> hil::screen::ScreenSetupClient for Screen<'a> { + fn command_complete(&self, r: Result<(), ErrorCode>) { + self.run_next_command(kernel::errorcode::into_statuscode(r), 0, 0); + } +} + +impl<'a> SyscallDriver for Screen<'a> { + fn command( + &self, + command_num: usize, + data1: usize, + data2: usize, + process_id: ProcessId, + ) -> CommandReturn { + match command_num { + // Driver existence check + 0 => CommandReturn::success(), + // Does it have the screen setup + 1 => CommandReturn::success_u32(self.screen_setup.is_some() as u32), + // Set power + 2 => self.enqueue_command(ScreenCommand::SetPower(data1 != 0), process_id), + // Set Brightness + 3 => self.enqueue_command(ScreenCommand::SetBrightness(data1 as u16), process_id), + // Invert on (deprecated) + 4 => self.enqueue_command(ScreenCommand::SetInvert(true), process_id), + // Invert off (deprecated) + 5 => self.enqueue_command(ScreenCommand::SetInvert(false), process_id), + // Set Invert + 6 => self.enqueue_command(ScreenCommand::SetInvert(data1 != 0), process_id), + + // Get Resolution Modes count + 11 => { + if let Some(screen) = self.screen_setup { + CommandReturn::success_u32(screen.get_num_supported_resolutions() as u32) + } else { + CommandReturn::failure(ErrorCode::NOSUPPORT) + } + } + // Get Resolution Mode Width and Height + 12 => { + if let Some(screen) = self.screen_setup { + match screen.get_supported_resolution(data1) { + Some((width, height)) if width > 0 && height > 0 => { + CommandReturn::success_u32_u32(width as u32, height as u32) + } + _ => CommandReturn::failure(ErrorCode::INVAL), + } + } else { + CommandReturn::failure(ErrorCode::NOSUPPORT) + } + } + + // Get pixel format Modes count + 13 => { + if let Some(screen) = self.screen_setup { + CommandReturn::success_u32(screen.get_num_supported_pixel_formats() as u32) + } else { + CommandReturn::failure(ErrorCode::NOSUPPORT) + } + } + // Get supported pixel format + 14 => { + if let Some(screen) = self.screen_setup { + match screen.get_supported_pixel_format(data1) { + Some(pixel_format) => CommandReturn::success_u32(pixel_format as u32), + _ => CommandReturn::failure(ErrorCode::INVAL), + } + } else { + CommandReturn::failure(ErrorCode::NOSUPPORT) + } + } + + // Get Rotation + 21 => CommandReturn::success_u32(self.screen.get_rotation() as u32), + // Set Rotation + 22 => self.enqueue_command( + ScreenCommand::SetRotation( + screen_rotation_from(data1).unwrap_or(ScreenRotation::Normal), + ), + process_id, + ), + + // Get Resolution + 23 => { + let (width, height) = self.screen.get_resolution(); + CommandReturn::success_u32_u32(width as u32, height as u32) + } + // Set Resolution + 24 => self.enqueue_command( + ScreenCommand::SetResolution { + width: data1, + height: data2, + }, + process_id, + ), + + // Get pixel format + 25 => CommandReturn::success_u32(self.screen.get_pixel_format() as u32), + // Set pixel format + 26 => { + if let Some(pixel_format) = screen_pixel_format_from(data1) { + self.enqueue_command(ScreenCommand::SetPixelFormat(pixel_format), process_id) + } else { + CommandReturn::failure(ErrorCode::INVAL) + } + } + + // Set Write Frame + 100 => self.enqueue_command( + ScreenCommand::SetWriteFrame { + x: (data1 >> 16) & 0xFFFF, + y: data1 & 0xFFFF, + width: (data2 >> 16) & 0xFFFF, + height: data2 & 0xFFFF, + }, + process_id, + ), + // Write + 200 => self.enqueue_command(ScreenCommand::Write(data1), process_id), + // Fill + 300 => self.enqueue_command(ScreenCommand::Fill, process_id), + + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { + self.apps.enter(processid, |_, _| {}) + } +} diff --git a/capsules/extra/src/screen_shared.rs b/capsules/extra/src/screen_shared.rs new file mode 100644 index 0000000000..4ec16a3ab4 --- /dev/null +++ b/capsules/extra/src/screen_shared.rs @@ -0,0 +1,456 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Shares a screen among multiple userspace processes. +//! +//! The screen can be split into multiple regions, and regions are assigned to +//! processes by AppID. +//! +//! Boards should create an array of `AppScreenRegion` objects that assign apps +//! to specific regions (frames) within the screen. +//! +//! ```rust,ignore +//! AppScreenRegion { +//! app_id: kernel::process:ShortId::new(id), +//! frame: Frame { +//! x: 0, +//! y: 0, +//! width: 8, +//! height: 16, +//! } +//! } +//! ``` +//! +//! This driver uses a subset of the API from `Screen`. It does not support any +//! screen config settings (brightness, invert) as those operations affect the +//! entire screen. + +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::hil; +use kernel::processbuffer::ReadableProcessBuffer; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::utilities::leasable_buffer::SubSliceMut; +use kernel::{ErrorCode, ProcessId}; + +/// Syscall driver number. +use capsules_core::driver; +pub const DRIVER_NUM: usize = driver::NUM::Screen as usize; + +/// Ids for read-only allow buffers +mod ro_allow { + pub const SHARED: usize = 0; + /// The number of allow buffers the kernel stores for this grant + pub const COUNT: u8 = 1; +} + +#[derive(Clone, Copy, PartialEq)] +enum ScreenCommand { + WriteSetFrame, + WriteBuffer, +} + +fn pixels_in_bytes(pixels: usize, bits_per_pixel: usize) -> usize { + let bytes = pixels * bits_per_pixel / 8; + if pixels * bits_per_pixel % 8 != 0 { + bytes + 1 + } else { + bytes + } +} + +/// Rectangular region of a screen. +#[derive(Default, Clone, Copy, PartialEq)] +pub struct Frame { + /// X coordinate of the upper left corner of the frame. + x: usize, + /// Y coordinate of the upper left corner of the frame. + y: usize, + /// Width of the frame. + width: usize, + /// Height of the frame. + height: usize, +} + +pub struct AppScreenRegion { + app_id: kernel::process::ShortId, + frame: Frame, +} + +impl AppScreenRegion { + pub fn new( + app_id: kernel::process::ShortId, + x: usize, + y: usize, + width: usize, + height: usize, + ) -> Self { + Self { + app_id, + frame: Frame { + x, + y, + width, + height, + }, + } + } +} + +#[derive(Default)] +pub struct App { + /// The app has requested some screen operation, or `None()` if idle. + command: Option, + /// The current frame the app is using. + frame: Frame, +} + +/// A userspace driver that allows multiple apps to use the same screen. +/// +/// Each app is given a pre-set rectangular region of the screen to use. +pub struct ScreenShared<'a, S: hil::screen::Screen<'a>> { + /// Underlying screen driver to use. + screen: &'a S, + + /// Grant region for apps using the screen. + apps: Grant, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>, + + /// Static allocations of screen regions for each app. + apps_regions: &'a [AppScreenRegion], + + /// The process currently executing a command on the screen. + current_process: OptionalCell, + + /// Internal buffer for write commands. + buffer: TakeCell<'static, [u8]>, +} + +impl<'a, S: hil::screen::Screen<'a>> ScreenShared<'a, S> { + pub fn new( + screen: &'a S, + grant: Grant, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>, + buffer: &'static mut [u8], + apps_regions: &'a [AppScreenRegion], + ) -> ScreenShared<'a, S> { + ScreenShared { + screen, + apps: grant, + current_process: OptionalCell::empty(), + buffer: TakeCell::new(buffer), + apps_regions, + } + } + + // Enqueue a command for the given app. + fn enqueue_command(&self, command: ScreenCommand, process_id: ProcessId) -> CommandReturn { + let ret = self + .apps + .enter(process_id, |app, _| { + if app.command.is_some() { + Err(ErrorCode::BUSY) + } else { + app.command = Some(command); + Ok(()) + } + }) + .map_err(ErrorCode::from) + .and_then(|r| r) + .into(); + + if self.current_process.is_none() { + self.run_next_command(); + } + + ret + } + + /// Calculate the frame within the entire screen that the app is currently + /// trying to use. This is the `app_frame` within the app's allocated + /// `app_screen_region`. + fn calculate_absolute_frame(&self, app_screen_region_frame: Frame, app_frame: Frame) -> Frame { + // x and y are sums + let mut absolute_x = app_screen_region_frame.x + app_frame.x; + let mut absolute_y = app_screen_region_frame.y + app_frame.y; + // width and height are simply the app_frame width and height. + let mut absolute_w = app_frame.width; + let mut absolute_h = app_frame.height; + + // Make sure that the calculate frame is within the allocated region. + absolute_x = core::cmp::min( + app_screen_region_frame.x + app_screen_region_frame.width, + absolute_x, + ); + absolute_y = core::cmp::min( + app_screen_region_frame.y + app_screen_region_frame.height, + absolute_y, + ); + absolute_w = core::cmp::min( + app_screen_region_frame.x + app_screen_region_frame.width - absolute_x, + absolute_w, + ); + absolute_h = core::cmp::min( + app_screen_region_frame.y + app_screen_region_frame.height - absolute_y, + absolute_h, + ); + + Frame { + x: absolute_x, + y: absolute_y, + width: absolute_w, + height: absolute_h, + } + } + + fn call_screen( + &self, + process_id: ProcessId, + app_screen_region_frame: Frame, + ) -> Result<(), ErrorCode> { + self.apps + .enter(process_id, |app, kernel_data| { + match app.command { + Some(ScreenCommand::WriteSetFrame) => { + let absolute_frame = + self.calculate_absolute_frame(app_screen_region_frame, app.frame); + + app.command = Some(ScreenCommand::WriteBuffer); + self.screen + .set_write_frame( + absolute_frame.x, + absolute_frame.y, + absolute_frame.width, + absolute_frame.height, + ) + .inspect_err(|_| { + app.command = None; + }) + } + Some(ScreenCommand::WriteBuffer) => { + app.command = None; + kernel_data + .get_readonly_processbuffer(ro_allow::SHARED) + .map(|allow_buf| { + let len = allow_buf.len(); + + if len == 0 { + Err(ErrorCode::NOMEM) + } else if !self.is_len_multiple_color_depth(len) { + Err(ErrorCode::INVAL) + } else { + // All good, copy buffer. + + self.buffer.take().map_or(Err(ErrorCode::FAIL), |buffer| { + let copy_len = + core::cmp::min(buffer.len(), allow_buf.len()); + allow_buf.enter(|ab| { + // buffer[..copy_len].copy_from_slice(ab[..copy_len]); + ab[..copy_len].copy_to_slice(&mut buffer[..copy_len]) + })?; + + // Send to screen. + let mut data = SubSliceMut::new(buffer); + data.slice(..copy_len); + self.screen.write(data, false) + }) + } + }) + .map_err(ErrorCode::from) + .and_then(|r| r) + } + _ => Err(ErrorCode::NOSUPPORT), + } + }) + .map_err(ErrorCode::from) + .and_then(|r| r) + } + + fn schedule_callback(&self, process_id: ProcessId, data1: usize, data2: usize, data3: usize) { + let _ = self.apps.enter(process_id, |_app, kernel_data| { + kernel_data.schedule_upcall(0, (data1, data2, data3)).ok(); + }); + } + + fn get_app_screen_region_frame(&self, process_id: ProcessId) -> Option { + let short_id = process_id.short_app_id(); + + for app_screen_region in self.apps_regions { + if short_id == app_screen_region.app_id { + return Some(app_screen_region.frame); + } + } + None + } + + fn run_next_command(&self) { + let ran_cmd = self.current_process.map_or(false, |process_id| { + let app_region_frame = self.get_app_screen_region_frame(process_id); + + app_region_frame.map_or(false, |frame| { + let r = self.call_screen(process_id, frame); + if r.is_err() { + // We were unable to run the screen operation meaning we + // will not get a callback and we need to report the error. + self.current_process.take().map(|process_id| { + self.schedule_callback( + process_id, + kernel::errorcode::into_statuscode(r), + 0, + 0, + ); + }); + false + } else { + true + } + }) + }); + + if !ran_cmd { + // Check if there are any pending events. + for app in self.apps.iter() { + let process_id = app.processid(); + + // Check if this process has both a pending command and is + // allocated a region on the screen. + let frame_maybe = app.enter(|app, _| { + if app.command.is_some() { + self.get_app_screen_region_frame(process_id) + } else { + None + } + }); + + // If we have a candidate, try to execute the screen operation. + if frame_maybe.is_some() { + match frame_maybe { + Some(frame) => { + // Reserve the screen for this process and execute + // the operation. + self.current_process.set(process_id); + match self.call_screen(process_id, frame) { + Ok(()) => { + // Everything is good, stop looking for apps + // to execute. + break; + } + Err(err) => { + // Could not run the screen command. + // Un-reserve the screen and do an upcall + // with the bad news. + self.current_process.clear(); + self.schedule_callback( + process_id, + kernel::errorcode::into_statuscode(Err(err)), + 0, + 0, + ); + } + } + } + None => {} + } + } + } + } + } + + fn is_len_multiple_color_depth(&self, len: usize) -> bool { + let depth = pixels_in_bytes(1, self.screen.get_pixel_format().get_bits_per_pixel()); + (len % depth) == 0 + } +} + +impl<'a, S: hil::screen::Screen<'a>> hil::screen::ScreenClient for ScreenShared<'a, S> { + fn command_complete(&self, r: Result<(), ErrorCode>) { + if r.is_err() { + self.current_process.take().map(|process_id| { + self.schedule_callback(process_id, kernel::errorcode::into_statuscode(r), 0, 0); + }); + } + + self.run_next_command(); + } + + fn write_complete(&self, data: SubSliceMut<'static, u8>, r: Result<(), ErrorCode>) { + self.buffer.replace(data.take()); + + // Notify that the write is finished. + self.current_process.take().map(|process_id| { + self.schedule_callback(process_id, kernel::errorcode::into_statuscode(r), 0, 0); + }); + + self.run_next_command(); + } + + fn screen_is_ready(&self) { + self.run_next_command(); + } +} + +impl<'a, S: hil::screen::Screen<'a>> SyscallDriver for ScreenShared<'a, S> { + fn command( + &self, + command_num: usize, + data1: usize, + data2: usize, + process_id: ProcessId, + ) -> CommandReturn { + match command_num { + // Driver existence check + 0 => CommandReturn::success(), + + // Get Rotation + 21 => CommandReturn::success_u32(self.screen.get_rotation() as u32), + + // Get Resolution + 23 => match self.get_app_screen_region_frame(process_id) { + Some(frame) => { + CommandReturn::success_u32_u32(frame.width as u32, frame.height as u32) + } + None => CommandReturn::failure(ErrorCode::NOSUPPORT), + }, + + // Get pixel format + 25 => CommandReturn::success_u32(self.screen.get_pixel_format() as u32), + + // Set Write Frame + 100 => { + let frame = Frame { + x: (data1 >> 16) & 0xFFFF, + y: data1 & 0xFFFF, + width: (data2 >> 16) & 0xFFFF, + height: data2 & 0xFFFF, + }; + + self.apps + .enter(process_id, |app, kernel_data| { + app.frame = frame; + + // Just issue upcall. + let _ = kernel_data + .schedule_upcall(0, (kernel::errorcode::into_statuscode(Ok(())), 0, 0)); + }) + .map_err(ErrorCode::from) + .into() + } + + // Write + 200 => { + // First check if this app has any screen real estate allocated. + // If not, return error. + if self.get_app_screen_region_frame(process_id).is_none() { + CommandReturn::failure(ErrorCode::NOSUPPORT) + } else { + self.enqueue_command(ScreenCommand::WriteSetFrame, process_id) + } + } + + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { + self.apps.enter(processid, |_, _| {}) + } +} diff --git a/capsules/src/sdcard.rs b/capsules/extra/src/sdcard.rs similarity index 97% rename from capsules/src/sdcard.rs rename to capsules/extra/src/sdcard.rs index 4ee0b71f8c..e808aea909 100644 --- a/capsules/src/sdcard.rs +++ b/capsules/extra/src/sdcard.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides driver for accessing an SD Card and a userspace Driver. //! //! This allows initialization and block reads or writes on top of SPI. @@ -5,7 +9,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! # use capsules::virtual_alarm::VirtualMuxAlarm; //! # use kernel::hil::spi::SpiMasterDevice; @@ -30,6 +34,11 @@ //! capsules::virtual_alarm::VirtualMuxAlarm::new(mux_alarm)); //! sdcard_virtual_alarm.setup(); //! +//! let sdcard_tx_buffer = static_init!([u8; capsules::sdcard::TXRX_BUFFER_LENGTH], +//! [0; capsules::sdcard::TXRX_BUFFER_LENGTH]); +//! let sdcard_rx_buffer = static_init!([u8; capsules::sdcard::TXRX_BUFFER_LENGTH], +//! [0; capsules::sdcard::TXRX_BUFFER_LENGTH]); +//! //! let sdcard = static_init!( //! capsules::sdcard::SDCard< //! 'static, @@ -37,19 +46,22 @@ //! capsules::sdcard::SDCard::new(sdcard_spi, //! sdcard_virtual_alarm, //! Some(&SD_DETECT_PIN), -//! &mut capsules::sdcard::TXBUFFER, -//! &mut capsules::sdcard::RXBUFFER)); +//! sdcard_tx_buffer, +//! sdcard_rx_buffer)); //! sdcard_spi.set_client(sdcard); //! sdcard_virtual_alarm.set_alarm_client(sdcard); //! SD_DETECT_PIN.set_client(sdcard); //! +//! let sdcard_kernel_buffer = static_init!([u8; capsules::sdcard::KERNEL_BUFFER_LENGTH], +//! [0; capsules::sdcard::KERNEL_BUFFER_LENGTH]); +//! //! let sdcard_driver = static_init!( //! capsules::sdcard::SDCardDriver< //! 'static, //! capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52833::rtc::Rtc>>, //! capsules::sdcard::SDCardDriver::new( //! sdcard, -//! &mut capsules::sdcard::KERNEL_BUFFER, +//! sdcard_kernel_buffer, //! board_kernel.create_grant( //! capsules::sdcard::DRIVER_NUM, //! &memory_allocation_capability))); @@ -74,21 +86,21 @@ use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::SdCard as usize; /// Ids for read-only allow buffers mod ro_allow { pub const WRITE: usize = 0; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; + pub const COUNT: u8 = 1; } /// Ids for read-write allow buffers mod rw_allow { pub const READ: usize = 0; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; + pub const COUNT: u8 = 1; } /// Buffers used for SD card transactions, assigned in board `main.rs` files @@ -96,12 +108,11 @@ mod rw_allow { /// /// * RXBUFFER must be greater than or equal to TXBUFFER in length /// * Both RXBUFFER and TXBUFFER must be longer than the SD card's block size -pub static mut TXBUFFER: [u8; 515] = [0; 515]; -pub static mut RXBUFFER: [u8; 515] = [0; 515]; +pub const TXRX_BUFFER_LENGTH: usize = 515; /// SD Card capsule, capable of being built on top of by other kernel capsules pub struct SDCard<'a, A: hil::time::Alarm<'a>> { - spi: &'a dyn hil::spi::SpiMasterDevice, + spi: &'a dyn hil::spi::SpiMasterDevice<'a>, state: Cell, after_state: Cell, @@ -117,7 +128,7 @@ pub struct SDCard<'a, A: hil::time::Alarm<'a>> { txbuffer: TakeCell<'static, [u8]>, rxbuffer: TakeCell<'static, [u8]>, - client: OptionalCell<&'static dyn SDCardClient>, + client: OptionalCell<&'a dyn SDCardClient>, client_buffer: TakeCell<'static, [u8]>, client_offset: Cell, } @@ -267,10 +278,10 @@ impl<'a, A: hil::time::Alarm<'a>> SDCard<'a, A> { // set up and return struct SDCard { - spi: spi, + spi, state: Cell::new(SpiState::Idle), after_state: Cell::new(SpiState::Idle), - alarm: alarm, + alarm, alarm_state: Cell::new(AlarmState::Idle), alarm_count: Cell::new(0), is_initialized: Cell::new(false), @@ -802,7 +813,7 @@ impl<'a, A: hil::time::Alarm<'a>> SDCard<'a, A> { self.read_bytes(write_buffer, read_buffer, 1); } else { // check for data block to be ready - self.state.set(SpiState::WaitReadBlocks { count: count }); + self.state.set(SpiState::WaitReadBlocks { count }); self.read_bytes(write_buffer, read_buffer, 1); } } else { @@ -879,7 +890,7 @@ impl<'a, A: hil::time::Alarm<'a>> SDCard<'a, A> { if read_buffer[0] == DATA_TOKEN { // data ready to read. Read block plus CRC self.alarm_count.set(0); - self.state.set(SpiState::ReceivedBlock { count: count }); + self.state.set(SpiState::ReceivedBlock { count }); self.read_bytes(write_buffer, read_buffer, 512 + 2); } else if read_buffer[0] == 0xFF { // line is idling high, data is not ready @@ -890,7 +901,7 @@ impl<'a, A: hil::time::Alarm<'a>> SDCard<'a, A> { // try again after 1 ms self.alarm_state - .set(AlarmState::WaitForDataBlocks { count: count }); + .set(AlarmState::WaitForDataBlocks { count }); let delay = self.alarm.ticks_from_ms(1); self.alarm.set_alarm(self.alarm.now(), delay); } else { @@ -1199,7 +1210,7 @@ impl<'a, A: hil::time::Alarm<'a>> SDCard<'a, A> { self.txbuffer.take().map(|write_buffer| { self.rxbuffer.take().map(move |read_buffer| { // wait until ready and then read data block, then done - self.state.set(SpiState::WaitReadBlocks { count: count }); + self.state.set(SpiState::WaitReadBlocks { count }); self.read_bytes(write_buffer, read_buffer, 1); }); }); @@ -1235,7 +1246,7 @@ impl<'a, A: hil::time::Alarm<'a>> SDCard<'a, A> { // if there is no detect pin, assume an sd card is installed self.detect_pin.get().map_or(true, |pin| { // sd card detection pin is active low - pin.read() == false + !pin.read() }) } @@ -1302,7 +1313,7 @@ impl<'a, A: hil::time::Alarm<'a>> SDCard<'a, A> { address *= 512; } - self.state.set(SpiState::StartReadBlocks { count: count }); + self.state.set(SpiState::StartReadBlocks { count }); if count == 1 { self.send_command( SDCmd::CMD17_ReadSingle, @@ -1361,7 +1372,7 @@ impl<'a, A: hil::time::Alarm<'a>> SDCard<'a, A> { address *= 512; } - self.state.set(SpiState::StartWriteBlocks { count: count }); + self.state.set(SpiState::StartWriteBlocks { count }); if count == 1 { self.send_command( SDCmd::CMD24_WriteSingle, @@ -1463,7 +1474,7 @@ pub struct SDCardDriver<'a, A: hil::time::Alarm<'a>> { pub struct App; /// Buffer for SD card driver, assigned in board `main.rs` files -pub static mut KERNEL_BUFFER: [u8; 512] = [0; 512]; +pub const KERNEL_BUFFER_LENGTH: usize = 512; /// Functions for SDCardDriver impl<'a, A: hil::time::Alarm<'a>> SDCardDriver<'a, A> { @@ -1496,7 +1507,7 @@ impl<'a, A: hil::time::Alarm<'a>> SDCardDriver<'a, A> { impl<'a, A: hil::time::Alarm<'a>> SDCardClient for SDCardDriver<'a, A> { fn card_detection_changed(&self, installed: bool) { self.current_process.map(|process_id| { - let _ = self.grants.enter(*process_id, |_app, kernel_data| { + let _ = self.grants.enter(process_id, |_app, kernel_data| { kernel_data .schedule_upcall(0, (0, installed as usize, 0)) .ok(); @@ -1506,7 +1517,7 @@ impl<'a, A: hil::time::Alarm<'a>> SDCardClient for SDCardDriver<'a, A> { fn init_done(&self, block_size: u32, total_size: u64) { self.current_process.map(|process_id| { - let _ = self.grants.enter(*process_id, |_app, kernel_data| { + let _ = self.grants.enter(process_id, |_app, kernel_data| { let size_in_kb = ((total_size >> 10) & 0xFFFFFFFF) as usize; kernel_data .schedule_upcall(0, (1, block_size as usize, size_in_kb)) @@ -1519,14 +1530,13 @@ impl<'a, A: hil::time::Alarm<'a>> SDCardClient for SDCardDriver<'a, A> { self.kernel_buf.replace(data); self.current_process.map(|process_id| { - let _ = self.grants.enter(*process_id, |_, kernel_data| { + let _ = self.grants.enter(process_id, |_, kernel_data| { let mut read_len = 0; self.kernel_buf.map(|data| { kernel_data .get_readwrite_processbuffer(rw_allow::READ) .and_then(|read| { read.mut_enter(|read_buffer| { - let read_buffer = read_buffer; // copy bytes to user buffer // Limit to minimum length between read_buffer, data, and // len field @@ -1554,7 +1564,7 @@ impl<'a, A: hil::time::Alarm<'a>> SDCardClient for SDCardDriver<'a, A> { self.kernel_buf.replace(buffer); self.current_process.map(|process_id| { - let _ = self.grants.enter(*process_id, |_app, kernel_data| { + let _ = self.grants.enter(process_id, |_app, kernel_data| { kernel_data.schedule_upcall(0, (3, 0, 0)).ok(); }); }); @@ -1562,7 +1572,7 @@ impl<'a, A: hil::time::Alarm<'a>> SDCardClient for SDCardDriver<'a, A> { fn error(&self, error: u32) { self.current_process.map(|process_id| { - let _ = self.grants.enter(*process_id, |_app, kernel_data| { + let _ = self.grants.enter(process_id, |_app, kernel_data| { kernel_data.schedule_upcall(0, (4, error as usize, 0)).ok(); }); }); @@ -1579,14 +1589,14 @@ impl<'a, A: hil::time::Alarm<'a>> SyscallDriver for SDCardDriver<'a, A> { process_id: ProcessId, ) -> CommandReturn { if command_num == 0 { - // Handle this first as it should be returned unconditionally. + // Handle unconditional driver existence check. return CommandReturn::success(); } // Check if this driver is free, or already dedicated to this process. let match_or_empty_or_nonexistant = self.current_process.map_or(true, |current_process| { self.grants - .enter(*current_process, |_, _| current_process == &process_id) + .enter(current_process, |_, _| current_process == process_id) .unwrap_or(true) }); if match_or_empty_or_nonexistant { @@ -1631,7 +1641,7 @@ impl<'a, A: hil::time::Alarm<'a>> SyscallDriver for SDCardDriver<'a, A> { // copy over write data from application // Limit to minimum length between kernel_buf, // write_buffer, and 512 (block size) - for (kernel_byte, ref write_byte) in kernel_buf + for (kernel_byte, write_byte) in kernel_buf .iter_mut() .zip(write_buffer.iter()) .take(512) diff --git a/capsules/src/segger_rtt.rs b/capsules/extra/src/segger_rtt.rs similarity index 97% rename from capsules/src/segger_rtt.rs rename to capsules/extra/src/segger_rtt.rs index 440bd2cc38..4696c183b8 100644 --- a/capsules/src/segger_rtt.rs +++ b/capsules/extra/src/segger_rtt.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Segger RTT implementation. //! //! RTT is a protocol for sending debugging messages to a connected host. The @@ -8,7 +12,7 @@ //! Receiving RTT Messages //! ---------------------- //! -//! With the jlink tools, reciving RTT messages is a two step process. First, +//! With the jlink tools, receiving RTT messages is a two step process. First, //! open a JTAG connection with a command like: //! //! ```shell @@ -38,7 +42,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! pub struct Platform { //! // Other fields omitted for clarity //! console: &'static capsules::console::Console<'static>, @@ -47,7 +51,7 @@ //! //! In `main()`: //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! # use capsules::virtual_alarm::VirtualMuxAlarm; //! @@ -191,7 +195,7 @@ impl<'a, A: hil::time::Alarm<'a>> SeggerRtt<'a, A> { down_buffer: &'a mut [u8], ) -> SeggerRtt<'a, A> { SeggerRtt { - alarm: alarm, + alarm, config: TakeCell::new(config), up_buffer: TakeCell::new(up_buffer), _down_buffer: TakeCell::new(down_buffer), diff --git a/capsules/extra/src/seven_segment.rs b/capsules/extra/src/seven_segment.rs new file mode 100644 index 0000000000..e6ef94a7e5 --- /dev/null +++ b/capsules/extra/src/seven_segment.rs @@ -0,0 +1,408 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Provides userspace access to 7 segment digit displays. +//! +//! This capsule was developed using the following components: +//! - Microbit_v2 +//! - Edge Connector Breakout Board for Microbit (PPMB00126) +//! - 7 segment display with 4 digits (3461BS-1) +//! - breadboard, 220 ohms resistances and jump wires +//! +//! Usage +//! ----- +//! +//! Example of use for a display with 4 digits and the Microbit: +//! Microbit Pins: +//! 4 digit 7 segment display pinout: +//! +//! ```rust,ignore +//! const NUM_DIGITS: usize = 4; +//! const DIGITS: [Pin; 4] = [Pin::P1_02, Pin::P0_12, Pin::P0_30, Pin::P0_09]; // [D1, D2, D3, D4] +//! const SEGMENTS: [Pin; 7] = [ +//! Pin::P0_02, // A +//! Pin::P0_03, // B +//! Pin::P0_04, // C +//! Pin::P0_31, // D +//! Pin::P0_28, // E +//! Pin::P0_10, // F +//! Pin::P1_05, // G +//! ]; +//! const DOT: Pin = Pin::P0_11; +//! +//! let segment_array = static_init!( +//! [&'static nrf52::gpio::GPIOPin<'static>; 8], +//! [ +//! static_init!( +//! &'static nrf52::gpio::GPIOPin<'static>, +//! &nrf52833_peripherals.gpio_port[SEGMENTS[0]] +//! ), +//! static_init!( +//! &'static nrf52::gpio::GPIOPin<'static>, +//! &nrf52833_peripherals.gpio_port[SEGMENTS[1]] +//! ), +//! static_init!( +//! &'static nrf52::gpio::GPIOPin<'static>, +//! &nrf52833_peripherals.gpio_port[SEGMENTS[2]] +//! ), +//! static_init!( +//! &'static nrf52::gpio::GPIOPin<'static>, +//! &nrf52833_peripherals.gpio_port[SEGMENTS[3]] +//! ), +//! static_init!( +//! &'static nrf52::gpio::GPIOPin<'static>, +//! &nrf52833_peripherals.gpio_port[SEGMENTS[4]] +//! ), +//! static_init!( +//! &'static nrf52::gpio::GPIOPin<'static>, +//! &nrf52833_peripherals.gpio_port[SEGMENTS[5]] +//! ), +//! static_init!( +//! &'static nrf52::gpio::GPIOPin<'static>, +//! &nrf52833_peripherals.gpio_port[SEGMENTS[6]] +//! ), +//! static_init!( +//! &'static nrf52::gpio::GPIOPin<'static>, +//! &nrf52833_peripherals.gpio_port[DOT] +//! ), +//! ] +//! ); +//! +//! let digit_array = static_init!( +//! [&'static nrf52::gpio::GPIOPin<'static>; 4], +//! [ +//! static_init!( +//! &'static nrf52::gpio::GPIOPin<'static>, +//! &nrf52833_peripherals.gpio_port[DIGITS[0]] +//! ), +//! static_init!( +//! &'static nrf52::gpio::GPIOPin<'static>, +//! &nrf52833_peripherals.gpio_port[DIGITS[1]] +//! ), +//! static_init!( +//! &'static nrf52::gpio::GPIOPin<'static>, +//! &nrf52833_peripherals.gpio_port[DIGITS[2]] +//! ), +//! static_init!( +//! &'static nrf52::gpio::GPIOPin<'static>, +//! &nrf52833_peripherals.gpio_port[DIGITS[3]] +//! ), +//! ] +//! ); +//! +//! let buffer = static_init!([u8; 4], [0; 4]); +//! +//! let digit_display = static_init!( +//! capsules::digits::DigitsDriver< +//! 'static, +//! nrf52::gpio::GPIOPin<'static>, +//! capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>, +//! >, +//! capsules::digits::DigitsDriver::new( +//! segment_array, +//! digit_array, +//! buffer, +//! virtual_alarm_digit, +//! kernel::hil::gpio::ActivationMode::ActiveLow, +//! kernel::hil::gpio::ActivationMode::ActiveHigh, +//! 60 +//! ), +//! ); +//! ``` +//! +//! virtual_alarm_digit.set_alarm_client(digit_display); +//! +//! digit_display.init(); +//! +//! +//! Syscall Interface +//! ----------------- +//! +//! ### Command +//! +//! All operations are synchronous, so this capsule only uses the `command` +//! syscall. +//! +//! #### `command_num` +//! +//! - `0`: Driver Check. +//! - `data1`: Unused. +//! - `data2`: Unused. +//! - Return: Number of digits. +//! - `1`: Prints one digit at the requested position. +//! - `data1`: The position of the digit. Starts at 1. +//! - `data2`: The digit to be represented, from 0 to 9. +//! - Return: `Ok(())` if the digit index was valid, `INVAL` otherwise. +//! - `2`: Clears all digits currently being displayed. +//! - `data1`: Unused. +//! - `data2`: Unused. +//! - `3`: Print a dot at the requested digit position. +//! - `data1`: The position of the dot. Starts at 1. +//! - Return: `Ok(())` if the index was valid, `INVAL` otherwise. +//! - `4`: Print a custom pattern for a digit on a certain position. +//! - `data1`: The position of the digit. Starts at 1. +//! - `data2`: The custom pattern to be represented. +//! - Return: `Ok(())` if the index was valid, `INVAL` otherwise. +//! - `5`: Return the number of digits on the display being used. +//! - `data1`: Unused. +//! - `data2`: Unused. + +use core::cell::Cell; + +use kernel::hil::gpio::{ActivationMode, Pin}; +use kernel::hil::time::{Alarm, AlarmClient, ConvertTicks}; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::utilities::cells::TakeCell; +use kernel::ErrorCode; +use kernel::ProcessId; + +/// Syscall driver number. +use capsules_core::driver; +pub const DRIVER_NUM: usize = driver::NUM::SevenSegment as usize; + +/// Digit patterns +// +// A +// _ +// F |_| B center = G +// E |_| C . Dp +// D +// +const DIGITS: [u8; 10] = [ + // pattern: 0bDpGFEDCBA + 0b00111111, // 0 + 0b00000110, // 1 + 0b01011011, // 2 + 0b01001111, // 3 + 0b01100110, // 4 + 0b01101101, // 5 + 0b01111101, // 6 + 0b00100111, // 7 + 0b01111111, // 8 + 0b01101111, // 9 +]; + +/// Holds an array of digits and an array of segments for each digit. + +pub struct SevenSegmentDriver<'a, P: Pin, A: Alarm<'a>, const NUM_DIGITS: usize> { + /// An array of 8 segment pins (7 for digit segments and one dot segment) + segments: &'a [&'a P; 8], + /// An array of `NUM_DIGITS` digit pins, each one corresponding to one digit on the display + /// For each digit selected, a pattern of lit and unlit segments will be represented + digits: &'a [&'a P; NUM_DIGITS], + /// A buffer which contains the patterns displayed for each digit + /// Each element of the buffer array represents the pattern for one digit, and + /// is a sequence of bits that have the value 1 for a lit segment and the value 0 for + /// an unlit segment. + buffer: TakeCell<'a, [u8; NUM_DIGITS]>, + alarm: &'a A, + current_digit: Cell, + /// How fast the driver should switch between digits (ms) + timing: u8, + segment_activation: ActivationMode, + digit_activation: ActivationMode, +} + +impl<'a, P: Pin, A: Alarm<'a>, const NUM_DIGITS: usize> SevenSegmentDriver<'a, P, A, NUM_DIGITS> { + pub fn new( + segments: &'a [&'a P; 8], + digits: &'a [&'a P; NUM_DIGITS], + buffer: &'a mut [u8; NUM_DIGITS], + alarm: &'a A, + segment_activation: ActivationMode, + digit_activation: ActivationMode, + refresh_rate: usize, + ) -> Self { + // Check if the buffer has enough space to hold patterns for all digits + if (buffer.len() * 8) < segments.len() * digits.len() { + panic!("Digits Driver: provided buffer is too small"); + } + + Self { + segments, + digits, + buffer: TakeCell::new(buffer), + alarm, + segment_activation, + digit_activation, + current_digit: Cell::new(0), + timing: (1000 / (refresh_rate * digits.len())) as u8, + } + } + + /// Initialize the digit and segment pins. + /// Does not override pins if they have already been initialized for another driver. + pub fn init(&self) { + for segment in self.segments { + segment.make_output(); + self.segment_clear(segment); + } + + for digit in self.digits { + digit.make_output(); + self.digit_clear(digit); + } + + self.next_digit(); + } + + /// Returns the number of digits on the display. + pub fn digits_len(&self) -> usize { + self.digits.len() + } + + /// Represents each digit with its corresponding pattern. + fn next_digit(&self) { + self.digit_clear(self.digits[self.current_digit.get()]); + self.current_digit + .set((self.current_digit.get() + 1) % self.digits.len()); + self.buffer.map(|bits| { + for segment in 0..self.segments.len() { + let location = self.current_digit.get() * self.segments.len() + segment; + if (bits[location / 8] >> (location % 8)) & 0x1 == 1 { + self.segment_set(self.segments[segment]); + } else { + self.segment_clear(self.segments[segment]); + } + } + }); + self.digit_set(self.digits[self.current_digit.get()]); + let interval = self.alarm.ticks_from_ms(self.timing as u32); + self.alarm.set_alarm(self.alarm.now(), interval); + } + + fn segment_set(&self, p: &P) { + match self.segment_activation { + ActivationMode::ActiveHigh => p.set(), + ActivationMode::ActiveLow => p.clear(), + } + } + + fn segment_clear(&self, p: &P) { + match self.segment_activation { + ActivationMode::ActiveHigh => p.clear(), + ActivationMode::ActiveLow => p.set(), + } + } + + fn digit_set(&self, p: &P) { + match self.digit_activation { + ActivationMode::ActiveHigh => p.set(), + ActivationMode::ActiveLow => p.clear(), + } + } + + fn digit_clear(&self, p: &P) { + match self.digit_activation { + ActivationMode::ActiveHigh => p.clear(), + ActivationMode::ActiveLow => p.set(), + } + } + + /// Sets the pattern for the digit on the requested position. + fn print_digit(&self, position: usize, digit: usize) -> Result<(), ErrorCode> { + if position <= self.digits.len() { + self.buffer.map(|bits| bits[position - 1] = DIGITS[digit]); + Ok(()) + } else { + Err(ErrorCode::INVAL) + } + } + + /// Clears all digits currently being displayed. + fn clear_digits(&self) -> Result<(), ErrorCode> { + self.buffer.map(|bits| { + for index in 0..self.digits.len() { + bits[index] = 0; + } + }); + Ok(()) + } + + /// Prints a dot at the requested digit position. + fn print_dot(&self, position: usize) -> Result<(), ErrorCode> { + if position <= self.digits.len() { + self.buffer.map(|bits| { + // set the first bit of the digit on this position + bits[position - 1] |= 1 << (self.segments.len() - 1); + }); + Ok(()) + } else { + Err(ErrorCode::INVAL) + } + } + + /// Prints a custom pattern at a requested position. + fn print(&self, position: usize, pattern: u8) -> Result<(), ErrorCode> { + if position <= self.digits.len() { + self.buffer.map(|bits| { + bits[position - 1] = pattern; + }); + Ok(()) + } else { + Err(ErrorCode::INVAL) + } + } +} + +impl<'a, P: Pin, A: Alarm<'a>, const NUM_DIGITS: usize> AlarmClient + for SevenSegmentDriver<'a, P, A, NUM_DIGITS> +{ + fn alarm(&self) { + self.next_digit(); + } +} + +impl<'a, P: Pin, A: Alarm<'a>, const NUM_DIGITS: usize> SyscallDriver + for SevenSegmentDriver<'a, P, A, NUM_DIGITS> +{ + /// Control the digit display. + /// + /// ### `command_num` + /// + /// - `0`: Driver existence check. + /// - `1`: Prints one digit at the requested position. Returns `INVAL` if the + /// position is not valid. + /// - `2`: Clears all digits currently being displayed. + /// - `3`: Print a dot at the requested digit position. Returns + /// `INVAL` if the position is not valid. + /// - `4`: Print a custom pattern for a certain digit. Returns + /// `INVAL` if the position is not valid. + /// - `5`: Returns the number of digits on the display. This will always be 0 or + /// greater, and therefore also allows for checking for this driver. + fn command( + &self, + command_num: usize, + data1: usize, + data2: usize, + _: ProcessId, + ) -> CommandReturn { + match command_num { + // Check existence + 0 => CommandReturn::success(), + + // Print one digit + 1 => CommandReturn::from(self.print_digit(data1, data2)), + + // Clear all digits + 2 => CommandReturn::from(self.clear_digits()), + + // Print dot + 3 => CommandReturn::from(self.print_dot(data1)), + + // Print a custom pattern + 4 => CommandReturn::from(self.print(data1, data2 as u8)), + + // Return number of digits + 5 => CommandReturn::success_u32(self.digits.len() as u32), + + // default + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, _processid: ProcessId) -> Result<(), kernel::process::Error> { + Ok(()) + } +} diff --git a/capsules/extra/src/sh1106.rs b/capsules/extra/src/sh1106.rs new file mode 100644 index 0000000000..96bf9c13a3 --- /dev/null +++ b/capsules/extra/src/sh1106.rs @@ -0,0 +1,392 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! SH1106 OLED Screen Driver +//! +//! This display is similar to the SSD1306, but has two key differences: +//! - The commands are different. In particular, the SH1106 does not support the +//! `SetColumnAddress` and `SetPageAddress` commands which are useful for +//! setting frames on the screen. +//! - The driver does not automatically wrap to the next page. This driver +//! manually sets up each page (row). + +use core::cell::Cell; + +use crate::ssd1306::Command; +use kernel::hil; +use kernel::utilities::cells::{MapCell, OptionalCell, TakeCell}; +use kernel::utilities::leasable_buffer::SubSliceMut; +use kernel::ErrorCode; + +// Only need to be able to write one page (row) at a time. +pub const BUFFER_SIZE: usize = 132; + +const WIDTH: usize = 128; +const HEIGHT: usize = 64; + +// #[derive(Copy, Clone, PartialEq)] +#[derive(Clone, Copy, PartialEq)] +enum State { + Idle, + Init, + SimpleCommand, + WriteSetPage(u8), + WritePage(u8), +} + +pub struct Sh1106<'a, I: hil::i2c::I2CDevice> { + i2c: &'a I, + state: Cell, + client: OptionalCell<&'a dyn hil::screen::ScreenClient>, + setup_client: OptionalCell<&'a dyn hil::screen::ScreenSetupClient>, + buffer: TakeCell<'static, [u8]>, + write_buffer: MapCell>, + enable_charge_pump: bool, + + active_frame_x: Cell, + active_frame_y: Cell, + active_frame_width: Cell, + active_frame_height: Cell, +} + +impl<'a, I: hil::i2c::I2CDevice> Sh1106<'a, I> { + pub fn new(i2c: &'a I, buffer: &'static mut [u8], enable_charge_pump: bool) -> Self { + Self { + i2c, + state: Cell::new(State::Idle), + client: OptionalCell::empty(), + setup_client: OptionalCell::empty(), + buffer: TakeCell::new(buffer), + write_buffer: MapCell::empty(), + enable_charge_pump, + active_frame_x: Cell::new(0), + active_frame_y: Cell::new(0), + active_frame_width: Cell::new(0), + active_frame_height: Cell::new(0), + } + } + + pub fn init_screen(&self) { + let commands = [ + Command::SetDisplayOnOff { on: false }, + Command::SetDisplayClockDivide { + divide_ratio: 0, + oscillator_frequency: 0x8, + }, + Command::SetMultiplexRatio { + ratio: HEIGHT as u8 - 1, + }, + Command::SetDisplayOffset { vertical_shift: 0 }, + Command::SetDisplayStartLine { line: 0 }, + Command::SetChargePump { + enable: self.enable_charge_pump, + }, + Command::SetMemoryAddressingMode { mode: 0 }, //horizontal + Command::SetSegmentRemap { reverse: true }, + Command::SetComScanDirection { decrement: true }, + Command::SetComPins { + alternative: true, + enable_com: false, + }, + Command::SetContrast { contrast: 0xcf }, + Command::SetPrechargePeriod { + phase1: 0x1, + phase2: 0xf, + }, + Command::SetVcomDeselect { level: 2 }, + Command::EntireDisplayOn { ignore_ram: false }, + Command::SetDisplayInvert { inverse: false }, + Command::DeactivateScroll, + Command::SetDisplayOnOff { on: true }, + ]; + + match self.send_sequence(&commands) { + Ok(()) => { + self.state.set(State::Init); + } + Err(_e) => {} + } + } + + fn send_sequence(&self, sequence: &[Command]) -> Result<(), ErrorCode> { + self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| { + let mut buf_slice = SubSliceMut::new(buffer); + + // Specify this is a series of command bytes. + buf_slice[0] = 0; // Co = 0, D/C̅ = 0 + + // Move the window of the subslice after the command byte header. + buf_slice.slice(1..); + + for cmd in sequence.iter() { + cmd.encode(&mut buf_slice); + } + + // We need the amount of data that has been sliced away + // at the start of the subslice. + let remaining_len = buf_slice.len(); + buf_slice.reset(); + let tx_len = buf_slice.len() - remaining_len; + + self.i2c.enable(); + match self.i2c.write(buf_slice.take(), tx_len) { + Ok(()) => Ok(()), + Err((_e, buf)) => { + self.buffer.replace(buf); + self.i2c.disable(); + Err(ErrorCode::INVAL) + } + } + }) + } + + fn write_continue(&self) -> Result<(), ErrorCode> { + match self.state.get() { + State::WriteSetPage(page_index) => { + self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| { + self.write_buffer.map_or(Err(ErrorCode::NOMEM), |data| { + // Calculate which part of the data buffer we need to + // write. + let start_page_index = self.active_frame_y.get() / 8; + let buffer_start_index = ((page_index - start_page_index) as usize) + * self.active_frame_width.get() as usize; + let page_len = self.active_frame_width.get() as usize; + + let mut buf_slice = SubSliceMut::new(buffer); + + // Specify this is data. + buf_slice[0] = 0x40; // Co = 0, D/C̅ = 1 + + // Move the window of the subslice after the command + // byte header. + buf_slice.slice(1..); + + // Copy the correct page data to the buffer. + for i in 0..page_len { + buf_slice[i] = data[buffer_start_index + i]; + } + + // Length includes the header byte. + let tx_len = page_len + 1; + + self.i2c.enable(); + match self.i2c.write(buf_slice.take(), tx_len) { + Ok(()) => { + self.state.set(State::WritePage(page_index)); + Ok(()) + } + Err((_e, buf)) => { + self.buffer.replace(buf); + Err(ErrorCode::INVAL) + } + } + }) + }) + } + + State::WritePage(page_index) => { + // Finished writing a page of data. Check if there is more to + // do. + let next_page = page_index + 1; + let last_page = (self.active_frame_y.get() + self.active_frame_height.get()) / 8; + + if next_page >= last_page { + // Done, can issue callback. + self.state.set(State::Idle); + self.write_buffer.take().map(|buf| { + self.client.map(|client| client.write_complete(buf, Ok(()))); + }); + Ok(()) + } else { + // Continue writing by setting up the next page. + self.set_page(next_page) + } + } + + _ => Err(ErrorCode::FAIL), + } + } + + fn set_page(&self, page_index: u8) -> Result<(), ErrorCode> { + let column_start = self.active_frame_x.get() + 2; + let commands = [ + Command::SetPageStartAddress { + address: page_index, + }, + Command::SetLowerColumnStartAddress { + address: column_start, + }, + Command::SetHigherColumnStartAddress { + address: column_start, + }, + ]; + match self.send_sequence(&commands) { + Ok(()) => { + self.state.set(State::WriteSetPage(page_index)); + Ok(()) + } + Err(e) => Err(e), + } + } +} + +impl<'a, I: hil::i2c::I2CDevice> hil::screen::ScreenSetup<'a> for Sh1106<'a, I> { + fn set_client(&self, client: &'a dyn hil::screen::ScreenSetupClient) { + self.setup_client.set(client); + } + + fn set_resolution(&self, _resolution: (usize, usize)) -> Result<(), ErrorCode> { + Err(ErrorCode::NOSUPPORT) + } + + fn set_pixel_format(&self, _depth: hil::screen::ScreenPixelFormat) -> Result<(), ErrorCode> { + Err(ErrorCode::NOSUPPORT) + } + + fn set_rotation(&self, _rotation: hil::screen::ScreenRotation) -> Result<(), ErrorCode> { + Err(ErrorCode::NOSUPPORT) + } + + fn get_num_supported_resolutions(&self) -> usize { + 1 + } + + fn get_supported_resolution(&self, index: usize) -> Option<(usize, usize)> { + match index { + 0 => Some((WIDTH, HEIGHT)), + _ => None, + } + } + + fn get_num_supported_pixel_formats(&self) -> usize { + 1 + } + + fn get_supported_pixel_format(&self, index: usize) -> Option { + match index { + 0 => Some(hil::screen::ScreenPixelFormat::Mono), + _ => None, + } + } +} + +impl<'a, I: hil::i2c::I2CDevice> hil::screen::Screen<'a> for Sh1106<'a, I> { + fn set_client(&self, client: &'a dyn hil::screen::ScreenClient) { + self.client.set(client); + } + + fn get_resolution(&self) -> (usize, usize) { + (WIDTH, HEIGHT) + } + + fn get_pixel_format(&self) -> hil::screen::ScreenPixelFormat { + hil::screen::ScreenPixelFormat::Mono + } + + fn get_rotation(&self) -> hil::screen::ScreenRotation { + hil::screen::ScreenRotation::Normal + } + + fn set_write_frame( + &self, + x: usize, + y: usize, + width: usize, + height: usize, + ) -> Result<(), ErrorCode> { + // Save the current frame settings. + self.active_frame_x.set(x as u8); + self.active_frame_y.set(y as u8); + self.active_frame_width.set(width as u8); + self.active_frame_height.set(height as u8); + + // The driver RAM is 132 bytes wide, the screen is 128 bytes wide, so we + // offset by two. + let column_start: u8 = (x as u8) + 2; + let commands = [ + Command::SetPageStartAddress { + address: (y / 8) as u8, + }, + Command::SetLowerColumnStartAddress { + address: column_start, + }, + Command::SetHigherColumnStartAddress { + address: column_start, + }, + ]; + match self.send_sequence(&commands) { + Ok(()) => { + self.state.set(State::SimpleCommand); + Ok(()) + } + Err(e) => Err(e), + } + } + + fn write(&self, data: SubSliceMut<'static, u8>, _continue: bool) -> Result<(), ErrorCode> { + self.write_buffer.replace(data); + + // Start by setting the page as active in the screen. + self.set_page(self.active_frame_y.get() / 8) + } + + fn set_brightness(&self, brightness: u16) -> Result<(), ErrorCode> { + let commands = [Command::SetContrast { + contrast: (brightness >> 8) as u8, + }]; + match self.send_sequence(&commands) { + Ok(()) => { + self.state.set(State::SimpleCommand); + Ok(()) + } + Err(e) => Err(e), + } + } + + fn set_power(&self, enabled: bool) -> Result<(), ErrorCode> { + let commands = [Command::SetDisplayOnOff { on: enabled }]; + match self.send_sequence(&commands) { + Ok(()) => { + self.state.set(State::SimpleCommand); + Ok(()) + } + Err(e) => Err(e), + } + } + + fn set_invert(&self, enabled: bool) -> Result<(), ErrorCode> { + let commands = [Command::SetDisplayInvert { inverse: enabled }]; + match self.send_sequence(&commands) { + Ok(()) => { + self.state.set(State::SimpleCommand); + Ok(()) + } + Err(e) => Err(e), + } + } +} + +impl<'a, I: hil::i2c::I2CDevice> hil::i2c::I2CClient for Sh1106<'a, I> { + fn command_complete(&self, buffer: &'static mut [u8], _status: Result<(), hil::i2c::Error>) { + self.buffer.replace(buffer); + self.i2c.disable(); + + match self.state.get() { + State::Init => { + self.state.set(State::Idle); + self.client.map(|client| client.screen_is_ready()); + } + + State::SimpleCommand => { + self.state.set(State::Idle); + self.client.map(|client| client.command_complete(Ok(()))); + } + + State::WritePage(_) | State::WriteSetPage(_) => { + let _ = self.write_continue(); + } + _ => {} + } + } +} diff --git a/capsules/src/sha.rs b/capsules/extra/src/sha.rs similarity index 83% rename from capsules/src/sha.rs rename to capsules/extra/src/sha.rs index 3481434733..6bcb0dde5d 100644 --- a/capsules/src/sha.rs +++ b/capsules/extra/src/sha.rs @@ -1,13 +1,17 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SHA //! //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! let sha = &earlgrey::sha::HMAC; //! //! let mux_sha = static_init!(MuxSha<'static, lowrisc::sha::Sha>, MuxSha::new(sha)); -//! digest::Digest::set_client(&earlgrey::sha::HMAC, mux_sha); +//! digest::DigestMut::set_client(&earlgrey::sha::HMAC, mux_sha); //! //! let virtual_sha_user = static_init!( //! VirtualMuxSha<'static, lowrisc::sha::Sha>, @@ -20,11 +24,12 @@ //! board_kernel.create_grant(&memory_allocation_cap), //! ) //! ); -//! digest::Digest::set_client(virtual_sha_user, sha); +//! digest::DigestMut::set_client(virtual_sha_user, sha); //! ``` -use crate::driver; +use capsules_core::driver; use kernel::errorcode::into_statuscode; + /// Syscall driver number. pub const DRIVER_NUM: usize = driver::NUM::Sha as usize; @@ -33,14 +38,14 @@ mod ro_allow { pub const DATA: usize = 1; pub const COMPARE: usize = 2; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 3; + pub const COUNT: u8 = 3; } /// Ids for read-write allow buffers mod rw_allow { pub const DEST: usize = 2; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 3; + pub const COUNT: u8 = 3; } use core::cell::Cell; @@ -50,7 +55,8 @@ use kernel::hil::digest; use kernel::processbuffer::{ReadableProcessBuffer, WriteableProcessBuffer}; use kernel::syscall::{CommandReturn, SyscallDriver}; use kernel::utilities::cells::{OptionalCell, TakeCell}; -use kernel::utilities::leasable_buffer::LeasableBuffer; +use kernel::utilities::leasable_buffer::SubSlice; +use kernel::utilities::leasable_buffer::SubSliceMut; use kernel::{ErrorCode, ProcessId}; enum ShaOperation { @@ -70,7 +76,7 @@ pub struct ShaDriver<'a, H: digest::Digest<'a, L>, const L: usize> { AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<{ rw_allow::COUNT }>, >, - appid: OptionalCell, + processid: OptionalCell, data_buffer: TakeCell<'static, [u8]>, data_copied: Cell, @@ -95,10 +101,10 @@ impl< >, ) -> ShaDriver<'a, H, L> { ShaDriver { - sha: sha, + sha, active: Cell::new(false), apps: grant, - appid: OptionalCell::empty(), + processid: OptionalCell::empty(), data_buffer: TakeCell::new(data_buffer), data_copied: Cell::new(0), dest_buffer: TakeCell::new(dest_buffer), @@ -106,20 +112,14 @@ impl< } fn run(&self) -> Result<(), ErrorCode> { - self.appid.map_or(Err(ErrorCode::RESERVE), |appid| { + self.processid.map_or(Err(ErrorCode::RESERVE), |processid| { self.apps - .enter(*appid, |app, kernel_data| { - let ret = if let Some(op) = &app.sha_operation { - match op { - ShaOperation::Sha256 => self.sha.set_mode_sha256(), - ShaOperation::Sha384 => self.sha.set_mode_sha384(), - ShaOperation::Sha512 => self.sha.set_mode_sha512(), - } - } else { - Err(ErrorCode::INVAL) - }; - if ret.is_err() { - return ret; + .enter(processid, |app, kernel_data| { + match app.sha_operation { + Some(ShaOperation::Sha256) => self.sha.set_mode_sha256()?, + Some(ShaOperation::Sha384) => self.sha.set_mode_sha384()?, + Some(ShaOperation::Sha512) => self.sha.set_mode_sha512()?, + _ => return Err(ErrorCode::INVAL), } kernel_data @@ -143,12 +143,12 @@ impl< }); // Add the data from the static buffer to the HMAC - let mut lease_buf = LeasableBuffer::new( + let mut lease_buf = SubSliceMut::new( self.data_buffer.take().ok_or(ErrorCode::RESERVE)?, ); lease_buf.slice(0..static_buffer_len); - if let Err(e) = self.sha.add_data(lease_buf) { - self.data_buffer.replace(e.1); + if let Err(e) = self.sha.add_mut_data(lease_buf) { + self.data_buffer.replace(e.1.take()); return Err(e.0); } Ok(()) @@ -164,14 +164,14 @@ impl< for appiter in self.apps.iter() { let started_command = appiter.enter(|app, _| { // If an app is already running let it complete - if self.appid.is_some() { + if self.processid.is_some() { return true; } // If this app has a pending command let's use it. - app.pending_run_app.take().map_or(false, |appid| { + app.pending_run_app.take().map_or(false, |processid| { // Mark this driver as being in use. - self.appid.set(appid); + self.processid.set(processid); // Actually make the buzz happen. self.run() == Ok(()) }) @@ -189,9 +189,9 @@ impl< .sha .run(self.dest_buffer.take().ok_or(ErrorCode::RESERVE)?) { - // Error, clear the appid and data + // Error, clear the processid and data self.sha.clear_data(); - self.appid.clear(); + self.processid.clear(); self.dest_buffer.replace(e.1); return Err(e.0); @@ -207,9 +207,9 @@ impl< .sha .verify(self.dest_buffer.take().ok_or(ErrorCode::RESERVE)?) { - // Error, clear the appid and data + // Error, clear the processid and data self.sha.clear_data(); - self.appid.clear(); + self.processid.clear(); self.dest_buffer.replace(e.1); return Err(e.0); @@ -223,17 +223,21 @@ impl< 'a, H: digest::Digest<'a, L> + digest::Sha256 + digest::Sha384 + digest::Sha512, const L: usize, - > digest::ClientData<'a, L> for ShaDriver<'a, H, L> + > digest::ClientData for ShaDriver<'a, H, L> { - fn add_data_done(&'a self, _result: Result<(), ErrorCode>, data: &'static mut [u8]) { - self.appid.map(move |id| { + // Because data needs to be copied from a userspace buffer into a kernel (RAM) one, + // we always pass mut data; this callback should never be invoked. + fn add_data_done(&self, _result: Result<(), ErrorCode>, _data: SubSlice<'static, u8>) {} + + fn add_mut_data_done(&self, _result: Result<(), ErrorCode>, data: SubSliceMut<'static, u8>) { + self.processid.map(move |id| { self.apps - .enter(*id, move |app, kernel_data| { + .enter(id, move |app, kernel_data| { let mut data_len = 0; let mut exit = false; let mut static_buffer_len = 0; - self.data_buffer.replace(data); + self.data_buffer.replace(data.take()); self.data_buffer.map(|buf| { let ret = kernel_data @@ -264,9 +268,9 @@ impl< .unwrap_or(Err(ErrorCode::RESERVE)); if ret == Err(ErrorCode::RESERVE) { - // No data buffer, clear the appid and data + // No data buffer, clear the processid and data self.sha.clear_data(); - self.appid.clear(); + self.processid.clear(); exit = true; } }); @@ -282,18 +286,17 @@ impl< // Update the amount of data copied self.data_copied.set(copied_data + static_buffer_len); - let mut lease_buf = - LeasableBuffer::new(self.data_buffer.take().unwrap()); + let mut lease_buf = SubSliceMut::new(self.data_buffer.take().unwrap()); // Add the data from the static buffer to the HMAC if data_len < (copied_data + static_buffer_len) { lease_buf.slice(..(data_len - copied_data)) } - if self.sha.add_data(lease_buf).is_err() { - // Error, clear the appid and data + if self.sha.add_mut_data(lease_buf).is_err() { + // Error, clear the processid and data self.sha.clear_data(); - self.appid.clear(); + self.processid.clear(); return; } @@ -345,7 +348,7 @@ impl< if err == kernel::process::Error::NoSuchApp || err == kernel::process::Error::InactiveApp { - self.appid.clear(); + self.processid.clear(); } }) }); @@ -358,12 +361,12 @@ impl< 'a, H: digest::Digest<'a, L> + digest::Sha256 + digest::Sha384 + digest::Sha512, const L: usize, - > digest::ClientHash<'a, L> for ShaDriver<'a, H, L> + > digest::ClientHash for ShaDriver<'a, H, L> { - fn hash_done(&'a self, result: Result<(), ErrorCode>, digest: &'static mut [u8; L]) { - self.appid.map(|id| { + fn hash_done(&self, result: Result<(), ErrorCode>, digest: &'static mut [u8; L]) { + self.processid.map(|id| { self.apps - .enter(*id, |_, kernel_data| { + .enter(id, |_, kernel_data| { self.sha.clear_data(); let pointer = digest.as_ref()[0] as *mut u8; @@ -383,7 +386,7 @@ impl< }); match result { - Ok(_) => kernel_data + Ok(()) => kernel_data .schedule_upcall(0, (0, pointer as usize, 0)) .ok(), Err(e) => kernel_data @@ -391,14 +394,14 @@ impl< .ok(), }; - // Clear the current appid as it has finished running - self.appid.clear(); + // Clear the current processid as it has finished running + self.processid.clear(); }) .map_err(|err| { if err == kernel::process::Error::NoSuchApp || err == kernel::process::Error::InactiveApp { - self.appid.clear(); + self.processid.clear(); } }) }); @@ -412,12 +415,12 @@ impl< 'a, H: digest::Digest<'a, L> + digest::Sha256 + digest::Sha384 + digest::Sha512, const L: usize, - > digest::ClientVerify<'a, L> for ShaDriver<'a, H, L> + > digest::ClientVerify for ShaDriver<'a, H, L> { - fn verification_done(&'a self, result: Result, compare: &'static mut [u8; L]) { - self.appid.map(|id| { + fn verification_done(&self, result: Result, compare: &'static mut [u8; L]) { + self.processid.map(|id| { self.apps - .enter(*id, |_app, kernel_data| { + .enter(id, |_app, kernel_data| { self.sha.clear_data(); match result { @@ -426,14 +429,14 @@ impl< } .ok(); - // Clear the current appid as it has finished running - self.appid.clear(); + // Clear the current processid as it has finished running + self.processid.clear(); }) .map_err(|err| { if err == kernel::process::Error::NoSuchApp || err == kernel::process::Error::InactiveApp { - self.appid.clear(); + self.processid.clear(); } }) }); @@ -456,13 +459,13 @@ impl< /// above allow calls. /// /// We expect userspace not to change the value while running. If userspace - /// changes the value we have no guarentee of what is passed to the + /// changes the value we have no guarantee of what is passed to the /// hardware. This isn't a security issue, it will just prove the requesting /// app with invalid data. /// - /// The driver will take care of clearing data from the underlying impelemenation + /// The driver will take care of clearing data from the underlying implementation /// by calling the `clear_data()` function when the `hash_complete()` callback - /// is called or if an error is encounted. + /// is called or if an error is encountered. /// /// ### `command_num` /// @@ -475,9 +478,9 @@ impl< command_num: usize, data1: usize, _data2: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { - let match_or_empty_or_nonexistant = self.appid.map_or(true, |owning_app| { + let match_or_empty_or_nonexistant = self.processid.map_or(true, |owning_app| { // We have recorded that an app has ownership of the HMAC. // If the HMAC is still active, then we need to wait for the operation @@ -486,7 +489,7 @@ impl< // we need to verify that that application still exists, and remove // it as owner if not. if self.active.get() { - owning_app == &appid + owning_app == processid } else { // Check the app still exists. // @@ -496,12 +499,12 @@ impl< // longer exists and we return `true` to signify the // "or_nonexistant" case. self.apps - .enter(*owning_app, |_, _| owning_app == &appid) + .enter(owning_app, |_, _| owning_app == processid) .unwrap_or(true) } }); - let app_match = self.appid.map_or(false, |owning_app| { + let app_match = self.processid.map_or(false, |owning_app| { // We have recorded that an app has ownership of the HMAC. // If the HMAC is still active, then we need to wait for the operation @@ -510,7 +513,7 @@ impl< // we need to verify that that application still exists, and remove // it as owner if not. if self.active.get() { - owning_app == &appid + owning_app == processid } else { // Check the app still exists. // @@ -520,7 +523,7 @@ impl< // longer exists and we return `true` to signify the // "or_nonexistant" case. self.apps - .enter(*owning_app, |_, _| owning_app == &appid) + .enter(owning_app, |_, _| owning_app == processid) .unwrap_or(true) } }); @@ -530,9 +533,9 @@ impl< if match_or_empty_or_nonexistant && (command_num == 1 || command_num == 2 || command_num == 4) { - self.appid.set(appid); + self.processid.set(processid); - let _ = self.apps.enter(appid, |app, _| { + let _ = self.apps.enter(processid, |app, _| { if command_num == 1 { // run // Use key and data to compute hash @@ -545,7 +548,7 @@ impl< app.op.set(Some(UserSpaceOp::Update)); } else if command_num == 4 { // verify - // Use key and data to compute hash and comapre it against + // Use key and data to compute hash and compare it against // the digest app.op.set(Some(UserSpaceOp::Verify)); } @@ -553,7 +556,7 @@ impl< return if let Err(e) = self.run() { self.sha.clear_data(); - self.appid.clear(); + self.processid.clear(); self.check_queue(); CommandReturn::failure(e) } else { @@ -562,7 +565,7 @@ impl< } self.apps - .enter(appid, |app, kernel_data| { + .enter(processid, |app, kernel_data| { match command_num { // set_algorithm 0 => { @@ -595,7 +598,7 @@ impl< CommandReturn::failure(ErrorCode::NOMEM) } else { // We can store this, so lets do it. - app.pending_run_app = Some(appid); + app.pending_run_app = Some(processid); app.op.set(Some(UserSpaceOp::Run)); CommandReturn::success() } @@ -610,7 +613,7 @@ impl< CommandReturn::failure(ErrorCode::NOMEM) } else { // We can store this, so lets do it. - app.pending_run_app = Some(appid); + app.pending_run_app = Some(processid); app.op.set(Some(UserSpaceOp::Update)); CommandReturn::success() } @@ -642,14 +645,14 @@ impl< CommandReturn::failure(ErrorCode::NOMEM) } else { // We can store this, so lets do it. - app.pending_run_app = Some(appid); + app.pending_run_app = Some(processid); app.op.set(Some(UserSpaceOp::Verify)); CommandReturn::success() } } // verify_finish - // Use key and data to compute hash and comapre it against + // Use key and data to compute hash and compare it against // the digest, useful after a update command 5 => { if app_match { diff --git a/capsules/extra/src/sha256.rs b/capsules/extra/src/sha256.rs new file mode 100644 index 0000000000..573a087855 --- /dev/null +++ b/capsules/extra/src/sha256.rs @@ -0,0 +1,506 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Software implementation of SHA-256. +//! +//! Implementation is based on the Wikipedia description of the +//! algorithm. It performs the hash using 32-bit native values, +//! translating the input data into the endianness of the processor +//! and translating the output into big endian format. + +use core::cell::Cell; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; + +use kernel::hil::digest::Sha256; +use kernel::hil::digest::{Client, ClientData, ClientHash, ClientVerify}; +use kernel::hil::digest::{ClientDataHash, ClientDataVerify, DigestDataHash, DigestDataVerify}; +use kernel::hil::digest::{Digest, DigestData, DigestHash, DigestVerify}; +use kernel::utilities::cells::{MapCell, OptionalCell}; +use kernel::utilities::leasable_buffer::SubSlice; +use kernel::utilities::leasable_buffer::SubSliceMut; +use kernel::utilities::leasable_buffer::SubSliceMutImmut; +use kernel::ErrorCode; + +#[derive(Clone, Copy, PartialEq)] +pub enum State { + Idle, + Data, + Hash, + Verify, + CancelData, + CancelHash, + CancelVerify, +} + +const SHA_BLOCK_LEN_BYTES: usize = 64; +const SHA_256_OUTPUT_LEN_BYTES: usize = 32; +const NUM_ROUND_CONSTANTS: usize = 64; + +const ROUND_CONSTANTS: [u32; NUM_ROUND_CONSTANTS] = [ + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, +]; + +pub struct Sha256Software<'a> { + state: Cell, + + client: OptionalCell<&'a dyn Client>, + input_data: OptionalCell>, + data_buffer: MapCell<[u8; SHA_BLOCK_LEN_BYTES]>, + buffered_length: Cell, + total_length: Cell, + + // Used to store the hash or the hash to compare against with verify + output_data: Cell>, + + hash_values: Cell<[u32; 8]>, + deferred_call: DeferredCall, +} + +impl<'a> Sha256Software<'a> { + pub fn new() -> Self { + let s = Self { + state: Cell::new(State::Idle), + client: OptionalCell::empty(), + input_data: OptionalCell::empty(), + data_buffer: MapCell::new([0; SHA_BLOCK_LEN_BYTES]), + buffered_length: Cell::new(0), + total_length: Cell::new(0), + + output_data: Cell::new(None), + hash_values: Cell::new([0; 8]), + + deferred_call: DeferredCall::new(), + }; + s.initialize(); + s + } + + pub fn busy(&self) -> bool { + match self.state.get() { + State::Idle => false, + _ => true, + } + } + + fn initialize(&self) { + let new_state = match self.state.get() { + State::Idle => State::Idle, + State::Data | State::CancelData => State::CancelData, + State::Hash | State::CancelHash => State::CancelHash, + State::Verify | State::CancelVerify => State::CancelVerify, + }; + self.state.set(new_state); + + self.buffered_length.set(0); + self.total_length.set(0); + self.data_buffer.map(|b| { + for i in 0..SHA_BLOCK_LEN_BYTES { + b[i] = 0; + } + }); + self.hash_values.set([ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, + 0x5be0cd19, + ]); + } + + // Complete the hash and produce a final hash result. + fn complete_sha256(&self) { + let mut buffered_length = self.buffered_length.get(); + // This shouldn't be necessary, as temp buffer should never be + // full. But if it is full, appending the 1 will be an + // out-of-bounds access and panic, so check and clear + // the buffered block just in case. + if buffered_length == 64 { + self.data_buffer.map(|b| { + self.compute_block(b); + for i in 0..SHA_BLOCK_LEN_BYTES { + b[i] = 0; + } + }); + buffered_length -= 64; + } + if buffered_length < 64 { + self.data_buffer.map(|b| { + for i in buffered_length..SHA_BLOCK_LEN_BYTES { + b[i] = 0; + } + }); + } + + self.data_buffer.map(|b| { + // Append the 1 + b.get_mut(buffered_length).map(|d| *d = 0x80); + //b[buffered_length] = 0x80; + buffered_length += 1; + // The length is 56 because of the 8 bytes appended. + // Since a block is 64 bytes, this means the last block + // must have at most 56 bytes including the appended 1, or + // it will bleed into the next block. + if buffered_length > 56 { + for i in buffered_length..SHA_BLOCK_LEN_BYTES { + b[i] = 0; + } + self.compute_block(b); + for i in 0..SHA_BLOCK_LEN_BYTES { + b[i] = 0; + } + buffered_length = 0; + } + let total_length = self.total_length.get(); + let length64 = (total_length * 8) as u64; + let len_high: u32 = (length64 >> 32) as u32; + let len_low: u32 = (length64 & 0xffffffff) as u32; + b[56] = (len_high >> 24 & 0xff) as u8; + b[57] = (len_high >> 16 & 0xff) as u8; + b[58] = (len_high >> 8 & 0xff) as u8; + b[59] = (len_high >> 0 & 0xff) as u8; + b[60] = (len_low >> 24 & 0xff) as u8; + b[61] = (len_low >> 16 & 0xff) as u8; + b[62] = (len_low >> 8 & 0xff) as u8; + b[63] = (len_low >> 0 & 0xff) as u8; + self.compute_block(b); + }); + } + + // This method computes SHA256 on data in input_data, + // updating the internal hash state. `data_buffer` + // contains input data that did or does not fill a block: + // the implementation first fills temp_buffer and computes + // on it, then operates on input_data. If the end of + // input_data does not complete a block then the remainder + // is stored in data_buffer. + fn compute_sha256(&self) { + if let Some(mut data) = self.input_data.take() { + let data_length = data.len(); + self.total_length.set(self.total_length.get() + data_length); + let mut buffered_length = self.buffered_length.get(); + if buffered_length != 0 { + // Copy bytes into the front of the temp buffer and + // compute if it fills. + self.data_buffer.map(|b| { + let copy_len = if data_length + buffered_length >= SHA_BLOCK_LEN_BYTES { + SHA_BLOCK_LEN_BYTES - buffered_length + } else { + data_length + }; + + for i in 0..copy_len { + b[i + buffered_length] = data[i]; + } + data.slice(copy_len..data.len()); + buffered_length += copy_len; + + if buffered_length == SHA_BLOCK_LEN_BYTES { + self.compute_block(b); + buffered_length = 0; + } + }); + } + // Process blocks + while data.len() >= 64 { + self.compute_buffer(&data[0..64]); + data.slice(64..data.len()); + } + // Process tail end of block + if data.len() != 0 { + self.data_buffer.map(|b| { + for i in 0..data.len() { + b[i] = data[i]; + } + buffered_length = data.len(); + // Go to end of data. + data.slice(data.len()..data.len()); + }); + } + self.input_data.set(data); + self.buffered_length.set(buffered_length); + } else { /* do nothing, no data */ + } + } + + fn right_rotate(&self, x: u32, rotate: u32) -> u32 { + (x >> rotate) | (x << (32 - rotate)) + } + + // Note: slice MUST be >= 64 bytes long + fn compute_buffer(&self, buffer: &[u8]) { + // This is clearly inefficient (copy a u8 array into a u32 + // array), but it's better than using unsafe. This + // implementation is not intended to be high performance. + let mut message_schedule: [u32; 64] = [0; 64]; + for i in 0..16 { + let val: u32 = (buffer[i * 4 + 0] as u32) << 24 + | (buffer[i * 4 + 1] as u32) << 16 + | (buffer[i * 4 + 2] as u32) << 8 + | (buffer[i * 4 + 3] as u32); + message_schedule[i] = val; + } + self.perform_sha(&mut message_schedule); + } + + fn compute_block(&self, data: &[u8; 64]) { + self.compute_buffer(data); + } + + fn perform_sha(&self, message_schedule: &mut [u32; 64]) { + // Message schedule + for i in 16..64 { + let mut s0 = self.right_rotate(message_schedule[i - 15], 7); + s0 ^= self.right_rotate(message_schedule[i - 15], 18); + s0 ^= message_schedule[i - 15] >> 3; + let mut s1 = self.right_rotate(message_schedule[i - 2], 17); + s1 ^= self.right_rotate(message_schedule[i - 2], 19); + s1 ^= message_schedule[i - 2] >> 10; + message_schedule[i] = message_schedule[i - 16] + s0 + message_schedule[i - 7] + s1; + } + + // Compression + let mut hashes = self.hash_values.get(); + for i in 0..64 { + let s1 = self.right_rotate(hashes[4], 6) + ^ self.right_rotate(hashes[4], 11) + ^ self.right_rotate(hashes[4], 25); + let ch = (hashes[4] & hashes[5]) ^ ((!hashes[4]) & hashes[6]); + let constant = ROUND_CONSTANTS[i]; + let temp1 = hashes[7] + s1 + ch + constant + message_schedule[i]; + let s0 = self.right_rotate(hashes[0], 2) + ^ self.right_rotate(hashes[0], 13) + ^ self.right_rotate(hashes[0], 22); + let maj = (hashes[0] & hashes[1]) ^ (hashes[0] & hashes[2]) ^ (hashes[1] & hashes[2]); + let temp2 = s0 + maj; + + hashes[7] = hashes[6]; + hashes[6] = hashes[5]; + hashes[5] = hashes[4]; + hashes[4] = hashes[3].wrapping_add(temp1); + hashes[3] = hashes[2]; + hashes[2] = hashes[1]; + hashes[1] = hashes[0]; + hashes[0] = temp1.wrapping_add(temp2); + } + + let mut new_hashes = self.hash_values.get(); + for i in 0..8 { + new_hashes[i] = new_hashes[i].wrapping_add(hashes[i]); + } + self.hash_values.set(new_hashes); + } +} + +impl<'a> DigestData<'a, 32> for Sha256Software<'a> { + fn add_data( + &self, + data: SubSlice<'static, u8>, + ) -> Result<(), (ErrorCode, SubSlice<'static, u8>)> { + if self.busy() { + Err((ErrorCode::BUSY, data)) + } else { + self.state.set(State::Data); + self.deferred_call.set(); + self.input_data.set(SubSliceMutImmut::Immutable(data)); + self.compute_sha256(); + Ok(()) + } + } + + fn add_mut_data( + &self, + data: SubSliceMut<'static, u8>, + ) -> Result<(), (ErrorCode, SubSliceMut<'static, u8>)> { + if self.busy() { + Err((ErrorCode::BUSY, data)) + } else { + self.state.set(State::Data); + self.deferred_call.set(); + self.input_data.set(SubSliceMutImmut::Mutable(data)); + self.compute_sha256(); + Ok(()) + } + } + + fn clear_data(&self) { + self.initialize(); + } + + fn set_data_client(&'a self, _client: &'a (dyn ClientData<32> + 'a)) { + unimplemented!() + } +} + +impl<'a> DigestHash<'a, 32> for Sha256Software<'a> { + fn run( + &'a self, + digest: &'static mut [u8; 32], + ) -> Result<(), (ErrorCode, &'static mut [u8; 32])> { + if self.busy() { + Err((ErrorCode::BUSY, digest)) + } else { + self.state.set(State::Hash); + self.complete_sha256(); + for i in 0..8 { + let val = self.hash_values.get()[i]; + digest[4 * i + 3] = (val >> 0 & 0xff) as u8; + digest[4 * i + 2] = (val >> 8 & 0xff) as u8; + digest[4 * i + 1] = (val >> 16 & 0xff) as u8; + digest[4 * i + 0] = (val >> 24 & 0xff) as u8; + } + self.output_data.set(Some(digest)); + self.deferred_call.set(); + Ok(()) + } + } + + fn set_hash_client(&'a self, _client: &'a (dyn ClientHash<32> + 'a)) { + unimplemented!() + } +} + +impl<'a> DigestVerify<'a, 32> for Sha256Software<'a> { + fn verify( + &'a self, + compare: &'static mut [u8; 32], + ) -> Result<(), (ErrorCode, &'static mut [u8; 32])> { + if self.busy() { + Err((ErrorCode::BUSY, compare)) + } else { + self.state.set(State::Verify); + self.complete_sha256(); + self.output_data.set(Some(compare)); + self.deferred_call.set(); + Ok(()) + } + } + + fn set_verify_client(&'a self, _client: &'a (dyn ClientVerify<32> + 'a)) { + unimplemented!() + } +} + +impl<'a> Digest<'a, 32> for Sha256Software<'a> { + fn set_client(&'a self, client: &'a dyn Client<32>) { + self.client.set(client); + } +} + +impl<'a> DeferredCallClient for Sha256Software<'a> { + fn handle_deferred_call(&self) { + let prior = self.state.get(); + self.state.set(State::Idle); + match prior { + State::Idle => {} + State::Verify => { + // Do the verification here so we don't have to store + // the result across the callback. + let output = self.output_data.replace(None).unwrap(); + let mut pass = true; + for i in 0..8 { + let hashval = self.hash_values.get()[i]; + if output[4 * i + 3] != (hashval >> 0 & 0xff) as u8 + || output[4 * i + 2] != (hashval >> 8 & 0xff) as u8 + || output[4 * i + 1] != (hashval >> 16 & 0xff) as u8 + || output[4 * i + 0] != (hashval >> 24 & 0xff) as u8 + { + pass = false; + break; + } + } + self.state.set(State::Idle); + self.clear_data(); + self.client.map(|c| { + c.verification_done(Ok(pass), output); + }); + } + State::Data => { + // Data already computed in method call + let data = self.input_data.take().unwrap(); + self.state.set(State::Idle); + match data { + SubSliceMutImmut::Mutable(buffer) => { + self.client.map(|client| { + client.add_mut_data_done(Ok(()), buffer); + }); + } + SubSliceMutImmut::Immutable(buffer) => { + self.client.map(|client| { + client.add_data_done(Ok(()), buffer); + }); + } + } + } + State::Hash => { + // Hash already copied in method call. + let output = self.output_data.replace(None).unwrap(); + self.state.set(State::Idle); + self.clear_data(); + self.client.map(|c| { + c.hash_done(Ok(()), output); + }); + } + State::CancelData => { + self.state.set(State::Idle); + self.clear_data(); + let data = self.input_data.take().unwrap(); + match data { + SubSliceMutImmut::Mutable(buffer) => { + self.client.map(|client| { + client.add_mut_data_done(Err(ErrorCode::CANCEL), buffer); + }); + } + SubSliceMutImmut::Immutable(buffer) => { + self.client.map(|client| { + client.add_data_done(Err(ErrorCode::CANCEL), buffer); + }); + } + } + } + State::CancelVerify => { + self.state.set(State::Idle); + self.clear_data(); + let output = self.output_data.replace(None).unwrap(); + self.client.map(|client| { + client.verification_done(Err(ErrorCode::CANCEL), output); + }); + } + State::CancelHash => { + self.state.set(State::Idle); + self.clear_data(); + let output = self.output_data.replace(None).unwrap(); + self.client.map(|client| { + client.hash_done(Err(ErrorCode::CANCEL), output); + }); + } + } + } + + fn register(&'static self) { + self.deferred_call.register(self); + } +} + +impl Sha256 for Sha256Software<'_> { + /// Call before adding data to perform Sha256 + fn set_mode_sha256(&self) -> Result<(), ErrorCode> { + Ok(()) + } +} + +impl<'a> DigestDataHash<'a, 32> for Sha256Software<'a> { + fn set_client(&'a self, _client: &'a dyn ClientDataHash<32>) { + unimplemented!() + } +} + +impl<'a> DigestDataVerify<'a, 32> for Sha256Software<'a> { + fn set_client(&'a self, _client: &'a dyn ClientDataVerify<32>) { + unimplemented!() + } +} diff --git a/capsules/src/sht3x.rs b/capsules/extra/src/sht3x.rs similarity index 80% rename from capsules/src/sht3x.rs rename to capsules/extra/src/sht3x.rs index 5c2764686a..acc2c3e514 100644 --- a/capsules/src/sht3x.rs +++ b/capsules/extra/src/sht3x.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for SHT3x Temperature and Humidity Sensor //! //! Author: Cosmin Daniel Radu @@ -55,20 +59,20 @@ fn crc8(data: &[u8]) -> u8 { let mut crc = 0xff; for x in 0..data.len() { - crc ^= data[x as usize] as u8; + crc ^= data[x]; for _i in 0..8 { if (crc & 0x80) != 0 { crc = crc << 1 ^ polynomial; } else { - crc = crc << 1; + crc <<= 1; } } } crc } -pub struct SHT3x<'a, A: Alarm<'a>> { - i2c: &'a dyn i2c::I2CDevice, +pub struct SHT3x<'a, A: Alarm<'a>, I: i2c::I2CDevice> { + i2c: &'a I, humidity_client: OptionalCell<&'a dyn kernel::hil::sensors::HumidityClient>, temperature_client: OptionalCell<&'a dyn kernel::hil::sensors::TemperatureClient>, state: Cell, @@ -78,26 +82,22 @@ pub struct SHT3x<'a, A: Alarm<'a>> { alarm: &'a A, } -impl<'a, A: Alarm<'a>> SHT3x<'a, A> { - pub fn new( - i2c: &'a dyn i2c::I2CDevice, - buffer: &'static mut [u8], - alarm: &'a A, - ) -> SHT3x<'a, A> { +impl<'a, A: Alarm<'a>, I: i2c::I2CDevice> SHT3x<'a, A, I> { + pub fn new(i2c: &'a I, buffer: &'static mut [u8], alarm: &'a A) -> SHT3x<'a, A, I> { SHT3x { - i2c: i2c, + i2c, humidity_client: OptionalCell::empty(), temperature_client: OptionalCell::empty(), state: Cell::new(State::Idle), buffer: TakeCell::new(buffer), read_temp: Cell::new(false), read_hum: Cell::new(false), - alarm: alarm, + alarm, } } fn read_humidity(&self) -> Result<(), ErrorCode> { - if self.read_hum.get() == true { + if self.read_hum.get() { Err(ErrorCode::BUSY) } else { if self.state.get() == State::Idle { @@ -111,7 +111,7 @@ impl<'a, A: Alarm<'a>> SHT3x<'a, A> { } fn read_temperature(&self) -> Result<(), ErrorCode> { - if self.read_temp.get() == true { + if self.read_temp.get() { Err(ErrorCode::BUSY) } else { if self.state.get() == State::Idle { @@ -143,7 +143,7 @@ impl<'a, A: Alarm<'a>> SHT3x<'a, A> { } } -impl<'a, A: Alarm<'a>> time::AlarmClient for SHT3x<'a, A> { +impl<'a, A: Alarm<'a>, I: i2c::I2CDevice> time::AlarmClient for SHT3x<'a, A, I> { fn alarm(&self) { let state = self.state.get(); match state { @@ -164,7 +164,7 @@ impl<'a, A: Alarm<'a>> time::AlarmClient for SHT3x<'a, A> { } } -impl<'a, A: Alarm<'a>> i2c::I2CClient for SHT3x<'a, A> { +impl<'a, A: Alarm<'a>, I: i2c::I2CDevice> i2c::I2CClient for SHT3x<'a, A, I> { fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) { match status { Ok(()) => { @@ -172,25 +172,25 @@ impl<'a, A: Alarm<'a>> i2c::I2CClient for SHT3x<'a, A> { match state { State::ReadData => { - if self.read_temp.get() == true { + if self.read_temp.get() { self.read_temp.set(false); if crc8(&buffer[0..2]) == buffer[2] { let mut stemp = buffer[0] as u32; - stemp = stemp << 8; - stemp = stemp | buffer[1] as u32; - stemp = ((4375 * stemp) >> 14) - 4500; - self.temperature_client - .map(|cb| cb.callback(stemp as usize)); + stemp <<= 8; + stemp |= buffer[1] as u32; + let stemp = ((4375 * stemp) >> 14) as i32 - 4500; + self.temperature_client.map(|cb| cb.callback(Ok(stemp))); } else { - self.temperature_client.map(|cb| cb.callback(usize::MAX)); + self.temperature_client + .map(|cb| cb.callback(Err(ErrorCode::FAIL))); } } - if self.read_hum.get() == true { + if self.read_hum.get() { self.read_hum.set(false); if crc8(&buffer[3..5]) == buffer[5] { let mut shum = buffer[3] as u32; - shum = shum << 8; - shum = shum | buffer[4] as u32; + shum <<= 8; + shum |= buffer[4] as u32; shum = (625 * shum) >> 12; self.humidity_client.map(|cb| cb.callback(shum as usize)); } else { @@ -208,14 +208,15 @@ impl<'a, A: Alarm<'a>> i2c::I2CClient for SHT3x<'a, A> { _ => {} } } - _ => { + Err(i2c_err) => { self.buffer.replace(buffer); self.i2c.disable(); - if self.read_temp.get() == true { + if self.read_temp.get() { self.read_temp.set(false); - self.temperature_client.map(|cb| cb.callback(usize::MAX)); + self.temperature_client + .map(|cb| cb.callback(Err(i2c_err.into()))); } - if self.read_hum.get() == true { + if self.read_hum.get() { self.read_hum.set(false); self.humidity_client.map(|cb| cb.callback(usize::MAX)); } @@ -224,7 +225,9 @@ impl<'a, A: Alarm<'a>> i2c::I2CClient for SHT3x<'a, A> { } } -impl<'a, A: Alarm<'a>> kernel::hil::sensors::HumidityDriver<'a> for SHT3x<'a, A> { +impl<'a, A: Alarm<'a>, I: i2c::I2CDevice> kernel::hil::sensors::HumidityDriver<'a> + for SHT3x<'a, A, I> +{ fn set_client(&self, client: &'a dyn kernel::hil::sensors::HumidityClient) { self.humidity_client.set(client); } @@ -234,7 +237,9 @@ impl<'a, A: Alarm<'a>> kernel::hil::sensors::HumidityDriver<'a> for SHT3x<'a, A> } } -impl<'a, A: Alarm<'a>> kernel::hil::sensors::TemperatureDriver<'a> for SHT3x<'a, A> { +impl<'a, A: Alarm<'a>, I: i2c::I2CDevice> kernel::hil::sensors::TemperatureDriver<'a> + for SHT3x<'a, A, I> +{ fn set_client(&self, client: &'a dyn kernel::hil::sensors::TemperatureClient) { self.temperature_client.set(client); } diff --git a/capsules/extra/src/sht4x.rs b/capsules/extra/src/sht4x.rs new file mode 100644 index 0000000000..3590895bb9 --- /dev/null +++ b/capsules/extra/src/sht4x.rs @@ -0,0 +1,264 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Driver for SHT4x Temperature and Humidity Sensor + +use core::cell::Cell; +use enum_primitive::cast::FromPrimitive; +use enum_primitive::enum_from_primitive; +use kernel::hil::i2c; +use kernel::hil::time::{self, Alarm, ConvertTicks}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::ErrorCode; + +pub static BASE_ADDR: u8 = 0x44; + +enum_from_primitive! { + enum Registers { + /// sht4x has no Clock Stretching + /// Measurement High Repeatability + MEASHIGHREP = 0xFD, + /// Measurement Medium Repeatability + MEASMEDREP = 0xF6, + /// Measurement Low Repeatability + MEASLOWREP = 0xE0, + /// Read Serial Number + READSERIALNUM = 0x89, + /// Soft Reset + SOFTRESET = 0x94, + /// Activate heater with 200mW for 1s + HEATER200MW1S = 0x39, + /// Activate heater with 200mW for 0.1s + HEATER200MW01S = 0x32, + /// Activate heater with 110mW for 1s + HEATER110MW1S = 0x2F, + /// Activate heater with 110mW for 0.1s + HEATER110MW01S = 0x24, + /// Activate heater with 20mW for 1s + HEATER20MW1S = 0x1E, + /// Activate heater with 20mW for 0.1s + HEATER20MW01S = 0x15, + } +} + +#[derive(Clone, Copy, PartialEq)] +enum State { + Idle, + Read, + ReadData, +} + +fn crc8(data: &[u8]) -> u8 { + let polynomial = 0x31; + let mut crc = 0xff; + + for x in 0..data.len() { + crc ^= data[x]; + for _i in 0..8 { + if (crc & 0x80) != 0 { + crc = crc << 1 ^ polynomial; + } else { + crc <<= 1; + } + } + } + crc +} + +pub struct SHT4x<'a, A: Alarm<'a>, I: i2c::I2CDevice> { + i2c: &'a I, + humidity_client: OptionalCell<&'a dyn kernel::hil::sensors::HumidityClient>, + temperature_client: OptionalCell<&'a dyn kernel::hil::sensors::TemperatureClient>, + state: Cell, + buffer: TakeCell<'static, [u8]>, + read_temp: Cell, + read_hum: Cell, + alarm: &'a A, +} + +impl<'a, A: Alarm<'a>, I: i2c::I2CDevice> SHT4x<'a, A, I> { + pub fn new(i2c: &'a I, buffer: &'static mut [u8], alarm: &'a A) -> SHT4x<'a, A, I> { + SHT4x { + i2c, + humidity_client: OptionalCell::empty(), + temperature_client: OptionalCell::empty(), + state: Cell::new(State::Idle), + buffer: TakeCell::new(buffer), + read_temp: Cell::new(false), + read_hum: Cell::new(false), + alarm, + } + } + + fn read_humidity(&self) -> Result<(), ErrorCode> { + if self.read_hum.get() { + Err(ErrorCode::BUSY) + } else { + if self.state.get() == State::Idle { + let result = self.read_temp_hum(); + if result.is_ok() { + self.read_hum.set(true); + } + result + } else { + self.read_hum.set(true); + Ok(()) + } + } + } + + fn read_temperature(&self) -> Result<(), ErrorCode> { + if self.read_temp.get() { + Err(ErrorCode::BUSY) + } else { + if self.state.get() == State::Idle { + let result = self.read_temp_hum(); + if result.is_ok() { + self.read_temp.set(true); + } + result + } else { + self.read_temp.set(true); + Ok(()) + } + } + } + + fn read_temp_hum(&self) -> Result<(), ErrorCode> { + self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| { + self.state.set(State::Read); + self.i2c.enable(); + + buffer[0] = Registers::MEASHIGHREP as u8; + + let _res = self.i2c.write(buffer, 1); + match _res { + Ok(()) => Ok(()), + Err((error, data)) => { + self.buffer.replace(data); + self.state.set(State::Idle); + self.i2c.disable(); + Err(error.into()) + } + } + }) + } +} + +impl<'a, A: Alarm<'a>, I: i2c::I2CDevice> time::AlarmClient for SHT4x<'a, A, I> { + fn alarm(&self) { + let state = self.state.get(); + match state { + State::Read => { + self.state.set(State::ReadData); + self.buffer.take().map(|buffer| { + let _res = self.i2c.read(buffer, 6); + }); + } + _ => { + // This should never happen + panic!("SHT4x Invalid alarm!"); + } + } + } +} + +impl<'a, A: Alarm<'a>, I: i2c::I2CDevice> i2c::I2CClient for SHT4x<'a, A, I> { + fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), i2c::Error>) { + match status { + Ok(()) => { + let state = self.state.get(); + + match state { + State::ReadData => { + let read_temp_res = if self.read_temp.get() { + self.read_temp.set(false); + if crc8(&buffer[0..2]) == buffer[2] { + let mut stemp = buffer[0] as u32; + stemp <<= 8; + stemp |= buffer[1] as u32; + let stemp = ((4375 * stemp) >> 14) as i32 - 4500; + Some(Ok(stemp)) + } else { + Some(Err(ErrorCode::FAIL)) + } + } else { + None + }; + + let read_hum_res = if self.read_hum.get() { + self.read_hum.set(false); + if crc8(&buffer[3..5]) == buffer[5] { + let mut shum = buffer[3] as u32; + shum <<= 8; + shum |= buffer[4] as u32; + shum = (625 * shum) >> 12; + Some(shum as usize) + } else { + Some(usize::MAX) + } + } else { + None + }; + + self.buffer.replace(buffer); + self.state.set(State::Idle); + + read_temp_res.map(|res| { + self.temperature_client.map(|cb| cb.callback(res)); + }); + + read_hum_res.map(|res| { + self.humidity_client.map(|cb| cb.callback(res)); + }); + } + State::Read => { + self.buffer.replace(buffer); + let interval = self.alarm.ticks_from_ms(20); + self.alarm.set_alarm(self.alarm.now(), interval); + } + _ => {} + } + } + Err(i2c_err) => { + self.buffer.replace(buffer); + self.i2c.disable(); + self.state.set(State::Idle); + if self.read_temp.get() { + self.read_temp.set(false); + self.temperature_client + .map(|cb| cb.callback(Err(i2c_err.into()))); + } + if self.read_hum.get() { + self.read_hum.set(false); + self.humidity_client.map(|cb| cb.callback(usize::MAX)); + } + } + } + } +} + +impl<'a, A: Alarm<'a>, I: i2c::I2CDevice> kernel::hil::sensors::HumidityDriver<'a> + for SHT4x<'a, A, I> +{ + fn set_client(&self, client: &'a dyn kernel::hil::sensors::HumidityClient) { + self.humidity_client.set(client); + } + + fn read_humidity(&self) -> Result<(), ErrorCode> { + self.read_humidity() + } +} + +impl<'a, A: Alarm<'a>, I: i2c::I2CDevice> kernel::hil::sensors::TemperatureDriver<'a> + for SHT4x<'a, A, I> +{ + fn set_client(&self, client: &'a dyn kernel::hil::sensors::TemperatureClient) { + self.temperature_client.set(client); + } + + fn read_temperature(&self) -> Result<(), ErrorCode> { + self.read_temperature() + } +} diff --git a/capsules/src/si7021.rs b/capsules/extra/src/si7021.rs similarity index 90% rename from capsules/src/si7021.rs rename to capsules/extra/src/si7021.rs index 17b97cf9ee..b8daa1e050 100644 --- a/capsules/src/si7021.rs +++ b/capsules/extra/src/si7021.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for the Silicon Labs SI7021 temperature/humidity sensor. //! //! @@ -17,7 +21,7 @@ //! Usage //! ----- //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! # use capsules::virtual_alarm::VirtualMuxAlarm; //! @@ -44,9 +48,6 @@ use kernel::hil::time::{self, ConvertTicks}; use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::ErrorCode; -// Buffer to use for I2C messages -pub static mut BUFFER: [u8; 14] = [0; 14]; - #[allow(dead_code)] enum Registers { MeasRelativeHumidityHoldMode = 0xe5, @@ -96,8 +97,8 @@ enum OnDeck { Humidity, } -pub struct SI7021<'a, A: time::Alarm<'a>> { - i2c: &'a dyn i2c::I2CDevice, +pub struct SI7021<'a, A: time::Alarm<'a>, I: i2c::I2CDevice> { + i2c: &'a I, alarm: &'a A, temp_callback: OptionalCell<&'a dyn kernel::hil::sensors::TemperatureClient>, humidity_callback: OptionalCell<&'a dyn kernel::hil::sensors::HumidityClient>, @@ -106,16 +107,12 @@ pub struct SI7021<'a, A: time::Alarm<'a>> { buffer: TakeCell<'static, [u8]>, } -impl<'a, A: time::Alarm<'a>> SI7021<'a, A> { - pub fn new( - i2c: &'a dyn i2c::I2CDevice, - alarm: &'a A, - buffer: &'static mut [u8], - ) -> SI7021<'a, A> { +impl<'a, A: time::Alarm<'a>, I: i2c::I2CDevice> SI7021<'a, A, I> { + pub fn new(i2c: &'a I, alarm: &'a A, buffer: &'static mut [u8]) -> SI7021<'a, A, I> { // setup and return struct SI7021 { - i2c: i2c, - alarm: alarm, + i2c, + alarm, temp_callback: OptionalCell::empty(), humidity_callback: OptionalCell::empty(), state: Cell::new(State::Idle), @@ -153,7 +150,7 @@ impl<'a, A: time::Alarm<'a>> SI7021<'a, A> { } } -impl<'a, A: time::Alarm<'a>> i2c::I2CClient for SI7021<'a, A> { +impl<'a, A: time::Alarm<'a>, I: i2c::I2CDevice> i2c::I2CClient for SI7021<'a, A, I> { fn command_complete(&self, buffer: &'static mut [u8], _status: Result<(), i2c::Error>) { match self.state.get() { State::SelectElectronicId1 => { @@ -204,10 +201,10 @@ impl<'a, A: time::Alarm<'a>> i2c::I2CClient for SI7021<'a, A> { } State::GotTempMeasurement => { // Temperature in hundredths of degrees centigrade - let temp_raw = (((buffer[0] as u32) << 8) | (buffer[1] as u32)) as u32; - let temp = (((temp_raw * 17572) / 65536) - 4685) as i16; + let temp_raw = ((buffer[0] as u32) << 8) | (buffer[1] as u32); + let temp = ((temp_raw * 17572) / 65536) as i32 - 4685; - self.temp_callback.map(|cb| cb.callback(temp as usize)); + self.temp_callback.map(|cb| cb.callback(Ok(temp))); match self.on_deck.get() { OnDeck::Humidity => { @@ -224,7 +221,7 @@ impl<'a, A: time::Alarm<'a>> i2c::I2CClient for SI7021<'a, A> { } State::GotRhMeasurement => { // Humidity in hundredths of percent - let humidity_raw = (((buffer[0] as u32) << 8) | (buffer[1] as u32)) as u32; + let humidity_raw = ((buffer[0] as u32) << 8) | (buffer[1] as u32); let humidity = (((humidity_raw * 125 * 100) / 65536) - 600) as u16; self.humidity_callback @@ -247,7 +244,9 @@ impl<'a, A: time::Alarm<'a>> i2c::I2CClient for SI7021<'a, A> { } } -impl<'a, A: time::Alarm<'a>> kernel::hil::sensors::TemperatureDriver<'a> for SI7021<'a, A> { +impl<'a, A: time::Alarm<'a>, I: i2c::I2CDevice> kernel::hil::sensors::TemperatureDriver<'a> + for SI7021<'a, A, I> +{ fn read_temperature(&self) -> Result<(), ErrorCode> { // This chip handles both humidity and temperature measurements. We can // only start a new measurement if the chip is idle. If it isn't then we @@ -280,7 +279,9 @@ impl<'a, A: time::Alarm<'a>> kernel::hil::sensors::TemperatureDriver<'a> for SI7 } } -impl<'a, A: time::Alarm<'a>> kernel::hil::sensors::HumidityDriver<'a> for SI7021<'a, A> { +impl<'a, A: time::Alarm<'a>, I: i2c::I2CDevice> kernel::hil::sensors::HumidityDriver<'a> + for SI7021<'a, A, I> +{ fn read_humidity(&self) -> Result<(), ErrorCode> { // This chip handles both humidity and temperature measurements. We can // only start a new measurement if the chip is idle. If it isn't then we @@ -314,7 +315,7 @@ impl<'a, A: time::Alarm<'a>> kernel::hil::sensors::HumidityDriver<'a> for SI7021 } } -impl<'a, A: time::Alarm<'a>> time::AlarmClient for SI7021<'a, A> { +impl<'a, A: time::Alarm<'a>, I: i2c::I2CDevice> time::AlarmClient for SI7021<'a, A, I> { fn alarm(&self) { self.buffer.take().map(|buffer| { // turn on i2c to send commands diff --git a/capsules/extra/src/sip_hash.rs b/capsules/extra/src/sip_hash.rs new file mode 100644 index 0000000000..8d35b11194 --- /dev/null +++ b/capsules/extra/src/sip_hash.rs @@ -0,0 +1,388 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Tock SipHash capsule. +//! +//! This is a async implementation of the SipHash. +//! +//! This capsule was originally written to be used as part of Tock's +//! key/value store. SipHash was used as it is generally fast, while also +//! being resilient against DOS attacks from userspace +//! (unlike ). +//! +//! Read for more +//! details on SipHash. +//! +//! The implementation is based on the Rust implementation from +//! rust-core, available here: +//! +//! Copyright 2012-2016 The Rust Project Developers. +//! Copyright 2016-2021 Frank Denis. +//! Copyright 2021 Western Digital +//! +//! Licensed under the Apache License, Version 2.0 LICENSE-APACHE or +//! or the MIT license +//! LICENSE-MIT or , at your +//! option. + +use core::cell::Cell; +use core::{cmp, mem}; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; +use kernel::hil::hasher::{Client, Hasher, SipHash}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::utilities::leasable_buffer::SubSlice; +use kernel::utilities::leasable_buffer::SubSliceMut; +use kernel::utilities::leasable_buffer::SubSliceMutImmut; +use kernel::ErrorCode; + +pub struct SipHasher24<'a> { + client: OptionalCell<&'a dyn Client<8>>, + + hasher: Cell, + + add_data_deferred_call: Cell, + complete_deferred_call: Cell, + deferred_call: DeferredCall, + + data_buffer: Cell>>, + out_buffer: TakeCell<'static, [u8; 8]>, +} + +#[derive(Debug, Clone, Copy)] +struct SipHasher { + k0: u64, + k1: u64, + length: usize, // how many bytes we've processed + state: State, // hash State + tail: u64, // unprocessed bytes le + ntail: usize, // how many bytes in tail are valid +} + +#[derive(Debug, Clone, Copy)] +struct State { + // v0, v2 and v1, v3 show up in pairs in the algorithm, + // and simd implementations of SipHash will use vectors + // of v02 and v13. By placing them in this order in the struct, + // the compiler can pick up on just a few simd optimizations by itself. + v0: u64, + v2: u64, + v1: u64, + v3: u64, +} + +impl<'a> SipHasher24<'a> { + pub fn new() -> Self { + let hasher = SipHasher { + k0: 0, + k1: 0, + length: 0, + state: State { + v0: 0x736f6d6570736575, + v1: 0x646f72616e646f6d, + v2: 0x6c7967656e657261, + v3: 0x7465646279746573, + }, + ntail: 0, + tail: 0, + }; + + Self { + client: OptionalCell::empty(), + hasher: Cell::new(hasher), + add_data_deferred_call: Cell::new(false), + complete_deferred_call: Cell::new(false), + deferred_call: DeferredCall::new(), + data_buffer: Cell::new(None), + out_buffer: TakeCell::empty(), + } + } + + pub fn new_with_keys(k0: u64, k1: u64) -> Self { + let hasher = SipHasher { + k0, + k1, + length: 0, + state: State { + v0: 0x736f6d6570736575, + v1: 0x646f72616e646f6d, + v2: 0x6c7967656e657261, + v3: 0x7465646279746573, + }, + ntail: 0, + tail: 0, + }; + + Self { + client: OptionalCell::empty(), + hasher: Cell::new(hasher), + add_data_deferred_call: Cell::new(false), + complete_deferred_call: Cell::new(false), + deferred_call: DeferredCall::new(), + data_buffer: Cell::new(None), + out_buffer: TakeCell::empty(), + } + } +} + +macro_rules! compress { + ($state:expr) => {{ + compress!($state.v0, $state.v1, $state.v2, $state.v3) + }}; + ($v0:expr, $v1:expr, $v2:expr, $v3:expr) => {{ + $v0 = $v0.wrapping_add($v1); + $v1 = $v1.rotate_left(13); + $v1 ^= $v0; + $v0 = $v0.rotate_left(32); + $v2 = $v2.wrapping_add($v3); + $v3 = $v3.rotate_left(16); + $v3 ^= $v2; + $v0 = $v0.wrapping_add($v3); + $v3 = $v3.rotate_left(21); + $v3 ^= $v0; + $v2 = $v2.wrapping_add($v1); + $v1 = $v1.rotate_left(17); + $v1 ^= $v2; + $v2 = $v2.rotate_left(32); + }}; +} + +fn read_le_u64(input: &[u8]) -> u64 { + let mut eight_buf: [u8; 8] = [0; 8]; + for i in 0..8 { + eight_buf[i] = *input.get(i).unwrap_or(&0); + } + u64::from_le_bytes(eight_buf) +} + +fn read_le_u16(input: &[u8]) -> u16 { + let (int_bytes, _rest) = input.split_at(mem::size_of::()); + u16::from_le_bytes(int_bytes.try_into().unwrap()) +} + +#[inline] +fn u8to64_le(buf: &[u8], start: usize, len: usize) -> u64 { + debug_assert!(len < 8); + let mut i = 0; // current byte index (from LSB) in the output u64 + let mut out = 0; + if i + 3 < len { + out = read_le_u64(&buf[start + i..]); + i += 4; + } + if i + 1 < len { + out |= (read_le_u16(&buf[start + i..]) as u64) << (i * 8); + i += 2 + } + if i < len { + out |= (buf[start + i] as u64) << (i * 8); + i += 1; + } + debug_assert_eq!(i, len); + out +} + +impl<'a> Hasher<'a, 8> for SipHasher24<'a> { + fn set_client(&'a self, client: &'a dyn Client<8>) { + self.client.set(client); + } + + fn add_data( + &self, + data: SubSlice<'static, u8>, + ) -> Result)> { + let length = data.len(); + let mut hasher = self.hasher.get(); + + hasher.length += length; + + let mut needed = 0; + + if hasher.ntail != 0 { + needed = 8 - hasher.ntail; + hasher.tail |= + u8to64_le(data.as_slice(), 0, cmp::min(length, needed)) << (8 * hasher.ntail); + if length < needed { + hasher.ntail += length; + return Ok(length); + } else { + hasher.state.v3 ^= hasher.tail; + compress!(&mut hasher.state); + compress!(&mut hasher.state); + hasher.state.v0 ^= hasher.tail; + hasher.ntail = 0; + } + } + + // Buffered tail is now flushed, process new input. + let len = length - needed; + let left = len & 0x7; + + let mut i = needed; + while i < len - left { + let mi = read_le_u64(&data[i..]); + + hasher.state.v3 ^= mi; + compress!(&mut hasher.state); + compress!(&mut hasher.state); + hasher.state.v0 ^= mi; + + i += 8; + } + + hasher.tail = u8to64_le(data.as_slice(), i, left); + hasher.ntail = left; + + self.hasher.set(hasher); + self.data_buffer + .set(Some(SubSliceMutImmut::Immutable(data))); + + self.add_data_deferred_call.set(true); + self.deferred_call.set(); + + Ok(length) + } + + fn add_mut_data( + &self, + mut data: SubSliceMut<'static, u8>, + ) -> Result)> { + let length = data.len(); + let mut hasher = self.hasher.get(); + + hasher.length += length; + + let mut needed = 0; + + if hasher.ntail != 0 { + needed = 8 - hasher.ntail; + hasher.tail |= + u8to64_le(data.as_slice(), 0, cmp::min(length, needed)) << (8 * hasher.ntail); + if length < needed { + hasher.ntail += length; + return Ok(length); + } else { + hasher.state.v3 ^= hasher.tail; + compress!(&mut hasher.state); + compress!(&mut hasher.state); + hasher.state.v0 ^= hasher.tail; + hasher.ntail = 0; + } + } + + // Buffered tail is now flushed, process new input. + let len = length - needed; + let left = len & 0x7; + + let mut i = needed; + while i < len - left { + let mi = read_le_u64(&data[i..]); + + hasher.state.v3 ^= mi; + compress!(&mut hasher.state); + compress!(&mut hasher.state); + hasher.state.v0 ^= mi; + + i += 8; + } + + hasher.tail = u8to64_le(data.as_slice(), i, left); + hasher.ntail = left; + + self.hasher.set(hasher); + self.data_buffer.set(Some(SubSliceMutImmut::Mutable(data))); + + self.add_data_deferred_call.set(true); + self.deferred_call.set(); + + Ok(length) + } + + fn run( + &'a self, + digest: &'static mut [u8; 8], + ) -> Result<(), (ErrorCode, &'static mut [u8; 8])> { + let mut hasher = self.hasher.get(); + + let b: u64 = ((hasher.length as u64 & 0xff) << 56) | hasher.tail; + + hasher.state.v3 ^= b; + compress!(&mut hasher.state); + compress!(&mut hasher.state); + hasher.state.v0 ^= b; + + hasher.state.v2 ^= 0xff; + compress!(&mut hasher.state); + compress!(&mut hasher.state); + compress!(&mut hasher.state); + compress!(&mut hasher.state); + + self.hasher.set(hasher); + self.out_buffer.replace(digest); + + self.complete_deferred_call.set(true); + self.deferred_call.set(); + + Ok(()) + } + + fn clear_data(&self) { + let mut hasher = self.hasher.get(); + + hasher.length = 0; + hasher.state.v0 = hasher.k0 ^ 0x736f6d6570736575; + hasher.state.v1 = hasher.k1 ^ 0x646f72616e646f6d; + hasher.state.v2 = hasher.k0 ^ 0x6c7967656e657261; + hasher.state.v3 = hasher.k1 ^ 0x7465646279746573; + hasher.ntail = 0; + + self.hasher.set(hasher); + } +} + +impl<'a> SipHash for SipHasher24<'a> { + fn set_keys(&self, k0: u64, k1: u64) -> Result<(), ErrorCode> { + let mut hasher = self.hasher.get(); + + hasher.k0 = k0; + hasher.k1 = k1; + + self.hasher.set(hasher); + self.clear_data(); + + Ok(()) + } +} + +impl<'a> DeferredCallClient for SipHasher24<'a> { + fn handle_deferred_call(&self) { + if self.add_data_deferred_call.get() { + self.add_data_deferred_call.set(false); + + self.client.map(|client| { + self.data_buffer.take().map(|buffer| match buffer { + SubSliceMutImmut::Immutable(b) => client.add_data_done(Ok(()), b), + SubSliceMutImmut::Mutable(b) => client.add_mut_data_done(Ok(()), b), + }); + }); + } + + if self.complete_deferred_call.get() { + self.complete_deferred_call.set(false); + + self.client.map(|client| { + self.out_buffer.take().map(|buffer| { + let state = self.hasher.get().state; + + let result = state.v0 ^ state.v1 ^ state.v2 ^ state.v3; + buffer.copy_from_slice(&result.to_le_bytes()); + + client.hash_done(Ok(()), buffer); + }); + }); + } + } + + fn register(&'static self) { + self.deferred_call.register(self); + } +} diff --git a/capsules/src/sound_pressure.rs b/capsules/extra/src/sound_pressure.rs similarity index 90% rename from capsules/src/sound_pressure.rs rename to capsules/extra/src/sound_pressure.rs index 7a012ec3bc..f798c28e25 100644 --- a/capsules/src/sound_pressure.rs +++ b/capsules/extra/src/sound_pressure.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides userspace with access to sound_pressure sensors. //! //! Userspace Interface @@ -38,7 +42,7 @@ //! //! You need a device that provides the `hil::sensors::SoundPressure` trait. //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); @@ -53,14 +57,13 @@ //! ``` use core::cell::Cell; -use core::convert::TryFrom; use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; use kernel::hil; use kernel::syscall::{CommandReturn, SyscallDriver}; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::SoundPressure as usize; #[derive(Default)] @@ -81,15 +84,15 @@ impl<'a> SoundPressureSensor<'a> { grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, ) -> SoundPressureSensor<'a> { SoundPressureSensor { - driver: driver, + driver, apps: grant, busy: Cell::new(false), } } - fn enqueue_command(&self, appid: ProcessId) -> CommandReturn { + fn enqueue_command(&self, processid: ProcessId) -> CommandReturn { self.apps - .enter(appid, |app, _| { + .enter(processid, |app, _| { if !self.busy.get() { app.subscribed = true; self.busy.set(true); @@ -140,19 +143,25 @@ impl hil::sensors::SoundPressureClient for SoundPressureSensor<'_> { } impl SyscallDriver for SoundPressureSensor<'_> { - fn command(&self, command_num: usize, _: usize, _: usize, appid: ProcessId) -> CommandReturn { + fn command( + &self, + command_num: usize, + _: usize, + _: usize, + processid: ProcessId, + ) -> CommandReturn { match command_num { // check whether the driver exists!! 0 => CommandReturn::success(), // read sound_pressure - 1 => self.enqueue_command(appid), + 1 => self.enqueue_command(processid), // enable 2 => { let res = self .apps - .enter(appid, |app, _| { + .enter(processid, |app, _| { app.enable = true; CommandReturn::success() }) @@ -169,7 +178,7 @@ impl SyscallDriver for SoundPressureSensor<'_> { 3 => { let res = self .apps - .enter(appid, |app, _| { + .enter(processid, |app, _| { app.enable = false; CommandReturn::success() }) diff --git a/capsules/extra/src/ssd1306.rs b/capsules/extra/src/ssd1306.rs new file mode 100644 index 0000000000..4e2517a0a4 --- /dev/null +++ b/capsules/extra/src/ssd1306.rs @@ -0,0 +1,564 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! SSD1306/SSD1315 OLED Screen + +use core::cell::Cell; +use kernel::hil; +use kernel::utilities::cells::{MapCell, OptionalCell, TakeCell}; +use kernel::utilities::leasable_buffer::SubSliceMut; +use kernel::ErrorCode; + +pub const BUFFER_SIZE: usize = 1032; + +const WIDTH: usize = 128; +const HEIGHT: usize = 64; + +#[derive(Copy, Clone, PartialEq)] +#[repr(usize)] +pub enum Command { + // Charge Pump Commands + /// Charge Pump Setting. + SetChargePump { enable: bool }, + + // Fundamental Commands + /// SetContrastControl. Double byte command to select 1 out of 256 contrast + /// steps. Contrast increases as the value increases. + SetContrast { contrast: u8 }, + /// Entire Display On. + EntireDisplayOn { ignore_ram: bool }, + /// Set Normal Display. + SetDisplayInvert { inverse: bool }, + /// Set Display Off. + SetDisplayOnOff { on: bool }, + + // Scrolling Commands + /// Continuous Horizontal Scroll. Right or Left Horizontal Scroll. + ContinuousHorizontalScroll { + left: bool, + page_start: u8, + interval: u8, + page_end: u8, + }, + /// Continuous Vertical and Horizontal Scroll. Vertical and Right Horizontal + /// Scroll. + ContinuousVerticalHorizontalScroll { + left: bool, + page_start: u8, + interval: u8, + page_end: u8, + vertical_offset: u8, + }, + /// Deactivate Scroll. Stop scrolling that is configured by scroll commands. + DeactivateScroll = 0x2e, + /// Activate Scroll. Start scrolling that is configured by scroll commands. + ActivateScroll = 0x2f, + /// Set Vertical Scroll Area. Set number of rows in top fixed area. The + /// number of rows in top fixed area is referenced to the top of the GDDRAM + /// (i.e. row 0). + SetVerticalScrollArea { rows_fixed: u8, rows_scroll: u8 }, + + // Addressing Setting Commands + /// Set Lower Column Start Address for Page Addressing Mode. + /// + /// Set the lower nibble of the column start address register for Page + /// Addressing Mode using `X[3:0]` as data bits. The initial display line + /// register is reset to 0000b after RESET. + SetLowerColumnStartAddress { address: u8 }, + /// Set Higher Column Start Address for Page Addressing Mode. + /// + /// Set the higher nibble of the column start address register for Page + /// Addressing Mode using `X[3:0]` as data bits. The initial display line + /// register is reset to 0000b after RESET. + SetHigherColumnStartAddress { address: u8 }, + /// Set Memory Addressing Mode. + SetMemoryAddressingMode { mode: u8 }, + /// Set Column Address. Setup column start and end address. + SetColumnAddress { column_start: u8, column_end: u8 }, + /// Set Page Address. Setup page start and end address. + SetPageAddress { page_start: u8, page_end: u8 }, + /// Set Page Start Address for Page Addressing Mode. Set GDDRAM Page Start + /// Address (PAGE0~PAGE7) for Page Addressing Mode using `X[2:0]`. + SetPageStartAddress { address: u8 }, + + // Hardware Configuration Commands + /// Set Display Start Line. Set display RAM display start line register from + /// 0-63 using `X[5:0]`. + SetDisplayStartLine { line: u8 }, + /// Set Segment Remap. + SetSegmentRemap { reverse: bool }, + /// Set Multiplex Ratio. + SetMultiplexRatio { ratio: u8 }, + /// Set COM Output Scan Direction. + SetComScanDirection { decrement: bool }, + /// Set Display Offset. Set vertical shift by COM from 0-63. + SetDisplayOffset { vertical_shift: u8 } = 0xd3, + /// Set COM Pins Hardware Configuration + SetComPins { alternative: bool, enable_com: bool }, + + // Timing & Driving Scheme Setting Commands. + /// Set Display Clock Divide Ratio/Oscillator Frequency. + SetDisplayClockDivide { + divide_ratio: u8, + oscillator_frequency: u8, + }, + /// Set Pre-charge Period. + SetPrechargePeriod { phase1: u8, phase2: u8 }, + /// Set VCOMH Deselect Level. + SetVcomDeselect { level: u8 }, +} + +impl Command { + pub fn encode(self, buffer: &mut SubSliceMut<'static, u8>) { + let take = match self { + Self::SetChargePump { enable } => { + buffer[0] = 0x8D; + buffer[1] = 0x10 | ((enable as u8) << 2); + 2 + } + Self::SetContrast { contrast } => { + buffer[0] = 0x81; + buffer[1] = contrast; + 2 + } + Self::EntireDisplayOn { ignore_ram } => { + buffer[0] = 0xa4 | (ignore_ram as u8); + 1 + } + Self::SetDisplayInvert { inverse } => { + buffer[0] = 0xa6 | (inverse as u8); + 1 + } + Self::SetDisplayOnOff { on } => { + buffer[0] = 0xae | (on as u8); + 1 + } + Self::ContinuousHorizontalScroll { + left, + page_start, + interval, + page_end, + } => { + buffer[0] = 0x26 | (left as u8); + buffer[1] = 0; + buffer[2] = page_start; + buffer[3] = interval; + buffer[4] = page_end; + buffer[5] = 0; + buffer[6] = 0xff; + 7 + } + Self::ContinuousVerticalHorizontalScroll { + left, + page_start, + interval, + page_end, + vertical_offset, + } => { + buffer[0] = 0x29 | (left as u8); + buffer[1] = 0; + buffer[2] = page_start; + buffer[3] = interval; + buffer[4] = page_end; + buffer[5] = vertical_offset; + 6 + } + Self::DeactivateScroll => { + buffer[0] = 0x2e; + 1 + } + Self::ActivateScroll => { + buffer[0] = 0x2f; + 1 + } + Self::SetVerticalScrollArea { + rows_fixed, + rows_scroll, + } => { + buffer[0] = 0xa3; + buffer[1] = rows_fixed; + buffer[2] = rows_scroll; + 3 + } + Self::SetLowerColumnStartAddress { address } => { + buffer[0] = 0x00 | (address & 0xF); + 1 + } + Self::SetHigherColumnStartAddress { address } => { + buffer[0] = 0x10 | ((address >> 4) & 0xF); + 1 + } + Self::SetMemoryAddressingMode { mode } => { + buffer[0] = 0x20; + buffer[1] = mode; + 2 + } + Self::SetColumnAddress { + column_start, + column_end, + } => { + buffer[0] = 0x21; + buffer[1] = column_start; + buffer[2] = column_end; + 3 + } + Self::SetPageAddress { + page_start, + page_end, + } => { + buffer[0] = 0x22; + buffer[1] = page_start; + buffer[2] = page_end; + 3 + } + Self::SetPageStartAddress { address } => { + buffer[0] = 0xb0 | (address & 0x7); + 1 + } + Self::SetDisplayStartLine { line } => { + buffer[0] = 0x40 | (line & 0x3F); + 1 + } + Self::SetSegmentRemap { reverse } => { + buffer[0] = 0xa0 | (reverse as u8); + 1 + } + Self::SetMultiplexRatio { ratio } => { + buffer[0] = 0xa8; + buffer[1] = ratio; + 2 + } + Self::SetComScanDirection { decrement } => { + buffer[0] = 0xc0 | ((decrement as u8) << 3); + 1 + } + Self::SetDisplayOffset { vertical_shift } => { + buffer[0] = 0xd3; + buffer[1] = vertical_shift; + 2 + } + Self::SetComPins { + alternative, + enable_com, + } => { + buffer[0] = 0xda; + buffer[1] = ((alternative as u8) << 4) | ((enable_com as u8) << 5) | 0x2; + 2 + } + Self::SetDisplayClockDivide { + divide_ratio, + oscillator_frequency, + } => { + buffer[0] = 0xd5; + buffer[1] = ((oscillator_frequency & 0xF) << 4) | (divide_ratio & 0xf); + 2 + } + Self::SetPrechargePeriod { phase1, phase2 } => { + buffer[0] = 0xd9; + buffer[1] = ((phase2 & 0xF) << 4) | (phase1 & 0xf); + 2 + } + Self::SetVcomDeselect { level } => { + buffer[0] = 0xdb; + buffer[1] = (level & 0xF) << 4; + 2 + } + }; + + // Move the available region of the buffer to what is remaining after + // this command was encoded. + buffer.slice(take..); + } +} + +// #[derive(Copy, Clone, PartialEq)] +#[derive(Clone, Copy, PartialEq)] +enum State { + Idle, + Init, + SimpleCommand, + Write, +} + +pub struct Ssd1306<'a, I: hil::i2c::I2CDevice> { + i2c: &'a I, + state: Cell, + client: OptionalCell<&'a dyn hil::screen::ScreenClient>, + setup_client: OptionalCell<&'a dyn hil::screen::ScreenSetupClient>, + buffer: TakeCell<'static, [u8]>, + write_buffer: MapCell>, + enable_charge_pump: bool, +} + +impl<'a, I: hil::i2c::I2CDevice> Ssd1306<'a, I> { + pub fn new(i2c: &'a I, buffer: &'static mut [u8], enable_charge_pump: bool) -> Ssd1306<'a, I> { + Ssd1306 { + i2c, + state: Cell::new(State::Idle), + client: OptionalCell::empty(), + setup_client: OptionalCell::empty(), + buffer: TakeCell::new(buffer), + write_buffer: MapCell::empty(), + enable_charge_pump, + } + } + + pub fn init_screen(&self) { + let commands = [ + Command::SetDisplayOnOff { on: false }, + Command::SetDisplayClockDivide { + divide_ratio: 0, + oscillator_frequency: 0x8, + }, + Command::SetMultiplexRatio { + ratio: HEIGHT as u8 - 1, + }, + Command::SetDisplayOffset { vertical_shift: 0 }, + Command::SetDisplayStartLine { line: 0 }, + Command::SetChargePump { + enable: self.enable_charge_pump, + }, + Command::SetMemoryAddressingMode { mode: 0 }, //horizontal + Command::SetSegmentRemap { reverse: true }, + Command::SetComScanDirection { decrement: true }, + Command::SetComPins { + alternative: true, + enable_com: false, + }, + Command::SetContrast { contrast: 0xcf }, + Command::SetPrechargePeriod { + phase1: 0x1, + phase2: 0xf, + }, + Command::SetVcomDeselect { level: 2 }, + Command::EntireDisplayOn { ignore_ram: false }, + Command::SetDisplayInvert { inverse: false }, + Command::DeactivateScroll, + Command::SetDisplayOnOff { on: true }, + ]; + + match self.send_sequence(&commands) { + Ok(()) => { + self.state.set(State::Init); + } + Err(_e) => {} + } + } + + fn send_sequence(&self, sequence: &[Command]) -> Result<(), ErrorCode> { + if self.state.get() == State::Idle { + self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| { + let mut buf_slice = SubSliceMut::new(buffer); + + // Specify this is a series of command bytes. + buf_slice[0] = 0; // Co = 0, D/C̅ = 0 + + // Move the window of the subslice after the command byte header. + buf_slice.slice(1..); + + for cmd in sequence.iter() { + cmd.encode(&mut buf_slice); + } + + // We need the amount of data that has been sliced away + // at the start of the subslice. + let remaining_len = buf_slice.len(); + buf_slice.reset(); + let tx_len = buf_slice.len() - remaining_len; + + self.i2c.enable(); + match self.i2c.write(buf_slice.take(), tx_len) { + Ok(()) => Ok(()), + Err((_e, buf)) => { + self.buffer.replace(buf); + self.i2c.disable(); + Err(ErrorCode::INVAL) + } + } + }) + } else { + Err(ErrorCode::BUSY) + } + } +} + +impl<'a, I: hil::i2c::I2CDevice> hil::screen::ScreenSetup<'a> for Ssd1306<'a, I> { + fn set_client(&self, client: &'a dyn hil::screen::ScreenSetupClient) { + self.setup_client.set(client); + } + + fn set_resolution(&self, _resolution: (usize, usize)) -> Result<(), ErrorCode> { + Err(ErrorCode::NOSUPPORT) + } + + fn set_pixel_format(&self, _depth: hil::screen::ScreenPixelFormat) -> Result<(), ErrorCode> { + Err(ErrorCode::NOSUPPORT) + } + + fn set_rotation(&self, _rotation: hil::screen::ScreenRotation) -> Result<(), ErrorCode> { + Err(ErrorCode::NOSUPPORT) + } + + fn get_num_supported_resolutions(&self) -> usize { + 1 + } + + fn get_supported_resolution(&self, index: usize) -> Option<(usize, usize)> { + match index { + 0 => Some((WIDTH, HEIGHT)), + _ => None, + } + } + + fn get_num_supported_pixel_formats(&self) -> usize { + 1 + } + + fn get_supported_pixel_format(&self, index: usize) -> Option { + match index { + 0 => Some(hil::screen::ScreenPixelFormat::Mono), + _ => None, + } + } +} + +impl<'a, I: hil::i2c::I2CDevice> hil::screen::Screen<'a> for Ssd1306<'a, I> { + fn set_client(&self, client: &'a dyn hil::screen::ScreenClient) { + self.client.set(client); + } + + fn get_resolution(&self) -> (usize, usize) { + (WIDTH, HEIGHT) + } + + fn get_pixel_format(&self) -> hil::screen::ScreenPixelFormat { + hil::screen::ScreenPixelFormat::Mono + } + + fn get_rotation(&self) -> hil::screen::ScreenRotation { + hil::screen::ScreenRotation::Normal + } + + fn set_write_frame( + &self, + x: usize, + y: usize, + width: usize, + height: usize, + ) -> Result<(), ErrorCode> { + let commands = [ + Command::SetPageAddress { + page_start: (y / 8) as u8, + page_end: ((y / 8) + (height / 8) - 1) as u8, + }, + Command::SetColumnAddress { + column_start: x as u8, + column_end: (x + width - 1) as u8, + }, + ]; + match self.send_sequence(&commands) { + Ok(()) => { + self.state.set(State::SimpleCommand); + Ok(()) + } + Err(e) => Err(e), + } + } + + fn write(&self, data: SubSliceMut<'static, u8>, _continue: bool) -> Result<(), ErrorCode> { + self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| { + let mut buf_slice = SubSliceMut::new(buffer); + + // Specify this is data. + buf_slice[0] = 0x40; // Co = 0, D/C̅ = 1 + + // Move the window of the subslice after the command byte header. + buf_slice.slice(1..); + + // Figure out how much we can send. + let copy_len = core::cmp::min(buf_slice.len(), data.len()); + + for i in 0..copy_len { + buf_slice[i] = data[i]; + } + + let tx_len = copy_len + 1; + + self.i2c.enable(); + match self.i2c.write(buf_slice.take(), tx_len) { + Ok(()) => { + self.state.set(State::Write); + self.write_buffer.replace(data); + Ok(()) + } + Err((_e, buf)) => { + self.buffer.replace(buf); + Err(ErrorCode::INVAL) + } + } + }) + } + + fn set_brightness(&self, brightness: u16) -> Result<(), ErrorCode> { + let commands = [Command::SetContrast { + contrast: (brightness >> 8) as u8, + }]; + match self.send_sequence(&commands) { + Ok(()) => { + self.state.set(State::SimpleCommand); + Ok(()) + } + Err(e) => Err(e), + } + } + + fn set_power(&self, enabled: bool) -> Result<(), ErrorCode> { + let commands = [Command::SetDisplayOnOff { on: enabled }]; + match self.send_sequence(&commands) { + Ok(()) => { + self.state.set(State::SimpleCommand); + Ok(()) + } + Err(e) => Err(e), + } + } + + fn set_invert(&self, enabled: bool) -> Result<(), ErrorCode> { + let commands = [Command::SetDisplayInvert { inverse: enabled }]; + match self.send_sequence(&commands) { + Ok(()) => { + self.state.set(State::SimpleCommand); + Ok(()) + } + Err(e) => Err(e), + } + } +} + +impl<'a, I: hil::i2c::I2CDevice> hil::i2c::I2CClient for Ssd1306<'a, I> { + fn command_complete(&self, buffer: &'static mut [u8], _status: Result<(), hil::i2c::Error>) { + self.buffer.replace(buffer); + self.i2c.disable(); + + match self.state.get() { + State::Init => { + self.state.set(State::Idle); + self.client.map(|client| client.screen_is_ready()); + } + + State::SimpleCommand => { + self.state.set(State::Idle); + self.client.map(|client| client.command_complete(Ok(()))); + } + + State::Write => { + self.state.set(State::Idle); + self.write_buffer.take().map(|buf| { + self.client.map(|client| client.write_complete(buf, Ok(()))); + }); + } + _ => {} + } + } +} diff --git a/capsules/src/st77xx.rs b/capsules/extra/src/st77xx.rs similarity index 90% rename from capsules/src/st77xx.rs rename to capsules/extra/src/st77xx.rs index 704a44c99e..83171160bd 100644 --- a/capsules/src/st77xx.rs +++ b/capsules/extra/src/st77xx.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! ST77xx Screen //! //! - @@ -10,26 +14,22 @@ //! //! SPI example //! -//! ```rust -//! let tft = components::st77xx::ST77XXComponent::new(mux_alarm).finalize( -//! components::st77xx_component_helper!( -//! // screen -//! &capsules::st77xx::ST7735, +//! ```rust,ignore +//! let tft = components::st77xx::ST77XXComponent::new(mux_alarm, +//! bus, +//! Some(&nrf52840::gpio::PORT[GPIO_D3]), +//! Some(&nrf52840::gpio::PORT[GPIO_D2]), +//! &capsules::st77xx::ST7735).finalize( +//! components::st77xx_component_static!( //! // bus type //! capsules::bus::SpiMasterBus< //! 'static, //! VirtualSpiMasterDevice<'static, nrf52840::spi::SPIM>, //! >, -//! // bus -//! &bus, //! // timer type //! nrf52840::rtc::Rtc, //! // pin type //! nrf52::gpio::GPIOPin<'static>, -//! // dc -//! Some(&nrf52840::gpio::PORT[GPIO_D3]), -//! // reset -//! Some(&nrf52840::gpio::PORT[GPIO_D2]) //! ), //! ); //! ``` @@ -42,6 +42,7 @@ use kernel::hil::screen::{ }; use kernel::hil::time::{self, Alarm, ConvertTicks}; use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::utilities::leasable_buffer::SubSliceMut; use kernel::ErrorCode; pub const BUFFER_SIZE: usize = 24; @@ -161,7 +162,6 @@ const COLMOD: Command = Command { const MADCTL: Command = Command { id: 0x36, - /// Default Parameters: parameters: Some(&[0x00]), delay: 0, }; @@ -215,8 +215,8 @@ pub struct ST77XX<'a, A: Alarm<'a>, B: Bus<'a>, P: Pin> { width: Cell, height: Cell, - client: OptionalCell<&'static dyn screen::ScreenClient>, - setup_client: OptionalCell<&'static dyn screen::ScreenSetupClient>, + client: OptionalCell<&'a dyn screen::ScreenClient>, + setup_client: OptionalCell<&'a dyn screen::ScreenSetupClient>, setup_command: Cell, sequence_buffer: TakeCell<'static, [SendCommand]>, @@ -247,11 +247,11 @@ impl<'a, A: Alarm<'a>, B: Bus<'a>, P: Pin> ST77XX<'a, A, B, P> { dc.map(|dc| dc.make_output()); reset.map(|reset| reset.make_output()); ST77XX { - alarm: alarm, + alarm, - dc: dc, - reset: reset, - bus: bus, + dc, + reset, + bus, status: Cell::new(Status::Idle), width: Cell::new(screen.default_width), @@ -273,7 +273,7 @@ impl<'a, A: Alarm<'a>, B: Bus<'a>, P: Pin> ST77XX<'a, A, B, P> { current_rotation: Cell::new(ScreenRotation::Normal), - screen: screen, + screen, } } @@ -324,7 +324,7 @@ impl<'a, A: Alarm<'a>, B: Bus<'a>, P: Pin> ST77XX<'a, A, B, P> { if let Some(parameters) = cmd.parameters { for parameter in parameters.iter() { buffer[len] = *parameter; - len = len + 1; + len += 1; } } }, @@ -493,16 +493,16 @@ impl<'a, A: Alarm<'a>, B: Bus<'a>, P: Pin> ST77XX<'a, A, B, P> { SendCommand::Nop => { self.do_next_op(); } - SendCommand::Default(ref cmd) => { + SendCommand::Default(cmd) => { self.send_command_with_default_parameters(cmd); } - SendCommand::Position(ref cmd, position, len) => { + SendCommand::Position(cmd, position, len) => { self.send_command(cmd, position, len, 1); } - SendCommand::Repeat(ref cmd, position, len, repeat) => { + SendCommand::Repeat(cmd, position, len, repeat) => { self.send_command(cmd, position, len, repeat); } - SendCommand::Slice(ref cmd, len) => { + SendCommand::Slice(cmd, len) => { self.send_command_slice(cmd, len); } }; @@ -526,7 +526,8 @@ impl<'a, A: Alarm<'a>, B: Bus<'a>, P: Pin> ST77XX<'a, A, B, P> { self.client.map(|client| { if self.write_buffer.is_some() { self.write_buffer.take().map(|buffer| { - client.write_complete(buffer, Ok(())); + let data = SubSliceMut::new(buffer); + client.write_complete(data, Ok(())); }); } else { client.command_complete(Ok(())); @@ -588,7 +589,7 @@ impl<'a, A: Alarm<'a>, B: Bus<'a>, P: Pin> ST77XX<'a, A, B, P> { } Status::Init => { self.status.set(Status::Idle); - let _ = self.send_sequence(&self.screen.init_sequence); + let _ = self.send_sequence(self.screen.init_sequence); } Status::Error(error) => { if self.setup_command.get() { @@ -600,7 +601,8 @@ impl<'a, A: Alarm<'a>, B: Bus<'a>, P: Pin> ST77XX<'a, A, B, P> { self.client.map(|client| { if self.write_buffer.is_some() { self.write_buffer.take().map(|buffer| { - client.write_complete(buffer, Err(error)); + let data = SubSliceMut::new(buffer); + client.write_complete(data, Err(error)); }); } else { client.command_complete(Err(error)); @@ -681,13 +683,9 @@ impl<'a, A: Alarm<'a>, B: Bus<'a>, P: Pin> ST77XX<'a, A, B, P> { } } -impl<'a, A: Alarm<'a>, B: Bus<'a>, P: Pin> screen::ScreenSetup for ST77XX<'a, A, B, P> { - fn set_client(&self, setup_client: Option<&'static dyn ScreenSetupClient>) { - if let Some(setup_client) = setup_client { - self.setup_client.set(setup_client); - } else { - self.setup_client.clear(); - } +impl<'a, A: Alarm<'a>, B: Bus<'a>, P: Pin> screen::ScreenSetup<'a> for ST77XX<'a, A, B, P> { + fn set_client(&self, setup_client: &'a dyn ScreenSetupClient) { + self.setup_client.set(setup_client); } fn set_resolution(&self, resolution: (usize, usize)) -> Result<(), ErrorCode> { @@ -743,7 +741,7 @@ impl<'a, A: Alarm<'a>, B: Bus<'a>, P: Pin> screen::ScreenSetup for ST77XX<'a, A, } } -impl<'a, A: Alarm<'a>, B: Bus<'a>, P: Pin> screen::Screen for ST77XX<'a, A, B, P> { +impl<'a, A: Alarm<'a>, B: Bus<'a>, P: Pin> screen::Screen<'a> for ST77XX<'a, A, B, P> { fn get_resolution(&self) -> (usize, usize) { (self.width.get(), self.height.get()) } @@ -792,66 +790,66 @@ impl<'a, A: Alarm<'a>, B: Bus<'a>, P: Pin> screen::Screen for ST77XX<'a, A, B, P } } - fn write(&self, buffer: &'static mut [u8], len: usize) -> Result<(), ErrorCode> { + fn write(&self, data: SubSliceMut<'static, u8>, continue_write: bool) -> Result<(), ErrorCode> { if self.status.get() == Status::Idle { self.setup_command.set(false); - self.write_buffer.replace(buffer); - let buffer_len = self.buffer.map_or_else( - || panic!("st77xx: buffer is not available"), - |buffer| buffer.len(), - ); - if buffer_len > 0 { - // set buffer - self.sequence_buffer.map_or_else( - || panic!("st77xx: write no sequence buffer"), - |sequence| { - sequence[0] = SendCommand::Slice(&WRITE_RAM, len); - self.sequence_len.set(1); - }, + let len = data.len(); + self.write_buffer.replace(data.take()); + + if !continue_write { + // Writing new data for the first time, make sure to reset + // the screen buffer location to the beginning. + + let buffer_len = self.buffer.map_or_else( + || panic!("st77xx: buffer is not available"), + |buffer| buffer.len(), ); - let _ = self.send_sequence_buffer(); - Ok(()) + if buffer_len > 0 { + // set buffer + self.sequence_buffer.map_or_else( + || panic!("st77xx: write no sequence buffer"), + |sequence| { + sequence[0] = SendCommand::Slice(&WRITE_RAM, len); + self.sequence_len.set(1); + }, + ); + let _ = self.send_sequence_buffer(); + Ok(()) + } else { + Err(ErrorCode::NOMEM) + } } else { - Err(ErrorCode::NOMEM) + // Continuing the previous write. + self.send_parameters_slice(len); + Ok(()) } } else { Err(ErrorCode::BUSY) } } - fn write_continue(&self, buffer: &'static mut [u8], len: usize) -> Result<(), ErrorCode> { - if self.status.get() == Status::Idle { - self.setup_command.set(false); - self.write_buffer.replace(buffer); - self.send_parameters_slice(len); - Ok(()) - } else { - Err(ErrorCode::BUSY) - } + fn set_client(&self, client: &'a dyn ScreenClient) { + self.client.set(client); } - fn set_client(&self, client: Option<&'static dyn ScreenClient>) { - if let Some(client) = client { - self.client.set(client); - } else { - self.client.clear(); - } + fn set_brightness(&self, _brightness: u16) -> Result<(), ErrorCode> { + Ok(()) } - fn set_brightness(&self, brightness: usize) -> Result<(), ErrorCode> { - if brightness > 0 { + fn set_power(&self, enabled: bool) -> Result<(), ErrorCode> { + if enabled { self.display_on() } else { self.display_off() } } - fn invert_on(&self) -> Result<(), ErrorCode> { - self.display_invert_on() - } - - fn invert_off(&self) -> Result<(), ErrorCode> { - self.display_invert_off() + fn set_invert(&self, enabled: bool) -> Result<(), ErrorCode> { + if enabled { + self.display_invert_on() + } else { + self.display_invert_off() + } } } @@ -888,84 +886,73 @@ impl<'a, A: Alarm<'a>, B: Bus<'a>, P: Pin> bus::Client for ST77XX<'a, A, B, P> { #[allow(dead_code)] const GAMSET: Command = Command { id: 0x26, - /// Default parameters: Gama Set + // Default parameters: Gama Set parameters: Some(&[0]), delay: 0, }; const FRMCTR1: Command = Command { id: 0xB1, - /// Default Parameters: parameters: Some(&[0x01, 0x2C, 0x2D]), delay: 0, }; const FRMCTR2: Command = Command { id: 0xB2, - /// Default Parameters: parameters: Some(&[0x01, 0x2C, 0x2D]), delay: 0, }; const FRMCTR3: Command = Command { id: 0xB3, - /// Default Parameters: parameters: Some(&[0x01, 0x2C, 0x2D, 0x01, 0x2C, 0x2D]), delay: 0, }; const INVCTR: Command = Command { id: 0xB4, - /// Default Parameters: parameters: Some(&[0x07]), delay: 0, }; const PWCTR1: Command = Command { id: 0xC0, - /// Default Parameters: parameters: Some(&[0xA2, 0x02, 0x84]), delay: 0, }; const PWCTR2: Command = Command { id: 0xC1, - /// Default Parameters: parameters: Some(&[0xC5]), delay: 0, }; const PWCTR3: Command = Command { id: 0xC2, - /// Default Parameters: parameters: Some(&[0x0A, 0x00]), delay: 0, }; const PWCTR4: Command = Command { id: 0xC3, - /// Default Parameters: parameters: Some(&[0x8A, 0x2A]), delay: 0, }; const PWCTR5: Command = Command { id: 0xC4, - /// Default Parameters: parameters: Some(&[0x8A, 0xEE]), delay: 0, }; const VMCTR1: Command = Command { id: 0xC5, - /// Default Parameters: parameters: Some(&[0x0E]), delay: 0, }; const GMCTRP1: Command = Command { id: 0xE0, - /// Default Parameters: parameters: Some(&[ 0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10, @@ -975,7 +962,6 @@ const GMCTRP1: Command = Command { const GMCTRN1: Command = Command { id: 0xE1, - /// Default Parameters: parameters: Some(&[ 0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10, diff --git a/capsules/src/symmetric_encryption/aes.rs b/capsules/extra/src/symmetric_encryption/aes.rs similarity index 75% rename from capsules/src/symmetric_encryption/aes.rs rename to capsules/extra/src/symmetric_encryption/aes.rs index 588bf774c2..83602d8da8 100644 --- a/capsules/src/symmetric_encryption/aes.rs +++ b/capsules/extra/src/symmetric_encryption/aes.rs @@ -1,6 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! AES. -use crate::driver; +use capsules_core::driver; /// Syscall driver number. pub const DRIVER_NUM: usize = driver::NUM::Aes as usize; @@ -8,7 +12,8 @@ use core::cell::Cell; use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; use kernel::hil::symmetric_encryption::{ - AES128Ctr, CCMClient, Client, AES128, AES128CBC, AES128CCM, AES128ECB, AES128_BLOCK_SIZE, + AES128Ctr, CCMClient, Client, GCMClient, AES128, AES128CBC, AES128CCM, AES128ECB, AES128GCM, + AES128_BLOCK_SIZE, }; use kernel::processbuffer::{ReadableProcessBuffer, WriteableProcessBuffer}; use kernel::syscall::{CommandReturn, SyscallDriver}; @@ -21,17 +26,17 @@ mod ro_allow { pub const IV: usize = 1; pub const SOURCE: usize = 2; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 3; + pub const COUNT: u8 = 3; } /// Ids for read-write allow buffers mod rw_allow { pub const DEST: usize = 0; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; + pub const COUNT: u8 = 1; } -pub struct AesDriver<'a, A: AES128<'a> + AES128CCM<'static>> { +pub struct AesDriver<'a, A: AES128<'a> + AES128CCM<'static> + AES128GCM<'static>> { aes: &'a A, active: Cell, @@ -42,15 +47,21 @@ pub struct AesDriver<'a, A: AES128<'a> + AES128CCM<'static>> { AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<{ rw_allow::COUNT }>, >, - appid: OptionalCell, + processid: OptionalCell, source_buffer: TakeCell<'static, [u8]>, data_copied: Cell, dest_buffer: TakeCell<'static, [u8]>, } -impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'static>> - AesDriver<'static, A> +impl< + A: AES128<'static> + + AES128Ctr + + AES128CBC + + AES128ECB + + AES128CCM<'static> + + AES128GCM<'static>, + > AesDriver<'static, A> { pub fn new( aes: &'static A, @@ -67,7 +78,7 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta aes, active: Cell::new(false), apps: grant, - appid: OptionalCell::empty(), + processid: OptionalCell::empty(), source_buffer: TakeCell::new(source_buffer), data_copied: Cell::new(0), dest_buffer: TakeCell::new(dest_buffer), @@ -75,28 +86,23 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta } fn run(&self) -> Result<(), ErrorCode> { - self.appid.map_or(Err(ErrorCode::RESERVE), |appid| { + self.processid.map_or(Err(ErrorCode::RESERVE), |processid| { self.apps - .enter(*appid, |app, kernel_data| { + .enter(processid, |app, kernel_data| { self.aes.enable(); - let ret = if let Some(op) = &app.aes_operation { - match op { - AesOperation::AES128Ctr(encrypt) => { - self.aes.set_mode_aes128ctr(*encrypt) - } - AesOperation::AES128CBC(encrypt) => { - self.aes.set_mode_aes128cbc(*encrypt) - } - AesOperation::AES128ECB(encrypt) => { - self.aes.set_mode_aes128ecb(*encrypt) - } - AesOperation::AES128CCM(_encrypt) => Ok(()), + match app.aes_operation { + Some(AesOperation::AES128Ctr(encrypt)) => { + self.aes.set_mode_aes128ctr(encrypt)? } - } else { - Err(ErrorCode::INVAL) - }; - if ret.is_err() { - return ret; + Some(AesOperation::AES128CBC(encrypt)) => { + self.aes.set_mode_aes128cbc(encrypt)? + } + Some(AesOperation::AES128ECB(encrypt)) => { + self.aes.set_mode_aes128ecb(encrypt)? + } + Some(AesOperation::AES128CCM(_encrypt)) => {} + Some(AesOperation::AES128GCM(_encrypt)) => {} + _ => return Err(ErrorCode::INVAL), } kernel_data @@ -121,15 +127,15 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta AesOperation::AES128Ctr(_) | AesOperation::AES128CBC(_) | AesOperation::AES128ECB(_) => { - if let Err(e) = AES128::set_key(self.aes, buf) { - return Err(e); - } + AES128::set_key(self.aes, buf)?; Ok(()) } AesOperation::AES128CCM(_) => { - if let Err(e) = AES128CCM::set_key(self.aes, buf) { - return Err(e); - } + AES128CCM::set_key(self.aes, buf)?; + Ok(()) + } + AesOperation::AES128GCM(_) => { + AES128GCM::set_key(self.aes, buf)?; Ok(()) } } @@ -163,15 +169,15 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta AesOperation::AES128Ctr(_) | AesOperation::AES128CBC(_) | AesOperation::AES128ECB(_) => { - if let Err(e) = self.aes.set_iv(buf) { - return Err(e); - } + AES128::set_iv(self.aes, buf)?; Ok(()) } AesOperation::AES128CCM(_) => { - if let Err(e) = self.aes.set_nonce(&buf[0..13]) { - return Err(e); - } + AES128CCM::set_nonce(self.aes, &buf[0..13])?; + Ok(()) + } + AesOperation::AES128GCM(_) => { + AES128GCM::set_iv(self.aes, &buf[0..13])?; Ok(()) } } @@ -237,18 +243,38 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta }, )?; } + AesOperation::AES128GCM(_) => { + self.dest_buffer.map_or( + Err(ErrorCode::NOMEM), + |buf| { + // Determine the size of the static buffer we have + static_buffer_len = buf.len(); + + if static_buffer_len > source.len() { + static_buffer_len = source.len() + } + + // Copy the data into the static buffer + source[..static_buffer_len].copy_to_slice( + &mut buf[..static_buffer_len], + ); + + self.data_copied.set(static_buffer_len); + + Ok(()) + }, + )?; + } } - if let Err(e) = self.calculate_output( + self.calculate_output( op, app.aoff.get(), app.moff.get(), app.mlen.get(), app.mic_len.get(), app.confidential.get(), - ) { - return Err(e); - } + )?; Ok(()) } else { Err(ErrorCode::FAIL) @@ -284,9 +310,9 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta 0, AES128_BLOCK_SIZE, ) { - // Error, clear the appid and data + // Error, clear the processid and data self.aes.disable(); - self.appid.clear(); + self.processid.clear(); if let Some(source_buf) = source { self.source_buffer.replace(source_buf); } @@ -310,9 +336,25 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta confidential, *encrypting, ) { + // Error, clear the processid and data + self.aes.disable(); + self.processid.clear(); + self.dest_buffer.replace(dest); + + return Err(e); + } + } else { + return Err(ErrorCode::FAIL); + } + } + AesOperation::AES128GCM(encrypting) => { + if let Some(buf) = self.dest_buffer.take() { + if let Err((e, dest)) = + AES128GCM::crypt(self.aes, buf, aoff, moff, mlen, *encrypting) + { // Error, clear the appid and data self.aes.disable(); - self.appid.clear(); + self.processid.clear(); self.dest_buffer.replace(dest); return Err(e); @@ -330,14 +372,14 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta for appiter in self.apps.iter() { let started_command = appiter.enter(|app, _| { // If an app is already running let it complete - if self.appid.is_some() { + if self.processid.is_some() { return true; } // If this app has a pending command let's use it. - app.pending_run_app.take().map_or(false, |appid| { + app.pending_run_app.take().map_or(false, |processid| { // Mark this driver as being in use. - self.appid.set(appid); + self.processid.set(processid); // Actually make the buzz happen. self.run() == Ok(()) }) @@ -349,8 +391,15 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta } } -impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'static>> - Client<'static> for AesDriver<'static, A> +impl< + 'a, + A: AES128<'static> + + AES128Ctr + + AES128CBC + + AES128ECB + + AES128CCM<'static> + + AES128GCM<'static>, + > Client<'static> for AesDriver<'static, A> { fn crypt_done(&'a self, source: Option<&'static mut [u8]>, destination: &'static mut [u8]) { if let Some(source_buf) = source { @@ -358,9 +407,9 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta } self.dest_buffer.replace(destination); - self.appid.map(|id| { + self.processid.map(|id| { self.apps - .enter(*id, |app, kernel_data| { + .enter(id, |app, kernel_data| { let mut data_len = 0; let mut exit = false; let mut static_buffer_len = 0; @@ -399,9 +448,9 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta }); if let Err(e) = ret { - // No data buffer, clear the appid and data + // No data buffer, clear the processid and data self.aes.disable(); - self.appid.clear(); + self.processid.clear(); kernel_data.schedule_upcall(0, (e as usize, 0, 0)).ok(); exit = true; } @@ -439,9 +488,9 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta .unwrap_or(Err(ErrorCode::RESERVE)); if let Err(e) = ret { - // No data buffer, clear the appid and data + // No data buffer, clear the processid and data self.aes.disable(); - self.appid.clear(); + self.processid.clear(); kernel_data.schedule_upcall(0, (e as usize, 0, 0)).ok(); exit = true; } @@ -470,9 +519,9 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta ) .is_err() { - // Error, clear the appid and data + // Error, clear the processid and data self.aes.disable(); - self.appid.clear(); + self.processid.clear(); self.check_queue(); return; } @@ -493,22 +542,28 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta if err == kernel::process::Error::NoSuchApp || err == kernel::process::Error::InactiveApp { - self.appid.clear(); + self.processid.clear(); } }) }); } } -impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'static>> CCMClient - for AesDriver<'static, A> +impl< + A: AES128<'static> + + AES128Ctr + + AES128CBC + + AES128ECB + + AES128CCM<'static> + + AES128GCM<'static>, + > CCMClient for AesDriver<'static, A> { fn crypt_done(&self, buf: &'static mut [u8], res: Result<(), ErrorCode>, tag_is_valid: bool) { self.dest_buffer.replace(buf); - self.appid.map(|id| { + self.processid.map(|id| { self.apps - .enter(*id, |_, kernel_data| { + .enter(id, |_, kernel_data| { let mut exit = false; if let Err(e) = res { @@ -541,9 +596,9 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta }); if let Err(e) = ret { - // No data buffer, clear the appid and data + // No data buffer, clear the processid and data self.aes.disable(); - self.appid.clear(); + self.processid.clear(); kernel_data.schedule_upcall(0, (e as usize, 0, 0)).ok(); exit = true; } @@ -564,24 +619,107 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta if err == kernel::process::Error::NoSuchApp || err == kernel::process::Error::InactiveApp { - self.appid.clear(); + self.processid.clear(); } }) }); } } -impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'static>> SyscallDriver - for AesDriver<'static, A> +impl< + A: AES128<'static> + + AES128Ctr + + AES128CBC + + AES128ECB + + AES128CCM<'static> + + AES128GCM<'static>, + > GCMClient for AesDriver<'static, A> +{ + fn crypt_done(&self, buf: &'static mut [u8], res: Result<(), ErrorCode>, tag_is_valid: bool) { + self.dest_buffer.replace(buf); + + self.processid.map(|id| { + self.apps + .enter(id, |_, kernel_data| { + let mut exit = false; + + if let Err(e) = res { + kernel_data.schedule_upcall(0, (e as usize, 0, 0)).ok(); + return; + } + + self.dest_buffer.map(|buf| { + let ret = kernel_data + .get_readwrite_processbuffer(rw_allow::DEST) + .and_then(|dest| { + dest.mut_enter(|dest| { + let offset = self.data_copied.get() + - (core::cmp::min(buf.len(), dest.len())); + let app_len = dest.len(); + let static_len = buf.len(); + + if app_len < static_len { + if app_len - offset > 0 { + dest[offset..app_len] + .copy_from_slice(&buf[0..(app_len - offset)]); + } + } else { + if offset + static_len <= app_len { + dest[offset..(offset + static_len)] + .copy_from_slice(&buf[0..static_len]); + } + } + }) + }); + + if let Err(e) = ret { + // No data buffer, clear the appid and data + self.aes.disable(); + self.processid.clear(); + kernel_data.schedule_upcall(0, (e as usize, 0, 0)).ok(); + exit = true; + } + }); + + if exit { + return; + } + + // AES GCM is online only we can't send any more data in, so + // just report what we did to the app. + kernel_data + .schedule_upcall(0, (0, self.data_copied.get(), tag_is_valid as usize)) + .ok(); + self.data_copied.set(0); + }) + .map_err(|err| { + if err == kernel::process::Error::NoSuchApp + || err == kernel::process::Error::InactiveApp + { + self.processid.clear(); + } + }) + }); + } +} + +impl< + A: AES128<'static> + + AES128Ctr + + AES128CBC + + AES128ECB + + AES128CCM<'static> + + AES128GCM<'static>, + > SyscallDriver for AesDriver<'static, A> { fn command( &self, command_num: usize, data1: usize, data2: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { - let match_or_empty_or_nonexistant = self.appid.map_or(true, |owning_app| { + let match_or_empty_or_nonexistant = self.processid.map_or(true, |owning_app| { // We have recorded that an app has ownership of the HMAC. // If the HMAC is still active, then we need to wait for the operation @@ -590,7 +728,7 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta // we need to verify that that application still exists, and remove // it as owner if not. if self.active.get() { - owning_app == &appid + owning_app == processid } else { // Check the app still exists. // @@ -600,12 +738,12 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta // longer exists and we return `true` to signify the // "or_nonexistant" case. self.apps - .enter(*owning_app, |_, _| owning_app == &appid) + .enter(owning_app, |_, _| owning_app == processid) .unwrap_or(true) } }); - let app_match = self.appid.map_or(false, |owning_app| { + let app_match = self.processid.map_or(false, |owning_app| { // We have recorded that an app has ownership of the HMAC. // If the HMAC is still active, then we need to wait for the operation @@ -614,7 +752,7 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta // we need to verify that that application still exists, and remove // it as owner if not. if self.active.get() { - owning_app == &appid + owning_app == processid } else { // Check the app still exists. // @@ -624,7 +762,7 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta // longer exists and we return `true` to signify the // "or_nonexistant" case. self.apps - .enter(*owning_app, |_, _| owning_app == &appid) + .enter(owning_app, |_, _| owning_app == processid) .unwrap_or(true) } }); @@ -632,12 +770,12 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta // Try the commands where we want to start an operation *not* entered in // an app grant first. if match_or_empty_or_nonexistant && command_num == 2 { - self.appid.set(appid); + self.processid.set(processid); let ret = self.run(); return if let Err(e) = ret { self.aes.disable(); - self.appid.clear(); + self.processid.clear(); self.check_queue(); CommandReturn::failure(e) } else { @@ -647,7 +785,7 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta let ret = self .apps - .enter(appid, |app, kernel_data| { + .enter(processid, |app, kernel_data| { match command_num { // check if present 0 => CommandReturn::success(), @@ -670,6 +808,10 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta app.aes_operation = Some(AesOperation::AES128CCM(data2 != 0)); CommandReturn::success() } + 4 => { + app.aes_operation = Some(AesOperation::AES128GCM(data2 != 0)); + CommandReturn::success() + } _ => CommandReturn::failure(ErrorCode::NOSUPPORT), }, @@ -684,7 +826,7 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta CommandReturn::failure(ErrorCode::NOMEM) } else { // We can store this, so lets do it. - app.pending_run_app = Some(appid); + app.pending_run_app = Some(processid); CommandReturn::success() } } @@ -721,16 +863,14 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta )?; if let Some(op) = app.aes_operation.as_ref() { - if let Err(e) = self.calculate_output( + self.calculate_output( op, app.aoff.get(), app.moff.get(), app.mlen.get(), app.mic_len.get(), app.confidential.get(), - ) { - return Err(e); - } + )?; Ok(()) } else { Err(ErrorCode::FAIL) @@ -760,7 +900,7 @@ impl<'a, A: AES128<'static> + AES128Ctr + AES128CBC + AES128ECB + AES128CCM<'sta 4 => { if app_match { self.aes.disable(); - self.appid.clear(); + self.processid.clear(); CommandReturn::success() } else { @@ -826,6 +966,7 @@ enum AesOperation { AES128CBC(bool), AES128ECB(bool), AES128CCM(bool), + AES128GCM(bool), } #[derive(Default)] diff --git a/capsules/extra/src/symmetric_encryption/mod.rs b/capsules/extra/src/symmetric_encryption/mod.rs new file mode 100644 index 0000000000..7d0063bdb0 --- /dev/null +++ b/capsules/extra/src/symmetric_encryption/mod.rs @@ -0,0 +1,5 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +pub mod aes; diff --git a/capsules/src/temperature.rs b/capsules/extra/src/temperature.rs similarity index 53% rename from capsules/src/temperature.rs rename to capsules/extra/src/temperature.rs index e0c537e69d..ce5c23d781 100644 --- a/capsules/src/temperature.rs +++ b/capsules/extra/src/temperature.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides userspace with access to temperature sensors. //! //! Userspace Interface @@ -18,19 +22,18 @@ //! //! ### `command` System Call //! -//! The `command` system call support one argument `cmd` which is used to specify the specific -//! operation, currently the following cmd's are supported: +//! The `command` system call support one argument `cmd` which is used to +//! specify the specific operation, currently the following cmd's are supported: //! -//! * `0`: check whether the driver exist +//! * `0`: check whether the driver exists //! * `1`: read the temperature //! //! //! The possible return from the 'command' system call indicates the following: //! //! * `Ok(())`: The operation has been successful. -//! * `BUSY`: The driver is busy. -//! * `ENOSUPPORT`: Invalid `cmd`. -//! * `NOMEM`: No sufficient memory available. +//! * `NOSUPPORT`: Invalid `cmd`. +//! * `NOMEM`: Insufficient memory available. //! * `INVAL`: Invalid address of the buffer or other error. //! //! Usage @@ -38,7 +41,7 @@ //! //! You need a device that provides the `hil::sensors::TemperatureDriver` trait. //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); @@ -53,7 +56,6 @@ //! ``` use core::cell::Cell; -use core::convert::TryFrom; use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; use kernel::hil; @@ -61,7 +63,7 @@ use kernel::syscall::{CommandReturn, SyscallDriver}; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::Temperature as usize; #[derive(Default)] @@ -69,66 +71,85 @@ pub struct App { subscribed: bool, } -pub struct TemperatureSensor<'a> { - driver: &'a dyn hil::sensors::TemperatureDriver<'a>, +pub struct TemperatureSensor<'a, T: hil::sensors::TemperatureDriver<'a>> { + driver: &'a T, apps: Grant, AllowRoCount<0>, AllowRwCount<0>>, busy: Cell, } -impl<'a> TemperatureSensor<'a> { +impl<'a, T: hil::sensors::TemperatureDriver<'a>> TemperatureSensor<'a, T> { pub fn new( - driver: &'a dyn hil::sensors::TemperatureDriver<'a>, + driver: &'a T, grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, - ) -> TemperatureSensor<'a> { + ) -> TemperatureSensor<'a, T> { TemperatureSensor { - driver: driver, + driver, apps: grant, busy: Cell::new(false), } } - fn enqueue_command(&self, appid: ProcessId) -> CommandReturn { + fn enqueue_command(&self, processid: ProcessId) -> CommandReturn { self.apps - .enter(appid, |app, _| { + .enter(processid, |app, _| { + // Unconditionally mark this client as subscribed so it will get + // a callback when we get the temperature reading. + app.subscribed = true; + + // If we do not already have an ongoing read, start one now. if !self.busy.get() { - app.subscribed = true; self.busy.set(true); - let rcode = self.driver.read_temperature(); - let eres = ErrorCode::try_from(rcode); - match eres { - Ok(ecode) => CommandReturn::failure(ecode), - _ => CommandReturn::success(), + match self.driver.read_temperature() { + Ok(()) => CommandReturn::success(), + Err(e) => CommandReturn::failure(e), } } else { - CommandReturn::failure(ErrorCode::BUSY) + // Just return success and we will get the upcall when the + // temperature read is ready. + CommandReturn::success() } }) .unwrap_or_else(|err| CommandReturn::failure(err.into())) } } -impl hil::sensors::TemperatureClient for TemperatureSensor<'_> { - fn callback(&self, temp_val: usize) { - for cntr in self.apps.iter() { - cntr.enter(|app, upcalls| { - if app.subscribed { - self.busy.set(false); - app.subscribed = false; - upcalls.schedule_upcall(0, (temp_val, 0, 0)).ok(); - } - }); +impl<'a, T: hil::sensors::TemperatureDriver<'a>> hil::sensors::TemperatureClient + for TemperatureSensor<'a, T> +{ + fn callback(&self, temp_val: Result) { + // We completed the operation so we clear the busy flag in case we get + // another measurement request. + self.busy.set(false); + + // Return the temperature reading to any waiting client. + if let Ok(temp_val) = temp_val { + // TODO: forward error conditions + for cntr in self.apps.iter() { + cntr.enter(|app, upcalls| { + if app.subscribed { + app.subscribed = false; + upcalls.schedule_upcall(0, (temp_val as usize, 0, 0)).ok(); + } + }); + } } } } -impl SyscallDriver for TemperatureSensor<'_> { - fn command(&self, command_num: usize, _: usize, _: usize, appid: ProcessId) -> CommandReturn { +impl<'a, T: hil::sensors::TemperatureDriver<'a>> SyscallDriver for TemperatureSensor<'a, T> { + fn command( + &self, + command_num: usize, + _: usize, + _: usize, + processid: ProcessId, + ) -> CommandReturn { match command_num { - // check whether the driver exists!! + // driver existence check 0 => CommandReturn::success(), // read temperature - 1 => self.enqueue_command(appid), + 1 => self.enqueue_command(processid), _ => CommandReturn::failure(ErrorCode::NOSUPPORT), } } diff --git a/capsules/src/temperature_rp2040.rs b/capsules/extra/src/temperature_rp2040.rs similarity index 62% rename from capsules/src/temperature_rp2040.rs rename to capsules/extra/src/temperature_rp2040.rs index b82b2f81a1..3c2c1cb7bc 100644 --- a/capsules/src/temperature_rp2040.rs +++ b/capsules/extra/src/temperature_rp2040.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for RP2040 ADC MCU temperature sensor use core::cell::Cell; @@ -6,7 +10,7 @@ use kernel::hil::sensors; use kernel::utilities::cells::OptionalCell; use kernel::ErrorCode; -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::Temperature as usize; #[derive(Copy, Clone, PartialEq)] @@ -15,41 +19,40 @@ pub enum Status { Idle, } -pub struct TemperatureRp2040<'a> { - adc: &'a dyn adc::AdcChannel, +pub struct TemperatureRp2040<'a, A: adc::AdcChannel<'a>> { + adc: &'a A, slope: f32, v_27: f32, temperature_client: OptionalCell<&'a dyn sensors::TemperatureClient>, status: Cell, } -impl<'a> TemperatureRp2040<'a> { +impl<'a, A: adc::AdcChannel<'a>> TemperatureRp2040<'a, A> { /// slope - device specific slope found in datasheet /// v_27 - voltage at 27 degrees Celsius found in datasheet - pub fn new(adc: &'a dyn adc::AdcChannel, slope: f32, v_27: f32) -> TemperatureRp2040<'a> { + pub fn new(adc: &'a A, slope: f32, v_27: f32) -> TemperatureRp2040<'a, A> { TemperatureRp2040 { - adc: adc, - slope: slope, - v_27: v_27, + adc, + slope, + v_27, temperature_client: OptionalCell::empty(), status: Cell::new(Status::Idle), } } } -impl<'a> adc::Client for TemperatureRp2040<'a> { +impl<'a, A: adc::AdcChannel<'a>> adc::Client for TemperatureRp2040<'a, A> { fn sample_ready(&self, sample: u16) { self.status.set(Status::Idle); self.temperature_client.map(|client| { - client.callback( - ((27.0 - (((sample as f32 * 3.3 / 4095.0) - self.v_27) * 1000.0 / self.slope)) - * 100.0) as usize, - ); + client.callback(Ok(((27.0 + - (((sample as f32 * 3.3 / 65535.0) - self.v_27) * 1000.0 / self.slope)) + * 100.0) as i32)); }); } } -impl<'a> sensors::TemperatureDriver<'a> for TemperatureRp2040<'a> { +impl<'a, A: adc::AdcChannel<'a>> sensors::TemperatureDriver<'a> for TemperatureRp2040<'a, A> { fn set_client(&self, temperature_client: &'a dyn sensors::TemperatureClient) { self.temperature_client.replace(temperature_client); } diff --git a/capsules/src/temperature_stm.rs b/capsules/extra/src/temperature_stm.rs similarity index 62% rename from capsules/src/temperature_stm.rs rename to capsules/extra/src/temperature_stm.rs index 11dc0ca6f8..9d4ced1255 100644 --- a/capsules/src/temperature_stm.rs +++ b/capsules/extra/src/temperature_stm.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for STM ADC MCU temperature sensor use core::cell::Cell; @@ -6,7 +10,7 @@ use kernel::hil::sensors; use kernel::utilities::cells::OptionalCell; use kernel::ErrorCode; -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::Temperature as usize; #[derive(Copy, Clone, PartialEq)] @@ -15,41 +19,41 @@ pub enum Status { Idle, } -pub struct TemperatureSTM<'a> { - adc: &'a dyn adc::AdcChannel, +pub struct TemperatureSTM<'a, A: adc::AdcChannel<'a>> { + adc: &'a A, slope: f32, v_25: f32, temperature_client: OptionalCell<&'a dyn sensors::TemperatureClient>, status: Cell, } -impl<'a> TemperatureSTM<'a> { +impl<'a, A: adc::AdcChannel<'a>> TemperatureSTM<'a, A> { /// slope - device specific slope found in datasheet /// v_25 - voltage at 25 degrees Celsius found in datasheet - pub fn new(adc: &'a dyn adc::AdcChannel, slope: f32, v_25: f32) -> TemperatureSTM<'a> { + pub fn new(adc: &'a A, slope: f32, v_25: f32) -> TemperatureSTM<'a, A> { TemperatureSTM { - adc: adc, - slope: slope, - v_25: v_25, + adc, + slope, + v_25, temperature_client: OptionalCell::empty(), status: Cell::new(Status::Idle), } } } -impl<'a> adc::Client for TemperatureSTM<'a> { +impl<'a, A: adc::AdcChannel<'a>> adc::Client for TemperatureSTM<'a, A> { fn sample_ready(&self, sample: u16) { self.status.set(Status::Idle); self.temperature_client.map(|client| { - client.callback( - ((((self.v_25 - (sample as f32 * 3.3 / 4095.0)) * 1000.0 / self.slope) + 25.0) - * 100.0) as usize, - ); + client.callback(Ok( + ((((self.v_25 - (sample as f32 * 3.3 / 65535.0)) * 1000.0 / self.slope) + 25.0) + * 100.0) as i32, + )); }); } } -impl<'a> sensors::TemperatureDriver<'a> for TemperatureSTM<'a> { +impl<'a, A: adc::AdcChannel<'a>> sensors::TemperatureDriver<'a> for TemperatureSTM<'a, A> { fn set_client(&self, temperature_client: &'a dyn sensors::TemperatureClient) { self.temperature_client.replace(temperature_client); } diff --git a/capsules/src/test/aes.rs b/capsules/extra/src/test/aes.rs similarity index 86% rename from capsules/src/test/aes.rs rename to capsules/extra/src/test/aes.rs index 0df552ee73..963f0182bb 100644 --- a/capsules/src/test/aes.rs +++ b/capsules/extra/src/test/aes.rs @@ -1,11 +1,17 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test the AES hardware. +use capsules_core::test::capsule_test::{CapsuleTest, CapsuleTestClient}; use core::cell::Cell; use kernel::debug; use kernel::hil; use kernel::hil::symmetric_encryption::{ AES128Ctr, AES128, AES128CBC, AES128ECB, AES128_BLOCK_SIZE, AES128_KEY_SIZE, }; +use kernel::utilities::cells::OptionalCell; use kernel::utilities::cells::TakeCell; pub struct TestAes128Ctr<'a, A: 'a> { @@ -15,9 +21,12 @@ pub struct TestAes128Ctr<'a, A: 'a> { iv: TakeCell<'a, [u8]>, source: TakeCell<'static, [u8]>, data: TakeCell<'static, [u8]>, + test_decrypt: bool, encrypting: Cell, use_source: Cell, + + client: OptionalCell<&'static dyn CapsuleTestClient>, } pub struct TestAes128Cbc<'a, A: 'a> { @@ -27,9 +36,12 @@ pub struct TestAes128Cbc<'a, A: 'a> { iv: TakeCell<'a, [u8]>, source: TakeCell<'static, [u8]>, data: TakeCell<'static, [u8]>, + test_decrypt: bool, encrypting: Cell, use_source: Cell, + + client: OptionalCell<&'static dyn CapsuleTestClient>, } pub struct TestAes128Ecb<'a, A: 'a> { @@ -38,9 +50,12 @@ pub struct TestAes128Ecb<'a, A: 'a> { key: TakeCell<'a, [u8]>, source: TakeCell<'static, [u8]>, data: TakeCell<'static, [u8]>, + test_decrypt: bool, encrypting: Cell, use_source: Cell, + + client: OptionalCell<&'static dyn CapsuleTestClient>, } const DATA_OFFSET: usize = AES128_BLOCK_SIZE; @@ -52,16 +67,20 @@ impl<'a, A: AES128<'a> + AES128ECB> TestAes128Ecb<'a, A> { key: &'a mut [u8], source: &'static mut [u8], data: &'static mut [u8], + test_decrypt: bool, ) -> Self { TestAes128Ecb { - aes: aes, + aes, key: TakeCell::new(key), source: TakeCell::new(source), data: TakeCell::new(data), + test_decrypt, encrypting: Cell::new(true), use_source: Cell::new(true), + + client: OptionalCell::empty(), } } @@ -135,6 +154,12 @@ impl<'a, A: AES128<'a> + AES128ECB> TestAes128Ecb<'a, A> { } } +impl<'a, A: AES128<'a> + AES128ECB> CapsuleTest for TestAes128Ecb<'a, A> { + fn set_client(&self, client: &'static dyn CapsuleTestClient) { + self.client.set(client); + } +} + impl<'a, A: AES128<'a> + AES128Ctr> TestAes128Ctr<'a, A> { pub fn new( aes: &'a A, @@ -142,17 +167,21 @@ impl<'a, A: AES128<'a> + AES128Ctr> TestAes128Ctr<'a, A> { iv: &'a mut [u8], source: &'static mut [u8], data: &'static mut [u8], + test_decrypt: bool, ) -> Self { TestAes128Ctr { - aes: aes, + aes, key: TakeCell::new(key), iv: TakeCell::new(iv), source: TakeCell::new(source), data: TakeCell::new(data), + test_decrypt, encrypting: Cell::new(true), use_source: Cell::new(true), + + client: OptionalCell::empty(), } } @@ -280,13 +309,29 @@ impl<'a, A: AES128<'a> + AES128Ctr> hil::symmetric_encryption::Client<'a> for Te self.aes.disable(); // Continue testing with other configurations - if self.encrypting.get() { - self.encrypting.set(false); + if self.use_source.get() { + self.use_source.set(false); self.run(); + } else { + if self.encrypting.get() && self.test_decrypt { + self.encrypting.set(false); + self.use_source.set(true); + self.run(); + } else { + self.client.map(|client| { + client.done(Ok(())); + }); + } } } } +impl<'a, A: AES128<'a> + AES128Ctr> CapsuleTest for TestAes128Ctr<'a, A> { + fn set_client(&self, client: &'static dyn CapsuleTestClient) { + self.client.set(client); + } +} + impl<'a, A: AES128<'a> + AES128CBC> TestAes128Cbc<'a, A> { pub fn new( aes: &'a A, @@ -294,17 +339,21 @@ impl<'a, A: AES128<'a> + AES128CBC> TestAes128Cbc<'a, A> { iv: &'a mut [u8], source: &'static mut [u8], data: &'static mut [u8], + test_decrypt: bool, ) -> Self { TestAes128Cbc { - aes: aes, + aes, key: TakeCell::new(key), iv: TakeCell::new(iv), source: TakeCell::new(source), data: TakeCell::new(data), + test_decrypt, encrypting: Cell::new(true), use_source: Cell::new(true), + + client: OptionalCell::empty(), } } @@ -431,19 +480,29 @@ impl<'a, A: AES128<'a> + AES128CBC> hil::symmetric_encryption::Client<'a> for Te self.aes.disable(); // Continue testing with other configurations - if self.encrypting.get() { - self.encrypting.set(false); + if self.use_source.get() { + self.use_source.set(false); self.run(); } else { - if self.use_source.get() { - self.use_source.set(false); - self.encrypting.set(true); + if self.encrypting.get() && self.test_decrypt { + self.encrypting.set(false); + self.use_source.set(true); self.run(); + } else { + self.client.map(|client| { + client.done(Ok(())); + }); } } } } +impl<'a, A: AES128<'a> + AES128CBC> CapsuleTest for TestAes128Cbc<'a, A> { + fn set_client(&self, client: &'static dyn CapsuleTestClient) { + self.client.set(client); + } +} + impl<'a, A: AES128<'a> + AES128ECB> hil::symmetric_encryption::Client<'a> for TestAes128Ecb<'a, A> { fn crypt_done(&'a self, source: Option<&'static mut [u8]>, dest: &'static mut [u8]) { if self.use_source.get() { @@ -486,14 +545,18 @@ impl<'a, A: AES128<'a> + AES128ECB> hil::symmetric_encryption::Client<'a> for Te self.aes.disable(); // Continue testing with other configurations - if self.encrypting.get() { - self.encrypting.set(false); + if self.use_source.get() { + self.use_source.set(false); self.run(); } else { - if self.use_source.get() { - self.use_source.set(false); - self.encrypting.set(true); + if self.encrypting.get() && self.test_decrypt { + self.encrypting.set(false); + self.use_source.set(true); self.run(); + } else { + self.client.map(|client| { + client.done(Ok(())); + }); } } } diff --git a/capsules/src/test/aes_ccm.rs b/capsules/extra/src/test/aes_ccm.rs similarity index 97% rename from capsules/src/test/aes_ccm.rs rename to capsules/extra/src/test/aes_ccm.rs index bcf3f8c93f..ae463198d2 100644 --- a/capsules/src/test/aes_ccm.rs +++ b/capsules/extra/src/test/aes_ccm.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test the AES CCM implementation on top of AES hardware. use core::cell::Cell; @@ -27,7 +31,7 @@ pub struct Test<'a, A: AES128CCM<'a>> { impl<'a, A: AES128CCM<'a>> Test<'a, A> { pub fn new(aes_ccm: &'a A, buf: &'static mut [u8]) -> Test<'a, A> { Test { - aes_ccm: aes_ccm, + aes_ccm, buf: TakeCell::new(buf), current_test: Cell::new(0), encrypting: Cell::new(true), @@ -97,7 +101,7 @@ impl<'a, A: AES128CCM<'a>> Test<'a, A> { buf[m_off..m_off + m_len + mic_len].copy_from_slice(c_data); } - if self.aes_ccm.set_key(&KEY) != Ok(()) || self.aes_ccm.set_nonce(&nonce) != Ok(()) { + if self.aes_ccm.set_key(&KEY) != Ok(()) || self.aes_ccm.set_nonce(nonce) != Ok(()) { panic!("aes_ccm_test failed: cannot set key or nonce."); } diff --git a/capsules/extra/src/test/aes_gcm.rs b/capsules/extra/src/test/aes_gcm.rs new file mode 100644 index 0000000000..48c15d62e0 --- /dev/null +++ b/capsules/extra/src/test/aes_gcm.rs @@ -0,0 +1,234 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Test the AES GCM implementation on top of AES hardware. + +use core::cell::Cell; +use kernel::debug; +use kernel::hil::symmetric_encryption::{GCMClient, AES128GCM, AES128_KEY_SIZE}; +use kernel::utilities::cells::TakeCell; +use kernel::ErrorCode; + +pub struct Test<'a, A: AES128GCM<'a>> { + aes_gcm: &'a A, + + buf: TakeCell<'static, [u8]>, + current_test: Cell, + encrypting: Cell, + + // (key, iv, pt, aad, ct, tag) + tests: [( + &'static [u8], + &'static [u8], + &'static [u8], + &'static [u8], + &'static [u8], + &'static [u8], + ); 2], +} + +impl<'a, A: AES128GCM<'a>> Test<'a, A> { + pub fn new(aes_gcm: &'a A, buf: &'static mut [u8]) -> Test<'a, A> { + Test { + aes_gcm, + buf: TakeCell::new(buf), + current_test: Cell::new(0), + encrypting: Cell::new(true), + tests: [ + ( + &KEY_128_TWELVE, + &IV_128_TWELVE, + &[], + &AAD_128_TWELVE, + &[], + &TAG_128_TWELVE, + ), + ( + &KEY_128_THIRTEEN, + &IV_128_THIRTEEN, + &PT_128_THIRTEEN, + &[], + &CT_128_THIRTEEN, + &TAG_128_THIRTEEN, + ), + ], + } + } + + pub fn run(&self) { + debug!("AES GCM* encryption/decryption tests"); + self.trigger_test(); + } + + fn next_test(&self) -> bool { + if self.encrypting.get() { + self.encrypting.set(false); + } else { + self.encrypting.set(true); + self.current_test.set(self.current_test.get() + 1); + if self.current_test.get() >= self.tests.len() { + return false; + } + } + true + } + + fn trigger_test(&self) { + let (key, iv, pt, aad, ct, tag) = self.tests[self.current_test.get()]; + let (aad_off, pt_off, pt_len) = (0, aad.len(), pt.len()); + let encrypting = self.encrypting.get(); + + let buf = match self.buf.take() { + None => panic!("aes_gcm_test failed: buffer is not present in trigger_test."), + Some(buf) => buf, + }; + + if encrypting { + buf[aad_off..pt_off].copy_from_slice(aad); + buf[pt_off..pt_off + pt_len].copy_from_slice(pt); + } else { + buf[aad_off..pt_off].copy_from_slice(aad); + buf[pt_off..pt_off + pt_len].copy_from_slice(ct); + buf[pt_off + pt_len..(pt_off + pt_len + tag.len())].copy_from_slice(tag); + } + + if self.aes_gcm.set_key(key) != Ok(()) { + panic!("aes_gcm_test failed: cannot set key."); + } + + if self.aes_gcm.set_iv(iv) != Ok(()) { + panic!("aes_gcm_test failed: cannot set IV."); + } + + let _ = self + .aes_gcm + .crypt(buf, aad_off, pt_off, pt_len, encrypting) + .map_err(|(_code, buf)| { + self.buf.replace(buf); + panic!("Failed to start test."); + }); + } + + fn check_test(&self, tag_is_valid: bool) { + let (_key, _iv, pt, aad, ct, tag) = self.tests[self.current_test.get()]; + let (_aad_off, pt_off, pt_len) = (0, aad.len(), pt.len()); + let encrypting = self.encrypting.get(); + + let buf = match self.buf.take() { + None => panic!("aes_gcm_test failed: buffer is not present in check_test."), + Some(buf) => buf, + }; + + if encrypting { + let ct_matches = buf[pt_off..(pt_off + pt_len)] + .iter() + .zip(ct.iter()) + .all(|(a, b)| *a == *b); + let tag_matches = buf[(pt_off + pt_len)..(pt_off + pt_len + tag.len())] + .iter() + .zip(tag.iter()) + .all(|(a, b)| *a == *b); + + if ct_matches && tag_matches && tag_is_valid { + debug!( + "aes_gcm_test passed: (current_test={}, encrypting={}, tag_is_valid={})", + self.current_test.get(), + self.encrypting.get(), + tag_is_valid + ); + } else { + panic!("aes_gcm_test failed: ct_matches={}, tag_matches={}, (current_test={}, encrypting={}, tag_is_valid={}", + ct_matches, + tag_matches, + self.current_test.get(), + self.encrypting.get(), + tag_is_valid); + } + } else { + let pt_matches = buf[pt_off..(pt_off + pt_len)] + .iter() + .zip(pt.iter()) + .all(|(a, b)| *a == *b); + let tag_matches = buf[(pt_off + pt_len)..(pt_off + pt_len + tag.len())] + .iter() + .zip(tag.iter()) + .all(|(a, b)| *a == *b); + + if pt_matches && tag_matches && tag_is_valid { + debug!( + "aes_gcm_test passed: (current_test={}, encrypting={}, tag_is_valid={})", + self.current_test.get(), + self.encrypting.get(), + tag_is_valid + ); + } else { + panic!("aes_gcm_test failed: pt_matches={}, tag_matches={}, (current_test={}, encrypting={}, tag_is_valid={}", + pt_matches, + tag_matches, + self.current_test.get(), + self.encrypting.get(), + tag_is_valid); + } + } + + self.buf.replace(buf); + } +} + +impl<'a, A: AES128GCM<'a>> GCMClient for Test<'a, A> { + fn crypt_done(&self, buf: &'static mut [u8], res: Result<(), ErrorCode>, tag_is_valid: bool) { + self.buf.replace(buf); + if res != Ok(()) { + panic!("aes_gcm_test failed: crypt_done returned {:?}", res); + } else { + self.check_test(tag_is_valid); + if self.next_test() { + self.trigger_test() + } + } + } +} + +static KEY_128_TWELVE: [u8; AES128_KEY_SIZE] = [ + 0x26, 0x73, 0x0f, 0x1a, 0xd2, 0x4b, 0x76, 0xd6, 0x6f, 0x7a, 0xb8, 0x45, 0x9d, 0xdc, 0xd1, 0x17, +]; + +static IV_128_TWELVE: [u8; 12] = [ + 0x1f, 0xfb, 0x3e, 0x75, 0x71, 0xcb, 0x70, 0x14, 0x5e, 0xa5, 0x16, 0x53, +]; + +static AAD_128_TWELVE: [u8; 90] = [ + 0xbf, 0xc3, 0xa8, 0x08, 0xc0, 0x60, 0xcd, 0xfd, 0x2a, 0xb7, 0x69, 0x1b, 0x32, 0x4a, 0xb3, 0x59, + 0x29, 0xe8, 0x0f, 0x26, 0x2b, 0xf3, 0xb9, 0x4c, 0xc2, 0xf4, 0x5c, 0x62, 0xbb, 0x0f, 0x32, 0xbc, + 0x4e, 0x4b, 0x96, 0x73, 0x69, 0x11, 0x0a, 0x7b, 0x4c, 0x47, 0x82, 0x7e, 0x93, 0xa9, 0xec, 0xd7, + 0xfc, 0xda, 0x5e, 0x6a, 0x97, 0x39, 0xa0, 0xd1, 0x78, 0x6d, 0x6d, 0xc7, 0xa4, 0x5c, 0x9c, 0x1e, + 0x8e, 0xcc, 0x8f, 0x90, 0xdc, 0x70, 0xbc, 0x5a, 0x5a, 0xe1, 0xa0, 0x31, 0x3f, 0xd6, 0xef, 0x87, + 0xd7, 0xb3, 0x6e, 0x3d, 0x48, 0xc4, 0x44, 0x8f, 0x70, 0x3e, +]; + +static TAG_128_TWELVE: [u8; 14] = [ + 0x45, 0xa9, 0xbe, 0x4c, 0x84, 0x9e, 0xcb, 0x25, 0x85, 0x42, 0x1a, 0x1f, 0x08, 0xe6, +]; + +static KEY_128_THIRTEEN: [u8; AES128_KEY_SIZE] = [ + 0x8f, 0x85, 0xd3, 0x66, 0x16, 0xa9, 0x5f, 0xc1, 0x05, 0x86, 0xc3, 0x16, 0xb3, 0x05, 0x37, 0x70, +]; + +static IV_128_THIRTEEN: [u8; 12] = [ + 0xd3, 0x20, 0xb5, 0x00, 0x26, 0x96, 0x09, 0xac, 0xe1, 0xbe, 0x67, 0xce, +]; + +static PT_128_THIRTEEN: [u8; 32] = [ + 0x3a, 0x75, 0x8e, 0xe0, 0x72, 0xfc, 0x70, 0xa6, 0x42, 0x75, 0xb5, 0x6e, 0x72, 0xcb, 0x23, 0xa1, + 0x59, 0x04, 0x58, 0x9c, 0xef, 0xbe, 0xeb, 0x58, 0x48, 0xec, 0x53, 0xff, 0xc0, 0x6c, 0x7a, 0x5d, +]; + +static CT_128_THIRTEEN: [u8; 32] = [ + 0xfb, 0x2f, 0xe3, 0xeb, 0x40, 0xed, 0xfb, 0xd2, 0x2a, 0x51, 0x6b, 0xec, 0x35, 0x9d, 0x4b, 0xb4, + 0x23, 0x8a, 0x07, 0x00, 0xa4, 0x6f, 0xee, 0x11, 0x36, 0xa0, 0x61, 0x85, 0x40, 0x22, 0x9c, 0x41, +]; + +static TAG_128_THIRTEEN: [u8; 16] = [ + 0x42, 0x26, 0x93, 0x16, 0xce, 0xce, 0x7d, 0x88, 0x2c, 0xc6, 0x8c, 0x3e, 0xd9, 0xd2, 0xf0, 0xae, +]; diff --git a/capsules/src/test/crc.rs b/capsules/extra/src/test/crc.rs similarity index 88% rename from capsules/src/test/crc.rs rename to capsules/extra/src/test/crc.rs index 208c3fdf51..424127aefe 100644 --- a/capsules/src/test/crc.rs +++ b/capsules/extra/src/test/crc.rs @@ -1,9 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test the CRC hardware. use kernel::debug; use kernel::hil::crc::{Client, Crc, CrcAlgorithm, CrcOutput}; use kernel::utilities::cells::TakeCell; -use kernel::utilities::leasable_buffer::LeasableBuffer; +use kernel::utilities::leasable_buffer::SubSliceMut; use kernel::ErrorCode; pub struct TestCrc<'a, C: 'a> { @@ -14,7 +18,7 @@ pub struct TestCrc<'a, C: 'a> { impl<'a, C: Crc<'a>> TestCrc<'a, C> { pub fn new(crc: &'a C, data: &'static mut [u8]) -> Self { TestCrc { - crc: crc, + crc, data: TakeCell::new(data), } } @@ -25,7 +29,7 @@ impl<'a, C: Crc<'a>> TestCrc<'a, C> { debug!("CrcTest ERROR: failed to set algorithm to Crc32: {:?}", res); return; } - let leasable: LeasableBuffer<'static, u8> = LeasableBuffer::new(self.data.take().unwrap()); + let leasable: SubSliceMut<'static, u8> = SubSliceMut::new(self.data.take().unwrap()); let res = self.crc.input(leasable); if let Err((error, _buffer)) = res { @@ -42,7 +46,7 @@ impl<'a, C: Crc<'a>> TestCrc<'a, C> { } impl<'a, C: Crc<'a>> Client for TestCrc<'a, C> { - fn input_done(&self, result: Result<(), ErrorCode>, buffer: LeasableBuffer<'static, u8>) { + fn input_done(&self, result: Result<(), ErrorCode>, buffer: SubSliceMut<'static, u8>) { if result.is_err() { debug!("CrcTest ERROR: failed to process input: {:?}", result); return; diff --git a/capsules/extra/src/test/hmac_sha256.rs b/capsules/extra/src/test/hmac_sha256.rs new file mode 100644 index 0000000000..a9d0ce3dd7 --- /dev/null +++ b/capsules/extra/src/test/hmac_sha256.rs @@ -0,0 +1,129 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Test the software implementation of HMAC-SHA256 by performing a hash and +//! checking it against the expected hash value. + +use crate::hmac_sha256::HmacSha256Software; +use crate::sha256::Sha256Software; +use capsules_core::test::capsule_test::{CapsuleTest, CapsuleTestClient, CapsuleTestError}; +use kernel::hil::digest; +use kernel::hil::digest::HmacSha256; +use kernel::hil::digest::{DigestData, DigestHash}; +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::cells::TakeCell; +use kernel::utilities::leasable_buffer::SubSlice; +use kernel::utilities::leasable_buffer::SubSliceMut; +use kernel::ErrorCode; + +pub struct TestHmacSha256 { + hmac: &'static HmacSha256Software<'static, Sha256Software<'static>>, + key: TakeCell<'static, [u8]>, // The key to use for HMAC + data: TakeCell<'static, [u8]>, // The data to hash + digest: TakeCell<'static, [u8; 32]>, // The supplied hash + correct: &'static [u8; 32], // The supplied hash + client: OptionalCell<&'static dyn CapsuleTestClient>, +} + +impl TestHmacSha256 { + pub fn new( + hmac: &'static HmacSha256Software<'static, Sha256Software<'static>>, + key: &'static mut [u8], + data: &'static mut [u8], + digest: &'static mut [u8; 32], + correct: &'static [u8; 32], + ) -> Self { + TestHmacSha256 { + hmac, + key: TakeCell::new(key), + data: TakeCell::new(data), + digest: TakeCell::new(digest), + correct, + client: OptionalCell::empty(), + } + } + + pub fn run(&'static self) { + kernel::hil::digest::Digest::set_client(self.hmac, self); + + let key = self.key.take().unwrap(); + let r = self.hmac.set_mode_hmacsha256(key); + if r.is_err() { + panic!("HmacSha256Test: failed to set key: {:?}", r); + } + let data = self.data.take().unwrap(); + let buffer = SubSliceMut::new(data); + let r = self.hmac.add_mut_data(buffer); + if r.is_err() { + panic!("HmacSha256Test: failed to add data: {:?}", r); + } + } +} + +impl digest::ClientData<32> for TestHmacSha256 { + fn add_data_done(&self, _result: Result<(), ErrorCode>, _data: SubSlice<'static, u8>) { + unimplemented!() + } + + fn add_mut_data_done(&self, result: Result<(), ErrorCode>, data: SubSliceMut<'static, u8>) { + self.data.replace(data.take()); + + match result { + Ok(()) => {} + Err(e) => { + kernel::debug!("HmacSha256Test: failed to add data: {:?}", e); + self.client.map(|client| { + client.done(Err(CapsuleTestError::ErrorCode(e))); + }); + return; + } + } + + let r = self.hmac.run(self.digest.take().unwrap()); + match r { + Ok(()) => {} + Err((e, d)) => { + kernel::debug!("HmacSha256Test: failed to run HMAC: {:?}", e); + + self.digest.replace(d); + self.client.map(|client| { + client.done(Err(CapsuleTestError::ErrorCode(e))); + }); + } + } + } +} + +impl digest::ClientHash<32> for TestHmacSha256 { + fn hash_done(&self, _result: Result<(), ErrorCode>, digest: &'static mut [u8; 32]) { + let mut error = false; + for i in 0..32 { + if self.correct[i] != digest[i] { + error = true; + } + } + if !error { + kernel::debug!("HMAC-SHA256 matches!"); + self.client.map(|client| { + client.done(Ok(())); + }); + } else { + kernel::debug!("HmacSha256Test: incorrect HMAC output!"); + self.client.map(|client| { + client.done(Err(CapsuleTestError::IncorrectResult)); + }); + } + } +} + +impl digest::ClientVerify<32> for TestHmacSha256 { + fn verification_done(&self, _result: Result, _compare: &'static mut [u8; 32]) { + } +} + +impl CapsuleTest for TestHmacSha256 { + fn set_client(&self, client: &'static dyn CapsuleTestClient) { + self.client.set(client); + } +} diff --git a/capsules/src/test/kv_system.rs b/capsules/extra/src/test/kv_system.rs similarity index 68% rename from capsules/src/test/kv_system.rs rename to capsules/extra/src/test/kv_system.rs index beccfa8d97..a1f7160fbe 100644 --- a/capsules/src/test/kv_system.rs +++ b/capsules/extra/src/test/kv_system.rs @@ -1,35 +1,17 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Test for Tock KV System capsules. //! -//! This capsule implements the tests for KV system libraies in Tock. +//! This capsule implements the tests for KV system libraries in Tock. //! This is originally written to test TicKV. //! -//! +-----------------------+ -//! | | -//! | Capsule using K-V | -//! | | -//! +-----------------------+ -//! -//! hil::kv_store -//! -//! +-----------------------+ -//! | | -//! | K-V in Tock | -//! | | -//! +-----------------------+ -//! -//! hil::kv_system -//! -//! +-----------------------+ -//! | | -//! | TicKV (this file) | -//! | | -//! +-----------------------+ -//! //! hil::flash //! //! The tests can be enabled by adding this line to the `main()` //! -//! ```rust +//! ```rust,ignore //! tickv_test::run_tickv_tests(kvstore) //! ``` //! @@ -38,8 +20,8 @@ //! ```text //! ---Starting TicKV Tests--- //! Key: [18, 52, 86, 120, 154, 188, 222, 240] with value [16, 32, 48] was added -//! Now retriving the key -//! Key: [18, 52, 86, 120, 154, 188, 222, 240] with value [16, 32, 48, 0] was retrived +//! Now retrieving the key +//! Key: [18, 52, 86, 120, 154, 188, 222, 240] with value [16, 32, 48, 0] was retrieved //! Removed Key: [18, 52, 86, 120, 154, 188, 222, 240] //! Try to read removed key: [18, 52, 86, 120, 154, 188, 222, 240] //! Unable to find key: [18, 52, 86, 120, 154, 188, 222, 240] @@ -48,11 +30,12 @@ //! ---Finished TicKV Tests--- //! ``` +use crate::tickv::{KVSystem, KVSystemClient, KeyType}; use core::cell::Cell; use core::marker::PhantomData; use kernel::debug; -use kernel::hil::kv_system::{self, KVSystem, KeyType}; -use kernel::utilities::cells::TakeCell; +use kernel::utilities::cells::{MapCell, TakeCell}; +use kernel::utilities::leasable_buffer::SubSliceMut; use kernel::ErrorCode; #[derive(Clone, Copy, PartialEq)] @@ -64,47 +47,64 @@ enum CurrentState { pub struct KVSystemTest<'a, S: KVSystem<'static>, T: KeyType> { kv_system: &'a S, phantom: PhantomData<&'a T>, + value: MapCell>, ret_buffer: TakeCell<'static, [u8]>, state: Cell, } impl<'a, S: KVSystem<'static>, T: KeyType> KVSystemTest<'a, S, T> { - pub fn new(kv_system: &'a S, static_buf: &'static mut [u8; 4]) -> KVSystemTest<'a, S, T> { + pub fn new( + kv_system: &'a S, + value: SubSliceMut<'static, u8>, + static_buf: &'static mut [u8; 4], + ) -> KVSystemTest<'a, S, T> { debug!("---Starting TicKV Tests---"); Self { - kv_system: kv_system, + kv_system, phantom: PhantomData, + value: MapCell::new(value), ret_buffer: TakeCell::new(static_buf), state: Cell::new(CurrentState::Normal), } } } -impl<'a, S: KVSystem<'static, K = T>, T: KeyType + core::fmt::Debug> kv_system::Client +impl<'a, S: KVSystem<'static, K = T>, T: KeyType + core::fmt::Debug> KVSystemClient for KVSystemTest<'a, S, T> { fn generate_key_complete( &self, - _result: Result<(), ErrorCode>, - _unhashed_key: &'static [u8], - _key_buf: &'static T, + result: Result<(), ErrorCode>, + _unhashed_key: SubSliceMut<'static, u8>, + key_buf: &'static mut T, ) { - unimplemented!() + match result { + Ok(()) => { + debug!("Generated key: {:?}", key_buf); + debug!("Now appending the key"); + self.kv_system + .append_key(key_buf, self.value.take().unwrap()) + .unwrap(); + } + Err(e) => { + panic!("Error adding key: {:?}", e); + } + } } fn append_key_complete( &self, result: Result<(), ErrorCode>, key: &'static mut T, - value: &'static [u8], + value: SubSliceMut<'static, u8>, ) { match result { Ok(()) => { debug!("Key: {:?} with value {:?} was added", key, value); - debug!("Now retriving the key"); + debug!("Now retrieving the key"); self.kv_system - .get_value(key, self.ret_buffer.take().unwrap()) + .get_value(key, SubSliceMut::new(self.ret_buffer.take().unwrap())) .unwrap(); } Err(e) => { @@ -117,12 +117,12 @@ impl<'a, S: KVSystem<'static, K = T>, T: KeyType + core::fmt::Debug> kv_system:: &self, result: Result<(), ErrorCode>, key: &'static mut T, - ret_buf: &'static mut [u8], + ret_buf: SubSliceMut<'static, u8>, ) { match result { Ok(()) => { - debug!("Key: {:?} with value {:?} was retrived", key, ret_buf); - self.ret_buffer.replace(ret_buf); + debug!("Key: {:?} with value {:?} was retrieved", key, ret_buf); + self.ret_buffer.replace(ret_buf.take()); self.kv_system.invalidate_key(key).unwrap(); } Err(e) => { @@ -148,7 +148,7 @@ impl<'a, S: KVSystem<'static, K = T>, T: KeyType + core::fmt::Debug> kv_system:: debug!("Try to read removed key: {:?}", key); self.state.set(CurrentState::ExpectGetValueFail); self.kv_system - .get_value(key, self.ret_buffer.take().unwrap()) + .get_value(key, SubSliceMut::new(self.ret_buffer.take().unwrap())) .unwrap(); } Err(e) => { diff --git a/capsules/extra/src/test/mod.rs b/capsules/extra/src/test/mod.rs new file mode 100644 index 0000000000..a2687eed8a --- /dev/null +++ b/capsules/extra/src/test/mod.rs @@ -0,0 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +pub mod aes; +pub mod aes_ccm; +pub mod aes_gcm; +pub mod crc; +pub mod hmac_sha256; +pub mod kv_system; +pub mod sha256; +pub mod siphash24; +pub mod udp; diff --git a/capsules/extra/src/test/sha256.rs b/capsules/extra/src/test/sha256.rs new file mode 100644 index 0000000000..144e9956de --- /dev/null +++ b/capsules/extra/src/test/sha256.rs @@ -0,0 +1,146 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Test the software implementation of SHA256 by performing a hash +//! and checking it against the expected hash value. It uses +//! DigestData::add_date and DigestVerify::verify through the +//! Digest trait. + +use core::cell::Cell; +use core::cmp; + +use crate::sha256::Sha256Software; +use capsules_core::test::capsule_test::{CapsuleTest, CapsuleTestClient}; +use kernel::debug; +use kernel::hil::digest; +use kernel::hil::digest::{Digest, DigestData, DigestVerify}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::utilities::leasable_buffer::SubSlice; +use kernel::utilities::leasable_buffer::SubSliceMut; +use kernel::ErrorCode; + +pub struct TestSha256 { + sha: &'static Sha256Software<'static>, + data: TakeCell<'static, [u8]>, // The data to hash + hash: TakeCell<'static, [u8; 32]>, // The supplied hash + position: Cell, // Keep track of position in data + correct: Cell, // Whether supplied hash is correct + client: OptionalCell<&'static dyn CapsuleTestClient>, +} + +// We add data in chunks of 12 bytes to ensure that the underlying +// buffering mechanism works correctly (it can handle filling blocks +// as well as zeroing out incomplete blocks). +const CHUNK_SIZE: usize = 12; + +impl TestSha256 { + pub fn new( + sha: &'static Sha256Software<'static>, + data: &'static mut [u8], + hash: &'static mut [u8; 32], + correct: bool, + ) -> Self { + TestSha256 { + sha, + data: TakeCell::new(data), + hash: TakeCell::new(hash), + position: Cell::new(0), + correct: Cell::new(correct), + client: OptionalCell::empty(), + } + } + + pub fn run(&'static self) { + self.sha.set_client(self); + let data = self.data.take().unwrap(); + let chunk_size = cmp::min(CHUNK_SIZE, data.len()); + self.position.set(chunk_size); + let mut buffer = SubSliceMut::new(data); + buffer.slice(0..chunk_size); + let r = self.sha.add_mut_data(buffer); + if r.is_err() { + panic!("Sha256Test: failed to add data: {:?}", r); + } + } +} + +impl digest::ClientData<32> for TestSha256 { + fn add_data_done(&self, _result: Result<(), ErrorCode>, _data: SubSlice<'static, u8>) { + unimplemented!() + } + + fn add_mut_data_done(&self, result: Result<(), ErrorCode>, mut data: SubSliceMut<'static, u8>) { + if data.len() != 0 { + let r = self.sha.add_mut_data(data); + if r.is_err() { + panic!("Sha256Test: failed to add data: {:?}", r); + } + } else { + data.reset(); + if self.position.get() < data.len() { + let new_position = cmp::min(data.len(), self.position.get() + CHUNK_SIZE); + data.slice(self.position.get()..new_position); + debug!( + "Sha256Test: Setting slice to {}..{}", + self.position.get(), + new_position + ); + let r = self.sha.add_mut_data(data); + if r.is_err() { + panic!("Sha256Test: failed to add data: {:?}", r); + } + self.position.set(new_position); + } else { + data.reset(); + self.data.put(Some(data.take())); + match result { + Ok(()) => { + let v = self.sha.verify(self.hash.take().unwrap()); + if v.is_err() { + panic!("Sha256Test: failed to verify: {:?}", v); + } + } + Err(e) => { + panic!("Sha256Test: adding data failed: {:?}", e); + } + } + } + } + } +} + +impl digest::ClientVerify<32> for TestSha256 { + fn verification_done(&self, result: Result, compare: &'static mut [u8; 32]) { + self.hash.put(Some(compare)); + debug!("Sha256Test: Verification result: {:?}", result); + match result { + Ok(success) => { + if success != self.correct.get() { + panic!( + "Sha256Test: Verification should have been {}, was {}", + self.correct.get(), + success + ); + } else { + self.client.map(|client| { + client.done(Ok(())); + }); + } + } + Err(e) => { + panic!("Sha256Test: Error in verification: {:?}", e); + } + } + } +} + +impl digest::ClientHash<32> for TestSha256 { + fn hash_done(&self, _result: Result<(), ErrorCode>, _digest: &'static mut [u8; 32]) {} +} + +impl CapsuleTest for TestSha256 { + fn set_client(&self, client: &'static dyn CapsuleTestClient) { + self.client.set(client); + } +} diff --git a/capsules/extra/src/test/siphash24.rs b/capsules/extra/src/test/siphash24.rs new file mode 100644 index 0000000000..7cc6f3870d --- /dev/null +++ b/capsules/extra/src/test/siphash24.rs @@ -0,0 +1,88 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Test the software implementation of SipHash24 by performing a hash +//! and checking it against the expected hash value. It uses +//! DigestData::add_date and DigestVerify::verify through the +//! Digest trait. + +use crate::sip_hash::SipHasher24; +use capsules_core::test::capsule_test::{CapsuleTest, CapsuleTestClient, CapsuleTestError}; +use kernel::hil::hasher::{Client, Hasher}; +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::cells::TakeCell; +use kernel::utilities::leasable_buffer::{SubSlice, SubSliceMut}; +use kernel::ErrorCode; + +pub struct TestSipHash24 { + hasher: &'static SipHasher24<'static>, + data: TakeCell<'static, [u8]>, // The data to hash + hash: TakeCell<'static, [u8; 8]>, // The supplied hash + correct_hash: TakeCell<'static, [u8; 8]>, // The correct hash + client: OptionalCell<&'static dyn CapsuleTestClient>, +} + +impl TestSipHash24 { + pub fn new( + hasher: &'static SipHasher24<'static>, + data: &'static mut [u8], + hash: &'static mut [u8; 8], + correct_hash: &'static mut [u8; 8], + ) -> Self { + TestSipHash24 { + hasher, + data: TakeCell::new(data), + hash: TakeCell::new(hash), + correct_hash: TakeCell::new(correct_hash), + client: OptionalCell::empty(), + } + } + + pub fn run(&'static self) { + self.hasher.set_client(self); + let data = self.data.take().unwrap(); + let buffer = SubSliceMut::new(data); + let r = self.hasher.add_mut_data(buffer); + if r.is_err() { + panic!("SipHash24Test: failed to add data: {:?}", r); + } + } +} + +impl Client<8> for TestSipHash24 { + fn add_mut_data_done(&self, _result: Result<(), ErrorCode>, data: SubSliceMut<'static, u8>) { + self.data.replace(data.take()); + self.hasher.run(self.hash.take().unwrap()).unwrap(); + } + + fn add_data_done(&self, _result: Result<(), ErrorCode>, _data: SubSlice<'static, u8>) {} + + fn hash_done(&self, _result: Result<(), ErrorCode>, digest: &'static mut [u8; 8]) { + let correct = self.correct_hash.take().unwrap(); + + let matches = correct != digest; + if !matches { + kernel::debug!("TestSipHash24: incorrect hash output!"); + } + kernel::debug!("TestSipHash24 matches!"); + + self.hash.replace(digest); + self.hasher.clear_data(); + + self.client.map(|client| { + let res = if matches { + Ok(()) + } else { + Err(CapsuleTestError::IncorrectResult) + }; + client.done(res); + }); + } +} + +impl CapsuleTest for TestSipHash24 { + fn set_client(&self, client: &'static dyn CapsuleTestClient) { + self.client.set(client); + } +} diff --git a/capsules/src/test/udp.rs b/capsules/extra/src/test/udp.rs similarity index 93% rename from capsules/src/test/udp.rs rename to capsules/extra/src/test/udp.rs index 3e026c3754..49c642fb14 100644 --- a/capsules/src/test/udp.rs +++ b/capsules/extra/src/test/udp.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Capsule used for testing in-kernel port binding, sending, and receiving. //! //! This capsule takes in a src port on which to receive/send from and a dst port to send to. @@ -15,7 +19,7 @@ use core::cell::Cell; use kernel::debug; use kernel::hil::time::{self, Alarm, Frequency}; use kernel::utilities::cells::MapCell; -use kernel::utilities::leasable_buffer::LeasableBuffer; +use kernel::utilities::leasable_buffer::SubSliceMut; use kernel::ErrorCode; pub const DST_ADDR: IPAddr = IPAddr([ @@ -33,7 +37,7 @@ pub struct MockUdp<'a, A: Alarm<'a>> { udp_sender: &'a dyn UDPSender<'a>, udp_receiver: &'a UDPReceiver<'a>, port_table: &'static UdpPortManager, - udp_dgram: MapCell>, + udp_dgram: MapCell>, src_port: Cell, dst_port: Cell, send_loop: Cell, @@ -47,16 +51,16 @@ impl<'a, A: Alarm<'a>> MockUdp<'a, A> { udp_sender: &'a dyn UDPSender<'a>, udp_receiver: &'a UDPReceiver<'a>, port_table: &'static UdpPortManager, - udp_dgram: LeasableBuffer<'static, u8>, + udp_dgram: SubSliceMut<'static, u8>, dst_port: u16, net_cap: &'static NetworkCapability, ) -> MockUdp<'a, A> { MockUdp { - id: id, - alarm: alarm, - udp_sender: udp_sender, - udp_receiver: udp_receiver, - port_table: port_table, + id, + alarm, + udp_sender, + udp_receiver, + port_table, udp_dgram: MapCell::new(udp_dgram), src_port: Cell::new(0), // invalid initial value dst_port: Cell::new(dst_port), @@ -164,7 +168,7 @@ impl<'a, A: Alarm<'a>> MockUdp<'a, A> { dgram, self.net_cap.get(), ) { - Ok(_) => Ok(()), + Ok(()) => Ok(()), Err(mut buf) => { buf.reset(); self.udp_dgram.replace(buf); @@ -189,7 +193,7 @@ impl<'a, A: Alarm<'a>> time::AlarmClient for MockUdp<'a, A> { } impl<'a, A: Alarm<'a>> UDPSendClient for MockUdp<'a, A> { - fn send_done(&self, result: Result<(), ErrorCode>, mut dgram: LeasableBuffer<'static, u8>) { + fn send_done(&self, result: Result<(), ErrorCode>, mut dgram: SubSliceMut<'static, u8>) { debug!("Mock UDP done sending. Result: {:?}", result); dgram.reset(); self.udp_dgram.replace(dgram); diff --git a/capsules/src/text_screen.rs b/capsules/extra/src/text_screen.rs similarity index 90% rename from capsules/src/text_screen.rs rename to capsules/extra/src/text_screen.rs index 8026add2c3..e59e381384 100644 --- a/capsules/src/text_screen.rs +++ b/capsules/extra/src/text_screen.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides userspace with access to the text screen. //! //! Usage: @@ -6,13 +10,12 @@ //! You need a screen that provides the `hil::text_screen::TextScreen` //! trait. //! -//! ```rust +//! ```rust,ignore //! let text_screen = components::text_screen::TextScreenComponent::new(board_kernel, lcd) //! .finalize(components::screen_buffer_size!(64)); //! ``` use core::cmp; -use core::convert::From; use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; use kernel::hil; @@ -22,14 +25,14 @@ use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::TextScreen as usize; /// Ids for read-only allow buffers mod ro_allow { pub const SHARED: usize = 0; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; + pub const COUNT: u8 = 1; } #[derive(Clone, Copy, PartialEq)] @@ -82,7 +85,7 @@ impl<'a> TextScreen<'a> { grant: Grant, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>, ) -> TextScreen<'a> { TextScreen { - text_screen: text_screen, + text_screen, apps: grant, current_app: OptionalCell::empty(), buffer: TakeCell::new(buffer), @@ -94,19 +97,19 @@ impl<'a> TextScreen<'a> { command: TextScreenCommand, data1: usize, data2: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { let res = self .apps - .enter(appid, |app, _| { + .enter(processid, |app, _| { if self.current_app.is_none() { - self.current_app.set(appid); + self.current_app.set(processid); app.data1 = data1; app.data2 = data2; app.command = command; Ok(true) } else { - if app.pending_command == true { + if app.pending_command { Err(ErrorCode::BUSY) } else { app.pending_command = true; @@ -144,7 +147,7 @@ impl<'a> TextScreen<'a> { let mut run_next = false; let res = self.current_app.map_or(Err(ErrorCode::FAIL), |app| { self.apps - .enter(*app, |app, kernel_data| match app.command { + .enter(app, |app, kernel_data| match app.command { TextScreenCommand::GetResolution => { let (x, y) = self.text_screen.get_size(); app.pending_command = false; @@ -211,11 +214,11 @@ impl<'a> TextScreen<'a> { fn run_next_command(&self) { // Check for pending events. for app in self.apps.iter() { - let appid = app.processid(); + let processid = app.processid(); let current_command = app.enter(|app, _| { if app.pending_command { app.pending_command = false; - self.current_app.set(appid); + self.current_app.set(processid); true } else { false @@ -232,8 +235,8 @@ impl<'a> TextScreen<'a> { } fn schedule_callback(&self, data1: usize, data2: usize, data3: usize) { - self.current_app.take().map(|appid| { - let _ = self.apps.enter(appid, |app, kernel_data| { + self.current_app.take().map(|processid| { + let _ = self.apps.enter(processid, |app, kernel_data| { app.pending_command = false; kernel_data.schedule_upcall(0, (data1, data2, data3)).ok(); }); @@ -247,33 +250,33 @@ impl<'a> SyscallDriver for TextScreen<'a> { command_num: usize, data1: usize, data2: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { match command_num { // This driver exists. 0 => CommandReturn::success(), // Get Resolution - 1 => self.enqueue_command(TextScreenCommand::GetResolution, data1, data2, appid), + 1 => self.enqueue_command(TextScreenCommand::GetResolution, data1, data2, processid), // Display - 2 => self.enqueue_command(TextScreenCommand::Display, data1, data2, appid), + 2 => self.enqueue_command(TextScreenCommand::Display, data1, data2, processid), // No Display - 3 => self.enqueue_command(TextScreenCommand::NoDisplay, data1, data2, appid), + 3 => self.enqueue_command(TextScreenCommand::NoDisplay, data1, data2, processid), // Blink - 4 => self.enqueue_command(TextScreenCommand::Blink, data1, data2, appid), + 4 => self.enqueue_command(TextScreenCommand::Blink, data1, data2, processid), // No Blink - 5 => self.enqueue_command(TextScreenCommand::NoBlink, data1, data2, appid), + 5 => self.enqueue_command(TextScreenCommand::NoBlink, data1, data2, processid), // Show Cursor - 6 => self.enqueue_command(TextScreenCommand::ShowCursor, data1, data2, appid), + 6 => self.enqueue_command(TextScreenCommand::ShowCursor, data1, data2, processid), // No Cursor - 7 => self.enqueue_command(TextScreenCommand::NoCursor, data1, data2, appid), + 7 => self.enqueue_command(TextScreenCommand::NoCursor, data1, data2, processid), // Write - 8 => self.enqueue_command(TextScreenCommand::Write, data1, data2, appid), + 8 => self.enqueue_command(TextScreenCommand::Write, data1, data2, processid), // Clear - 9 => self.enqueue_command(TextScreenCommand::Clear, data1, data2, appid), + 9 => self.enqueue_command(TextScreenCommand::Clear, data1, data2, processid), // Home - 10 => self.enqueue_command(TextScreenCommand::Home, data1, data2, appid), + 10 => self.enqueue_command(TextScreenCommand::Home, data1, data2, processid), //Set Curosr - 11 => self.enqueue_command(TextScreenCommand::SetCursor, data1, data2, appid), + 11 => self.enqueue_command(TextScreenCommand::SetCursor, data1, data2, processid), // NOSUPPORT _ => CommandReturn::failure(ErrorCode::NOSUPPORT), } diff --git a/capsules/extra/src/tickv.rs b/capsules/extra/src/tickv.rs new file mode 100644 index 0000000000..0f39ecc13f --- /dev/null +++ b/capsules/extra/src/tickv.rs @@ -0,0 +1,790 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Tock TicKV capsule. +//! +//! This capsule implements the TicKV library in Tock. This is done using the +//! TicKV library (libraries/tickv). +//! +//! This capsule interfaces with flash and exposes the Tock `tickv::kv_system` +//! interface to others. +//! +//! ```text +//! +-----------------------+ +//! | Capsule using K-V | +//! +-----------------------+ +//! +//! hil::kv::KV +//! +//! +-----------------------+ +//! | TickVKVStore | +//! +-----------------------+ +//! +//! capsules::tickv::KVSystem +//! +//! +-----------------------+ +//! | TicKV (this file) | +//! +-----------------------+ +//! | | +//! hil::flash | +//! +-----------------+ +//! | libraries/tickv | +//! +-----------------+ +//! ``` + +use core::cell::Cell; +use kernel::hil::flash::{self, Flash}; +use kernel::hil::hasher::{self, Hasher}; +use kernel::utilities::cells::{MapCell, OptionalCell, TakeCell}; +use kernel::utilities::leasable_buffer::{SubSlice, SubSliceMut}; +use kernel::ErrorCode; +use tickv::AsyncTicKV; + +/// The type of keys, this should define the output size of the digest +/// operations. +pub trait KeyType: Eq + Copy + Clone + Sized + AsRef<[u8]> + AsMut<[u8]> {} + +impl KeyType for [u8; 8] {} + +/// Implement this trait and use `set_client()` in order to receive callbacks. +pub trait KVSystemClient { + /// This callback is called when the append_key operation completes. + /// + /// - `result`: Nothing on success, 'ErrorCode' on error + /// - `unhashed_key`: The unhashed_key buffer + /// - `key_buf`: The key_buf buffer + fn generate_key_complete( + &self, + result: Result<(), ErrorCode>, + unhashed_key: SubSliceMut<'static, u8>, + key_buf: &'static mut K, + ); + + /// This callback is called when the append_key operation completes. + /// + /// - `result`: Nothing on success, 'ErrorCode' on error + /// - `key`: The key buffer + /// - `value`: The value buffer + fn append_key_complete( + &self, + result: Result<(), ErrorCode>, + key: &'static mut K, + value: SubSliceMut<'static, u8>, + ); + + /// This callback is called when the get_value operation completes. + /// + /// - `result`: Nothing on success, 'ErrorCode' on error + /// - `key`: The key buffer + /// - `ret_buf`: The ret_buf buffer + fn get_value_complete( + &self, + result: Result<(), ErrorCode>, + key: &'static mut K, + ret_buf: SubSliceMut<'static, u8>, + ); + + /// This callback is called when the invalidate_key operation completes. + /// + /// - `result`: Nothing on success, 'ErrorCode' on error + /// - `key`: The key buffer + fn invalidate_key_complete(&self, result: Result<(), ErrorCode>, key: &'static mut K); + + /// This callback is called when the garbage_collect operation completes. + /// + /// - `result`: Nothing on success, 'ErrorCode' on error + fn garbage_collect_complete(&self, result: Result<(), ErrorCode>); +} + +pub trait KVSystem<'a> { + /// The type of the hashed key. For example `[u8; 8]`. + type K: KeyType; + + /// Set the client. + fn set_client(&self, client: &'a dyn KVSystemClient); + + /// Generate key. + /// + /// - `unhashed_key`: A unhashed key that should be hashed. + /// - `key_buf`: A buffer to store the hashed key output. + /// + /// On success returns nothing. + /// On error the unhashed_key, key_buf and `Result<(), ErrorCode>` will be returned. + fn generate_key( + &self, + unhashed_key: SubSliceMut<'static, u8>, + key_buf: &'static mut Self::K, + ) -> Result<(), (SubSliceMut<'static, u8>, &'static mut Self::K, ErrorCode)>; + + /// Appends the key/value pair. + /// + /// If the key already exists in the store and has not been invalidated then + /// the append operation will fail. To update an existing key to a new value + /// the key must first be invalidated. + /// + /// - `key`: A hashed key. This key will be used in future to retrieve + /// or remove the `value`. + /// - `value`: A buffer containing the data to be stored to flash. + /// + /// On success nothing will be returned. + /// On error the key, value and a `Result<(), ErrorCode>` will be returned. + /// + /// The possible `Result<(), ErrorCode>`s are: + /// - `BUSY`: An operation is already in progress + /// - `INVAL`: An invalid parameter was passed + /// - `NODEVICE`: No KV store was setup + /// - `NOSUPPORT`: The key could not be added due to a collision. + /// - `NOMEM`: The key could not be added due to no more space. + fn append_key( + &self, + key: &'static mut Self::K, + value: SubSliceMut<'static, u8>, + ) -> Result<(), (&'static mut Self::K, SubSliceMut<'static, u8>, ErrorCode)>; + + /// Retrieves the value from a specified key. + /// + /// - `key`: A hashed key. This key will be used to retrieve the `value`. + /// - `ret_buf`: A buffer to store the value to. + /// + /// On success nothing will be returned. + /// On error the key, ret_buf and a `Result<(), ErrorCode>` will be returned. + /// + /// The possible `Result<(), ErrorCode>`s are: + /// - `BUSY`: An operation is already in progress + /// - `INVAL`: An invalid parameter was passed + /// - `NODEVICE`: No KV store was setup + /// - `ENOSUPPORT`: The key could not be found. + /// - `SIZE`: The value is longer than the provided buffer. + fn get_value( + &self, + key: &'static mut Self::K, + ret_buf: SubSliceMut<'static, u8>, + ) -> Result<(), (&'static mut Self::K, SubSliceMut<'static, u8>, ErrorCode)>; + + /// Invalidates the key in flash storage. + /// + /// - `key`: A hashed key. This key will be used to remove the `value`. + /// + /// On success nothing will be returned. + /// On error the key and a `Result<(), ErrorCode>` will be returned. + /// + /// The possible `Result<(), ErrorCode>`s are: + /// - `BUSY`: An operation is already in progress + /// - `INVAL`: An invalid parameter was passed + /// - `NODEVICE`: No KV store was setup + /// - `ENOSUPPORT`: The key could not be found. + fn invalidate_key( + &self, + key: &'static mut Self::K, + ) -> Result<(), (&'static mut Self::K, ErrorCode)>; + + /// Perform a garbage collection on the KV Store. + /// + /// For implementations that don't require garbage collecting this should + /// return `Err(ErrorCode::ALREADY)`. + /// + /// On success nothing will be returned. + /// On error a `Result<(), ErrorCode>` will be returned. + /// + /// The possible `ErrorCode`s are: + /// - `BUSY`: An operation is already in progress. + /// - `ALREADY`: Nothing to be done. Callback will not trigger. + /// - `INVAL`: An invalid parameter was passed. + /// - `NODEVICE`: No KV store was setup. + fn garbage_collect(&self) -> Result<(), ErrorCode>; +} + +#[derive(Clone, Copy, PartialEq, Debug)] +enum Operation { + None, + Init, + GetKey, + AppendKey, + InvalidateKey, + GarbageCollect, +} + +/// Wrapper object that provides the flash interface TicKV expects using the +/// Tock flash HIL. +/// +/// Note, TicKV expects a synchronous flash implementation, but the Tock flash +/// HIL is asynchronous. To mediate this, this wrapper starts a flash +/// read/write/erase, but returns without the requested operation having +/// completed. To signal TicKV that this is what happened, this implementation +/// returns `NotReady` errors. When the underlying flash operation has completed +/// the `TicKVSystem` object will get the callback and then notify TicKV that +/// the requested operation is now ready. +pub struct TickFSFlashCtrl<'a, F: Flash + 'static> { + flash: &'a F, + flash_read_buffer: TakeCell<'static, F::Page>, + region_offset: usize, +} + +impl<'a, F: Flash> TickFSFlashCtrl<'a, F> { + pub fn new( + flash: &'a F, + flash_read_buffer: &'static mut F::Page, + region_offset: usize, + ) -> TickFSFlashCtrl<'a, F> { + Self { + flash, + flash_read_buffer: TakeCell::new(flash_read_buffer), + region_offset, + } + } +} + +impl<'a, F: Flash, const PAGE_SIZE: usize> tickv::flash_controller::FlashController + for TickFSFlashCtrl<'a, F> +{ + fn read_region( + &self, + region_number: usize, + _buf: &mut [u8; PAGE_SIZE], + ) -> Result<(), tickv::error_codes::ErrorCode> { + if self + .flash + .read_page( + self.region_offset + region_number, + self.flash_read_buffer.take().unwrap(), + ) + .is_err() + { + Err(tickv::error_codes::ErrorCode::ReadFail) + } else { + Err(tickv::error_codes::ErrorCode::ReadNotReady(region_number)) + } + } + + fn write(&self, address: usize, buf: &[u8]) -> Result<(), tickv::error_codes::ErrorCode> { + let data_buf = self.flash_read_buffer.take().unwrap(); + + for (i, d) in buf.iter().enumerate() { + data_buf.as_mut()[i + (address % PAGE_SIZE)] = *d; + } + + if self + .flash + .write_page(self.region_offset + (address / PAGE_SIZE), data_buf) + .is_err() + { + return Err(tickv::error_codes::ErrorCode::WriteFail); + } + + Err(tickv::error_codes::ErrorCode::WriteNotReady(address)) + } + + fn erase_region(&self, region_number: usize) -> Result<(), tickv::error_codes::ErrorCode> { + let _ = self.flash.erase_page(self.region_offset + region_number); + + Err(tickv::error_codes::ErrorCode::EraseNotReady(region_number)) + } +} + +pub type TicKVKeyType = [u8; 8]; + +/// `TicKVSystem` implements `KVSystem` using the TicKV library. +pub struct TicKVSystem<'a, F: Flash + 'static, H: Hasher<'a, 8>, const PAGE_SIZE: usize> { + /// Underlying asynchronous TicKV implementation. + tickv: AsyncTicKV<'a, TickFSFlashCtrl<'a, F>, PAGE_SIZE>, + /// Hash engine that converts key strings to 8 byte keys. + hasher: &'a H, + /// Track our internal asynchronous state machine. + operation: Cell, + /// The operation to run _after_ initialization has completed. + next_operation: Cell, + /// Holder for the key string passed from the caller until the operation + /// completes. + unhashed_key_buffer: MapCell>, + /// Holder for the hashed key used in the given operation. + key_buffer: TakeCell<'static, [u8; 8]>, + /// Holder for a buffer containing a value being read from or written to the + /// key-value store. + value_buffer: MapCell>, + /// Callback client when the `KVSystem` operation completes. + client: OptionalCell<&'a dyn KVSystemClient>, +} + +impl<'a, F: Flash, H: Hasher<'a, 8>, const PAGE_SIZE: usize> TicKVSystem<'a, F, H, PAGE_SIZE> { + pub fn new( + flash: &'a F, + hasher: &'a H, + tickfs_read_buf: &'static mut [u8; PAGE_SIZE], + flash_read_buffer: &'static mut F::Page, + region_offset: usize, + flash_size: usize, + ) -> TicKVSystem<'a, F, H, PAGE_SIZE> { + let tickv = AsyncTicKV::, PAGE_SIZE>::new( + TickFSFlashCtrl::new(flash, flash_read_buffer, region_offset), + tickfs_read_buf, + flash_size, + ); + + Self { + tickv, + hasher, + operation: Cell::new(Operation::None), + next_operation: Cell::new(Operation::None), + unhashed_key_buffer: MapCell::empty(), + key_buffer: TakeCell::empty(), + value_buffer: MapCell::empty(), + client: OptionalCell::empty(), + } + } + + pub fn initialise(&self) { + let _ret = self.tickv.initialise(0x7bc9f7ff4f76f244); + self.operation.set(Operation::Init); + } + + fn complete_init(&self) { + self.operation.set(Operation::None); + match self.next_operation.get() { + Operation::None | Operation::Init => {} + Operation::GetKey => { + match self.get_value( + self.key_buffer.take().unwrap(), + self.value_buffer.take().unwrap(), + ) { + Err((key, value, error)) => { + self.client.map(move |cb| { + cb.get_value_complete(Err(error), key, value); + }); + } + _ => {} + } + } + Operation::AppendKey => { + match self.append_key( + self.key_buffer.take().unwrap(), + self.value_buffer.take().unwrap(), + ) { + Err((key, value, error)) => { + self.client.map(move |cb| { + cb.append_key_complete(Err(error), key, value); + }); + } + _ => {} + } + } + Operation::InvalidateKey => { + match self.invalidate_key(self.key_buffer.take().unwrap()) { + Err((key, error)) => { + self.client.map(move |cb| { + cb.invalidate_key_complete(Err(error), key); + }); + } + _ => {} + } + } + Operation::GarbageCollect => match self.garbage_collect() { + Err(error) => { + self.client.map(move |cb| { + cb.garbage_collect_complete(Err(error)); + }); + } + _ => {} + }, + } + self.next_operation.set(Operation::None); + } +} + +impl<'a, F: Flash, H: Hasher<'a, 8>, const PAGE_SIZE: usize> hasher::Client<8> + for TicKVSystem<'a, F, H, PAGE_SIZE> +{ + fn add_mut_data_done(&self, _result: Result<(), ErrorCode>, data: SubSliceMut<'static, u8>) { + self.unhashed_key_buffer.replace(data); + self.hasher.run(self.key_buffer.take().unwrap()).unwrap(); + } + + fn add_data_done(&self, _result: Result<(), ErrorCode>, _data: SubSlice<'static, u8>) {} + + fn hash_done(&self, _result: Result<(), ErrorCode>, digest: &'static mut [u8; 8]) { + self.client.map(move |cb| { + cb.generate_key_complete(Ok(()), self.unhashed_key_buffer.take().unwrap(), digest); + }); + + self.hasher.clear_data(); + } +} + +impl<'a, F: Flash, H: Hasher<'a, 8>, const PAGE_SIZE: usize> flash::Client + for TicKVSystem<'a, F, H, PAGE_SIZE> +{ + fn read_complete(&self, pagebuffer: &'static mut F::Page, _result: Result<(), flash::Error>) { + self.tickv.set_read_buffer(pagebuffer.as_mut()); + self.tickv + .tickv + .controller + .flash_read_buffer + .replace(pagebuffer); + let (ret, tickv_buf, tickv_buf_len) = self.tickv.continue_operation(); + + // If we got the buffer back from TicKV then store it. + tickv_buf.map(|buf| { + let mut val_buf = SubSliceMut::new(buf); + if tickv_buf_len > 0 { + // Length of zero means nothing was inserted into the buffer so + // no need to slice it. + val_buf.slice(0..tickv_buf_len); + } + self.value_buffer.replace(val_buf); + }); + + match self.operation.get() { + Operation::Init => match ret { + Ok(tickv::success_codes::SuccessCode::Complete) + | Ok(tickv::success_codes::SuccessCode::Written) => { + self.complete_init(); + } + _ => {} + }, + Operation::GetKey => { + match ret { + Ok(tickv::success_codes::SuccessCode::Complete) + | Ok(tickv::success_codes::SuccessCode::Written) => { + // We successfully got the key-value object and we can + // call the callback with the retrieved value. + self.operation.set(Operation::None); + self.client.map(|cb| { + cb.get_value_complete( + Ok(()), + self.key_buffer.take().unwrap(), + self.value_buffer.take().unwrap(), + ); + }); + } + Err(tickv::error_codes::ErrorCode::BufferTooSmall(_)) => { + // Notify the upper layer using the `SIZE` error that + // the entire value was not read into the buffer as + // there was not enough room to store the entire value. + // The buffer still contains the portion of the value + // that would fit. + self.operation.set(Operation::None); + self.client.map(|cb| { + cb.get_value_complete( + Err(ErrorCode::SIZE), + self.key_buffer.take().unwrap(), + self.value_buffer.take().unwrap(), + ); + }); + } + Err(tickv::error_codes::ErrorCode::ReadNotReady(_)) => { + // Need to do another flash read. + // + // `self.operation` will still be `GetKey`, so this will automatically + // be retried by the primary state machine. + } + Err(tickv::error_codes::ErrorCode::EraseNotReady(_)) | Ok(_) => {} + Err(e) => { + let get_tock_err = match e { + tickv::error_codes::ErrorCode::KeyNotFound => ErrorCode::NOSUPPORT, + _ => ErrorCode::FAIL, + }; + self.operation.set(Operation::None); + self.client.map(|cb| { + cb.get_value_complete( + Err(get_tock_err), + self.key_buffer.take().unwrap(), + self.value_buffer.take().unwrap(), + ); + }); + } + } + } + Operation::AppendKey => { + match ret { + Ok(tickv::success_codes::SuccessCode::Complete) + | Ok(tickv::success_codes::SuccessCode::Written) => { + // Nothing to do at this point as we need to wait + // for the flash write to complete. + self.operation.set(Operation::None); + } + Ok(tickv::success_codes::SuccessCode::Queued) => {} + Err(tickv::error_codes::ErrorCode::ReadNotReady(_)) + | Err(tickv::error_codes::ErrorCode::WriteNotReady(_)) + | Err(tickv::error_codes::ErrorCode::EraseNotReady(_)) => { + // Need to do another flash operation. + } + Err(e) => { + self.operation.set(Operation::None); + + let tock_hil_error = match e { + tickv::error_codes::ErrorCode::KeyAlreadyExists => ErrorCode::NOSUPPORT, + tickv::error_codes::ErrorCode::RegionFull => ErrorCode::NOMEM, + tickv::error_codes::ErrorCode::FlashFull => ErrorCode::NOMEM, + _ => ErrorCode::FAIL, + }; + self.client.map(|cb| { + cb.append_key_complete( + Err(tock_hil_error), + self.key_buffer.take().unwrap(), + self.value_buffer.take().unwrap(), + ); + }); + } + } + } + Operation::InvalidateKey => match ret { + Ok(tickv::success_codes::SuccessCode::Complete) + | Ok(tickv::success_codes::SuccessCode::Written) => { + // Need to wait for flash write to complete. + self.operation.set(Operation::None); + } + Ok(tickv::success_codes::SuccessCode::Queued) => {} + Err(tickv::error_codes::ErrorCode::ReadNotReady(_)) + | Err(tickv::error_codes::ErrorCode::WriteNotReady(_)) + | Err(tickv::error_codes::ErrorCode::EraseNotReady(_)) => { + // Need to do another flash operation. + } + Err(e) => { + self.operation.set(Operation::None); + + let tock_hil_error = match e { + tickv::error_codes::ErrorCode::KeyNotFound => ErrorCode::NOSUPPORT, + _ => ErrorCode::FAIL, + }; + self.client.map(|cb| { + cb.invalidate_key_complete( + Err(tock_hil_error), + self.key_buffer.take().unwrap(), + ); + }); + } + }, + Operation::GarbageCollect => match ret { + Ok(tickv::success_codes::SuccessCode::Complete) + | Ok(tickv::success_codes::SuccessCode::Written) => { + self.operation.set(Operation::None); + self.client.map(|cb| { + cb.garbage_collect_complete(Ok(())); + }); + } + _ => {} + }, + _ => unreachable!(), + } + } + + fn write_complete(&self, pagebuffer: &'static mut F::Page, _result: Result<(), flash::Error>) { + self.tickv + .tickv + .controller + .flash_read_buffer + .replace(pagebuffer); + + match self.operation.get() { + Operation::Init => { + self.complete_init(); + } + Operation::AppendKey => { + self.operation.set(Operation::None); + self.client.map(|cb| { + cb.append_key_complete( + Ok(()), + self.key_buffer.take().unwrap(), + self.value_buffer.take().unwrap(), + ); + }); + } + Operation::InvalidateKey => { + self.operation.set(Operation::None); + self.client.map(|cb| { + cb.invalidate_key_complete(Ok(()), self.key_buffer.take().unwrap()); + }); + } + _ => unreachable!(), + } + } + + fn erase_complete(&self, _result: Result<(), flash::Error>) { + let (ret, tickv_buf, tickv_buf_len) = self.tickv.continue_operation(); + + // If we got the buffer back from TicKV then store it. + tickv_buf.map(|buf| { + let mut val_buf = SubSliceMut::new(buf); + if tickv_buf_len > 0 { + // Length of zero means nothing was inserted into the buffer so + // no need to slice it. + val_buf.slice(0..tickv_buf_len); + } + self.value_buffer.replace(val_buf); + }); + + match self.operation.get() { + Operation::Init => match ret { + Ok(tickv::success_codes::SuccessCode::Complete) + | Ok(tickv::success_codes::SuccessCode::Written) => { + self.complete_init(); + } + _ => {} + }, + Operation::GarbageCollect => match ret { + Ok(tickv::success_codes::SuccessCode::Complete) + | Ok(tickv::success_codes::SuccessCode::Written) => { + self.operation.set(Operation::None); + self.client.map(|cb| { + cb.garbage_collect_complete(Ok(())); + }); + } + _ => {} + }, + _ => unreachable!(), + } + } +} + +impl<'a, F: Flash, H: Hasher<'a, 8>, const PAGE_SIZE: usize> KVSystem<'a> + for TicKVSystem<'a, F, H, PAGE_SIZE> +{ + type K = TicKVKeyType; + + fn set_client(&self, client: &'a dyn KVSystemClient) { + self.client.set(client); + } + + fn generate_key( + &self, + unhashed_key: SubSliceMut<'static, u8>, + key: &'static mut Self::K, + ) -> Result<(), (SubSliceMut<'static, u8>, &'static mut Self::K, ErrorCode)> { + match self.hasher.add_mut_data(unhashed_key) { + Ok(_) => { + self.key_buffer.replace(key); + Ok(()) + } + Err((e, buf)) => Err((buf, key, e)), + } + } + + fn append_key( + &self, + key: &'static mut Self::K, + value: SubSliceMut<'static, u8>, + ) -> Result<(), (&'static mut [u8; 8], SubSliceMut<'static, u8>, ErrorCode)> { + match self.operation.get() { + Operation::None => { + self.operation.set(Operation::AppendKey); + + let length = value.len(); + match self + .tickv + .append_key(u64::from_be_bytes(*key), value.take(), length) + { + Ok(_ret) => { + self.key_buffer.replace(key); + Ok(()) + } + Err((buf, e)) => { + let tock_error = match e { + tickv::error_codes::ErrorCode::ObjectTooLarge => ErrorCode::SIZE, + _ => ErrorCode::FAIL, + }; + Err((key, SubSliceMut::new(buf), tock_error)) + } + } + } + Operation::Init => { + // The init process is still occurring. + // We can save this request and start it after init + self.next_operation.set(Operation::AppendKey); + self.key_buffer.replace(key); + self.value_buffer.replace(value); + Ok(()) + } + _ => { + // An operation is already in process. + Err((key, value, ErrorCode::BUSY)) + } + } + } + + fn get_value( + &self, + key: &'static mut Self::K, + value: SubSliceMut<'static, u8>, + ) -> Result<(), (&'static mut [u8; 8], SubSliceMut<'static, u8>, ErrorCode)> { + if value.is_sliced() { + return Err((key, value, ErrorCode::SIZE)); + } + match self.operation.get() { + Operation::None => { + self.operation.set(Operation::GetKey); + + match self.tickv.get_key(u64::from_be_bytes(*key), value.take()) { + Ok(_ret) => { + self.key_buffer.replace(key); + Ok(()) + } + Err((buf, _e)) => Err((key, SubSliceMut::new(buf), ErrorCode::FAIL)), + } + } + Operation::Init => { + // The init process is still occurring. + // We can save this request and start it after init + self.next_operation.set(Operation::GetKey); + self.key_buffer.replace(key); + self.value_buffer.replace(value); + Ok(()) + } + _ => { + // An operation is already in process. + Err((key, value, ErrorCode::BUSY)) + } + } + } + + fn invalidate_key( + &self, + key: &'static mut Self::K, + ) -> Result<(), (&'static mut Self::K, ErrorCode)> { + match self.operation.get() { + Operation::None => { + self.operation.set(Operation::InvalidateKey); + + match self.tickv.invalidate_key(u64::from_be_bytes(*key)) { + Ok(_ret) => { + self.key_buffer.replace(key); + Ok(()) + } + Err(_e) => Err((key, ErrorCode::FAIL)), + } + } + Operation::Init => { + // The init process is still occurring. + // We can save this request and start it after init. + self.next_operation.set(Operation::InvalidateKey); + self.key_buffer.replace(key); + Ok(()) + } + _ => { + // An operation is already in process. + Err((key, ErrorCode::BUSY)) + } + } + } + + fn garbage_collect(&self) -> Result<(), ErrorCode> { + match self.operation.get() { + Operation::None => { + self.operation.set(Operation::GarbageCollect); + self.tickv + .garbage_collect() + .and(Ok(())) + .or(Err(ErrorCode::FAIL)) + } + Operation::Init => { + // The init process is still occurring. + // We can save this request and start it after init. + self.next_operation.set(Operation::GarbageCollect); + Ok(()) + } + _ => { + // An operation is already in process. + Err(ErrorCode::BUSY) + } + } + } +} diff --git a/capsules/extra/src/tickv_kv_store.rs b/capsules/extra/src/tickv_kv_store.rs new file mode 100644 index 0000000000..6e3a6bc817 --- /dev/null +++ b/capsules/extra/src/tickv_kv_store.rs @@ -0,0 +1,571 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! TicKV to Tock key-value store capsule. +//! +//! This capsule provides a higher level Key-Value store interface based on an +//! underlying `tickv::kv_system` storage layer. +//! +//! ```text +//! +-----------------------+ +//! | Capsule using K-V | +//! +-----------------------+ +//! +//! hil::kv::KV +//! +//! +-----------------------+ +//! | K-V store (this file) | +//! +-----------------------+ +//! +//! capsules::tickv::kv_system +//! +//! +-----------------------+ +//! | K-V library | +//! +-----------------------+ +//! +//! hil::flash +//! ``` + +use crate::tickv::{KVSystem, KVSystemClient, KeyType}; +use kernel::hil::kv; +use kernel::utilities::cells::{MapCell, OptionalCell, TakeCell}; +use kernel::utilities::leasable_buffer::SubSliceMut; +use kernel::ErrorCode; + +#[derive(Clone, Copy, PartialEq, Debug)] +enum Operation { + Get, + Set, + Add, + Update, + Delete, +} + +/// `TicKVKVStore` implements the KV interface using the TicKV KVSystem +/// interface. +pub struct TicKVKVStore<'a, K: KVSystem<'a> + KVSystem<'a, K = T>, T: 'static + KeyType> { + kv: &'a K, + hashed_key: TakeCell<'static, T>, + + client: OptionalCell<&'a dyn kv::KVClient>, + operation: OptionalCell, + + unhashed_key: MapCell>, + value: MapCell>, +} + +impl<'a, K: KVSystem<'a, K = T>, T: KeyType> TicKVKVStore<'a, K, T> { + pub fn new(kv: &'a K, key: &'static mut T) -> TicKVKVStore<'a, K, T> { + Self { + kv, + hashed_key: TakeCell::new(key), + client: OptionalCell::empty(), + operation: OptionalCell::empty(), + unhashed_key: MapCell::empty(), + value: MapCell::empty(), + } + } + + fn insert( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + operation: Operation, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + > { + if self.operation.is_some() { + return Err((key, value, ErrorCode::BUSY)); + } + + self.operation.set(operation); + + match self.hashed_key.take() { + Some(hashed_key) => match self.kv.generate_key(key, hashed_key) { + Ok(()) => { + self.value.replace(value); + Ok(()) + } + Err((unhashed_key, hashed_key, _e)) => { + self.operation.clear(); + self.hashed_key.replace(hashed_key); + Err((unhashed_key, value, ErrorCode::FAIL)) + } + }, + None => Err((key, value, ErrorCode::FAIL)), + } + } +} + +impl<'a, K: KVSystem<'a, K = T>, T: KeyType> kv::KV<'a> for TicKVKVStore<'a, K, T> { + fn set_client(&self, client: &'a dyn kv::KVClient) { + self.client.set(client); + } + + fn get( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + > { + if self.operation.is_some() { + return Err((key, value, ErrorCode::BUSY)); + } + + self.operation.set(Operation::Get); + + match self.hashed_key.take() { + Some(hashed_key) => match self.kv.generate_key(key, hashed_key) { + Ok(()) => { + self.value.replace(value); + Ok(()) + } + Err((unhashed_key, hashed_key, _e)) => { + self.operation.clear(); + self.hashed_key.replace(hashed_key); + Err((unhashed_key, value, ErrorCode::FAIL)) + } + }, + None => Err((key, value, ErrorCode::FAIL)), + } + } + + fn set( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + > { + self.insert(key, value, Operation::Set) + } + + fn add( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + > { + self.insert(key, value, Operation::Add) + } + + fn update( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + > { + self.insert(key, value, Operation::Update) + } + + fn delete( + &self, + key: SubSliceMut<'static, u8>, + ) -> Result<(), (SubSliceMut<'static, u8>, ErrorCode)> { + if self.operation.is_some() { + return Err((key, ErrorCode::BUSY)); + } + + self.operation.set(Operation::Delete); + + match self.hashed_key.take() { + Some(hashed_key) => match self.kv.generate_key(key, hashed_key) { + Ok(()) => Ok(()), + Err((unhashed_key, hashed_key, _e)) => { + self.hashed_key.replace(hashed_key); + self.operation.clear(); + Err((unhashed_key, ErrorCode::FAIL)) + } + }, + None => Err((key, ErrorCode::FAIL)), + } + } +} + +impl<'a, K: KVSystem<'a, K = T>, T: KeyType> KVSystemClient for TicKVKVStore<'a, K, T> { + fn generate_key_complete( + &self, + result: Result<(), ErrorCode>, + unhashed_key: SubSliceMut<'static, u8>, + hashed_key: &'static mut T, + ) { + self.operation.map(|op| { + if result.is_err() { + // On error, we re-store our state, run the next pending + // operation, and notify the original user that their operation + // failed using a callback. + self.hashed_key.replace(hashed_key); + self.operation.clear(); + + match op { + Operation::Get => { + self.value.take().map(|value| { + self.client.map(move |cb| { + cb.get_complete(Err(ErrorCode::FAIL), unhashed_key, value); + }); + }); + } + Operation::Set => { + self.value.take().map(|value| { + self.client.map(move |cb| { + cb.set_complete(Err(ErrorCode::FAIL), unhashed_key, value); + }); + }); + } + Operation::Add => { + self.value.take().map(|value| { + self.client.map(move |cb| { + cb.add_complete(Err(ErrorCode::FAIL), unhashed_key, value); + }); + }); + } + Operation::Update => { + self.value.take().map(|value| { + self.client.map(move |cb| { + cb.update_complete(Err(ErrorCode::FAIL), unhashed_key, value); + }); + }); + } + Operation::Delete => { + self.client.map(move |cb| { + cb.delete_complete(Err(ErrorCode::FAIL), unhashed_key); + }); + } + } + } else { + match op { + Operation::Get => { + self.value + .take() + .map(|value| match self.kv.get_value(hashed_key, value) { + Ok(()) => { + self.unhashed_key.replace(unhashed_key); + } + Err((key, value, _e)) => { + self.hashed_key.replace(key); + self.operation.clear(); + self.client.map(move |cb| { + cb.get_complete(Err(ErrorCode::FAIL), unhashed_key, value); + }); + } + }); + } + Operation::Set => { + self.value.take().map(|value| { + // Try to append which will work if the key is new. + match self.kv.append_key(hashed_key, value) { + Ok(()) => { + self.unhashed_key.replace(unhashed_key); + } + Err((key, value, e)) => { + self.hashed_key.replace(key); + self.operation.clear(); + self.client.map(move |cb| { + cb.set_complete(Err(e), unhashed_key, value); + }); + } + } + }); + } + Operation::Add => { + self.value.take().map(|value| { + // Add only works if the key does not exist, so we + // can go right to append. + match self.kv.append_key(hashed_key, value) { + Ok(()) => { + self.unhashed_key.replace(unhashed_key); + } + Err((key, value, e)) => { + self.hashed_key.replace(key); + self.operation.clear(); + self.client.map(move |cb| { + cb.add_complete(Err(e), unhashed_key, value); + }); + } + } + }); + } + Operation::Update => { + // Update requires the key to exist, so we start by + // trying to delete it. + match self.kv.invalidate_key(hashed_key) { + Ok(()) => { + self.unhashed_key.replace(unhashed_key); + } + Err((key, _e)) => { + self.hashed_key.replace(key); + self.operation.clear(); + self.value.take().map(|value| { + self.client.map(move |cb| { + cb.update_complete( + Err(ErrorCode::FAIL), + unhashed_key, + value, + ); + }); + }); + } + } + } + Operation::Delete => { + match self.kv.invalidate_key(hashed_key) { + Ok(()) => { + self.unhashed_key.replace(unhashed_key); + } + Err((key, _e)) => { + self.hashed_key.replace(key); + self.operation.clear(); + self.client.map(move |cb| { + cb.delete_complete(Err(ErrorCode::FAIL), unhashed_key); + }); + } + }; + } + } + } + }); + } + + fn append_key_complete( + &self, + result: Result<(), ErrorCode>, + key: &'static mut T, + value: SubSliceMut<'static, u8>, + ) { + self.hashed_key.replace(key); + + self.operation.map(|op| match op { + Operation::Get | Operation::Delete => {} + Operation::Set => { + match result { + Err(ErrorCode::NOSUPPORT) => { + // We could not append because of a collision. So now we + // need to delete the existing key. + self.hashed_key.take().map(|hashed_key| { + match self.kv.invalidate_key(hashed_key) { + Ok(()) => { + self.value.replace(value); + } + Err((key, _e)) => { + self.hashed_key.replace(key); + self.operation.clear(); + self.unhashed_key.take().map(|unhashed_key| { + self.client.map(move |cb| { + cb.set_complete( + Err(ErrorCode::FAIL), + unhashed_key, + value, + ); + }); + }); + } + } + }); + } + _ => { + // On success or any other error we just return the + // result back to the caller via a callback. + self.operation.clear(); + self.unhashed_key.take().map(|unhashed_key| { + self.client.map(move |cb| { + cb.set_complete( + result.map_err(|e| match e { + ErrorCode::NOMEM => ErrorCode::NOMEM, + _ => ErrorCode::FAIL, + }), + unhashed_key, + value, + ); + }); + }); + } + } + } + Operation::Add => { + self.operation.clear(); + self.unhashed_key.take().map(|unhashed_key| { + self.client.map(move |cb| { + cb.add_complete( + result.map_err(|e| match e { + ErrorCode::NOSUPPORT => ErrorCode::NOSUPPORT, + ErrorCode::NOMEM => ErrorCode::NOMEM, + _ => ErrorCode::FAIL, + }), + unhashed_key, + value, + ); + }); + }); + } + Operation::Update => { + self.operation.clear(); + self.unhashed_key.take().map(|unhashed_key| { + self.client.map(move |cb| { + cb.update_complete( + result.map_err(|e| match e { + ErrorCode::NOMEM => ErrorCode::NOMEM, + _ => ErrorCode::FAIL, + }), + unhashed_key, + value, + ); + }); + }); + } + }); + } + + fn get_value_complete( + &self, + result: Result<(), ErrorCode>, + key: &'static mut T, + ret_buf: SubSliceMut<'static, u8>, + ) { + self.operation.map(|op| match op { + Operation::Get => { + self.hashed_key.replace(key); + self.operation.clear(); + + self.unhashed_key.take().map(|unhashed_key| { + self.client.map(move |cb| { + cb.get_complete( + result.map_err(|e| match e { + ErrorCode::SIZE => ErrorCode::SIZE, + ErrorCode::NOSUPPORT => ErrorCode::NOSUPPORT, + _ => ErrorCode::FAIL, + }), + unhashed_key, + ret_buf, + ); + }); + }); + } + _ => {} + }); + } + + fn invalidate_key_complete(&self, result: Result<(), ErrorCode>, key: &'static mut T) { + self.hashed_key.replace(key); + + self.operation.map(|op| match op { + Operation::Get | Operation::Add => {} + Operation::Set => { + // Now that we have deleted the existing key-value we can store + // our new key and value. + match result { + Ok(()) => { + self.hashed_key.take().map(|hashed_key| { + self.value.take().map(|value| { + match self.kv.append_key(hashed_key, value) { + Ok(()) => {} + Err((key, value, e)) => { + self.hashed_key.replace(key); + self.operation.clear(); + self.unhashed_key.take().map(|unhashed_key| { + self.client.map(move |cb| { + cb.set_complete(Err(e), unhashed_key, value); + }); + }); + } + } + }); + }); + } + _ => { + // Some error with delete, signal error. + self.operation.clear(); + self.unhashed_key.take().map(|unhashed_key| { + self.value.take().map(|value| { + self.client.map(move |cb| { + cb.set_complete(Err(ErrorCode::FAIL), unhashed_key, value); + }); + }); + }); + } + } + } + Operation::Update => { + // Now that we have deleted the existing key-value we can store + // our new key and value. + match result { + Ok(()) => { + self.hashed_key.take().map(|hashed_key| { + self.value.take().map(|value| { + match self.kv.append_key(hashed_key, value) { + Ok(()) => {} + Err((key, value, _e)) => { + self.hashed_key.replace(key); + self.operation.clear(); + self.unhashed_key.take().map(|unhashed_key| { + self.client.map(move |cb| { + cb.update_complete( + Err(ErrorCode::FAIL), + unhashed_key, + value, + ); + }); + }); + } + } + }); + }); + } + _ => { + // Could not remove which means we can not update. + self.operation.clear(); + self.unhashed_key.take().map(|unhashed_key| { + self.value.take().map(|value| { + self.client.map(move |cb| { + cb.update_complete( + Err(ErrorCode::NOSUPPORT), + unhashed_key, + value, + ); + }); + }); + }); + } + } + } + Operation::Delete => { + self.operation.clear(); + self.unhashed_key.take().map(|unhashed_key| { + self.client.map(move |cb| { + cb.delete_complete(result, unhashed_key); + }); + }); + } + }); + } + + fn garbage_collect_complete(&self, _result: Result<(), ErrorCode>) {} +} diff --git a/capsules/src/touch.rs b/capsules/extra/src/touch.rs similarity index 92% rename from capsules/src/touch.rs rename to capsules/extra/src/touch.rs index 4309888529..146c2c9471 100644 --- a/capsules/src/touch.rs +++ b/capsules/extra/src/touch.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides userspace with access to the touch panel. //! //! Usage @@ -6,7 +10,7 @@ //! You need a touch that provides the `hil::touch::Touch` trait. //! An optional gesture client and a screen can be connected to it. //! -//! ```rust +//! ```rust,ignore //! let touch = //! components::touch::TouchComponent::new(board_kernel, ts, Some(ts), Some(screen)).finalize(()); //! ``` @@ -23,7 +27,7 @@ use kernel::syscall::{CommandReturn, SyscallDriver}; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::Touch as usize; /// Ids for read-write allow buffers @@ -37,7 +41,7 @@ mod rw_allow { // | Touch 0 | Touch 1 ... pub const EVENTS: usize = 2; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 3; + pub const COUNT: u8 = 3; } fn touch_status_to_number(status: &TouchStatus) -> usize { @@ -81,7 +85,7 @@ pub struct Touch<'a> { /// 90 deg (clockwise), 180 deg (upside-down), 270 deg(clockwise). /// The touch gets the rotation from the screen and /// updates the touch (x, y) position - screen: Option<&'a dyn hil::screen::Screen>, + screen: Option<&'a dyn hil::screen::Screen<'a>>, apps: Grant, AllowRoCount<0>, AllowRwCount<{ rw_allow::COUNT }>>, screen_rotation_offset: Cell, } @@ -90,13 +94,13 @@ impl<'a> Touch<'a> { pub fn new( touch: Option<&'a dyn hil::touch::Touch<'a>>, multi_touch: Option<&'a dyn hil::touch::MultiTouch<'a>>, - screen: Option<&'a dyn hil::screen::Screen>, + screen: Option<&'a dyn hil::screen::Screen<'a>>, grant: Grant, AllowRoCount<0>, AllowRwCount<{ rw_allow::COUNT }>>, ) -> Touch<'a> { Touch { - touch: touch, - multi_touch: multi_touch, - screen: screen, + touch, + multi_touch, + screen, screen_rotation_offset: Cell::new(ScreenRotation::Normal), apps: grant, } @@ -109,7 +113,7 @@ impl<'a> Touch<'a> { fn touch_enable(&self) -> Result<(), ErrorCode> { let mut enabled = false; for app in self.apps.iter() { - if app.enter(|app, _| if app.touch_enable { true } else { false }) { + if app.enter(|app, _| app.touch_enable) { enabled = true; break; } @@ -141,7 +145,7 @@ impl<'a> Touch<'a> { fn multi_touch_enable(&self) -> Result<(), ErrorCode> { let mut enabled = false; for app in self.apps.iter() { - if app.enter(|app, _| if app.multi_touch_enable { true } else { false }) { + if app.enter(|app, _| app.multi_touch_enable) { enabled = true; break; } @@ -173,7 +177,7 @@ impl<'a> Touch<'a> { } ScreenRotation::Rotated270 => { mem::swap(&mut width, &mut height); - (width as u16 - touch_event.y as u16, touch_event.x) + (width as u16 - touch_event.y, touch_event.x) } _ => (touch_event.x, touch_event.y), }; @@ -251,7 +255,7 @@ impl<'a> hil::touch::MultiTouchClient for Touch<'a> { }; for event_index in 0..num { - let mut event = touch_events[event_index].clone(); + let mut event = touch_events[event_index]; self.update_rotation(&mut event); let event_status = touch_status_to_number(&event.status); // debug!( @@ -300,7 +304,7 @@ impl<'a> hil::touch::MultiTouchClient for Touch<'a> { .ok(); } } else { - app.dropped_events = app.dropped_events + 1; + app.dropped_events += 1; } }); } @@ -332,19 +336,16 @@ impl<'a> SyscallDriver for Touch<'a> { command_num: usize, _data1: usize, _data2: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { match command_num { - 0 => - // This driver exists. - { - CommandReturn::success() - } + // driver existence check + 0 => CommandReturn::success(), // touch enable 1 => { self.apps - .enter(appid, |app, _| { + .enter(processid, |app, _| { app.touch_enable = true; }) .unwrap_or(()); @@ -355,7 +356,7 @@ impl<'a> SyscallDriver for Touch<'a> { // touch disable 2 => { self.apps - .enter(appid, |app, _| { + .enter(processid, |app, _| { app.touch_enable = false; }) .unwrap_or(()); @@ -366,7 +367,7 @@ impl<'a> SyscallDriver for Touch<'a> { // multi touch ack 10 => { self.apps - .enter(appid, |app, _| { + .enter(processid, |app, _| { app.ack = true; }) .unwrap_or(()); @@ -376,7 +377,7 @@ impl<'a> SyscallDriver for Touch<'a> { // multi touch enable 11 => { self.apps - .enter(appid, |app, _| { + .enter(processid, |app, _| { app.multi_touch_enable = true; }) .unwrap_or(()); @@ -387,7 +388,7 @@ impl<'a> SyscallDriver for Touch<'a> { // multi touch disable 12 => { self.apps - .enter(appid, |app, _| { + .enter(processid, |app, _| { app.multi_touch_enable = false; }) .unwrap_or(()); @@ -400,11 +401,7 @@ impl<'a> SyscallDriver for Touch<'a> { let num_touches = if let Some(multi_touch) = self.multi_touch { multi_touch.get_num_touches() } else { - if self.touch.is_some() { - 1 - } else { - 0 - } + usize::from(self.touch.is_some()) }; CommandReturn::success_u32(num_touches as u32) } diff --git a/capsules/src/tsl2561.rs b/capsules/extra/src/tsl2561.rs similarity index 95% rename from capsules/src/tsl2561.rs rename to capsules/extra/src/tsl2561.rs index 60c81dbd79..dc02644ff9 100644 --- a/capsules/src/tsl2561.rs +++ b/capsules/extra/src/tsl2561.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SyscallDriver for the Taos TSL2561 light sensor. //! //! @@ -23,11 +27,11 @@ use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::{ErrorCode, ProcessId}; /// Syscall driver number. -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::Tsl2561 as usize; // Buffer to use for I2C messages -pub static mut BUFFER: [u8; 4] = [0; 4]; +pub const BUFFER_LENGTH: usize = 4; /// Command register defines const COMMAND_REG: u8 = 0x80; @@ -207,8 +211,8 @@ enum State { #[derive(Default)] pub struct App {} -pub struct TSL2561<'a> { - i2c: &'a dyn i2c::I2CDevice, +pub struct TSL2561<'a, I: i2c::I2CDevice> { + i2c: &'a I, interrupt_pin: &'a dyn gpio::InterruptPin<'a>, state: Cell, buffer: TakeCell<'static, [u8]>, @@ -216,17 +220,17 @@ pub struct TSL2561<'a> { owning_process: OptionalCell, } -impl<'a> TSL2561<'a> { +impl<'a, I: i2c::I2CDevice> TSL2561<'a, I> { pub fn new( - i2c: &'a dyn i2c::I2CDevice, + i2c: &'a I, interrupt_pin: &'a dyn gpio::InterruptPin<'a>, buffer: &'static mut [u8], apps: Grant, AllowRoCount<0>, AllowRwCount<0>>, ) -> Self { // setup and return struct Self { - i2c: i2c, - interrupt_pin: interrupt_pin, + i2c, + interrupt_pin, state: Cell::new(State::Idle), buffer: TakeCell::new(buffer), apps, @@ -273,7 +277,7 @@ impl<'a> TSL2561<'a> { // let mut ch_scale: usize = 1 << CH_SCALE; // Default // Scale if gain is NOT 16X - ch_scale = ch_scale << 4; // scale 1X to 16X + ch_scale <<= 4; // scale 1X to 16X // scale the channel values let channel0 = (chan0 as usize * ch_scale) >> CH_SCALE; @@ -356,7 +360,7 @@ impl<'a> TSL2561<'a> { } } -impl i2c::I2CClient for TSL2561<'_> { +impl i2c::I2CClient for TSL2561<'_, I> { fn command_complete(&self, buffer: &'static mut [u8], _status: Result<(), i2c::Error>) { match self.state.get() { State::SelectId => { @@ -424,7 +428,7 @@ impl i2c::I2CClient for TSL2561<'_> { let lux = self.calculate_lux(chan0, chan1); self.owning_process.map(|pid| { - let _ = self.apps.enter(*pid, |_, upcalls| { + let _ = self.apps.enter(pid, |_, upcalls| { upcalls.schedule_upcall(0, (0, lux, 0)).ok(); }); }); @@ -446,7 +450,7 @@ impl i2c::I2CClient for TSL2561<'_> { } } -impl gpio::Client for TSL2561<'_> { +impl gpio::Client for TSL2561<'_, I> { fn fired(&self) { self.buffer.take().map(|buffer| { // turn on i2c to send commands @@ -461,7 +465,7 @@ impl gpio::Client for TSL2561<'_> { } } -impl SyscallDriver for TSL2561<'_> { +impl SyscallDriver for TSL2561<'_, I> { fn command( &self, command_num: usize, @@ -478,7 +482,7 @@ impl SyscallDriver for TSL2561<'_> { // some (alive) process let match_or_empty_or_nonexistant = self.owning_process.map_or(true, |current_process| { self.apps - .enter(*current_process, |_, _| current_process == &process_id) + .enter(current_process, |_, _| current_process == process_id) .unwrap_or(true) }); if match_or_empty_or_nonexistant { diff --git a/capsules/extra/src/tutorials/README.md b/capsules/extra/src/tutorials/README.md new file mode 100644 index 0000000000..4c37aa88e1 --- /dev/null +++ b/capsules/extra/src/tutorials/README.md @@ -0,0 +1,6 @@ +Tutorial Capsules +================= + +These capsules are explicitly designed for use in tutorials that introduce +aspects of Tock in a guided fashion. Please see the [Tock +Book](https://book.tockos.org) for the corresponding guides. diff --git a/capsules/extra/src/tutorials/encryption_oracle_chkpt0.rs b/capsules/extra/src/tutorials/encryption_oracle_chkpt0.rs new file mode 100644 index 0000000000..f92045d3cb --- /dev/null +++ b/capsules/extra/src/tutorials/encryption_oracle_chkpt0.rs @@ -0,0 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +pub static KEY: &[u8; kernel::hil::symmetric_encryption::AES128_KEY_SIZE] = b"InsecureAESKey12"; + +pub struct EncryptionOracleDriver {} + +impl EncryptionOracleDriver { + /// Create a new instance of our encryption oracle userspace driver: + pub fn new() -> Self { + EncryptionOracleDriver {} + } +} diff --git a/capsules/extra/src/tutorials/encryption_oracle_chkpt1.rs b/capsules/extra/src/tutorials/encryption_oracle_chkpt1.rs new file mode 100644 index 0000000000..2aca75f25e --- /dev/null +++ b/capsules/extra/src/tutorials/encryption_oracle_chkpt1.rs @@ -0,0 +1,59 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::ErrorCode; +use kernel::ProcessId; + +pub static KEY: &[u8; kernel::hil::symmetric_encryption::AES128_KEY_SIZE] = b"InsecureAESKey12"; + +#[derive(Default)] +pub struct ProcessState { + request_pending: bool, +} + +pub struct EncryptionOracleDriver { + process_grants: Grant, AllowRoCount<0>, AllowRwCount<0>>, +} + +impl EncryptionOracleDriver { + /// Create a new instance of our encryption oracle userspace driver: + pub fn new( + process_grants: Grant, AllowRoCount<0>, AllowRwCount<0>>, + ) -> Self { + EncryptionOracleDriver { process_grants } + } +} + +impl SyscallDriver for EncryptionOracleDriver { + fn command( + &self, + command_num: usize, + _data1: usize, + _data2: usize, + processid: ProcessId, + ) -> CommandReturn { + match command_num { + // Check whether the driver is present: + 0 => CommandReturn::success(), + + // Request the decryption operation: + 1 => self + .process_grants + .enter(processid, |grant, _kernel_data| { + grant.request_pending = true; + CommandReturn::success() + }) + .unwrap_or_else(|err| err.into()), + + // Unknown command number, return a NOSUPPORT error + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { + self.process_grants.enter(processid, |_, _| {}) + } +} diff --git a/capsules/extra/src/tutorials/encryption_oracle_chkpt2.rs b/capsules/extra/src/tutorials/encryption_oracle_chkpt2.rs new file mode 100644 index 0000000000..14a2c308ad --- /dev/null +++ b/capsules/extra/src/tutorials/encryption_oracle_chkpt2.rs @@ -0,0 +1,69 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::hil::symmetric_encryption::{AES128Ctr, AES128}; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::ErrorCode; +use kernel::ProcessId; + +pub const DRIVER_NUM: usize = 0x99999; + +pub static KEY: &[u8; kernel::hil::symmetric_encryption::AES128_KEY_SIZE] = b"InsecureAESKey12"; + +#[derive(Default)] +pub struct ProcessState { + request_pending: bool, +} + +pub struct EncryptionOracleDriver<'a, A: AES128<'a> + AES128Ctr> { + aes: &'a A, + process_grants: Grant, AllowRoCount<0>, AllowRwCount<0>>, +} + +impl<'a, A: AES128<'a> + AES128Ctr> EncryptionOracleDriver<'a, A> { + /// Create a new instance of our encryption oracle userspace driver: + pub fn new( + aes: &'a A, + _source_buffer: &'static mut [u8], + _dest_buffer: &'static mut [u8], + process_grants: Grant, AllowRoCount<0>, AllowRwCount<0>>, + ) -> Self { + EncryptionOracleDriver { + aes, + process_grants, + } + } +} + +impl<'a, A: AES128<'a> + AES128Ctr> SyscallDriver for EncryptionOracleDriver<'a, A> { + fn command( + &self, + command_num: usize, + _data1: usize, + _data2: usize, + processid: ProcessId, + ) -> CommandReturn { + match command_num { + // Check whether the driver is present: + 0 => CommandReturn::success(), + + // Request the decryption operation: + 1 => self + .process_grants + .enter(processid, |grant, _kernel_data| { + grant.request_pending = true; + CommandReturn::success() + }) + .unwrap_or_else(|err| err.into()), + + // Unknown command number, return a NOSUPPORT error + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { + self.process_grants.enter(processid, |_, _| {}) + } +} diff --git a/capsules/extra/src/tutorials/encryption_oracle_chkpt3.rs b/capsules/extra/src/tutorials/encryption_oracle_chkpt3.rs new file mode 100644 index 0000000000..471785dcb9 --- /dev/null +++ b/capsules/extra/src/tutorials/encryption_oracle_chkpt3.rs @@ -0,0 +1,87 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::hil::symmetric_encryption::{AES128Ctr, AES128}; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::utilities::cells::OptionalCell; +use kernel::ErrorCode; +use kernel::ProcessId; + +pub const DRIVER_NUM: usize = 0x99999; + +pub static KEY: &[u8; kernel::hil::symmetric_encryption::AES128_KEY_SIZE] = b"InsecureAESKey12"; + +#[derive(Default)] +pub struct ProcessState { + request_pending: bool, +} + +pub struct EncryptionOracleDriver<'a, A: AES128<'a> + AES128Ctr> { + aes: &'a A, + process_grants: Grant, AllowRoCount<0>, AllowRwCount<0>>, + current_process: OptionalCell, +} + +impl<'a, A: AES128<'a> + AES128Ctr> EncryptionOracleDriver<'a, A> { + /// Create a new instance of our encryption oracle userspace driver: + pub fn new( + aes: &'a A, + _source_buffer: &'static mut [u8], + _dest_buffer: &'static mut [u8], + process_grants: Grant, AllowRoCount<0>, AllowRwCount<0>>, + ) -> Self { + EncryptionOracleDriver { + process_grants, + aes, + current_process: OptionalCell::empty(), + } + } + + /// Return a `ProcessId` which has `request_pending` set, if there is some: + fn next_pending(&self) -> Option { + for process_grant in self.process_grants.iter() { + let processid = process_grant.processid(); + if process_grant.enter(|grant, _| grant.request_pending) { + // The process to which `process_grant` belongs + // has a request pending, return its id: + return Some(processid); + } + } + + // No process with `request_pending` found: + None + } +} + +impl<'a, A: AES128<'a> + AES128Ctr> SyscallDriver for EncryptionOracleDriver<'a, A> { + fn command( + &self, + command_num: usize, + _data1: usize, + _data2: usize, + processid: ProcessId, + ) -> CommandReturn { + match command_num { + // Check whether the driver is present: + 0 => CommandReturn::success(), + + // Request the decryption operation: + 1 => self + .process_grants + .enter(processid, |grant, _kernel_data| { + grant.request_pending = true; + CommandReturn::success() + }) + .unwrap_or_else(|err| err.into()), + + // Unknown command number, return a NOSUPPORT error + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { + self.process_grants.enter(processid, |_, _| {}) + } +} diff --git a/capsules/extra/src/tutorials/encryption_oracle_chkpt4.rs b/capsules/extra/src/tutorials/encryption_oracle_chkpt4.rs new file mode 100644 index 0000000000..6d45c32c37 --- /dev/null +++ b/capsules/extra/src/tutorials/encryption_oracle_chkpt4.rs @@ -0,0 +1,308 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use core::cell::Cell; + +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::hil::symmetric_encryption::{AES128Ctr, Client, AES128, AES128_BLOCK_SIZE}; +use kernel::processbuffer::ReadableProcessBuffer; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::ErrorCode; +use kernel::ProcessId; + +pub const DRIVER_NUM: usize = 0x99999; + +pub static KEY: &[u8; kernel::hil::symmetric_encryption::AES128_KEY_SIZE] = b"InsecureAESKey12"; + +#[derive(Default)] +pub struct ProcessState { + request_pending: bool, + offset: usize, +} + +/// Ids for subscribe upcalls +mod upcall { + pub const DONE: usize = 0; + /// The number of subscribe upcalls the kernel stores for this grant + pub const COUNT: u8 = 1; +} + +/// Ids for read-only allow buffers +mod ro_allow { + pub const IV: usize = 0; + pub const SOURCE: usize = 1; + /// The number of allow buffers the kernel stores for this grant + pub const COUNT: u8 = 2; +} + +/// Ids for read-write allow buffers +mod rw_allow { + pub const DEST: usize = 0; + /// The number of allow buffers the kernel stores for this grant + pub const COUNT: u8 = 1; +} + +pub struct EncryptionOracleDriver<'a, A: AES128<'a> + AES128Ctr> { + aes: &'a A, + process_grants: Grant< + ProcessState, + UpcallCount<{ upcall::COUNT }>, + AllowRoCount<{ ro_allow::COUNT }>, + AllowRwCount<{ rw_allow::COUNT }>, + >, + + current_process: OptionalCell, + source_buffer: TakeCell<'static, [u8]>, + dest_buffer: TakeCell<'static, [u8]>, + crypt_len: Cell, +} + +impl<'a, A: AES128<'a> + AES128Ctr> EncryptionOracleDriver<'a, A> { + /// Create a new instance of our encryption oracle userspace driver: + pub fn new( + aes: &'a A, + source_buffer: &'static mut [u8], + dest_buffer: &'static mut [u8], + process_grants: Grant< + ProcessState, + UpcallCount<{ upcall::COUNT }>, + AllowRoCount<{ ro_allow::COUNT }>, + AllowRwCount<{ rw_allow::COUNT }>, + >, + ) -> Self { + EncryptionOracleDriver { + process_grants, + aes, + current_process: OptionalCell::empty(), + source_buffer: TakeCell::new(source_buffer), + dest_buffer: TakeCell::new(dest_buffer), + crypt_len: Cell::new(0), + } + } + + /// Return a `ProcessId` which has `request_pending` set, if there is some: + fn next_pending(&self) -> Option { + for process_grant in self.process_grants.iter() { + let processid = process_grant.processid(); + if process_grant.enter(|grant, _| grant.request_pending) { + // The process to which `process_grant` belongs + // has a request pending, return its id: + return Some(processid); + } + } + + // No process with `request_pending` found: + None + } + + /// The run method initiates a new decryption operation or continues an + /// existing asynchronous decryption in the context of a process. + /// + /// If the process-state `offset` is larger or equal to the process-provided + /// source or destination buffer size, this indicates that we have completed + /// the requested description operation and return an error of + /// `ErrorCode::NOMEM`. A caller can use this as a method to check whether + /// the descryption operation has finished. + /// + /// If the process-state `offset` is `0`, we will initialize the AES engine + /// with an initialization vector (IV) provided by the application, and + /// configure it to perform an AES128-CTR operation. + fn run(&self, processid: ProcessId) -> Result<(), ErrorCode> { + // If the kernel's source buffer is not present in its TakeCell, + // this implies that we're currently running a decryption + // operation: + if self.source_buffer.is_none() { + return Err(ErrorCode::BUSY); + } + + self.process_grants + .enter(processid, |grant, kernel_data| { + // Get a reference to both the application-provided source + // and destination buffers: + let (source_processbuffer, dest_processbuffer) = kernel_data + .get_readonly_processbuffer(ro_allow::SOURCE) + .and_then(|source| { + kernel_data + .get_readwrite_processbuffer(rw_allow::DEST) + .map(|dest| (source, dest)) + })?; + + // Calculate the minimum length of the source & destination + // buffers: + let min_processbuffer_len = + core::cmp::min(source_processbuffer.len(), dest_processbuffer.len()); + + // If our operation-offset stored in the process grant exceeds + // `min_buffer_len`, then our operation is finished. Return with + // the appropriate error code. + // + // This check also ensures that we're never running a zero-byte + // encryption operation. + if grant.offset >= min_processbuffer_len { + return Err(ErrorCode::NOMEM); + } + + // Perform some special initialization if this is the first + // invocation of our AES engine as part of this decryption + // operation: + if grant.offset == 0 { + // Offset = 0, initialize the AES engine & IV: + self.aes.enable(); + + // Set the AES engine mode to AES128 CTR (counter) mode, and + // make this a decryption operation: + self.aes.set_mode_aes128ctr(true)?; + + self.aes.set_key(KEY)?; + + // Set the initialization vector: + kernel_data + .get_readonly_processbuffer(ro_allow::IV) + .and_then(|iv| { + iv.enter(|iv| { + let mut static_buf = + [0; kernel::hil::symmetric_encryption::AES128_KEY_SIZE]; + // Determine the size of the static buffer we have + let copy_len = core::cmp::min(static_buf.len(), iv.len()); + + // Clear any previous iv + for c in static_buf.iter_mut() { + *c = 0; + } + // Copy the data into the static buffer + iv[..copy_len].copy_to_slice(&mut static_buf[..copy_len]); + + AES128::set_iv(self.aes, &static_buf[..copy_len]) + }) + .map_err(Into::into) + })??; + } + + // Our AES engine works with kernel-provided `&'static mut` + // buffers. We copy a chunk of application's source buffer + // contents into this kernel buffer: + source_processbuffer.enter(|source_processbuffer| { + // Attempt to "take" the kernel-internal source & + // destination buffers from their TakeCells. We expect them + // to be placed back into the TakeCell after an encryption + // operation is done, and checked that the `source_buffer` + // is present above -- thus this should never fail: + let source_buffer = self.source_buffer.take().unwrap(); + let dest_buffer = self.dest_buffer.take().unwrap(); + + // Determine the amount of data we pass to the AES engine, + // which is the minimum of the user-provided buffer space + // and our kernel-internal buffer capacity: + let data_len = core::cmp::min(source_buffer.len(), min_processbuffer_len); + + // Now, copy this data into the kernel-internal buffer: + source_processbuffer[..data_len].copy_to_slice(&mut source_buffer[..data_len]); + + // However, our AES engine requires us to pass it at least + // `AES128_BLOCK_SIZE` data, and have our data length be a + // multiple of the `AES128_BLOCK_SIZE`. We assume that our + // `source_buffer` holds at least a full AES128 block. Then, + // we can round up or down to a multiple of the + // `AES128_BLOCK_SIZE` as required: + let crypt_len = if data_len < AES128_BLOCK_SIZE { + AES128_BLOCK_SIZE + } else { + data_len - (data_len % AES128_BLOCK_SIZE) + }; + + // Save `crypt_len`, so we know how much data to copy back + // to the process buffer after the operation finished: + self.crypt_len.set(crypt_len); + + // Set this process as active: + self.current_process.set(processid); + + // Now, run the operation: + if let Some((e, source, dest)) = + AES128::crypt(self.aes, Some(source_buffer), dest_buffer, 0, crypt_len) + { + // An error occurred, clear the currently active process + // and replace the buffers. Reset the current process' + // offset: + grant.offset = 0; + self.aes.disable(); + self.current_process.clear(); + self.source_buffer.replace(source.unwrap()); + self.dest_buffer.replace(dest); + e + } else { + Ok(()) + } + })? + }) + .unwrap_or(Err(ErrorCode::RESERVE)) + } + + fn run_next_pending(&self) { + if self.current_process.is_some() { + return; + } + + while let Some(processid) = self.next_pending() { + let res = self.run(processid); + + let _ = self.process_grants.enter(processid, |grant, kernel_data| { + grant.request_pending = false; + + if let Err(e) = res { + kernel_data + .schedule_upcall( + upcall::DONE, + (kernel::errorcode::into_statuscode(Err(e)), 0, 0), + ) + .ok(); + } + }); + } + } +} + +impl<'a, A: AES128<'a> + AES128Ctr> SyscallDriver for EncryptionOracleDriver<'a, A> { + fn command( + &self, + command_num: usize, + _data1: usize, + _data2: usize, + processid: ProcessId, + ) -> CommandReturn { + match command_num { + // Check whether the driver is present: + 0 => CommandReturn::success(), + + // Request the decryption operation: + 1 => { + let res = self + .process_grants + .enter(processid, |grant, _kernel_data| { + grant.request_pending = true; + CommandReturn::success() + }) + .unwrap_or_else(|err| err.into()); + + self.run_next_pending(); + + res + } + + // Unknown command number, return a NOSUPPORT error + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { + self.process_grants.enter(processid, |_, _| {}) + } +} + +impl<'a, A: AES128<'a> + AES128Ctr> Client<'a> for EncryptionOracleDriver<'a, A> { + fn crypt_done(&'a self, mut source: Option<&'static mut [u8]>, destination: &'static mut [u8]) { + unimplemented!() + } +} diff --git a/capsules/extra/src/tutorials/encryption_oracle_chkpt5.rs b/capsules/extra/src/tutorials/encryption_oracle_chkpt5.rs new file mode 100644 index 0000000000..d7186dd17a --- /dev/null +++ b/capsules/extra/src/tutorials/encryption_oracle_chkpt5.rs @@ -0,0 +1,375 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use core::cell::Cell; + +use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; +use kernel::hil::symmetric_encryption::{AES128Ctr, Client, AES128, AES128_BLOCK_SIZE}; +use kernel::processbuffer::{ReadableProcessBuffer, WriteableProcessBuffer}; +use kernel::syscall::{CommandReturn, SyscallDriver}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::ErrorCode; +use kernel::ProcessId; + +pub const DRIVER_NUM: usize = 0x99999; + +pub static KEY: &[u8; kernel::hil::symmetric_encryption::AES128_KEY_SIZE] = b"InsecureAESKey12"; + +#[derive(Default)] +pub struct ProcessState { + request_pending: bool, + offset: usize, +} + +/// Ids for subscribe upcalls +mod upcall { + pub const DONE: usize = 0; + /// The number of subscribe upcalls the kernel stores for this grant + pub const COUNT: u8 = 1; +} + +/// Ids for read-only allow buffers +mod ro_allow { + pub const IV: usize = 0; + pub const SOURCE: usize = 1; + /// The number of allow buffers the kernel stores for this grant + pub const COUNT: u8 = 2; +} + +/// Ids for read-write allow buffers +mod rw_allow { + pub const DEST: usize = 0; + /// The number of allow buffers the kernel stores for this grant + pub const COUNT: u8 = 1; +} + +pub struct EncryptionOracleDriver<'a, A: AES128<'a> + AES128Ctr> { + aes: &'a A, + process_grants: Grant< + ProcessState, + UpcallCount<{ upcall::COUNT }>, + AllowRoCount<{ ro_allow::COUNT }>, + AllowRwCount<{ rw_allow::COUNT }>, + >, + + current_process: OptionalCell, + source_buffer: TakeCell<'static, [u8]>, + dest_buffer: TakeCell<'static, [u8]>, + crypt_len: Cell, +} + +impl<'a, A: AES128<'a> + AES128Ctr> EncryptionOracleDriver<'a, A> { + /// Create a new instance of our encryption oracle userspace driver: + pub fn new( + aes: &'a A, + source_buffer: &'static mut [u8], + dest_buffer: &'static mut [u8], + process_grants: Grant< + ProcessState, + UpcallCount<{ upcall::COUNT }>, + AllowRoCount<{ ro_allow::COUNT }>, + AllowRwCount<{ rw_allow::COUNT }>, + >, + ) -> Self { + EncryptionOracleDriver { + process_grants, + aes, + current_process: OptionalCell::empty(), + source_buffer: TakeCell::new(source_buffer), + dest_buffer: TakeCell::new(dest_buffer), + crypt_len: Cell::new(0), + } + } + + /// Return a `ProcessId` which has `request_pending` set, if there is some: + fn next_pending(&self) -> Option { + for process_grant in self.process_grants.iter() { + let processid = process_grant.processid(); + if process_grant.enter(|grant, _| grant.request_pending) { + // The process to which `process_grant` belongs + // has a request pending, return its id: + return Some(processid); + } + } + + // No process with `request_pending` found: + None + } + + /// The run method initiates a new decryption operation or continues an + /// existing asynchronous decryption in the context of a process. + /// + /// If the process-state `offset` is larger or equal to the process-provided + /// source or destination buffer size, this indicates that we have completed + /// the requested description operation and return an error of + /// `ErrorCode::NOMEM`. A caller can use this as a method to check whether + /// the descryption operation has finished. + /// + /// If the process-state `offset` is `0`, we will initialize the AES engine + /// with an initialization vector (IV) provided by the application, and + /// configure it to perform an AES128-CTR operation. + fn run(&self, processid: ProcessId) -> Result<(), ErrorCode> { + // If the kernel's source buffer is not present in its TakeCell, + // this implies that we're currently running a decryption + // operation: + if self.source_buffer.is_none() { + return Err(ErrorCode::BUSY); + } + + self.process_grants + .enter(processid, |grant, kernel_data| { + // Get a reference to both the application-provided source + // and destination buffers: + let (source_processbuffer, dest_processbuffer) = kernel_data + .get_readonly_processbuffer(ro_allow::SOURCE) + .and_then(|source| { + kernel_data + .get_readwrite_processbuffer(rw_allow::DEST) + .map(|dest| (source, dest)) + })?; + + // Calculate the minimum length of the source & destination + // buffers: + let min_processbuffer_len = + core::cmp::min(source_processbuffer.len(), dest_processbuffer.len()); + + // If our operation-offset stored in the process grant exceeds + // `min_buffer_len`, then our operation is finished. Return with + // the appropriate error code. + // + // This check also ensures that we're never running a zero-byte + // encryption operation. + if grant.offset >= min_processbuffer_len { + return Err(ErrorCode::NOMEM); + } + + // Perform some special initialization if this is the first + // invocation of our AES engine as part of this decryption + // operation: + if grant.offset == 0 { + // Offset = 0, initialize the AES engine & IV: + self.aes.enable(); + + // Set the AES engine mode to AES128 CTR (counter) mode, and + // make this a decryption operation: + self.aes.set_mode_aes128ctr(true)?; + + self.aes.set_key(KEY)?; + + // Set the initialization vector: + kernel_data + .get_readonly_processbuffer(ro_allow::IV) + .and_then(|iv| { + iv.enter(|iv| { + let mut static_buf = + [0; kernel::hil::symmetric_encryption::AES128_KEY_SIZE]; + // Determine the size of the static buffer we have + let copy_len = core::cmp::min(static_buf.len(), iv.len()); + + // Clear any previous iv + for c in static_buf.iter_mut() { + *c = 0; + } + // Copy the data into the static buffer + iv[..copy_len].copy_to_slice(&mut static_buf[..copy_len]); + + AES128::set_iv(self.aes, &static_buf[..copy_len]) + }) + .map_err(Into::into) + })??; + } + + // Our AES engine works with kernel-provided `&'static mut` + // buffers. We copy a chunk of application's source buffer + // contents into this kernel buffer: + source_processbuffer.enter(|source_processbuffer| { + // Attempt to "take" the kernel-internal source & + // destination buffers from their TakeCells. We expect them + // to be placed back into the TakeCell after an encryption + // operation is done, and checked that the `source_buffer` + // is present above -- thus this should never fail: + let source_buffer = self.source_buffer.take().unwrap(); + let dest_buffer = self.dest_buffer.take().unwrap(); + + // Determine the amount of data we pass to the AES engine, + // which is the minimum of the user-provided buffer space + // and our kernel-internal buffer capacity: + let data_len = core::cmp::min(source_buffer.len(), min_processbuffer_len); + + // Now, copy this data into the kernel-internal buffer: + source_processbuffer[..data_len].copy_to_slice(&mut source_buffer[..data_len]); + + // However, our AES engine requires us to pass it at least + // `AES128_BLOCK_SIZE` data, and have our data length be a + // multiple of the `AES128_BLOCK_SIZE`. We assume that our + // `source_buffer` holds at least a full AES128 block. Then, + // we can round up or down to a multiple of the + // `AES128_BLOCK_SIZE` as required: + let crypt_len = if data_len < AES128_BLOCK_SIZE { + AES128_BLOCK_SIZE + } else { + data_len - (data_len % AES128_BLOCK_SIZE) + }; + + // Save `crypt_len`, so we know how much data to copy back + // to the process buffer after the operation finished: + self.crypt_len.set(crypt_len); + + // Set this process as active: + self.current_process.set(processid); + + // Now, run the operation: + if let Some((e, source, dest)) = + AES128::crypt(self.aes, Some(source_buffer), dest_buffer, 0, crypt_len) + { + // An error occurred, clear the currently active process + // and replace the buffers. Reset the current process' + // offset: + grant.offset = 0; + self.aes.disable(); + self.current_process.clear(); + self.source_buffer.replace(source.unwrap()); + self.dest_buffer.replace(dest); + e + } else { + Ok(()) + } + })? + }) + .unwrap_or(Err(ErrorCode::RESERVE)) + } + + fn run_next_pending(&self) { + if self.current_process.is_some() { + return; + } + + while let Some(processid) = self.next_pending() { + let res = self.run(processid); + + let _ = self.process_grants.enter(processid, |grant, kernel_data| { + grant.request_pending = false; + + if let Err(e) = res { + kernel_data + .schedule_upcall( + upcall::DONE, + (kernel::errorcode::into_statuscode(Err(e)), 0, 0), + ) + .ok(); + } + }); + } + } +} + +impl<'a, A: AES128<'a> + AES128Ctr> SyscallDriver for EncryptionOracleDriver<'a, A> { + fn command( + &self, + command_num: usize, + _data1: usize, + _data2: usize, + processid: ProcessId, + ) -> CommandReturn { + match command_num { + // Check whether the driver is present: + 0 => CommandReturn::success(), + + // Request the decryption operation: + 1 => { + let res = self + .process_grants + .enter(processid, |grant, _kernel_data| { + grant.request_pending = true; + CommandReturn::success() + }) + .unwrap_or_else(|err| err.into()); + + self.run_next_pending(); + + res + } + + // Unknown command number, return a NOSUPPORT error + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } + + fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { + self.process_grants.enter(processid, |_, _| {}) + } +} + +impl<'a, A: AES128<'a> + AES128Ctr> Client<'a> for EncryptionOracleDriver<'a, A> { + fn crypt_done(&'a self, mut source: Option<&'static mut [u8]>, destination: &'static mut [u8]) { + // One segment of encryption/decryption complete, move to next one or + // callback to user if done. + // + // In either case, place our kernel-internal source buffer back: + self.source_buffer + .replace(source.take().expect("source should never be None")); + + // Attempt to get a reference to the current process. This should never + // be none, given that we've just completed an operation: + let processid = self.current_process.unwrap_or_panic(); + + // Enter the process' grant, to copy the decrypted data back into the + // output processbuffer: + let _ = self.process_grants.enter(processid, |grant, kernel_data| { + if let Ok(dest_processbuffer) = kernel_data.get_readwrite_processbuffer(rw_allow::DEST) + { + // We have decrypted `self.crypt_len` bytes, starting + // `grant.offset` in the source processbuffer. If the + // destination processbuffer does not have enough space, + // truncate the data. If the buffer is smaller than the current + // offset, don't copy anything. + if grant.offset < dest_processbuffer.len() { + let copy_len = core::cmp::min( + self.crypt_len.get(), + dest_processbuffer.len() - grant.offset, + ); + let _ = dest_processbuffer.mut_enter(|dest_buffer| { + dest_buffer[grant.offset..(grant.offset + copy_len)] + .copy_from_slice(&destination[..copy_len]) + }); + grant.offset += copy_len; + } + } + }); + + // Place back the kernel-internal dest buffer: + self.dest_buffer.replace(destination); + + // Try to continue the decryption operation. This will return an error + // of `ErrorCode::NOMEM` if there is no more data to decrypt for the + // current process. + if let Err(ErrorCode::NOMEM) = self.run(processid) { + // We've completed the client's decryption request. Remove its + // `request_pending` flag, the `currrent_process` indication, + // and schedule an upcall accordingly: + self.current_process.clear(); + + let _ = self.process_grants.enter(processid, |grant, kernel_data| { + grant.offset = 0; + + // Pass the encryption/decryption operation length in the 2nd + // upcall argument. This is always the minimum of the app's + // provided source and destination buffers: + let len = core::cmp::min( + kernel_data + .get_readonly_processbuffer(ro_allow::SOURCE) + .map_or(0, |source| source.len()), + kernel_data + .get_readwrite_processbuffer(rw_allow::DEST) + .map_or(0, |dest| dest.len()), + ); + + kernel_data.schedule_upcall(upcall::DONE, (0, len, 0)).ok(); + }); + + // Attempt to schedule another operation for a new process: + self.run_next_pending(); + } + } +} diff --git a/capsules/extra/src/tutorials/mod.rs b/capsules/extra/src/tutorials/mod.rs new file mode 100644 index 0000000000..76c38ad7d1 --- /dev/null +++ b/capsules/extra/src/tutorials/mod.rs @@ -0,0 +1,15 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +pub mod encryption_oracle_chkpt0; +#[allow(dead_code)] +pub mod encryption_oracle_chkpt1; +#[allow(dead_code)] +pub mod encryption_oracle_chkpt2; +#[allow(dead_code)] +pub mod encryption_oracle_chkpt3; +#[allow(dead_code, unused_variables, unused_mut)] +pub mod encryption_oracle_chkpt4; +#[allow(dead_code)] +pub mod encryption_oracle_chkpt5; diff --git a/capsules/src/usb/cdc.rs b/capsules/extra/src/usb/cdc.rs similarity index 96% rename from capsules/src/usb/cdc.rs rename to capsules/extra/src/usb/cdc.rs index 77b52fd37f..df1b48ecaf 100644 --- a/capsules/src/usb/cdc.rs +++ b/capsules/extra/src/usb/cdc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Communications Class Device for USB //! //! This capsule allows Tock to support a serial port over USB. @@ -14,9 +18,7 @@ use super::descriptors::InterfaceDescriptor; use super::descriptors::TransferDirection; use super::usbc_client_ctrl::ClientCtrl; -use kernel::dynamic_deferred_call::{ - DeferredCallHandle, DynamicDeferredCall, DynamicDeferredCallClient, -}; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; use kernel::hil; use kernel::hil::time::{Alarm, AlarmClient, ConvertTicks}; use kernel::hil::uart; @@ -33,7 +35,7 @@ const ENDPOINT_IN_NUM: usize = 2; /// us. const ENDPOINT_OUT_NUM: usize = 3; -static LANGUAGES: &'static [u16; 1] = &[ +static LANGUAGES: &[u16; 1] = &[ 0x0409, // English (United States) ]; /// Platform-specific packet length for the `SAM4L` USB hardware. @@ -153,10 +155,8 @@ pub struct CdcAcm<'a, U: 'a, A: 'a + Alarm<'a>> { /// delivered over the console). boot_period: Cell, - /// Deferred Caller - deferred_caller: &'a DynamicDeferredCall, - /// Deferred Call Handle - handle: OptionalCell, + /// Deferred Call + deferred_call: DeferredCall, /// Flag to mark we are waiting on a deferred call for dropping a TX. This /// can happen if an upper layer told us to transmit a buffer, but there is /// no host connected and therefore we cannot actually transmit. However, @@ -188,7 +188,6 @@ impl<'a, U: hil::usb::UsbController<'a>, A: 'a + Alarm<'a>> CdcAcm<'a, U, A> { product_id: u16, strings: &'static [&'static str; 3], timeout_alarm: &'a A, - deferred_caller: &'a DynamicDeferredCall, host_initiated_function: Option<&'a (dyn Fn() + 'a)>, ) -> Self { let interfaces: &mut [InterfaceDescriptor] = &mut [ @@ -263,8 +262,8 @@ impl<'a, U: hil::usb::UsbController<'a>, A: 'a + Alarm<'a>> CdcAcm<'a, U, A> { let (device_descriptor_buffer, other_descriptor_buffer) = descriptors::create_descriptor_buffers( descriptors::DeviceDescriptor { - vendor_id: vendor_id, - product_id: product_id, + vendor_id, + product_id, manufacturer_string: 1, product_string: 2, serial_number_string: 3, @@ -308,18 +307,13 @@ impl<'a, U: hil::usb::UsbController<'a>, A: 'a + Alarm<'a>> CdcAcm<'a, U, A> { rx_client: OptionalCell::empty(), timeout_alarm, boot_period: Cell::new(true), - deferred_caller, - handle: OptionalCell::empty(), + deferred_call: DeferredCall::new(), deferred_call_pending_droptx: Cell::new(false), deferred_call_pending_abortrx: Cell::new(false), host_initiated_function, } } - pub fn initialize_callback_handle(&self, handle: DeferredCallHandle) { - self.handle.replace(handle); - } - #[inline] pub fn controller(&self) -> &'a U { self.client_ctrl.controller() @@ -354,8 +348,8 @@ impl<'a, U: hil::usb::UsbController<'a>, A: 'a + Alarm<'a>> CdcAcm<'a, U, A> { match self.state.get() { State::Enumerated => { self.state.set(State::Connecting { - line_coding: line_coding, - line_state: line_state, + line_coding, + line_state, }); } State::Connecting { @@ -704,7 +698,7 @@ impl<'a, U: hil::usb::UsbController<'a>, A: 'a + Alarm<'a>> uart::Transmit<'a> // indicate success, but we will not actually queue this message -- just schedule // a deferred callback to return the buffer immediately. self.deferred_call_pending_droptx.set(true); - self.handle.map(|handle| self.deferred_caller.set(*handle)); + self.deferred_call.set(); Ok(()) } } @@ -750,7 +744,7 @@ impl<'a, U: hil::usb::UsbController<'a>, A: 'a + Alarm<'a>> uart::Receive<'a> fo // If we do have a receive pending then we need to start a deferred // call to set the callback and return `BUSY`. self.deferred_call_pending_abortrx.set(true); - self.handle.map(|handle| self.deferred_caller.set(*handle)); + self.deferred_call.set(); Err(ErrorCode::BUSY) } } @@ -783,10 +777,10 @@ impl<'a, U: hil::usb::UsbController<'a>, A: 'a + Alarm<'a>> AlarmClient for CdcA } } -impl<'a, U: hil::usb::UsbController<'a>, A: 'a + Alarm<'a>> DynamicDeferredCallClient +impl<'a, U: hil::usb::UsbController<'a>, A: 'a + Alarm<'a>> DeferredCallClient for CdcAcm<'a, U, A> { - fn call(&self, _handle: DeferredCallHandle) { + fn handle_deferred_call(&self) { if self.deferred_call_pending_droptx.replace(false) { self.indicate_tx_success() } @@ -810,4 +804,8 @@ impl<'a, U: hil::usb::UsbController<'a>, A: 'a + Alarm<'a>> DynamicDeferredCallC }); } } + + fn register(&'static self) { + self.deferred_call.register(self); + } } diff --git a/capsules/src/usb/ctap.rs b/capsules/extra/src/usb/ctap.rs similarity index 96% rename from capsules/src/usb/ctap.rs rename to capsules/extra/src/usb/ctap.rs index d085b72865..066b24bd7f 100644 --- a/capsules/src/usb/ctap.rs +++ b/capsules/extra/src/usb/ctap.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Client to Authenticator Protocol CTAPv2 over USB HID //! //! Based on the spec avaliable at: @@ -30,7 +34,7 @@ const ENDPOINT_NUM: usize = 1; const OUT_BUFFER: usize = 0; const IN_BUFFER: usize = 1; -static LANGUAGES: &'static [u16; 1] = &[ +static LANGUAGES: &[u16; 1] = &[ 0x0409, // English (United States) ]; /// Max packet size specified by spec @@ -42,8 +46,8 @@ const N_ENDPOINTS: usize = 2; /// This is a combination of: /// - the CTAP spec, example 8 /// - USB HID spec examples -/// Plus it matches: https://chromium.googlesource.com/chromiumos/platform2/+/master/u2fd/u2fhid.cc -static REPORT_DESCRIPTOR: &'static [u8] = &[ +/// Plus it matches: +static REPORT_DESCRIPTOR: &[u8] = &[ 0x06, 0xD0, 0xF1, // HID_UsagePage ( FIDO_USAGE_PAGE ), 0x09, 0x01, // HID_Usage ( FIDO_USAGE_CTAPHID ), 0xA1, 0x01, // HID_Collection ( HID_Application ), @@ -66,7 +70,7 @@ static REPORT: ReportDescriptor<'static> = ReportDescriptor { desc: REPORT_DESCRIPTOR, }; -static SUB_HID_DESCRIPTOR: &'static [HIDSubordinateDescriptor] = &[HIDSubordinateDescriptor { +static SUB_HID_DESCRIPTOR: &[HIDSubordinateDescriptor] = &[HIDSubordinateDescriptor { typ: DescriptorType::Report, len: REPORT_DESCRIPTOR.len() as u16, }]; @@ -140,8 +144,8 @@ impl<'a, U: hil::usb::UsbController<'a>> CtapHid<'a, U> { let (device_descriptor_buffer, other_descriptor_buffer) = descriptors::create_descriptor_buffers( descriptors::DeviceDescriptor { - vendor_id: vendor_id, - product_id: product_id, + vendor_id, + product_id, manufacturer_string: 1, product_string: 2, serial_number_string: 3, diff --git a/capsules/src/usb/descriptors.rs b/capsules/extra/src/usb/descriptors.rs similarity index 98% rename from capsules/src/usb/descriptors.rs rename to capsules/extra/src/usb/descriptors.rs index 3a16919d17..effcb997d8 100644 --- a/capsules/src/usb/descriptors.rs +++ b/capsules/extra/src/usb/descriptors.rs @@ -1,10 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Platform-independent USB 2.0 protocol library. //! //! Mostly data types for USB descriptors. use core::cell::Cell; use core::cmp::min; -use core::convert::From; use core::fmt; use kernel::hil::usb::TransferType; @@ -26,7 +29,7 @@ pub struct Buffer64 { impl Default for Buffer64 { fn default() -> Self { Self { - buf: [VolatileCell::default(); 64], + buf: [(); 64].map(|()| VolatileCell::default()), } } } @@ -697,7 +700,7 @@ impl Descriptor for EndpointDescriptor { // The below implicitly sets Synchronization Type to "No Synchronization" and // Usage Type to "Data endpoint" buf[3].set(self.transfer_type as u8); - put_u16(&buf[4..6], self.max_packet_size & 0x7ff as u16); + put_u16(&buf[4..6], self.max_packet_size & 0x7ff_u16); buf[6].set(self.interval); len } @@ -945,7 +948,7 @@ fn get_u32(b0: u8, b1: u8, b2: u8, b3: u8) -> u32 { } /// Write a `u16` to a buffer for transmission on the bus -fn put_u16<'a>(buf: &'a [Cell], n: u16) { +fn put_u16(buf: &[Cell], n: u16) { buf[0].set((n & 0xff) as u8); buf[1].set((n >> 8) as u8); } diff --git a/capsules/extra/src/usb/keyboard_hid.rs b/capsules/extra/src/usb/keyboard_hid.rs new file mode 100644 index 0000000000..52a411ce58 --- /dev/null +++ b/capsules/extra/src/usb/keyboard_hid.rs @@ -0,0 +1,323 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Keyboard USB HID device + +use super::descriptors; +use super::descriptors::Buffer64; +use super::descriptors::DescriptorType; +use super::descriptors::EndpointAddress; +use super::descriptors::EndpointDescriptor; +use super::descriptors::HIDCountryCode; +use super::descriptors::HIDDescriptor; +use super::descriptors::HIDSubordinateDescriptor; +use super::descriptors::InterfaceDescriptor; +use super::descriptors::ReportDescriptor; +use super::descriptors::TransferDirection; +use super::usbc_client_ctrl::ClientCtrl; + +use kernel::hil; +use kernel::hil::usb::TransferType; +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::cells::TakeCell; +use kernel::ErrorCode; + +/// Use 1 Interrupt transfer IN/OUT endpoint +const ENDPOINT_NUM: usize = 1; + +const IN_BUFFER: usize = 0; + +static LANGUAGES: &[u16; 1] = &[ + 0x0409, // English (United States) +]; +/// Max packet size specified by spec +pub const MAX_CTRL_PACKET_SIZE: u8 = 64; + +const N_ENDPOINTS: usize = 1; + +/// The HID report descriptor for keyboard from +/// . +static REPORT_DESCRIPTOR: &[u8] = &[ + 0x05, 0x01, // Usage Page (Generic Desktop), + 0x09, 0x06, // Usage (Keyboard), + 0xA1, 0x01, // Collection (Application), + 0x75, 0x01, // Report Size (1), + 0x95, 0x08, // Report Count (8), + 0x05, 0x07, // Usage Page (Key Codes), + 0x19, 0xE0, // Usage Minimum (224), + 0x29, 0xE7, // Usage Maximum (231), + 0x15, 0x00, // Logical Minimum (0), + 0x25, 0x01, // Logical Maximum (1), + 0x81, 0x02, // Input (Data, Variable, Absolute), + // ;Modifier byte + 0x95, 0x01, // Report Count (1), + 0x75, 0x08, // Report Size (8), + 0x81, 0x03, // Input (Constant), + // ;Reserved byte + 0x95, 0x05, // Report Count (5), + 0x75, 0x01, // Report Size (1), + 0x05, 0x08, // Usage Page (LEDs), + 0x19, 0x01, // Usage Minimum (1), + 0x29, 0x05, // Usage Maximum (5), + 0x91, 0x02, // Output (Data, Variable, Absolute), + // ;LED report + 0x95, 0x01, // Report Count (1), + 0x75, 0x03, // Report Size (3), + 0x91, 0x03, // Output (Constant), ;LED report + // padding + 0x95, 0x06, //................// Report Count (6), + 0x75, 0x08, // Report Size (8), + 0x15, 0x00, // Logical Minimum (0), + 0x25, 0x68, // Logical Maximum(104), + 0x05, 0x07, // Usage Page (Key Codes), + 0x19, 0x00, // Usage Minimum (0), + 0x29, 0x68, // Usage Maximum (104), + 0x81, 0x00, // Input (Data, Array), + 0xc0, // End Collection +]; + +static REPORT: ReportDescriptor<'static> = ReportDescriptor { + desc: REPORT_DESCRIPTOR, +}; + +static SUB_HID_DESCRIPTOR: &[HIDSubordinateDescriptor] = &[HIDSubordinateDescriptor { + typ: DescriptorType::Report, + len: REPORT_DESCRIPTOR.len() as u16, +}]; + +static HID_DESCRIPTOR: HIDDescriptor<'static> = HIDDescriptor { + hid_class: 0x0111, + country_code: HIDCountryCode::NotSupported, + sub_descriptors: SUB_HID_DESCRIPTOR, +}; + +/// Implementation of the CTAP HID (Human Interface Device) +pub struct KeyboardHid<'a, U: 'a> { + /// Helper USB client library for handling many USB operations. + client_ctrl: ClientCtrl<'a, 'static, U>, + + /// 64 byte buffers for each endpoint. + buffers: [Buffer64; N_ENDPOINTS], + + client: OptionalCell<&'a dyn hil::usb_hid::Client<'a, [u8; 64]>>, + + /// A buffer to hold the data we want to send + send_buffer: TakeCell<'static, [u8; 64]>, +} + +impl<'a, U: hil::usb::UsbController<'a>> KeyboardHid<'a, U> { + pub fn new( + controller: &'a U, + vendor_id: u16, + product_id: u16, + strings: &'static [&'static str; 3], + ) -> Self { + let interfaces: &mut [InterfaceDescriptor] = &mut [InterfaceDescriptor { + interface_number: 0, + interface_class: 0x03, // HID + interface_subclass: 0x01, // Boot subclass + interface_protocol: 0x01, // Keyboard + ..InterfaceDescriptor::default() + }]; + + let endpoints: &[&[EndpointDescriptor]] = &[&[EndpointDescriptor { + endpoint_address: EndpointAddress::new_const( + ENDPOINT_NUM, + TransferDirection::DeviceToHost, + ), + transfer_type: TransferType::Interrupt, + max_packet_size: 8, + interval: 10, + }]]; + + let (device_descriptor_buffer, other_descriptor_buffer) = + descriptors::create_descriptor_buffers( + descriptors::DeviceDescriptor { + vendor_id, + product_id, + manufacturer_string: 1, + product_string: 2, + serial_number_string: 3, + max_packet_size_ep0: MAX_CTRL_PACKET_SIZE, + ..descriptors::DeviceDescriptor::default() + }, + descriptors::ConfigurationDescriptor { + attributes: descriptors::ConfigurationAttributes::new(true, true), + max_power: 0x32, + ..descriptors::ConfigurationDescriptor::default() + }, + interfaces, + endpoints, + Some(&HID_DESCRIPTOR), + None, + ); + + KeyboardHid { + client_ctrl: ClientCtrl::new( + controller, + device_descriptor_buffer, + other_descriptor_buffer, + Some(&HID_DESCRIPTOR), + Some(&REPORT), + LANGUAGES, + strings, + ), + buffers: [Buffer64::default()], + client: OptionalCell::empty(), + send_buffer: TakeCell::empty(), + } + } + + #[inline] + fn controller(&self) -> &'a U { + self.client_ctrl.controller() + } + + pub fn set_client(&'a self, client: &'a dyn hil::usb_hid::Client<'a, [u8; 64]>) { + self.client.set(client); + } +} + +impl<'a, U: hil::usb::UsbController<'a>> hil::usb_hid::UsbHid<'a, [u8; 64]> for KeyboardHid<'a, U> { + fn send_buffer( + &'a self, + send: &'static mut [u8; 64], + ) -> Result { + let len = send.len(); + + self.send_buffer.replace(send); + self.controller().endpoint_resume_in(ENDPOINT_NUM); + + Ok(len) + } + + fn send_cancel(&'a self) -> Result<&'static mut [u8; 64], ErrorCode> { + match self.send_buffer.take() { + Some(buf) => Ok(buf), + None => Err(ErrorCode::BUSY), + } + } + + // Keyboard doesn't use receive so this is unimplemented. + fn receive_buffer( + &'a self, + _recv: &'static mut [u8; 64], + ) -> Result<(), (ErrorCode, &'static mut [u8; 64])> { + Ok(()) + } + + // Keyboard doesn't use receive so this is unimplemented. + fn receive_cancel(&'a self) -> Result<&'static mut [u8; 64], ErrorCode> { + Err(ErrorCode::BUSY) + } +} + +impl<'a, U: hil::usb::UsbController<'a>> hil::usb::Client<'a> for KeyboardHid<'a, U> { + fn enable(&'a self) { + // Set up the default control endpoint + self.client_ctrl.enable(); + + // Setup buffers for IN data transfer. + self.controller() + .endpoint_set_in_buffer(ENDPOINT_NUM, &self.buffers[IN_BUFFER].buf); + self.controller() + .endpoint_in_out_enable(TransferType::Interrupt, ENDPOINT_NUM); + } + + fn attach(&'a self) { + self.client_ctrl.attach(); + } + + fn bus_reset(&'a self) {} + + /// Handle a Control Setup transaction. + fn ctrl_setup(&'a self, endpoint: usize) -> hil::usb::CtrlSetupResult { + self.client_ctrl.ctrl_setup(endpoint) + } + + /// Handle a Control In transaction + fn ctrl_in(&'a self, endpoint: usize) -> hil::usb::CtrlInResult { + self.client_ctrl.ctrl_in(endpoint) + } + + /// Handle a Control Out transaction + fn ctrl_out(&'a self, _endpoint: usize, _packet_bytes: u32) -> hil::usb::CtrlOutResult { + // self.client_ctrl.ctrl_out(endpoint, packet_bytes) + hil::usb::CtrlOutResult::Ok + } + + fn ctrl_status(&'a self, endpoint: usize) { + self.client_ctrl.ctrl_status(endpoint) + } + + /// Handle the completion of a Control transfer + fn ctrl_status_complete(&'a self, endpoint: usize) { + self.client_ctrl.ctrl_status_complete(endpoint) + } + + /// Handle a Bulk/Interrupt IN transaction. + /// + /// This is called when we can send data to the host. It should get called + /// when we tell the controller we want to resume the IN endpoint (meaning + /// we know we have data to send) and afterwards until we return + /// `hil::usb::InResult::Delay` from this function. That means we can use + /// this as a callback to mean that the transmission finished by waiting + /// until this function is called when we don't have anything left to send. + fn packet_in(&'a self, transfer_type: TransferType, _endpoint: usize) -> hil::usb::InResult { + match transfer_type { + TransferType::Interrupt => { + self.send_buffer + .take() + .map_or(hil::usb::InResult::Delay, |buf| { + // Get packet that we have shared with the underlying + // USB stack to copy the tx into. + let packet = &self.buffers[IN_BUFFER].buf; + + // Copy from the TX buffer to the outgoing USB packet. + // Our endpoint supports exactly 8 bytes so we use that + // length. + for i in 0..8 { + packet[i].set(buf[i]); + } + + // Put the TX buffer back so we can keep sending from + // it. + self.send_buffer.replace(buf); + + // Return that we have data to send. + hil::usb::InResult::Packet(8) + }) + } + TransferType::Bulk | TransferType::Control | TransferType::Isochronous => { + hil::usb::InResult::Error + } + } + } + + /// Handle a Bulk/Interrupt OUT transaction + /// + /// Unused for keyboard. + fn packet_out( + &'a self, + transfer_type: TransferType, + _endpoint: usize, + _packet_bytes: u32, + ) -> hil::usb::OutResult { + match transfer_type { + TransferType::Interrupt => hil::usb::OutResult::Ok, + + TransferType::Bulk | TransferType::Control | TransferType::Isochronous => { + hil::usb::OutResult::Error + } + } + } + + fn packet_transmitted(&'a self, endpoint: usize) { + self.send_buffer.take().map(|buf| { + self.client.map(move |client| { + client.packet_transmitted(Ok(()), buf, endpoint); + }); + }); + } +} diff --git a/capsules/extra/src/usb/mod.rs b/capsules/extra/src/usb/mod.rs new file mode 100644 index 0000000000..af59a95319 --- /dev/null +++ b/capsules/extra/src/usb/mod.rs @@ -0,0 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +pub mod cdc; +pub mod ctap; +pub mod descriptors; +pub mod keyboard_hid; +pub mod usb_user; +pub mod usbc_client; +pub mod usbc_client_ctrl; diff --git a/capsules/src/usb/usb_user.rs b/capsules/extra/src/usb/usb_user.rs similarity index 93% rename from capsules/src/usb/usb_user.rs rename to capsules/extra/src/usb/usb_user.rs index 767291851d..ecd322282e 100644 --- a/capsules/src/usb/usb_user.rs +++ b/capsules/extra/src/usb/usb_user.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! USB system call interface //! //! This capsule provides a system call interface to the USB controller. @@ -9,7 +13,7 @@ //! the USBC), as well as a `Grant` for managing application requests. For //! example: //! -//! ```rust +//! ```rust,ignore //! # use kernel::static_init; //! //! // Configure the USB controller @@ -32,7 +36,7 @@ use kernel::syscall::{CommandReturn, SyscallDriver}; use kernel::utilities::cells::OptionalCell; use kernel::{ErrorCode, ProcessId}; -use crate::driver; +use capsules_core::driver; pub const DRIVER_NUM: usize = driver::NUM::UsbUser as usize; #[derive(Default)] @@ -55,8 +59,8 @@ where apps: Grant, AllowRoCount<0>, AllowRwCount<0>>, ) -> Self { UsbSyscallDriver { - usbc_client: usbc_client, - apps: apps, + usbc_client, + apps, serving_app: OptionalCell::empty(), } } @@ -116,7 +120,7 @@ where command_num: usize, _arg: usize, _: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { match command_num { // This driver is present @@ -126,7 +130,7 @@ where 1 => { let result = self .apps - .enter(appid, |app, _| { + .enter(processid, |app, _| { if app.awaiting.is_some() { // Each app may make only one request at a time Err(ErrorCode::BUSY) diff --git a/capsules/src/usb/usbc_client.rs b/capsules/extra/src/usb/usbc_client.rs similarity index 97% rename from capsules/src/usb/usbc_client.rs rename to capsules/extra/src/usb/usbc_client.rs index 72b004ea28..12b49d31c6 100644 --- a/capsules/src/usb/usbc_client.rs +++ b/capsules/extra/src/usb/usbc_client.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! A bare-bones client of the USB hardware interface. //! //! It responds to standard device requests and can be enumerated. @@ -17,11 +21,11 @@ use kernel::utilities::cells::VolatileCell; const VENDOR_ID: u16 = 0x6667; const PRODUCT_ID: u16 = 0xabcd; -static LANGUAGES: &'static [u16; 1] = &[ +static LANGUAGES: &[u16; 1] = &[ 0x0409, // English (United States) ]; -static STRINGS: &'static [&'static str] = &[ +static STRINGS: &[&str] = &[ "XYZ Corp.", // Manufacturer "The Zorpinator", // Product "Serial No. 5", // Serial number @@ -236,7 +240,7 @@ impl<'a, C: hil::usb::UsbController<'a>> hil::usb::Client<'a> for Client<'a, C> // Consume a packet from the endpoint buffer let new_len = packet_bytes as usize; let current_len = self.echo_len.get(); - let total_len = current_len + new_len as usize; + let total_len = current_len + new_len; if total_len > self.echo_buf.len() { // The packet won't fit in our little buffer. We'll have diff --git a/capsules/src/usb/usbc_client_ctrl.rs b/capsules/extra/src/usb/usbc_client_ctrl.rs similarity index 98% rename from capsules/src/usb/usbc_client_ctrl.rs rename to capsules/extra/src/usb/usbc_client_ctrl.rs index c8e55a8a86..1d5cf13cb7 100644 --- a/capsules/src/usb/usbc_client_ctrl.rs +++ b/capsules/extra/src/usb/usbc_client_ctrl.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! A generic USB client layer managing control requests //! //! This layer responds to control requests and handles the state machine for @@ -5,7 +9,7 @@ //! //! Right now, the stack looks like this: //! -//! ``` +//! ```text //! Client //! | ^ //! |----- | @@ -81,8 +85,9 @@ pub struct ClientCtrl<'a, 'b, U: 'a> { } /// States for the individual endpoints. -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Default)] enum State { + #[default] Init, /// We are doing a Control In transfer of some data in @@ -95,12 +100,6 @@ enum State { SetAddress, } -impl Default for State { - fn default() -> Self { - State::Init - } -} - impl<'a, 'b, U: hil::usb::UsbController<'a>> ClientCtrl<'a, 'b, U> { pub fn new( controller: &'a U, @@ -112,7 +111,7 @@ impl<'a, 'b, U: hil::usb::UsbController<'a>> ClientCtrl<'a, 'b, U> { strings: &'b [&'b str], ) -> Self { ClientCtrl { - controller: controller, + controller, state: Default::default(), // For the moment, the Default trait is not implemented for arrays // of length > 32, and the Cell type is not Copy, so we have to diff --git a/capsules/src/ctap.rs b/capsules/extra/src/usb_hid_driver.rs similarity index 81% rename from capsules/src/ctap.rs rename to capsules/extra/src/usb_hid_driver.rs index 8714003c66..6767199876 100644 --- a/capsules/src/ctap.rs +++ b/capsules/extra/src/usb_hid_driver.rs @@ -1,31 +1,9 @@ -//! Provides userspace with access CTAP devices over any transport -//! layer (USB HID, BLE, NFC). Currently only USB HID is supported. -//! -//! Setup -//! ----- -//! -//! You need a device that provides the `hil::usb::UsbController` and -//! `hil::usb_hid::UsbHid` trait. -//! -//! ```rust -//! let ctap_send_buffer = static_init!([u8; 64], [0; 64]); -//! let ctap_recv_buffer = static_init!([u8; 64], [0; 64]); -//! -//! let (ctap, ctap_driver) = components::ctap::CtapComponent::new( -//! &earlgrey::usbdev::USB, -//! 0x1337, // My important company -//! 0x0DEC, // My device name -//! strings, -//! board_kernel, -//! ctap_send_buffer, -//! ctap_recv_buffer, -//! ) -//! .finalize(components::usb_ctap_component_helper!(lowrisc::usbdev::Usb)); -//! -//! ctap.enable(); -//! ctap.attach(); -//! ``` -//! +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Provides userspace with access to USB HID devices with a simple syscall +//! interface. use core::cell::Cell; use core::marker::PhantomData; @@ -37,16 +15,12 @@ use kernel::syscall::{CommandReturn, SyscallDriver}; use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::{ErrorCode, ProcessId}; -/// Syscall driver number. -use crate::driver; -pub const DRIVER_NUM: usize = driver::NUM::CtapHid as usize; - /// Ids for read-write allow buffers mod rw_allow { pub const RECV: usize = 0; pub const SEND: usize = 1; /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 2; + pub const COUNT: u8 = 2; } pub struct App { @@ -61,28 +35,28 @@ impl Default for App { } } -pub struct CtapDriver<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> { +pub struct UsbHidDriver<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> { usb: Option<&'a U>, app: Grant, AllowRoCount<0>, AllowRwCount<{ rw_allow::COUNT }>>, - appid: OptionalCell, + processid: OptionalCell, phantom: PhantomData<&'a U>, send_buffer: TakeCell<'static, [u8; 64]>, recv_buffer: TakeCell<'static, [u8; 64]>, } -impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> CtapDriver<'a, U> { +impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> UsbHidDriver<'a, U> { pub fn new( usb: Option<&'a U>, send_buffer: &'static mut [u8; 64], recv_buffer: &'static mut [u8; 64], grant: Grant, AllowRoCount<0>, AllowRwCount<{ rw_allow::COUNT }>>, - ) -> CtapDriver<'a, U> { - CtapDriver { - usb: usb, + ) -> UsbHidDriver<'a, U> { + UsbHidDriver { + usb, app: grant, - appid: OptionalCell::empty(), + processid: OptionalCell::empty(), phantom: PhantomData, send_buffer: TakeCell::new(send_buffer), recv_buffer: TakeCell::new(recv_buffer), @@ -90,7 +64,7 @@ impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> CtapDriver<'a, U> { } } -impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> usb_hid::UsbHid<'a, [u8; 64]> for CtapDriver<'a, U> { +impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> usb_hid::UsbHid<'a, [u8; 64]> for UsbHidDriver<'a, U> { fn send_buffer( &'a self, send: &'static mut [u8; 64], @@ -130,16 +104,16 @@ impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> usb_hid::UsbHid<'a, [u8; 64]> for Cta } } -impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> usb_hid::Client<'a, [u8; 64]> for CtapDriver<'a, U> { +impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> usb_hid::Client<'a, [u8; 64]> for UsbHidDriver<'a, U> { fn packet_received( &'a self, _result: Result<(), ErrorCode>, buffer: &'static mut [u8; 64], _endpoint: usize, ) { - self.appid.map(|id| { + self.processid.map(|id| { self.app - .enter(*id, |app, kernel_data| { + .enter(id, |app, kernel_data| { let _ = kernel_data .get_readwrite_processbuffer(rw_allow::RECV) .and_then(|recv| { @@ -167,9 +141,9 @@ impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> usb_hid::Client<'a, [u8; 64]> for Cta buffer: &'static mut [u8; 64], _endpoint: usize, ) { - self.appid.map(|id| { + self.processid.map(|id| { self.app - .enter(*id, |_app, kernel_data| { + .enter(id, |_app, kernel_data| { kernel_data.schedule_upcall(0, (1, 0, 0)).ok(); }) .map_err(|err| { @@ -184,24 +158,24 @@ impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> usb_hid::Client<'a, [u8; 64]> for Cta } fn can_receive(&'a self) -> bool { - self.appid + self.processid .map(|id| { self.app - .enter(*id, |app, _| app.can_receive.get()) + .enter(id, |app, _| app.can_receive.get()) .unwrap_or(false) }) .unwrap_or(false) } } -impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> SyscallDriver for CtapDriver<'a, U> { - // Subscribe to CtapDriver events. +impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> SyscallDriver for UsbHidDriver<'a, U> { + // Subscribe to UsbHidDriver events. // // ### `subscribe_num` // - // - `0`: Subscribe to interrupts from Ctap events. + // - `0`: Subscribe to interrupts from HID events. // The callback signature is `fn(direction: u32)` - // `fn(0)` indicates a packet was recieved + // `fn(0)` indicates a packet was received // `fn(1)` indicates a packet was transmitted fn command( @@ -209,11 +183,11 @@ impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> SyscallDriver for CtapDriver<'a, U> { command_num: usize, _data1: usize, _data2: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { - let can_access = self.appid.map_or(true, |owning_app| { - if owning_app == &appid { - // We own the Ctap device + let can_access = self.processid.map_or(true, |owning_app| { + if owning_app == processid { + // We own the HID device true } else { false @@ -225,18 +199,20 @@ impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> SyscallDriver for CtapDriver<'a, U> { } match command_num { + 0 => CommandReturn::success(), + // Send data - 0 => self + 1 => self .app - .enter(appid, |_, kernel_data| { - self.appid.set(appid); + .enter(processid, |_, kernel_data| { + self.processid.set(processid); if let Some(usb) = self.usb { kernel_data .get_readwrite_processbuffer(rw_allow::SEND) .and_then(|send| { send.enter(|data| { self.send_buffer.take().map_or( - CommandReturn::failure(ErrorCode::RESERVE), + CommandReturn::failure(ErrorCode::BUSY), |buf| { // Copy the data into the static buffer data.copy_to_slice(buf); @@ -253,16 +229,17 @@ impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> SyscallDriver for CtapDriver<'a, U> { } }) .unwrap_or_else(|err| err.into()), + // Allow receive - 1 => self + 2 => self .app - .enter(appid, |app, _| { - self.appid.set(appid); + .enter(processid, |app, _| { + self.processid.set(processid); if let Some(usb) = self.usb { app.can_receive.set(true); if let Some(buf) = self.recv_buffer.take() { match usb.receive_buffer(buf) { - Ok(_) => CommandReturn::success(), + Ok(()) => CommandReturn::success(), Err((err, buffer)) => { self.recv_buffer.replace(buffer); CommandReturn::failure(err) @@ -276,11 +253,12 @@ impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> SyscallDriver for CtapDriver<'a, U> { } }) .unwrap_or_else(|err| err.into()), + // Cancel send - 2 => self + 3 => self .app - .enter(appid, |_app, _| { - self.appid.set(appid); + .enter(processid, |_app, _| { + self.processid.set(processid); if let Some(usb) = self.usb { match usb.receive_cancel() { Ok(buf) => { @@ -294,11 +272,12 @@ impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> SyscallDriver for CtapDriver<'a, U> { } }) .unwrap_or_else(|err| err.into()), + // Cancel receive - 3 => self + 4 => self .app - .enter(appid, |_app, _| { - self.appid.set(appid); + .enter(processid, |_app, _| { + self.processid.set(processid); if let Some(usb) = self.usb { match usb.receive_cancel() { Ok(buf) => { @@ -312,6 +291,7 @@ impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> SyscallDriver for CtapDriver<'a, U> { } }) .unwrap_or_else(|err| err.into()), + // Send or receive // This command has two parts. // Part 1: Receive @@ -326,9 +306,9 @@ impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> SyscallDriver for CtapDriver<'a, U> { // outcome as calling the Allow receive command. // As well as that we will then send the data in the // send buffer. - 4 => self + 5 => self .app - .enter(appid, |app, kernel_data| { + .enter(processid, |app, kernel_data| { if let Some(usb) = self.usb { if app.can_receive.get() { // We are already receiving @@ -337,7 +317,7 @@ impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> SyscallDriver for CtapDriver<'a, U> { app.can_receive.set(true); if let Some(buf) = self.recv_buffer.take() { match usb.receive_buffer(buf) { - Ok(_) => CommandReturn::success(), + Ok(()) => CommandReturn::success(), Err((err, buffer)) => { self.recv_buffer.replace(buffer); return CommandReturn::failure(err); @@ -356,7 +336,7 @@ impl<'a, U: usb_hid::UsbHid<'a, [u8; 64]>> SyscallDriver for CtapDriver<'a, U> { .and_then(|send| { send.enter(|data| { self.send_buffer.take().map_or( - CommandReturn::failure(ErrorCode::RESERVE), + CommandReturn::failure(ErrorCode::BUSY), |buf| { // Copy the data into the static buffer data.copy_to_slice(buf); diff --git a/capsules/extra/src/virtual_kv.rs b/capsules/extra/src/virtual_kv.rs new file mode 100644 index 0000000000..96e741605e --- /dev/null +++ b/capsules/extra/src/virtual_kv.rs @@ -0,0 +1,448 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Tock Key-Value virtualizer. +//! +//! This capsule provides a virtualized Key-Value store interface. +//! +//! ```text +//! +-------------------------+ +//! | Capsule using K-V | +//! +-------------------------+ +//! +//! hil::kv::KVPermissions +//! +//! +-------------------------+ +//! | Virtualizer (this file) | +//! +-------------------------+ +//! +//! hil::kv::KVPermissions +//! +//! +-------------------------+ +//! | K-V store Permissions | +//! +-------------------------+ +//! +//! hil::kv::KV +//! +//! +-------------------------+ +//! | K-V library | +//! +-------------------------+ +//! +//! hil::flash +//! ``` + +use kernel::collections::list::{List, ListLink, ListNode}; + +use kernel::hil::kv; +use kernel::hil::kv::KVPermissions; +use kernel::storage_permissions::StoragePermissions; +use kernel::utilities::cells::{MapCell, OptionalCell}; +use kernel::utilities::leasable_buffer::SubSliceMut; +use kernel::ErrorCode; + +#[derive(Clone, Copy, PartialEq, Debug)] +enum Operation { + Get, + Set, + Delete, + Add, + Update, +} + +pub struct VirtualKVPermissions<'a, V: kv::KVPermissions<'a>> { + mux_kv: &'a MuxKVPermissions<'a, V>, + next: ListLink<'a, VirtualKVPermissions<'a, V>>, + + client: OptionalCell<&'a dyn kv::KVClient>, + operation: OptionalCell, + + key: MapCell>, + value: MapCell>, + valid_ids: OptionalCell, +} + +impl<'a, V: kv::KVPermissions<'a>> ListNode<'a, VirtualKVPermissions<'a, V>> + for VirtualKVPermissions<'a, V> +{ + fn next(&self) -> &'a ListLink> { + &self.next + } +} + +impl<'a, V: kv::KVPermissions<'a>> VirtualKVPermissions<'a, V> { + pub fn new(mux_kv: &'a MuxKVPermissions<'a, V>) -> VirtualKVPermissions<'a, V> { + Self { + mux_kv, + next: ListLink::empty(), + client: OptionalCell::empty(), + operation: OptionalCell::empty(), + key: MapCell::empty(), + value: MapCell::empty(), + valid_ids: OptionalCell::empty(), + } + } + + pub fn setup(&'a self) { + self.mux_kv.users.push_head(self); + } + + fn insert( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + permissions: StoragePermissions, + operation: Operation, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + > { + match permissions.get_write_id() { + Some(_write_id) => {} + None => return Err((key, value, ErrorCode::INVAL)), + } + + if self.operation.is_some() { + return Err((key, value, ErrorCode::BUSY)); + } + + // The caller must ensure there is space for the header. + if value.len() < self.header_size() { + return Err((key, value, ErrorCode::SIZE)); + } + + self.operation.set(operation); + self.valid_ids.set(permissions); + self.key.replace(key); + self.value.replace(value); + + self.mux_kv + .do_next_op(false) + .map_err(|e| (self.key.take().unwrap(), self.value.take().unwrap(), e)) + } +} + +impl<'a, V: kv::KVPermissions<'a>> kv::KVPermissions<'a> for VirtualKVPermissions<'a, V> { + fn set_client(&self, client: &'a dyn kv::KVClient) { + self.client.set(client); + } + + fn get( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + permissions: StoragePermissions, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + > { + if self.operation.is_some() { + return Err((key, value, ErrorCode::BUSY)); + } + + self.operation.set(Operation::Get); + self.valid_ids.set(permissions); + self.key.replace(key); + self.value.replace(value); + + self.mux_kv + .do_next_op(false) + .map_err(|e| (self.key.take().unwrap(), self.value.take().unwrap(), e)) + } + + fn set( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + permissions: StoragePermissions, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + > { + self.insert(key, value, permissions, Operation::Set) + } + + fn add( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + permissions: StoragePermissions, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + > { + self.insert(key, value, permissions, Operation::Add) + } + + fn update( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + permissions: StoragePermissions, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + > { + self.insert(key, value, permissions, Operation::Update) + } + + fn delete( + &self, + key: SubSliceMut<'static, u8>, + permissions: StoragePermissions, + ) -> Result<(), (SubSliceMut<'static, u8>, ErrorCode)> { + if self.operation.is_some() { + return Err((key, ErrorCode::BUSY)); + } + + self.operation.set(Operation::Delete); + self.valid_ids.set(permissions); + self.key.replace(key); + + self.mux_kv + .do_next_op(false) + .map_err(|e| (self.key.take().unwrap(), e)) + } + + fn header_size(&self) -> usize { + self.mux_kv.kv.header_size() + } +} + +pub struct MuxKVPermissions<'a, V: kv::KVPermissions<'a>> { + kv: &'a V, + users: List<'a, VirtualKVPermissions<'a, V>>, + inflight: OptionalCell<&'a VirtualKVPermissions<'a, V>>, +} + +impl<'a, V: kv::KVPermissions<'a>> MuxKVPermissions<'a, V> { + pub fn new(kv: &'a V) -> MuxKVPermissions<'a, V> { + Self { + kv, + inflight: OptionalCell::empty(), + users: List::new(), + } + } + + fn do_next_op(&self, async_op: bool) -> Result<(), ErrorCode> { + // Find a virtual device which has pending work. + let mnode = self.users.iter().find(|node| node.operation.is_some()); + + mnode.map_or(Ok(()), |node| { + node.operation.map_or(Ok(()), |op| { + node.key.take().map_or(Ok(()), |key| match op { + Operation::Get => node.value.take().map_or(Ok(()), |value| { + node.valid_ids.map_or(Ok(()), |perms| { + match self.kv.get(key, value, perms) { + Ok(()) => { + self.inflight.set(node); + Ok(()) + } + Err((key, value, e)) => { + node.operation.clear(); + if async_op { + node.client.map(move |cb| { + cb.get_complete(Err(e), key, value); + }); + Ok(()) + } else { + node.key.replace(key); + node.value.replace(value); + Err(e) + } + } + } + }) + }), + Operation::Set => node.value.take().map_or(Ok(()), |value| { + node.valid_ids.map_or(Ok(()), |perms| { + match self.kv.set(key, value, perms) { + Ok(()) => { + self.inflight.set(node); + Ok(()) + } + Err((key, value, e)) => { + node.operation.clear(); + if async_op { + node.client.map(move |cb| { + cb.set_complete(Err(e), key, value); + }); + Ok(()) + } else { + node.key.replace(key); + node.value.replace(value); + Err(e) + } + } + } + }) + }), + Operation::Add => node.value.take().map_or(Ok(()), |value| { + node.valid_ids.map_or(Ok(()), |perms| { + match self.kv.add(key, value, perms) { + Ok(()) => { + self.inflight.set(node); + Ok(()) + } + Err((key, value, e)) => { + node.operation.clear(); + if async_op { + node.client.map(move |cb| { + cb.add_complete(Err(e), key, value); + }); + Ok(()) + } else { + node.key.replace(key); + node.value.replace(value); + Err(e) + } + } + } + }) + }), + Operation::Update => node.value.take().map_or(Ok(()), |value| { + node.valid_ids.map_or(Ok(()), |perms| { + match self.kv.update(key, value, perms) { + Ok(()) => { + self.inflight.set(node); + Ok(()) + } + Err((key, value, e)) => { + node.operation.clear(); + if async_op { + node.client.map(move |cb| { + cb.update_complete(Err(e), key, value); + }); + Ok(()) + } else { + node.key.replace(key); + node.value.replace(value); + Err(e) + } + } + } + }) + }), + Operation::Delete => { + node.valid_ids + .map_or(Ok(()), |perms| match self.kv.delete(key, perms) { + Ok(()) => { + self.inflight.set(node); + Ok(()) + } + Err((key, e)) => { + node.operation.clear(); + if async_op { + node.client.map(move |cb| { + cb.delete_complete(Err(e), key); + }); + Ok(()) + } else { + node.key.replace(key); + Err(e) + } + } + }) + } + }) + }) + }) + } +} + +impl<'a, V: kv::KVPermissions<'a>> kv::KVClient for MuxKVPermissions<'a, V> { + fn get_complete( + &self, + result: Result<(), ErrorCode>, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ) { + self.inflight.take().map(|node| { + node.operation.clear(); + node.client.map(move |cb| { + cb.get_complete(result, key, value); + }); + }); + + let _ = self.do_next_op(true); + } + + fn set_complete( + &self, + result: Result<(), ErrorCode>, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ) { + self.inflight.take().map(|node| { + node.operation.clear(); + node.client.map(move |cb| { + cb.set_complete(result, key, value); + }); + }); + + let _ = self.do_next_op(true); + } + + fn add_complete( + &self, + result: Result<(), ErrorCode>, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ) { + self.inflight.take().map(|node| { + node.operation.clear(); + node.client.map(move |cb| { + cb.add_complete(result, key, value); + }); + }); + + let _ = self.do_next_op(true); + } + + fn update_complete( + &self, + result: Result<(), ErrorCode>, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ) { + self.inflight.take().map(|node| { + node.operation.clear(); + node.client.map(move |cb| { + cb.update_complete(result, key, value); + }); + }); + + let _ = self.do_next_op(true); + } + + fn delete_complete(&self, result: Result<(), ErrorCode>, key: SubSliceMut<'static, u8>) { + self.inflight.take().map(|node| { + node.operation.clear(); + node.client.map(move |cb| { + cb.delete_complete(result, key); + }); + }); + + let _ = self.do_next_op(true); + } +} diff --git a/capsules/src/alarm.rs b/capsules/src/alarm.rs deleted file mode 100644 index 583db9e540..0000000000 --- a/capsules/src/alarm.rs +++ /dev/null @@ -1,285 +0,0 @@ -//! Tock syscall driver capsule for Alarms, which issue callbacks when -//! a point in time has been reached. - -use core::cell::Cell; - -use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; -use kernel::hil::time::{self, Alarm, Frequency, Ticks, Ticks32}; -use kernel::syscall::{CommandReturn, SyscallDriver}; -use kernel::{ErrorCode, ProcessId}; - -/// Syscall driver number. -use crate::driver; -pub const DRIVER_NUM: usize = driver::NUM::Alarm as usize; - -#[derive(Copy, Clone, Debug)] -enum Expiration { - Disabled, - Enabled { reference: u32, dt: u32 }, -} - -#[derive(Copy, Clone)] -pub struct AlarmData { - expiration: Expiration, -} - -const ALARM_CALLBACK_NUM: usize = 0; -const NUM_UPCALLS: usize = 1; - -impl Default for AlarmData { - fn default() -> AlarmData { - AlarmData { - expiration: Expiration::Disabled, - } - } -} - -pub struct AlarmDriver<'a, A: Alarm<'a>> { - alarm: &'a A, - num_armed: Cell, - app_alarms: Grant, AllowRoCount<0>, AllowRwCount<0>>, - next_alarm: Cell, -} - -impl<'a, A: Alarm<'a>> AlarmDriver<'a, A> { - pub const fn new( - alarm: &'a A, - grant: Grant, AllowRoCount<0>, AllowRwCount<0>>, - ) -> AlarmDriver<'a, A> { - AlarmDriver { - alarm: alarm, - num_armed: Cell::new(0), - app_alarms: grant, - next_alarm: Cell::new(Expiration::Disabled), - } - } - - // This logic is tricky because it needs to handle the case when the - // underlying alarm is wider than 32 bits. - fn reset_active_alarm(&self) { - let mut earliest_alarm = Expiration::Disabled; - let mut earliest_end: A::Ticks = A::Ticks::from(0); - // Scale now down to a u32 since that is the width of the alarm; - // otherwise for larger widths (e.g., u64) now can be outside of - // the range of what an alarm can be set to. - let now = self.alarm.now(); - let now_lower_bits = A::Ticks::from(now.into_u32()); - // Find the first alarm to fire and store it in earliest_alarm, - // its counter value at earliest_end. In the case that there - // are multiple alarms in the past, just store one of them - // and resolve ordering later, when we fire. - for alarm in self.app_alarms.iter() { - alarm.enter(|alarm, _upcalls| match alarm.expiration { - Expiration::Enabled { reference, dt } => { - // Do this because `reference` shadowed below - let current_reference = reference; - let current_reference_ticks = A::Ticks::from(current_reference); - let current_dt = dt; - let current_dt_ticks = A::Ticks::from(current_dt); - let current_end_ticks = current_reference_ticks.wrapping_add(current_dt_ticks); - - earliest_alarm = match earliest_alarm { - Expiration::Disabled => { - earliest_end = current_end_ticks; - alarm.expiration - } - Expiration::Enabled { reference, dt } => { - // There are two cases when current might be - // an earlier alarm. The first is if it - // fires inside the interval (reference, - // reference+dt) of the existing earliest. - // The second is if now is not within the - // interval: this means that it has - // passed. It could be the earliest has passed - // too, but at this point we don't need to track - // which is earlier: the key point is that - // the alarm must fire immediately, and then when - // we handle the alarm callback the userspace - // callbacks will all be pushed onto processes. - // Because there is at most a single callback per - // process and they must go through the scheduler - // we don't care about the order in which we push - // their callbacks, as their order of execution is - // determined by the scheduler not push order. -pal - let temp_earliest_reference = A::Ticks::from(reference); - let temp_earliest_dt = A::Ticks::from(dt); - let temp_earliest_end = - temp_earliest_reference.wrapping_add(temp_earliest_dt); - - if current_end_ticks - .within_range(temp_earliest_reference, temp_earliest_end) - { - earliest_end = current_end_ticks; - alarm.expiration - } else if !now_lower_bits - .within_range(temp_earliest_reference, temp_earliest_end) - { - earliest_end = temp_earliest_end; - alarm.expiration - } else { - earliest_alarm - } - } - } - } - Expiration::Disabled => {} - }); - } - self.next_alarm.set(earliest_alarm); - match earliest_alarm { - Expiration::Disabled => { - let _ = self.alarm.disarm(); - } - Expiration::Enabled { reference, dt } => { - // This logic handles when the underlying Alarm is wider than - // 32 bits; it sets the reference to include the high bits of now - let mut high_bits = now.wrapping_sub(now_lower_bits); - // If lower bits have wrapped around from reference, this means the - // reference's high bits are actually one less; if we don't subtract - // one then the alarm will incorrectly be set 1<<32 higher than it should. - // This uses the invariant that reference <= now. - if now_lower_bits.into_u32() < reference { - // Build 1<<32 in a way that just overflows to 0 if we are 32 bits - let bit33 = A::Ticks::from(0xffffffff).wrapping_add(A::Ticks::from(0x1)); - high_bits = high_bits.wrapping_sub(bit33); - } - let real_reference = high_bits.wrapping_add(A::Ticks::from(reference)); - self.alarm.set_alarm(real_reference, A::Ticks::from(dt)); - } - } - } -} - -impl<'a, A: Alarm<'a>> SyscallDriver for AlarmDriver<'a, A> { - /// Setup and read the alarm. - /// - /// ### `command_num` - /// - /// - `0`: Driver check. - /// - `1`: Return the clock frequency in Hz. - /// - `2`: Read the the current clock value - /// - `3`: Stop the alarm if it is outstanding - /// - `4`: Set an alarm to fire at a given clock value `time`. - /// - `5`: Set an alarm to fire at a given clock value `time` relative to `now` (EXPERIMENTAL). - fn command( - &self, - cmd_type: usize, - data: usize, - data2: usize, - caller_id: ProcessId, - ) -> CommandReturn { - // Returns the error code to return to the user and whether we need to - // reset which is the next active alarm. We _don't_ reset if - // - we're disabling the underlying alarm anyway, - // - the underlying alarm is currently disabled and we're enabling the first alarm, or - // - on an error (i.e. no change to the alarms). - self.app_alarms - .enter(caller_id, |td, _upcalls| { - // helper function to rearm alarm - let mut rearm = |reference: usize, dt: usize| { - if let Expiration::Disabled = td.expiration { - self.num_armed.set(self.num_armed.get() + 1); - } - td.expiration = Expiration::Enabled { - reference: reference as u32, - dt: dt as u32, - }; - ( - CommandReturn::success_u32(reference.wrapping_add(dt) as u32), - true, - ) - }; - let now = self.alarm.now(); - match cmd_type { - 0 /* check if present */ => (CommandReturn::success(), false), - 1 /* Get clock frequency */ => { - let freq = ::frequency(); - (CommandReturn::success_u32(freq), false) - }, - 2 /* capture time */ => { - (CommandReturn::success_u32(now.into_u32()), false) - }, - 3 /* Stop */ => { - match td.expiration { - Expiration::Disabled => { - // Request to stop when already stopped - (CommandReturn::failure(ErrorCode::ALREADY), false) - }, - _ => { - td.expiration = Expiration::Disabled; - let new_num_armed = self.num_armed.get() - 1; - self.num_armed.set(new_num_armed); - (CommandReturn::success(), true) - } - } - }, - 4 /* Deprecated in 2.0, used to be: set absolute expiration */ => { - (CommandReturn::failure(ErrorCode::NOSUPPORT), false) - }, - 5 /* Set relative expiration */ => { - let reference = now.into_u32() as usize; - let dt = data; - // if previously unarmed, but now will become armed - rearm(reference, dt) - }, - 6 /* Set absolute expiration with reference point */ => { - let reference = data; - let dt = data2; - rearm(reference, dt) - } - _ => (CommandReturn::failure(ErrorCode::NOSUPPORT), false) - } - }) - .map_or_else( - |err| CommandReturn::failure(err.into()), - |(result, reset)| { - if reset { - self.reset_active_alarm(); - } - result - }, - ) - } - - fn allocate_grant(&self, appid: ProcessId) -> Result<(), kernel::process::Error> { - self.app_alarms.enter(appid, |_, _| {}) - } -} - -impl<'a, A: Alarm<'a>> time::AlarmClient for AlarmDriver<'a, A> { - fn alarm(&self) { - let now: Ticks32 = Ticks32::from(self.alarm.now().into_u32()); - self.app_alarms.each(|_processid, alarm, upcalls| { - if let Expiration::Enabled { reference, dt } = alarm.expiration { - // Now is not within reference, reference + ticks; this timer - // as passed (since reference must be in the past) - if !now.within_range( - Ticks32::from(reference), - Ticks32::from(reference.wrapping_add(dt)), - ) { - alarm.expiration = Expiration::Disabled; - self.num_armed.set(self.num_armed.get() - 1); - upcalls - .schedule_upcall( - ALARM_CALLBACK_NUM, - ( - now.into_u32() as usize, - reference.wrapping_add(dt) as usize, - 0, - ), - ) - .ok(); - } - } - }); - - // If there are no armed alarms left, skip checking and just disable. - // Otherwise, check all the alarms and find the next one, rescheduling - // the underlying alarm. - if self.num_armed.get() == 0 { - let _ = self.alarm.disarm(); - } else { - self.reset_active_alarm(); - } - } -} diff --git a/capsules/src/net/sixlowpan/mod.rs b/capsules/src/net/sixlowpan/mod.rs deleted file mode 100644 index 12594a1c0f..0000000000 --- a/capsules/src/net/sixlowpan/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod sixlowpan_compression; -pub mod sixlowpan_state; diff --git a/capsules/src/net/thread/mod.rs b/capsules/src/net/thread/mod.rs deleted file mode 100644 index f0c01fab94..0000000000 --- a/capsules/src/net/thread/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod tlv; diff --git a/capsules/src/nrf51822_serialization.rs b/capsules/src/nrf51822_serialization.rs deleted file mode 100644 index 8226cea4e1..0000000000 --- a/capsules/src/nrf51822_serialization.rs +++ /dev/null @@ -1,284 +0,0 @@ -//! Provides userspace with the UART API that the nRF51822 serialization library -//! requires. -//! -//! This capsule handles interfacing with the UART driver, and includes some -//! nuances that keep the Nordic BLE serialization library happy. -//! -//! Usage -//! ----- -//! -//! ```rust -//! # use kernel::{hil, static_init}; -//! # use capsules::nrf51822_serialization; -//! # use capsules::nrf51822_serialization::Nrf51822Serialization; -//! -//! let nrf_serialization = static_init!( -//! Nrf51822Serialization, -//! Nrf51822Serialization::new(&usart::USART3, -//! &mut nrf51822_serialization::WRITE_BUF, -//! &mut nrf51822_serialization::READ_BUF)); -//! hil::uart::UART::set_client(&usart::USART3, nrf_serialization); -//! ``` - -use core::cmp; - -use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; -use kernel::hil; -use kernel::hil::uart; -use kernel::processbuffer::{ReadableProcessBuffer, WriteableProcessBuffer}; -use kernel::syscall::{CommandReturn, SyscallDriver}; -use kernel::utilities::cells::{OptionalCell, TakeCell}; -use kernel::{ErrorCode, ProcessId}; - -/// Syscall driver number. -use crate::driver; -pub const DRIVER_NUM: usize = driver::NUM::Nrf51822Serialization as usize; - -/// Ids for read-only allow buffers -mod ro_allow { - pub const TX: usize = 0; - /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; -} - -/// Ids for read-write allow buffers -mod rw_allow { - pub const RX: usize = 0; - /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; -} - -#[derive(Default)] -pub struct App; - -// Local buffer for passing data between applications and the underlying -// transport hardware. -pub static mut WRITE_BUF: [u8; 600] = [0; 600]; -pub static mut READ_BUF: [u8; 600] = [0; 600]; - -// We need two resources: a UART HW driver and driver state for each -// application. -pub struct Nrf51822Serialization<'a> { - uart: &'a dyn uart::UartAdvanced<'a>, - reset_pin: &'a dyn hil::gpio::Pin, - apps: Grant< - App, - UpcallCount<1>, - AllowRoCount<{ ro_allow::COUNT }>, - AllowRwCount<{ rw_allow::COUNT }>, - >, - active_app: OptionalCell, - tx_buffer: TakeCell<'static, [u8]>, - rx_buffer: TakeCell<'static, [u8]>, -} - -impl<'a> Nrf51822Serialization<'a> { - pub fn new( - uart: &'a dyn uart::UartAdvanced<'a>, - grant: Grant< - App, - UpcallCount<1>, - AllowRoCount<{ ro_allow::COUNT }>, - AllowRwCount<{ rw_allow::COUNT }>, - >, - reset_pin: &'a dyn hil::gpio::Pin, - tx_buffer: &'static mut [u8], - rx_buffer: &'static mut [u8], - ) -> Nrf51822Serialization<'a> { - Nrf51822Serialization { - uart: uart, - reset_pin: reset_pin, - apps: grant, - active_app: OptionalCell::empty(), - tx_buffer: TakeCell::new(tx_buffer), - rx_buffer: TakeCell::new(rx_buffer), - } - } - - pub fn initialize(&self) { - let _ = self.uart.configure(uart::Parameters { - baud_rate: 250000, - width: uart::Width::Eight, - stop_bits: uart::StopBits::One, - parity: uart::Parity::Even, - hw_flow_control: true, - }); - } - - pub fn reset(&self) { - self.reset_pin.make_output(); - self.reset_pin.clear(); - // minimum hold time is 200ns, ~20ns per instruction, so overshoot a bit - for _ in 0..10 { - self.reset_pin.clear(); - } - self.reset_pin.set(); - } -} - -impl SyscallDriver for Nrf51822Serialization<'_> { - /// Pass application space memory to this driver. - /// - /// This also sets which app is currently using this driver. Only one app - /// can control the nRF51 serialization driver. - /// - /// ### `allow_num` - /// - /// - `0`: Provide a RX buffer. - - /// Pass application space memory to this driver. - /// - /// This also sets which app is currently using this driver. Only one app - /// can control the nRF51 serialization driver. - /// - /// ### `allow_num` - /// - /// - `0`: Provide a TX buffer. - - // Register a callback to the Nrf51822Serialization driver. - // - // The callback will be called when a TX finishes and when - // RX data is available. - // - // ### `subscribe_num` - // - // - `0`: Set callback. - - /// Issue a command to the Nrf51822Serialization driver. - /// - /// ### `command_type` - /// - /// - `0`: Driver check. - /// - `1`: Send the allowed buffer to the nRF. - /// - `2`: Received from the nRF into the allowed buffer. - /// - `3`: Reset the nRF51822. - fn command( - &self, - command_type: usize, - arg1: usize, - _: usize, - appid: ProcessId, - ) -> CommandReturn { - match command_type { - 0 /* check if present */ => CommandReturn::success(), - - // Send a buffer to the nRF51822 over UART. - 1 => { - self.apps.enter(appid, |_, kernel_data| { - kernel_data.get_readonly_processbuffer(ro_allow::TX).and_then(|tx| - tx.enter(|slice| { - let write_len = slice.len(); - self.tx_buffer.take().map_or(CommandReturn::failure(ErrorCode::FAIL), |buffer| { - for (i, c) in slice.iter().enumerate() { - buffer[i] = c.get(); - } - // Set this as the active app for the transmit callback - self.active_app.set(appid); - let _ = self.uart.transmit_buffer(buffer, write_len); - CommandReturn::success() - }) - })).unwrap_or(CommandReturn::failure(ErrorCode::FAIL)) - }).unwrap_or(CommandReturn::failure(ErrorCode::FAIL)) - } - // Receive from the nRF51822 - 2 => { - self.rx_buffer.take().map_or(CommandReturn::failure(ErrorCode::RESERVE), |buffer| { - let len = arg1; - if len > buffer.len() { - CommandReturn::failure(ErrorCode::SIZE) - } else { - // Set this as the active app for the receive callback - self.active_app.set(appid); - let _ = self.uart.receive_automatic(buffer, len, 250); - CommandReturn::success_u32(len as u32) - } - }) - } - - // Initialize the nRF51822 by resetting it. - 3 => { - self.reset(); - CommandReturn::success() - } - - _ => CommandReturn::failure(ErrorCode::NOSUPPORT), - } - } - - fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { - self.apps.enter(processid, |_, _| {}) - } -} - -// Callbacks from the underlying UART driver. -impl uart::TransmitClient for Nrf51822Serialization<'_> { - // Called when the UART TX has finished. - fn transmitted_buffer( - &self, - buffer: &'static mut [u8], - _tx_len: usize, - _rcode: Result<(), ErrorCode>, - ) { - self.tx_buffer.replace(buffer); - - self.active_app.map(|appid| { - let _ = self.apps.enter(*appid, |_app, kernel_data| { - // Call the callback after TX has finished - kernel_data.schedule_upcall(0, (1, 0, 0)).ok(); - }); - }); - } - - fn transmitted_word(&self, _rcode: Result<(), ErrorCode>) {} -} - -impl uart::ReceiveClient for Nrf51822Serialization<'_> { - // Called when a buffer is received on the UART. - fn received_buffer( - &self, - buffer: &'static mut [u8], - rx_len: usize, - _rcode: Result<(), ErrorCode>, - _error: uart::Error, - ) { - self.rx_buffer.replace(buffer); - - self.active_app.map(|appid| { - let _ = self.apps.enter(*appid, |_, kernel_data| { - let len = kernel_data - .get_readwrite_processbuffer(rw_allow::RX) - .and_then(|rx| { - rx.mut_enter(|rb| { - // Figure out length to copy. - let max_len = cmp::min(rx_len, rb.len()); - - // Copy over data to app buffer. - self.rx_buffer.map_or(0, |buffer| { - for idx in 0..max_len { - rb[idx].set(buffer[idx]); - } - max_len - }) - }) - }) - .unwrap_or(0); - - // Notify the serialization library in userspace about the - // received buffer. - // - // Note: This indicates how many bytes were received by - // hardware, regardless of how much space (if any) was - // available in the buffer provided by the app. - kernel_data.schedule_upcall(0, (4, rx_len, len)).ok(); - }); - }); - - // Restart the UART receive. - self.rx_buffer.take().map(|buffer| { - let len = buffer.len(); - let _ = self.uart.receive_automatic(buffer, len, 250); - }); - } - - fn received_word(&self, _word: u32, _rcode: Result<(), ErrorCode>, _err: uart::Error) {} -} diff --git a/capsules/src/screen.rs b/capsules/src/screen.rs deleted file mode 100644 index c38758580c..0000000000 --- a/capsules/src/screen.rs +++ /dev/null @@ -1,631 +0,0 @@ -//! Provides userspace with access to the screen. -//! -//! Usage -//! ----- -//! -//! You need a screen that provides the `hil::screen::Screen` trait. -//! -//! ```rust -//! let screen = -//! components::screen::ScreenComponent::new(board_kernel, tft).finalize(); -//! ``` - -use core::cell::Cell; -use core::convert::From; - -use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount}; -use kernel::hil; -use kernel::hil::screen::{ScreenPixelFormat, ScreenRotation}; -use kernel::processbuffer::ReadableProcessBuffer; -use kernel::syscall::{CommandReturn, SyscallDriver}; -use kernel::utilities::cells::{OptionalCell, TakeCell}; -use kernel::{ErrorCode, ProcessId}; - -/// Syscall driver number. -use crate::driver; -pub const DRIVER_NUM: usize = driver::NUM::Screen as usize; - -/// Ids for read-only allow buffers -mod ro_allow { - pub const SHARED: usize = 0; - /// The number of allow buffers the kernel stores for this grant - pub const COUNT: usize = 1; -} - -fn screen_rotation_from(screen_rotation: usize) -> Option { - match screen_rotation { - 0 => Some(ScreenRotation::Normal), - 1 => Some(ScreenRotation::Rotated90), - 2 => Some(ScreenRotation::Rotated180), - 3 => Some(ScreenRotation::Rotated270), - _ => None, - } -} - -fn screen_pixel_format_from(screen_pixel_format: usize) -> Option { - match screen_pixel_format { - 0 => Some(ScreenPixelFormat::Mono), - 1 => Some(ScreenPixelFormat::RGB_233), - 2 => Some(ScreenPixelFormat::RGB_565), - 3 => Some(ScreenPixelFormat::RGB_888), - 4 => Some(ScreenPixelFormat::ARGB_8888), - _ => None, - } -} - -#[derive(Clone, Copy, PartialEq)] -enum ScreenCommand { - Nop, - SetBrightness(usize), - InvertOn, - InvertOff, - GetSupportedResolutionModes, - GetSupportedResolution(usize), - GetSupportedPixelFormats, - GetSupportedPixelFormat(usize), - GetRotation, - SetRotation(ScreenRotation), - GetResolution, - SetResolution { - width: usize, - height: usize, - }, - GetPixelFormat, - SetPixelFormat(ScreenPixelFormat), - SetWriteFrame { - x: usize, - y: usize, - width: usize, - height: usize, - }, - Write(usize), - Fill, -} - -fn pixels_in_bytes(pixels: usize, bits_per_pixel: usize) -> usize { - let bytes = pixels * bits_per_pixel / 8; - if pixels * bits_per_pixel % 8 != 0 { - bytes + 1 - } else { - bytes - } -} - -pub struct App { - pending_command: bool, - write_position: usize, - write_len: usize, - command: ScreenCommand, - width: usize, - height: usize, -} - -impl Default for App { - fn default() -> App { - App { - pending_command: false, - command: ScreenCommand::Nop, - width: 0, - height: 0, - write_len: 0, - write_position: 0, - } - } -} - -pub struct Screen<'a> { - screen: &'a dyn hil::screen::Screen, - screen_setup: Option<&'a dyn hil::screen::ScreenSetup>, - apps: Grant, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>, - screen_ready: Cell, - current_process: OptionalCell, - pixel_format: Cell, - buffer: TakeCell<'static, [u8]>, -} - -impl<'a> Screen<'a> { - pub fn new( - screen: &'a dyn hil::screen::Screen, - screen_setup: Option<&'a dyn hil::screen::ScreenSetup>, - buffer: &'static mut [u8], - grant: Grant, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>, - ) -> Screen<'a> { - Screen { - screen: screen, - screen_setup: screen_setup, - apps: grant, - current_process: OptionalCell::empty(), - screen_ready: Cell::new(false), - pixel_format: Cell::new(screen.get_pixel_format()), - buffer: TakeCell::new(buffer), - } - } - - // Check to see if we are doing something. If not, - // go ahead and do this command. If so, this is queued - // and will be run when the pending command completes. - fn enqueue_command(&self, command: ScreenCommand, process_id: ProcessId) -> CommandReturn { - match self - .apps - .enter(process_id, |app, _| { - if app.pending_command == true { - CommandReturn::failure(ErrorCode::BUSY) - } else { - app.pending_command = true; - app.command = command; - app.write_position = 0; - CommandReturn::success() - } - }) - .map_err(ErrorCode::from) - { - Err(e) => CommandReturn::failure(e), - Ok(r) => { - if self.screen_ready.get() && self.current_process.is_none() { - self.current_process.set(process_id); - let r = self.call_screen(command, process_id); - if r != Ok(()) { - self.current_process.clear(); - } - CommandReturn::from(r) - } else { - r - } - } - } - } - - fn is_len_multiple_color_depth(&self, len: usize) -> bool { - let depth = pixels_in_bytes(1, self.screen.get_pixel_format().get_bits_per_pixel()); - (len % depth) == 0 - } - - fn call_screen(&self, command: ScreenCommand, process_id: ProcessId) -> Result<(), ErrorCode> { - match command { - ScreenCommand::SetBrightness(brighness) => self.screen.set_brightness(brighness), - ScreenCommand::InvertOn => self.screen.invert_on(), - ScreenCommand::InvertOff => self.screen.invert_off(), - ScreenCommand::SetRotation(rotation) => { - if let Some(screen) = self.screen_setup { - screen.set_rotation(rotation) - } else { - Err(ErrorCode::NOSUPPORT) - } - } - ScreenCommand::GetRotation => { - let rotation = self.screen.get_rotation(); - self.run_next_command( - kernel::errorcode::into_statuscode(Ok(())), - rotation as usize, - 0, - ); - Ok(()) - } - ScreenCommand::SetResolution { width, height } => { - if let Some(screen) = self.screen_setup { - screen.set_resolution((width, height)) - } else { - Err(ErrorCode::NOSUPPORT) - } - } - ScreenCommand::GetResolution => { - let (width, height) = self.screen.get_resolution(); - self.run_next_command(kernel::errorcode::into_statuscode(Ok(())), width, height); - Ok(()) - } - ScreenCommand::SetPixelFormat(pixel_format) => { - if let Some(screen) = self.screen_setup { - screen.set_pixel_format(pixel_format) - } else { - Err(ErrorCode::NOSUPPORT) - } - } - ScreenCommand::GetPixelFormat => { - let pixel_format = self.screen.get_pixel_format(); - self.run_next_command( - kernel::errorcode::into_statuscode(Ok(())), - pixel_format as usize, - 0, - ); - Ok(()) - } - ScreenCommand::GetSupportedResolutionModes => { - if let Some(screen) = self.screen_setup { - let resolution_modes = screen.get_num_supported_resolutions(); - self.run_next_command( - kernel::errorcode::into_statuscode(Ok(())), - resolution_modes, - 0, - ); - Ok(()) - } else { - Err(ErrorCode::NOSUPPORT) - } - } - ScreenCommand::GetSupportedResolution(resolution_index) => { - if let Some(screen) = self.screen_setup { - if let Some((width, height)) = screen.get_supported_resolution(resolution_index) - { - self.run_next_command( - kernel::errorcode::into_statuscode(if width > 0 && height > 0 { - Ok(()) - } else { - Err(ErrorCode::INVAL) - }), - width, - height, - ); - Ok(()) - } else { - Err(ErrorCode::INVAL) - } - } else { - Err(ErrorCode::NOSUPPORT) - } - } - ScreenCommand::GetSupportedPixelFormats => { - if let Some(screen) = self.screen_setup { - let color_modes = screen.get_num_supported_pixel_formats(); - self.run_next_command( - kernel::errorcode::into_statuscode(Ok(())), - color_modes, - 0, - ); - Ok(()) - } else { - Err(ErrorCode::NOSUPPORT) - } - } - ScreenCommand::GetSupportedPixelFormat(pixel_format_index) => { - if let Some(screen) = self.screen_setup { - if let Some(pixel_format) = - screen.get_supported_pixel_format(pixel_format_index) - { - self.run_next_command( - kernel::errorcode::into_statuscode(Ok(())), - pixel_format as usize, - 0, - ); - Ok(()) - } else { - Err(ErrorCode::INVAL) - } - } else { - Err(ErrorCode::NOSUPPORT) - } - } - ScreenCommand::Fill => match self - .apps - .enter(process_id, |app, kernel_data| { - let len = kernel_data - .get_readonly_processbuffer(ro_allow::SHARED) - .map_or(0, |shared| shared.len()); - // Ensure we have a buffer that is the correct size - if len == 0 { - Err(ErrorCode::NOMEM) - } else if !self.is_len_multiple_color_depth(len) { - Err(ErrorCode::INVAL) - } else { - app.write_position = 0; - app.write_len = pixels_in_bytes( - app.width * app.height, - self.pixel_format.get().get_bits_per_pixel(), - ); - Ok(()) - } - }) - .unwrap_or_else(|err| err.into()) - { - Err(e) => Err(e), - Ok(()) => self.buffer.take().map_or(Err(ErrorCode::NOMEM), |buffer| { - let len = self.fill_next_buffer_for_write(buffer); - if len > 0 { - self.screen.write(buffer, len) - } else { - self.buffer.replace(buffer); - self.run_next_command(kernel::errorcode::into_statuscode(Ok(())), 0, 0); - Ok(()) - } - }), - }, - - ScreenCommand::Write(data_len) => match self - .apps - .enter(process_id, |app, kernel_data| { - let len = kernel_data - .get_readonly_processbuffer(ro_allow::SHARED) - .map_or(0, |shared| shared.len()) - .min(data_len); - // Ensure we have a buffer that is the correct size - if len == 0 { - Err(ErrorCode::NOMEM) - } else if !self.is_len_multiple_color_depth(len) { - Err(ErrorCode::INVAL) - } else { - app.write_position = 0; - app.write_len = len; - Ok(()) - } - }) - .unwrap_or_else(|err| err.into()) - { - Ok(()) => self.buffer.take().map_or(Err(ErrorCode::FAIL), |buffer| { - let len = self.fill_next_buffer_for_write(buffer); - if len > 0 { - self.screen.write(buffer, len) - } else { - self.buffer.replace(buffer); - self.run_next_command(kernel::errorcode::into_statuscode(Ok(())), 0, 0); - Ok(()) - } - }), - Err(e) => Err(e), - }, - ScreenCommand::SetWriteFrame { - x, - y, - width, - height, - } => self - .apps - .enter(process_id, |app, _| { - app.write_position = 0; - app.width = width; - app.height = height; - - self.screen.set_write_frame(x, y, width, height) - }) - .unwrap_or_else(|err| err.into()), - _ => Err(ErrorCode::NOSUPPORT), - } - } - - fn schedule_callback(&self, data1: usize, data2: usize, data3: usize) { - if !self.screen_ready.get() { - self.screen_ready.set(true); - } else { - self.current_process.take().map(|process_id| { - let _ = self.apps.enter(process_id, |app, upcalls| { - app.pending_command = false; - upcalls.schedule_upcall(0, (data1, data2, data3)).ok(); - }); - }); - } - } - - fn run_next_command(&self, data1: usize, data2: usize, data3: usize) { - self.schedule_callback(data1, data2, data3); - - let mut command = ScreenCommand::Nop; - - // Check if there are any pending events. - for app in self.apps.iter() { - let process_id = app.processid(); - let start_command = app.enter(|app, _| { - if app.pending_command { - app.pending_command = false; - command = app.command; - self.current_process.set(process_id); - true - } else { - false - } - }); - if start_command { - match self.call_screen(command, process_id) { - Err(err) => { - self.current_process.clear(); - self.schedule_callback(kernel::errorcode::into_statuscode(Err(err)), 0, 0); - } - Ok(()) => { - break; - } - } - } - } - } - - fn fill_next_buffer_for_write(&self, buffer: &mut [u8]) -> usize { - self.current_process.map_or_else( - || 0, - |process_id| { - self.apps - .enter(*process_id, |app, kernel_data| { - let position = app.write_position; - let mut len = app.write_len; - if position < len { - let buffer_size = buffer.len(); - let chunk_number = position / buffer_size; - let initial_pos = chunk_number * buffer_size; - let mut pos = initial_pos; - match app.command { - ScreenCommand::Write(_) => { - let res = kernel_data - .get_readonly_processbuffer(ro_allow::SHARED) - .and_then(|shared| { - shared.enter(|s| { - let mut chunks = s.chunks(buffer_size); - if let Some(chunk) = chunks.nth(chunk_number) { - for (i, byte) in chunk.iter().enumerate() { - if pos < len { - buffer[i] = byte.get(); - pos = pos + 1 - } else { - break; - } - } - app.write_len - initial_pos - } else { - // stop writing - 0 - } - }) - }) - .unwrap_or(0); - if res > 0 { - app.write_position = pos; - } - res - } - ScreenCommand::Fill => { - // TODO bytes per pixel - len = len - position; - let bytes_per_pixel = pixels_in_bytes( - 1, - self.pixel_format.get().get_bits_per_pixel(), - ); - let mut write_len = buffer_size / bytes_per_pixel; - if write_len > len { - write_len = len - }; - app.write_position = - app.write_position + write_len * bytes_per_pixel; - kernel_data - .get_readonly_processbuffer(ro_allow::SHARED) - .and_then(|shared| { - shared.enter(|data| { - let mut bytes = data.iter(); - // bytes per pixel - for i in 0..bytes_per_pixel { - if let Some(byte) = bytes.next() { - buffer[i] = byte.get(); - } - } - for i in 1..write_len { - // bytes per pixel - for j in 0..bytes_per_pixel { - buffer[bytes_per_pixel * i + j] = buffer[j] - } - } - write_len * bytes_per_pixel - }) - }) - .unwrap_or(0) - } - _ => 0, - } - } else { - 0 - } - }) - .unwrap_or_else(|_| 0) - }, - ) - } -} - -impl<'a> hil::screen::ScreenClient for Screen<'a> { - fn command_complete(&self, r: Result<(), ErrorCode>) { - self.run_next_command(kernel::errorcode::into_statuscode(r), 0, 0); - } - - fn write_complete(&self, buffer: &'static mut [u8], r: Result<(), ErrorCode>) { - let len = self.fill_next_buffer_for_write(buffer); - - if r == Ok(()) && len > 0 { - let _ = self.screen.write_continue(buffer, len); - } else { - self.buffer.replace(buffer); - self.run_next_command(kernel::errorcode::into_statuscode(r), 0, 0); - } - } - - fn screen_is_ready(&self) { - self.run_next_command(kernel::errorcode::into_statuscode(Ok(())), 0, 0); - } -} - -impl<'a> hil::screen::ScreenSetupClient for Screen<'a> { - fn command_complete(&self, r: Result<(), ErrorCode>) { - self.run_next_command(kernel::errorcode::into_statuscode(r), 0, 0); - } -} - -impl<'a> SyscallDriver for Screen<'a> { - fn command( - &self, - command_num: usize, - data1: usize, - data2: usize, - process_id: ProcessId, - ) -> CommandReturn { - match command_num { - 0 => - // This driver exists. - { - CommandReturn::success() - } - // Does it have the screen setup - 1 => CommandReturn::success_u32(self.screen_setup.is_some() as u32), - // Set Brightness - 3 => self.enqueue_command(ScreenCommand::SetBrightness(data1), process_id), - // Invert On - 4 => self.enqueue_command(ScreenCommand::InvertOn, process_id), - // Invert Off - 5 => self.enqueue_command(ScreenCommand::InvertOff, process_id), - - // Get Resolution Modes Number - 11 => self.enqueue_command(ScreenCommand::GetSupportedResolutionModes, process_id), - // Get Resolution Mode Width and Height - 12 => self.enqueue_command(ScreenCommand::GetSupportedResolution(data1), process_id), - - // Get Color Depth Modes Number - 13 => self.enqueue_command(ScreenCommand::GetSupportedPixelFormats, process_id), - // Get Color Depth Mode Bits per Pixel - 14 => self.enqueue_command(ScreenCommand::GetSupportedPixelFormat(data1), process_id), - - // Get Rotation - 21 => self.enqueue_command(ScreenCommand::GetRotation, process_id), - // Set Rotation - 22 => self.enqueue_command( - ScreenCommand::SetRotation( - screen_rotation_from(data1).unwrap_or(ScreenRotation::Normal), - ), - process_id, - ), - - // Get Resolution - 23 => self.enqueue_command(ScreenCommand::GetResolution, process_id), - // Set Resolution - 24 => self.enqueue_command( - ScreenCommand::SetResolution { - width: data1, - height: data2, - }, - process_id, - ), - - // Get Color Depth - 25 => self.enqueue_command(ScreenCommand::GetPixelFormat, process_id), - // Set Color Depth - 26 => { - if let Some(pixel_format) = screen_pixel_format_from(data1) { - self.enqueue_command(ScreenCommand::SetPixelFormat(pixel_format), process_id) - } else { - CommandReturn::failure(ErrorCode::INVAL) - } - } - - // Set Write Frame - 100 => self.enqueue_command( - ScreenCommand::SetWriteFrame { - x: (data1 >> 16) & 0xFFFF, - y: data1 & 0xFFFF, - width: (data2 >> 16) & 0xFFFF, - height: data2 & 0xFFFF, - }, - process_id, - ), - // Write - 200 => self.enqueue_command(ScreenCommand::Write(data1), process_id), - // Fill - 300 => self.enqueue_command(ScreenCommand::Fill, process_id), - - _ => CommandReturn::failure(ErrorCode::NOSUPPORT), - } - } - - fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> { - self.apps.enter(processid, |_, _| {}) - } -} diff --git a/capsules/src/symmetric_encryption/mod.rs b/capsules/src/symmetric_encryption/mod.rs deleted file mode 100644 index 5657404aa8..0000000000 --- a/capsules/src/symmetric_encryption/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod aes; diff --git a/capsules/src/test/mod.rs b/capsules/src/test/mod.rs deleted file mode 100644 index 792a8871fd..0000000000 --- a/capsules/src/test/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -pub mod aes; -pub mod aes_ccm; -pub mod alarm; -pub mod alarm_edge_cases; -pub mod crc; -pub mod double_grant_entry; -pub mod kv_system; -pub mod random_alarm; -pub mod random_timer; -pub mod rng; -pub mod udp; -pub mod virtual_rng; -pub mod virtual_uart; diff --git a/capsules/src/tickv.rs b/capsules/src/tickv.rs deleted file mode 100644 index 3f8e3d6447..0000000000 --- a/capsules/src/tickv.rs +++ /dev/null @@ -1,518 +0,0 @@ -//! Tock TicKV capsule. -//! -//! This capsule implements the TicKV library in Tock. This is done -//! using the TicKV library (libraries/tickv). -//! -//! This capsule interfaces with flash and exposes the Tock `hil::kv_system` -//! interface to others. -//! -//! +-----------------------+ -//! | | -//! | Capsule using K-V | -//! | | -//! +-----------------------+ -//! -//! hil::kv_store -//! -//! +-----------------------+ -//! | | -//! | K-V in Tock | -//! | | -//! +-----------------------+ -//! -//! hil::kv_system -//! -//! +-----------------------+ -//! | | -//! | TicKV (this file) | -//! | | -//! +-----------------------+ -//! -//! hil::flash - -use core::cell::Cell; -use kernel::hil::flash::{self, Flash}; -use kernel::hil::kv_system::{self, KVSystem}; -use kernel::utilities::cells::{OptionalCell, TakeCell}; -use kernel::ErrorCode; -use tickv::{self, AsyncTicKV}; - -#[derive(Clone, Copy, PartialEq)] -enum Operation { - None, - Init, - GetKey, - AppendKey, - InvalidateKey, - GarbageCollect, -} - -pub struct TickFSFlastCtrl<'a, F: Flash + 'static> { - flash: &'a F, - flash_read_buffer: TakeCell<'static, F::Page>, - region_offset: usize, -} - -impl<'a, F: Flash> TickFSFlastCtrl<'a, F> { - pub fn new( - flash: &'a F, - flash_read_buffer: &'static mut F::Page, - region_offset: usize, - ) -> TickFSFlastCtrl<'a, F> { - Self { - flash, - flash_read_buffer: TakeCell::new(flash_read_buffer), - region_offset, - } - } -} - -impl<'a, F: Flash> tickv::flash_controller::FlashController<64> for TickFSFlastCtrl<'a, F> { - fn read_region( - &self, - region_number: usize, - _offset: usize, - _buf: &mut [u8; 64], - ) -> Result<(), tickv::error_codes::ErrorCode> { - if self - .flash - .read_page( - self.region_offset + region_number, - self.flash_read_buffer.take().unwrap(), - ) - .is_err() - { - Err(tickv::error_codes::ErrorCode::ReadFail) - } else { - Err(tickv::error_codes::ErrorCode::ReadNotReady(region_number)) - } - } - - fn write(&self, address: usize, buf: &[u8]) -> Result<(), tickv::error_codes::ErrorCode> { - let data_buf = self.flash_read_buffer.take().unwrap(); - - for (i, d) in buf.iter().enumerate() { - data_buf.as_mut()[i + (address % 64)] = *d; - } - - if self - .flash - .write_page(self.region_offset + (address / 64), data_buf) - .is_err() - { - return Err(tickv::error_codes::ErrorCode::WriteFail); - } - - Err(tickv::error_codes::ErrorCode::WriteNotReady(address)) - } - - fn erase_region(&self, region_number: usize) -> Result<(), tickv::error_codes::ErrorCode> { - let _ = self.flash.erase_page(self.region_offset + region_number); - - Err(tickv::error_codes::ErrorCode::EraseNotReady(region_number)) - } -} - -pub type TicKVKeyType = [u8; 8]; - -pub struct TicKVStore<'a, F: Flash + 'static> { - tickv: AsyncTicKV<'a, TickFSFlastCtrl<'a, F>, 64>, - operation: Cell, - next_operation: Cell, - - value_buffer: Cell>, - key_buffer: TakeCell<'static, [u8; 8]>, - ret_buffer: TakeCell<'static, [u8]>, - - client: OptionalCell<&'a dyn kv_system::Client>, -} - -impl<'a, F: Flash> TicKVStore<'a, F> { - pub fn new( - flash: &'a F, - tickfs_read_buf: &'static mut [u8; 64], - flash_read_buffer: &'static mut F::Page, - region_offset: usize, - flash_size: usize, - ) -> TicKVStore<'a, F> { - let tickv = AsyncTicKV::, 64>::new( - TickFSFlastCtrl::new(flash, flash_read_buffer, region_offset), - tickfs_read_buf, - flash_size, - ); - - Self { - tickv, - operation: Cell::new(Operation::None), - next_operation: Cell::new(Operation::None), - value_buffer: Cell::new(None), - key_buffer: TakeCell::empty(), - ret_buffer: TakeCell::empty(), - client: OptionalCell::empty(), - } - } - - pub fn initalise(&self) { - let _ret = self.tickv.initalise(0x7bc9f7ff4f76f244); - self.operation.set(Operation::Init); - } - - fn complete_init(&self) { - self.operation.set(Operation::None); - match self.next_operation.get() { - Operation::None | Operation::Init => {} - Operation::AppendKey => { - match self.append_key( - self.key_buffer.take().unwrap(), - self.value_buffer.take().unwrap(), - ) { - Err((key, value, error)) => { - self.client.map(move |cb| { - cb.append_key_complete(error, key, value); - }); - } - _ => {} - } - } - Operation::GetKey => { - match self.get_value( - self.key_buffer.take().unwrap(), - self.ret_buffer.take().unwrap(), - ) { - Err((key, ret_buf, error)) => { - self.client.map(move |cb| { - cb.get_value_complete(error, key, ret_buf); - }); - } - _ => {} - } - } - Operation::InvalidateKey => { - match self.invalidate_key(self.key_buffer.take().unwrap()) { - Err((key, error)) => { - self.client.map(move |cb| { - cb.invalidate_key_complete(error, key); - }); - } - _ => {} - } - } - Operation::GarbageCollect => match self.garbage_collect() { - Err(error) => { - self.client.map(move |cb| { - cb.garbage_collect_complete(error); - }); - } - _ => {} - }, - } - self.next_operation.set(Operation::None); - } -} - -impl<'a, F: Flash> flash::Client for TicKVStore<'a, F> { - fn read_complete(&self, pagebuffer: &'static mut F::Page, _error: flash::Error) { - self.tickv.set_read_buffer(pagebuffer.as_mut()); - self.tickv - .tickv - .controller - .flash_read_buffer - .replace(pagebuffer); - let (ret, buf_buffer) = self.tickv.continue_operation(); - - buf_buffer.map(|buf| { - self.ret_buffer.replace(buf); - }); - - match self.operation.get() { - Operation::Init => match ret { - Ok(tickv::success_codes::SuccessCode::Complete) - | Ok(tickv::success_codes::SuccessCode::Written) => { - self.operation.set(Operation::None) - } - _ => {} - }, - Operation::GetKey => match ret { - Ok(tickv::success_codes::SuccessCode::Complete) - | Ok(tickv::success_codes::SuccessCode::Written) => { - self.operation.set(Operation::None); - self.client.map(|cb| { - cb.get_value_complete( - Ok(()), - self.key_buffer.take().unwrap(), - self.ret_buffer.take().unwrap(), - ); - }); - } - Err(tickv::error_codes::ErrorCode::EraseNotReady(_)) | Ok(_) => {} - _ => { - self.operation.set(Operation::None); - self.client.map(|cb| { - cb.get_value_complete( - Err(ErrorCode::FAIL), - self.key_buffer.take().unwrap(), - self.ret_buffer.take().unwrap(), - ); - }); - } - }, - Operation::AppendKey => match ret { - Ok(tickv::success_codes::SuccessCode::Complete) - | Ok(tickv::success_codes::SuccessCode::Written) => { - self.operation.set(Operation::None); - } - _ => {} - }, - Operation::InvalidateKey => match ret { - Ok(tickv::success_codes::SuccessCode::Complete) - | Ok(tickv::success_codes::SuccessCode::Written) => { - self.operation.set(Operation::None); - } - _ => {} - }, - Operation::GarbageCollect => match ret { - Ok(tickv::success_codes::SuccessCode::Complete) - | Ok(tickv::success_codes::SuccessCode::Written) => { - self.operation.set(Operation::None); - self.client.map(|cb| { - cb.garbage_collect_complete(Ok(())); - }); - } - _ => {} - }, - _ => unreachable!(), - } - } - - fn write_complete(&self, pagebuffer: &'static mut F::Page, _error: flash::Error) { - self.tickv - .tickv - .controller - .flash_read_buffer - .replace(pagebuffer); - - match self.operation.get() { - Operation::Init => { - self.complete_init(); - } - Operation::AppendKey => { - self.operation.set(Operation::None); - self.client.map(|cb| { - cb.append_key_complete( - Ok(()), - self.key_buffer.take().unwrap(), - self.tickv.get_stored_value_buffer().unwrap(), - ); - }); - } - Operation::InvalidateKey => { - self.operation.set(Operation::None); - self.client.map(|cb| { - cb.invalidate_key_complete(Ok(()), self.key_buffer.take().unwrap()); - }); - } - _ => unreachable!(), - } - } - - fn erase_complete(&self, _error: flash::Error) { - let (ret, buf_buffer) = self.tickv.continue_operation(); - - buf_buffer.map(|buf| { - self.ret_buffer.replace(buf); - }); - - match self.operation.get() { - Operation::Init => match ret { - Ok(tickv::success_codes::SuccessCode::Complete) - | Ok(tickv::success_codes::SuccessCode::Written) => { - self.complete_init(); - } - _ => {} - }, - Operation::GarbageCollect => match ret { - Ok(tickv::success_codes::SuccessCode::Complete) - | Ok(tickv::success_codes::SuccessCode::Written) => { - self.operation.set(Operation::None); - self.client.map(|cb| { - cb.garbage_collect_complete(Ok(())); - }); - } - _ => {} - }, - _ => unreachable!(), - } - } -} - -impl<'a, F: Flash> KVSystem<'a> for TicKVStore<'a, F> { - type K = TicKVKeyType; - - fn set_client(&self, client: &'a dyn kv_system::Client) { - self.client.set(client); - } - - fn generate_key( - &self, - _unhashed_key: &'static mut [u8], - _key_buf: &'static mut Self::K, - ) -> Result< - (), - ( - &'static mut [u8], - &'static mut Self::K, - Result<(), ErrorCode>, - ), - > { - unimplemented!() - } - - fn append_key( - &self, - key: &'static mut Self::K, - value: &'static [u8], - ) -> Result<(), (&'static mut Self::K, &'static [u8], Result<(), ErrorCode>)> { - match self.operation.get() { - Operation::None => { - self.operation.set(Operation::AppendKey); - - match self.tickv.append_key(u64::from_le_bytes(*key), value) { - Ok(_ret) => { - self.key_buffer.replace(key); - Ok(()) - } - Err(e) => match e { - tickv::error_codes::ErrorCode::ReadNotReady(_) - | tickv::error_codes::ErrorCode::WriteNotReady(_) => { - self.key_buffer.replace(key); - Ok(()) - } - _ => Err((key, value, Err(ErrorCode::FAIL))), - }, - } - } - Operation::Init => { - // The init process is still occuring. - // We can save this request and start it after init - self.next_operation.set(Operation::AppendKey); - self.key_buffer.replace(key); - self.value_buffer.replace(Some(value)); - Ok(()) - } - _ => { - // An operation is already in process. - Err((key, value, Err(ErrorCode::BUSY))) - } - } - } - - fn get_value( - &self, - key: &'static mut Self::K, - ret_buf: &'static mut [u8], - ) -> Result< - (), - ( - &'static mut Self::K, - &'static mut [u8], - Result<(), ErrorCode>, - ), - > { - match self.operation.get() { - Operation::None => { - self.operation.set(Operation::GetKey); - - match self.tickv.get_key(u64::from_le_bytes(*key), ret_buf) { - Ok(_ret) => { - self.key_buffer.replace(key); - Ok(()) - } - Err((buf, e)) => match e { - tickv::error_codes::ErrorCode::ReadNotReady(_) - | tickv::error_codes::ErrorCode::WriteNotReady(_) => { - self.key_buffer.replace(key); - Ok(()) - } - _ => Err((key, buf.unwrap(), Err(ErrorCode::FAIL))), - }, - } - } - Operation::Init => { - // The init process is still occuring. - // We can save this request and start it after init - self.next_operation.set(Operation::GetKey); - self.key_buffer.replace(key); - self.ret_buffer.replace(ret_buf); - Ok(()) - } - _ => { - // An operation is already in process. - Err((key, ret_buf, Err(ErrorCode::BUSY))) - } - } - } - - fn invalidate_key( - &self, - key: &'static mut Self::K, - ) -> Result<(), (&'static mut Self::K, Result<(), ErrorCode>)> { - match self.operation.get() { - Operation::None => { - self.operation.set(Operation::InvalidateKey); - - match self.tickv.invalidate_key(u64::from_le_bytes(*key)) { - Ok(_ret) => { - self.key_buffer.replace(key); - Ok(()) - } - Err(e) => match e { - tickv::error_codes::ErrorCode::ReadNotReady(_) - | tickv::error_codes::ErrorCode::WriteNotReady(_) => { - self.key_buffer.replace(key); - Ok(()) - } - _ => Err((key, Err(ErrorCode::FAIL))), - }, - } - } - Operation::Init => { - // The init process is still occuring. - // We can save this request and start it after init - self.next_operation.set(Operation::InvalidateKey); - self.key_buffer.replace(key); - Ok(()) - } - _ => { - // An operation is already in process. - Err((key, Err(ErrorCode::BUSY))) - } - } - } - - fn garbage_collect(&self) -> Result> { - match self.operation.get() { - Operation::None => { - self.operation.set(Operation::GarbageCollect); - - match self.tickv.garbage_collect() { - Ok(freed) => Ok(freed), - Err(e) => match e { - tickv::error_codes::ErrorCode::ReadNotReady(_) - | tickv::error_codes::ErrorCode::WriteNotReady(_) => Ok(0), - _ => Err(Err(ErrorCode::FAIL)), - }, - } - } - Operation::Init => { - // The init process is still occuring. - // We can save this request and start it after init - self.next_operation.set(Operation::GarbageCollect); - Ok(0) - } - _ => { - // An operation is already in process. - Err(Err(ErrorCode::BUSY)) - } - } - } -} diff --git a/capsules/src/usb/mod.rs b/capsules/src/usb/mod.rs deleted file mode 100644 index 6d5daa444b..0000000000 --- a/capsules/src/usb/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod cdc; -pub mod ctap; -pub mod descriptors; -pub mod usb_user; -pub mod usbc_client; -pub mod usbc_client_ctrl; diff --git a/capsules/src/virtual_digest.rs b/capsules/src/virtual_digest.rs deleted file mode 100644 index 32b3fd9bc8..0000000000 --- a/capsules/src/virtual_digest.rs +++ /dev/null @@ -1,491 +0,0 @@ -//! Virtualize the Digest interface to enable multiple users of an underlying -//! Digest hardware peripheral. - -use core::cell::Cell; - -use kernel::collections::list::{List, ListLink, ListNode}; -use kernel::hil::digest::{self, ClientData, ClientHash, ClientVerify, DigestData}; -use kernel::utilities::cells::{OptionalCell, TakeCell}; -use kernel::utilities::leasable_buffer::LeasableBuffer; -use kernel::ErrorCode; - -#[derive(Clone, Copy, PartialEq)] -pub enum Operation { - Sha256, - Sha384, - Sha512, -} - -#[derive(Clone, Copy, PartialEq)] -pub enum Mode { - None, - Hmac(Operation), - Sha(Operation), -} - -pub struct VirtualMuxDigest<'a, A: digest::Digest<'a, L>, const L: usize> { - mux: &'a MuxDigest<'a, A, L>, - next: ListLink<'a, VirtualMuxDigest<'a, A, L>>, - sha_client: OptionalCell<&'a dyn digest::Client<'a, L>>, - hmac_client: OptionalCell<&'a dyn digest::Client<'a, L>>, - key: TakeCell<'static, [u8]>, - data: TakeCell<'static, [u8]>, - data_len: Cell, - digest: TakeCell<'static, [u8; L]>, - verify: Cell, - mode: Cell, - ready: Cell, - id: u32, -} - -impl<'a, A: digest::Digest<'a, L>, const L: usize> ListNode<'a, VirtualMuxDigest<'a, A, L>> - for VirtualMuxDigest<'a, A, L> -{ - fn next(&self) -> &'a ListLink> { - &self.next - } -} - -impl<'a, A: digest::Digest<'a, L>, const L: usize> VirtualMuxDigest<'a, A, L> { - pub fn new( - mux_digest: &'a MuxDigest<'a, A, L>, - key: &'static mut [u8], - ) -> VirtualMuxDigest<'a, A, L> { - let id = mux_digest.next_id.get(); - mux_digest.next_id.set(id + 1); - - VirtualMuxDigest { - mux: mux_digest, - next: ListLink::empty(), - sha_client: OptionalCell::empty(), - hmac_client: OptionalCell::empty(), - key: TakeCell::new(key), - data: TakeCell::empty(), - data_len: Cell::new(0), - digest: TakeCell::empty(), - verify: Cell::new(false), - mode: Cell::new(Mode::None), - ready: Cell::new(false), - id: id, - } - } - - pub fn set_hmac_client(&'a self, client: &'a dyn digest::Client<'a, L>) { - let node = self.mux.users.iter().find(|node| node.id == self.id); - if node.is_none() { - self.mux.users.push_head(self); - } - self.hmac_client.set(client); - } - - pub fn set_sha_client(&'a self, client: &'a dyn digest::Client<'a, L>) { - let node = self.mux.users.iter().find(|node| node.id == self.id); - if node.is_none() { - self.mux.users.push_head(self); - } - self.sha_client.set(client); - } -} - -impl<'a, A: digest::Digest<'a, L>, const L: usize> digest::DigestData<'a, L> - for VirtualMuxDigest<'a, A, L> -{ - /// Add data to the digest IP. - /// All data passed in is fed to the Digest hardware block. - /// Returns the number of bytes written on success - fn add_data( - &self, - data: LeasableBuffer<'static, u8>, - ) -> Result { - // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running_id.get() == self.id { - self.mux.digest.add_data(data) - } else { - // Another app is already running, queue this app as long as we - // don't already have data queued. - if self.data.is_none() { - let len = data.len(); - self.data.replace(data.take()); - self.data_len.set(len); - Ok(len) - } else { - Err((ErrorCode::BUSY, data.take())) - } - } - } - - /// Disable the Digest hardware and clear the keys and any other sensitive - /// data - fn clear_data(&self) { - if self.mux.running_id.get() == self.id { - self.mux.running.set(false); - self.mode.set(Mode::None); - self.mux.digest.clear_data() - } - } -} - -impl<'a, A: digest::Digest<'a, L>, const L: usize> digest::DigestHash<'a, L> - for VirtualMuxDigest<'a, A, L> -{ - /// Request the hardware block to generate a Digest - /// This doesn't return anything, instead the client needs to have - /// set a `hash_done` handler. - fn run( - &'a self, - digest: &'static mut [u8; L], - ) -> Result<(), (ErrorCode, &'static mut [u8; L])> { - // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running_id.get() == self.id { - self.mux.digest.run(digest) - } else { - // Another app is already running, queue this app as long as we - // don't already have data queued. - if self.digest.is_none() { - self.digest.replace(digest); - self.ready.set(true); - Ok(()) - } else { - Err((ErrorCode::BUSY, digest)) - } - } - } -} - -impl<'a, A: digest::Digest<'a, L>, const L: usize> digest::DigestVerify<'a, L> - for VirtualMuxDigest<'a, A, L> -{ - fn verify( - &self, - compare: &'static mut [u8; L], - ) -> Result<(), (ErrorCode, &'static mut [u8; L])> { - // Check if any mux is enabled - if self.mux.running_id.get() == self.id { - self.mux.digest.verify(compare) - } else { - // Another app is already running, queue this app as long as we - // don't already have data queued. - if self.digest.is_none() { - self.digest.replace(compare); - self.verify.set(true); - self.ready.set(true); - Ok(()) - } else { - Err((ErrorCode::BUSY, compare)) - } - } - } -} - -impl<'a, A: digest::Digest<'a, L>, const L: usize> digest::Digest<'a, L> - for VirtualMuxDigest<'a, A, L> -{ - /// Set the client instance which will receive `add_data_done()` and - /// `hash_done()` callbacks - fn set_client(&'a self, _client: &'a dyn digest::Client<'a, L>) { - unimplemented!() - } -} - -impl< - 'a, - A: digest::Digest<'a, L> - + digest::HMACSha256 - + digest::HMACSha384 - + digest::HMACSha512 - + digest::Sha256 - + digest::Sha384 - + digest::Sha512, - const L: usize, - > digest::ClientData<'a, L> for VirtualMuxDigest<'a, A, L> -{ - fn add_data_done(&'a self, result: Result<(), ErrorCode>, data: &'static mut [u8]) { - match self.mode.get() { - Mode::None => {} - Mode::Hmac(_) => { - self.hmac_client - .map(move |client| client.add_data_done(result, data)); - } - Mode::Sha(_) => { - self.sha_client - .map(move |client| client.add_data_done(result, data)); - } - } - self.mux.do_next_op(); - } -} - -impl< - 'a, - A: digest::Digest<'a, L> - + digest::HMACSha256 - + digest::HMACSha384 - + digest::HMACSha512 - + digest::Sha256 - + digest::Sha384 - + digest::Sha512, - const L: usize, - > digest::ClientHash<'a, L> for VirtualMuxDigest<'a, A, L> -{ - fn hash_done(&'a self, result: Result<(), ErrorCode>, digest: &'static mut [u8; L]) { - match self.mode.get() { - Mode::None => {} - Mode::Hmac(_) => { - self.hmac_client - .map(move |client| client.hash_done(result, digest)); - } - Mode::Sha(_) => { - self.sha_client - .map(move |client| client.hash_done(result, digest)); - } - } - - // Forcefully clear the data to allow other apps to use the HMAC - self.clear_data(); - self.mux.do_next_op(); - } -} -impl< - 'a, - A: digest::Digest<'a, L> - + digest::HMACSha256 - + digest::HMACSha384 - + digest::HMACSha512 - + digest::Sha256 - + digest::Sha384 - + digest::Sha512, - const L: usize, - > digest::ClientVerify<'a, L> for VirtualMuxDigest<'a, A, L> -{ - fn verification_done(&'a self, result: Result, compare: &'static mut [u8; L]) { - match self.mode.get() { - Mode::None => {} - Mode::Hmac(_) => { - self.hmac_client - .map(move |client| client.verification_done(result, compare)); - } - Mode::Sha(_) => { - self.sha_client - .map(move |client| client.verification_done(result, compare)); - } - } - - // Forcefully clear the data to allow other apps to use the HMAC - self.clear_data(); - self.mux.do_next_op(); - } -} - -impl<'a, A: digest::Digest<'a, L> + digest::HMACSha256, const L: usize> digest::HMACSha256 - for VirtualMuxDigest<'a, A, L> -{ - fn set_mode_hmacsha256(&self, key: &[u8]) -> Result<(), ErrorCode> { - // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running.get() == false { - self.mux.running.set(true); - self.mux.running_id.set(self.id); - self.mode.set(Mode::Hmac(Operation::Sha256)); - self.mux.digest.set_mode_hmacsha256(key) - } else { - self.mode.set(Mode::Hmac(Operation::Sha256)); - self.key.map(|buf| buf.copy_from_slice(key)); - Ok(()) - } - } -} - -impl<'a, A: digest::Digest<'a, L> + digest::HMACSha384, const L: usize> digest::HMACSha384 - for VirtualMuxDigest<'a, A, L> -{ - fn set_mode_hmacsha384(&self, key: &[u8]) -> Result<(), ErrorCode> { - // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running.get() == false { - self.mux.running.set(true); - self.mux.running_id.set(self.id); - self.mode.set(Mode::Hmac(Operation::Sha384)); - self.mux.digest.set_mode_hmacsha384(key) - } else { - self.mode.set(Mode::Hmac(Operation::Sha384)); - self.key.map(|buf| buf.copy_from_slice(key)); - Ok(()) - } - } -} - -impl<'a, A: digest::Digest<'a, L> + digest::HMACSha512, const L: usize> digest::HMACSha512 - for VirtualMuxDigest<'a, A, L> -{ - fn set_mode_hmacsha512(&self, key: &[u8]) -> Result<(), ErrorCode> { - // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running.get() == false { - self.mux.running.set(true); - self.mux.running_id.set(self.id); - self.mode.set(Mode::Hmac(Operation::Sha512)); - self.mux.digest.set_mode_hmacsha512(key) - } else { - self.mode.set(Mode::Hmac(Operation::Sha512)); - self.key.map(|buf| buf.copy_from_slice(key)); - Ok(()) - } - } -} - -impl<'a, A: digest::Digest<'a, L> + digest::Sha256, const L: usize> digest::Sha256 - for VirtualMuxDigest<'a, A, L> -{ - fn set_mode_sha256(&self) -> Result<(), ErrorCode> { - // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running.get() == false { - self.mux.running.set(true); - self.mux.running_id.set(self.id); - self.mode.set(Mode::Sha(Operation::Sha256)); - self.mux.digest.set_mode_sha256() - } else { - self.mode.set(Mode::Sha(Operation::Sha256)); - Ok(()) - } - } -} - -impl<'a, A: digest::Digest<'a, L> + digest::Sha384, const L: usize> digest::Sha384 - for VirtualMuxDigest<'a, A, L> -{ - fn set_mode_sha384(&self) -> Result<(), ErrorCode> { - // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running.get() == false { - self.mux.running.set(true); - self.mux.running_id.set(self.id); - self.mode.set(Mode::Sha(Operation::Sha384)); - self.mux.digest.set_mode_sha384() - } else { - self.mode.set(Mode::Sha(Operation::Sha384)); - Ok(()) - } - } -} - -impl<'a, A: digest::Digest<'a, L> + digest::Sha512, const L: usize> digest::Sha512 - for VirtualMuxDigest<'a, A, L> -{ - fn set_mode_sha512(&self) -> Result<(), ErrorCode> { - // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running.get() == false { - self.mux.running.set(true); - self.mux.running_id.set(self.id); - self.mode.set(Mode::Sha(Operation::Sha512)); - self.mux.digest.set_mode_sha512() - } else { - self.mode.set(Mode::Sha(Operation::Sha512)); - Ok(()) - } - } -} - -/// Calling a 'set_mode*()' function from a `VirtualMuxDigest` will mark that -/// `VirtualMuxDigest` as the one that has been enabled and running. Until that -/// Mux calls `clear_data()` it will be the only `VirtualMuxDigest` that can -/// interact with the underlying device. -pub struct MuxDigest<'a, A: digest::Digest<'a, L>, const L: usize> { - digest: &'a A, - running: Cell, - running_id: Cell, - next_id: Cell, - users: List<'a, VirtualMuxDigest<'a, A, L>>, -} - -impl< - 'a, - A: digest::Digest<'a, L> - + digest::HMACSha256 - + digest::HMACSha384 - + digest::HMACSha512 - + digest::Sha256 - + digest::Sha384 - + digest::Sha512, - const L: usize, - > MuxDigest<'a, A, L> -{ - pub const fn new(digest: &'a A) -> MuxDigest<'a, A, L> { - MuxDigest { - digest: digest, - running: Cell::new(false), - running_id: Cell::new(0), - next_id: Cell::new(0), - users: List::new(), - } - } - - fn do_next_op(&self) { - // Search for a node that has a mode set and is set as ready. - // Ready will indicate that `run()` has been called and the operation - // can complete - let mnode = self - .users - .iter() - .find(|node| node.mode.get() != Mode::None && node.ready.get()); - mnode.map(|node| { - self.running.set(true); - self.running_id.set(node.id); - - match node.mode.get() { - Mode::None => {} - Mode::Hmac(op) => { - match op { - Operation::Sha256 => { - node.key.map(|buf| { - self.digest.set_mode_hmacsha256(buf).unwrap(); - }); - } - Operation::Sha384 => { - node.key.map(|buf| { - self.digest.set_mode_hmacsha384(buf).unwrap(); - }); - } - Operation::Sha512 => { - node.key.map(|buf| { - self.digest.set_mode_hmacsha512(buf).unwrap(); - }); - } - } - return; - } - Mode::Sha(op) => { - match op { - Operation::Sha256 => { - self.digest.set_mode_sha256().unwrap(); - } - Operation::Sha384 => { - self.digest.set_mode_sha384().unwrap(); - } - Operation::Sha512 => { - self.digest.set_mode_sha512().unwrap(); - } - } - return; - } - } - - if node.data.is_some() { - let mut lease = LeasableBuffer::new(node.data.take().unwrap()); - lease.slice(0..node.data_len.get()); - - if let Err((err, digest)) = self.digest.add_data(lease) { - node.add_data_done(Err(err), digest); - } - return; - } - - if node.digest.is_some() { - if node.verify.get() { - if let Err((err, compare)) = self.digest.verify(node.digest.take().unwrap()) { - node.verification_done(Err(err), compare); - } - } else { - if let Err((err, data)) = self.digest.run(node.digest.take().unwrap()) { - node.hash_done(Err(err), data); - } - } - } - }); - } -} diff --git a/capsules/src/virtual_hmac.rs b/capsules/src/virtual_hmac.rs deleted file mode 100644 index fd2ee28313..0000000000 --- a/capsules/src/virtual_hmac.rs +++ /dev/null @@ -1,337 +0,0 @@ -//! Virtualize the HMAC interface to enable multiple users of an underlying -//! HMAC hardware peripheral. - -use core::cell::Cell; - -use kernel::collections::list::{List, ListLink, ListNode}; -use kernel::hil::digest::{self, ClientData, ClientHash, ClientVerify, DigestData}; -use kernel::utilities::cells::{OptionalCell, TakeCell}; -use kernel::utilities::leasable_buffer::LeasableBuffer; -use kernel::ErrorCode; - -use crate::virtual_digest::{Mode, Operation}; - -pub struct VirtualMuxHmac<'a, A: digest::Digest<'a, L>, const L: usize> { - mux: &'a MuxHmac<'a, A, L>, - next: ListLink<'a, VirtualMuxHmac<'a, A, L>>, - client: OptionalCell<&'a dyn digest::Client<'a, L>>, - key: TakeCell<'static, [u8]>, - data: TakeCell<'static, [u8]>, - data_len: Cell, - digest: TakeCell<'static, [u8; L]>, - verify: Cell, - mode: Cell, - id: u32, -} - -impl<'a, A: digest::Digest<'a, L>, const L: usize> ListNode<'a, VirtualMuxHmac<'a, A, L>> - for VirtualMuxHmac<'a, A, L> -{ - fn next(&self) -> &'a ListLink> { - &self.next - } -} - -impl<'a, A: digest::Digest<'a, L>, const L: usize> VirtualMuxHmac<'a, A, L> { - pub fn new( - mux_hmac: &'a MuxHmac<'a, A, L>, - key: &'static mut [u8], - ) -> VirtualMuxHmac<'a, A, L> { - let id = mux_hmac.next_id.get(); - mux_hmac.next_id.set(id + 1); - - VirtualMuxHmac { - mux: mux_hmac, - next: ListLink::empty(), - client: OptionalCell::empty(), - key: TakeCell::new(key), - data: TakeCell::empty(), - data_len: Cell::new(0), - digest: TakeCell::empty(), - verify: Cell::new(false), - mode: Cell::new(Mode::None), - id: id, - } - } -} - -impl<'a, A: digest::Digest<'a, L>, const L: usize> digest::DigestData<'a, L> - for VirtualMuxHmac<'a, A, L> -{ - /// Add data to the hmac IP. - /// All data passed in is fed to the HMAC hardware block. - /// Returns the number of bytes written on success - fn add_data( - &self, - data: LeasableBuffer<'static, u8>, - ) -> Result { - // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running_id.get() == self.id { - self.mux.hmac.add_data(data) - } else { - // Another app is already running, queue this app as long as we - // don't already have data queued. - if self.data.is_none() { - let len = data.len(); - self.data.replace(data.take()); - self.data_len.set(len); - Ok(len) - } else { - Err((ErrorCode::BUSY, data.take())) - } - } - } - - /// Disable the HMAC hardware and clear the keys and any other sensitive - /// data - fn clear_data(&self) { - if self.mux.running_id.get() == self.id { - self.mux.running.set(false); - self.mode.set(Mode::None); - self.mux.hmac.clear_data() - } - } -} - -impl<'a, A: digest::Digest<'a, L>, const L: usize> digest::DigestHash<'a, L> - for VirtualMuxHmac<'a, A, L> -{ - /// Request the hardware block to generate a HMAC - /// This doesn't return anything, instead the client needs to have - /// set a `hash_done` handler. - fn run( - &'a self, - digest: &'static mut [u8; L], - ) -> Result<(), (ErrorCode, &'static mut [u8; L])> { - // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running_id.get() == self.id { - self.mux.hmac.run(digest) - } else { - // Another app is already running, queue this app as long as we - // don't already have data queued. - if self.digest.is_none() { - self.digest.replace(digest); - Ok(()) - } else { - Err((ErrorCode::BUSY, digest)) - } - } - } -} - -impl<'a, A: digest::Digest<'a, L>, const L: usize> digest::DigestVerify<'a, L> - for VirtualMuxHmac<'a, A, L> -{ - fn verify( - &self, - compare: &'static mut [u8; L], - ) -> Result<(), (ErrorCode, &'static mut [u8; L])> { - // Check if any mux is enabled - if self.mux.running_id.get() == self.id { - self.mux.hmac.verify(compare) - } else { - // Another app is already running, queue this app as long as we - // don't already have data queued. - if self.digest.is_none() { - self.digest.replace(compare); - self.verify.set(true); - Ok(()) - } else { - Err((ErrorCode::BUSY, compare)) - } - } - } -} - -impl<'a, A: digest::Digest<'a, L>, const L: usize> digest::Digest<'a, L> - for VirtualMuxHmac<'a, A, L> -{ - /// Set the client instance which will receive `add_data_done()` and - /// `hash_done()` callbacks - fn set_client(&'a self, client: &'a dyn digest::Client<'a, L>) { - let node = self.mux.users.iter().find(|node| node.id == self.id); - if node.is_none() { - self.mux.users.push_head(self); - } - self.mux.hmac.set_client(client); - } -} - -impl< - 'a, - A: digest::Digest<'a, L> + digest::HMACSha256 + digest::HMACSha384 + digest::HMACSha512, - const L: usize, - > digest::ClientData<'a, L> for VirtualMuxHmac<'a, A, L> -{ - fn add_data_done(&'a self, result: Result<(), ErrorCode>, data: &'static mut [u8]) { - self.client - .map(move |client| client.add_data_done(result, data)); - self.mux.do_next_op(); - } -} - -impl< - 'a, - A: digest::Digest<'a, L> + digest::HMACSha256 + digest::HMACSha384 + digest::HMACSha512, - const L: usize, - > digest::ClientHash<'a, L> for VirtualMuxHmac<'a, A, L> -{ - fn hash_done(&'a self, result: Result<(), ErrorCode>, digest: &'static mut [u8; L]) { - self.client - .map(move |client| client.hash_done(result, digest)); - - // Forcefully clear the data to allow other apps to use the HMAC - self.clear_data(); - self.mux.do_next_op(); - } -} - -impl< - 'a, - A: digest::Digest<'a, L> + digest::HMACSha256 + digest::HMACSha384 + digest::HMACSha512, - const L: usize, - > digest::ClientVerify<'a, L> for VirtualMuxHmac<'a, A, L> -{ - fn verification_done(&'a self, result: Result, digest: &'static mut [u8; L]) { - self.client - .map(move |client| client.verification_done(result, digest)); - - // Forcefully clear the data to allow other apps to use the HMAC - self.clear_data(); - self.mux.do_next_op(); - } -} - -impl<'a, A: digest::Digest<'a, L> + digest::HMACSha256, const L: usize> digest::HMACSha256 - for VirtualMuxHmac<'a, A, L> -{ - fn set_mode_hmacsha256(&self, key: &[u8]) -> Result<(), ErrorCode> { - // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running.get() == false { - self.mux.running.set(true); - self.mux.running_id.set(self.id); - self.mode.set(Mode::Hmac(Operation::Sha256)); - self.mux.hmac.set_mode_hmacsha256(key) - } else { - self.mode.set(Mode::Hmac(Operation::Sha256)); - self.key.map(|buf| buf.copy_from_slice(key)); - Ok(()) - } - } -} - -impl<'a, A: digest::Digest<'a, L> + digest::HMACSha384, const L: usize> digest::HMACSha384 - for VirtualMuxHmac<'a, A, L> -{ - fn set_mode_hmacsha384(&self, key: &[u8]) -> Result<(), ErrorCode> { - // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running.get() == false { - self.mux.running.set(true); - self.mux.running_id.set(self.id); - self.mode.set(Mode::Hmac(Operation::Sha384)); - self.mux.hmac.set_mode_hmacsha384(key) - } else { - self.mode.set(Mode::Hmac(Operation::Sha384)); - self.key.map(|buf| buf.copy_from_slice(key)); - Ok(()) - } - } -} - -impl<'a, A: digest::Digest<'a, L> + digest::HMACSha512, const L: usize> digest::HMACSha512 - for VirtualMuxHmac<'a, A, L> -{ - fn set_mode_hmacsha512(&self, key: &[u8]) -> Result<(), ErrorCode> { - // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running.get() == false { - self.mux.running.set(true); - self.mux.running_id.set(self.id); - self.mode.set(Mode::Hmac(Operation::Sha512)); - self.mux.hmac.set_mode_hmacsha512(key) - } else { - self.mode.set(Mode::Hmac(Operation::Sha512)); - self.key.map(|buf| buf.copy_from_slice(key)); - Ok(()) - } - } -} - -pub struct MuxHmac<'a, A: digest::Digest<'a, L>, const L: usize> { - hmac: &'a A, - running: Cell, - running_id: Cell, - next_id: Cell, - users: List<'a, VirtualMuxHmac<'a, A, L>>, -} - -impl< - 'a, - A: digest::Digest<'a, L> + digest::HMACSha256 + digest::HMACSha384 + digest::HMACSha512, - const L: usize, - > MuxHmac<'a, A, L> -{ - pub const fn new(hmac: &'a A) -> MuxHmac<'a, A, L> { - MuxHmac { - hmac, - running: Cell::new(false), - running_id: Cell::new(0), - next_id: Cell::new(0), - users: List::new(), - } - } - - fn do_next_op(&self) { - let mnode = self.users.iter().find(|node| node.mode.get() != Mode::None); - mnode.map(|node| { - self.running.set(true); - self.running_id.set(node.id); - - match node.mode.get() { - Mode::None => {} - Mode::Sha(_) => {} - Mode::Hmac(op) => { - match op { - Operation::Sha256 => { - node.key.map(|buf| { - self.hmac.set_mode_hmacsha256(buf).unwrap(); - }); - } - Operation::Sha384 => { - node.key.map(|buf| { - self.hmac.set_mode_hmacsha384(buf).unwrap(); - }); - } - Operation::Sha512 => { - node.key.map(|buf| { - self.hmac.set_mode_hmacsha512(buf).unwrap(); - }); - } - } - return; - } - } - - if node.data.is_some() { - let mut lease = LeasableBuffer::new(node.data.take().unwrap()); - lease.slice(0..node.data_len.get()); - - if let Err((err, digest)) = self.hmac.add_data(lease) { - node.add_data_done(Err(err), digest); - } - return; - } - - if node.digest.is_some() { - if node.verify.get() { - if let Err((err, compare)) = self.hmac.verify(node.digest.take().unwrap()) { - node.verification_done(Err(err), compare); - } - } else { - if let Err((err, data)) = self.hmac.run(node.digest.take().unwrap()) { - node.hash_done(Err(err), data); - } - } - } - }); - } -} diff --git a/capsules/src/virtual_sha.rs b/capsules/src/virtual_sha.rs deleted file mode 100644 index 1b1a58a37b..0000000000 --- a/capsules/src/virtual_sha.rs +++ /dev/null @@ -1,323 +0,0 @@ -//! Virtualize the SHA interface to enable multiple users of an underlying -//! SHA hardware peripheral. - -use core::cell::Cell; - -use kernel::collections::list::{List, ListLink, ListNode}; -use kernel::hil::digest::{self, ClientData, ClientHash, ClientVerify, DigestData}; -use kernel::utilities::cells::{OptionalCell, TakeCell}; -use kernel::utilities::leasable_buffer::LeasableBuffer; -use kernel::ErrorCode; - -use crate::virtual_digest::{Mode, Operation}; - -pub struct VirtualMuxSha<'a, A: digest::Digest<'a, L>, const L: usize> { - mux: &'a MuxSha<'a, A, L>, - next: ListLink<'a, VirtualMuxSha<'a, A, L>>, - client: OptionalCell<&'a dyn digest::Client<'a, L>>, - data: TakeCell<'static, [u8]>, - data_len: Cell, - digest: TakeCell<'static, [u8; L]>, - verify: Cell, - mode: Cell, - id: u32, -} - -impl<'a, A: digest::Digest<'a, L>, const L: usize> ListNode<'a, VirtualMuxSha<'a, A, L>> - for VirtualMuxSha<'a, A, L> -{ - fn next(&self) -> &'a ListLink> { - &self.next - } -} - -impl<'a, A: digest::Digest<'a, L>, const L: usize> VirtualMuxSha<'a, A, L> { - pub fn new(mux_sha: &'a MuxSha<'a, A, L>) -> VirtualMuxSha<'a, A, L> { - let id = mux_sha.next_id.get(); - mux_sha.next_id.set(id + 1); - - VirtualMuxSha { - mux: mux_sha, - next: ListLink::empty(), - client: OptionalCell::empty(), - data: TakeCell::empty(), - data_len: Cell::new(0), - digest: TakeCell::empty(), - verify: Cell::new(false), - mode: Cell::new(Mode::None), - id: id, - } - } -} - -impl<'a, A: digest::Digest<'a, L>, const L: usize> digest::DigestData<'a, L> - for VirtualMuxSha<'a, A, L> -{ - /// Add data to the sha IP. - /// All data passed in is fed to the SHA hardware block. - /// Returns the number of bytes written on success - fn add_data( - &self, - data: LeasableBuffer<'static, u8>, - ) -> Result { - // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running_id.get() == self.id { - self.mux.sha.add_data(data) - } else { - // Another app is already running, queue this app as long as we - // don't already have data queued. - if self.data.is_none() { - let len = data.len(); - self.data.replace(data.take()); - self.data_len.set(len); - Ok(len) - } else { - Err((ErrorCode::BUSY, data.take())) - } - } - } - - /// Disable the SHA hardware and clear the keys and any other sensitive - /// data - fn clear_data(&self) { - if self.mux.running_id.get() == self.id { - self.mux.running.set(false); - self.mode.set(Mode::None); - self.mux.sha.clear_data() - } - } -} - -impl<'a, A: digest::Digest<'a, L>, const L: usize> digest::DigestHash<'a, L> - for VirtualMuxSha<'a, A, L> -{ - /// Request the hardware block to generate a SHA - /// This doesn't return anything, instead the client needs to have - /// set a `hash_done` handler. - fn run( - &'a self, - digest: &'static mut [u8; L], - ) -> Result<(), (ErrorCode, &'static mut [u8; L])> { - // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running_id.get() == self.id { - self.mux.sha.run(digest) - } else { - // Another app is already running, queue this app as long as we - // don't already have data queued. - if self.digest.is_none() { - self.digest.replace(digest); - Ok(()) - } else { - Err((ErrorCode::BUSY, digest)) - } - } - } -} - -impl<'a, A: digest::Digest<'a, L>, const L: usize> digest::DigestVerify<'a, L> - for VirtualMuxSha<'a, A, L> -{ - fn verify( - &self, - compare: &'static mut [u8; L], - ) -> Result<(), (ErrorCode, &'static mut [u8; L])> { - // Check if any mux is enabled - if self.mux.running_id.get() == self.id { - self.mux.sha.verify(compare) - } else { - // Another app is already running, queue this app as long as we - // don't already have data queued. - if self.digest.is_none() { - self.digest.replace(compare); - self.verify.set(true); - Ok(()) - } else { - Err((ErrorCode::BUSY, compare)) - } - } - } -} - -impl<'a, A: digest::Digest<'a, L>, const L: usize> digest::Digest<'a, L> - for VirtualMuxSha<'a, A, L> -{ - /// Set the client instance which will receive `add_data_done()` and - /// `hash_done()` callbacks - fn set_client(&'a self, client: &'a dyn digest::Client<'a, L>) { - let node = self.mux.users.iter().find(|node| node.id == self.id); - if node.is_none() { - self.mux.users.push_head(self); - } - self.mux.sha.set_client(client); - } -} - -impl< - 'a, - A: digest::Digest<'a, L> + digest::Sha256 + digest::Sha384 + digest::Sha512, - const L: usize, - > digest::ClientData<'a, L> for VirtualMuxSha<'a, A, L> -{ - fn add_data_done(&'a self, result: Result<(), ErrorCode>, data: &'static mut [u8]) { - self.client - .map(move |client| client.add_data_done(result, data)); - self.mux.do_next_op(); - } -} - -impl< - 'a, - A: digest::Digest<'a, L> + digest::Sha256 + digest::Sha384 + digest::Sha512, - const L: usize, - > digest::ClientHash<'a, L> for VirtualMuxSha<'a, A, L> -{ - fn hash_done(&'a self, result: Result<(), ErrorCode>, digest: &'static mut [u8; L]) { - self.client - .map(move |client| client.hash_done(result, digest)); - - // Forcefully clear the data to allow other apps to use the HMAC - self.clear_data(); - self.mux.do_next_op(); - } -} - -impl< - 'a, - A: digest::Digest<'a, L> + digest::Sha256 + digest::Sha384 + digest::Sha512, - const L: usize, - > digest::ClientVerify<'a, L> for VirtualMuxSha<'a, A, L> -{ - fn verification_done(&'a self, result: Result, digest: &'static mut [u8; L]) { - self.client - .map(move |client| client.verification_done(result, digest)); - - // Forcefully clear the data to allow other apps to use the HMAC - self.clear_data(); - self.mux.do_next_op(); - } -} - -impl<'a, A: digest::Digest<'a, L> + digest::Sha256, const L: usize> digest::Sha256 - for VirtualMuxSha<'a, A, L> -{ - fn set_mode_sha256(&self) -> Result<(), ErrorCode> { - // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running.get() == false { - self.mux.running.set(true); - self.mux.running_id.set(self.id); - self.mode.set(Mode::Sha(Operation::Sha256)); - self.mux.sha.set_mode_sha256() - } else { - self.mode.set(Mode::Sha(Operation::Sha256)); - Ok(()) - } - } -} - -impl<'a, A: digest::Digest<'a, L> + digest::Sha384, const L: usize> digest::Sha384 - for VirtualMuxSha<'a, A, L> -{ - fn set_mode_sha384(&self) -> Result<(), ErrorCode> { - // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running.get() == false { - self.mux.running.set(true); - self.mux.running_id.set(self.id); - self.mode.set(Mode::Sha(Operation::Sha384)); - self.mux.sha.set_mode_sha384() - } else { - self.mode.set(Mode::Sha(Operation::Sha384)); - Ok(()) - } - } -} - -impl<'a, A: digest::Digest<'a, L> + digest::Sha512, const L: usize> digest::Sha512 - for VirtualMuxSha<'a, A, L> -{ - fn set_mode_sha512(&self) -> Result<(), ErrorCode> { - // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running.get() == false { - self.mux.running.set(true); - self.mux.running_id.set(self.id); - self.mode.set(Mode::Sha(Operation::Sha512)); - self.mux.sha.set_mode_sha512() - } else { - self.mode.set(Mode::Sha(Operation::Sha512)); - Ok(()) - } - } -} - -pub struct MuxSha<'a, A: digest::Digest<'a, L>, const L: usize> { - sha: &'a A, - running: Cell, - running_id: Cell, - next_id: Cell, - users: List<'a, VirtualMuxSha<'a, A, L>>, -} - -impl< - 'a, - A: digest::Digest<'a, L> + digest::Sha256 + digest::Sha384 + digest::Sha512, - const L: usize, - > MuxSha<'a, A, L> -{ - pub const fn new(sha: &'a A) -> MuxSha<'a, A, L> { - MuxSha { - sha, - running: Cell::new(false), - running_id: Cell::new(0), - next_id: Cell::new(0), - users: List::new(), - } - } - - fn do_next_op(&self) { - let mnode = self.users.iter().find(|node| node.mode.get() != Mode::None); - mnode.map(|node| { - self.running.set(true); - self.running_id.set(node.id); - - match node.mode.get() { - Mode::None => {} - Mode::Hmac(_) => {} - Mode::Sha(op) => { - match op { - Operation::Sha256 => { - self.sha.set_mode_sha256().unwrap(); - } - Operation::Sha384 => { - self.sha.set_mode_sha384().unwrap(); - } - Operation::Sha512 => { - self.sha.set_mode_sha512().unwrap(); - } - } - return; - } - } - - if node.data.is_some() { - let mut lease = LeasableBuffer::new(node.data.take().unwrap()); - lease.slice(0..node.data_len.get()); - - if let Err((err, digest)) = self.sha.add_data(lease) { - node.add_data_done(Err(err), digest); - } - return; - } - - if node.digest.is_some() { - if node.verify.get() { - if let Err((err, compare)) = self.sha.verify(node.digest.take().unwrap()) { - node.verification_done(Err(err), compare); - } - } else { - if let Err((err, data)) = self.sha.run(node.digest.take().unwrap()) { - node.hash_done(Err(err), data); - } - } - } - }); - } -} diff --git a/capsules/system/Cargo.toml b/capsules/system/Cargo.toml new file mode 100644 index 0000000000..2cec4b28b3 --- /dev/null +++ b/capsules/system/Cargo.toml @@ -0,0 +1,16 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +[package] +name = "capsules-system" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[dependencies] +kernel = { path = "../../kernel" } +tock-tbf = { path = "../../libraries/tock-tbf" } + +[lints] +workspace = true diff --git a/capsules/system/README.md b/capsules/system/README.md new file mode 100644 index 0000000000..4fc8f701eb --- /dev/null +++ b/capsules/system/README.md @@ -0,0 +1,12 @@ +System Tock Capsules +================== + +System capsules are largely the same as other (i.e., core and extra) capsules, +in that they are logical software modules that contain untrusted code that +cannot use `unsafe`. The difference is that system capsules implement non-HIL +interfaces defined in the core kernel crate and extend the functionality of the +core kernel. + +These capsules are used the same way as other capsules in that they are +instantiated in board main.rs files (often using components). However, these +capsule objects are passed to the core kernel. diff --git a/capsules/system/src/lib.rs b/capsules/system/src/lib.rs new file mode 100644 index 0000000000..cf172dea16 --- /dev/null +++ b/capsules/system/src/lib.rs @@ -0,0 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +#![forbid(unsafe_code)] +#![no_std] + +pub mod process_checker; +pub mod process_policies; +pub mod process_printer; diff --git a/capsules/system/src/process_checker/basic.rs b/capsules/system/src/process_checker/basic.rs new file mode 100644 index 0000000000..07d0a342f2 --- /dev/null +++ b/capsules/system/src/process_checker/basic.rs @@ -0,0 +1,331 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Sample implementations of application credentials checkers, used +//! to decide whether an application can be loaded. See +//| the [AppID TRD](../../doc/reference/trd-appid.md). + +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; +use kernel::hil::digest::{ClientData, ClientHash, ClientVerify}; +use kernel::hil::digest::{DigestDataVerify, Sha256}; +use kernel::process::{Process, ProcessBinary, ShortId}; +use kernel::process_checker::CheckResult; +use kernel::process_checker::{AppCredentialsPolicy, AppCredentialsPolicyClient}; +use kernel::process_checker::{AppUniqueness, Compress}; +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::cells::TakeCell; +use kernel::utilities::leasable_buffer::{SubSlice, SubSliceMut}; +use kernel::ErrorCode; +use tock_tbf::types::TbfFooterV2Credentials; +use tock_tbf::types::TbfFooterV2CredentialsType; + +/// A sample Credentials Checking Policy that approves all apps. +pub struct AppCheckerNull {} + +impl AppCheckerNull { + pub fn new() -> Self { + Self {} + } +} + +impl<'a> AppCredentialsPolicy<'a> for AppCheckerNull { + fn require_credentials(&self) -> bool { + false + } + + fn check_credentials( + &self, + credentials: TbfFooterV2Credentials, + binary: &'a [u8], + ) -> Result<(), (ErrorCode, TbfFooterV2Credentials, &'a [u8])> { + Err((ErrorCode::NOSUPPORT, credentials, binary)) + } + + fn set_client(&self, _client: &'a dyn AppCredentialsPolicyClient<'a>) {} +} + +pub struct AppIdAssignerSimulated {} + +impl AppUniqueness for AppIdAssignerSimulated { + // This checker doesn't allow you to run two processes with the + // same name. + fn different_identifier(&self, process_a: &ProcessBinary, process_b: &ProcessBinary) -> bool { + let a = process_a.header.get_package_name().unwrap_or(""); + let b = process_b.header.get_package_name().unwrap_or(""); + !a.eq(b) + } + + fn different_identifier_process( + &self, + process_binary: &ProcessBinary, + process: &dyn Process, + ) -> bool { + let a = process_binary.header.get_package_name().unwrap_or(""); + let b = process.get_process_name(); + !a.eq(b) + } + + fn different_identifier_processes( + &self, + process_a: &dyn Process, + process_b: &dyn Process, + ) -> bool { + let a = process_a.get_process_name(); + let b = process_b.get_process_name(); + !a.eq(b) + } +} + +impl Compress for AppIdAssignerSimulated { + fn to_short_id(&self, _process: &ProcessBinary) -> ShortId { + ShortId::LocallyUnique + } +} + +pub trait Sha256Verifier<'a>: DigestDataVerify<'a, 32_usize> + Sha256 {} +impl<'a, T: DigestDataVerify<'a, 32_usize> + Sha256> Sha256Verifier<'a> for T {} + +/// A Credentials Checking Policy that only runs Userspace Binaries +/// which have a unique SHA256 credential. A Userspace Binary without +/// a SHA256 credential fails checking, and only one Userspace Binary +/// with a particular SHA256 hash runs at any time. +pub struct AppCheckerSha256 { + hasher: &'static dyn Sha256Verifier<'static>, + client: OptionalCell<&'static dyn AppCredentialsPolicyClient<'static>>, + hash: TakeCell<'static, [u8; 32]>, + binary: OptionalCell<&'static [u8]>, + credentials: OptionalCell, +} + +impl AppCheckerSha256 { + pub fn new( + hash: &'static dyn Sha256Verifier<'static>, + buffer: &'static mut [u8; 32], + ) -> AppCheckerSha256 { + AppCheckerSha256 { + hasher: hash, + client: OptionalCell::empty(), + hash: TakeCell::new(buffer), + credentials: OptionalCell::empty(), + binary: OptionalCell::empty(), + } + } +} + +impl AppCredentialsPolicy<'static> for AppCheckerSha256 { + fn require_credentials(&self) -> bool { + true + } + + fn check_credentials( + &self, + credentials: TbfFooterV2Credentials, + binary: &'static [u8], + ) -> Result<(), (ErrorCode, TbfFooterV2Credentials, &'static [u8])> { + self.credentials.set(credentials); + match credentials.format() { + TbfFooterV2CredentialsType::SHA256 => { + self.hash.map(|h| { + h[..32].copy_from_slice(&credentials.data()[..32]); + }); + self.hasher.clear_data(); + match self.hasher.add_data(SubSlice::new(binary)) { + Ok(()) => Ok(()), + Err((e, b)) => Err((e, credentials, b.take())), + } + } + _ => Err((ErrorCode::NOSUPPORT, credentials, binary)), + } + } + + fn set_client(&self, client: &'static dyn AppCredentialsPolicyClient<'static>) { + self.client.replace(client); + } +} + +impl ClientData<32_usize> for AppCheckerSha256 { + fn add_mut_data_done(&self, _result: Result<(), ErrorCode>, _data: SubSliceMut<'static, u8>) {} + + fn add_data_done(&self, result: Result<(), ErrorCode>, data: SubSlice<'static, u8>) { + match result { + Err(e) => panic!("Internal error during application binary checking. SHA256 engine threw error in adding data: {:?}", e), + Ok(()) => { + self.binary.set(data.take()); + let hash: &'static mut [u8; 32_usize] = self.hash.take().unwrap(); + if let Err((e, _)) = self.hasher.verify(hash) { panic!("Failed invoke hash verification in process credential checking: {:?}", e) } + } + } + } +} + +impl ClientVerify<32_usize> for AppCheckerSha256 { + fn verification_done( + &self, + result: Result, + compare: &'static mut [u8; 32_usize], + ) { + self.hash.replace(compare); + match result { + Ok(true) => { + self.client.map(|c| { + c.check_done( + Ok(CheckResult::Accept), + self.credentials.take().unwrap(), + self.binary.take().unwrap(), + ); + }); + } + Ok(false) => { + self.client.map(|c| { + c.check_done( + Ok(CheckResult::Reject), + self.credentials.take().unwrap(), + self.binary.take().unwrap(), + ); + }); + } + Err(e) => { + panic!("Error {:?} in processing application credentials.", e); + } + } + } +} + +impl ClientHash<32_usize> for AppCheckerSha256 { + fn hash_done(&self, _result: Result<(), ErrorCode>, _digest: &'static mut [u8; 32_usize]) {} +} + +/// A sample AppID Assignment tool that assigns pseudo-unique AppIDs and +/// ShortIds based on the process name. +/// +/// ShortIds are assigned as a non-secure hash of the process name. +/// +/// ### Usage +/// +/// ```rust,ignore +/// let assigner = static_init!( +/// kernel::process_checker::basic::AppIdAssignerNames u32>, +/// kernel::process_checker::basic::AppIdAssignerNames::new( +/// &((|s| { kernel::utilities::helpers::crc32_posix(s.as_bytes()) }) +/// as fn(&'static str) -> u32) +/// ) +/// ); +/// ``` +pub struct AppIdAssignerNames<'a, F: Fn(&'static str) -> u32> { + hasher: &'a F, +} + +impl<'a, F: Fn(&'static str) -> u32> AppIdAssignerNames<'a, F> { + pub fn new(hasher: &'a F) -> Self { + Self { hasher } + } +} + +impl<'a, F: Fn(&'static str) -> u32> AppUniqueness for AppIdAssignerNames<'a, F> { + fn different_identifier(&self, process_a: &ProcessBinary, process_b: &ProcessBinary) -> bool { + self.to_short_id(process_a) != self.to_short_id(process_b) + } + + fn different_identifier_process( + &self, + process_a: &ProcessBinary, + process_b: &dyn Process, + ) -> bool { + self.to_short_id(process_a) != process_b.short_app_id() + } + + fn different_identifier_processes( + &self, + process_a: &dyn Process, + process_b: &dyn Process, + ) -> bool { + process_a.short_app_id() != process_b.short_app_id() + } +} + +impl<'a, F: Fn(&'static str) -> u32> Compress for AppIdAssignerNames<'a, F> { + fn to_short_id(&self, process: &ProcessBinary) -> ShortId { + let name = process.header.get_package_name().unwrap_or(""); + let sum = (self.hasher)(name); + core::num::NonZeroU32::new(sum).into() + } +} + +/// A sample Credentials Checking Policy that loads and runs Userspace +/// Binaries that have RSA3072 or RSA4096 credentials. It uses the +/// public key stored in the credentials as the Application +/// Identifier, and the bottom 31 bits of the public key as the +/// ShortId. WARNING: this policy does not actually check the RSA +/// signature: it always blindly assumes it is correct. This checker +/// exists to test that the Tock boot sequence correctly handles +/// ID collisions and version numbers. +pub struct AppCheckerRsaSimulated<'a> { + deferred_call: DeferredCall, + client: OptionalCell<&'a dyn AppCredentialsPolicyClient<'a>>, + credentials: OptionalCell, + binary: OptionalCell<&'a [u8]>, +} + +impl<'a> AppCheckerRsaSimulated<'a> { + pub fn new() -> AppCheckerRsaSimulated<'a> { + Self { + deferred_call: DeferredCall::new(), + client: OptionalCell::empty(), + credentials: OptionalCell::empty(), + binary: OptionalCell::empty(), + } + } +} + +impl<'a> DeferredCallClient for AppCheckerRsaSimulated<'a> { + fn handle_deferred_call(&self) { + // This checker does not actually verify the RSA signature; it + // assumes the signature is valid and so accepts any RSA + // signature. This checker is intended for testing kernel + // process loading logic, and not for real uses requiring + // integrity or authenticity. + self.client.map(|c| { + let binary = self.binary.take().unwrap(); + let cred = self.credentials.take().unwrap(); + let result = if cred.format() == TbfFooterV2CredentialsType::Rsa3072Key + || cred.format() == TbfFooterV2CredentialsType::Rsa4096Key + { + Ok(CheckResult::Accept) + } else { + Ok(CheckResult::Pass) + }; + + c.check_done(result, cred, binary) + }); + } + + fn register(&'static self) { + self.deferred_call.register(self); + } +} + +impl<'a> AppCredentialsPolicy<'a> for AppCheckerRsaSimulated<'a> { + fn require_credentials(&self) -> bool { + true + } + + fn check_credentials( + &self, + credentials: TbfFooterV2Credentials, + binary: &'a [u8], + ) -> Result<(), (ErrorCode, TbfFooterV2Credentials, &'a [u8])> { + if self.credentials.is_none() { + self.credentials.replace(credentials); + self.binary.replace(binary); + self.deferred_call.set(); + Ok(()) + } else { + Err((ErrorCode::BUSY, credentials, binary)) + } + } + + fn set_client(&self, client: &'a dyn AppCredentialsPolicyClient<'a>) { + self.client.replace(client); + } +} diff --git a/capsules/system/src/process_checker/mod.rs b/capsules/system/src/process_checker/mod.rs new file mode 100644 index 0000000000..b75e04b00c --- /dev/null +++ b/capsules/system/src/process_checker/mod.rs @@ -0,0 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +pub mod basic; +pub mod signature; +pub mod tbf; diff --git a/capsules/system/src/process_checker/signature.rs b/capsules/system/src/process_checker/signature.rs new file mode 100644 index 0000000000..72c3edeae1 --- /dev/null +++ b/capsules/system/src/process_checker/signature.rs @@ -0,0 +1,235 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Signature credential checker for checking process credentials. + +use kernel::hil; +use kernel::process_checker::CheckResult; +use kernel::process_checker::{AppCredentialsPolicy, AppCredentialsPolicyClient}; +use kernel::utilities::cells::MapCell; +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::leasable_buffer::{SubSlice, SubSliceMut}; +use kernel::ErrorCode; +use tock_tbf::types::TbfFooterV2Credentials; +use tock_tbf::types::TbfFooterV2CredentialsType; + +/// Checker that validates a correct signature credential. +/// +/// This checker provides the scaffolding on top of a hasher (`&H`) and a +/// verifier (`&S`) for a given `TbfFooterV2CredentialsType`. +/// +/// This assumes the `TbfFooterV2CredentialsType` data format only contains the +/// signature (i.e. the data length of the credential in the TBF footer is the +/// same as `SL`). +pub struct AppCheckerSignature< + 'a, + S: hil::public_key_crypto::signature::SignatureVerify<'static, HL, SL>, + H: hil::digest::DigestDataHash<'a, HL>, + const HL: usize, + const SL: usize, +> { + hasher: &'a H, + verifier: &'a S, + hash: MapCell<&'static mut [u8; HL]>, + signature: MapCell<&'static mut [u8; SL]>, + client: OptionalCell<&'static dyn AppCredentialsPolicyClient<'static>>, + credential_type: TbfFooterV2CredentialsType, + credentials: OptionalCell, + binary: OptionalCell<&'static [u8]>, +} + +impl< + 'a, + S: hil::public_key_crypto::signature::SignatureVerify<'static, HL, SL>, + H: hil::digest::DigestDataHash<'a, HL>, + const HL: usize, + const SL: usize, + > AppCheckerSignature<'a, S, H, HL, SL> +{ + pub fn new( + hasher: &'a H, + verifier: &'a S, + hash_buffer: &'static mut [u8; HL], + signature_buffer: &'static mut [u8; SL], + credential_type: TbfFooterV2CredentialsType, + ) -> AppCheckerSignature<'a, S, H, HL, SL> { + Self { + hasher, + verifier, + hash: MapCell::new(hash_buffer), + signature: MapCell::new(signature_buffer), + client: OptionalCell::empty(), + credential_type, + credentials: OptionalCell::empty(), + binary: OptionalCell::empty(), + } + } +} + +impl< + 'a, + S: hil::public_key_crypto::signature::SignatureVerify<'static, HL, SL>, + H: hil::digest::DigestDataHash<'a, HL>, + const HL: usize, + const SL: usize, + > hil::digest::ClientData for AppCheckerSignature<'a, S, H, HL, SL> +{ + fn add_mut_data_done(&self, _result: Result<(), ErrorCode>, _data: SubSliceMut<'static, u8>) {} + + fn add_data_done(&self, result: Result<(), ErrorCode>, data: SubSlice<'static, u8>) { + self.binary.set(data.take()); + + // We added the binary data to the hasher, now we can compute the hash. + match result { + Err(e) => { + self.client.map(|c| { + let binary = self.binary.take().unwrap(); + let cred = self.credentials.take().unwrap(); + c.check_done(Err(e), cred, binary) + }); + } + Ok(()) => { + self.hash.take().map(|h| { + if let Err((e, _)) = self.hasher.run(h) { + self.client.map(|c| { + let binary = self.binary.take().unwrap(); + let cred = self.credentials.take().unwrap(); + c.check_done(Err(e), cred, binary) + }); + } + }); + } + } + } +} + +impl< + 'a, + S: hil::public_key_crypto::signature::SignatureVerify<'static, HL, SL>, + H: hil::digest::DigestDataHash<'a, HL>, + const HL: usize, + const SL: usize, + > hil::digest::ClientHash for AppCheckerSignature<'a, S, H, HL, SL> +{ + fn hash_done(&self, result: Result<(), ErrorCode>, digest: &'static mut [u8; HL]) { + match result { + Err(e) => { + self.hash.replace(digest); + self.client.map(|c| { + let binary = self.binary.take().unwrap(); + let cred = self.credentials.take().unwrap(); + c.check_done(Err(e), cred, binary) + }); + } + Ok(()) => match self.signature.take() { + Some(sig) => { + if let Err((e, d, s)) = self.verifier.verify(digest, sig) { + self.hash.replace(d); + self.signature.replace(s); + self.client.map(|c| { + let binary = self.binary.take().unwrap(); + let cred = self.credentials.take().unwrap(); + c.check_done(Err(e), cred, binary) + }); + } + } + None => { + self.hash.replace(digest); + self.client.map(|c| { + let binary = self.binary.take().unwrap(); + let cred = self.credentials.take().unwrap(); + c.check_done(Err(ErrorCode::FAIL), cred, binary) + }); + } + }, + } + } +} + +impl< + 'a, + S: hil::public_key_crypto::signature::SignatureVerify<'static, HL, SL>, + H: hil::digest::DigestDataHash<'a, HL>, + const HL: usize, + const SL: usize, + > hil::digest::ClientVerify for AppCheckerSignature<'a, S, H, HL, SL> +{ + fn verification_done(&self, _result: Result, _compare: &'static mut [u8; HL]) { + // Unused for this checker. + // Needed to make the sha256 client work. + } +} + +impl< + 'a, + S: hil::public_key_crypto::signature::SignatureVerify<'static, HL, SL>, + H: hil::digest::DigestDataHash<'a, HL>, + const HL: usize, + const SL: usize, + > hil::public_key_crypto::signature::ClientVerify + for AppCheckerSignature<'a, S, H, HL, SL> +{ + fn verification_done( + &self, + result: Result, + hash: &'static mut [u8; HL], + signature: &'static mut [u8; SL], + ) { + self.hash.replace(hash); + self.signature.replace(signature); + + self.client.map(|c| { + let binary = self.binary.take().unwrap(); + let cred = self.credentials.take().unwrap(); + let check_result = if result.unwrap_or(false) { + Ok(CheckResult::Accept) + } else { + Ok(CheckResult::Pass) + }; + + c.check_done(check_result, cred, binary) + }); + } +} + +impl< + 'a, + S: hil::public_key_crypto::signature::SignatureVerify<'static, HL, SL>, + H: hil::digest::DigestDataHash<'a, HL>, + const HL: usize, + const SL: usize, + > AppCredentialsPolicy<'static> for AppCheckerSignature<'a, S, H, HL, SL> +{ + fn require_credentials(&self) -> bool { + true + } + + fn check_credentials( + &self, + credentials: TbfFooterV2Credentials, + binary: &'static [u8], + ) -> Result<(), (ErrorCode, TbfFooterV2Credentials, &'static [u8])> { + self.credentials.set(credentials); + + if credentials.format() == self.credential_type { + // Save the signature we are trying to compare with. + self.signature.map(|b| { + b.as_mut_slice()[..SL].copy_from_slice(&credentials.data()[..SL]); + }); + + // Add the process binary to compute the hash. + self.hasher.clear_data(); + match self.hasher.add_data(SubSlice::new(binary)) { + Ok(()) => Ok(()), + Err((e, b)) => Err((e, credentials, b.take())), + } + } else { + Err((ErrorCode::NOSUPPORT, credentials, binary)) + } + } + + fn set_client(&self, client: &'static dyn AppCredentialsPolicyClient<'static>) { + self.client.replace(client); + } +} diff --git a/capsules/system/src/process_checker/tbf.rs b/capsules/system/src/process_checker/tbf.rs new file mode 100644 index 0000000000..05c4c31ed5 --- /dev/null +++ b/capsules/system/src/process_checker/tbf.rs @@ -0,0 +1,46 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! AppID mechanisms based on TBF headers. + +use kernel::process::{Process, ProcessBinary, ShortId}; +use kernel::process_checker::{AppUniqueness, Compress}; + +/// Assign AppIDs based on fields in the app's TBF header. +/// +/// This uses the ShortId TBF header to assign short IDs to applications. If the +/// header is not present the application will be assigned a +/// `ShortID::LocallyUnique` ID. +/// +/// This assigner uses ShortIds as the AppID, so the built-in check for ShortId +/// uniqueness is sufficient. +pub struct AppIdAssignerTbfHeader {} + +impl AppUniqueness for AppIdAssignerTbfHeader { + fn different_identifier(&self, _process_a: &ProcessBinary, _process_b: &ProcessBinary) -> bool { + true + } + + fn different_identifier_process( + &self, + _process_binary: &ProcessBinary, + _process: &dyn Process, + ) -> bool { + true + } + + fn different_identifier_processes( + &self, + _process_a: &dyn Process, + _process_b: &dyn Process, + ) -> bool { + true + } +} + +impl Compress for AppIdAssignerTbfHeader { + fn to_short_id(&self, process: &ProcessBinary) -> ShortId { + process.header.get_fixed_short_id().into() + } +} diff --git a/capsules/system/src/process_policies.rs b/capsules/system/src/process_policies.rs new file mode 100644 index 0000000000..edf11f2529 --- /dev/null +++ b/capsules/system/src/process_policies.rs @@ -0,0 +1,115 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Process policy implementations for the Tock kernel. +//! +//! This file contains implementations of policies the Tock kernel can use when +//! managing processes. For example, these policies control decisions such as +//! whether a specific process should be restarted. + +use kernel::process; +use kernel::process::Process; +use kernel::process::ProcessFaultPolicy; + +/// Simply panic the entire board if a process faults. +pub struct PanicFaultPolicy {} + +impl ProcessFaultPolicy for PanicFaultPolicy { + fn action(&self, _: &dyn Process) -> process::FaultAction { + process::FaultAction::Panic + } +} + +/// Simply stop the process and no longer schedule it if a process faults. +pub struct StopFaultPolicy {} + +impl ProcessFaultPolicy for StopFaultPolicy { + fn action(&self, _: &dyn Process) -> process::FaultAction { + process::FaultAction::Stop + } +} + +/// Stop the process and no longer schedule it if a process faults, but also +/// print a debug message notifying the user that the process faulted and +/// stopped. +pub struct StopWithDebugFaultPolicy {} + +impl ProcessFaultPolicy for StopWithDebugFaultPolicy { + fn action(&self, process: &dyn Process) -> process::FaultAction { + kernel::debug!( + "Process {} faulted and was stopped.", + process.get_process_name() + ); + process::FaultAction::Stop + } +} + +/// Always restart the process if it faults. +pub struct RestartFaultPolicy {} + +impl ProcessFaultPolicy for RestartFaultPolicy { + fn action(&self, _: &dyn Process) -> process::FaultAction { + process::FaultAction::Restart + } +} + +/// Always restart the process if it faults, but print a debug message: +pub struct RestartWithDebugFaultPolicy {} + +impl ProcessFaultPolicy for RestartWithDebugFaultPolicy { + fn action(&self, process: &dyn Process) -> process::FaultAction { + kernel::debug!( + "Process {} faulted and will be restarted.", + process.get_process_name() + ); + process::FaultAction::Restart + } +} + +/// Implementation of `ProcessFaultPolicy` that uses a threshold to decide +/// whether to restart a process when it faults. If the process has been +/// restarted more times than the threshold then the process will be stopped +/// and no longer scheduled. +pub struct ThresholdRestartFaultPolicy { + threshold: usize, +} + +impl ThresholdRestartFaultPolicy { + pub const fn new(threshold: usize) -> ThresholdRestartFaultPolicy { + ThresholdRestartFaultPolicy { threshold } + } +} + +impl ProcessFaultPolicy for ThresholdRestartFaultPolicy { + fn action(&self, process: &dyn Process) -> process::FaultAction { + if process.get_restart_count() <= self.threshold { + process::FaultAction::Restart + } else { + process::FaultAction::Stop + } + } +} + +/// Implementation of `ProcessFaultPolicy` that uses a threshold to decide +/// whether to restart a process when it faults. If the process has been +/// restarted more times than the threshold then the board will panic. +pub struct ThresholdRestartThenPanicFaultPolicy { + threshold: usize, +} + +impl ThresholdRestartThenPanicFaultPolicy { + pub const fn new(threshold: usize) -> ThresholdRestartThenPanicFaultPolicy { + ThresholdRestartThenPanicFaultPolicy { threshold } + } +} + +impl ProcessFaultPolicy for ThresholdRestartThenPanicFaultPolicy { + fn action(&self, process: &dyn Process) -> process::FaultAction { + if process.get_restart_count() <= self.threshold { + process::FaultAction::Restart + } else { + process::FaultAction::Panic + } + } +} diff --git a/capsules/system/src/process_printer.rs b/capsules/system/src/process_printer.rs new file mode 100644 index 0000000000..04ea8ce833 --- /dev/null +++ b/capsules/system/src/process_printer.rs @@ -0,0 +1,252 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Tools for displaying process state. + +use core::fmt::Write; + +use kernel::process::Process; +use kernel::process::{ProcessPrinter, ProcessPrinterContext}; +use kernel::utilities::binary_write::BinaryWrite; +use kernel::utilities::binary_write::WriteToBinaryOffsetWrapper; + +/// A Process Printer that displays a process as a human-readable string. +pub struct ProcessPrinterText {} + +impl ProcessPrinterText { + pub fn new() -> ProcessPrinterText { + ProcessPrinterText {} + } +} + +impl ProcessPrinter for ProcessPrinterText { + // `print_overview()` must be synchronous, but does not assume a synchronous + // writer or an infinite (or very large) underlying buffer in the writer. To + // do this, this implementation assumes the underlying writer _is_ + // synchronous. This makes the printing code cleaner, as it does not need to + // be broken up into chunks of some length (which would need to match the + // underlying buffer length). However, not all writers are synchronous, so + // this implementation keeps track of how many bytes were sent on the last + // call, and only prints new bytes on the next call. This works by having + // the function start from the beginning each time, formats the entire + // overview message, and just drops bytes until getting back to where it + // left off on the last call. + // + // ### Assumptions + // + // This implementation makes two assumptions: + // 1. That `print_overview()` is not called in performance-critical code. + // Since each time it formats and "prints" the message starting from the + // beginning, it duplicates a fair bit of formatting work. Since this is + // for debugging, the performance impact of that shouldn't matter. + // 2. That `printer_overview()` will be called in a tight loop, and no + // process state will change between calls. That could change the length + // of the printed message, and lead to gaps or parts of the overview + // being duplicated. However, it does not make sense that the kernel + // would want to run the process while it is displaying debugging + // information about it, so this should be a safe assumption. + fn print_overview( + &self, + process: &dyn Process, + writer: &mut dyn BinaryWrite, + context: Option, + ) -> Option { + let offset = context.map_or(0, |c| c.offset); + + // Process statistics + let events_queued = process.pending_tasks(); + let syscall_count = process.debug_syscall_count(); + let dropped_upcall_count = process.debug_dropped_upcall_count(); + let restart_count = process.get_restart_count(); + + let addresses = process.get_addresses(); + let sizes = process.get_sizes(); + + let process_struct_memory_location = addresses.sram_end + - sizes.grant_pointers + - sizes.upcall_list + - sizes.process_control_block; + let sram_grant_size = process_struct_memory_location - addresses.sram_grant_start; + + let mut bww = WriteToBinaryOffsetWrapper::new(writer); + bww.set_offset(offset); + + let _ = bww.write_fmt(format_args!( + "\ + 𝐀𝐩𝐩: {} - [{:?}]\ + \r\n Events Queued: {} Syscall Count: {} Dropped Upcall Count: {}\ + \r\n Restart Count: {}\ + \r\n", + process.get_process_name(), + process.get_state(), + events_queued, + syscall_count, + dropped_upcall_count, + restart_count, + )); + + let _ = match process.debug_syscall_last() { + Some(syscall) => bww.write_fmt(format_args!(" Last Syscall: {:?}\r\n", syscall)), + None => bww.write_str(" Last Syscall: None\r\n"), + }; + + let _ = match process.get_completion_code() { + Some(opt_cc) => match opt_cc { + Some(cc) => bww.write_fmt(format_args!(" Completion Code: {}\r\n", cc as isize)), + None => bww.write_str(" Completion Code: Faulted\r\n"), + }, + None => bww.write_str(" Completion Code: None\r\n"), + }; + + let _ = bww.write_fmt(format_args!( + "\ + \r\n\ + \r\n ╔═══════════╤══════════════════════════════════════════╗\ + \r\n ║ Address │ Region Name Used | Allocated (bytes) ║\ + \r\n ╚{:#010X}═╪══════════════════════════════════════════╝\ + \r\n │ Grant Ptrs {:6}\ + \r\n │ Upcalls {:6}\ + \r\n │ Process {:6}\ + \r\n {:#010X} ┼───────────────────────────────────────────\ + \r\n │ ▼ Grant {:6}\ + \r\n {:#010X} ┼───────────────────────────────────────────\ + \r\n │ Unused\ + \r\n {:#010X} ┼───────────────────────────────────────────", + addresses.sram_end, + sizes.grant_pointers, + sizes.upcall_list, + sizes.process_control_block, + process_struct_memory_location, + sram_grant_size, + addresses.sram_grant_start, + addresses.sram_app_brk, + )); + + // We check to see if the underlying writer has more work to do. If it + // does, then its buffer is full and any additional writes are just + // going to be dropped. So, we skip doing more printing if there are + // bytes remaining as a slight performance optimization. + if !bww.bytes_remaining() { + match addresses.sram_heap_start { + Some(sram_heap_start) => { + let sram_heap_size = addresses.sram_app_brk - sram_heap_start; + let sram_heap_allocated = addresses.sram_grant_start - sram_heap_start; + + let _ = bww.write_fmt(format_args!( + "\ + \r\n │ ▲ Heap {:6} | {:6}{} S\ + \r\n {:#010X} ┼─────────────────────────────────────────── R", + sram_heap_size, + sram_heap_allocated, + exceeded_check(sram_heap_size, sram_heap_allocated), + sram_heap_start, + )); + } + None => { + let _ = bww.write_str( + "\ + \r\n │ ▲ Heap ? | ? S\ + \r\n ?????????? ┼─────────────────────────────────────────── R", + ); + } + } + } + + if !bww.bytes_remaining() { + match (addresses.sram_heap_start, addresses.sram_stack_top) { + (Some(sram_heap_start), Some(sram_stack_top)) => { + let sram_data_size = sram_heap_start - sram_stack_top; + let sram_data_allocated = sram_data_size; + + let _ = bww.write_fmt(format_args!( + "\ + \r\n │ Data {:6} | {:6} A", + sram_data_size, sram_data_allocated, + )); + } + _ => { + let _ = bww.write_str( + "\ + \r\n │ Data ? | ? A", + ); + } + } + } + + if !bww.bytes_remaining() { + match (addresses.sram_stack_top, addresses.sram_stack_bottom) { + (Some(sram_stack_top), Some(sram_stack_bottom)) => { + let sram_stack_size = sram_stack_top - sram_stack_bottom; + let sram_stack_allocated = sram_stack_top - addresses.sram_start; + + let _ = bww.write_fmt(format_args!( + "\ + \r\n {:#010X} ┼─────────────────────────────────────────── M\ + \r\n │ ▼ Stack {:6} | {:6}{}", + sram_stack_top, + sram_stack_size, + sram_stack_allocated, + exceeded_check(sram_stack_size, sram_stack_allocated), + )); + } + _ => { + let _ = bww.write_str( + "\ + \r\n ?????????? ┼─────────────────────────────────────────── M\ + \r\n │ ▼ Stack ? | ?", + ); + } + } + } + + if !bww.bytes_remaining() { + let flash_protected_size = addresses.flash_non_protected_start - addresses.flash_start; + let flash_app_size = addresses.flash_end - addresses.flash_non_protected_start; + + let _ = bww.write_fmt(format_args!( + "\ + \r\n {:#010X} ┼───────────────────────────────────────────\ + \r\n │ Unused\ + \r\n {:#010X} ┴───────────────────────────────────────────\ + \r\n .....\ + \r\n {:#010X} ┬─────────────────────────────────────────── F\ + \r\n │ App Flash {:6} L\ + \r\n {:#010X} ┼─────────────────────────────────────────── A\ + \r\n │ Protected {:6} S\ + \r\n {:#010X} ┴─────────────────────────────────────────── H\ + \r\n", + addresses.sram_stack_bottom.unwrap_or(0), + addresses.sram_start, + addresses.flash_end, + flash_app_size, + addresses.flash_non_protected_start, + flash_protected_size, + addresses.flash_start + )); + } + + if bww.bytes_remaining() { + // The underlying writer is indicating there are still bytes + // remaining to be sent. That means we want to return a context so + // the caller knows to call us again and we can keep printing until + // we have displayed the entire process overview. + let new_context = ProcessPrinterContext { + offset: bww.get_index(), + }; + Some(new_context) + } else { + None + } + } +} + +/// If `size` is greater than `allocated` then it returns a warning string to +/// help with debugging. +fn exceeded_check(size: usize, allocated: usize) -> &'static str { + if size > allocated { + " EXCEEDED!" + } else { + " " + } +} diff --git a/chips/README.md b/chips/README.md index 3fd0512aff..44548ec527 100644 --- a/chips/README.md +++ b/chips/README.md @@ -11,56 +11,64 @@ HIL Support -| HIL | apollo3 | arty_e21_chip | e310x | earlgrey | esp32 | esp32-c3 | imxrt10xx | litex | litex_vexriscv | lowrisc | msp432 | nrf52832 | nrf52833 | nrf52840 | rp2040 | sam4l | stm32f303xc | stm32f401cc | stm32f412g | stm32f429zi | stm32f446re | stm32f4xx | swerv | swervolf-eh1 | -|-----------------------------------------|---------|---------------|-------|----------|-------|----------|-----------|-------|----------------|---------|--------|----------|----------|----------|--------|-------|-------------|-------------|------------|-------------|-------------|-----------|-------|--------------| -| adc::Adc | | | | | | | | | | | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | | | | | ✓ | | | -| adc::AdcHighSpeed | | | | | | | | | | | ✓ | | | | | ✓ | ✓ | | | | | ✓ | | | -| analog_comparator::AnalogComparator | | | | | | | | | | | | ✓ | | ✓ | | ✓ | | | | | | | | | -| ble_advertising::BleAdvertisementDriver | ✓ | | | | | | | | | | | ✓ | | ✓ | | | | | | | | | | | -| ble_advertising::BleConfig | ✓ | | | | | | | | | | | ✓ | | ✓ | | | | | | | | | | | -| bus8080::Bus8080 | | | | | | | | | | | | | | | | | | | | | | ✓ | | | -| crc::Crc | | | | | | | | | | | | | | | | ✓ | | | | | | | | | -| dac::DacChannel | | | | | | | | | | | | | | | | ✓ | | | | | | | | | -| digest::Digest | | | | | | | | | | ✓ | | | | | | | | | | | | | | | -| digest::HMACSha256 | | | | | | | | | | ✓ | | | | | | | | | | | | | | | -| digest::HMACSha384 | | | | | | | | | | ✓ | | | | | | | | | | | | | | | -| digest::HMACSha512 | | | | | | | | | | ✓ | | | | | | | | | | | | | | | -| digest::Sha256 | | | | | | | | | | ✓ | | | | | | | | | | | | | | | -| digest::Sha384 | | | | | | | | | | ✓ | | | | | | | | | | | | | | | -| digest::Sha512 | | | | | | | | | | ✓ | | | | | | | | | | | | | | | -| eic::ExternalInterruptController | | | | | | | | | | | | | | | | ✓ | | | | | | | | | -| entropy::Entropy32 | | | | | | | | | | | | ✓ | | ✓ | | ✓ | | | | | | ✓ | | | -| flash::Flash | | | | | | | | | | ✓ | | ✓ | | ✓ | | ✓ | ✓ | | | | | | | | -| gpio::Input | ✓ | | ✓ | | ✓ | | ✓ | | | ✓ | | ✓ | | ✓ | ✓ | ✓ | ✓ | | | | | ✓ | | | -| gpio::Interrupt | ✓ | | ✓ | | ✓ | | ✓ | | | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | | | | | ✓ | | | -| gpio::Output | ✓ | | ✓ | | ✓ | | ✓ | | | ✓ | | ✓ | | ✓ | ✓ | ✓ | ✓ | | | | | ✓ | | | -| gpio::Pin | | | | | | | | | | | | ✓ | | ✓ | | | | | | | | | | | -| i2c::I2CMaster | ✓ | | | | | | ✓ | | | ✓ | ✓ | ✓ | | ✓ | | ✓ | ✓ | | | | | ✓ | | | -| i2c::I2CSlave | | | | | | | | | | | | ✓ | | ✓ | | ✓ | | | | | | | | | -| i2c::SMBusMaster | ✓ | | | | | | | | | | | | | | | | | | | | | | | | -| led::Led | | | | | | | | ✓ | | | | | | | | | | | | | | | | | -| mod::Controller | | | | | | | | | | | | | | | | ✓ | | | | | | | | | -| pwm::Pwm | | | | | | | | | | | | ✓ | | ✓ | | | | | | | | | | | -| radio::RadioConfig | | | | | | | | | | | | ✓ | | ✓ | | | | | | | | | | | -| radio::RadioData | | | | | | | | | | | | ✓ | | ✓ | | | | | | | | | | | -| sensors::TemperatureDriver | | | | | | | | | | | | ✓ | | ✓ | | | | | | | | | | | -| spi::SpiMaster | | | | | | | | | | | | ✓ | | ✓ | ✓ | ✓ | ✓ | | | | | ✓ | | | -| spi::SpiSlave | | | | | | | | | | | | | | | | ✓ | | | | | | | | | -| symmetric_encryption::AES128 | | | | ✓ | | | | | | | | ✓ | | ✓ | | ✓ | | | | | | | | | -| symmetric_encryption::AES128CBC | | | | ✓ | | | | | | | | ✓ | | ✓ | | ✓ | | | | | | | | | -| symmetric_encryption::AES128CCM | | | | | | | | | | | | ✓ | | ✓ | | | | | | | | | | | -| symmetric_encryption::AES128Ctr | | | | ✓ | | | | | | | | ✓ | | ✓ | | ✓ | | | | | | | | | -| symmetric_encryption::AES128ECB | | | | ✓ | | | | | | | | | | | | | | | | | | | | | -| time::Alarm | ✓ | | ✓ | ✓ | ✓ | | ✓ | | | | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | | | | | ✓ | ✓ | ✓ | -| time::Counter | ✓ | | | ✓ | ✓ | | | | | | ✓ | ✓ | | ✓ | | ✓ | ✓ | | | | | ✓ | ✓ | ✓ | -| time::Frequency | | | | ✓ | ✓ | | ✓ | ✓ | | | ✓ | | | | | | | | | | | | ✓ | ✓ | -| time::Time | ✓ | | ✓ | ✓ | ✓ | | ✓ | ✓ | | | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | | | | | ✓ | ✓ | ✓ | -| time::Timer | | | | | | | | ✓ | | | | | | | | | | | | | | | | | -| uart::Configure | ✓ | | ✓ | | ✓ | | ✓ | ✓ | | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | | | | | ✓ | | ✓ | -| uart::Receive | ✓ | | ✓ | | ✓ | | ✓ | ✓ | | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | | | | | ✓ | | ✓ | -| uart::ReceiveAdvanced | | | | | | | | | | | | | | | | ✓ | | | | | | | | | -| uart::Transmit | ✓ | | ✓ | | ✓ | | ✓ | ✓ | | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | | | | | ✓ | | ✓ | -| usb::UsbController | | | | | | | | | | ✓ | | ✓ | | ✓ | | ✓ | | | | | | | | | +| HIL | apollo3 | arty_e21_chip | e310_g002 | e310_g003 | earlgrey | esp32-c3 | imxrt10xx | litex_vexriscv | lowrisc | msp432 | nrf52832 | nrf52833 | nrf52840 | qemu_rv32_virt_chip | rp2040 | sam4l | stm32f303xc | stm32f401cc | stm32f412g | stm32f429zi | stm32f446re | swervolf-eh1 | +|-----------------------------------------|---------|---------------|-----------|-----------|----------|----------|-----------|----------------|---------|--------|----------|----------|----------|---------------------|--------|-------|-------------|-------------|------------|-------------|-------------|--------------| +| adc::Adc | | | | | | | | | | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| adc::AdcHighSpeed | | | | | | | | | | ✓ | ✓ | ✓ | ✓ | | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| analog_comparator::AnalogComparator | | | | | | | | | | | ✓ | ✓ | ✓ | | | ✓ | | | | | | | +| ble_advertising::BleAdvertisementDriver | ✓ | | | | | | | | | | ✓ | ✓ | ✓ | | | | | | | | | | +| ble_advertising::BleConfig | ✓ | | | | | | | | | | ✓ | ✓ | ✓ | | | | | | | | | | +| bus8080::Bus8080 | | | | | | | | | | | | | | | | | | ✓ | ✓ | ✓ | ✓ | | +| can::Configure | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| can::Controller | | | | | | | | | | | | | | | | ✓ | | ✓ | ✓ | ✓ | ✓ | | +| can::Receive | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| can::Transmit | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| crc::Crc | | | | | | | | | | | | | | | | ✓ | | | | | | | +| dac::DacChannel | | | | | | | | | | | | | | | | ✓ | | ✓ | ✓ | ✓ | ✓ | | +| date_time::DateTime | | | | | | | | | | | | | | | ✓ | | | | | ✓ | | | +| digest::Digest | | | | | | | | | ✓ | | | | | | | | | | | | | | +| digest::DigestData | | | | | | | | | ✓ | | | | | | | | | | | | | | +| digest::DigestHash | | | | | | | | | ✓ | | | | | | | | | | | | | | +| digest::DigestVerify | | | | | | | | | ✓ | | | | | | | | | | | | | | +| digest::HmacSha256 | | | | | | | | | ✓ | | | | | | | | | | | | | | +| digest::HmacSha384 | | | | | | | | | ✓ | | | | | | | | | | | | | | +| digest::HmacSha512 | | | | | | | | | ✓ | | | | | | | | | | | | | | +| digest::Sha256 | | | | | | | | | ✓ | | | | | | | | | | | | | | +| digest::Sha384 | | | | | | | | | ✓ | | | | | | | | | | | | | | +| digest::Sha512 | | | | | | | | | ✓ | | | | | | | | | | | | | | +| eic::ExternalInterruptController | | | | | | | | | | | | | | | | ✓ | | | | | | | +| entropy::Entropy32 | | | | | | ✓ | | | ✓ | | ✓ | ✓ | ✓ | | | ✓ | | ✓ | ✓ | ✓ | ✓ | | +| flash::Flash | | | | | | | | | ✓ | | ✓ | ✓ | ✓ | | | ✓ | ✓ | | | | | | +| gpio::Input | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| gpio::Interrupt | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| gpio::Output | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| gpio::Pin | | | | | | | | | | | ✓ | ✓ | ✓ | | | | | | | | | | +| i2c::I2CMaster | ✓ | | | | | | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| i2c::I2CMasterSlave | | | | | | | | | | | ✓ | ✓ | ✓ | | | | | | | | | | +| i2c::I2CSlave | ✓ | | | | | | | | | | ✓ | ✓ | ✓ | | | ✓ | | | | | | | +| i2c::SMBusMaster | ✓ | | | | | | | | | | | | | | | | | | | | | | +| led::Led | | | | | | | | ✓ | | | | | | | | | | | | | | | +| pwm::Pwm | | | | | | | | | | | ✓ | ✓ | ✓ | | ✓ | | | | | | | | +| pwm::PwmPin | | | | | | | | | | | | | | | ✓ | | | | | | | | +| radio::RadioConfig | | | | | | | | | | | | | ✓ | | | | | | | | | | +| radio::RadioData | | | | | | | | | | | | | ✓ | | | | | | | | | | +| rng::Rng | | | | | | | | | | | | | | ✓ | | | | | | | | | +| rsa_math::RsaCryptoBase | | | | | | | | | ✓ | | | | | | | | | | | | | | +| sensors::TemperatureDriver | | | | | | | | | | | ✓ | ✓ | ✓ | | | | | | | | | | +| spi::SpiMaster | ✓ | | | | | | | | ✓ | | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | +| spi::SpiSlave | | | | | | | | | | | | | | | | ✓ | | | | | | | +| symmetric_encryption::AES128 | | | | | ✓ | | | | | | ✓ | ✓ | ✓ | | | ✓ | | | | | | | +| symmetric_encryption::AES128CBC | | | | | ✓ | | | | | | ✓ | ✓ | ✓ | | | ✓ | | | | | | | +| symmetric_encryption::AES128CCM | | | | | | | | | | | ✓ | ✓ | ✓ | | | | | | | | | | +| symmetric_encryption::AES128Ctr | | | | | ✓ | | | | | | ✓ | ✓ | ✓ | | | ✓ | | | | | | | +| symmetric_encryption::AES128ECB | | | | | ✓ | | | | | | ✓ | ✓ | ✓ | | | ✓ | | | | | | | +| time::Alarm | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| time::Counter | ✓ | | | | ✓ | ✓ | | | | ✓ | ✓ | ✓ | ✓ | | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| time::Frequency | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | ✓ | | | | ✓ | | | | | | | | ✓ | +| time::Time | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | +| time::Timer | | | | | | | | ✓ | | | | | | | | | | | | | | | +| uart::ReceiveAdvanced | | | | | | | | | ✓ | | | | | | | ✓ | | | | | | | +| usb::UsbController | | | | | | | | | ✓ | | ✓ | ✓ | ✓ | | ✓ | ✓ | | | | | | | diff --git a/chips/apollo3/Cargo.toml b/chips/apollo3/Cargo.toml index 1e2af8a8d6..1ef0007aca 100644 --- a/chips/apollo3/Cargo.toml +++ b/chips/apollo3/Cargo.toml @@ -1,10 +1,17 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "apollo3" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] cortexm4 = { path = "../../arch/cortex-m4" } enum_primitive = { path = "../../libraries/enum_primitive" } kernel = { path = "../../kernel" } + +[lints] +workspace = true diff --git a/chips/apollo3/src/ble.rs b/chips/apollo3/src/ble.rs index 81f4e57323..a4cf3b9513 100644 --- a/chips/apollo3/src/ble.rs +++ b/chips/apollo3/src/ble.rs @@ -1,6 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! BLE driver. use core::cell::Cell; +use core::ptr::addr_of_mut; use kernel::hil::ble_advertising; use kernel::hil::ble_advertising::RadioChannel; use kernel::utilities::cells::OptionalCell; @@ -268,7 +273,7 @@ pub struct Ble<'a> { } impl<'a> Ble<'a> { - pub const fn new() -> Self { + pub fn new() -> Self { Self { registers: BLE_BASE, rx_client: OptionalCell::empty(), @@ -413,10 +418,10 @@ impl<'a> Ble<'a> { PAYLOAD[i + 2] = temp[2]; PAYLOAD[i + 3] = temp[3]; - i = i + 4; + i += 4; } - client.receive_event(&mut PAYLOAD, 10, Ok(())); + client.receive_event(&mut *addr_of_mut!(PAYLOAD), 10, Ok(())); } }); } @@ -448,7 +453,7 @@ impl<'a> ble_advertising::BleAdvertisementDriver<'a> for Ble<'a> { // Setup all of the buffers self.buffer.replace(res); - self.write_len.set(len as usize); + self.write_len.set(len); self.read_len.set(0); self.read_index.set(0); diff --git a/chips/apollo3/src/cachectrl.rs b/chips/apollo3/src/cachectrl.rs index d9605735b7..4fed44778a 100644 --- a/chips/apollo3/src/cachectrl.rs +++ b/chips/apollo3/src/cachectrl.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Cache Control driver. use kernel::utilities::registers::interfaces::Writeable; diff --git a/chips/apollo3/src/chip.rs b/chips/apollo3/src/chip.rs index fd2a94da07..94b7df6dba 100644 --- a/chips/apollo3/src/chip.rs +++ b/chips/apollo3/src/chip.rs @@ -1,17 +1,21 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Chip trait setup. use core::fmt::Write; -use cortexm4; +use cortexm4::{CortexM4, CortexMVariant}; use kernel::platform::chip::Chip; use kernel::platform::chip::InterruptService; -pub struct Apollo3 + 'static> { +pub struct Apollo3 { mpu: cortexm4::mpu::MPU, userspace_kernel_boundary: cortexm4::syscall::SysCall, interrupt_service: &'static I, } -impl + 'static> Apollo3 { +impl Apollo3 { pub unsafe fn new(interrupt_service: &'static I) -> Self { Self { mpu: cortexm4::mpu::MPU::new(), @@ -36,6 +40,7 @@ pub struct Apollo3DefaultPeripherals { pub iom3: crate::iom::Iom<'static>, pub iom4: crate::iom::Iom<'static>, pub iom5: crate::iom::Iom<'static>, + pub ios: crate::ios::Ios<'static>, pub ble: crate::ble::Ble<'static>, } @@ -52,12 +57,13 @@ impl Apollo3DefaultPeripherals { iom3: crate::iom::Iom::new3(), iom4: crate::iom::Iom::new4(), iom5: crate::iom::Iom::new5(), + ios: crate::ios::Ios::new(), ble: crate::ble::Ble::new(), } } } -impl kernel::platform::chip::InterruptService<()> for Apollo3DefaultPeripherals { +impl kernel::platform::chip::InterruptService for Apollo3DefaultPeripherals { unsafe fn service_interrupt(&self, interrupt: u32) -> bool { use crate::nvic; match interrupt { @@ -71,17 +77,15 @@ impl kernel::platform::chip::InterruptService<()> for Apollo3DefaultPeripherals nvic::IOMSTR3 => self.iom3.handle_interrupt(), nvic::IOMSTR4 => self.iom4.handle_interrupt(), nvic::IOMSTR5 => self.iom5.handle_interrupt(), + nvic::IOSLAVE | nvic::IOSLAVEACC => self.ios.handle_interrupt(), nvic::BLE => self.ble.handle_interrupt(), _ => return false, } true } - unsafe fn service_deferred_call(&self, _: ()) -> bool { - false - } } -impl + 'static> Chip for Apollo3 { +impl Chip for Apollo3 { type MPU = cortexm4::mpu::MPU; type UserspaceKernelBoundary = cortexm4::syscall::SysCall; @@ -130,6 +134,6 @@ impl + 'static> Chip for Apollo3 { } unsafe fn print_state(&self, write: &mut dyn Write) { - cortexm4::print_cortexm4_state(write); + CortexM4::print_cortexm_state(write); } } diff --git a/chips/apollo3/src/clkgen.rs b/chips/apollo3/src/clkgen.rs index b8b5a375f8..e3268dd34f 100644 --- a/chips/apollo3/src/clkgen.rs +++ b/chips/apollo3/src/clkgen.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Power Reset Clock Interrupt controller driver. use kernel::utilities::registers::interfaces::{ReadWriteable, Writeable}; diff --git a/chips/apollo3/src/gpio.rs b/chips/apollo3/src/gpio.rs index 26041b2eb3..a68d7ee10f 100644 --- a/chips/apollo3/src/gpio.rs +++ b/chips/apollo3/src/gpio.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! General Purpose Input/Output driver. use core::ops::{Index, IndexMut}; @@ -112,7 +116,9 @@ impl Port<'_> { let mut count = 0; while irqs != 0 && count < self.pins.len() { if (irqs & 0b1) != 0 { - self.pins[count].handle_interrupt(); + // Offset the count by 32 as it's the second GPIO bank + // (top 32 GPIOs) + self.pins[count + 32].handle_interrupt(); } count += 1; irqs >>= 1; @@ -139,8 +145,16 @@ impl Port<'_> { match rx_pin.pin as usize { 49 => { regs.padkey.set(115); - regs.padreg[12].modify(PADREG::PAD1INPEN::SET); - regs.cfg[6].modify(CFG::GPIO1INTD.val(0x00) + CFG::GPIO1OUTCFG.val(0x00)); + regs.padreg[12].modify( + PADREG::PAD1PULL::CLEAR + + PADREG::PAD1INPEN::SET + + PADREG::PAD1STRNG::CLEAR + + PADREG::PAD1FNCSEL::CLEAR + + PADREG::PAD1RSEL::CLEAR, + ); + regs.cfg[6].modify( + CFG::GPIO1INCFG::CLEAR + CFG::GPIO1INTD.val(0x00) + CFG::GPIO1OUTCFG.val(0x00), + ); regs.altpadcfgm .modify(ALTPADCFG::PAD1_DS1::CLEAR + ALTPADCFG::PAD1_SR::CLEAR); regs.padkey.set(0x00); @@ -151,10 +165,114 @@ impl Port<'_> { } } + /// This function configures some GPIO pins on the Apollo3 to allow + /// communication with a SX1262 LoRa module. + /// + /// The pin mapping is setup to match what is used by the NM180100 SoC (shown below) + /// + /// IOM3: Semtech SX1262 + /// Apollo 3 Pin Number | Apollo 3 Name | SX1262 Pin Number | SX1262 Name | SX1262 Description + /// H6 | GPIO 36 | 19 | NSS | SPI slave select + /// J6 | GPIO 38 | 17 | MOSI | SPI slave input + /// J5 | GPIO 43 | 16 | MISO | SPI slave output + /// H5 | GPIO 42 | 18 | SCK | SPI clock input + /// J8 | GPIO 39 | 14 | BUSY | Radio busy indicator + /// J9 | GPIO 40 | 13 | DIO1 | Multipurpose digital I/O + /// H9 | GPIO 47 | 6 | DIO3 | Multipurpose digital I/O + /// J7 | GPIO 44 | 15 | NRESET | Radio reset signal, active low + /// + /// This should be used by the lora_things_plus board or any other board using + /// the Apollo3 based NM180100 SoC. This function would also work for any + /// Apollo3 board using the same pins and IOM as specified above. + pub fn enable_sx1262_radio_pins(&self) { + let regs = GPIO_BASE; + + regs.padkey.set(115); + + // Pin 36 NSS + regs.padreg[9].modify( + PADREG::PAD0PULL::CLEAR + PADREG::PAD0INPEN::CLEAR + PADREG::PAD0FNCSEL.val(0x1), + ); + regs.cfg[4].modify(CFG::GPIO4INCFG.val(0x00) + CFG::GPIO4OUTCFG.val(0x00)); + regs.altpadcfgj + .modify(ALTPADCFG::PAD0_DS1::CLEAR + ALTPADCFG::PAD0_SR::CLEAR); + + // Pin 39 Busy + regs.padreg[9].modify( + PADREG::PAD3INPEN::SET + + PADREG::PAD3STRNG::CLEAR + + PADREG::PAD3FNCSEL.val(0x3) + + PADREG::PAD3RSEL.val(0x0), + ); + regs.cfg[4].modify( + CFG::GPIO7INCFG.val(0x00) + CFG::GPIO7OUTCFG.val(0x00) + CFG::GPIO7INTD.val(0x00), + ); + regs.altpadcfgj + .modify(ALTPADCFG::PAD3_DS1::CLEAR + ALTPADCFG::PAD3_SR::CLEAR); + + // Pin 40 DIO1 + regs.padreg[10].modify( + PADREG::PAD0PULL::CLEAR + + PADREG::PAD0INPEN::SET + + PADREG::PAD0STRING::CLEAR + + PADREG::PAD0FNCSEL.val(0x3) + + PADREG::PAD0RSEL.val(0x0), + ); + regs.cfg[5].modify( + CFG::GPIO0INCFG.val(0x00) + CFG::GPIO0OUTCFG.val(0x00) + CFG::GPIO0INTD.val(0x00), + ); + regs.altpadcfgk + .modify(ALTPADCFG::PAD0_DS1::CLEAR + ALTPADCFG::PAD0_SR::CLEAR); + + // Pin 47 DIO3 + regs.padreg[11].modify( + PADREG::PAD3PULL::CLEAR + + PADREG::PAD3INPEN::SET + + PADREG::PAD3STRNG::CLEAR + + PADREG::PAD3FNCSEL.val(0x3) + + PADREG::PAD3RSEL.val(0x0), + ); + regs.cfg[5].modify( + CFG::GPIO7INCFG.val(0x00) + CFG::GPIO7OUTCFG.val(0x00) + CFG::GPIO7INTD.val(0x00), + ); + regs.altpadcfgl + .modify(ALTPADCFG::PAD3_DS1::CLEAR + ALTPADCFG::PAD3_SR::CLEAR); + + // Pin 44 NReset + regs.padreg[11].modify( + PADREG::PAD0PULL::CLEAR + + PADREG::PAD0INPEN::CLEAR + + PADREG::PAD0STRING::CLEAR + + PADREG::PAD0FNCSEL.val(0x3) + + PADREG::PAD0RSEL.val(0x0), + ); + regs.cfg[5].modify( + CFG::GPIO4INCFG.val(0x00) + CFG::GPIO4OUTCFG.val(0x00) + CFG::GPIO4INTD.val(0x00), + ); + regs.altpadcfgl + .modify(ALTPADCFG::PAD0_DS1::CLEAR + ALTPADCFG::PAD0_SR::CLEAR); + + regs.padkey.set(0x00); + } + pub fn enable_i2c(&self, sda: &GpioPin, scl: &GpioPin) { let regs = GPIO_BASE; match sda.pin as usize { + 40 => { + regs.padkey.set(115); + regs.padreg[10].modify( + PADREG::PAD0PULL::SET + + PADREG::PAD0INPEN::SET + + PADREG::PAD0STRING::SET + + PADREG::PAD0FNCSEL.val(0x4) + + PADREG::PAD0RSEL.val(0x00), + ); + regs.cfg[5].modify(CFG::GPIO0INCFG.val(0x00) + CFG::GPIO0OUTCFG.val(0x02)); + regs.altpadcfgk + .modify(ALTPADCFG::PAD0_DS1::SET + ALTPADCFG::PAD0_DS1::CLEAR); + regs.padkey.set(0x00); + } 25 => { regs.padkey.set(115); regs.padreg[6].modify( @@ -168,12 +286,39 @@ impl Port<'_> { .modify(ALTPADCFG::PAD1_DS1::CLEAR + ALTPADCFG::PAD1_SR::CLEAR); regs.padkey.set(0x00); } + 6 => { + regs.padkey.set(115); + regs.padreg[1].modify( + PADREG::PAD2PULL::SET + + PADREG::PAD2INPEN::SET + + PADREG::PAD2STRNG::SET + + PADREG::PAD2FNCSEL.val(0x00), + ); + regs.cfg[0].modify(CFG::GPIO6OUTCFG.val(0x03) + CFG::GPIO6OUTCFG.val(0x00)); + regs.altpadcfgb + .modify(ALTPADCFG::PAD2_DS1::SET + ALTPADCFG::PAD2_SR::CLEAR); + regs.padkey.set(0x00); + } _ => { panic!("sda not supported"); } } match scl.pin as usize { + 39 => { + regs.padkey.set(115); + regs.padreg[9].modify( + PADREG::PAD3PULL::SET + + PADREG::PAD3INPEN::SET + + PADREG::PAD3STRNG::SET + + PADREG::PAD3FNCSEL.val(0x4) + + PADREG::PAD3RSEL.val(0x00), + ); + regs.cfg[4].modify(CFG::GPIO7INTD.val(0x00) + CFG::GPIO7OUTCFG.val(0x02)); + regs.altpadcfgj + .modify(ALTPADCFG::PAD3_DS1::SET + ALTPADCFG::PAD3_SR::CLEAR); + regs.padkey.set(0x00); + } 27 => { regs.padkey.set(115); regs.padreg[6].modify( @@ -187,11 +332,304 @@ impl Port<'_> { .modify(ALTPADCFG::PAD3_DS1::CLEAR + ALTPADCFG::PAD3_SR::CLEAR); regs.padkey.set(0x00); } + 5 => { + regs.padkey.set(115); + regs.padreg[1].modify( + PADREG::PAD1PULL::SET + + PADREG::PAD1INPEN::SET + + PADREG::PAD1STRNG::SET + + PADREG::PAD1FNCSEL.val(0x00) + + PADREG::PAD1RSEL.val(0x00), + ); + regs.cfg[0].modify(CFG::GPIO5OUTCFG.val(0x03) + CFG::GPIO1OUTCFG.val(0x00)); + regs.altpadcfgb + .modify(ALTPADCFG::PAD1_DS1::SET + ALTPADCFG::PAD1_SR::CLEAR); + regs.padkey.set(0x00); + } _ => { panic!("scl not supported"); } } } + + pub fn enable_i2c_slave(&self, sda: &GpioPin, scl: &GpioPin) { + let regs = GPIO_BASE; + + match sda.pin as usize { + 1 => { + regs.padkey.set(115); + regs.padreg[0].modify( + PADREG::PAD1PULL::SET + + PADREG::PAD1INPEN::SET + + PADREG::PAD1STRNG::CLEAR + + PADREG::PAD1FNCSEL.val(0x00) + + PADREG::PAD1RSEL.val(0x00), + ); + regs.cfg[0].modify( + CFG::GPIO1INCFG::CLEAR + CFG::GPIO1OUTCFG.val(0x02) + CFG::GPIO1INTD::CLEAR, + ); + regs.altpadcfga + .modify(ALTPADCFG::PAD1_DS1::CLEAR + ALTPADCFG::PAD1_SR::CLEAR); + regs.padkey.set(0x00); + } + _ => { + panic!("sda not supported"); + } + } + + match scl.pin as usize { + 0 => { + regs.padkey.set(115); + regs.padreg[0].modify( + PADREG::PAD0PULL::CLEAR + + PADREG::PAD0INPEN::SET + + PADREG::PAD0STRING::CLEAR + + PADREG::PAD0FNCSEL.val(0x0) + + PADREG::PAD0RSEL.val(0x0), + ); + regs.cfg[0].modify( + CFG::GPIO0INCFG::CLEAR + CFG::GPIO0OUTCFG.val(0x0) + CFG::GPIO0INTD::CLEAR, + ); + regs.altpadcfga + .modify(ALTPADCFG::PAD0_DS1::CLEAR + ALTPADCFG::PAD0_SR::CLEAR); + regs.padkey.set(0x00); + } + _ => { + panic!("scl not supported"); + } + } + } + + pub fn enable_spi(&self, sck: &GpioPin, mosi: &GpioPin, miso: &GpioPin) { + let regs = GPIO_BASE; + + match sck.pin as usize { + 5 => { + regs.padkey.set(115); + regs.padreg[1].modify( + PADREG::PAD1PULL::CLEAR + + PADREG::PAD1INPEN::SET + + PADREG::PAD1STRNG::SET + + PADREG::PAD1FNCSEL.val(0x1) + + PADREG::PAD1RSEL.val(0x00), + ); + regs.cfg[0].modify( + CFG::GPIO1INCFG.val(0x00) + + CFG::GPIO1OUTCFG.val(0x000) + + CFG::GPIO1INTD.val(0x00), + ); + regs.altpadcfgb + .modify(ALTPADCFG::PAD1_DS1::SET + ALTPADCFG::PAD1_SR::CLEAR); + regs.padkey.set(0x00); + } + 18 => { + regs.padkey.set(115); + regs.padreg[4].modify( + PADREG::PAD2PULL::CLEAR + + PADREG::PAD2INPEN::CLEAR + + PADREG::PAD2STRNG::SET + + PADREG::PAD2FNCSEL.val(0x5), + ); + regs.cfg[2].modify( + CFG::GPIO2INCFG.val(0x00) + + CFG::GPIO2OUTCFG.val(0x000) + + CFG::GPIO2INTD.val(0x00), + ); + regs.altpadcfge + .modify(ALTPADCFG::PAD2_DS1::SET + ALTPADCFG::PAD2_SR::CLEAR); + regs.padkey.set(0x00); + } + 27 => { + regs.padkey.set(115); + regs.padreg[6].modify( + PADREG::PAD3PULL::CLEAR + + PADREG::PAD3INPEN::SET + + PADREG::PAD3STRNG::SET + + PADREG::PAD3FNCSEL.val(0x5) + + PADREG::PAD3RSEL.val(0x00), + ); + regs.cfg[3].modify( + CFG::GPIO3INCFG.val(0x00) + + CFG::GPIO3OUTCFG.val(0x000) + + CFG::GPIO3INTD.val(0x00), + ); + regs.altpadcfgg + .modify(ALTPADCFG::PAD3_DS1::SET + ALTPADCFG::PAD3_SR::CLEAR); + regs.padkey.set(0x00); + } + 42 => { + regs.padkey.set(115); + regs.padreg[10].modify( + PADREG::PAD2PULL::CLEAR + + PADREG::PAD2INPEN::SET + + PADREG::PAD2STRNG::SET + + PADREG::PAD2FNCSEL.val(0x5), + ); + regs.cfg[5].modify( + CFG::GPIO2INCFG.val(0x00) + + CFG::GPIO2OUTCFG.val(0x000) + + CFG::GPIO2INTD.val(0x00), + ); + regs.altpadcfgk + .modify(ALTPADCFG::PAD2_DS1::SET + ALTPADCFG::PAD2_SR::CLEAR); + regs.padkey.set(0x00); + } + _ => { + panic!("sck not supported"); + } + } + + match mosi.pin as usize { + 7 => { + regs.padkey.set(115); + regs.padreg[1].modify( + PADREG::PAD3PULL::CLEAR + + PADREG::PAD3INPEN::CLEAR + + PADREG::PAD3STRNG::SET + + PADREG::PAD3FNCSEL.val(0x1) + + PADREG::PAD3RSEL.val(0x00), + ); + regs.cfg[0].modify( + CFG::GPIO4INCFG.val(0x00) + + CFG::GPIO4OUTCFG.val(0x000) + + CFG::GPIO4INTD.val(0x00), + ); + regs.altpadcfgb + .modify(ALTPADCFG::PAD3_DS1::SET + ALTPADCFG::PAD3_SR::CLEAR); + regs.padkey.set(0x00); + } + 17 => { + regs.padkey.set(115); + regs.padreg[4].modify( + PADREG::PAD2PULL::CLEAR + + PADREG::PAD2INPEN::SET + + PADREG::PAD2STRNG::CLEAR + + PADREG::PAD2FNCSEL.val(0x5), + ); + regs.cfg[2].modify( + CFG::GPIO1INCFG.val(0x00) + + CFG::GPIO1OUTCFG.val(0x000) + + CFG::GPIO1INTD.val(0x00), + ); + regs.altpadcfge + .modify(ALTPADCFG::PAD1_DS1::SET + ALTPADCFG::PAD1_SR::CLEAR); + regs.padkey.set(0x00); + } + 28 => { + regs.padkey.set(115); + regs.padreg[7].modify( + PADREG::PAD0PULL::CLEAR + + PADREG::PAD0INPEN::CLEAR + + PADREG::PAD0STRING::SET + + PADREG::PAD0FNCSEL.val(0x5) + + PADREG::PAD0RSEL.val(0x00), + ); + regs.cfg[3].modify( + CFG::GPIO4INCFG.val(0x00) + + CFG::GPIO4OUTCFG.val(0x000) + + CFG::GPIO4INTD.val(0x00), + ); + regs.altpadcfgh + .modify(ALTPADCFG::PAD0_DS1::SET + ALTPADCFG::PAD0_SR::CLEAR); + regs.padkey.set(0x00); + } + 38 => { + regs.padkey.set(115); + regs.padreg[9].modify( + PADREG::PAD2PULL::CLEAR + + PADREG::PAD2INPEN::CLEAR + + PADREG::PAD2STRNG::CLEAR + + PADREG::PAD2FNCSEL.val(0x5), + ); + regs.cfg[4].modify( + CFG::GPIO6INCFG.val(0x00) + + CFG::GPIO6OUTCFG.val(0x000) + + CFG::GPIO6INTD.val(0x00), + ); + regs.altpadcfgj + .modify(ALTPADCFG::PAD2_DS1::CLEAR + ALTPADCFG::PAD2_SR::CLEAR); + regs.padkey.set(0x00); + } + _ => { + panic!("mosi not supported"); + } + } + + match miso.pin as usize { + 6 => { + regs.padkey.set(115); + regs.padreg[1].modify( + PADREG::PAD2PULL::CLEAR + + PADREG::PAD2INPEN::SET + + PADREG::PAD2STRNG::CLEAR + + PADREG::PAD2FNCSEL.val(0x1), + ); + regs.cfg[0].modify( + CFG::GPIO3INCFG.val(0x00) + + CFG::GPIO3OUTCFG.val(0x000) + + CFG::GPIO3INTD.val(0x00), + ); + regs.altpadcfgb + .modify(ALTPADCFG::PAD2_DS1::CLEAR + ALTPADCFG::PAD2_SR::CLEAR); + regs.padkey.set(0x00); + } + 25 => { + regs.padkey.set(115); + regs.padreg[6].modify( + PADREG::PAD1PULL::CLEAR + + PADREG::PAD1INPEN::SET + + PADREG::PAD1STRNG::CLEAR + + PADREG::PAD1FNCSEL.val(0x5) + + PADREG::PAD1RSEL.val(0x00), + ); + regs.cfg[3].modify( + CFG::GPIO1INCFG.val(0x00) + + CFG::GPIO1OUTCFG.val(0x000) + + CFG::GPIO1INTD.val(0x00), + ); + regs.altpadcfgg + .modify(ALTPADCFG::PAD1_DS1::CLEAR + ALTPADCFG::PAD1_SR::CLEAR); + regs.padkey.set(0x00); + } + 26 => { + regs.padkey.set(115); + regs.padreg[6].modify( + PADREG::PAD2PULL::CLEAR + + PADREG::PAD2INPEN::SET + + PADREG::PAD2STRNG::CLEAR + + PADREG::PAD2FNCSEL.val(0x5), + ); + regs.cfg[3].modify( + CFG::GPIO2INCFG.val(0x00) + + CFG::GPIO2OUTCFG.val(0x000) + + CFG::GPIO2INTD.val(0x00), + ); + regs.altpadcfgg + .modify(ALTPADCFG::PAD2_DS1::CLEAR + ALTPADCFG::PAD2_SR::CLEAR); + regs.padkey.set(0x00); + } + 43 => { + regs.padkey.set(115); + regs.padreg[10].modify( + PADREG::PAD3PULL::CLEAR + + PADREG::PAD3INPEN::SET + + PADREG::PAD3STRNG::CLEAR + + PADREG::PAD3FNCSEL.val(0x5), + ); + regs.cfg[5].modify( + CFG::GPIO3INCFG.val(0x00) + + CFG::GPIO3OUTCFG.val(0x000) + + CFG::GPIO3INTD.val(0x00), + ); + regs.altpadcfgk + .modify(ALTPADCFG::PAD3_DS1::CLEAR + ALTPADCFG::PAD3_SR::CLEAR); + regs.padkey.set(0x00); + } + _ => { + panic!("miso not supported"); + } + } + } } enum_from_primitive! { @@ -495,7 +933,10 @@ impl<'a> GpioPin<'a> { } pub fn handle_interrupt(&self) { - unimplemented!(); + // Trigger the upcall + self.client.map(|client| { + client.fired(); + }); } } @@ -504,8 +945,89 @@ impl<'a> gpio::Configure for GpioPin<'a> { unimplemented!(); } - fn set_floating_state(&self, _mode: gpio::FloatingState) { - unimplemented!(); + fn set_floating_state(&self, mode: gpio::FloatingState) { + // Set the key + self.registers.padkey.set(115); + + // Configure the pin as GPIO + let pagreg_offset = self.pin as usize / 4; + let pagreg_value = match self.pin as usize % 4 { + 0 => PADREG::PAD0FNCSEL.val(0x3), + 1 => PADREG::PAD1FNCSEL.val(0x3), + 2 => PADREG::PAD2FNCSEL.val(0x3), + 3 => PADREG::PAD3FNCSEL.val(0x3), + _ => unreachable!(), + }; + self.registers.padreg[pagreg_offset].modify(pagreg_value); + + match mode { + gpio::FloatingState::PullUp => { + let cfgreg_offset = self.pin as usize / 8; + let cfgreg_value = match self.pin as usize % 8 { + 0 => CFG::GPIO0OUTCFG.val(0x1), + 1 => CFG::GPIO1OUTCFG.val(0x1), + 2 => CFG::GPIO2OUTCFG.val(0x1), + 3 => CFG::GPIO3OUTCFG.val(0x1), + 4 => CFG::GPIO4OUTCFG.val(0x1), + 5 => CFG::GPIO5OUTCFG.val(0x1), + 6 => CFG::GPIO6OUTCFG.val(0x1), + 7 => CFG::GPIO7OUTCFG.val(0x1), + _ => unreachable!(), + }; + self.registers.cfg[cfgreg_offset].modify(cfgreg_value); + + let pagreg_value = match self.pin as usize % 4 { + 0 => PADREG::PAD0PULL.val(0x1), + 1 => PADREG::PAD1PULL.val(0x1), + 2 => PADREG::PAD2PULL.val(0x1), + 3 => PADREG::PAD3PULL.val(0x1), + _ => unreachable!(), + }; + self.registers.padreg[pagreg_offset].modify(pagreg_value); + } + gpio::FloatingState::PullDown => { + let cfgreg_offset = self.pin as usize / 8; + let cfgreg_value = match self.pin as usize % 8 { + 0 => CFG::GPIO0OUTCFG.val(0x2), + 1 => CFG::GPIO1OUTCFG.val(0x2), + 2 => CFG::GPIO2OUTCFG.val(0x2), + 3 => CFG::GPIO3OUTCFG.val(0x2), + 4 => CFG::GPIO4OUTCFG.val(0x2), + 5 => CFG::GPIO5OUTCFG.val(0x2), + 6 => CFG::GPIO6OUTCFG.val(0x2), + 7 => CFG::GPIO7OUTCFG.val(0x2), + _ => unreachable!(), + }; + self.registers.cfg[cfgreg_offset].modify(cfgreg_value); + } + gpio::FloatingState::PullNone => { + let cfgreg_offset = self.pin as usize / 8; + let cfgreg_value = match self.pin as usize % 8 { + 0 => CFG::GPIO0OUTCFG.val(0x3), + 1 => CFG::GPIO1OUTCFG.val(0x3), + 2 => CFG::GPIO2OUTCFG.val(0x3), + 3 => CFG::GPIO3OUTCFG.val(0x3), + 4 => CFG::GPIO4OUTCFG.val(0x3), + 5 => CFG::GPIO5OUTCFG.val(0x3), + 6 => CFG::GPIO6OUTCFG.val(0x3), + 7 => CFG::GPIO7OUTCFG.val(0x3), + _ => unreachable!(), + }; + self.registers.cfg[cfgreg_offset].modify(cfgreg_value); + + let pagreg_value = match self.pin as usize % 4 { + 0 => PADREG::PAD0PULL.val(0x0), + 1 => PADREG::PAD1PULL.val(0x0), + 2 => PADREG::PAD2PULL.val(0x0), + 3 => PADREG::PAD3PULL.val(0x0), + _ => unreachable!(), + }; + self.registers.padreg[pagreg_offset].modify(pagreg_value); + } + } + + // Unset key + self.registers.padkey.set(0x00); } fn floating_state(&self) -> gpio::FloatingState { @@ -537,14 +1059,14 @@ impl<'a> gpio::Configure for GpioPin<'a> { // Set to push/pull let cfgreg_offset = self.pin as usize / 8; let cfgreg_value = match self.pin as usize % 8 { - 0 => CFG::GPIO0INTD::CLEAR + CFG::GPIO0OUTCFG.val(0x1), - 1 => CFG::GPIO1INTD::CLEAR + CFG::GPIO1OUTCFG.val(0x1), - 2 => CFG::GPIO2INTD::CLEAR + CFG::GPIO2OUTCFG.val(0x1), - 3 => CFG::GPIO3INTD::CLEAR + CFG::GPIO3OUTCFG.val(0x1), - 4 => CFG::GPIO4INTD::CLEAR + CFG::GPIO4OUTCFG.val(0x1), - 5 => CFG::GPIO5INTD::CLEAR + CFG::GPIO5OUTCFG.val(0x1), - 6 => CFG::GPIO6INTD::CLEAR + CFG::GPIO6OUTCFG.val(0x1), - 7 => CFG::GPIO7INTD::CLEAR + CFG::GPIO7OUTCFG.val(0x1), + 0 => CFG::GPIO0OUTCFG.val(0x1), + 1 => CFG::GPIO1OUTCFG.val(0x1), + 2 => CFG::GPIO2OUTCFG.val(0x1), + 3 => CFG::GPIO3OUTCFG.val(0x1), + 4 => CFG::GPIO4OUTCFG.val(0x1), + 5 => CFG::GPIO5OUTCFG.val(0x1), + 6 => CFG::GPIO6OUTCFG.val(0x1), + 7 => CFG::GPIO7OUTCFG.val(0x1), _ => unreachable!(), }; regs.cfg[cfgreg_offset].modify(cfgreg_value); @@ -556,15 +1078,87 @@ impl<'a> gpio::Configure for GpioPin<'a> { } fn disable_output(&self) -> gpio::Configuration { - unimplemented!(); + let regs = self.registers; + + // Set the key + regs.padkey.set(115); + + // Configure the pin as GPIO + let pagreg_offset = self.pin as usize / 4; + let pagreg_value = match self.pin as usize % 4 { + 0 => PADREG::PAD0FNCSEL.val(0x3), + 1 => PADREG::PAD1FNCSEL.val(0x3), + 2 => PADREG::PAD2FNCSEL.val(0x3), + 3 => PADREG::PAD3FNCSEL.val(0x3), + _ => unreachable!(), + }; + regs.padreg[pagreg_offset].modify(pagreg_value); + + // Set to disabled (GPIO mode) + let cfgreg_offset = self.pin as usize / 8; + let cfgreg_value = match self.pin as usize % 8 { + 0 => CFG::GPIO0OUTCFG.val(0x00), + 1 => CFG::GPIO1OUTCFG.val(0x00), + 2 => CFG::GPIO2OUTCFG.val(0x00), + 3 => CFG::GPIO3OUTCFG.val(0x00), + 4 => CFG::GPIO4OUTCFG.val(0x00), + 5 => CFG::GPIO5OUTCFG.val(0x00), + 6 => CFG::GPIO6OUTCFG.val(0x00), + 7 => CFG::GPIO7OUTCFG.val(0x00), + _ => unreachable!(), + }; + regs.cfg[cfgreg_offset].modify(cfgreg_value); + + // Unset key + regs.padkey.set(0x00); + + gpio::Configuration::LowPower } fn make_input(&self) -> gpio::Configuration { - unimplemented!(); + let regs = self.registers; + + // Set the key + regs.padkey.set(115); + + // Configure the pin as GPIO with input enabled + let pagreg_offset = self.pin as usize / 4; + let pagreg_value = match self.pin as usize % 4 { + 0 => PADREG::PAD0FNCSEL.val(0x3) + PADREG::PAD0INPEN.val(0x1), + 1 => PADREG::PAD1FNCSEL.val(0x3) + PADREG::PAD1INPEN.val(0x1), + 2 => PADREG::PAD2FNCSEL.val(0x3) + PADREG::PAD2INPEN.val(0x1), + 3 => PADREG::PAD3FNCSEL.val(0x3) + PADREG::PAD3INPEN.val(0x1), + _ => unreachable!(), + }; + regs.padreg[pagreg_offset].modify(pagreg_value); + + // Unset key + regs.padkey.set(0x00); + + gpio::Configuration::Input } fn disable_input(&self) -> gpio::Configuration { - unimplemented!(); + let regs = self.registers; + + // Set the key + regs.padkey.set(115); + + // Configure the pin as GPIO with input disabled + let pagreg_offset = self.pin as usize / 4; + let pagreg_value = match self.pin as usize % 4 { + 0 => PADREG::PAD0INPEN.val(0x0), + 1 => PADREG::PAD1INPEN.val(0x0), + 2 => PADREG::PAD2INPEN.val(0x0), + 3 => PADREG::PAD3INPEN.val(0x0), + _ => unreachable!(), + }; + regs.padreg[pagreg_offset].modify(pagreg_value); + + // Unset key + regs.padkey.set(0x00); + + gpio::Configuration::Output } } @@ -595,9 +1189,11 @@ impl<'a> gpio::Output for GpioPin<'a> { } else { cur_value = (regs.wtsb.get() & 1 << self.pin as usize) != 0; if cur_value { - regs.wtb.set(1 << self.pin as usize - 32 | regs.wtsb.get()); + regs.wtb + .set(1 << (self.pin as usize - 32) | regs.wtsb.get()); } else { - regs.wtb.set(0 << self.pin as usize - 32 | regs.wtsb.get()); + regs.wtb + .set(0 << (self.pin as usize - 32) | regs.wtsb.get()); } } @@ -715,7 +1311,7 @@ impl<'a> gpio::Interrupt<'a> for GpioPin<'a> { .set(!(1 << self.pin as usize) & regs.int0en.get()); } else { regs.int1en - .set(!(1 << (self.pin as usize - 32)) & regs.int0en.get()); + .set(!(1 << (self.pin as usize - 32)) & regs.int1en.get()); } // Clear interrupt diff --git a/chips/apollo3/src/iom.rs b/chips/apollo3/src/iom.rs index 3f7fb76b51..b6b1ae8d00 100644 --- a/chips/apollo3/src/iom.rs +++ b/chips/apollo3/src/iom.rs @@ -1,13 +1,20 @@ -//! IO Master Driver (I2C) +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! IO Master Driver (I2C and SPI) use core::cell::Cell; use kernel::hil; +use kernel::hil::gpio::{Configure, Output}; use kernel::hil::i2c; +use kernel::hil::spi::{ClockPhase, ClockPolarity, SpiMaster, SpiMasterClient}; use kernel::utilities::cells::OptionalCell; use kernel::utilities::cells::TakeCell; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; use kernel::utilities::registers::{register_bitfields, register_structs, ReadOnly, ReadWrite}; use kernel::utilities::StaticRef; +use kernel::ErrorCode; const IOM0_BASE: StaticRef = unsafe { StaticRef::new(0x5000_4000 as *const IomRegisters) }; @@ -259,103 +266,153 @@ register_bitfields![u32, ] ]; +#[derive(Clone, Copy, PartialEq, Debug)] +enum Operation { + None, + I2C, + SPI, +} + pub struct Iom<'a> { registers: StaticRef, - master_client: OptionalCell<&'a dyn hil::i2c::I2CHwMasterClient>, + i2c_master_client: OptionalCell<&'a dyn hil::i2c::I2CHwMasterClient>, + spi_master_client: OptionalCell<&'a dyn SpiMasterClient>, buffer: TakeCell<'static, [u8]>, + spi_read_buffer: TakeCell<'static, [u8]>, write_len: Cell, write_index: Cell, read_len: Cell, read_index: Cell, + op: Cell, + spi_phase: Cell, + spi_cs: OptionalCell<&'a crate::gpio::GpioPin<'a>>, smbus: Cell, } impl<'a> Iom<'_> { - pub const fn new0() -> Iom<'a> { + pub fn new0() -> Iom<'a> { Iom { registers: IOM0_BASE, - master_client: OptionalCell::empty(), + i2c_master_client: OptionalCell::empty(), + spi_master_client: OptionalCell::empty(), buffer: TakeCell::empty(), + spi_read_buffer: TakeCell::empty(), write_len: Cell::new(0), write_index: Cell::new(0), read_len: Cell::new(0), read_index: Cell::new(0), + op: Cell::new(Operation::None), + spi_phase: Cell::new(ClockPhase::SampleLeading), + spi_cs: OptionalCell::empty(), smbus: Cell::new(false), } } - pub const fn new1() -> Iom<'a> { + pub fn new1() -> Iom<'a> { Iom { registers: IOM1_BASE, - master_client: OptionalCell::empty(), + i2c_master_client: OptionalCell::empty(), + spi_master_client: OptionalCell::empty(), buffer: TakeCell::empty(), + spi_read_buffer: TakeCell::empty(), write_len: Cell::new(0), write_index: Cell::new(0), read_len: Cell::new(0), read_index: Cell::new(0), + op: Cell::new(Operation::None), + spi_phase: Cell::new(ClockPhase::SampleLeading), + spi_cs: OptionalCell::empty(), smbus: Cell::new(false), } } - pub const fn new2() -> Iom<'a> { + pub fn new2() -> Iom<'a> { Iom { registers: IOM2_BASE, - master_client: OptionalCell::empty(), + i2c_master_client: OptionalCell::empty(), + spi_master_client: OptionalCell::empty(), buffer: TakeCell::empty(), + spi_read_buffer: TakeCell::empty(), write_len: Cell::new(0), write_index: Cell::new(0), read_len: Cell::new(0), read_index: Cell::new(0), + op: Cell::new(Operation::None), + spi_phase: Cell::new(ClockPhase::SampleLeading), + spi_cs: OptionalCell::empty(), smbus: Cell::new(false), } } - pub const fn new3() -> Iom<'a> { + pub fn new3() -> Iom<'a> { Iom { registers: IOM3_BASE, - master_client: OptionalCell::empty(), + i2c_master_client: OptionalCell::empty(), + spi_master_client: OptionalCell::empty(), buffer: TakeCell::empty(), + spi_read_buffer: TakeCell::empty(), write_len: Cell::new(0), write_index: Cell::new(0), read_len: Cell::new(0), read_index: Cell::new(0), + op: Cell::new(Operation::None), + spi_phase: Cell::new(ClockPhase::SampleLeading), + spi_cs: OptionalCell::empty(), smbus: Cell::new(false), } } - pub const fn new4() -> Iom<'a> { + pub fn new4() -> Iom<'a> { Iom { registers: IOM4_BASE, - master_client: OptionalCell::empty(), + i2c_master_client: OptionalCell::empty(), + spi_master_client: OptionalCell::empty(), buffer: TakeCell::empty(), + spi_read_buffer: TakeCell::empty(), write_len: Cell::new(0), write_index: Cell::new(0), read_len: Cell::new(0), read_index: Cell::new(0), + op: Cell::new(Operation::None), + spi_phase: Cell::new(ClockPhase::SampleLeading), + spi_cs: OptionalCell::empty(), smbus: Cell::new(false), } } - pub const fn new5() -> Iom<'a> { + pub fn new5() -> Iom<'a> { Iom { registers: IOM5_BASE, - master_client: OptionalCell::empty(), + i2c_master_client: OptionalCell::empty(), + spi_master_client: OptionalCell::empty(), buffer: TakeCell::empty(), + spi_read_buffer: TakeCell::empty(), write_len: Cell::new(0), write_index: Cell::new(0), read_len: Cell::new(0), read_index: Cell::new(0), + op: Cell::new(Operation::None), + spi_phase: Cell::new(ClockPhase::SampleLeading), + spi_cs: OptionalCell::empty(), smbus: Cell::new(false), } } - fn reset_fifo(&self) { + fn i2c_reset_fifo(&self) { let regs = self.registers; + // Set the value low to reset regs.fifoctrl.modify(FIFOCTRL::FIFORSTN::CLEAR); + + // Wait a few cycles to ensure the reset completes + for _i in 0..30 { + cortexm4::support::nop(); + } + + // Exit the reset state regs.fifoctrl.modify(FIFOCTRL::FIFORSTN::SET); } - fn write_data(&self) { + fn i2c_write_data(&self) { let regs = self.registers; let mut data_pushed = self.write_index.get(); let len = self.write_len.get(); @@ -370,7 +427,7 @@ impl<'a> Iom<'_> { let data_idx = i * 4; if regs.fifoptr.read(FIFOPTR::FIFO0REM) <= 4 { - self.write_index.set(data_pushed as usize); + self.write_index.set(data_pushed); break; } @@ -388,27 +445,27 @@ impl<'a> Iom<'_> { if len < 4 || data_pushed > (len - 4) { // Check if we have any left over data if len % 4 == 1 { - let d = buf[len as usize - 1] as u32; + let d = buf[len - 1] as u32; regs.fifopush.set(d); } else if len % 4 == 2 { - let mut d = (buf[len as usize - 1] as u32) << 8; - d |= (buf[len as usize - 2] as u32) << 0; + let mut d = (buf[len - 1] as u32) << 8; + d |= (buf[len - 2] as u32) << 0; regs.fifopush.set(d); } else if len % 4 == 3 { - let mut d = (buf[len as usize - 1] as u32) << 16; - d |= (buf[len as usize - 2] as u32) << 8; - d |= (buf[len as usize - 3] as u32) << 0; + let mut d = (buf[len - 1] as u32) << 16; + d |= (buf[len - 2] as u32) << 8; + d |= (buf[len - 3] as u32) << 0; regs.fifopush.set(d); } - self.write_index.set(len as usize); + self.write_index.set(len); } }); } - fn read_data(&self) { + fn i2c_read_data(&self) { let regs = self.registers; let mut data_popped = self.read_index.get(); let len = self.read_len.get(); @@ -423,7 +480,7 @@ impl<'a> Iom<'_> { let data_idx = i * 4; if regs.fifoptr.read(FIFOPTR::FIFO1SIZ) < 4 { - self.read_index.set(data_popped as usize); + self.read_index.set(data_popped); break; } @@ -437,7 +494,7 @@ impl<'a> Iom<'_> { data_popped = data_idx + 4; } - // Get an remaining data that isn't 4 bytes long + // Get remaining data that isn't 4 bytes long if len < 4 || data_popped > (len - 4) { // Check if we have any left over data if len % 4 == 1 { @@ -456,7 +513,7 @@ impl<'a> Iom<'_> { buf[len - 2] = d[1]; buf[len - 1] = d[2]; } - self.read_index.set(len as usize); + self.read_index.set(len); } }); } @@ -467,46 +524,19 @@ impl<'a> Iom<'_> { // Clear interrrupts regs.intclr.set(0xFFFF_FFFF); + // Ensure interrupts remain enabled + regs.inten.set(0xFFFF_FFFF); - if irqs.is_set(INT::CMDCMP) || irqs.is_set(INT::THR) { - // Enable interrupts - regs.inten.set(0xFFFF_FFFF); - - if regs.fifothr.read(FIFOTHR::FIFOWTHR) > 0 { - let remaining = self.write_len.get() - self.write_index.get(); - - if remaining > 4 { - regs.fifothr.write( - FIFOTHR::FIFORTHR.val(0) + FIFOTHR::FIFOWTHR.val(remaining as u32 / 2), - ); - } else { - regs.fifothr - .write(FIFOTHR::FIFORTHR.val(0) + FIFOTHR::FIFOWTHR.val(1)); - } - - self.write_data(); - } else if regs.fifothr.read(FIFOTHR::FIFORTHR) > 0 { - let remaining = self.read_len.get() - self.read_index.get(); - - if remaining > 4 { - regs.fifothr.write( - FIFOTHR::FIFORTHR.val(remaining as u32 / 2) + FIFOTHR::FIFOWTHR.val(0), - ); - } else { - regs.fifothr - .write(FIFOTHR::FIFORTHR.val(1) + FIFOTHR::FIFOWTHR.val(0)); - } - - self.read_data(); - } - } + if irqs.is_set(INT::NAK) { + if self.op.get() == Operation::I2C { + // Disable interrupts + regs.inten.set(0x00); + self.i2c_reset_fifo(); - if irqs.is_set(INT::CMDCMP) { - if (self.read_len.get() > 0 && self.read_index.get() == self.read_len.get()) - || (self.write_len.get() > 0 && self.write_index.get() == self.write_len.get()) - { - self.master_client.map(|client| { - client.command_complete(self.buffer.take().unwrap(), Ok(())); + self.i2c_master_client.map(|client| { + self.buffer.take().map(|buffer| { + client.command_complete(buffer, Err(i2c::Error::DataNak)); + }); }); // Finished with SMBus @@ -523,16 +553,294 @@ impl<'a> Iom<'_> { self.smbus.set(false); } + } else { + // Disable interrupts + regs.inten.set(0x00); + + // Clear CS + self.spi_cs.map(|cs| cs.set()); + + self.op.set(Operation::None); + + self.spi_master_client.map(|client| { + self.buffer.take().map(|buffer| { + let read_buffer = self.spi_read_buffer.take(); + client.read_write_done( + buffer, + read_buffer, + self.write_len.get(), + Err(ErrorCode::NOACK), + ); + }); + }); + } + return; + } + + if self.op.get() == Operation::SPI { + // Read the incoming data + if let Some(buf) = self.spi_read_buffer.take() { + while self.registers.fifoptr.read(FIFOPTR::FIFO1SIZ) > 0 { + // The IOM doesn't correctly pop the data if we read it too fast + // there are a few erratas against the IOM when reading data + // from the FIFO (compared to DMA). Adding a small delay here is + // enough to ensure the fifoptr values update before the next + // iteration. + // See: https://ambiq.com/wp-content/uploads/2022/01/Apollo3-Blue-Errata-List.pdf + for _i in 0..3000 { + cortexm4::support::nop(); + } + + let d = self.registers.fifopop.get().to_ne_bytes(); + let data_idx = self.read_index.get(); + + if let Some(b) = buf.get_mut(data_idx + 0) { + *b = d[0]; + self.read_index.set(data_idx + 1); + } + if let Some(b) = buf.get_mut(data_idx + 1) { + *b = d[1]; + self.read_index.set(data_idx + 2); + } + if let Some(b) = buf.get_mut(data_idx + 2) { + *b = d[2]; + self.read_index.set(data_idx + 3); + } + if let Some(b) = buf.get_mut(data_idx + 3) { + *b = d[3]; + self.read_index.set(data_idx + 4); + } + } + + self.spi_read_buffer.replace(buf); + + if self.read_len.get() > self.read_index.get() { + let remaining_bytes = (self.read_len.get() - self.read_index.get()).min(32); + self.registers + .fifothr + .modify(FIFOTHR::FIFORTHR.val(remaining_bytes as u32)); + } else { + self.registers.fifothr.modify(FIFOTHR::FIFORTHR.val(0)); + } + } else { + while self.registers.fifoptr.read(FIFOPTR::FIFO1SIZ) > 0 { + // The IOM doesn't correctly pop the data if we read it too fast + // there are a few erratas against the IOM when reading data + // from the FIFO (compared to DMA). Adding a small delay here is + // enough to ensure the fifoptr values update before the next + // iteration. + // See: https://ambiq.com/wp-content/uploads/2022/01/Apollo3-Blue-Errata-List.pdf + for _i in 0..3000 { + cortexm4::support::nop(); + } + + let _d = self.registers.fifopop.get().to_ne_bytes(); + } + + self.registers.fifothr.modify(FIFOTHR::FIFORTHR.val(0)); + } + + // Write more data out + if self.write_index.get() < self.write_len.get() { + if let Some(write_buffer) = self.buffer.take() { + let mut transfered_bytes = 0; + + // While there is some free space in FIFO0 (writing to the SPI bus) and + // at least 4 bytes free in FIFO1 (reading from the SPI bus to the + // hardware FIFO) we write up to 24 bytes of data. + // + // The `> 4` really could be `>= 4` but > gives us a little wiggle room + // as the hardware does seem a little slow at updating the FIFO size + // registers. + // + // The 24 byte limit is along the same lines, of just making sure we + // don't write too much data. I don't have a good answer of why it should + // be 24, but that seems to work reliably from testing. + // + // There isn't a specific errata for this issue, but the official HAL + // uses DMA so there aren't a lot of FIFO users for large transfers like + // this. + while self.registers.fifoptr.read(FIFOPTR::FIFO0REM) > 0 + && self.registers.fifoptr.read(FIFOPTR::FIFO1REM) > 4 + && self.write_index.get() < self.write_len.get() + && transfered_bytes < 24 + { + let idx = self.write_index.get(); + let data = u32::from_le_bytes( + write_buffer[idx..(idx + 4)].try_into().unwrap_or([0; 4]), + ); + + self.registers.fifopush.set(data); + self.write_index.set(idx + 4); + transfered_bytes += 4; + } + + self.buffer.replace(write_buffer); + } + + let remaining_bytes = (self.write_len.get() - self.write_index.get()).min(32); + self.registers + .fifothr + .modify(FIFOTHR::FIFOWTHR.val(remaining_bytes as u32)); + } else { + self.registers.fifothr.modify(FIFOTHR::FIFOWTHR.val(0)); + } + + if (self.write_len.get() > 0 + && self.write_index.get() >= self.write_len.get() + && self.read_len.get() > 0 + && self.read_index.get() >= self.read_len.get()) + || irqs.is_set(INT::CMDCMP) + { + // Disable interrupts + regs.inten.set(0x00); + + // Clear CS + self.spi_cs.map(|cs| cs.set()); + + self.op.set(Operation::None); + + self.spi_master_client.map(|client| { + self.buffer.take().map(|buffer| { + let read_buffer = self.spi_read_buffer.take(); + client.read_write_done(buffer, read_buffer, self.write_len.get(), Ok(())); + }); + }); + } + + return; + } + + if irqs.is_set(INT::CMDCMP) || irqs.is_set(INT::THR) { + if self.op.get() == Operation::I2C { + if irqs.is_set(INT::THR) { + if regs.fifothr.read(FIFOTHR::FIFOWTHR) > 0 { + let remaining = self.write_len.get() - self.write_index.get(); + + if remaining > 4 { + regs.fifothr.write( + FIFOTHR::FIFORTHR.val(0) + + FIFOTHR::FIFOWTHR.val(remaining as u32 / 2), + ); + } else { + regs.fifothr + .write(FIFOTHR::FIFORTHR.val(0) + FIFOTHR::FIFOWTHR.val(1)); + } + + self.i2c_write_data(); + } else if regs.fifothr.read(FIFOTHR::FIFORTHR) > 0 { + let remaining = self.read_len.get() - self.read_index.get(); + + if remaining > 4 { + regs.fifothr.write( + FIFOTHR::FIFORTHR.val(remaining as u32 / 2) + + FIFOTHR::FIFOWTHR.val(0), + ); + } else { + regs.fifothr + .write(FIFOTHR::FIFORTHR.val(1) + FIFOTHR::FIFOWTHR.val(0)); + } + + self.i2c_read_data(); + } + } + + if irqs.is_set(INT::CMDCMP) || regs.intstat.is_set(INT::CMDCMP) { + if (self.read_len.get() > 0 && self.read_index.get() == self.read_len.get()) + || (self.write_len.get() > 0 + && self.write_index.get() == self.write_len.get()) + { + // Disable interrupts + regs.inten.set(0x00); + self.i2c_reset_fifo(); + + self.i2c_master_client.map(|client| { + self.buffer.take().map(|buffer| { + client.command_complete(buffer, Ok(())); + }); + }); + + // Finished with SMBus + if self.smbus.get() { + // Setup 400kHz + regs.clkcfg.write( + CLKCFG::TOTPER.val(0x1D) + + CLKCFG::LOWPER.val(0xE) + + CLKCFG::DIVEN.val(1) + + CLKCFG::DIV3.val(0) + + CLKCFG::FSEL.val(2) + + CLKCFG::IOCLKEN::SET, + ); + + self.smbus.set(false); + } + } + } + } else { + self.buffer.take().map(|write_buffer| { + let offset = self.write_index.get(); + if self.write_len.get() > offset { + let burst_len = (self.write_len.get() - offset) + .min(self.registers.fifoptr.read(FIFOPTR::FIFO0REM) as usize); + + // Start the transfer + self.registers.cmd.write( + CMD::TSIZE.val(burst_len as u32) + + CMD::CMDSEL.val(1) + + CMD::CONT::CLEAR + + CMD::CMD::WRITE + + CMD::OFFSETCNT.val(0_u32) + + CMD::OFFSETLO.val(0), + ); + + while self.registers.fifoptr.read(FIFOPTR::FIFO0REM) > 4 + && self.registers.fifoptr.read(FIFOPTR::FIFO1SIZ) < 32 + && self.write_index.get() < (((offset + burst_len) / 4) * 4) + && self.write_len.get() - self.write_index.get() > 4 + { + let idx = self.write_index.get(); + let data = u32::from_le_bytes( + write_buffer[idx..(idx + 4)].try_into().unwrap_or([0; 4]), + ); + + self.registers.fifopush.set(data); + self.write_index.set(idx + 4); + } + + // Get remaining data that isn't 4 bytes long + if self.write_len.get() - self.write_index.get() < 4 + && self.write_index.get() < (offset + burst_len) + { + let len = self.write_len.get() - self.write_index.get(); + let mut buf = [0; 4]; + // Check if we have any left over data + if len % 4 == 1 { + buf[len - 1] = write_buffer[self.write_index.get() + 0]; + } else if len % 4 == 2 { + buf[len - 2] = write_buffer[self.write_index.get() + 0]; + buf[len - 1] = write_buffer[self.write_index.get() + 1]; + } else if len % 4 == 3 { + buf[len - 3] = write_buffer[self.write_index.get() + 0]; + buf[len - 2] = write_buffer[self.write_index.get() + 1]; + buf[len - 1] = write_buffer[self.write_index.get() + 2]; + } + self.registers.fifopush.set(u32::from_le_bytes(buf)); + self.write_index.set(self.write_index.get() + len); + } + } + + self.buffer.replace(write_buffer); + }); } } } - fn tx_rx( + fn i2c_tx_rx( &self, addr: u8, data: &'static mut [u8], - write_len: u8, - read_len: u8, + write_len: usize, + read_len: usize, ) -> Result<(), (i2c::Error, &'static mut [u8])> { let regs = self.registers; let mut offsetlo = 0; @@ -555,7 +863,7 @@ impl<'a> Iom<'_> { .write(FIFOTHR::FIFORTHR.val(1) + FIFOTHR::FIFOWTHR.val(0)); } - self.reset_fifo(); + self.i2c_reset_fifo(); if write_len > 0 { offsetlo = data[0] as u32; @@ -572,8 +880,8 @@ impl<'a> Iom<'_> { } else { // Save all the data and offsets we still need to send self.buffer.replace(data); - self.write_len.set(write_len as usize); - self.read_len.set(read_len as usize); + self.write_len.set(write_len); + self.read_len.set(read_len); self.write_index.set(0); self.read_index.set(0); // Clear and enable interrupts @@ -588,16 +896,15 @@ impl<'a> Iom<'_> { + CMD::OFFSETLO.val(offsetlo), ); - self.read_data(); Ok(()) } } - fn tx( + fn i2c_tx( &self, addr: u8, data: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (i2c::Error, &'static mut [u8])> { let regs = self.registers; @@ -619,15 +926,15 @@ impl<'a> Iom<'_> { .write(FIFOTHR::FIFORTHR.val(0) + FIFOTHR::FIFOWTHR.val(1)); } - self.reset_fifo(); + self.i2c_reset_fifo(); // Save all the data and offsets we still need to send self.buffer.replace(data); - self.write_len.set(len as usize); + self.write_len.set(len); self.read_len.set(0); self.write_index.set(0); - self.write_data(); + self.i2c_write_data(); // Clear and enable interrupts regs.intclr.set(0xFFFF_FFFF); @@ -639,11 +946,11 @@ impl<'a> Iom<'_> { Ok(()) } - fn rx( + fn i2c_rx( &self, addr: u8, buffer: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (i2c::Error, &'static mut [u8])> { let regs = self.registers; @@ -665,7 +972,7 @@ impl<'a> Iom<'_> { .write(FIFOTHR::FIFORTHR.val(1) + FIFOTHR::FIFOWTHR.val(0)); } - self.reset_fifo(); + self.i2c_reset_fifo(); // Clear and enable interrupts regs.intclr.set(0xFFFF_FFFF); @@ -677,24 +984,26 @@ impl<'a> Iom<'_> { // Save all the data and offsets we still need to send self.buffer.replace(buffer); - self.read_len.set(len as usize); + self.read_len.set(len); self.write_len.set(0); self.read_index.set(0); - self.read_data(); + self.i2c_read_data(); Ok(()) } } -impl<'a> hil::i2c::I2CMaster for Iom<'a> { - fn set_master_client(&self, master_client: &'a dyn i2c::I2CHwMasterClient) { - self.master_client.set(master_client); +impl<'a> hil::i2c::I2CMaster<'a> for Iom<'a> { + fn set_master_client(&self, i2c_master_client: &'a dyn i2c::I2CHwMasterClient) { + self.i2c_master_client.set(i2c_master_client); } fn enable(&self) { let regs = self.registers; + self.op.set(Operation::I2C); + // Setup the I2C regs.mi2ccfg.write( MI2CCFG::STRDIS.val(0) @@ -727,48 +1036,74 @@ impl<'a> hil::i2c::I2CMaster for Iom<'a> { fn disable(&self) { let regs = self.registers; - regs.submodctrl.write(SUBMODCTRL::SMOD1EN::CLEAR); + if self.op.get() == Operation::I2C { + regs.submodctrl.write(SUBMODCTRL::SMOD1EN::CLEAR); + + self.op.set(Operation::None); + } } fn write_read( &self, addr: u8, data: &'static mut [u8], - write_len: u8, - read_len: u8, + write_len: usize, + read_len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { - self.tx_rx(addr, data, write_len, read_len) + if self.op.get() != Operation::I2C { + return Err((hil::i2c::Error::Busy, data)); + } + if data.len() < write_len { + return Err((hil::i2c::Error::Overrun, data)); + } + self.i2c_tx_rx(addr, data, write_len, read_len) } fn write( &self, addr: u8, data: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { - self.tx(addr, data, len) + if self.op.get() != Operation::I2C { + return Err((hil::i2c::Error::Busy, data)); + } + if data.len() < len { + return Err((hil::i2c::Error::Overrun, data)); + } + self.i2c_tx(addr, data, len) } fn read( &self, addr: u8, buffer: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { - self.rx(addr, buffer, len) + if self.op.get() != Operation::I2C { + return Err((hil::i2c::Error::Busy, buffer)); + } + if buffer.len() < len { + return Err((hil::i2c::Error::Overrun, buffer)); + } + self.i2c_rx(addr, buffer, len) } } -impl<'a> hil::i2c::SMBusMaster for Iom<'a> { +impl<'a> hil::i2c::SMBusMaster<'a> for Iom<'a> { fn smbus_write_read( &self, addr: u8, data: &'static mut [u8], - write_len: u8, - read_len: u8, + write_len: usize, + read_len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { let regs = self.registers; + if self.op.get() != Operation::I2C { + return Err((hil::i2c::Error::Busy, data)); + } + // Setup 100kHz regs.clkcfg.write( CLKCFG::TOTPER.val(0x77) @@ -781,17 +1116,21 @@ impl<'a> hil::i2c::SMBusMaster for Iom<'a> { self.smbus.set(true); - self.tx_rx(addr, data, write_len, read_len) + self.i2c_tx_rx(addr, data, write_len, read_len) } fn smbus_write( &self, addr: u8, data: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { let regs = self.registers; + if self.op.get() != Operation::I2C { + return Err((hil::i2c::Error::Busy, data)); + } + // Setup 100kHz regs.clkcfg.write( CLKCFG::TOTPER.val(0x77) @@ -804,17 +1143,21 @@ impl<'a> hil::i2c::SMBusMaster for Iom<'a> { self.smbus.set(true); - self.tx(addr, data, len) + self.i2c_tx(addr, data, len) } fn smbus_read( &self, addr: u8, buffer: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { let regs = self.registers; + if self.op.get() != Operation::I2C { + return Err((hil::i2c::Error::Busy, buffer)); + } + // Setup 100kHz regs.clkcfg.write( CLKCFG::TOTPER.val(0x77) @@ -827,6 +1170,409 @@ impl<'a> hil::i2c::SMBusMaster for Iom<'a> { self.smbus.set(true); - self.rx(addr, buffer, len) + self.i2c_rx(addr, buffer, len) + } +} + +impl<'a> SpiMaster<'a> for Iom<'a> { + type ChipSelect = &'a crate::gpio::GpioPin<'a>; + + fn init(&self) -> Result<(), ErrorCode> { + self.op.set(Operation::SPI); + + self.registers.mspicfg.write( + MSPICFG::FULLDUP::SET + + MSPICFG::WTFC::CLEAR + + MSPICFG::RDFC::CLEAR + + MSPICFG::MOSIINV::CLEAR + + MSPICFG::WTFCIRQ::CLEAR + + MSPICFG::WTFCPOL::CLEAR + + MSPICFG::RDFCPOL::CLEAR + + MSPICFG::SPILSB::CLEAR + + MSPICFG::DINDLY::CLEAR + + MSPICFG::DOUTDLY::CLEAR + + MSPICFG::MSPIRST::CLEAR, + ); + + // Enable SPI + self.registers + .submodctrl + .write(SUBMODCTRL::SMOD1EN::CLEAR + SUBMODCTRL::SMOD0EN::SET); + + self.registers.dmatrigen.write(DMATRIGEN::DTHREN::SET); + + Ok(()) + } + + fn set_client(&self, client: &'a dyn SpiMasterClient) { + self.spi_master_client.set(client); + } + + fn is_busy(&self) -> bool { + self.op.get() != Operation::None + } + + fn read_write_bytes( + &self, + write_buffer: &'static mut [u8], + read_buffer: Option<&'static mut [u8]>, + len: usize, + ) -> Result<(), (ErrorCode, &'static mut [u8], Option<&'static mut [u8]>)> { + let write_len = write_buffer.len().min(len); + let read_len = if let Some(ref buffer) = read_buffer { + buffer.len().min(len) + } else { + 0 + }; + + // Disable DMA as we don't support it + self.registers.dmacfg.write(DMACFG::DMAEN::CLEAR); + + // Set the DCX + self.registers.dcx.set(0); + + self.write_index.set(0); + self.read_index.set(0); + + // Clear interrupts + self.registers.intclr.set(0xFFFF_FFFF); + + // Trigger CS + self.spi_cs.map(|cs| cs.clear()); + + // Start the transfer + self.registers.cmd.write( + CMD::TSIZE.val(write_len as u32) + + CMD::CMDSEL.val(1) + + CMD::CONT::CLEAR + + CMD::CMD::WRITE + + CMD::OFFSETCNT.val(0_u32) + + CMD::OFFSETLO.val(0), + ); + + if let Some(buf) = read_buffer { + self.spi_read_buffer.replace(buf); + } + + while self.registers.cmdstat.read(CMDSTAT::CMDSTAT) == 0x02 {} + + let mut transfered_bytes = 0; + + // While there is some free space in FIFO0 (writing to the SPI bus) and + // at least 4 bytes free in FIFO1 (reading from the SPI bus to the + // hardware FIFO) we write up to 24 bytes of data. + // + // The `> 4` really could be `>= 4` but > gives us a little wiggle room + // as the hardware does seem a little slow at updating the FIFO size + // registers. + // + // The 24 byte limit is along the same lines, of just making sure we + // don't write too much data. I don't have a good answer of why it should + // be 24, but that seems to work reliably from testing. + // + // There isn't a specific errata for this issue, but the official HAL + // uses DMA so there aren't a lot of FIFO users for large transfers like + // this. + while self.registers.fifoptr.read(FIFOPTR::FIFO0REM) > 0 + && self.registers.fifoptr.read(FIFOPTR::FIFO1REM) > 4 + && self.write_index.get() < write_len + && transfered_bytes < 24 + { + let idx = self.write_index.get(); + let data = + u32::from_le_bytes(write_buffer[idx..(idx + 4)].try_into().unwrap_or([0; 4])); + + self.registers.fifopush.set(data); + self.write_index.set(idx + 4); + transfered_bytes += 4; + + if let Some(buf) = self.spi_read_buffer.take() { + if self.registers.fifoptr.read(FIFOPTR::FIFO1SIZ) > 0 + && self.read_index.get() < read_len + { + let d = self.registers.fifopop.get().to_ne_bytes(); + + let data_idx = self.read_index.get(); + + buf[data_idx + 0] = d[0]; + buf[data_idx + 1] = d[1]; + buf[data_idx + 2] = d[2]; + buf[data_idx + 3] = d[3]; + + self.read_index.set(data_idx + 4); + } + + self.spi_read_buffer.replace(buf); + } else { + if self.registers.fifoptr.read(FIFOPTR::FIFO1SIZ) > 0 { + let _d = self.registers.fifopop.get(); + } + } + } + + // Save all the data and offsets we still need to send + self.buffer.replace(write_buffer); + self.write_len.set(write_len); + self.read_len.set(read_len); + self.op.set(Operation::SPI); + + if read_len > self.read_index.get() { + let remaining_bytes = (read_len - self.read_index.get()).min(32); + self.registers + .fifothr + .modify(FIFOTHR::FIFORTHR.val(remaining_bytes as u32)); + } else { + self.registers.fifothr.modify(FIFOTHR::FIFORTHR.val(0)); + } + + if write_len > self.write_index.get() { + let remaining_bytes = (self.write_len.get() - self.write_index.get()).min(32); + + self.registers + .fifothr + .modify(FIFOTHR::FIFOWTHR.val(remaining_bytes as u32)); + } else { + self.registers.fifothr.modify(FIFOTHR::FIFOWTHR.val(0)); + } + + // Enable interrupts + self.registers.inten.set(0xFFFF_FFFF); + + Ok(()) + } + + fn write_byte(&self, val: u8) -> Result<(), ErrorCode> { + let burst_len = 1; + + // Disable DMA as we don't support it + self.registers.dmacfg.write(DMACFG::DMAEN::CLEAR); + + // Set the DCX + self.registers.dcx.set(0); + + // Clear interrupts + self.registers.intclr.set(0xFFFF_FFFF); + + // Trigger CS + self.spi_cs.map(|cs| cs.clear()); + + // Start the transfer + self.registers.cmd.write( + CMD::TSIZE.val(burst_len as u32) + + CMD::CMDSEL.val(1) + + CMD::CONT::CLEAR + + CMD::CMD::WRITE + + CMD::OFFSETCNT.val(0_u32) + + CMD::OFFSETLO.val(0), + ); + + self.registers.fifopush.set(val as u32); + + self.spi_cs.map(|cs| cs.set()); + + Ok(()) + } + + fn read_byte(&self) -> Result { + let burst_len = 1; + + // Disable DMA as we don't support it + self.registers.dmacfg.write(DMACFG::DMAEN::CLEAR); + + // Set the DCX + self.registers.dcx.set(0); + + // Clear interrupts + self.registers.intclr.set(0xFFFF_FFFF); + + // Trigger CS + self.spi_cs.map(|cs| cs.clear()); + + // Start the transfer + self.registers.cmd.write( + CMD::TSIZE.val(burst_len as u32) + + CMD::CMDSEL.val(1) + + CMD::CONT::CLEAR + + CMD::CMD::READ + + CMD::OFFSETCNT.val(0_u32) + + CMD::OFFSETLO.val(0), + ); + + if self.registers.fifoptr.read(FIFOPTR::FIFO1SIZ) > 0 { + let d = self.registers.fifopop.get().to_ne_bytes(); + + self.spi_cs.map(|cs| cs.set()); + return Ok(d[0]); + } + + self.spi_cs.map(|cs| cs.set()); + + Err(ErrorCode::FAIL) + } + + fn read_write_byte(&self, val: u8) -> Result { + let burst_len = 1; + + // Disable DMA as we don't support it + self.registers.dmacfg.write(DMACFG::DMAEN::CLEAR); + + // Set the DCX + self.registers.dcx.set(0); + + // Clear interrupts + self.registers.intclr.set(0xFFFF_FFFF); + + // Trigger CS + self.spi_cs.map(|cs| cs.clear()); + + // Start the transfer + self.registers.cmd.write( + CMD::TSIZE.val(burst_len as u32) + + CMD::CMDSEL.val(1) + + CMD::CONT::CLEAR + + CMD::CMD::WRITE + + CMD::OFFSETCNT.val(0_u32) + + CMD::OFFSETLO.val(0), + ); + + self.registers.fifopush.set(val as u32); + + if self.registers.fifoptr.read(FIFOPTR::FIFO1SIZ) > 0 { + let d = self.registers.fifopop.get().to_ne_bytes(); + + self.spi_cs.map(|cs| cs.set()); + return Ok(d[0]); + } + + self.spi_cs.map(|cs| cs.set()); + + Err(ErrorCode::FAIL) + } + + fn specify_chip_select(&self, cs: Self::ChipSelect) -> Result<(), ErrorCode> { + cs.make_output(); + cs.set(); + self.spi_cs.set(cs); + + Ok(()) + } + + fn set_rate(&self, rate: u32) -> Result { + if self.op.get() != Operation::SPI && self.op.get() != Operation::None { + return Err(ErrorCode::BUSY); + } + + let div: u32 = 48000000 / rate; // TODO: Change to `48000000_u32.div_ceil(rate)` when api out of nightly + let n = div.trailing_zeros().max(6); + + let div3 = u32::from( + (rate < (48000000 / 16384)) + || ((rate >= (48000000 / 3)) && (rate <= ((48000000 / 2) - 1))), + ); + let denom = (1 << n) * (1 + (div3 * 2)); + let tot_per = if div % denom > 0 { + (div / denom) + 1 + } else { + div / denom + }; + let v1 = 31 - tot_per.leading_zeros(); + let fsel = if v1 > 7 { v1 + n - 6 } else { n + 1 }; + + if fsel > 7 { + return Err(ErrorCode::NOSUPPORT); + } + + let diven = u32::from((rate >= (48000000 / 4)) || ((1 << (fsel - 1)) == div)); + let low_per = if self.spi_phase.get() == ClockPhase::SampleLeading { + (tot_per - 1) / 2 + } else { + (tot_per - 2) / 2 + }; + + self.registers.clkcfg.write( + CLKCFG::TOTPER.val(tot_per - 1) + + CLKCFG::LOWPER.val(low_per) + + CLKCFG::DIVEN.val(diven) + + CLKCFG::DIV3.val(div3) + + CLKCFG::FSEL.val(fsel) + + CLKCFG::IOCLKEN::SET, + ); + + Ok(self.get_rate()) + } + + fn get_rate(&self) -> u32 { + let fsel = self.registers.clkcfg.read(CLKCFG::FSEL); + let div3 = self.registers.clkcfg.read(CLKCFG::DIV3); + let diven = self.registers.clkcfg.read(CLKCFG::DIVEN); + let tot_per = self.registers.clkcfg.read(CLKCFG::TOTPER) + 1; + + let denom_final = (1 << (fsel - 1)) * (1 + div3 * 2) * (1 + diven * (tot_per)); + + if ((48000000) % denom_final) > (denom_final / 2) { + (48000000 / denom_final) + 1 + } else { + 48000000 / denom_final + } + } + + fn set_polarity(&self, polarity: ClockPolarity) -> Result<(), ErrorCode> { + if self.op.get() != Operation::SPI && self.op.get() != Operation::None { + return Err(ErrorCode::BUSY); + } + + if polarity == ClockPolarity::IdleLow { + self.registers.mspicfg.modify(MSPICFG::SPOL::CLEAR); + } else { + self.registers.mspicfg.modify(MSPICFG::SPOL::SET); + } + + Ok(()) + } + + fn get_polarity(&self) -> ClockPolarity { + if self.registers.mspicfg.is_set(MSPICFG::SPOL) { + ClockPolarity::IdleHigh + } else { + ClockPolarity::IdleLow + } + } + + fn set_phase(&self, phase: ClockPhase) -> Result<(), ErrorCode> { + if self.op.get() != Operation::SPI && self.op.get() != Operation::None { + return Err(ErrorCode::BUSY); + } + + let low_per = if self.spi_phase.get() == ClockPhase::SampleLeading { + (self.registers.clkcfg.read(CLKCFG::LOWPER) * 2) + 1 + } else { + (self.registers.clkcfg.read(CLKCFG::LOWPER) * 2) + 2 + }; + + if phase == ClockPhase::SampleLeading { + self.registers + .clkcfg + .modify(CLKCFG::LOWPER.val((low_per - 1) / 2)); + } else { + self.registers + .clkcfg + .modify(CLKCFG::LOWPER.val((low_per - 2) / 2)); + } + + self.spi_phase.set(phase); + + Ok(()) + } + + fn get_phase(&self) -> ClockPhase { + self.spi_phase.get() + } + + fn hold_low(&self) { + self.spi_cs.map(|cs| cs.clear()); + } + + fn release_low(&self) { + self.spi_cs.map(|cs| cs.set()); } } diff --git a/chips/apollo3/src/ios.rs b/chips/apollo3/src/ios.rs new file mode 100644 index 0000000000..447822f9cf --- /dev/null +++ b/chips/apollo3/src/ios.rs @@ -0,0 +1,325 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! IO Slave Driver (I2C and SPI) +//! +//! This file provides support for the Apollo3 IOS. The IOS is a little +//! strange in that I2C operations go straight to a local RAM area. +//! +//! The first byte of data (after the I2C address and operation byte) +//! will be interpreteted as an address offset by the hardware. +//! +//! If the offset was between 0x00 and 0x77 the data will be in the +//! RAM which we can access from 0x5000_0000 to 0x5000_0077. This +//! will generate the XCMPWR interrupt on writes from the master. +//! +//! If the offset was the following it is written to the interrupt +//! or FIFO registers. This will generate a XCMPWF on writes from the +//! master: +//! - 0x78-7B -> IOINT Regs +//! - 0x7C -> FIFOCTRLO +//! - 0x7D -> FIFOCTRUP +//! - 0x7F -> FIFO (DATA) +//! +//! Unfortunately we have no way to know where the data was written. +//! +//! We currently don't support the FIFO registers. As there is no way +//! to know where the data was written we assume it was written to offset +//! 0x0F. This is the first non interrupt flag generating address. +//! This also matches the first byte of a MCTP packet. +//! +//! So, if you would like to write data to this device, the first byte of +//! data must be 0x0F. + +use crate::ios::i2c::SlaveTransmissionType; +use core::cell::Cell; +use kernel::debug; +use kernel::hil::i2c::{self, Error, I2CHwSlaveClient, I2CSlave}; +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::cells::TakeCell; +use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; +use kernel::utilities::registers::{register_bitfields, register_structs, ReadOnly, ReadWrite}; +use kernel::utilities::StaticRef; + +const SRAM_ROBASE_OFFSET: u32 = 0x78; + +const IOS_BASE: StaticRef = + unsafe { StaticRef::new(0x5000_0100 as *const IosRegisters) }; + +register_structs! { + pub IosRegisters { + (0x000 => fifoptr: ReadWrite), + (0x004 => fifocfg: ReadWrite), + (0x008 => fifothr: ReadWrite), + (0x00C => fupd: ReadWrite), + (0x010 => fifoctr: ReadWrite), + (0x014 => fifoinc : ReadWrite), + (0x018 => cfg: ReadWrite), + (0x01C => prenc: ReadWrite), + (0x020 => iointctl: ReadWrite), + (0x024 => genadd: ReadOnly), + (0x028 => _reserved2), + (0x100 => inten: ReadWrite), + (0x104 => intstat: ReadWrite), + (0x108 => intclr: ReadWrite), + (0x10C => intset: ReadWrite), + (0x110 => regaccinten: ReadWrite), + (0x114 => regaccintstat: ReadWrite), + (0x118 => regaccintclr: ReadWrite), + (0x11C => regaccintset: ReadWrite), + (0x120 => @END), + } +} + +register_bitfields![u32, + FIFOPTR [ + FIFOPTR OFFSET(0) NUMBITS(8) [], + FIFOSIZ OFFSET(8) NUMBITS(8) [] + ], + FIFOCFG [ + FIFOBASE OFFSET(0) NUMBITS(5) [], + FIFOMAX OFFSET(8) NUMBITS(6) [], + ROBASE OFFSET(24) NUMBITS(6) [], + ], + FIFOTHR [ + FIFOTHR OFFSET(8) NUMBITS(8) [] + ], + FUPD [ + FIFOUPD OFFSET(0) NUMBITS(1) [], + IOREAD OFFSET(1) NUMBITS(1) [] + ], + FIFOCTR [ + FIFOCTR OFFSET(0) NUMBITS(10) [] + ], + FIFOINC [ + FIFOINC OFFSET(0) NUMBITS(10) [] + ], + CFG [ + IFCSEL OFFSET(0) NUMBITS(1) [], + SPOL OFFSET(1) NUMBITS(1) [], + LSB OFFSET(2) NUMBITS(1) [], + STARTRD OFFSET(4) NUMBITS(1) [], + I2CADDR OFFSET(8) NUMBITS(11) [], + IFCEN OFFSET(31) NUMBITS(1) [] + ], + PRENC [ + PRENC OFFSET(0) NUMBITS(5) [] + ], + IOINTCTL [ + IOINTEN OFFSET(0) NUMBITS(8) [], + IOINT OFFSET(8) NUMBITS(8) [], + IOINTCLR OFFSET(16) NUMBITS(1) [], + IOINTSET OFFSET(24) NUMBITS(8) [] + ], + GENADD [ + GADATA OFFSET(0) NUMBITS(8) [], + ], + INT [ + FSIZE OFFSET(0) NUMBITS(1) [], + FOVFL OFFSET(1) NUMBITS(1) [], + FUNDFL OFFSET(2) NUMBITS(1) [], + FRDERR OFFSET(3) NUMBITS(1) [], + GENAD OFFSET(4) NUMBITS(1) [], + IOINTW OFFSET(5) NUMBITS(1) [], + XCMPRF OFFSET(6) NUMBITS(1) [], + XCMPRR OFFSET(7) NUMBITS(1) [], + XCMPWF OFFSET(8) NUMBITS(1) [], + XCMPWR OFFSET(9) NUMBITS(1) [] + ], + REGACC [ + REGACC OFFSET(0) NUMBITS(32) [] + ] +]; + +#[derive(Clone, Copy, PartialEq, Debug)] +enum Operation { + None, + I2C, +} + +pub struct Ios<'a> { + registers: StaticRef, + + i2c_slave_client: OptionalCell<&'a dyn I2CHwSlaveClient>, + + write_buf: TakeCell<'static, [u8]>, + write_len: Cell, + read_buf: TakeCell<'static, [u8]>, + read_len: Cell, + op: Cell, +} + +impl<'a> Ios<'_> { + pub fn new() -> Ios<'a> { + Ios { + registers: IOS_BASE, + i2c_slave_client: OptionalCell::empty(), + write_buf: TakeCell::empty(), + write_len: Cell::new(0), + read_buf: TakeCell::empty(), + read_len: Cell::new(0), + op: Cell::new(Operation::None), + } + } + + fn i2c_interface_enable(&self) { + let regs = self.registers; + + regs.cfg.modify(CFG::IFCEN::SET); + regs.cfg.modify(CFG::IFCSEL::CLEAR); + } + + pub fn handle_interrupt(&self) { + let irqs = self.registers.intstat.extract(); + + // Clear interrrupts + self.registers.intclr.set(0xFFFF_FFFF); + self.registers.regaccintclr.set(0xFFFF_FFFF); + // Ensure interrupts remain enabled + self.registers.inten.set(0xFFFF_FFFF); + self.registers.regaccinten.set(0xFFFF_FFFF); + + let _offset = self.registers.fifoptr.read(FIFOPTR::FIFOPTR); + + if irqs.is_set(INT::XCMPWR) { + // If we get here that means the I2C master has written something + // addressed to us and the offset is in the "Direct Area", between + // 0x00 and 0x77. + // + // Unfortunately we have no way to know where the data was written, + // so we assume it starts at 0x0F. + let len = (SRAM_ROBASE_OFFSET as usize).min(self.write_len.get()); + + self.write_buf.take().map(|buf| { + buf[0] = 0x0F; + + for i in 1..len { + unsafe { + buf[i] = *((0x5000_000F + (i as u32 - 1)) as *mut u8); + // Zero the data after we read it + *((0x5000_000F + (i as u32 - 1)) as *mut u8) = 0x00; + } + } + + self.i2c_slave_client.get().map(|client| { + client.command_complete(buf, len, SlaveTransmissionType::Write); + }); + }); + } + + if irqs.is_set(INT::XCMPWF) { + // If we get here that means the I2C master has written something + // addressed to us and the offset is in the "FIFO Area", 0x7F + + // We currently don't support the FIFO area. We have no way + // to report errors, so let's just print something. + + debug!("Write to the FIFO area, which is not currently supported"); + } + } +} + +impl<'a> I2CSlave<'a> for Ios<'a> { + fn set_slave_client(&self, slave_client: &'a dyn i2c::I2CHwSlaveClient) { + self.i2c_slave_client.set(slave_client); + } + + fn enable(&self) { + self.op.set(Operation::I2C); + + // Eliminate the "read-only" section, so an external host can use the + // entire "direct write" section. + self.registers + .fifocfg + .modify(FIFOCFG::ROBASE.val(SRAM_ROBASE_OFFSET / 8)); + + // Set the FIFO base to the maximum value, making the "direct write" + // section as big as possible. + self.registers + .fifocfg + .modify(FIFOCFG::FIFOBASE.val(SRAM_ROBASE_OFFSET / 8)); + + // We don't need any RAM space, so extend the FIFO all the way to the end + // of the LRAM. + self.registers + .fifocfg + .modify(FIFOCFG::FIFOMAX.val(0x100 / 8)); + + // Clear FIFOs + self.registers.fifoctr.modify(FIFOCTR::FIFOCTR.val(0x00)); + self.registers.fifoptr.modify(FIFOPTR::FIFOSIZ.val(0x00)); + + // Setup FIFO interrupt threshold + self.registers.fifothr.modify(FIFOTHR::FIFOTHR.val(0x08)); + + self.i2c_interface_enable(); + + // Clear interrrupts + self.registers.intclr.set(0xFFFF_FFFF); + + // Update the FIFO + self.registers.fupd.modify(FUPD::FIFOUPD::SET); + self.registers.fifoptr.modify(FIFOPTR::FIFOPTR.val(0x80)); + self.registers.fupd.modify(FUPD::FIFOUPD::CLEAR); + } + + fn disable(&self) { + if self.op.get() == Operation::I2C { + self.registers.cfg.modify(CFG::IFCEN::CLEAR); + + self.op.set(Operation::None); + } + } + + fn set_address(&self, addr: u8) -> Result<(), Error> { + self.registers + .cfg + .modify(CFG::I2CADDR.val((addr as u32) << 1)); + + Ok(()) + } + + fn write_receive( + &self, + data: &'static mut [u8], + max_len: usize, + ) -> Result<(), (Error, &'static mut [u8])> { + self.write_len.set(max_len.min(data.len())); + self.write_buf.replace(data); + + Ok(()) + } + + fn read_send( + &self, + data: &'static mut [u8], + max_len: usize, + ) -> Result<(), (Error, &'static mut [u8])> { + for (i, d) in data.iter().enumerate() { + unsafe { + *((0x5000_0000 + 0x7F + (i as u32)) as *mut u8) = *d; + } + } + + self.read_len.set(max_len.min(data.len())); + self.read_buf.replace(data); + + Ok(()) + } + + fn listen(&self) { + self.registers.inten.modify( + INT::FSIZE::SET + + INT::FOVFL::SET + + INT::FUNDFL::SET + + INT::FRDERR::SET + + INT::GENAD::SET + + INT::IOINTW::SET + + INT::XCMPRF::SET + + INT::XCMPRF::SET + + INT::XCMPWF::SET + + INT::XCMPWR::SET, + ); + } +} diff --git a/chips/apollo3/src/lib.rs b/chips/apollo3/src/lib.rs index 7043bffd16..b8744db126 100644 --- a/chips/apollo3/src/lib.rs +++ b/chips/apollo3/src/lib.rs @@ -1,8 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Peripheral implementations for the Apollo3 MCU. #![crate_name = "apollo3"] #![crate_type = "rlib"] -#![feature(asm, const_fn_trait_bound)] #![no_std] // Peripherals @@ -12,16 +15,14 @@ pub mod chip; pub mod clkgen; pub mod gpio; pub mod iom; +pub mod ios; pub mod mcuctrl; pub mod nvic; pub mod pwrctrl; pub mod stimer; pub mod uart; -use cortexm4::{ - generic_isr, hard_fault_handler, initialize_ram_jump_to_main, scb, svc_handler, - systick_handler, unhandled_interrupt, -}; +use cortexm4::{initialize_ram_jump_to_main, scb, unhandled_interrupt, CortexM4, CortexMVariant}; extern "C" { // _estack is not really a function, but it makes the types work @@ -38,20 +39,20 @@ extern "C" { pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ _estack, initialize_ram_jump_to_main, - unhandled_interrupt, // NMI - hard_fault_handler, // Hard Fault - unhandled_interrupt, // MemManage - unhandled_interrupt, // BusFault - unhandled_interrupt, // UsageFault + unhandled_interrupt, // NMI + CortexM4::HARD_FAULT_HANDLER, // Hard Fault + unhandled_interrupt, // MemManage + unhandled_interrupt, // BusFault + unhandled_interrupt, // UsageFault unhandled_interrupt, unhandled_interrupt, unhandled_interrupt, unhandled_interrupt, - svc_handler, // SVC - unhandled_interrupt, // DebugMon + CortexM4::SVC_HANDLER, // SVC + unhandled_interrupt, // DebugMon unhandled_interrupt, - unhandled_interrupt, // PendSV - systick_handler, // SysTick + unhandled_interrupt, // PendSV + CortexM4::SYSTICK_HANDLER, // SysTick ]; #[cfg_attr( @@ -60,7 +61,7 @@ pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ )] // used Ensures that the symbol is kept until the final binary #[cfg_attr(all(target_arch = "arm", target_os = "none"), used)] -pub static IRQS: [unsafe extern "C" fn(); 32] = [generic_isr; 32]; +pub static IRQS: [unsafe extern "C" fn(); 32] = [CortexM4::GENERIC_ISR; 32]; // The Patch table. // @@ -74,8 +75,12 @@ pub static IRQS: [unsafe extern "C" fn(); 32] = [generic_isr; 32]; #[cfg_attr(all(target_arch = "arm", target_os = "none"), used)] pub static PATCH: [unsafe extern "C" fn(); 16] = [unhandled_interrupt; 16]; +// The SVC call in this function means that we need to ensure it's inlined in +// `main()` otherwise we end up with a clobbered stack. #[cfg(all(target_arch = "arm", target_os = "none"))] +#[inline(always)] pub unsafe fn init() { + use core::arch::asm; let cache_ctrl = crate::cachectrl::CacheCtrl::new(); cache_ctrl.enable_cache(); @@ -99,7 +104,7 @@ pub unsafe fn init() { } // Mock implementation for tests -#[cfg(not(any(target_arch = "arm", target_os = "none")))] +#[cfg(not(all(target_arch = "arm", target_os = "none")))] pub unsafe fn init() { // Prevent unused code warning. scb::disable_fpca(); diff --git a/chips/apollo3/src/mcuctrl.rs b/chips/apollo3/src/mcuctrl.rs index 6be5a11efa..fd66d1438d 100644 --- a/chips/apollo3/src/mcuctrl.rs +++ b/chips/apollo3/src/mcuctrl.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! MCU Control driver. use kernel::debug; diff --git a/chips/apollo3/src/nvic.rs b/chips/apollo3/src/nvic.rs index 083bc61d87..ec79c19f78 100644 --- a/chips/apollo3/src/nvic.rs +++ b/chips/apollo3/src/nvic.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Named constants for NVIC ids shared across the Apollo3 family of chips pub const BROWNOUT: u32 = 0; diff --git a/chips/apollo3/src/pwrctrl.rs b/chips/apollo3/src/pwrctrl.rs index e06357cc55..9cb68ebde7 100644 --- a/chips/apollo3/src/pwrctrl.rs +++ b/chips/apollo3/src/pwrctrl.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Power Control driver. use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; @@ -83,12 +87,42 @@ impl PwrCtrl { regs.devpwren.modify(DEVPWREN::PWRUART0::SET); } + pub fn enable_ios(&self) { + let regs = self.registers; + + regs.devpwren.modify(DEVPWREN::PWRIOS::SET); + } + + pub fn enable_iom0(&self) { + let regs = self.registers; + + regs.devpwren.modify(DEVPWREN::PWRIOM0::SET); + } + + pub fn enable_iom1(&self) { + let regs = self.registers; + + regs.devpwren.modify(DEVPWREN::PWRIOM1::SET); + } + pub fn enable_iom2(&self) { let regs = self.registers; regs.devpwren.modify(DEVPWREN::PWRIOM2::SET); } + pub fn enable_iom3(&self) { + let regs = self.registers; + + regs.devpwren.modify(DEVPWREN::PWRIOM3::SET); + } + + pub fn enable_iom4(&self) { + let regs = self.registers; + + regs.devpwren.modify(DEVPWREN::PWRIOM4::SET); + } + pub fn enable_ble(&self) { let regs = self.registers; diff --git a/chips/apollo3/src/stimer.rs b/chips/apollo3/src/stimer.rs index c5765fcdf1..e137cbf35f 100644 --- a/chips/apollo3/src/stimer.rs +++ b/chips/apollo3/src/stimer.rs @@ -1,14 +1,17 @@ -//! STimer driver for the Apollo3 +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. -use kernel::utilities::cells::OptionalCell; -use kernel::ErrorCode; +//! STimer driver for the Apollo3 use kernel::hil::time::{ Alarm, AlarmClient, Counter, Freq16KHz, OverflowClient, Ticks, Ticks32, Time, }; +use kernel::utilities::cells::OptionalCell; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite}; use kernel::utilities::StaticRef; +use kernel::ErrorCode; const STIMER_BASE: StaticRef = unsafe { StaticRef::new(0x4000_8000 as *const STimerRegisters) }; @@ -110,13 +113,16 @@ impl<'a> STimer<'_> { let regs = self.registers; // Disable timer - regs.stcfg.modify(STCFG::COMPARE_A_EN::CLEAR); + regs.stcfg + .modify(STCFG::COMPARE_A_EN::CLEAR + STCFG::COMPARE_B_EN::CLEAR); // Disable interrupt - regs.stminten.modify(STMINT::COMPAREA::CLEAR); + regs.stminten + .modify(STMINT::COMPAREA::CLEAR + STMINT::COMPAREB::CLEAR); // Clear interrupt - regs.stmintclr.modify(STMINT::COMPAREA::SET); + regs.stmintclr + .modify(STMINT::COMPAREA::SET + STMINT::COMPAREB::SET); self.client.map(|client| client.alarm()); } @@ -152,7 +158,7 @@ impl<'a> Counter<'a> for STimer<'a> { fn is_running(&self) -> bool { let regs = self.registers; - regs.stcfg.matches_any(STCFG::CLKSEL::XTAL_DIV2) + regs.stcfg.matches_any(&[STCFG::CLKSEL::XTAL_DIV2]) } } @@ -164,33 +170,56 @@ impl<'a> Alarm<'a> for STimer<'a> { fn set_alarm(&self, reference: Self::Ticks, dt: Self::Ticks) { let regs = self.registers; let now = self.now(); - let mut expire = reference.wrapping_add(dt); - if !now.within_range(reference, expire) { - expire = now; - } + // Errata 4.22: Sometimes the clock can increment twice + // This means the timer occurs earlier then actually requested + // From testing this scaling results in the correct time, so we + // scale the requested ticks to give us an accurate alarm. + let scaled_time = Self::Ticks::from(((dt.into_u32() as u64 * 1000) / (1000 - 32)) as u32); + let expire = reference.wrapping_add(scaled_time); + + // Disable the compare + regs.stcfg + .modify(STCFG::COMPARE_A_EN::CLEAR + STCFG::COMPARE_B_EN::CLEAR); // Enable interrupts - regs.stminten.modify(STMINT::COMPAREA::SET); + regs.stminten + .modify(STMINT::COMPAREA::SET + STMINT::COMPAREB::SET); + + // Check if the alarm has already expired or if it will expire before we set + // the compare. + if !now.within_range(reference, expire) || expire.wrapping_sub(now) < self.minimum_dt() { + // The alarm has already expired! + // Let's set the interrupt manually + regs.stcfg.modify(STCFG::COMPARE_A_EN::SET); + regs.stmintset.modify(STMINT::COMPAREA::SET); + return; + } // Set the delta, this can take a few goes - // See Errata 4.14 at at https://ambiqmicro.com/static/mcu/files/Apollo3_Blue_MCU_Errata_List_v2_0.pdf + // See Errata 4.14 at at https://ambiq.com/wp-content/uploads/2022/01/Apollo3-Blue-Errata-List.pdf let mut timer_delta = expire.wrapping_sub(now); let mut tries = 0; - if timer_delta < self.minimum_dt() { - timer_delta = self.minimum_dt(); - expire = now.wrapping_add(timer_delta); - } - // Apollo3 Blue Datasheet 14.1: 'Only offsets from "NOW" are written to // comparator registers.' while Self::Ticks::from(regs.scmpr[0].get()) != expire && tries < 5 { regs.scmpr[0].set(timer_delta.into_u32()); - tries = tries + 1; + tries += 1; + } + + // Timers can be missed, so set a second one a little larger + // See Errata 4.22 at at https://ambiq.com/wp-content/uploads/2022/01/Apollo3-Blue-Errata-List.pdf + timer_delta = timer_delta.wrapping_add(1.into()); + tries = 0; + + while Self::Ticks::from(regs.scmpr[1].get()) != expire && tries < 5 { + regs.scmpr[1].set(timer_delta.into_u32()); + tries += 1; } // Enable the compare - regs.stcfg.modify(STCFG::COMPARE_A_EN::SET); + regs.stcfg + .modify(STCFG::COMPARE_A_EN::SET + STCFG::COMPARE_B_EN::SET); } fn get_alarm(&self) -> Self::Ticks { @@ -221,6 +250,6 @@ impl<'a> Alarm<'a> for STimer<'a> { } fn minimum_dt(&self) -> Self::Ticks { - Self::Ticks::from(2) + Self::Ticks::from(5) } } diff --git a/chips/apollo3/src/uart.rs b/chips/apollo3/src/uart.rs index 74a7e18c56..08d411cfa4 100644 --- a/chips/apollo3/src/uart.rs +++ b/chips/apollo3/src/uart.rs @@ -1,9 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! UART driver. use core::cell::Cell; use kernel::ErrorCode; use kernel::hil; +use kernel::hil::uart; use kernel::utilities::cells::OptionalCell; use kernel::utilities::cells::TakeCell; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; @@ -168,6 +173,10 @@ pub struct Uart<'a> { tx_buffer: TakeCell<'static, [u8]>, tx_len: Cell, tx_index: Cell, + + rx_buffer: TakeCell<'static, [u8]>, + rx_len: Cell, + rx_index: Cell, } #[derive(Copy, Clone)] @@ -177,7 +186,7 @@ pub struct UartParams { impl Uart<'_> { // unsafe bc of UART0_BASE usage, called twice would alias location - pub const fn new_uart_0() -> Self { + pub fn new_uart_0() -> Self { Self { registers: UART0_BASE, clock_frequency: 24_000_000, @@ -186,11 +195,14 @@ impl Uart<'_> { tx_buffer: TakeCell::empty(), tx_len: Cell::new(0), tx_index: Cell::new(0), + rx_buffer: TakeCell::empty(), + rx_len: Cell::new(0), + rx_index: Cell::new(0), } } // unsafe bc of UART0_BASE usage, called twice would alias location - pub const fn new_uart_1() -> Self { + pub fn new_uart_1() -> Self { Self { registers: UART1_BASE, clock_frequency: 24_000_000, @@ -199,6 +211,9 @@ impl Uart<'_> { tx_buffer: TakeCell::empty(), tx_len: Cell::new(0), tx_index: Cell::new(0), + rx_buffer: TakeCell::empty(), + rx_len: Cell::new(0), + rx_index: Cell::new(0), } } @@ -227,7 +242,23 @@ impl Uart<'_> { let regs = self.registers; regs.ier.modify(IER::TXIM::CLEAR + IER::TXCMPMIM::CLEAR); - regs.iec.modify(IEC::TXIC::SET); + regs.iec.modify(IEC::TXIC::SET + IEC::TXCMPMMIC::SET); + } + + fn enable_rx_interrupt(&self) { + let regs = self.registers; + + // Set RX FIFO to fire at 1 + regs.ifls.modify(IFLS::RXIFLSEL.val(1)); + + regs.ier.modify(IER::RXIM::SET + IER::RTIM::SET); + } + + fn disable_rx_interrupt(&self) { + let regs = self.registers; + + regs.ier.modify(IER::RXIM::CLEAR + IER::RTIM::CLEAR); + regs.iec.modify(IEC::RXIC::SET + IEC::RTIC::SET); } fn tx_progress(&self) { @@ -259,6 +290,38 @@ impl Uart<'_> { } } + fn rx_progress(&self) { + let regs = self.registers; + let idx = self.rx_index.get(); + let len = self.rx_len.get(); + + if idx < len { + // Read from the transmit buffer and send bytes to the UART hardware + // until either the buffer is empty or the UART hardware is full. + self.rx_buffer.map(|rx_buf| { + let rx_len = len - idx; + + for i in 0..rx_len { + if regs.fr.is_set(FR::RXFE) { + break; + } + + let rx_idx = idx + i; + let charecter = regs.dr.read(DR::DATA); + + rx_buf[rx_idx] = charecter as u8; + + self.rx_index.set(rx_idx + 1) + } + }); + } + + if self.rx_index.get() < self.rx_len.get() { + // Enable interrupts to get future events + self.enable_rx_interrupt(); + } + } + pub fn handle_interrupt(&self) { let regs = self.registers; let irq = regs.ies.extract(); @@ -280,6 +343,25 @@ impl Uart<'_> { self.tx_progress(); } } + + if irq.is_set(IES::RXIS) || irq.is_set(IES::RTIS) { + self.disable_rx_interrupt(); + + self.rx_progress(); + + if self.rx_index.get() >= self.rx_len.get() { + self.rx_client.map(|client| { + self.rx_buffer.take().map(|rx_buf| { + client.received_buffer( + rx_buf, + self.rx_len.get(), + Ok(()), + uart::Error::None, + ); + }); + }); + } + } } pub fn transmit_sync(&self, bytes: &[u8]) { @@ -300,7 +382,7 @@ impl hil::uart::Configure for Uart<'_> { .write(CR::UARTEN::CLEAR + CR::RXE::CLEAR + CR::TXE::CLEAR); // Enable the clocks - regs.cr.write(CR::CLKEN::SET + CR::CLKSEL::CLK_24MHZ); + regs.cr.modify(CR::CLKEN::SET + CR::CLKSEL::CLK_24MHZ); // Set the baud rate self.set_baud_rate(params.baud_rate); @@ -316,6 +398,10 @@ impl hil::uart::Configure for Uart<'_> { regs.cr .modify(CR::UARTEN::SET + CR::RXE::SET + CR::TXE::SET); + // Disable interrupts + regs.ier.set(0x00); + regs.iec.set(0xFF); + Ok(()) } } @@ -362,9 +448,19 @@ impl<'a> hil::uart::Receive<'a> for Uart<'a> { fn receive_buffer( &self, rx_buffer: &'static mut [u8], - _rx_len: usize, + rx_len: usize, ) -> Result<(), (ErrorCode, &'static mut [u8])> { - Err((ErrorCode::FAIL, rx_buffer)) + if rx_len == 0 || rx_len > rx_buffer.len() { + Err((ErrorCode::SIZE, rx_buffer)) + } else { + // Save the buffer so we can keep sending it. + self.rx_buffer.replace(rx_buffer); + self.rx_len.set(rx_len); + self.rx_index.set(0); + + self.rx_progress(); + Ok(()) + } } fn receive_abort(&self) -> Result<(), ErrorCode> { diff --git a/chips/arty_e21_chip/Cargo.toml b/chips/arty_e21_chip/Cargo.toml index 88453caf59..0e1c4761ed 100644 --- a/chips/arty_e21_chip/Cargo.toml +++ b/chips/arty_e21_chip/Cargo.toml @@ -1,11 +1,18 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "arty_e21_chip" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] sifive = { path = "../sifive" } rv32i = { path = "../../arch/rv32i" } kernel = { path = "../../kernel" } + +[lints] +workspace = true diff --git a/chips/arty_e21_chip/src/chip.rs b/chips/arty_e21_chip/src/chip.rs index 6ff85dff79..4f8abdefe3 100644 --- a/chips/arty_e21_chip/src/chip.rs +++ b/chips/arty_e21_chip/src/chip.rs @@ -1,28 +1,33 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; -use kernel; use kernel::debug; +use kernel::hil::time::Freq32KHz; use kernel::platform::chip::InterruptService; use kernel::utilities::registers::interfaces::Readable; -use rv32i; use crate::clint; use crate::interrupts; -use rv32i::pmp::PMP; +use rv32i::pmp::{simple::SimplePMP, PMPUserMPU}; extern "C" { fn _start_trap(); } -pub struct ArtyExx<'a, I: InterruptService<()> + 'a> { - pmp: PMP<2>, +pub type ArtyExxClint<'a> = sifive::clint::Clint<'a, Freq32KHz>; + +pub struct ArtyExx<'a, I: InterruptService + 'a> { + pmp: PMPUserMPU<2, SimplePMP<4>>, userspace_kernel_boundary: rv32i::syscall::SysCall, clic: rv32i::clic::Clic, - machinetimer: &'a sifive::clint::Clint<'a>, + machinetimer: &'a ArtyExxClint<'a>, interrupt_service: &'a I, } pub struct ArtyExxDefaultPeripherals<'a> { - pub machinetimer: sifive::clint::Clint<'a>, + pub machinetimer: ArtyExxClint<'a>, pub gpio_port: crate::gpio::Port<'a>, pub uart0: sifive::uart::Uart<'a>, } @@ -30,14 +35,19 @@ pub struct ArtyExxDefaultPeripherals<'a> { impl<'a> ArtyExxDefaultPeripherals<'a> { pub fn new() -> Self { Self { - machinetimer: sifive::clint::Clint::new(&clint::CLINT_BASE), + machinetimer: ArtyExxClint::new(&clint::CLINT_BASE), gpio_port: crate::gpio::Port::new(), uart0: sifive::uart::Uart::new(crate::uart::UART0_BASE, 32_000_000), } } + + // Resolves any circular dependencies and sets up deferred calls + pub fn init(&'static self) { + kernel::deferred_call::DeferredCallClient::register(&self.uart0); + } } -impl<'a> InterruptService<()> for ArtyExxDefaultPeripherals<'a> { +impl<'a> InterruptService for ArtyExxDefaultPeripherals<'a> { unsafe fn service_interrupt(&self, interrupt: u32) -> bool { match interrupt { interrupts::MTIP => self.machinetimer.handle_interrupt(), @@ -65,24 +75,17 @@ impl<'a> InterruptService<()> for ArtyExxDefaultPeripherals<'a> { } true } - - unsafe fn service_deferred_call(&self, _: ()) -> bool { - false - } } -impl<'a, I: InterruptService<()> + 'a> ArtyExx<'a, I> { - pub unsafe fn new( - machinetimer: &'a sifive::clint::Clint<'a>, - interrupt_service: &'a I, - ) -> Self { +impl<'a, I: InterruptService + 'a> ArtyExx<'a, I> { + pub unsafe fn new(machinetimer: &'a ArtyExxClint<'a>, interrupt_service: &'a I) -> Self { // Make a bit-vector of all interrupt locations that we actually intend // to use on this chip. // 0001 1111 1111 1111 1111 0000 0000 1000 0000 let in_use_interrupts: u64 = 0x1FFFF0080; Self { - pmp: PMP::new(), + pmp: PMPUserMPU::new(SimplePMP::new().unwrap()), userspace_kernel_boundary: rv32i::syscall::SysCall::new(), clic: rv32i::clic::Clic::new(in_use_interrupts), machinetimer, @@ -109,6 +112,7 @@ impl<'a, I: InterruptService<()> + 'a> ArtyExx<'a, I> { /// valid for platforms with a CLIC. #[cfg(all(target_arch = "riscv32", target_os = "none"))] pub unsafe fn configure_trap_handler(&self) { + use core::arch::asm; asm!( " // The csrw instruction writes a Control and Status Register (CSR) @@ -128,7 +132,7 @@ impl<'a, I: InterruptService<()> + 'a> ArtyExx<'a, I> { } // Mock implementation for tests on Travis-CI. - #[cfg(not(any(target_arch = "riscv32", target_os = "none")))] + #[cfg(not(all(target_arch = "riscv32", target_os = "none")))] pub unsafe fn configure_trap_handler(&self) { unimplemented!() } @@ -142,8 +146,8 @@ impl<'a, I: InterruptService<()> + 'a> ArtyExx<'a, I> { } } -impl<'a, I: InterruptService<()> + 'a> kernel::platform::chip::Chip for ArtyExx<'a, I> { - type MPU = PMP<2>; +impl<'a, I: InterruptService + 'a> kernel::platform::chip::Chip for ArtyExx<'a, I> { + type MPU = PMPUserMPU<2, SimplePMP<4>>; type UserspaceKernelBoundary = rv32i::syscall::SysCall; fn mpu(&self) -> &Self::MPU { @@ -226,6 +230,6 @@ pub extern "C" fn disable_interrupt_trap_handler(mcause: u32) { // bits. let interrupt_index = mcause & 0xFF; unsafe { - rv32i::clic::disable_interrupt(interrupt_index as u32); + rv32i::clic::disable_interrupt(interrupt_index); } } diff --git a/chips/arty_e21_chip/src/clint.rs b/chips/arty_e21_chip/src/clint.rs index 2313fc61d4..f4a08112e9 100644 --- a/chips/arty_e21_chip/src/clint.rs +++ b/chips/arty_e21_chip/src/clint.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Machine Timer instantiation. use kernel::utilities::StaticRef; diff --git a/chips/arty_e21_chip/src/gpio.rs b/chips/arty_e21_chip/src/gpio.rs index 9604efeb40..84c97c4fd1 100644 --- a/chips/arty_e21_chip/src/gpio.rs +++ b/chips/arty_e21_chip/src/gpio.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::ops::{Index, IndexMut}; use kernel::utilities::StaticRef; diff --git a/chips/arty_e21_chip/src/interrupts.rs b/chips/arty_e21_chip/src/interrupts.rs index 011d076e17..e9dfd61bc2 100644 --- a/chips/arty_e21_chip/src/interrupts.rs +++ b/chips/arty_e21_chip/src/interrupts.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Named interrupts for the E21 Arty core. #![allow(dead_code)] diff --git a/chips/arty_e21_chip/src/lib.rs b/chips/arty_e21_chip/src/lib.rs index 99e9ea0112..62b6ca0d00 100644 --- a/chips/arty_e21_chip/src/lib.rs +++ b/chips/arty_e21_chip/src/lib.rs @@ -1,6 +1,9 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Drivers and chip support for the E21 soft core. -#![feature(asm)] #![no_std] #![crate_name = "arty_e21_chip"] #![crate_type = "rlib"] diff --git a/chips/arty_e21_chip/src/uart.rs b/chips/arty_e21_chip/src/uart.rs index 1a5ea3fd94..75b0a322a4 100644 --- a/chips/arty_e21_chip/src/uart.rs +++ b/chips/arty_e21_chip/src/uart.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::utilities::StaticRef; use sifive::uart::UartRegisters; diff --git a/chips/e310_g002/Cargo.toml b/chips/e310_g002/Cargo.toml new file mode 100644 index 0000000000..687d43a813 --- /dev/null +++ b/chips/e310_g002/Cargo.toml @@ -0,0 +1,18 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[package] +name = "e310_g002" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[dependencies] +sifive = { path = "../sifive" } +rv32i = { path = "../../arch/rv32i" } +kernel = { path = "../../kernel" } +e310x = { path = "../e310x" } + +[lints] +workspace = true diff --git a/chips/e310_g002/README.md b/chips/e310_g002/README.md new file mode 100644 index 0000000000..0213d30359 --- /dev/null +++ b/chips/e310_g002/README.md @@ -0,0 +1,4 @@ +HiFive Freedom E310-G002 MCU +======================= + +- https://www.sifive.com/documentation -> Chips -> Freedom E310-G002 diff --git a/chips/e310_g002/src/interrupt_service.rs b/chips/e310_g002/src/interrupt_service.rs new file mode 100644 index 0000000000..90d1a24848 --- /dev/null +++ b/chips/e310_g002/src/interrupt_service.rs @@ -0,0 +1,39 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::chip::E310xDefaultPeripherals; +use crate::interrupts; + +#[repr(transparent)] +pub struct E310G002DefaultPeripherals<'a> { + pub e310x: E310xDefaultPeripherals<'a>, +} + +impl<'a> E310G002DefaultPeripherals<'a> { + pub unsafe fn new(clock_frequency: u32) -> Self { + Self { + e310x: E310xDefaultPeripherals::new(clock_frequency), + } + } + + pub fn init(&'static self) { + self.e310x.init(); + } +} +impl<'a> kernel::platform::chip::InterruptService for E310G002DefaultPeripherals<'a> { + unsafe fn service_interrupt(&self, interrupt: u32) -> bool { + match interrupt { + interrupts::UART0 => self.e310x.uart0.handle_interrupt(), + interrupts::UART1 => self.e310x.uart1.handle_interrupt(), + int_pin @ interrupts::GPIO0..=interrupts::GPIO31 => { + let pin = &self.e310x.gpio_port[(int_pin - interrupts::GPIO0) as usize]; + pin.handle_interrupt(); + } + + // put E310x specific interrupts here + _ => return self.e310x.service_interrupt(interrupt), + } + true + } +} diff --git a/chips/e310x/src/interrupts.rs b/chips/e310_g002/src/interrupts.rs similarity index 83% rename from chips/e310x/src/interrupts.rs rename to chips/e310_g002/src/interrupts.rs index 4d4965a92c..a7f6ec5a48 100644 --- a/chips/e310x/src/interrupts.rs +++ b/chips/e310_g002/src/interrupts.rs @@ -1,4 +1,8 @@ -//! Named interrupts for the E310X chip. +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Named interrupts for the E310-G002 chip. #![allow(dead_code)] @@ -7,8 +11,8 @@ pub const RTC: u32 = 2; pub const UART0: u32 = 3; pub const UART1: u32 = 4; pub const QSPI0: u32 = 5; -pub const QSPI1: u32 = 6; -pub const QSPI2: u32 = 7; +pub const SPI1: u32 = 6; +pub const SPI2: u32 = 7; pub const GPIO0: u32 = 8; pub const GPIO1: u32 = 9; pub const GPIO2: u32 = 10; @@ -53,3 +57,4 @@ pub const PWM2CMP0: u32 = 48; pub const PWM2CMP1: u32 = 49; pub const PWM2CMP2: u32 = 50; pub const PWM2CMP3: u32 = 51; +pub const I2C: u32 = 52; diff --git a/chips/e310_g002/src/lib.rs b/chips/e310_g002/src/lib.rs new file mode 100644 index 0000000000..533b3b5a65 --- /dev/null +++ b/chips/e310_g002/src/lib.rs @@ -0,0 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Chip support for the E310-G002 from SiFive. + +#![no_std] +#![crate_name = "e310_g002"] +#![crate_type = "rlib"] + +pub use e310x::{chip, clint, gpio, plic, prci, pwm, rtc, uart, watchdog}; + +pub mod interrupt_service; +mod interrupts; diff --git a/chips/e310_g003/Cargo.toml b/chips/e310_g003/Cargo.toml new file mode 100644 index 0000000000..2f4473d1e7 --- /dev/null +++ b/chips/e310_g003/Cargo.toml @@ -0,0 +1,18 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[package] +name = "e310_g003" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[dependencies] +sifive = { path = "../sifive" } +rv32i = { path = "../../arch/rv32i" } +kernel = { path = "../../kernel" } +e310x = { path = "../e310x" } + +[lints] +workspace = true diff --git a/chips/e310_g003/README.md b/chips/e310_g003/README.md new file mode 100644 index 0000000000..45e6cddbe6 --- /dev/null +++ b/chips/e310_g003/README.md @@ -0,0 +1,4 @@ +HiFive Freedom E310-G003 MCU +======================= + +- https://www.sifive.com/documentation -> Chips -> Freedom E310-G003 diff --git a/chips/e310_g003/src/interrupt_service.rs b/chips/e310_g003/src/interrupt_service.rs new file mode 100644 index 0000000000..e3bd025fff --- /dev/null +++ b/chips/e310_g003/src/interrupt_service.rs @@ -0,0 +1,39 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::chip::E310xDefaultPeripherals; +use crate::interrupts; + +#[repr(transparent)] +pub struct E310G003DefaultPeripherals<'a> { + pub e310x: E310xDefaultPeripherals<'a>, +} + +impl<'a> E310G003DefaultPeripherals<'a> { + pub unsafe fn new(clock_frequency: u32) -> Self { + Self { + e310x: E310xDefaultPeripherals::new(clock_frequency), + } + } + + pub fn init(&'static self) { + self.e310x.init(); + } +} +impl<'a> kernel::platform::chip::InterruptService for E310G003DefaultPeripherals<'a> { + unsafe fn service_interrupt(&self, interrupt: u32) -> bool { + match interrupt { + interrupts::UART0 => self.e310x.uart0.handle_interrupt(), + interrupts::UART1 => self.e310x.uart1.handle_interrupt(), + int_pin @ interrupts::GPIO0..=interrupts::GPIO31 => { + let pin = &self.e310x.gpio_port[(int_pin - interrupts::GPIO0) as usize]; + pin.handle_interrupt(); + } + + // put E310x specific interrupts here + _ => return self.e310x.service_interrupt(interrupt), + } + true + } +} diff --git a/chips/e310_g003/src/interrupts.rs b/chips/e310_g003/src/interrupts.rs new file mode 100644 index 0000000000..9f3fc41b64 --- /dev/null +++ b/chips/e310_g003/src/interrupts.rs @@ -0,0 +1,60 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Named interrupts for the E310-G003 chip. + +#![allow(dead_code)] + +pub const GPIO0: u32 = 1; +pub const GPIO1: u32 = 2; +pub const GPIO2: u32 = 3; +pub const GPIO3: u32 = 4; +pub const GPIO4: u32 = 5; +pub const GPIO5: u32 = 6; +pub const GPIO6: u32 = 7; +pub const GPIO7: u32 = 8; +pub const GPIO8: u32 = 9; +pub const GPIO9: u32 = 10; +pub const GPIO10: u32 = 11; +pub const GPIO11: u32 = 12; +pub const GPIO12: u32 = 13; +pub const GPIO13: u32 = 14; +pub const GPIO14: u32 = 15; +pub const GPIO15: u32 = 16; +pub const GPIO16: u32 = 17; +pub const GPIO17: u32 = 18; +pub const GPIO18: u32 = 19; +pub const GPIO19: u32 = 20; +pub const GPIO20: u32 = 21; +pub const GPIO21: u32 = 22; +pub const GPIO22: u32 = 23; +pub const GPIO23: u32 = 24; +pub const GPIO24: u32 = 25; +pub const GPIO25: u32 = 26; +pub const GPIO26: u32 = 27; +pub const GPIO27: u32 = 28; +pub const GPIO28: u32 = 29; +pub const GPIO29: u32 = 30; +pub const GPIO30: u32 = 31; +pub const GPIO31: u32 = 32; +pub const UART0: u32 = 33; +pub const UART1: u32 = 34; +pub const QSPI0: u32 = 35; +pub const SPI1: u32 = 36; +pub const SPI2: u32 = 37; +pub const PWM0CMP0: u32 = 38; +pub const PWM0CMP1: u32 = 39; +pub const PWM0CMP2: u32 = 40; +pub const PWM0CMP3: u32 = 41; +pub const PWM1CMP0: u32 = 42; +pub const PWM1CMP1: u32 = 43; +pub const PWM1CMP2: u32 = 44; +pub const PWM1CMP3: u32 = 45; +pub const PWM2CMP0: u32 = 46; +pub const PWM2CMP1: u32 = 47; +pub const PWM2CMP2: u32 = 48; +pub const PWM2CMP3: u32 = 49; +pub const I2C: u32 = 50; +pub const WATCHDOG: u32 = 51; +pub const RTC: u32 = 52; diff --git a/chips/e310_g003/src/lib.rs b/chips/e310_g003/src/lib.rs new file mode 100644 index 0000000000..4f233fdab4 --- /dev/null +++ b/chips/e310_g003/src/lib.rs @@ -0,0 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Chip support for the E310-G003 from SiFive. + +#![no_std] +#![crate_name = "e310_g003"] +#![crate_type = "rlib"] + +pub use e310x::{chip, clint, gpio, plic, prci, pwm, rtc, uart, watchdog}; + +pub mod interrupt_service; +mod interrupts; diff --git a/chips/e310x/Cargo.toml b/chips/e310x/Cargo.toml index 43c67127c9..5557f889bc 100644 --- a/chips/e310x/Cargo.toml +++ b/chips/e310x/Cargo.toml @@ -1,11 +1,17 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "e310x" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] sifive = { path = "../sifive" } rv32i = { path = "../../arch/rv32i" } kernel = { path = "../../kernel" } +[lints] +workspace = true diff --git a/chips/e310x/src/chip.rs b/chips/e310x/src/chip.rs index f5bfa13116..4b5e06d356 100644 --- a/chips/e310x/src/chip.rs +++ b/chips/e310x/src/chip.rs @@ -1,29 +1,36 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! High-level setup and interrupt mapping for the chip. use core::fmt::Write; -use kernel; +use core::ptr::addr_of; use kernel::debug; use kernel::platform::chip::Chip; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; -use rv32i; +use rv32i::csr; use rv32i::csr::{mcause, mie::mie, mip::mip, CSR}; -use rv32i::pmp::PMP; +use rv32i::pmp::{simple::SimplePMP, PMPUserMPU}; -use crate::interrupts; -use crate::plic::Plic; use crate::plic::PLIC; +use kernel::hil::time::Freq32KHz; use kernel::platform::chip::InterruptService; +use sifive::plic::Plic; + +pub type E310xClint<'a> = sifive::clint::Clint<'a, Freq32KHz>; -pub struct E310x<'a, I: InterruptService<()> + 'a> { +pub struct E310x<'a, I: InterruptService + 'a> { userspace_kernel_boundary: rv32i::syscall::SysCall, - pmp: PMP<4>, + pmp: PMPUserMPU<4, SimplePMP<8>>, plic: &'a Plic, - timer: &'a sifive::clint::Clint<'a>, + timer: &'a E310xClint<'a>, plic_interrupt_service: &'a I, } pub struct E310xDefaultPeripherals<'a> { pub uart0: sifive::uart::Uart<'a>, + pub uart1: sifive::uart::Uart<'a>, pub gpio_port: crate::gpio::Port<'a>, pub prci: sifive::prci::Prci, pub pwm0: sifive::pwm::Pwm, @@ -34,9 +41,10 @@ pub struct E310xDefaultPeripherals<'a> { } impl<'a> E310xDefaultPeripherals<'a> { - pub fn new() -> Self { + pub fn new(clock_frequency: u32) -> Self { Self { - uart0: sifive::uart::Uart::new(crate::uart::UART0_BASE, 16_000_000), + uart0: sifive::uart::Uart::new(crate::uart::UART0_BASE, clock_frequency), + uart1: sifive::uart::Uart::new(crate::uart::UART1_BASE, clock_frequency), gpio_port: crate::gpio::Port::new(), prci: sifive::prci::Prci::new(crate::prci::PRCI_BASE), pwm0: sifive::pwm::Pwm::new(crate::pwm::PWM0_BASE), @@ -46,42 +54,50 @@ impl<'a> E310xDefaultPeripherals<'a> { watchdog: sifive::watchdog::Watchdog::new(crate::watchdog::WATCHDOG_BASE), } } -} -impl<'a> InterruptService<()> for E310xDefaultPeripherals<'a> { - unsafe fn service_interrupt(&self, interrupt: u32) -> bool { - match interrupt { - interrupts::UART0 => self.uart0.handle_interrupt(), - int_pin @ interrupts::GPIO0..=interrupts::GPIO31 => { - let pin = &self.gpio_port[(int_pin - interrupts::GPIO0) as usize]; - pin.handle_interrupt(); - } - - _ => return false, - } - true + // Resolve any circular dependencies and register deferred calls + pub fn init(&'static self) { + kernel::deferred_call::DeferredCallClient::register(&self.uart0); + kernel::deferred_call::DeferredCallClient::register(&self.uart1); } +} - unsafe fn service_deferred_call(&self, _: ()) -> bool { +impl<'a> InterruptService for E310xDefaultPeripherals<'a> { + unsafe fn service_interrupt(&self, _interrupt: u32) -> bool { false } } -impl<'a, I: InterruptService<()> + 'a> E310x<'a, I> { - pub unsafe fn new(plic_interrupt_service: &'a I, timer: &'a sifive::clint::Clint<'a>) -> Self { +impl<'a, I: InterruptService + 'a> E310x<'a, I> { + pub unsafe fn new(plic_interrupt_service: &'a I, timer: &'a E310xClint<'a>) -> Self { Self { userspace_kernel_boundary: rv32i::syscall::SysCall::new(), - pmp: PMP::new(), - plic: &PLIC, + pmp: PMPUserMPU::new(SimplePMP::new().unwrap()), + plic: &*addr_of!(PLIC), timer, plic_interrupt_service, } } pub unsafe fn enable_plic_interrupts(&self) { - self.plic.disable_all(); - self.plic.clear_all_pending(); + /* E31 core manual + * https://sifive.cdn.prismic.io/sifive/c29f9c69-5254-4f9a-9e18-24ea73f34e81_e31_core_complex_manual_21G2.pdf + * PLIC Chapter 9.4 p.114: A pending bit in the PLIC core can be cleared + * by setting the associated enable bit then performing a claim. + */ + + // first disable interrupts globally + let old_mie = csr::CSR + .mstatus + .read_and_clear_field(csr::mstatus::mstatus::mie); + self.plic.enable_all(); + self.plic.clear_all_pending(); + + // restore the old external interrupt enable bit + csr::CSR + .mstatus + .modify(csr::mstatus::mstatus::mie.val(old_mie)); } unsafe fn handle_plic_interrupts(&self) { @@ -96,8 +112,8 @@ impl<'a, I: InterruptService<()> + 'a> E310x<'a, I> { } } -impl<'a, I: InterruptService<()> + 'a> kernel::platform::chip::Chip for E310x<'a, I> { - type MPU = PMP<4>; +impl<'a, I: InterruptService + 'a> kernel::platform::chip::Chip for E310x<'a, I> { + type MPU = PMPUserMPU<4, SimplePMP<8>>; type UserspaceKernelBoundary = rv32i::syscall::SysCall; fn mpu(&self) -> &Self::MPU { @@ -121,7 +137,9 @@ impl<'a, I: InterruptService<()> + 'a> kernel::platform::chip::Chip for E310x<'a } } - if !mip.matches_any(mip::mtimer::SET) && self.plic.get_saved_interrupts().is_none() { + if !mip.any_matching_bits_set(mip::mtimer::SET) + && self.plic.get_saved_interrupts().is_none() + { break; } } diff --git a/chips/e310x/src/clint.rs b/chips/e310x/src/clint.rs index 2313fc61d4..f4a08112e9 100644 --- a/chips/e310x/src/clint.rs +++ b/chips/e310x/src/clint.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Machine Timer instantiation. use kernel::utilities::StaticRef; diff --git a/chips/e310x/src/gpio.rs b/chips/e310x/src/gpio.rs index 5a97024694..7f50efe2a9 100644 --- a/chips/e310x/src/gpio.rs +++ b/chips/e310x/src/gpio.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! GPIO instantiation. use core::ops::{Index, IndexMut}; diff --git a/chips/e310x/src/lib.rs b/chips/e310x/src/lib.rs index a58316a5bb..0b9ff32bb3 100644 --- a/chips/e310x/src/lib.rs +++ b/chips/e310x/src/lib.rs @@ -1,11 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Chip support for the E310 from SiFive. #![no_std] #![crate_name = "e310x"] #![crate_type = "rlib"] -mod interrupts; - pub mod chip; pub mod clint; pub mod gpio; diff --git a/chips/e310x/src/plic.rs b/chips/e310x/src/plic.rs index 824093ed54..e903e9d99d 100644 --- a/chips/e310x/src/plic.rs +++ b/chips/e310x/src/plic.rs @@ -1,148 +1,13 @@ -//! Platform Level Interrupt Control peripheral driver. +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Instantiation of the sifive Platform Level Interrupt Controller -use kernel::utilities::cells::VolatileCell; -use kernel::utilities::registers::interfaces::{Readable, Writeable}; -use kernel::utilities::registers::LocalRegisterCopy; -use kernel::utilities::registers::{register_bitfields, ReadWrite}; use kernel::utilities::StaticRef; +use sifive::plic::{Plic, PlicRegisters}; pub const PLIC_BASE: StaticRef = unsafe { StaticRef::new(0x0c00_0000 as *const PlicRegisters) }; pub static mut PLIC: Plic = Plic::new(PLIC_BASE); - -#[repr(C)] -pub struct PlicRegisters { - /// Interrupt Priority Register - _reserved0: u32, - priority: [ReadWrite; 51], - _reserved1: [u8; 3888], - /// Interrupt Pending Register - pending: [ReadWrite; 2], - _reserved2: [u8; 4088], - /// Interrupt Enable Register - enable: [ReadWrite; 2], - _reserved3: [u8; 2088952], - /// Priority Threshold Register - threshold: ReadWrite, - /// Claim/Complete Register - claim: ReadWrite, -} - -register_bitfields![u32, - priority [ - Priority OFFSET(0) NUMBITS(3) [] - ] -]; - -pub struct Plic { - registers: StaticRef, - saved: [VolatileCell>; 2], -} - -impl Plic { - pub const fn new(base: StaticRef) -> Self { - Plic { - registers: base, - saved: [ - VolatileCell::new(LocalRegisterCopy::new(0)), - VolatileCell::new(LocalRegisterCopy::new(0)), - ], - } - } - - /// Clear all pending interrupts. - pub fn clear_all_pending(&self) { - for pending in self.registers.pending.iter() { - pending.set(0); - } - } - - /// Enable all interrupts. - pub fn enable_all(&self) { - for enable in self.registers.enable.iter() { - enable.set(0xFFFF_FFFF); - } - - // Set some default priority for each interrupt. This is not really used - // at this point. - for priority in self.registers.priority.iter() { - priority.write(priority::Priority.val(4)); - } - - // Accept all interrupts. - self.registers.threshold.write(priority::Priority.val(0)); - } - - /// Disable all interrupts. - pub fn disable_all(&self) { - for enable in self.registers.enable.iter() { - enable.set(0); - } - } - - /// Get the index (0-256) of the lowest number pending interrupt, or `None` if - /// none is pending. RISC-V PLIC has a "claim" register which makes it easy - /// to grab the highest priority pending interrupt. - pub fn next_pending(&self) -> Option { - let claim = self.registers.claim.get(); - if claim == 0 { - None - } else { - Some(claim) - } - } - - /// Save the current interrupt to be handled later - /// This will save the interrupt at index internally to be handled later. - /// Interrupts must be disabled before this is called. - /// Saved interrupts can be retrieved by calling `get_saved_interrupts()`. - /// Saved interrupts are cleared when `'complete()` is called. - pub unsafe fn save_interrupt(&self, index: u32) { - let offset = if index < 32 { 0 } else { 1 }; - let irq = index % 32; - - // OR the current saved state with the new value - let new_saved = self.saved[offset].get().get() | 1 << irq; - - // Set the new state - self.saved[offset].set(LocalRegisterCopy::new(new_saved)); - } - - /// The `next_pending()` function will only return enabled interrupts. - /// This function will return a pending interrupt that has been disabled by - /// `save_interrupt()`. - pub fn get_saved_interrupts(&self) -> Option { - for (i, pending) in self.saved.iter().enumerate() { - let saved = pending.get().get(); - if saved != 0 { - return Some(saved.trailing_zeros() + (i as u32 * 32)); - } - } - - None - } - - /// Signal that an interrupt is finished being handled. In Tock, this should be - /// called from the normal main loop (not the interrupt handler). - /// Interrupts must be disabled before this is called. - pub unsafe fn complete(&self, index: u32) { - self.registers.claim.set(index); - - let offset = if index < 32 { 0 } else { 1 }; - let irq = index % 32; - - // OR the current saved state with the new value - let new_saved = self.saved[offset].get().get() & !(1 << irq); - - // Set the new state - self.saved[offset].set(LocalRegisterCopy::new(new_saved)); - } - - /// This is a generic implementation. There may be board specific versions as - /// some platforms have added more bits to the `mtvec` register. - pub fn suppress_all(&self) { - // Accept all interrupts. - self.registers.threshold.write(priority::Priority.val(0)); - } -} diff --git a/chips/e310x/src/prci.rs b/chips/e310x/src/prci.rs index c237cad252..108c26a745 100644 --- a/chips/e310x/src/prci.rs +++ b/chips/e310x/src/prci.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Power Reset Clock Interrupt controller instantiation. use kernel::utilities::StaticRef; diff --git a/chips/e310x/src/pwm.rs b/chips/e310x/src/pwm.rs index 22b1cb464f..10ef3df2bb 100644 --- a/chips/e310x/src/pwm.rs +++ b/chips/e310x/src/pwm.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! PWM instantiation. use kernel::utilities::StaticRef; diff --git a/chips/e310x/src/rtc.rs b/chips/e310x/src/rtc.rs index 419c6bc4aa..7f9f0adb29 100644 --- a/chips/e310x/src/rtc.rs +++ b/chips/e310x/src/rtc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! RTC instantiation. use kernel::utilities::StaticRef; diff --git a/chips/e310x/src/uart.rs b/chips/e310x/src/uart.rs index f67c74a584..07ae042a18 100644 --- a/chips/e310x/src/uart.rs +++ b/chips/e310x/src/uart.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! UART instantiation. use kernel::utilities::StaticRef; @@ -5,3 +9,6 @@ use sifive::uart::UartRegisters; pub const UART0_BASE: StaticRef = unsafe { StaticRef::new(0x1001_3000 as *const UartRegisters) }; + +pub const UART1_BASE: StaticRef = + unsafe { StaticRef::new(0x1002_3000 as *const UartRegisters) }; diff --git a/chips/e310x/src/watchdog.rs b/chips/e310x/src/watchdog.rs index 92a1f5ad49..674f790736 100644 --- a/chips/e310x/src/watchdog.rs +++ b/chips/e310x/src/watchdog.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Watchdog registers. use kernel::utilities::StaticRef; diff --git a/chips/earlgrey/Cargo.toml b/chips/earlgrey/Cargo.toml index 0839e4ade6..1972dddcc0 100644 --- a/chips/earlgrey/Cargo.toml +++ b/chips/earlgrey/Cargo.toml @@ -1,19 +1,18 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "earlgrey" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" - -[features] -# Compiling this crate requires enabling one of these features, otherwise -# the default will be chosen. -config_fpga_nexysvideo = ["config_disable_default"] -config_fpga_cw310 = ["config_disable_default"] -config_sim_verilator = ["config_disable_default"] -config_disable_default = [] +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] lowrisc = { path = "../lowrisc" } rv32i = { path = "../../arch/rv32i" } kernel = { path = "../../kernel" } + +[lints] +workspace = true diff --git a/chips/earlgrey/src/aes.rs b/chips/earlgrey/src/aes.rs index d0770d2b54..66eba02c94 100644 --- a/chips/earlgrey/src/aes.rs +++ b/chips/earlgrey/src/aes.rs @@ -1,11 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Support for the AES hardware block on OpenTitan //! //! +use crate::registers::top_earlgrey::AES_BASE_ADDR; use core::cell::Cell; -use kernel::dynamic_deferred_call::{ - DeferredCallHandle, DynamicDeferredCall, DynamicDeferredCallClient, -}; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; use kernel::hil; use kernel::hil::symmetric_encryption; use kernel::hil::symmetric_encryption::{AES128_BLOCK_SIZE, AES128_KEY_SIZE}; @@ -51,9 +54,11 @@ register_structs! { (0x6C => data_out2: ReadOnly), (0x70 => data_out3: ReadOnly), (0x74 => ctrl: ReadWrite), - (0x78 => trigger: WriteOnly), - (0x7C => status: ReadOnly), - (0x80 => @END), + (0x78 => ctrl_aux: ReadWrite), + (0x7C => ctrl_aux_regwen: ReadWrite), + (0x80 => trigger: WriteOnly), + (0x84 => status: ReadOnly), + (0x88 => @END), } } @@ -63,11 +68,11 @@ register_bitfields![u32, FATAL_FAULT OFFSET(1) NUMBITS(1) [], ], CTRL [ - OPERATION OFFSET(0) NUMBITS(1) [ - Encrypting = 0, - Decrypting = 1, + OPERATION OFFSET(0) NUMBITS(2) [ + Encrypting = 1, + Decrypting = 2, ], - MODE OFFSET(1) NUMBITS(6) [ + MODE OFFSET(2) NUMBITS(6) [ AES_ECB = 1, AES_CBC = 2, AES_CFB = 4, @@ -75,13 +80,13 @@ register_bitfields![u32, AES_CTR = 16, AES_NONE = 32, ], - KEY_LEN OFFSET(7) NUMBITS(3) [ + KEY_LEN OFFSET(8) NUMBITS(3) [ Key128 = 1, Key192 = 2, Key256 = 4, ], - MANUAL_OPERATION OFFSET(10) NUMBITS(1) [], - FORCE_ZERO_MASKS OFFSET(11) NUMBITS(1) [], + MANUAL_OPERATION OFFSET(15) NUMBITS(1) [], + FORCE_ZERO_MASKS OFFSET(16) NUMBITS(1) [], ], TRIGGER [ START OFFSET(0) NUMBITS(1) [], @@ -110,7 +115,7 @@ enum Mode { // https://docs.opentitan.org/hw/top_earlgrey/doc/ const AES_BASE: StaticRef = - unsafe { StaticRef::new(0x4110_0000 as *const AesRegisters) }; + unsafe { StaticRef::new(AES_BASE_ADDR as *const AesRegisters) }; pub struct Aes<'a> { registers: StaticRef, @@ -120,31 +125,42 @@ pub struct Aes<'a> { dest: TakeCell<'static, [u8]>, mode: Cell, - deferred_call: Cell, - deferred_caller: &'static DynamicDeferredCall, - deferred_handle: OptionalCell, + deferred_call: DeferredCall, } impl<'a> Aes<'a> { - pub const fn new(deferred_caller: &'static DynamicDeferredCall) -> Aes<'a> { + pub fn new() -> Aes<'a> { Aes { registers: AES_BASE, client: OptionalCell::empty(), source: TakeCell::empty(), dest: TakeCell::empty(), mode: Cell::new(Mode::IDLE), - deferred_call: Cell::new(false), - deferred_caller, - deferred_handle: OptionalCell::empty(), + deferred_call: DeferredCall::new(), } } - pub fn initialise(&self, deferred_call_handle: DeferredCallHandle) { - self.deferred_handle.set(deferred_call_handle); + pub fn idle(&self) -> bool { + self.registers.status.is_set(STATUS::IDLE) } - fn idle(&self) -> bool { - self.registers.status.is_set(STATUS::IDLE) + /// Must wait for IDLE, for trigger to set. + /// On reset, AES unit will first reseed the internal PRNGs + /// for register clearing and masking via EDN, and then + /// clear all key, IV and data registers with pseudo-random data. + /// Only after this sequence has finished, the unit becomes idle + /// + /// NOTE: This is needed for Verilator, and is suggested by documentation + /// in general. + /// Refer: + fn wait_on_idle_ready(&self) -> Result<(), ErrorCode> { + for _i in 0..10000 { + if self.idle() { + return Ok(()); + } + } + // AES Busy + Err(ErrorCode::BUSY) } fn input_ready(&self) -> bool { @@ -264,7 +280,7 @@ impl<'a> Aes<'a> { self.wait_for_output_valid()?; self.read_block(i)?; - write_block = write_block + AES128_BLOCK_SIZE; + write_block += AES128_BLOCK_SIZE; } Ok(()) @@ -293,9 +309,7 @@ impl<'a> hil::symmetric_encryption::AES128<'a> for Aes<'a> { } fn set_iv(&self, iv: &[u8]) -> Result<(), ErrorCode> { - if !self.idle() { - return Err(ErrorCode::BUSY); - } + self.wait_on_idle_ready()?; if iv.len() != AES128_BLOCK_SIZE { return Err(ErrorCode::INVAL); @@ -321,9 +335,7 @@ impl<'a> hil::symmetric_encryption::AES128<'a> for Aes<'a> { } fn set_key(&self, key: &[u8]) -> Result<(), ErrorCode> { - if !self.idle() { - return Err(ErrorCode::BUSY); - } + self.wait_on_idle_ready()?; if key.len() != AES128_KEY_SIZE { return Err(ErrorCode::INVAL); @@ -397,7 +409,7 @@ impl<'a> hil::symmetric_encryption::AES128<'a> for Aes<'a> { } } - if self.deferred_call.get() { + if self.deferred_call.is_pending() { return Some(( Err(ErrorCode::BUSY), self.source.take(), @@ -405,23 +417,18 @@ impl<'a> hil::symmetric_encryption::AES128<'a> for Aes<'a> { )); } - let ret; self.dest.replace(dest); - match source { - None => { - ret = self.do_crypt(start_index, stop_index, start_index); - } + let ret = match source { + None => self.do_crypt(start_index, stop_index, start_index), Some(src) => { self.source.replace(src); - ret = self.do_crypt(start_index, stop_index, 0); + self.do_crypt(start_index, stop_index, 0) } - } + }; if ret.is_ok() { // Schedule a deferred call - self.deferred_call.set(true); - self.deferred_handle - .map(|handle| self.deferred_caller.set(*handle)); + self.deferred_call.set(); None } else { Some((ret, self.source.take(), self.dest.take().unwrap())) @@ -431,10 +438,7 @@ impl<'a> hil::symmetric_encryption::AES128<'a> for Aes<'a> { impl kernel::hil::symmetric_encryption::AES128Ctr for Aes<'_> { fn set_mode_aes128ctr(&self, encrypting: bool) -> Result<(), ErrorCode> { - if !self.idle() { - return Err(ErrorCode::BUSY); - } - + self.wait_on_idle_ready()?; self.mode.set(Mode::AES128CTR); let mut ctrl = if encrypting { @@ -457,10 +461,7 @@ impl kernel::hil::symmetric_encryption::AES128Ctr for Aes<'_> { impl kernel::hil::symmetric_encryption::AES128ECB for Aes<'_> { fn set_mode_aes128ecb(&self, encrypting: bool) -> Result<(), ErrorCode> { - if !self.idle() { - return Err(ErrorCode::BUSY); - } - + self.wait_on_idle_ready()?; self.mode.set(Mode::AES128ECB); let mut ctrl = if encrypting { @@ -483,10 +484,7 @@ impl kernel::hil::symmetric_encryption::AES128ECB for Aes<'_> { impl kernel::hil::symmetric_encryption::AES128CBC for Aes<'_> { fn set_mode_aes128cbc(&self, encrypting: bool) -> Result<(), ErrorCode> { - if !self.idle() { - return Err(ErrorCode::BUSY); - } - + self.wait_on_idle_ready()?; self.mode.set(Mode::AES128CBC); let mut ctrl = if encrypting { @@ -507,15 +505,14 @@ impl kernel::hil::symmetric_encryption::AES128CBC for Aes<'_> { } } -impl<'a> DynamicDeferredCallClient for Aes<'_> { - fn call(&self, _handle: DeferredCallHandle) { - // Are we currently in a TX or RX transaction? - if self.deferred_call.get() { - self.deferred_call.set(false); +impl DeferredCallClient for Aes<'_> { + fn register(&'static self) { + self.deferred_call.register(self); + } - self.client.map(|client| { - client.crypt_done(self.source.take(), self.dest.take().unwrap()); - }); - } + fn handle_deferred_call(&self) { + self.client.map(|client| { + client.crypt_done(self.source.take(), self.dest.take().unwrap()); + }); } } diff --git a/chips/earlgrey/src/aon_timer.rs b/chips/earlgrey/src/aon_timer.rs new file mode 100644 index 0000000000..ac0e6437ce --- /dev/null +++ b/chips/earlgrey/src/aon_timer.rs @@ -0,0 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::registers::top_earlgrey::AON_TIMER_AON_BASE_ADDR; +use kernel::utilities::StaticRef; +use lowrisc::aon_timer::AonTimerRegisters; + +pub const AON_TIMER_BASE: StaticRef = + unsafe { StaticRef::new(AON_TIMER_AON_BASE_ADDR as *const AonTimerRegisters) }; diff --git a/chips/earlgrey/src/chip.rs b/chips/earlgrey/src/chip.rs index 7bf2dc4c66..a61641078a 100644 --- a/chips/earlgrey/src/chip.rs +++ b/chips/earlgrey/src/chip.rs @@ -1,29 +1,43 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! High-level setup and interrupt mapping for the chip. -use core::fmt::Write; -use kernel; -use kernel::dynamic_deferred_call::DynamicDeferredCall; +use core::fmt::{Display, Write}; +use core::marker::PhantomData; +use core::ptr::addr_of; use kernel::platform::chip::{Chip, InterruptService}; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; -use rv32i::csr::{mcause, mie::mie, mip::mip, mtvec::mtvec, CSR}; -use rv32i::epmp::PMP; +use rv32i::csr::{mcause, mie::mie, mtvec::mtvec, CSR}; +use rv32i::pmp::{PMPUserMPU, TORUserPMP}; use rv32i::syscall::SysCall; -use crate::chip_config::CONFIG; +use crate::chip_config::EarlGreyConfig; use crate::interrupts; +use crate::pinmux_config::EarlGreyPinmuxConfig; use crate::plic::Plic; use crate::plic::PLIC; -pub struct EarlGrey<'a, I: InterruptService<()> + 'a> { +pub struct EarlGrey< + 'a, + const MPU_REGIONS: usize, + I: InterruptService + 'a, + CFG: EarlGreyConfig + 'static, + PINMUX: EarlGreyPinmuxConfig, + PMP: TORUserPMP<{ MPU_REGIONS }> + Display + 'static, +> { userspace_kernel_boundary: SysCall, - pub pmp: PMP<8>, + pub mpu: PMPUserMPU, plic: &'a Plic, - timer: &'static crate::timer::RvTimer<'static>, + timer: &'static crate::timer::RvTimer<'static, CFG>, pwrmgr: lowrisc::pwrmgr::PwrMgr, plic_interrupt_service: &'a I, + _cfg: PhantomData, + _pinmux: PhantomData, } -pub struct EarlGreyDefaultPeripherals<'a> { +pub struct EarlGreyDefaultPeripherals<'a, CFG: EarlGreyConfig, PINMUX: EarlGreyPinmuxConfig> { pub aes: crate::aes::Aes<'a>, pub hmac: lowrisc::hmac::Hmac<'a>, pub usb: lowrisc::usbdev::Usb<'a>, @@ -31,33 +45,59 @@ pub struct EarlGreyDefaultPeripherals<'a> { pub otbn: lowrisc::otbn::Otbn<'a>, pub gpio_port: crate::gpio::Port<'a>, pub i2c0: lowrisc::i2c::I2c<'a>, + pub spi_host0: lowrisc::spi_host::SpiHost<'a>, + pub spi_host1: lowrisc::spi_host::SpiHost<'a>, pub flash_ctrl: lowrisc::flash_ctrl::FlashCtrl<'a>, pub rng: lowrisc::csrng::CsRng<'a>, + pub watchdog: lowrisc::aon_timer::AonTimer, + _cfg: PhantomData, + _pinmux: PhantomData, } -impl<'a> EarlGreyDefaultPeripherals<'a> { - pub fn new(deferred_caller: &'static DynamicDeferredCall) -> Self { +impl<'a, CFG: EarlGreyConfig, PINMUX: EarlGreyPinmuxConfig> + EarlGreyDefaultPeripherals<'a, CFG, PINMUX> +{ + pub fn new() -> Self { Self { - aes: crate::aes::Aes::new(deferred_caller), + aes: crate::aes::Aes::new(), hmac: lowrisc::hmac::Hmac::new(crate::hmac::HMAC0_BASE), usb: lowrisc::usbdev::Usb::new(crate::usbdev::USB0_BASE), - uart0: lowrisc::uart::Uart::new(crate::uart::UART0_BASE, CONFIG.peripheral_freq), + uart0: lowrisc::uart::Uart::new(crate::uart::UART0_BASE, CFG::PERIPHERAL_FREQ), otbn: lowrisc::otbn::Otbn::new(crate::otbn::OTBN_BASE), - gpio_port: crate::gpio::Port::new(), - i2c0: lowrisc::i2c::I2c::new( - crate::i2c::I2C0_BASE, - (1 / CONFIG.cpu_freq) * 1000 * 1000, + gpio_port: crate::gpio::Port::new::(), + i2c0: lowrisc::i2c::I2c::new(crate::i2c::I2C0_BASE, (1 / CFG::CPU_FREQ) * 1000 * 1000), + spi_host0: lowrisc::spi_host::SpiHost::new( + crate::spi_host::SPIHOST0_BASE, + CFG::CPU_FREQ, + ), + spi_host1: lowrisc::spi_host::SpiHost::new( + crate::spi_host::SPIHOST1_BASE, + CFG::CPU_FREQ, ), flash_ctrl: lowrisc::flash_ctrl::FlashCtrl::new( crate::flash_ctrl::FLASH_CTRL_BASE, lowrisc::flash_ctrl::FlashRegion::REGION0, ), + rng: lowrisc::csrng::CsRng::new(crate::csrng::CSRNG_BASE), + watchdog: lowrisc::aon_timer::AonTimer::new( + crate::aon_timer::AON_TIMER_BASE, + CFG::CPU_FREQ, + ), + _cfg: PhantomData, + _pinmux: PhantomData, } } + + pub fn init(&'static self) { + kernel::deferred_call::DeferredCallClient::register(&self.aes); + kernel::deferred_call::DeferredCallClient::register(&self.uart0); + } } -impl<'a> InterruptService<()> for EarlGreyDefaultPeripherals<'a> { +impl<'a, CFG: EarlGreyConfig, PINMUX: EarlGreyPinmuxConfig> InterruptService + for EarlGreyDefaultPeripherals<'a, CFG, PINMUX> +{ unsafe fn service_interrupt(&self, interrupt: u32) -> bool { match interrupt { interrupts::UART0_TX_WATERMARK..=interrupts::UART0_RX_PARITYERR => { @@ -83,28 +123,43 @@ impl<'a> InterruptService<()> for EarlGreyDefaultPeripherals<'a> { interrupts::CSRNG_CSCMDREQDONE..=interrupts::CSRNG_CSFATALERR => { self.rng.handle_interrupt() } + interrupts::SPIHOST0_ERROR..=interrupts::SPIHOST0_SPIEVENT => { + self.spi_host0.handle_interrupt() + } + interrupts::SPIHOST1_ERROR..=interrupts::SPIHOST1_SPIEVENT => { + self.spi_host1.handle_interrupt() + } + interrupts::AON_TIMER_AON_WKUP_TIMER_EXPIRED + ..=interrupts::AON_TIMER_AON_WDOG_TIMER_BARK => self.watchdog.handle_interrupt(), _ => return false, } true } - - unsafe fn service_deferred_call(&self, _: ()) -> bool { - false - } } -impl<'a, I: InterruptService<()> + 'a> EarlGrey<'a, I> { +impl< + 'a, + const MPU_REGIONS: usize, + I: InterruptService + 'a, + CFG: EarlGreyConfig, + PINMUX: EarlGreyPinmuxConfig, + PMP: TORUserPMP<{ MPU_REGIONS }> + Display + 'static, + > EarlGrey<'a, MPU_REGIONS, I, CFG, PINMUX, PMP> +{ pub unsafe fn new( plic_interrupt_service: &'a I, - timer: &'static crate::timer::RvTimer, + timer: &'static crate::timer::RvTimer, + pmp: PMP, ) -> Self { Self { userspace_kernel_boundary: SysCall::new(), - pmp: PMP::new(), - plic: &PLIC, + mpu: PMPUserMPU::new(pmp), + plic: &*addr_of!(PLIC), pwrmgr: lowrisc::pwrmgr::PwrMgr::new(crate::pwrmgr::PWRMGR_BASE), timer, plic_interrupt_service, + _cfg: PhantomData, + _pinmux: PhantomData, } } @@ -198,12 +253,20 @@ impl<'a, I: InterruptService<()> + 'a> EarlGrey<'a, I> { } } -impl<'a, I: InterruptService<()> + 'a> kernel::platform::chip::Chip for EarlGrey<'a, I> { - type MPU = PMP<8>; +impl< + 'a, + const MPU_REGIONS: usize, + I: InterruptService + 'a, + CFG: EarlGreyConfig, + PINMUX: EarlGreyPinmuxConfig, + PMP: TORUserPMP<{ MPU_REGIONS }> + Display + 'static, + > kernel::platform::chip::Chip for EarlGrey<'a, MPU_REGIONS, I, CFG, PINMUX, PMP> +{ + type MPU = PMPUserMPU; type UserspaceKernelBoundary = SysCall; fn mpu(&self) -> &Self::MPU { - &self.pmp + &self.mpu } fn userspace_kernel_boundary(&self) -> &SysCall { @@ -212,28 +275,25 @@ impl<'a, I: InterruptService<()> + 'a> kernel::platform::chip::Chip for EarlGrey fn service_pending_interrupts(&self) { loop { - let mip = CSR.mip.extract(); - if self.plic.get_saved_interrupts().is_some() { unsafe { self.handle_plic_interrupts(); } } - if !mip.matches_any(mip::mtimer::SET) && self.plic.get_saved_interrupts().is_none() { + if self.plic.get_saved_interrupts().is_none() { break; } } // Re-enable all MIE interrupts that we care about. Since we looped // until we handled them all, we can re-enable all of them. - CSR.mie.modify(mie::mext::SET + mie::mtimer::SET); + CSR.mie.modify(mie::mext::SET + mie::mtimer::CLEAR); self.plic.enable_all(); } fn has_pending_interrupts(&self) -> bool { - let mip = CSR.mip.extract(); - self.plic.get_saved_interrupts().is_some() || mip.matches_any(mip::mtimer::SET) + self.plic.get_saved_interrupts().is_some() } fn sleep(&self) { @@ -254,10 +314,10 @@ impl<'a, I: InterruptService<()> + 'a> kernel::platform::chip::Chip for EarlGrey unsafe fn print_state(&self, writer: &mut dyn Write) { let _ = writer.write_fmt(format_args!( "\r\n---| OpenTitan Earlgrey configuration for {} |---", - CONFIG.name + CFG::NAME )); rv32i::print_riscv_state(writer); - let _ = writer.write_fmt(format_args!("{}", self.pmp)); + let _ = writer.write_fmt(format_args!("{}", self.mpu.pmp)); } } @@ -266,7 +326,9 @@ fn handle_exception(exception: mcause::Exception) { mcause::Exception::UserEnvCall | mcause::Exception::SupervisorEnvCall => (), // Breakpoints occur from the tests running on hardware - mcause::Exception::Breakpoint => loop {}, + mcause::Exception::Breakpoint => loop { + unsafe { rv32i::support::wfi() } + }, mcause::Exception::InstructionMisaligned | mcause::Exception::InstructionFault @@ -366,16 +428,22 @@ pub unsafe extern "C" fn disable_interrupt_trap_handler(mcause_val: u32) { } pub unsafe fn configure_trap_handler() { + // The common _start_trap handler uses mscratch to determine + // whether we are executing kernel or process code. Set to `0` to + // indicate we're in the kernel right now. + CSR.mscratch.set(0); + // The Ibex CPU does not support non-vectored trap entries. - CSR.mtvec - .write(mtvec::trap_addr.val(_start_trap_vectored as usize >> 2) + mtvec::mode::Vectored) + CSR.mtvec.write( + mtvec::trap_addr.val(_earlgrey_start_trap_vectored as usize >> 2) + mtvec::mode::Vectored, + ); } // Mock implementation for crate tests that does not include the section // specifier, as the test will not use our linker script, and the host // compilation environment may not allow the section name. -#[cfg(not(any(target_arch = "riscv32", target_os = "none")))] -pub extern "C" fn _start_trap_vectored() { +#[cfg(not(all(target_arch = "riscv32", target_os = "none")))] +pub extern "C" fn _earlgrey_start_trap_vectored() { use core::hint::unreachable_unchecked; unsafe { unreachable_unchecked(); @@ -383,54 +451,56 @@ pub extern "C" fn _start_trap_vectored() { } #[cfg(all(target_arch = "riscv32", target_os = "none"))] -#[link_section = ".riscv.trap_vectored"] -#[export_name = "_start_trap_vectored"] -#[naked] -pub extern "C" fn _start_trap_vectored() -> ! { - unsafe { - // According to the Ibex user manual: - // [NMI] has interrupt ID 31, i.e., it has the highest priority of all - // interrupts and the core jumps to the trap-handler base address (in - // mtvec) plus 0x7C to handle the NMI. - // - // Below are 32 (non-compressed) jumps to cover the entire possible - // range of vectored traps. - asm!( - " - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - j _start_trap - ", - options(noreturn) - ); - } +extern "C" { + pub fn _earlgrey_start_trap_vectored(); } + +#[cfg(all(target_arch = "riscv32", target_os = "none"))] +// According to the Ibex user manual: +// [NMI] has interrupt ID 31, i.e., it has the highest priority of all +// interrupts and the core jumps to the trap-handler base address (in +// mtvec) plus 0x7C to handle the NMI. +// +// Below are 32 (non-compressed) jumps to cover the entire possible +// range of vectored traps. +core::arch::global_asm!( + " + .section .riscv.trap_vectored, \"ax\" + .globl _start_trap_vectored + _earlgrey_start_trap_vectored: + + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + j {start_trap} + ", + start_trap = sym rv32i::_start_trap, +); diff --git a/chips/earlgrey/src/chip_config.rs b/chips/earlgrey/src/chip_config.rs index 5433be3e8b..dde6d2c958 100644 --- a/chips/earlgrey/src/chip_config.rs +++ b/chips/earlgrey/src/chip_config.rs @@ -1,55 +1,33 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Chip specific configuration. //! -//! This file includes configuration values for different implementations and -//! uses of the same earlgrey chip. For example, running the chip on an FPGA -//! requires different parameters from running it in a verilog simulator. -//! Additionally, chips on different platforms can be used differently, so this -//! also permits changing values like the UART baud rate to enable better -//! debugging on platforms that can support it. -//! -//! The configuration used is selected via Cargo features specified when the -//! board is compiled. +//! This file includes a common configuration trait and pre-defined constants +//! values for different implementations and uses of the same earlgrey chip. For +//! example, running the chip on an FPGA requires different parameters from +//! running it in a verilog simulator. Additionally, chips on different +//! platforms can be used differently, so this also permits changing values like +//! the UART baud rate to enable better debugging on platforms that can support +//! it. /// Earlgrey configuration based on the target device. -pub struct Config<'a> { +pub trait EarlGreyConfig { /// Identifier for the platform. This is useful for debugging to confirm the /// correct configuration of the chip is being used. - pub name: &'a str, + const NAME: &'static str; + /// The clock speed of the CPU in Hz. - pub cpu_freq: u32, + const CPU_FREQ: u32; + /// The clock speed of the peripherals in Hz. - pub peripheral_freq: u32, + const PERIPHERAL_FREQ: u32; + + /// The clock of the AON Timer + const AON_TIMER_FREQ: u32; + /// The baud rate for UART. This allows for a version of the chip that can /// support a faster baud rate to use it to help with debugging. - pub uart_baudrate: u32, + const UART_BAUDRATE: u32; } - -/// Config for running EarlGrey on an Nexys Video FPGA. Also the default configuration. -#[cfg(any( - feature = "config_fpga_nexysvideo", - not(feature = "config_disable_default") -))] -pub const CONFIG: Config = Config { - name: "fpga_nexysvideo", - cpu_freq: 10_000_000, - peripheral_freq: 2_500_000, - uart_baudrate: 115200, -}; - -/// Config for running EarlGrey on the CW310 FPGA -#[cfg(feature = "config_fpga_cw310")] -pub const CONFIG: Config = Config { - name: "fpga_cw310", - cpu_freq: 10_000_000, - peripheral_freq: 2_500_000, - uart_baudrate: 115200, -}; - -/// Config for running EarlGrey in a verilog simulator. -#[cfg(feature = "config_sim_verilator")] -pub const CONFIG: Config = Config { - name: "sim_verilator", - cpu_freq: 500_000, - peripheral_freq: 125_000, - uart_baudrate: 9600, -}; diff --git a/chips/earlgrey/src/csrng.rs b/chips/earlgrey/src/csrng.rs index 40405dbfc2..c75f432344 100644 --- a/chips/earlgrey/src/csrng.rs +++ b/chips/earlgrey/src/csrng.rs @@ -1,5 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::registers::top_earlgrey::CSRNG_BASE_ADDR; use kernel::utilities::StaticRef; use lowrisc::csrng::CsRngRegisters; pub const CSRNG_BASE: StaticRef = - unsafe { StaticRef::new(0x4115_0000 as *const CsRngRegisters) }; + unsafe { StaticRef::new(CSRNG_BASE_ADDR as *const CsRngRegisters) }; diff --git a/chips/earlgrey/src/epmp.rs b/chips/earlgrey/src/epmp.rs new file mode 100644 index 0000000000..9616bf0dfb --- /dev/null +++ b/chips/earlgrey/src/epmp.rs @@ -0,0 +1,1272 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! The EarlGrey SoC ePMP implementation. +//! +//! Refer to the main [`EarlGreyEPMP`] struct documentation. + +use core::cell::Cell; +use core::fmt; +use core::marker::PhantomData; +use kernel::platform::mpu; +use kernel::utilities::registers::FieldValue; +use rv32i::csr; +use rv32i::pmp::{ + format_pmp_entries, pmpcfg_octet, NAPOTRegionSpec, TORRegionSpec, TORUserPMP, TORUserPMPCFG, +}; + +// ---------- EarlGrey ePMP implementation named constants --------------------- +// +// The ePMP implementation (in part) relies on these constant values. Simply +// changing them here may break the implementation below. +const PMP_ENTRIES: usize = 16; +const PMP_ENTRIES_OVER_TWO: usize = 8; +const TOR_USER_REGIONS_DEBUG_ENABLE: usize = 4; +const TOR_USER_REGIONS_DEBUG_DISABLE: usize = 4; +const TOR_USER_ENTRIES_OFFSET_DEBUG_ENABLE: usize = 0; +const TOR_USER_ENTRIES_OFFSET_DEBUG_DISABLE: usize = 4; + +// ---------- EarlGrey ePMP memory region wrapper types ------------------------ +// +// These types exist primarily to avoid argument confusion in the +// [`EarlGreyEPMP`] constructor, which accepts the addresses of these memory +// regions as arguments. They further encode whether a region must adhere to the +// `NAPOT` or `TOR` addressing mode constraints: + +/// The EarlGrey SOC's flash memory region address range. +/// +/// Configured in the PMP as a `NAPOT` region. +#[derive(Copy, Clone, Debug)] +pub struct FlashRegion(pub NAPOTRegionSpec); + +/// The EarlGrey SOC's RAM region address range. +/// +/// Configured in the PMP as a `NAPOT` region. +#[derive(Copy, Clone, Debug)] +pub struct RAMRegion(pub NAPOTRegionSpec); + +/// The EarlGrey SOC's MMIO region address range. +/// +/// Configured in the PMP as a `NAPOT` region. +#[derive(Copy, Clone, Debug)] +pub struct MMIORegion(pub NAPOTRegionSpec); + +/// The EarlGrey SOC's PMP region specification for the kernel `.text` section. +/// +/// This is to be made accessible to machine-mode as read-execute. Configured in +/// the PMP as a `TOR` region. +#[derive(Copy, Clone, Debug)] +pub struct KernelTextRegion(pub TORRegionSpec); + +/// The EarlGrey SOC's RISC-V Debug Manager memory region. +/// +/// Configured in the PMP as a read/write/execute `NAPOT` region. Because R/W/X +/// regions are not supported in machine-mode lockdown (MML) mode, to enable +/// JTAG debugging, the generic [`EPMPDebugConfig`] argument must be set to +/// [`EPMPDebugEnable`], which will configure the ePMP to operate in non +/// machine-mode lockdown (MML), but still machine-mode whitelist policy (MMWP), +/// instead. +#[derive(Copy, Clone, Debug)] +pub struct RVDMRegion(pub NAPOTRegionSpec); + +// ---------- EarlGrey SoC ePMP JTAG Debugging Configuration ------------------- + +/// EarlGrey SoC ePMP JTAG Debugging Configuration +/// +/// The EarlGrey SoC includes a RISC-V Debug Manager mapped to a NAPOT-aligned +/// memory region. To use a JTAG-debugger with the EarlGrey SoC, this region +/// needs to be allowed as R/W/X in the ePMP, at least for machine-mode. +/// However, the RISC-V ePMP does not support R/W/X regions when in machine-mode +/// lockdown (MML) mode. Furthermore, with the machine-mode whitelist policy +/// (MMWP) enabled, machine-mode (the kernel) must be given explicit access for +/// any memory regions to be accessed. +/// +/// Thus, to enable debugger access, the following changes have to be made in +/// the EarlGrey ePMP from its default locked-down configuration: +/// +/// - Machine-Mode Lockdown (MML) must not be enabled +/// +/// - A locked (machine-mode) PMP memory region must be allocated for the RISC-V +/// Debug Manager (RVDM) allocated, and be given R/W/X permissions. +/// +/// - Locked regions are enforced & locked for both machine-mode and +/// user-mode. This means that we can no longer use locked regions in +/// combination with the machine-mode whitelist policy to take away access +/// permissions from user-mode. This means that we need to place all user-mode +/// regions as non-locked regions _in front of_ all locked machine-mode +/// regions, and insert a "deny-all" non-locked fallback user-mode region in +/// between to achieve our desired isolation properties. +/// +/// As a consequence, because of this "deny-all" user-mode region, we have one +/// fewer memory regions available to be used as a userspace MPU. +/// +/// Because all of this is much too complex to implement at runtime (and can't +/// be reconfigured at runtime once MML is configured), we define a new trait +/// [`EPMPDebugConfig`] with two implementations [`EPMPDebugEnable`] and +/// [`EPMPDebugDisable`]. The EPMP implementation is generic over those traits +/// and can, for instance, advertise a different number of MPU regions available +/// for userspace. It further contains a method to retrieve the RVDM memory +/// region's NAPOT address specification irrespective of whether the debug +/// memory is enabled, and an associated constant to use in the configuration +/// code (such that the branches not taken can be optimized out). +pub trait EPMPDebugConfig { + /// Whether the debug port shall be enabled or not. + const DEBUG_ENABLE: bool; + + /// How many userspace MPU (TOR) regions are available under this + /// configuration. + const TOR_USER_REGIONS: usize; + + /// The offset where the user-mode TOR PMP entries start. This counts + /// "entries", meaning `pmpaddrX` registers. A single "TOR region" uses two + /// consecutive "entries". + const TOR_USER_ENTRIES_OFFSET: usize; +} + +pub enum EPMPDebugEnable {} +impl EPMPDebugConfig for EPMPDebugEnable { + const DEBUG_ENABLE: bool = true; + const TOR_USER_REGIONS: usize = TOR_USER_REGIONS_DEBUG_ENABLE; + const TOR_USER_ENTRIES_OFFSET: usize = TOR_USER_ENTRIES_OFFSET_DEBUG_ENABLE; +} + +pub enum EPMPDebugDisable {} +impl EPMPDebugConfig for EPMPDebugDisable { + const DEBUG_ENABLE: bool = false; + const TOR_USER_REGIONS: usize = TOR_USER_REGIONS_DEBUG_DISABLE; + const TOR_USER_ENTRIES_OFFSET: usize = TOR_USER_ENTRIES_OFFSET_DEBUG_DISABLE; +} + +/// EarlGrey ePMP Configuration Errors +#[derive(Debug, Copy, Clone)] +pub enum EarlGreyEPMPError { + /// The ePMP driver cannot be instantiated because of an unexpected + /// `mseccfg` register value. + InvalidInitialMseccfgValue, + /// The ePMP driver cannot be instantiated because of an unexpected `pmpcfg` + /// register value (where the `usize` value contains the index of the + /// `pmpcfg` register). + InvalidInitialPmpcfgValue(usize), + /// The ePMP registers do not match their expected values after + /// configuration. The system cannot be assumed to be in a secure state. + SanityCheckFail, +} + +/// RISC-V ePMP memory protection implementation for the EarlGrey SoC. +/// +/// The EarlGrey ePMP implementation hard-codes many assumptions about the +/// behavior and state of the underlying hardware, to reduce complexity of this +/// codebase, and improve its security, reliability and auditability. +/// +/// Namely, it makes and checks assumptions about the machine security policy +/// prior to its initialization routine, locks down the hardware through a +/// static set of PMP configuration steps, and then exposes a subset of regions +/// for user-mode protection through the `PMPUserMPU` trait. +/// +/// The EarlGrey ePMP implementation supports JTAG debug-port access through the +/// integrated RISC-V Debug Manger (RVDM) core, which requires R/W/X-access to a +/// given region of memory in machine-mode and user-mode. The [`EarlGreyEPMP`] +/// struct accepts a generic [`EPMPDebugConfig`] implementation, which either +/// enables (in the case of [`EPMPDebugEnable`]) or disables +/// ([`EPMPDebugDisable`]) the debug-port access. However, enabling debug-port +/// access can potentially weaken the system's security by not enabling +/// machine-mode lockdown (MML), and uses an additional PMP region otherwise +/// available to userspace. See the documentation of [`EPMPDebugConfig`] for +/// more information on this. +/// +/// ## ePMP Region Layout & Configuration (`EPMPDebugDisable` mode) +/// +/// Because of the machine-mode lockdown (MML) mode, no region can have R/W/X +/// permissions. The machine-mode whitelist policy (MMWP) further requires all +/// memory accessed by machine-mode to have a corresponding locked PMP entry +/// defined. Lower-indexed PMP entires have precedence over entries with higher +/// indices. Under MML mode, a non-locked (user-mode) entry prevents +/// machine-mode access to that memory. Thus, the ePMP is to be configured in a +/// "sandwiched" layout (with decreasing precedence): +/// +/// 1. High-priority machine-mode "lockdown" entries. +/// +/// These entries are only accessible to machine mode. Once locked, they can +/// only be changed through a hart reset. Examples for such memory sections +/// can be the kernel's `.text` or certain RAM (e.g. stack) sections. +/// +/// 2. Tock's user-mode "MPU" +/// +/// This section defines entries corresponding to memory sections made +/// accessible to user-mode. These entires are exposed through the +/// implementation of the `TORUserPMP` trait. +/// +/// **Effectively, this is Tock's "MPU" sandwiched in between the +/// high-priority and low-priority PMP sections.** +/// +/// These entires are not locked and must be turned off prior to the kernel +/// being able to access them. +/// +/// This section must take precende over the lower kernel-mode entries, as +/// these entries are aliased by the lower kernel-mode entries. Having a +/// locked machine-mode entry take precende over an alias a user-space one +/// prevents user-mode from accessing the aliased memory. +/// +/// 3. Low-priority machine-mode "accessability" entires. +/// +/// These entires provide the kernel access to memory regions which are +/// (partially) aliased by user-mode regions above. This allows for +/// implementing memory sharing between userspace and the kernel (moving +/// acccess to user-mode by turning on a region above, and falling back onto +/// these rules when turning the user-mode region off). +/// +/// These regions can be granular (e.g. grant R/W on the entire RAM), but +/// should not provide any excess permissions where not required (e.g. avoid +/// granting R/X on flash-memory where only R is required, because the +/// kernel-text is already marked as R/X in the high-priority regions above. +/// +/// Because the ROM_EXT and test ROM set up different ePMP configs, there are +/// separate initialization routines (`new` and `new_test_rom`) for those +/// environments. +/// +/// `new` (only available when the debug-port is disabled) attempts to set up +/// the following memory protection rules and layout: +/// +/// - `msseccfg` CSR: +/// +/// ```text +/// |-----+-----------------------------------------------------------+-------| +/// | BIT | LABEL | STATE | +/// |-----+-----------------------------------------------------------+-------| +/// | 0 | Machine-Mode Lockdown (MML) | 1 | +/// | 1 | Machine-Mode Whitelist Policy (MMWP) | 1 | +/// | 2 | Rule-Lock Bypass (RLB) | 0 | +/// |-----+-----------------------------------------------------------+-------| +/// ``` +/// +/// - `pmpcfgX` / `pmpaddrX` CSRs: +/// +/// ```text +/// |-------+----------------------------------------+-----------+---+-------| +/// | ENTRY | REGION / ADDR | MODE | L | PERMS | +/// |-------+----------------------------------------+-----------+---+-------| +/// | 0 | Locked by the ROM_EXT or unused | NAPOT/OFF | X | | +/// | | | | | | +/// | 1 | Locked by the ROM_EXT or unused | NAPOT/OFF | X | | +/// | | | | | | +/// | 2 | -------------------------------------- | OFF | X | ----- | +/// | 3 | Kernel .text section | TOR | X | R/X | +/// | | | | | | +/// | 4 | / \ | OFF | | | +/// | 5 | \ Userspace TOR region #0 / | TOR | | ????? | +/// | | | | | | +/// | 6 | / \ | OFF | | | +/// | 7 | \ Userspace TOR region #1 / | TOR | | ????? | +/// | | | | | | +/// | 8 | / \ | OFF | | | +/// | 9 | \ Userspace TOR region #2 / | TOR | | ????? | +/// | | | | | | +/// | 10 | / \ | OFF | | | +/// | 11 | \ Userspace TOR region #3 / | TOR | | ????? | +/// | | | | | | +/// | 12 | FLASH (spanning kernel & apps) | NAPOT | X | R | +/// | | | | | | +/// | 13 | -------------------------------------- | OFF | X | ----- | +/// | | | | | | +/// | 14 | RAM (spanning kernel & apps) | NAPOT | X | R/W | +/// | | | | | | +/// | 15 | MMIO | NAPOT | X | R/W | +/// |-------+----------------------------------------+-----------+---+-------| +/// ``` +/// +/// `new_test_rom` (only available when the debug-port is disabled) attempts to +/// set up the following memory protection rules and layout: +/// +/// - `msseccfg` CSR: +/// +/// ```text +/// |-----+-----------------------------------------------------------+-------| +/// | BIT | LABEL | STATE | +/// |-----+-----------------------------------------------------------+-------| +/// | 0 | Machine-Mode Lockdown (MML) | 1 | +/// | 1 | Machine-Mode Whitelist Policy (MMWP) | 1 | +/// | 2 | Rule-Lock Bypass (RLB) | 0 | +/// |-----+-----------------------------------------------------------+-------| +/// ``` +/// +/// - `pmpcfgX` / `pmpaddrX` CSRs: +/// +/// ```text +/// |-------+---------------------------------------------+-------+---+-------| +/// | ENTRY | REGION / ADDR | MODE | L | PERMS | +/// |-------+---------------------------------------------+-------+---+-------| +/// | 0 | ------------------------------------------- | OFF | X | ----- | +/// | 1 | Kernel .text section | TOR | X | R/X | +/// | | | | | | +/// | 2 | ------------------------------------------- | OFF | X | | +/// | | | | | | +/// | 3 | ------------------------------------------- | OFF | X | | +/// | | | | | | +/// | 4 | / \ | OFF | | | +/// | 5 | \ Userspace TOR region #0 / | TOR | | ????? | +/// | | | | | | +/// | 6 | / \ | OFF | | | +/// | 7 | \ Userspace TOR region #1 / | TOR | | ????? | +/// | | | | | | +/// | 8 | / \ | OFF | | | +/// | 9 | \ Userspace TOR region #2 / | TOR | | ????? | +/// | | | | | | +/// | 10 | / \ | OFF | | | +/// | 11 | \ Userspace TOR region #3 / | TOR | | ????? | +/// | | | | | | +/// | 12 | ------------------------------------------- | OFF | X | ----- | +/// | | | | | | +/// | 13 | FLASH (spanning kernel & apps) | NAPOT | X | R | +/// | | | | | | +/// | 14 | RAM (spanning kernel & apps) | NAPOT | X | R/W | +/// | | | | | | +/// | 15 | MMIO | NAPOT | X | R/W | +/// |-------+---------------------------------------------+-------+---+-------| +/// ``` +/// +/// ## ePMP Region Layout & Configuration (`EPMPDebugEnable` mode) +/// +/// When enabling the RISC-V Debug Manager (JTAG debug port), the ePMP must be +/// configured differently. This is because the `RVDM` requires a memory section +/// to be mapped with read-write-execute privileges, which is not possible under +/// the machine-mode lockdown (MML) mode. However, when simply disabling MML in +/// the above policy, it would grant userspace access to kernel memory through +/// the locked PMP entires. We still need to define locked PMP entries to grant +/// the kernel (machine-mode) access to its required memory regions, as the +/// machine-mode whitelist policy (MMWP) is enabled. +/// +/// Thus we split the PMP entires into three parts, as outlined in the +/// following: +/// +/// 1. Tock's user-mode "MPU" +/// +/// This section defines entries corresponding to memory sections made +/// accessible to user-mode. These entires are exposed through the +/// implementation of the `TORUserPMP` trait. +/// +/// These entires are not locked. Because the machine-mode lockdown (MML) +/// mode is not enabled, non-locked regions are ignored in machine-mode. The +/// kernel does not have to disable these entires prior to being able to +/// access them. +/// +/// This section must take precende over the lower kernel-mode entries, as +/// these entries are aliased by the lower kernel-mode entries. Having a +/// locked machine-mode entry take precende over an alias a user-space one +/// prevents user-mode from accessing the aliased memory. +/// +/// 2. User-mode "deny-all" rule. +/// +/// Without machine-mode lockdown (MML) mode, locked regions apply to both +/// user- and kernel-mode. Because the machine-mode whitelist policy (MMWP) +/// is enabled, the kernel must be granted explicit permission to access +/// memory (default-deny policy). This means that we must prevent any +/// user-mode access from "falling through" to kernel-mode regions. For this +/// purpose, we insert a non-locked "deny-all" rule which disallows all +/// user-mode accesses to the entire address space, if no other +/// higher-priority user-mode rule matches. +/// +/// 3. Machine-mode "accessability" entires. +/// +/// These entires provide the kernel access to certain memory regions, as +/// required by the machine-mode whitelist policy (MMWP). +/// +/// `new_debug` (only available when the debug-port is enabled) attempts to set +/// up the following memory protection rules and layout: +/// +/// - `msseccfg` CSR: +/// +/// ```text +/// |-----+-----------------------------------------------------------+-------| +/// | BIT | LABEL | STATE | +/// |-----+-----------------------------------------------------------+-------| +/// | 0 | Machine-Mode Lockdown (MML) | 0 | +/// | 1 | Machine-Mode Whitelist Policy (MMWP) | 1 | +/// | 2 | Rule-Lock Bypass (RLB) | 0 | +/// |-----+-----------------------------------------------------------+-------| +/// ``` +/// +/// - `pmpcfgX` / `pmpaddrX` CSRs: +/// +/// ```text +/// |-------+---------------------------------------------+-------+---+-------| +/// | ENTRY | REGION / ADDR | MODE | L | PERMS | +/// |-------+---------------------------------------------+-------+---+-------| +/// | 0 | / \ | OFF | | | +/// | 1 | \ Userspace TOR region #0 / | TOR | | ????? | +/// | | | | | | +/// | 2 | / \ | OFF | | | +/// | 3 | \ Userspace TOR region #1 / | TOR | | ????? | +/// | | | | | | +/// | 4 | / \ | OFF | | | +/// | 5 | \ Userspace TOR region #2 / | TOR | | ????? | +/// | | | | | | +/// | 6 | / \ | OFF | | | +/// | 7 | \ Userspace TOR region #3 / | TOR | | ????? | +/// | | | | | | +/// | 8 | ------------------------------------------- | OFF | | ----- | +/// | | | | | | +/// | 9 | "Deny-all" user-mode rule (all memory) | NAPOT | | ----- | +/// | | | | | | +/// | 10 | ------------------------------------------- | OFF | X | ----- | +/// | 11 | Kernel .text section | TOR | X | R/X | +/// | | | | | | +/// | 12 | RVDM Debug Core Memory | NAPOT | X | R/W/X | +/// | | | | | | +/// | 13 | FLASH (spanning kernel & apps) | NAPOT | X | R | +/// | | | | | | +/// | 14 | RAM (spanning kernel & apps) | NAPOT | X | R/W | +/// | | | | | | +/// | 15 | MMIO | NAPOT | X | R/W | +/// |-------+---------------------------------------------+-------+---+-------| +/// ``` +pub struct EarlGreyEPMP { + user_pmp_enabled: Cell, + // We can't use our generic parameter to determine the length of the + // TORUserPMPCFG array (missing `generic_const_exprs` feature). Thus we + // always assume that the debug-port is disabled and we can fit + // `TOR_USER_REGIONS_DEBUG_DISABLE` user-mode TOR regions. + shadow_user_pmpcfgs: [Cell; TOR_USER_REGIONS_DEBUG_DISABLE], + _pd: PhantomData, +} + +impl EarlGreyEPMP<{ HANDOVER_CONFIG_CHECK }, EPMPDebugDisable> { + pub unsafe fn new( + flash: FlashRegion, + ram: RAMRegion, + mmio: MMIORegion, + kernel_text: KernelTextRegion, + ) -> Result { + use kernel::utilities::registers::interfaces::{Readable, Writeable}; + + // --> We start with the "high-priority" ("lockdown") section of the + // ePMP configuration: + + // Provide R/X access to the kernel .text as passed to us above. + // Allocate a TOR region in PMP entries 2 and 3: + csr::CSR.pmpaddr2.set((kernel_text.0.start() as usize) >> 2); + csr::CSR.pmpaddr3.set((kernel_text.0.end() as usize) >> 2); + + // Set the appropriate `pmpcfg0` register value: + // + // 0x80 = 0b10000000, for start the address of the kernel .text TOR + // entry as well as entries 0 and 1. + // setting L(7) = 1, A(4-3) = OFF, X(2) = 0, W(1) = 0, R(0) = 0 + // + // 0x8d = 0b10001101, for kernel .text TOR region + // setting L(7) = 1, A(4-3) = TOR, X(2) = 1, W(1) = 0, R(0) = 1 + // + // Note that we try to lock entries 0 and 1 into OFF mode. If the + // ROM_EXT set these up and locked them, this will do nothing, otherwise + // it will permanently disable these entries (preventing them from being + // misused later). + csr::CSR.pmpcfg0.set(0x8d_80_80_80); + + // --> Continue with the "low-priority" ("accessibility") section of the + // ePMP configuration: + + // Configure a Read-Only NAPOT region for the entire flash (spanning + // kernel & apps, but overlayed by the R/X kernel text TOR section) + csr::CSR.pmpaddr12.set(flash.0.napot_addr()); + + // Configure a Read-Write NAPOT region for MMIO. + csr::CSR.pmpaddr14.set(mmio.0.napot_addr()); + + // Configure a Read-Write NAPOT region for the entire RAM (spanning + // kernel & apps) + csr::CSR.pmpaddr15.set(ram.0.napot_addr()); + + // With the FLASH, RAM and MMIO configured in separate regions, we can + // activate this new configuration, and further adjust the permissions + // of the (currently all-capable) last PMP entry `pmpaddr15` to be R/W, + // as required for MMIO: + // + // 0x99 = 0b10011001, for FLASH NAPOT region + // setting L(7) = 1, A(4-3) = NAPOT, X(2) = 0, W(1) = 0, R(0) = 1 + // + // 0x80 = 0b10000000, for the unused region + // setting L(7) = 1, A(4-3) = OFF, X(2) = 0, W(1) = 0, R(0) = 0 + // + // 0x9B = 0b10011011, for RAM & MMIO NAPOT regions + // setting L(7) = 1, A(4-3) = NAPOT, X(2) = 0, W(1) = 1, R(0) = 1 + csr::CSR.pmpcfg3.set(0x9B_9B_80_99); + + // Ensure that the other pmpcfgX CSRs are cleared: + csr::CSR.pmpcfg1.set(0x00000000); + csr::CSR.pmpcfg2.set(0x00000000); + + // ---------- PMP machine CSRs configured, lock down the system + + // Finally, enable machine-mode lockdown. + // Set RLB(2) = 0, MMWP(1) = 1, MML(0) = 1 + csr::CSR.mseccfg.set(0x00000003); + + // ---------- System locked down, cross-check config + + // Now, cross-check that the CSRs have the expected values. This acts as + // a sanity check, and can also help to protect against some set of + // fault-injection attacks. These checks can't be optimized out by the + // compiler, as they invoke assembly underneath which is not marked as + // ["pure"](https://doc.rust-lang.org/reference/inline-assembly.html). + // + // Note that different ROM_EXT versions configure entries 0 and 1 + // differently, so we only confirm they are locked here. + if csr::CSR.mseccfg.get() != 0x00000003 + || (csr::CSR.pmpcfg0.get() & 0xFFFF8080) != 0x8d808080 + || csr::CSR.pmpcfg1.get() != 0x00000000 + || csr::CSR.pmpcfg2.get() != 0x00000000 + || csr::CSR.pmpcfg3.get() != 0x9B9B8099 + || csr::CSR.pmpaddr2.get() != (kernel_text.0.start() as usize) >> 2 + || csr::CSR.pmpaddr3.get() != (kernel_text.0.end() as usize) >> 2 + || csr::CSR.pmpaddr12.get() != flash.0.napot_addr() + || csr::CSR.pmpaddr14.get() != mmio.0.napot_addr() + || csr::CSR.pmpaddr15.get() != ram.0.napot_addr() + { + return Err(EarlGreyEPMPError::SanityCheckFail); + } + + // The ePMP hardware was correctly configured, build the ePMP struct: + const DEFAULT_USER_PMPCFG_OCTET: Cell = Cell::new(TORUserPMPCFG::OFF); + Ok(EarlGreyEPMP { + user_pmp_enabled: Cell::new(false), + shadow_user_pmpcfgs: [DEFAULT_USER_PMPCFG_OCTET; TOR_USER_REGIONS_DEBUG_DISABLE], + _pd: PhantomData, + }) + } + + pub unsafe fn new_test_rom( + flash: FlashRegion, + ram: RAMRegion, + mmio: MMIORegion, + kernel_text: KernelTextRegion, + ) -> Result { + use kernel::utilities::registers::interfaces::{Readable, Writeable}; + + if HANDOVER_CONFIG_CHECK { + Self::check_initial_hardware_config()?; + } else { + // We aren't supposed to run a handover configuration check. This is + // useful for environments which don't replicate the OpenTitan + // EarlGrey chip behavior entirely accurately, such as + // QEMU. However, in those environments, we cannot guarantee that + // this configuration is actually going to work, and not break the + // system in the meantime. + // + // We perform a best-effort configuration, starting by setting rule-lock + // bypass... + csr::CSR.mseccfg.set(0x00000004); + // ...adding our required kernel-mode mode memory access rule... + csr::CSR.pmpaddr15.set(0x7FFFFFFF); + csr::CSR.pmpcfg3.set(0x9F000000); + // ...and enabling the machine-mode whitelist policy: + csr::CSR.mseccfg.set(0x00000006); + } + + // ---------- HW configured as expected, start setting PMP CSRs + + // The below instructions are an intricate dance to achieve our desired + // ePMP configuration. For correctness sake, we -- at no intermediate + // point -- want to lose access to RAM, FLASH or MMIO. + // + // This is challenging, as the last section currently provides us access + // to all of these regions, and we can't atomically change both its + // pmpaddrX and pmpcfgX CSRs to limit it to a subset of its address + // range and permissions. Thus, before changing the `pmpcfg3` / + // `pmpaddr15` region, we first utilize another higher-priority CSR to + // provide us access to one of the memory regions we'd lose access to, + // namely we use the PMP entry 12 to provide us access to MMIO. + + // --> We start with the "high-priority" ("lockdown") section of the + // ePMP configuration: + + // Provide R/X access to the kernel .text as passed to us above. + // Allocate a TOR region in PMP entries 0 and 1: + csr::CSR.pmpaddr0.set((kernel_text.0.start() as usize) >> 2); + csr::CSR.pmpaddr1.set((kernel_text.0.end() as usize) >> 2); + + // Set the appropriate `pmpcfg0` register value: + // + // 0x80 = 0b10000000, for start address of the kernel .text TOR entry + // and to disable regions 2 & 3 (to be compatible with the + // non-test-rom constructor). + // setting L(7) = 1, A(4-3) = OFF, X(2) = 0, W(1) = 0, R(0) = 0 + // + // 0x8d = 0b10001101, for kernel .text TOR region + // setting L(7) = 1, A(4-3) = TOR, X(2) = 1, W(1) = 0, R(0) = 1 + csr::CSR.pmpcfg0.set(0x80808d80); + + // --> Continue with the "low-priority" ("accessability") section of the + // ePMP configuration: + + // Now, onto `pmpcfg3`. As discussed above, we want to use a temporary + // region to retain MMIO access while reconfiguring the `pmpcfg3` / + // `pmpaddr15` register. Thus, write the MMIO region access into + // `pmpaddr12`: + csr::CSR.pmpaddr12.set(mmio.0.napot_addr()); + + // Configure a Read-Only NAPOT region for the entire flash (spanning + // kernel & apps, but overlayed by the R/X kernel text TOR section) + csr::CSR.pmpaddr13.set(flash.0.napot_addr()); + + // Configure a Read-Write NAPOT region for the entire RAM (spanning + // kernel & apps) + csr::CSR.pmpaddr14.set(ram.0.napot_addr()); + + // With the FLASH, RAM and MMIO configured in separate regions, we can + // activate this new configuration, and further adjust the permissions + // of the (currently all-capable) last PMP entry `pmpaddr15` to be R/W, + // as required for MMIO: + // + // 0x99 = 0b10011001, for FLASH NAPOT region + // setting L(7) = 1, A(4-3) = NAPOT, X(2) = 0, W(1) = 0, R(0) = 1 + // + // 0x9B = 0b10011011, for RAM & MMIO NAPOT regions + // setting L(7) = 1, A(4-3) = NAPOT, X(2) = 0, W(1) = 1, R(0) = 1 + csr::CSR.pmpcfg3.set(0x9B9B999B); + + // With the new configuration in place, we can adjust the last region's + // address to be limited to the MMIO region, ... + csr::CSR.pmpaddr15.set(mmio.0.napot_addr()); + + // ...and then deactivate the `pmpaddr12` fallback MMIO region + // + // Remove the temporary MMIO region permissions from `pmpaddr12`: + // + // 0x80 = 0b10000000 + // setting L(7) = 1, A(4-3) = OFF, X(2) = 0, W(1) = 0, R(0) = 0 + // + // 0x99 = 0b10011001, for FLASH NAPOT region + // setting L(7) = 1, A(4-3) = NAPOT, X(2) = 0, W(1) = 0, R(0) = 1 + // + // 0x9B = 0b10011011, for RAM & MMIO NAPOT regions + // setting L(7) = 1, A(4-3) = NAPOT, X(2) = 0, W(1) = 1, R(0) = 1 + csr::CSR.pmpcfg3.set(0x9B9B9980); + + // Ensure that the other pmpcfgX CSRs are cleared: + csr::CSR.pmpcfg1.set(0x00000000); + csr::CSR.pmpcfg2.set(0x00000000); + + // ---------- PMP machine CSRs configured, lock down the system + + // Finally, unset the rule-lock bypass (RLB) bit. If we don't have a + // debug memory region provided, further set machine-mode lockdown (we + // can't enable MML and also have a R/W/X region). We also set MMWP for + // good measure, but that shouldn't make a difference -- it can't be + // cleared anyways as it is a sticky bit. + // + // Unsetting RLB with at least one locked region will mean that we can't + // set it again, thus actually enforcing the region lock bits. + // + // Set RLB(2) = 0, MMWP(1) = 1, MML(0) = 1 + csr::CSR.mseccfg.set(0x00000003); + + // ---------- System locked down, cross-check config + + // Now, cross-check that the CSRs have the expected values. This acts as + // a sanity check, and can also help to protect against some set of + // fault-injection attacks. These checks can't be optimized out by the + // compiler, as they invoke assembly underneath which is not marked as + // ["pure"](https://doc.rust-lang.org/reference/inline-assembly.html). + if csr::CSR.mseccfg.get() != 0x00000003 + || csr::CSR.pmpcfg0.get() != 0x00008d80 + || csr::CSR.pmpcfg1.get() != 0x00000000 + || csr::CSR.pmpcfg2.get() != 0x00000000 + || csr::CSR.pmpcfg3.get() != 0x9B9B9980 + || csr::CSR.pmpaddr0.get() != (kernel_text.0.start() as usize) >> 2 + || csr::CSR.pmpaddr1.get() != (kernel_text.0.end() as usize) >> 2 + || csr::CSR.pmpaddr13.get() != flash.0.napot_addr() + || csr::CSR.pmpaddr14.get() != ram.0.napot_addr() + || csr::CSR.pmpaddr15.get() != mmio.0.napot_addr() + { + return Err(EarlGreyEPMPError::SanityCheckFail); + } + + // The ePMP hardware was correctly configured, build the ePMP struct: + const DEFAULT_USER_PMPCFG_OCTET: Cell = Cell::new(TORUserPMPCFG::OFF); + Ok(EarlGreyEPMP { + user_pmp_enabled: Cell::new(false), + shadow_user_pmpcfgs: [DEFAULT_USER_PMPCFG_OCTET; TOR_USER_REGIONS_DEBUG_DISABLE], + _pd: PhantomData, + }) + } +} + +impl EarlGreyEPMP<{ HANDOVER_CONFIG_CHECK }, EPMPDebugEnable> { + pub unsafe fn new_debug( + flash: FlashRegion, + ram: RAMRegion, + mmio: MMIORegion, + kernel_text: KernelTextRegion, + debug_memory: RVDMRegion, + ) -> Result { + use kernel::utilities::registers::interfaces::{Readable, Writeable}; + + if HANDOVER_CONFIG_CHECK { + Self::check_initial_hardware_config()?; + } else { + // We aren't supposed to run a handover configuration check. This is + // useful for environments which don't replicate the OpenTitan + // EarlGrey chip behavior entirely accurately, such as + // QEMU. However, in those environments, we cannot guarantee that + // this configuration is actually going to work, and not break the + // system in the meantime. + // + // We perform a best-effort configuration, starting by setting rule-lock + // bypass... + csr::CSR.mseccfg.set(0x00000004); + // ...adding our required kernel-mode mode memory access rule... + csr::CSR.pmpaddr15.set(0x7FFFFFFF); + csr::CSR.pmpcfg3.set(0x9F000000); + // ...and enabling the machine-mode whitelist policy: + csr::CSR.mseccfg.set(0x00000006); + } + + // ---------- HW configured as expected, start setting PMP CSRs + + // The below instructions are an intricate dance to achieve our desired + // ePMP configuration. For correctness sake, we -- at no intermediate + // point -- want to lose access to RAM, FLASH or MMIO. + // + // This is challenging, as the last section currently provides us access + // to all of these regions, and we can't atomically change both its + // pmpaddrX and pmpcfgX CSRs to limit it to a subset of its address + // range and permissions. Thus, before changing the `pmpcfg3` / + // `pmpaddr15` region, we first utilize another higher-priority CSR to + // provide us access to one of the memory regions we'd lose access to, + // namely we use the PMP entry 12 to provide us access to MMIO. + + // Provide R/X access to the kernel .text as passed to us above. + // Allocate a TOR region in PMP entries 10 and 11: + csr::CSR + .pmpaddr10 + .set((kernel_text.0.start() as usize) >> 2); + csr::CSR.pmpaddr11.set((kernel_text.0.end() as usize) >> 2); + + // Set the appropriate `pmpcfg2` register value: + // + // 0x80 = 0b10000000, for start address of the kernel .text TOR entry + // setting L(7) = 1, A(4-3) = OFF, X(2) = 0, W(1) = 0, R(0) = 0 + // + // 0x8d = 0b10001101, for kernel .text TOR region + // setting L(7) = 1, A(4-3) = TOR, X(2) = 1, W(1) = 0, R(0) = 1 + csr::CSR.pmpcfg2.set(0x8d800000); + + // Now, onto `pmpcfg3`. As discussed above, we want to use a temporary + // region to retain MMIO access while reconfiguring the `pmpcfg3` / + // `pmpaddr15` register. Thus, write the MMIO region access into + // `pmpaddr12`: + csr::CSR.pmpaddr12.set(mmio.0.napot_addr()); + + // Configure a Read-Only NAPOT region for the entire flash (spanning + // kernel & apps, but overlayed by the R/X kernel text TOR section) + csr::CSR.pmpaddr13.set(flash.0.napot_addr()); + + // Configure a Read-Write NAPOT region for the entire RAM (spanning + // kernel & apps) + csr::CSR.pmpaddr14.set(ram.0.napot_addr()); + + // With the FLASH, RAM and MMIO configured in separate regions, we can + // activate this new configuration, and further adjust the permissions + // of the (currently all-capable) last PMP entry `pmpaddr15` to be R/W, + // as required for MMIO: + // + // 0x99 = 0b10011001, for FLASH NAPOT region + // setting L(7) = 1, A(4-3) = NAPOT, X(2) = 0, W(1) = 0, R(0) = 1 + // + // 0x9B = 0b10011011, for RAM & MMIO NAPOT regions + // setting L(7) = 1, A(4-3) = NAPOT, X(2) = 0, W(1) = 1, R(0) = 1 + csr::CSR.pmpcfg3.set(0x9B9B999B); + + // With the new configuration in place, we can adjust the last region's + // address to be limited to the MMIO region, ... + csr::CSR.pmpaddr15.set(mmio.0.napot_addr()); + + // ...and then repurpose `pmpaddr12` for the debug port: + csr::CSR.pmpaddr12.set(debug_memory.0.napot_addr()); + + // 0x9F = 0b10011111, for RVDM R/W/X memory region + // setting L(7) = 1, A(4-3) = NAPOT, X(2) = 1, W(1) = 1, R(0) = 1 + // + // 0x99 = 0b10011001, for FLASH NAPOT region + // setting L(7) = 1, A(4-3) = NAPOT, X(2) = 0, W(1) = 0, R(0) = 1 + // + // 0x9B = 0b10011011, for RAM & MMIO NAPOT regions + // setting L(7) = 1, A(4-3) = NAPOT, X(2) = 0, W(1) = 1, R(0) = 1 + csr::CSR.pmpcfg3.set(0x9B9B999F); + + // Ensure that the other pmpcfgX CSRs are cleared: + csr::CSR.pmpcfg0.set(0x00000000); + csr::CSR.pmpcfg1.set(0x00000000); + + // ---------- PMP machine CSRs configured, lock down the system + + // Finally, unset the rule-lock bypass (RLB) bit. If we don't have a + // debug memory region provided, further set machine-mode lockdown (we + // can't enable MML and also have a R/W/X region). We also set MMWP for + // good measure, but that shouldn't make a difference -- it can't be + // cleared anyways as it is a sticky bit. + // + // Unsetting RLB with at least one locked region will mean that we can't + // set it again, thus actually enforcing the region lock bits. + // + // Set RLB(2) = 0, MMWP(1) = 1, MML(0) = 0 + csr::CSR.mseccfg.set(0x00000002); + + // ---------- System locked down, cross-check config + + // Now, cross-check that the CSRs have the expected values. This acts as + // a sanity check, and can also help to protect against some set of + // fault-injection attacks. These checks can't be optimized out by the + // compiler, as they invoke assembly underneath which is not marked as + // ["pure"](https://doc.rust-lang.org/reference/inline-assembly.html). + if csr::CSR.mseccfg.get() != 0x00000002 + || csr::CSR.pmpcfg0.get() != 0x00000000 + || csr::CSR.pmpcfg1.get() != 0x00000000 + || csr::CSR.pmpcfg2.get() != 0x8d800000 + || csr::CSR.pmpcfg3.get() != 0x9B9B999F + || csr::CSR.pmpaddr10.get() != (kernel_text.0.start() as usize) >> 2 + || csr::CSR.pmpaddr11.get() != (kernel_text.0.end() as usize) >> 2 + || csr::CSR.pmpaddr12.get() != debug_memory.0.napot_addr() + || csr::CSR.pmpaddr13.get() != flash.0.napot_addr() + || csr::CSR.pmpaddr14.get() != ram.0.napot_addr() + || csr::CSR.pmpaddr15.get() != mmio.0.napot_addr() + { + return Err(EarlGreyEPMPError::SanityCheckFail); + } + + // Now, as we're not in the machine-mode lockdown (MML) mode, locked PMP + // regions will still be accessible to userspace. To prevent our + // kernel-mode access regions from being accessible to user-mode, we use + // the last user-mode TOR region (`pmpaddr9`) to configure a + // "protection" region which disallows access to all memory that has not + // otherwise been granted access to. + csr::CSR.pmpaddr9.set(0x7FFFFFFF); // the entire address space + + // And finally apply this configuration to the `pmpcfg2` CSR. For good + // measure, we also include the locked regions (which we can no longer + // modify thanks to RLB = 0). + // + // 0x18 = 0b00011000, to revoke user-mode perms to all memory + // setting L(7) = 0, A(4-3) = NAPOT, X(2) = 0, W(1) = 0, R(0) = 0 + // + // 0x80 = 0b10000000, for start address of the kernel .text TOR entry + // setting L(7) = 1, A(4-3) = OFF, X(2) = 0, W(1) = 0, R(0) = 0 + // + // 0x8d = 0b10001101, for kernel .text TOR region + // setting L(7) = 1, A(4-3) = TOR, X(2) = 1, W(1) = 0, R(0) = 1 + csr::CSR.pmpcfg2.set(0x8d81800); + + // The ePMP hardware was correctly configured, build the ePMP struct: + const DEFAULT_USER_PMPCFG_OCTET: Cell = Cell::new(TORUserPMPCFG::OFF); + let epmp = EarlGreyEPMP { + user_pmp_enabled: Cell::new(false), + shadow_user_pmpcfgs: [DEFAULT_USER_PMPCFG_OCTET; TOR_USER_REGIONS_DEBUG_DISABLE], + _pd: PhantomData, + }; + + Ok(epmp) + } +} + +impl + EarlGreyEPMP<{ HANDOVER_CONFIG_CHECK }, DBG> +{ + fn check_initial_hardware_config() -> Result<(), EarlGreyEPMPError> { + use kernel::utilities::registers::interfaces::Readable; + + // This initialization code is written to work with 16 PMP entries. Add + // an explicit assertion such that things break when the constant above + // is changed: + #[allow(clippy::assertions_on_constants)] + const _: () = assert!( + PMP_ENTRIES_OVER_TWO == 8, + "EarlGrey ePMP initialization is written for 16 PMP entries.", + ); + + // ---------- Check current HW config + + // Ensure that the `mseccfg` CSR has the expected value, namely that + // we're in "machine-mode whitelist policy" and have "rule-lock bypass" + // enabled. If this register has an unexpected value, we risk + // accidentally revoking important permissions for the Tock kernel + // itself. + if csr::CSR.mseccfg.get() != 0x00000006 { + return Err(EarlGreyEPMPError::InvalidInitialMseccfgValue); + } + + // We assume the very last PMP region is set to provide us RXW access to + // the entirety of memory, and all other regions are disabled. Check the + // CSRs to make sure that this is indeed the case. + for i in 0..(PMP_ENTRIES_OVER_TWO / 2 - 1) { + // 0x98 = 0b10011000, extracting L(7) and A(4-3) bits. + if csr::CSR.pmpconfig_get(i) & 0x98989898 != 0x00000000 { + return Err(EarlGreyEPMPError::InvalidInitialPmpcfgValue(i)); + } + } + + // The last CSR is special, as we expect it to contain the NAPOT region + // which currently gives us memory access. + // + // 0x98 = 0b10011000, extracting L(7) and A(4-3) bits. + // 0x9F = 0b10011111, extracing L(7), A(4-3), X(2), W(1), R(0) bits. + if csr::CSR.pmpconfig_get(PMP_ENTRIES_OVER_TWO / 2 - 1) & 0x9F989898 != 0x9F000000 { + return Err(EarlGreyEPMPError::InvalidInitialPmpcfgValue( + PMP_ENTRIES_OVER_TWO / 2 - 1, + )); + } + + Ok(()) + } + + // ---------- Backing functions for the TORUserPMP implementations --------- + // + // The EarlGrey ePMP implementations of `TORUserPMP` differ between + // `EPMPDebugEnable` and `EPMPDebugDisable` configurations. These backing + // functions here are applicable to both, and called by those trait + // implementations respectively: + + fn user_available_regions(&self) -> usize { + // Always assume to have `TOR_USER_REGIONS` usable TOR regions. We have a + // fixed number of kernel memory protection regions, and a fixed mapping + // of user regions to hardware PMP entries. + TOR_USER_REGIONS + } + + fn user_configure_pmp( + &self, + regions: &[(TORUserPMPCFG, *const u8, *const u8); TOR_USER_REGIONS], + ) -> Result<(), ()> { + // Configure all of the regions' addresses and store their pmpcfg octets + // in our shadow storage. If the user PMP is already enabled, we further + // apply this configuration (set the pmpcfgX CSRs) by running + // `enable_user_pmp`: + for (i, (region, shadow_user_pmpcfg)) in regions + .iter() + .zip(self.shadow_user_pmpcfgs.iter()) + .enumerate() + { + // The ePMP in MML mode does not support read-write-execute + // regions. If such a region is to be configured, abort. As this + // loop here only modifies the shadow state, we can simply abort and + // return an error. We don't make any promises about the ePMP state + // if the configuration files, but it is still being activated with + // `enable_user_pmp`: + if region.0.get() + == >::from( + mpu::Permissions::ReadWriteExecute, + ) + .get() + { + return Err(()); + } + + // Set the CSR addresses for this region (if its not OFF, in which + // case the hardware-configured addresses are irrelevant): + if region.0 != TORUserPMPCFG::OFF { + csr::CSR.pmpaddr_set( + DBG::TOR_USER_ENTRIES_OFFSET + (i * 2) + 0, + (region.1 as usize).overflowing_shr(2).0, + ); + csr::CSR.pmpaddr_set( + DBG::TOR_USER_ENTRIES_OFFSET + (i * 2) + 1, + (region.2 as usize).overflowing_shr(2).0, + ); + } + + // Store the region's pmpcfg octet: + shadow_user_pmpcfg.set(region.0); + } + + // If the PMP is currently active, apply the changes to the CSRs: + if self.user_pmp_enabled.get() { + self.user_enable_user_pmp()?; + } + + Ok(()) + } + + fn user_enable_user_pmp(&self) -> Result<(), ()> { + // Currently, this code requires the TOR regions to start at an even PMP + // region index. Assert that this is indeed the case: + #[allow(clippy::let_unit_value)] + let _: () = assert!(DBG::TOR_USER_ENTRIES_OFFSET % 2 == 0); + + // We store the "enabled" PMPCFG octets of user regions in the + // `shadow_user_pmpcfg` field, such that we can re-enable the PMP + // without a call to `configure_pmp` (where the `TORUserPMPCFG`s are + // provided by the caller). + + // Could use `iter_array_chunks` once that's stable. + // + // Limit iteration to `DBG::TOR_USER_REGIONS` to avoid overwriting any + // configured debug regions in the last user-mode TOR region. + let mut shadow_user_pmpcfgs_iter = self.shadow_user_pmpcfgs[..DBG::TOR_USER_REGIONS].iter(); + let mut i = DBG::TOR_USER_ENTRIES_OFFSET / 2; + + while let Some(first_region_pmpcfg) = shadow_user_pmpcfgs_iter.next() { + // If we're at a "region" offset divisible by two (where "region" = + // 2 PMP "entries"), then we can configure an entire `pmpcfgX` CSR + // in one operation. As CSR writes are expensive, this is an + // operation worth making: + let second_region_opt = if i % 2 == 0 { + shadow_user_pmpcfgs_iter.next() + } else { + None + }; + + if let Some(second_region_pmpcfg) = second_region_opt { + // We're at an even index and have two regions to configure, so + // do that with a single CSR write: + csr::CSR.pmpconfig_set( + i / 2, + u32::from_be_bytes([ + second_region_pmpcfg.get().get(), + TORUserPMPCFG::OFF.get(), + first_region_pmpcfg.get().get(), + TORUserPMPCFG::OFF.get(), + ]) as usize, + ); + + i += 2; + } else if i % 2 == 0 { + // This is a single region at an even index. Thus, modify the + // first two pmpcfgX octets for this region. + csr::CSR.pmpconfig_modify( + i / 2, + FieldValue::::new( + 0x0000FFFF, + 0, // lower two octets + u32::from_be_bytes([ + 0, + 0, + first_region_pmpcfg.get().get(), + TORUserPMPCFG::OFF.get(), + ]) as usize, + ), + ); + + i += 1; + } else { + // This is a single region at an odd index. Thus, modify the + // latter two pmpcfgX octets for this region. + csr::CSR.pmpconfig_modify( + i / 2, + FieldValue::::new( + 0x0000FFFF, + 16, // higher two octets + u32::from_be_bytes([ + 0, + 0, + first_region_pmpcfg.get().get(), + TORUserPMPCFG::OFF.get(), + ]) as usize, + ), + ); + + i += 1; + } + } + + self.user_pmp_enabled.set(true); + + Ok(()) + } + + fn user_disable_user_pmp(&self) { + // Simply set all of the user-region pmpcfg octets to OFF: + let mut user_region_pmpcfg_octet_pairs = (DBG::TOR_USER_ENTRIES_OFFSET / 2) + ..((DBG::TOR_USER_ENTRIES_OFFSET / 2) + DBG::TOR_USER_REGIONS); + + while let Some(first_region_idx) = user_region_pmpcfg_octet_pairs.next() { + let second_region_opt = if first_region_idx % 2 == 0 { + user_region_pmpcfg_octet_pairs.next() + } else { + None + }; + + if let Some(_second_region_idx) = second_region_opt { + // We're at an even index and have two regions to configure, so + // do that with a single CSR write: + csr::CSR.pmpconfig_set( + first_region_idx / 2, + u32::from_be_bytes([ + TORUserPMPCFG::OFF.get(), + TORUserPMPCFG::OFF.get(), + TORUserPMPCFG::OFF.get(), + TORUserPMPCFG::OFF.get(), + ]) as usize, + ); + } else if first_region_idx % 2 == 0 { + // This is a single region at an even index. Thus, modify the + // first two pmpcfgX octets for this region. + csr::CSR.pmpconfig_modify( + first_region_idx / 2, + FieldValue::::new( + 0x0000FFFF, + 0, // lower two octets + u32::from_be_bytes([ + 0, + 0, + TORUserPMPCFG::OFF.get(), + TORUserPMPCFG::OFF.get(), + ]) as usize, + ), + ); + } else { + // This is a single region at an odd index. Thus, modify the + // latter two pmpcfgX octets for this region. + csr::CSR.pmpconfig_modify( + first_region_idx / 2, + FieldValue::::new( + 0x0000FFFF, + 16, // higher two octets + u32::from_be_bytes([ + 0, + 0, + TORUserPMPCFG::OFF.get(), + TORUserPMPCFG::OFF.get(), + ]) as usize, + ), + ); + } + } + + self.user_pmp_enabled.set(false); + } +} + +impl TORUserPMP<{ TOR_USER_REGIONS_DEBUG_ENABLE }> + for EarlGreyEPMP<{ HANDOVER_CONFIG_CHECK }, EPMPDebugEnable> +{ + // Don't require any const-assertions in the EarlGreyEPMP. + const CONST_ASSERT_CHECK: () = (); + + fn available_regions(&self) -> usize { + self.user_available_regions::() + } + + fn configure_pmp( + &self, + regions: &[(TORUserPMPCFG, *const u8, *const u8); TOR_USER_REGIONS_DEBUG_ENABLE], + ) -> Result<(), ()> { + self.user_configure_pmp::(regions) + } + + fn enable_user_pmp(&self) -> Result<(), ()> { + self.user_enable_user_pmp() + } + + fn disable_user_pmp(&self) { + // Technically, the `disable_user_pmp` can be implemented as a no-op in + // the debug-mode ePMP, as machine-mode lockdown (MML) is not enabled. + // However, we still execercise these routines to stay as close to the + // non-debug ePMP configuration as possible: + self.user_disable_user_pmp() + } +} + +impl TORUserPMP<{ TOR_USER_REGIONS_DEBUG_DISABLE }> + for EarlGreyEPMP<{ HANDOVER_CONFIG_CHECK }, EPMPDebugDisable> +{ + // Don't require any const-assertions in the EarlGreyEPMP. + const CONST_ASSERT_CHECK: () = (); + + fn available_regions(&self) -> usize { + self.user_available_regions::() + } + + fn configure_pmp( + &self, + regions: &[(TORUserPMPCFG, *const u8, *const u8); TOR_USER_REGIONS_DEBUG_DISABLE], + ) -> Result<(), ()> { + self.user_configure_pmp::(regions) + } + + fn enable_user_pmp(&self) -> Result<(), ()> { + self.user_enable_user_pmp() + } + + fn disable_user_pmp(&self) { + self.user_disable_user_pmp() + } +} + +impl fmt::Display + for EarlGreyEPMP<{ HANDOVER_CONFIG_CHECK }, DBG> +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use kernel::utilities::registers::interfaces::Readable; + + write!(f, " EarlGrey ePMP configuration:\r\n")?; + write!( + f, + " mseccfg: {:#08X}, user-mode PMP active: {:?}\r\n", + csr::CSR.mseccfg.get(), + self.user_pmp_enabled.get() + )?; + unsafe { format_pmp_entries::(f) }?; + + write!(f, " Shadow PMP entries for user-mode:\r\n")?; + for (i, shadowed_pmpcfg) in self.shadow_user_pmpcfgs[..DBG::TOR_USER_REGIONS] + .iter() + .enumerate() + { + let (start_pmpaddr_label, startaddr_pmpaddr, endaddr, mode) = + if shadowed_pmpcfg.get() == TORUserPMPCFG::OFF { + ( + "pmpaddr", + csr::CSR.pmpaddr_get(DBG::TOR_USER_ENTRIES_OFFSET + (i * 2)), + 0, + "OFF", + ) + } else { + ( + " start", + csr::CSR + .pmpaddr_get(DBG::TOR_USER_ENTRIES_OFFSET + (i * 2)) + .overflowing_shl(2) + .0, + csr::CSR + .pmpaddr_get(DBG::TOR_USER_ENTRIES_OFFSET + (i * 2) + 1) + .overflowing_shl(2) + .0 + | 0b11, + "TOR", + ) + }; + + write!( + f, + " [{:02}]: {}={:#010X}, end={:#010X}, cfg={:#04X} ({}) ({}{}{}{})\r\n", + DBG::TOR_USER_ENTRIES_OFFSET + (i * 2) + 1, + start_pmpaddr_label, + startaddr_pmpaddr, + endaddr, + shadowed_pmpcfg.get().get(), + mode, + if shadowed_pmpcfg.get().get_reg().is_set(pmpcfg_octet::l) { + "l" + } else { + "-" + }, + if shadowed_pmpcfg.get().get_reg().is_set(pmpcfg_octet::r) { + "r" + } else { + "-" + }, + if shadowed_pmpcfg.get().get_reg().is_set(pmpcfg_octet::w) { + "w" + } else { + "-" + }, + if shadowed_pmpcfg.get().get_reg().is_set(pmpcfg_octet::x) { + "x" + } else { + "-" + }, + )?; + } + + Ok(()) + } +} diff --git a/chips/earlgrey/src/flash_ctrl.rs b/chips/earlgrey/src/flash_ctrl.rs index 0c8ef8cebb..2eececafcc 100644 --- a/chips/earlgrey/src/flash_ctrl.rs +++ b/chips/earlgrey/src/flash_ctrl.rs @@ -1,5 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::registers::top_earlgrey::FLASH_CTRL_CORE_BASE_ADDR; use kernel::utilities::StaticRef; use lowrisc::flash_ctrl::FlashCtrlRegisters; pub const FLASH_CTRL_BASE: StaticRef = - unsafe { StaticRef::new(0x4100_0000 as *const FlashCtrlRegisters) }; + unsafe { StaticRef::new(FLASH_CTRL_CORE_BASE_ADDR as *const FlashCtrlRegisters) }; diff --git a/chips/earlgrey/src/gpio.rs b/chips/earlgrey/src/gpio.rs index b5dacfcf13..42ae7f7cf1 100644 --- a/chips/earlgrey/src/gpio.rs +++ b/chips/earlgrey/src/gpio.rs @@ -1,73 +1,127 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! GPIO instantiation. use core::ops::{Index, IndexMut}; use kernel::utilities::StaticRef; use lowrisc::gpio::GpioRegisters; -pub use lowrisc::gpio::{pins, GpioPin}; -use lowrisc::padctrl::PadCtrlRegisters; +pub use lowrisc::gpio::{pins, GpioBitfield, GpioPin}; -pub const PADCTRL_BASE: StaticRef = - unsafe { StaticRef::new(0x4047_0000 as *const PadCtrlRegisters) }; +use crate::pinmux::PadConfig; +use crate::pinmux_config::EarlGreyPinmuxConfig; +use crate::registers::top_earlgrey::GPIO_BASE_ADDR; +use crate::registers::top_earlgrey::{ + MuxedPads, PinmuxInsel, PinmuxOutsel, PinmuxPeripheralIn, PINMUX_MIO_PERIPH_INSEL_IDX_OFFSET, + PINMUX_PERIPH_OUTSEL_IDX_OFFSET, +}; -pub const GPIO0_BASE: StaticRef = - unsafe { StaticRef::new(0x4004_0000 as *const GpioRegisters) }; +pub const GPIO_BASE: StaticRef = + unsafe { StaticRef::new(GPIO_BASE_ADDR as *const GpioRegisters) }; pub struct Port<'a> { - pins: [GpioPin<'a>; 32], + pins: [GpioPin<'a, PadConfig>; 32], +} + +impl From for PinmuxPeripheralIn { + fn from(pin: GpioBitfield) -> PinmuxPeripheralIn { + // We used fact that first 0-31 values are directly maped to GPIO + Self::try_from(pin.shift as u32).unwrap() + } +} + +impl From for PinmuxOutsel { + fn from(pin: GpioBitfield) -> Self { + // We skip first 3 constans to convert value to output selector + match Self::try_from(pin.shift as u32 + PINMUX_PERIPH_OUTSEL_IDX_OFFSET as u32) { + Ok(outsel) => outsel, + Err(_) => PinmuxOutsel::ConstantHighZ, + } + } +} + +// This function use extract GPIO mapping from initial pinmux configurations +pub fn gpio_pad_config(pin: GpioBitfield) -> PadConfig { + let inp: PinmuxPeripheralIn = PinmuxPeripheralIn::from(pin); + match Layout::INPUT[inp as usize] { + // Current implementation don't support Output only GPIO + PinmuxInsel::ConstantZero | PinmuxInsel::ConstantOne => PadConfig::Unconnected, + input_selector => { + if let Ok(pad) = MuxedPads::try_from( + input_selector as u32 - PINMUX_MIO_PERIPH_INSEL_IDX_OFFSET as u32, + ) { + let out: PinmuxOutsel = Layout::OUTPUT[pad as usize]; + // Checking for bi-directional I/O + if out == PinmuxOutsel::from(pin) { + PadConfig::InOut(pad, inp, out) + } else { + PadConfig::Input(pad, inp) + } + } else { + // Upper match checked for unconnected pad so in this + // place we probably have some invalid value in INPUT array. + PadConfig::Unconnected + } + } + } } +// Configuring first all GPIO based on board layout impl<'a> Port<'a> { - pub const fn new() -> Self { + pub fn new() -> Self { Self { + // Intentionally prevent splitting GpioPin to multiple line + #[rustfmt::skip] pins: [ - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin0), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin1), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin2), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin3), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin4), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin5), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin6), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin7), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin8), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin9), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin10), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin11), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin12), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin13), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin14), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin15), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin16), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin17), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin18), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin19), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin20), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin21), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin22), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin23), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin24), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin25), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin26), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin27), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin28), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin29), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin30), - GpioPin::new(GPIO0_BASE, PADCTRL_BASE, pins::pin31), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin0), pins::pin0), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin1), pins::pin1), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin2), pins::pin2), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin3), pins::pin3), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin4), pins::pin4), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin5), pins::pin5), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin6), pins::pin6), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin7), pins::pin7), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin8), pins::pin8), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin9), pins::pin9), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin10), pins::pin10), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin11), pins::pin11), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin12), pins::pin12), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin13), pins::pin13), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin14), pins::pin14), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin15), pins::pin15), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin16), pins::pin16), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin17), pins::pin17), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin18), pins::pin18), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin19), pins::pin19), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin20), pins::pin20), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin21), pins::pin21), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin22), pins::pin22), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin23), pins::pin23), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin24), pins::pin24), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin25), pins::pin25), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin26), pins::pin26), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin27), pins::pin27), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin28), pins::pin28), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin29), pins::pin29), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin30), pins::pin30), + GpioPin::new(GPIO_BASE, gpio_pad_config::(pins::pin31), pins::pin31), ], } } } impl<'a> Index for Port<'a> { - type Output = GpioPin<'a>; + type Output = GpioPin<'a, PadConfig>; - fn index(&self, index: usize) -> &GpioPin<'a> { + fn index(&self, index: usize) -> &GpioPin<'a, PadConfig> { &self.pins[index] } } impl<'a> IndexMut for Port<'a> { - fn index_mut(&mut self, index: usize) -> &mut GpioPin<'a> { + fn index_mut(&mut self, index: usize) -> &mut GpioPin<'a, PadConfig> { &mut self.pins[index] } } diff --git a/chips/earlgrey/src/hmac.rs b/chips/earlgrey/src/hmac.rs index 933c05723a..4c99b77cad 100644 --- a/chips/earlgrey/src/hmac.rs +++ b/chips/earlgrey/src/hmac.rs @@ -1,5 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::registers::top_earlgrey::HMAC_BASE_ADDR; use kernel::utilities::StaticRef; use lowrisc::hmac::HmacRegisters; pub const HMAC0_BASE: StaticRef = - unsafe { StaticRef::new(0x4111_0000 as *const HmacRegisters) }; + unsafe { StaticRef::new(HMAC_BASE_ADDR as *const HmacRegisters) }; diff --git a/chips/earlgrey/src/i2c.rs b/chips/earlgrey/src/i2c.rs index ef14703490..a1067e7b5b 100644 --- a/chips/earlgrey/src/i2c.rs +++ b/chips/earlgrey/src/i2c.rs @@ -1,5 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::registers::top_earlgrey::I2C0_BASE_ADDR; use kernel::utilities::StaticRef; use lowrisc::i2c::I2cRegisters; pub const I2C0_BASE: StaticRef = - unsafe { StaticRef::new(0x4008_0000 as *const I2cRegisters) }; + unsafe { StaticRef::new(I2C0_BASE_ADDR as *const I2cRegisters) }; diff --git a/chips/earlgrey/src/interrupts.rs b/chips/earlgrey/src/interrupts.rs index 7d9fa47af5..a08e656f2e 100644 --- a/chips/earlgrey/src/interrupts.rs +++ b/chips/earlgrey/src/interrupts.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Named interrupts for the Earl Grey chip. #![allow(dead_code)] @@ -73,33 +77,33 @@ pub const GPIO_PIN29: u32 = 62; pub const GPIO_PIN30: u32 = 63; pub const GPIO_PIN31: u32 = 64; -pub const SPI_DEVICERXF: u32 = 65; -pub const SPI_DEVICERXLVL: u32 = 66; -pub const SPI_DEVICETXLVL: u32 = 67; -pub const SPI_DEVICERXERR: u32 = 68; -pub const SPI_DEVICERXOVERFLOW: u32 = 69; -pub const SPI_DEVICETXUNDERFLOW: u32 = 70; -pub const SPI_DEVICE_TPM_HEADER_NOT_EMPTY: u32 = 71; -pub const SPI_HOST0ERROR: u32 = 72; -pub const SPI_HOST0SPIEVENT: u32 = 73; -pub const SPI_HOST1ERROR: u32 = 74; -pub const SPI_HOST1SPIEVENT: u32 = 75; - -pub const I2C0_FMTWATERMARK: u32 = 76; -pub const I2C0_RXWATERMARK: u32 = 77; -pub const I2C0_FMTOVERFLOW: u32 = 78; -pub const I2C0_RXOVERFLOW: u32 = 79; -pub const I2C0_NAK: u32 = 80; -pub const I2C0_SCLINTERFERENCE: u32 = 81; -pub const I2C0_SDAINTERFERENCE: u32 = 82; -pub const I2C0_STRETCHTIMEOUT: u32 = 83; -pub const I2C0_SDAUNSTABLE: u32 = 84; -pub const I2C0_TRANSCOMPLETE: u32 = 85; -pub const I2C0_TXEMPTY: u32 = 86; -pub const I2C0_TXNONEMPTY: u32 = 87; +pub const SPI_DEVICE_GENERICRXFULL: u32 = 65; +pub const SPI_DEVICE_GENERICRXWATERMARK: u32 = 66; +pub const SPI_DEVICE_GENERICTXWATERMARK: u32 = 67; +pub const SPI_DEVICE_GENERICRXERROR: u32 = 68; +pub const SPI_DEVICE_GENERICRXOVERFLOW: u32 = 69; +pub const SPI_DEVICE_GENERICTXUNDERFLOW: u32 = 70; +pub const SPI_DEVICE_UPLOADCMDFIFONOTEMPTY: u32 = 71; +pub const SPI_DEVICE_UPLOADPAYLOADNOTEMPTY: u32 = 72; +pub const SPI_DEVICE_UPLOADPAYLOADOVERFLOW: u32 = 73; +pub const SPI_DEVICE_READBUFWATERMARK: u32 = 74; +pub const SPI_DEVICE_READBUFFLIP: u32 = 75; +pub const SPI_DEVICE_TPMHEADERNOTEMPTY: u32 = 76; + +pub const I2C0_FMTWATERMARK: u32 = 77; +pub const I2C0_RXWATERMARK: u32 = 78; +pub const I2C0_FMTOVERFLOW: u32 = 79; +pub const I2C0_RXOVERFLOW: u32 = 80; +pub const I2C0_NAK: u32 = 81; +pub const I2C0_SCLINTERFERENCE: u32 = 82; +pub const I2C0_SDAINTERFERENCE: u32 = 83; +pub const I2C0_STRETCHTIMEOUT: u32 = 84; +pub const I2C0_SDAUNSTABLE: u32 = 85; +pub const I2C0_CMDCOMPLETE: u32 = 86; +pub const I2C0_TXSTRETCH: u32 = 87; pub const I2C0_TXOVERFLOW: u32 = 88; -pub const I2C0_ACQOVERFLOW: u32 = 89; -pub const I2C0_ACKSTOP: u32 = 90; +pub const I2C0_ACQFULL: u32 = 89; +pub const I2C0_UNEXPSTOP: u32 = 90; pub const I2C0_HOSTTIMEOUT: u32 = 91; pub const I2C1_FMTWATERMARK: u32 = 92; @@ -111,101 +115,108 @@ pub const I2C1_SCLINTERFERENCE: u32 = 97; pub const I2C1_SDAINTERFERENCE: u32 = 98; pub const I2C1_STRETCHTIMEOUT: u32 = 99; pub const I2C1_SDAUNSTABLE: u32 = 100; -pub const I2C1_TRANSCOMPLETE: u32 = 101; -pub const I2C1_TXEMPTY: u32 = 102; -pub const I2C1_TXNONEMPTY: u32 = 103; -pub const I2C1_TXOVERFLOW: u32 = 104; -pub const I2C1_ACQOVERFLOW: u32 = 105; -pub const I2C1_ACKSTOP: u32 = 106; -pub const I2C1_HOSTTIMEOUT: u32 = 107; - -pub const I2C2_FMTWATERMARK: u32 = 108; -pub const I2C2_RXWATERMARK: u32 = 109; -pub const I2C2_FMTOVERFLOW: u32 = 110; -pub const I2C2_RXOVERFLOW: u32 = 111; -pub const I2C2_NAK: u32 = 112; -pub const I2C2_SCLINTERFERENCE: u32 = 113; -pub const I2C2_SDAINTERFERENCE: u32 = 114; -pub const I2C2_STRETCHTIMEOUT: u32 = 115; -pub const I2C2_SDAUNSTABLE: u32 = 116; -pub const I2C2_TRANSCOMPLETE: u32 = 117; -pub const I2C2_TXEMPTY: u32 = 118; -pub const I2C2_TXNONEMPTY: u32 = 119; -pub const I2C2_TXOVERFLOW: u32 = 120; -pub const I2C2_ACQOVERFLOW: u32 = 121; -pub const I2C2_ACKSTOP: u32 = 122; -pub const I2C2_HOSTTIMEOUT: u32 = 123; - -pub const PATTGENDONECH0: u32 = 124; -pub const PATTGENDONECH1: u32 = 125; - -pub const RVTIMERTIMEREXPIRED0_0: u32 = 126; - -pub const USBDEV_PKTRECEIVED: u32 = 127; -pub const USBDEV_PKTSENT: u32 = 128; -pub const USBDEV_DISCONNECTED: u32 = 129; -pub const USBDEV_HOSTLOST: u32 = 130; -pub const USBDEV_LINKRESET: u32 = 131; -pub const USBDEV_LINKSUSPEND: u32 = 132; -pub const USBDEV_LINKRESUME: u32 = 133; -pub const USBDEV_AVEMPTY: u32 = 134; -pub const USBDEV_RXFULL: u32 = 135; -pub const USBDEV_AVOVERFLOW: u32 = 136; -pub const USBDEV_LINKINERR: u32 = 137; -pub const USBDEV_RXCRCERR: u32 = 138; -pub const USBDEV_RXPIDERR: u32 = 139; -pub const USBDEV_RXBITSTUFFERR: u32 = 140; -pub const USBDEV_FRAME: u32 = 141; -pub const USBDEV_CONNECTED: u32 = 142; -pub const USBDEV_LINKOUTERR: u32 = 143; - -pub const OTP_CTRLOTPOPERATIONDONE: u32 = 144; -pub const OTP_CTRLOTPERROR: u32 = 145; - -pub const ALERTHANDLERCLASSA: u32 = 146; -pub const ALERTHANDLERCLASSB: u32 = 147; -pub const ALERTHANDLERCLASSC: u32 = 148; -pub const ALERTHANDLERCLASSD: u32 = 1489; - -pub const PWRMGRAONWAKEUP: u32 = 150; - -pub const SYSRSTCTRL_AON_SYSRSTCTRL: u32 = 151; -pub const ADCCTRL_AON_DEBUGCABLE: u32 = 152; -pub const AONTIMER_AON_WKUP_TIMEREXPIRED: u32 = 153; -pub const AONTIMER_AON_WKUP_TIMERBARK: u32 = 154; - -pub const FLASHCTRL_PROGEMPTY: u32 = 155; -pub const FLASHCTRL_PROGLVL: u32 = 156; -pub const FLASHCTRL_RDFULL: u32 = 157; -pub const FLASHCTRL_RDLVL: u32 = 158; -pub const FLASHCTRL_OPDONE: u32 = 159; -pub const FLASHCTRL_CTRLERR: u32 = 160; - -pub const HMAC_HMACDONE: u32 = 161; -pub const HMAC_FIFOEMPTY: u32 = 162; -pub const HMAC_HMACERR: u32 = 163; - -pub const KMAC_KMACDONE: u32 = 164; -pub const KMAC_FIFOEMPTY: u32 = 165; -pub const KMAC_KMACERR: u32 = 166; - -pub const OTBN_DONE: u32 = 167; - -pub const KEYMGR_OPDONE: u32 = 168; - -pub const CSRNG_CSCMDREQDONE: u32 = 169; -pub const CSRNG_CSENTROPYREQ: u32 = 170; -pub const CSRNG_CSHWINSTEXC: u32 = 171; -pub const CSRNG_CSFATALERR: u32 = 172; - -pub const ENTROPYSRC_ESENTROPYVALID: u32 = 173; -pub const ENTROPYSRC_ESHEALTHTESTFAILED: u32 = 174; -pub const ENTROPYSRC_ESOBSERVEFIFOREADY: u32 = 175; -pub const ENTROPYSRC_ESFATALERR: u32 = 176; - -pub const EDN0EDN_CMDREQDONE: u32 = 177; -pub const EDN0EDN_FATALERR: u32 = 178; -pub const EDN1EDN_CMDREQDONE: u32 = 179; -pub const EDN1EDN_FATALERR: u32 = 180; - -pub const LAST: u32 = 178; +pub const I2C1_CMDCOMPLETE: u32 = 101; +pub const I2C1_TXSTRETCH: u32 = 102; +pub const I2C1_TXOVERFLOW: u32 = 103; +pub const I2C1_ACQFULL: u32 = 104; +pub const I2C1_UNEXPSTOP: u32 = 105; +pub const I2C1_HOSTTIMEOUT: u32 = 106; + +pub const I2C2_FMTWATERMARK: u32 = 107; +pub const I2C2_RXWATERMARK: u32 = 108; +pub const I2C2_FMTOVERFLOW: u32 = 109; +pub const I2C2_RXOVERFLOW: u32 = 110; +pub const I2C2_NAK: u32 = 111; +pub const I2C2_SCLINTERFERENCE: u32 = 112; +pub const I2C2_SDAINTERFERENCE: u32 = 113; +pub const I2C2_STRETCHTIMEOUT: u32 = 114; +pub const I2C2_SDAUNSTABLE: u32 = 115; +pub const I2C2_CMDCOMPLETE: u32 = 116; +pub const I2C2_TXSTRETCH: u32 = 117; +pub const I2C2_TXOVERFLOW: u32 = 118; +pub const I2C2_ACQFULL: u32 = 119; +pub const I2C2_UNEXPSTOP: u32 = 120; +pub const I2C2_HOSTTIMEOUT: u32 = 121; + +pub const PATTGENDONECH0: u32 = 122; +pub const PATTGENDONECH1: u32 = 123; + +pub const RVTIMERTIMEREXPIRED0_0: u32 = 124; + +pub const OTPCTRL_OTPOPERATIONDONE: u32 = 125; +pub const OTPCTRL_OTPERROR: u32 = 126; + +pub const ALERTHANDLER_CLASSA: u32 = 127; +pub const ALERTHANDLER_CLASSB: u32 = 128; +pub const ALERTHANDLER_CLASSC: u32 = 129; +pub const ALERTHANDLER_CLASSD: u32 = 130; + +pub const SPIHOST0_ERROR: u32 = 131; +pub const SPIHOST0_SPIEVENT: u32 = 132; + +pub const SPIHOST1_ERROR: u32 = 133; +pub const SPIHOST1_SPIEVENT: u32 = 134; + +pub const USBDEV_PKTRECEIVED: u32 = 135; +pub const USBDEV_PKTSENT: u32 = 136; +pub const USBDEV_DISCONNECTED: u32 = 137; +pub const USBDEV_HOSTLOST: u32 = 138; +pub const USBDEV_LINKRESET: u32 = 139; +pub const USBDEV_LINKSUSPEND: u32 = 140; +pub const USBDEV_LINKRESUME: u32 = 141; +pub const USBDEV_AVEMPTY: u32 = 142; +pub const USBDEV_RXFULL: u32 = 143; +pub const USBDEV_AVOVERFLOW: u32 = 144; +pub const USBDEV_LINKINERR: u32 = 145; +pub const USBDEV_RXCRCERR: u32 = 146; +pub const USBDEV_RXPIDERR: u32 = 147; +pub const USBDEV_RXBITSTUFFERR: u32 = 148; +pub const USBDEV_FRAME: u32 = 149; +pub const USBDEV_POWERED: u32 = 150; +pub const USBDEV_LINKOUTERR: u32 = 151; + +pub const PWRMGRAONWAKEUP: u32 = 152; +pub const SYSRST_CTRL_AON_SYSRST_CTRL: u32 = 153; +pub const ADC_CTRL_AON_MATCH_DONE: u32 = 154; + +pub const AON_TIMER_AON_WKUP_TIMER_EXPIRED: u32 = 155; +pub const AON_TIMER_AON_WDOG_TIMER_BARK: u32 = 156; + +pub const SENSOR_CTRL_IO_STATUS_CHANGE: u32 = 157; +pub const SENSOR_CTRL_INIT_STATUS_CHANGE: u32 = 158; + +pub const FLASHCTRL_PROGEMPTY: u32 = 159; +pub const FLASHCTRL_PROGLVL: u32 = 160; +pub const FLASHCTRL_RDFULL: u32 = 161; +pub const FLASHCTRL_RDLVL: u32 = 162; +pub const FLASHCTRL_OPDONE: u32 = 163; +pub const FLASHCTRL_CORRERR: u32 = 164; + +pub const HMAC_HMACDONE: u32 = 165; +pub const HMAC_FIFOEMPTY: u32 = 166; +pub const HMAC_HMACERR: u32 = 167; + +pub const KMAC_KMACDONE: u32 = 168; +pub const KMAC_FIFOEMPTY: u32 = 169; +pub const KMAC_KMACERR: u32 = 170; + +pub const OTBN_DONE: u32 = 171; + +pub const KEYMGR_OP_DONE: u32 = 172; + +pub const CSRNG_CSCMDREQDONE: u32 = 173; +pub const CSRNG_CSENTROPYREQ: u32 = 174; +pub const CSRNG_CSHWINSTEXC: u32 = 175; +pub const CSRNG_CSFATALERR: u32 = 176; + +pub const ENTROPY_SRC_ES_ENTROPY_VALID: u32 = 177; +pub const ENTROPY_SRC_ES_HEALTH_TEST_FAILED: u32 = 178; +pub const ENTROPY_SRC_ES_OBSERVE_FIFO_READY: u32 = 179; +pub const ENTROPY_SRC_ES_FATAL_ERR: u32 = 180; + +pub const EDN0_EDN_CMD_REQ_DONE: u32 = 181; +pub const EDN0_EDN_FATAL_ERR: u32 = 182; +pub const EDN1_EDN_CMD_REQ_DONE: u32 = 183; +pub const EDN1_EDN_FATAL_ERR: u32 = 184; +// Last valid plic interrupt ID +pub const IRQ_ID_LAST: u32 = 184; diff --git a/chips/earlgrey/src/lib.rs b/chips/earlgrey/src/lib.rs index 05b42032d5..96fb4c0848 100644 --- a/chips/earlgrey/src/lib.rs +++ b/chips/earlgrey/src/lib.rs @@ -1,23 +1,36 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Drivers and chip support for EarlGrey. -#![feature(asm, const_fn_trait_bound, naked_functions)] #![no_std] #![crate_name = "earlgrey"] #![crate_type = "rlib"] +// `registers/rv_plic_regs` has many register definitions in `register_structs()!` +// and requires a deeper recursion limit than the default to fully expand. +#![recursion_limit = "256"] pub mod chip_config; +pub mod pinmux_config; + mod interrupts; pub mod aes; +pub mod aon_timer; pub mod chip; pub mod csrng; +pub mod epmp; pub mod flash_ctrl; pub mod gpio; pub mod hmac; pub mod i2c; pub mod otbn; +pub mod pinmux; pub mod plic; pub mod pwrmgr; +pub mod registers; +pub mod spi_host; pub mod timer; pub mod uart; pub mod usbdev; diff --git a/chips/earlgrey/src/otbn.rs b/chips/earlgrey/src/otbn.rs index b763ef3c2b..07755e24a8 100644 --- a/chips/earlgrey/src/otbn.rs +++ b/chips/earlgrey/src/otbn.rs @@ -1,5 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::registers::top_earlgrey::OTBN_BASE_ADDR; use kernel::utilities::StaticRef; use lowrisc::otbn::OtbnRegisters; pub const OTBN_BASE: StaticRef = - unsafe { StaticRef::new(0x4113_0000 as *const OtbnRegisters) }; + unsafe { StaticRef::new(OTBN_BASE_ADDR as *const OtbnRegisters) }; diff --git a/chips/earlgrey/src/pinmux.rs b/chips/earlgrey/src/pinmux.rs new file mode 100644 index 0000000000..3ac7b95aa2 --- /dev/null +++ b/chips/earlgrey/src/pinmux.rs @@ -0,0 +1,414 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Mux'ing between physical pads and GPIO or other peripherals. + +use kernel::hil::gpio; +use kernel::hil::gpio::{Configuration, Configure, FloatingState}; +use kernel::utilities::registers::interfaces::{Readable, Writeable}; +use kernel::utilities::registers::{register_bitfields, FieldValue, LocalRegisterCopy}; +use kernel::utilities::StaticRef; + +use crate::registers::pinmux_regs::{ + PinmuxRegisters, DIO_PAD_ATTR_REGWEN, MIO_OUTSEL_REGWEN, MIO_PAD_ATTR_REGWEN, + MIO_PERIPH_INSEL_REGWEN, +}; +use crate::registers::top_earlgrey::{ + DirectPads, MuxedPads, PinmuxInsel, PinmuxOutsel, PinmuxPeripheralIn, PINMUX_AON_BASE_ADDR, + PINMUX_MIO_PERIPH_INSEL_IDX_OFFSET, +}; + +pub const PINMUX_BASE: StaticRef = + unsafe { StaticRef::new(PINMUX_AON_BASE_ADDR as *const PinmuxRegisters) }; + +// To avoid code duplication for MIO/DIO we introduce +// one register layout for both types of IO. In the future this code +// should be replaced by official improved auto generated definitions. +// OpenTitan documentation reference: +// +// +register_bitfields![u32, + pub(crate) PAD_ATTR [ + INVERT OFFSET(0) NUMBITS(1) [], + VIRTUAL_OPEN_DRAIN_EN OFFSET(1) NUMBITS(1) [], + PULL_EN OFFSET(2) NUMBITS(1) [], + PULL OFFSET(3) NUMBITS(1) [ + DOWN = 0, + UP = 1, + ], + KEEPER_EN OFFSET(4) NUMBITS(1) [], + SCHMITT_EN OFFSET(5) NUMBITS(1) [], + OPEN_DRAIN_EN OFFSET(6) NUMBITS(1) [], + SLEW_RATE OFFSET(16) NUMBITS(2) [], + DRIVE_STRENGTH OFFSET(20) NUMBITS(4) [], + ], +]; + +type PadAttribute = LocalRegisterCopy; + +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum Pad { + Mio(MuxedPads), + Dio(DirectPads), +} + +impl Pad { + /// Extract value of attributes using common layout + fn pad_attr(&self) -> PadAttribute { + PadAttribute::new(match *self { + Self::Mio(mio) => PINMUX_BASE.mio_pad_attr[mio as usize].get(), + Self::Dio(dio) => PINMUX_BASE.dio_pad_attr[dio as usize].get(), + }) + } + + /// Modify value of pad attribute using common MIO/DIO register layout + fn modify_pad_attr(&self, flags: FieldValue) { + let mut attr = self.pad_attr(); + attr.modify(flags); + match *self { + Self::Mio(mio) => &PINMUX_BASE.mio_pad_attr[mio as usize].set(attr.get()), + Self::Dio(dio) => &PINMUX_BASE.dio_pad_attr[dio as usize].set(attr.get()), + }; + } + + pub fn set_floating_state(&self, mode: gpio::FloatingState) { + self.modify_pad_attr(match mode { + gpio::FloatingState::PullUp => PAD_ATTR::PULL_EN::SET + PAD_ATTR::PULL::UP, + gpio::FloatingState::PullDown => PAD_ATTR::PULL_EN::SET + PAD_ATTR::PULL::DOWN, + gpio::FloatingState::PullNone => PAD_ATTR::PULL_EN::CLEAR + PAD_ATTR::PULL::CLEAR, + }); + } + + pub fn set_output_open_drain(&self) { + self.modify_pad_attr(PAD_ATTR::OPEN_DRAIN_EN::SET); + } + + pub fn set_output_push_pull(&self) { + self.modify_pad_attr(PAD_ATTR::OPEN_DRAIN_EN::CLEAR); + } + + pub fn set_invert_sense(&self, invert: bool) { + if invert { + self.modify_pad_attr(PAD_ATTR::INVERT::SET) + } else { + self.modify_pad_attr(PAD_ATTR::INVERT::CLEAR) + } + } + + pub fn floating_state(&self) -> gpio::FloatingState { + let pad_attr: PadAttribute = self.pad_attr(); + if pad_attr.matches_all(PAD_ATTR::PULL::UP + PAD_ATTR::PULL_EN::SET) { + gpio::FloatingState::PullUp + } else if pad_attr.matches_all(PAD_ATTR::PULL::DOWN + PAD_ATTR::PULL_EN::SET) { + gpio::FloatingState::PullDown + } else { + gpio::FloatingState::PullNone + } + } + + /// Prohibits any further changes to input/output/open-drain or pullup configuration. + pub fn lock_pad_attributes(&self) { + match *self { + Self::Mio(mio) => PINMUX_BASE.mio_pad_attr_regwen[(mio as u32) as usize] + .write(MIO_PAD_ATTR_REGWEN::EN_0::CLEAR), + Self::Dio(dio) => PINMUX_BASE.dio_pad_attr_regwen[(dio as u32) as usize] + .write(DIO_PAD_ATTR_REGWEN::EN_0::CLEAR), + }; + } +} + +// Configuration of PINMUX multiplexers for I/O +// OpenTitan Documentation reference: +// https://opentitan.org/book/hw/ip/pinmux/doc/programmers_guide.html#pinmux-configuration + +pub trait SelectOutput { + /// Connect particular pad to internal peripheral + fn connect_output(self, output: PinmuxOutsel); + + /// Connect particular pad output to always low + fn connect_low(self); + + /// Connect particular pad output to always high + fn connect_high(self); + + /// This function disconnect pad from peripheral + /// and set it to High-Impedance state + fn connect_high_z(self); + + /// Lock selection of output for particular pad + fn lock(self); + + /// Get value of current output selection + fn get_selector(self) -> PinmuxOutsel; +} + +// We make a implicit conversion between PinmuxMioOut and MuxedPad +impl SelectOutput for MuxedPads { + fn connect_output(self, output: PinmuxOutsel) { + PINMUX_BASE.mio_outsel[self as usize].set(output as u32) + } + + fn connect_low(self) { + PINMUX_BASE.mio_outsel[self as usize].set(PinmuxOutsel::ConstantZero as u32) + } + + fn connect_high(self) { + PINMUX_BASE.mio_outsel[self as usize].set(PinmuxOutsel::ConstantOne as u32) + } + + fn connect_high_z(self) { + PINMUX_BASE.mio_outsel[self as usize].set(PinmuxOutsel::ConstantHighZ as u32) + } + + fn lock(self) { + PINMUX_BASE.mio_outsel_regwen[self as usize].write(MIO_OUTSEL_REGWEN::EN_0::CLEAR); + } + + fn get_selector(self) -> PinmuxOutsel { + match PinmuxOutsel::try_from(PINMUX_BASE.mio_outsel[self as usize].get()) { + Ok(sel) => sel, + // When this panic happend it mean we have some glitch in registers + // or a incorect version definition of registers. + Err(val) => panic!("PINMUX: Invalid register value: {}", val), + } + } +} + +pub trait SelectInput { + /// Connect internal peripheral input to particular pad + fn connect_input(self, input: PinmuxInsel); + + /// Connect internal peripherals input to always low + fn connect_low(self); + + /// Connect internal peripherals input to always high + fn connect_high(self); + + /// Lock input configurations + fn lock(self); + + /// Get value of current input selection + fn get_selector(self) -> PinmuxInsel; +} + +/// MuxedPads names and values overlap with PinmuxInsel, +/// function below is used to convert it to valid PinmuxInsel. +/// OpenTitan documentation reference: +/// +impl From for PinmuxInsel { + fn from(pad: MuxedPads) -> Self { + // Add 2 to skip constant ConstantZero and ConstantOne. + match PinmuxInsel::try_from(pad as u32 + PINMUX_MIO_PERIPH_INSEL_IDX_OFFSET as u32) { + Ok(select) => select, + Err(_) => PinmuxInsel::ConstantZero, + } + } +} + +impl SelectInput for PinmuxPeripheralIn { + fn connect_input(self, input: PinmuxInsel) { + PINMUX_BASE.mio_periph_insel[self as usize].set(input as u32) + } + + fn connect_low(self) { + PINMUX_BASE.mio_periph_insel[self as usize].set(PinmuxInsel::ConstantZero as u32) + } + + fn connect_high(self) { + PINMUX_BASE.mio_periph_insel[self as usize].set(PinmuxInsel::ConstantOne as u32) + } + + fn lock(self) { + PINMUX_BASE.mio_periph_insel_regwen[self as usize] + .write(MIO_PERIPH_INSEL_REGWEN::EN_0::CLEAR); + } + + fn get_selector(self) -> PinmuxInsel { + match PinmuxInsel::try_from(PINMUX_BASE.mio_periph_insel[self as usize].get()) { + Ok(sel) => sel, + // + Err(val) => panic!("PINMUX: Invalid insel register value {}", val), + } + } +} + +// Enum below represent connection betwen pad and peripherals +// Diagram bellow help with interpreting meaning of input/output in enum bellow +// +// According to OpenTitan documentations uninitialized pinmux I/O selector are set to default +// values. With are respectively +// output selector - PinmuxOutsel::ConstantHighZ +// input selector - PinmuxInsel::ConstantZero +// +// +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum PadConfig { + // Internal Output and input not conected to any pad + Unconnected, + // Allow to pass signal from pad to peripheral + // [PAD]------>[PeripherapInput] + Input(MuxedPads, PinmuxPeripheralIn), + // Allow to pass signal form peripheral to pad + // [PAD]<------[PeripheralOut] + Output(MuxedPads, PinmuxOutsel), + // Allow to pass signal form pad to peripheral in bouth directions + // [PAD]------>[PeripherapInput] + // [PAD]<------[PeripheralOut] + InOut(MuxedPads, PinmuxPeripheralIn, PinmuxOutsel), +} + +impl PadConfig { + /// Connect Pad to internal peripheral I/O using pinmux multiplexers + pub fn connect(&self) { + match *self { + PadConfig::Unconnected => {} + PadConfig::Input(pad, peripheral_in) => { + peripheral_in.connect_input(PinmuxInsel::from(pad)); + } + PadConfig::Output(pad, peripheral_out) => { + pad.connect_output(peripheral_out); + } + PadConfig::InOut(pad, peripheral_in, peripheral_out) => { + peripheral_in.connect_input(PinmuxInsel::from(pad)); + pad.connect_output(peripheral_out); + } + } + } + + /// Disconnect pad from internal input and connect to always Low signal + pub fn disconnect_input(&self) { + match *self { + PadConfig::Unconnected => {} + PadConfig::Input(_pad, peripheral_in) => peripheral_in.connect_low(), + PadConfig::Output(_pad, _peripheral_out) => {} + PadConfig::InOut(_pad, peripheral_in, _peripheral_out) => { + peripheral_in.connect_low(); + } + }; + } + + // Disconnect pad from internal output and connect to Hi-Z + pub fn disconnect_output(&self) { + match *self { + PadConfig::Unconnected => {} + PadConfig::Input(_pad, _peripheral_in) => {} + PadConfig::Output(pad, _peripheral_out) => pad.connect_high_z(), + PadConfig::InOut(pad, _peripheral_in, _peripheral_out) => { + pad.connect_high_z(); + } + }; + } + + /// Disconnect input and output from peripheral/pad + /// and connect to internal Hi-Z/Low signal + pub fn disconnect(&self) { + match *self { + PadConfig::Unconnected => {} + PadConfig::Input(_pad, peripheral_in) => { + peripheral_in.connect_low(); + } + PadConfig::Output(pad, _peripheral_out) => { + pad.connect_high_z(); + } + PadConfig::InOut(pad, peripheral_in, _peripheral_out) => { + peripheral_in.connect_low(); + pad.connect_high_z(); + } + } + } + + /// Return copy of `enum` representing MIO pad + /// associated with this connection + pub fn get_pad(&self) -> Option { + match *self { + PadConfig::Unconnected => None, + PadConfig::Input(pad, _) => Some(Pad::Mio(pad)), + PadConfig::Output(pad, _) => Some(Pad::Mio(pad)), + PadConfig::InOut(pad, _, _) => Some(Pad::Mio(pad)), + } + } +} + +impl From for Configuration { + fn from(pad: PadConfig) -> Configuration { + match pad { + PadConfig::Unconnected => Configuration::Other, + PadConfig::Input(_pad, peripheral_in) => match peripheral_in.get_selector() { + PinmuxInsel::ConstantZero => Configuration::LowPower, + PinmuxInsel::ConstantOne => Configuration::Function, + _ => Configuration::Input, + }, + PadConfig::Output(pad, _peripheral_out) => match pad.get_selector() { + PinmuxOutsel::ConstantZero => Configuration::Function, + PinmuxOutsel::ConstantOne => Configuration::Function, + PinmuxOutsel::ConstantHighZ => Configuration::LowPower, + _ => Configuration::Output, + }, + PadConfig::InOut(pad, peripheral_in, _peripheral_out) => { + let input_selector = peripheral_in.get_selector(); + let output_selector = pad.get_selector(); + match (input_selector, output_selector) { + (PinmuxInsel::ConstantZero, PinmuxOutsel::ConstantHighZ) => { + Configuration::LowPower + } + ( + PinmuxInsel::ConstantOne | PinmuxInsel::ConstantZero, + PinmuxOutsel::ConstantZero | PinmuxOutsel::ConstantOne, + ) => Configuration::Function, + (_, _) => Configuration::InputOutput, + } + } + } + } +} + +impl Configure for PadConfig { + fn configuration(&self) -> Configuration { + Configuration::from(*self) + } + + fn make_output(&self) -> Configuration { + match self.configuration() { + Configuration::LowPower => self.connect(), + _ => {} + }; + self.configuration() + } + + fn disable_output(&self) -> Configuration { + self.disconnect_output(); + self.configuration() + } + + fn make_input(&self) -> Configuration { + match self.configuration() { + Configuration::LowPower => self.connect(), + _ => {} + }; + self.configuration() + } + + fn disable_input(&self) -> Configuration { + self.disconnect_input(); + self.configuration() + } + + fn deactivate_to_low_power(&self) { + self.disconnect(); + } + + fn set_floating_state(&self, state: FloatingState) { + if let Some(pad) = self.get_pad() { + pad.set_floating_state(state); + } + } + + fn floating_state(&self) -> FloatingState { + if let Some(pad) = self.get_pad() { + pad.floating_state() + } else { + FloatingState::PullNone + } + } +} diff --git a/chips/earlgrey/src/pinmux_config.rs b/chips/earlgrey/src/pinmux_config.rs new file mode 100644 index 0000000000..32a1781367 --- /dev/null +++ b/chips/earlgrey/src/pinmux_config.rs @@ -0,0 +1,40 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Chip sepcific pinmux Configurations + +use crate::pinmux::{SelectInput, SelectOutput}; +use crate::registers::top_earlgrey::{ + MuxedPads, PinmuxInsel, PinmuxOutsel, PinmuxPeripheralIn, NUM_MIO_PADS, +}; + +/// Number of input selector entry (Last input + 1) +pub const INPUT_NUM: usize = PinmuxPeripheralIn::UsbdevSense as usize + 1; +/// Number of output selctor entry +pub const OUTPUT_NUM: usize = NUM_MIO_PADS; + +/// Representations of Earlgrey pinmux configuration on targeted board +pub trait EarlGreyPinmuxConfig { + /// Array representing configuration of pinmux input selctor + const INPUT: &'static [PinmuxInsel; INPUT_NUM]; + + /// Array representing configurations of pinmux output selecto + const OUTPUT: &'static [PinmuxOutsel; OUTPUT_NUM]; + + /// Setup pinmux configurations for all multiplexed pads + fn setup() { + // setup pinmux input + for index in 0..INPUT_NUM { + if let Ok(peripheral) = PinmuxPeripheralIn::try_from(index as u32) { + peripheral.connect_input(Self::INPUT[index]); + } + } + // setup pinmux output + for index in 0..OUTPUT_NUM { + if let Ok(pad) = MuxedPads::try_from(index as u32) { + pad.connect_output(Self::OUTPUT[index]); + } + } + } +} diff --git a/chips/earlgrey/src/plic.rs b/chips/earlgrey/src/plic.rs index dec49945fa..e074197b17 100644 --- a/chips/earlgrey/src/plic.rs +++ b/chips/earlgrey/src/plic.rs @@ -1,5 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Platform Level Interrupt Control peripheral driver. +use crate::registers::top_earlgrey::RV_PLIC_BASE_ADDR; use kernel::utilities::cells::VolatileCell; use kernel::utilities::registers::interfaces::{Readable, Writeable}; use kernel::utilities::registers::LocalRegisterCopy; @@ -7,17 +12,18 @@ use kernel::utilities::registers::{register_bitfields, register_structs, ReadOnl use kernel::utilities::StaticRef; pub const PLIC_BASE: StaticRef = - unsafe { StaticRef::new(0x4800_0000 as *const PlicRegisters) }; + unsafe { StaticRef::new(RV_PLIC_BASE_ADDR as *const PlicRegisters) }; pub static mut PLIC: Plic = Plic::new(PLIC_BASE); pub const PLIC_REGS: usize = 6; +pub const PLIC_IRQ_NUM: usize = 185; register_structs! { pub PlicRegisters { /// Interrupt Priority Registers - (0x000 => priority: [ReadWrite; 181]), - (0x2d4 => _reserved0), + (0x000 => priority: [ReadWrite; PLIC_IRQ_NUM]), + (0x2e4 => _reserved0), /// Interrupt Pending Register (0x1000 => pending: [ReadOnly; PLIC_REGS]), (0x1018 => _reserved1), @@ -39,7 +45,7 @@ register_structs! { register_bitfields![u32, priority [ - Priority OFFSET(0) NUMBITS(3) [] + Priority OFFSET(0) NUMBITS(2) [] ] ]; @@ -86,24 +92,11 @@ impl Plic { /// Disable specific interrupt. pub fn disable(&self, index: u32) { - let offset = if index < 32 { - 0 - } else if index < 64 { - 1 - } else if index < 96 { - 2 - } else if index < 128 { - 3 - } else if index < 160 { - 4 - } else if index < 192 { - 5 - } else { + if index >= PLIC_IRQ_NUM as u32 { panic!("Invalid IRQ: {}", index); }; - - let irq = index % 32; - let mask = !(1 << irq); + let offset = (index / 32) as usize; + let mask = !(1 << (index % 32)); self.registers.enable[offset].set(self.registers.enable[offset].get() & mask); } @@ -133,25 +126,14 @@ impl Plic { /// Saved interrupts can be retrieved by calling `get_saved_interrupts()`. /// Saved interrupts are cleared when `'complete()` is called. pub unsafe fn save_interrupt(&self, index: u32) { - let offset = if index < 32 { - 0 - } else if index < 64 { - 1 - } else if index < 96 { - 2 - } else if index < 128 { - 3 - } else if index < 160 { - 4 - } else if index < 192 { - 5 - } else { + if index >= PLIC_IRQ_NUM as u32 { panic!("Invalid IRQ: {}", index); }; - let irq = index % 32; + let offset = (index / 32) as usize; + let mask = 1 << (index % 32); // OR the current saved state with the new value - let new_saved = self.saved[offset].get().get() | 1 << irq; + let new_saved = self.saved[offset].get().get() | mask; // Set the new state self.saved[offset].set(LocalRegisterCopy::new(new_saved)); @@ -167,7 +149,6 @@ impl Plic { return Some(saved.trailing_zeros() + (i as u32 * 32)); } } - None } @@ -176,26 +157,14 @@ impl Plic { /// Interrupts must be disabled before this is called. pub unsafe fn complete(&self, index: u32) { self.registers.claim.set(index); - - let offset = if index < 32 { - 0 - } else if index < 64 { - 1 - } else if index < 96 { - 2 - } else if index < 128 { - 3 - } else if index < 160 { - 4 - } else if index < 192 { - 5 - } else { + if index >= PLIC_IRQ_NUM as u32 { panic!("Invalid IRQ: {}", index); }; - let irq = index % 32; + let offset = (index / 32) as usize; + let mask = !(1 << (index % 32)); // OR the current saved state with the new value - let new_saved = self.saved[offset].get().get() & !(1 << irq); + let new_saved = self.saved[offset].get().get() & mask; // Set the new state self.saved[offset].set(LocalRegisterCopy::new(new_saved)); diff --git a/chips/earlgrey/src/pwrmgr.rs b/chips/earlgrey/src/pwrmgr.rs index ca9cd12fb5..71da22a3a1 100644 --- a/chips/earlgrey/src/pwrmgr.rs +++ b/chips/earlgrey/src/pwrmgr.rs @@ -1,5 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::registers::top_earlgrey::PWRMGR_AON_BASE_ADDR; use kernel::utilities::StaticRef; use lowrisc::pwrmgr::PwrMgrRegisters; pub(crate) const PWRMGR_BASE: StaticRef = - unsafe { StaticRef::new(0x4040_0000 as *const PwrMgrRegisters) }; + unsafe { StaticRef::new(PWRMGR_AON_BASE_ADDR as *const PwrMgrRegisters) }; diff --git a/chips/earlgrey/src/registers/README.md b/chips/earlgrey/src/registers/README.md new file mode 100644 index 0000000000..9afecdce72 --- /dev/null +++ b/chips/earlgrey/src/registers/README.md @@ -0,0 +1,16 @@ +# Earlgrey Register Definitions + +The files in this directory are auto-generated register definitions for +Earlgrey peripherals. These files were auto-generated from the OpenTitan +codebase as follows: + +```bash +$ cd $OPENTITAN_TREE +$ git checkout earlgrey_es +$ bazel build //sw/device/tock:tock_earlgrey_registers +$ tar -C $TOCK_TREE -xvf bazel-bin/sw/device/tock/tock_earlgrey_registers.tar +``` + +Note: the existence of a file in this directory does not necessarily mean +that a tock peripheral implementation exists. These files are _only_ +the register definitions. diff --git a/chips/earlgrey/src/registers/alert_handler_regs.rs b/chips/earlgrey/src/registers/alert_handler_regs.rs new file mode 100644 index 0000000000..099e06eeb1 --- /dev/null +++ b/chips/earlgrey/src/registers/alert_handler_regs.rs @@ -0,0 +1,500 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for alert_handler. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/top_earlgrey/ip_autogen/alert_handler/data/alert_handler.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of alert channels. +pub const ALERT_HANDLER_PARAM_N_ALERTS: u32 = 65; +/// Number of LPGs. +pub const ALERT_HANDLER_PARAM_N_LPG: u32 = 24; +/// Width of LPG ID. +pub const ALERT_HANDLER_PARAM_N_LPG_WIDTH: u32 = 5; +/// Width of the escalation timer. +pub const ALERT_HANDLER_PARAM_ESC_CNT_DW: u32 = 32; +/// Width of the accumulation counter. +pub const ALERT_HANDLER_PARAM_ACCU_CNT_DW: u32 = 16; +/// Number of classes +pub const ALERT_HANDLER_PARAM_N_CLASSES: u32 = 4; +/// Number of escalation severities +pub const ALERT_HANDLER_PARAM_N_ESC_SEV: u32 = 4; +/// Number of escalation phases +pub const ALERT_HANDLER_PARAM_N_PHASES: u32 = 4; +/// Number of local alerts +pub const ALERT_HANDLER_PARAM_N_LOC_ALERT: u32 = 7; +/// Width of ping counter +pub const ALERT_HANDLER_PARAM_PING_CNT_DW: u32 = 16; +/// Width of phase ID +pub const ALERT_HANDLER_PARAM_PHASE_DW: u32 = 2; +/// Width of class ID +pub const ALERT_HANDLER_PARAM_CLASS_DW: u32 = 2; +/// Local alert ID for alert ping failure. +pub const ALERT_HANDLER_PARAM_LOCAL_ALERT_ID_ALERT_PINGFAIL: u32 = 0; +/// Local alert ID for escalation ping failure. +pub const ALERT_HANDLER_PARAM_LOCAL_ALERT_ID_ESC_PINGFAIL: u32 = 1; +/// Local alert ID for alert integrity failure. +pub const ALERT_HANDLER_PARAM_LOCAL_ALERT_ID_ALERT_INTEGFAIL: u32 = 2; +/// Local alert ID for escalation integrity failure. +pub const ALERT_HANDLER_PARAM_LOCAL_ALERT_ID_ESC_INTEGFAIL: u32 = 3; +/// Local alert ID for bus integrity failure. +pub const ALERT_HANDLER_PARAM_LOCAL_ALERT_ID_BUS_INTEGFAIL: u32 = 4; +/// Local alert ID for shadow register update error. +pub const ALERT_HANDLER_PARAM_LOCAL_ALERT_ID_SHADOW_REG_UPDATE_ERROR: u32 = 5; +/// Local alert ID for shadow register storage error. +pub const ALERT_HANDLER_PARAM_LOCAL_ALERT_ID_SHADOW_REG_STORAGE_ERROR: u32 = 6; +/// Last local alert ID. +pub const ALERT_HANDLER_PARAM_LOCAL_ALERT_ID_LAST: u32 = 6; +/// Register width +pub const ALERT_HANDLER_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub AlertHandlerRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Register write enable for !!PING_TIMEOUT_CYC_SHADOWED and !!PING_TIMER_EN_SHADOWED. + (0x000c => pub(crate) ping_timer_regwen: ReadWrite), + /// Ping timeout cycle count. + (0x0010 => pub(crate) ping_timeout_cyc_shadowed: ReadWrite), + /// Ping timer enable. + (0x0014 => pub(crate) ping_timer_en_shadowed: ReadWrite), + /// Register write enable for alert enable bits. + (0x0018 => pub(crate) alert_regwen: [ReadWrite; 65]), + /// Enable register for alerts. + (0x011c => pub(crate) alert_en_shadowed: [ReadWrite; 65]), + /// Class assignment of alerts. + (0x0220 => pub(crate) alert_class_shadowed: [ReadWrite; 65]), + /// Alert Cause Register + (0x0324 => pub(crate) alert_cause: [ReadWrite; 65]), + /// Register write enable for alert enable bits. + (0x0428 => pub(crate) loc_alert_regwen: [ReadWrite; 7]), + /// Enable register for the local alerts + (0x0444 => pub(crate) loc_alert_en_shadowed: [ReadWrite; 7]), + /// Class assignment of the local alerts + (0x0460 => pub(crate) loc_alert_class_shadowed: [ReadWrite; 7]), + /// Alert Cause Register for the local alerts + (0x047c => pub(crate) loc_alert_cause: [ReadWrite; 7]), + /// Lock bit for Class A configuration. + (0x0498 => pub(crate) classa_regwen: ReadWrite), + /// Escalation control register for alert Class A. Can not be modified if !!CLASSA_REGWEN is + /// false. + (0x049c => pub(crate) classa_ctrl_shadowed: ReadWrite), + /// Clear enable for escalation protocol of Class A alerts. + (0x04a0 => pub(crate) classa_clr_regwen: ReadWrite), + /// Clear for escalation protocol of Class A. + (0x04a4 => pub(crate) classa_clr_shadowed: ReadWrite), + /// Current accumulation value for alert Class A. Software can clear this register + (0x04a8 => pub(crate) classa_accum_cnt: ReadWrite), + /// Accumulation threshold value for alert Class A. + (0x04ac => pub(crate) classa_accum_thresh_shadowed: ReadWrite), + /// Interrupt timeout in cycles. + (0x04b0 => pub(crate) classa_timeout_cyc_shadowed: ReadWrite), + /// Crashdump trigger configuration for Class A. + (0x04b4 => pub(crate) classa_crashdump_trigger_shadowed: ReadWrite), + /// Duration of escalation phase 0 for Class A. + (0x04b8 => pub(crate) classa_phase0_cyc_shadowed: ReadWrite), + /// Duration of escalation phase 1 for Class A. + (0x04bc => pub(crate) classa_phase1_cyc_shadowed: ReadWrite), + /// Duration of escalation phase 2 for Class A. + (0x04c0 => pub(crate) classa_phase2_cyc_shadowed: ReadWrite), + /// Duration of escalation phase 3 for Class A. + (0x04c4 => pub(crate) classa_phase3_cyc_shadowed: ReadWrite), + /// Escalation counter in cycles for Class A. + (0x04c8 => pub(crate) classa_esc_cnt: ReadWrite), + /// Current escalation state of Class A. See also !!CLASSA_ESC_CNT. + (0x04cc => pub(crate) classa_state: ReadWrite), + /// Lock bit for Class B configuration. + (0x04d0 => pub(crate) classb_regwen: ReadWrite), + /// Escalation control register for alert Class B. Can not be modified if !!CLASSB_REGWEN is + /// false. + (0x04d4 => pub(crate) classb_ctrl_shadowed: ReadWrite), + /// Clear enable for escalation protocol of Class B alerts. + (0x04d8 => pub(crate) classb_clr_regwen: ReadWrite), + /// Clear for escalation protocol of Class B. + (0x04dc => pub(crate) classb_clr_shadowed: ReadWrite), + /// Current accumulation value for alert Class B. Software can clear this register + (0x04e0 => pub(crate) classb_accum_cnt: ReadWrite), + /// Accumulation threshold value for alert Class B. + (0x04e4 => pub(crate) classb_accum_thresh_shadowed: ReadWrite), + /// Interrupt timeout in cycles. + (0x04e8 => pub(crate) classb_timeout_cyc_shadowed: ReadWrite), + /// Crashdump trigger configuration for Class B. + (0x04ec => pub(crate) classb_crashdump_trigger_shadowed: ReadWrite), + /// Duration of escalation phase 0 for Class B. + (0x04f0 => pub(crate) classb_phase0_cyc_shadowed: ReadWrite), + /// Duration of escalation phase 1 for Class B. + (0x04f4 => pub(crate) classb_phase1_cyc_shadowed: ReadWrite), + /// Duration of escalation phase 2 for Class B. + (0x04f8 => pub(crate) classb_phase2_cyc_shadowed: ReadWrite), + /// Duration of escalation phase 3 for Class B. + (0x04fc => pub(crate) classb_phase3_cyc_shadowed: ReadWrite), + /// Escalation counter in cycles for Class B. + (0x0500 => pub(crate) classb_esc_cnt: ReadWrite), + /// Current escalation state of Class B. See also !!CLASSB_ESC_CNT. + (0x0504 => pub(crate) classb_state: ReadWrite), + /// Lock bit for Class C configuration. + (0x0508 => pub(crate) classc_regwen: ReadWrite), + /// Escalation control register for alert Class C. Can not be modified if !!CLASSC_REGWEN is + /// false. + (0x050c => pub(crate) classc_ctrl_shadowed: ReadWrite), + /// Clear enable for escalation protocol of Class C alerts. + (0x0510 => pub(crate) classc_clr_regwen: ReadWrite), + /// Clear for escalation protocol of Class C. + (0x0514 => pub(crate) classc_clr_shadowed: ReadWrite), + /// Current accumulation value for alert Class C. Software can clear this register + (0x0518 => pub(crate) classc_accum_cnt: ReadWrite), + /// Accumulation threshold value for alert Class C. + (0x051c => pub(crate) classc_accum_thresh_shadowed: ReadWrite), + /// Interrupt timeout in cycles. + (0x0520 => pub(crate) classc_timeout_cyc_shadowed: ReadWrite), + /// Crashdump trigger configuration for Class C. + (0x0524 => pub(crate) classc_crashdump_trigger_shadowed: ReadWrite), + /// Duration of escalation phase 0 for Class C. + (0x0528 => pub(crate) classc_phase0_cyc_shadowed: ReadWrite), + /// Duration of escalation phase 1 for Class C. + (0x052c => pub(crate) classc_phase1_cyc_shadowed: ReadWrite), + /// Duration of escalation phase 2 for Class C. + (0x0530 => pub(crate) classc_phase2_cyc_shadowed: ReadWrite), + /// Duration of escalation phase 3 for Class C. + (0x0534 => pub(crate) classc_phase3_cyc_shadowed: ReadWrite), + /// Escalation counter in cycles for Class C. + (0x0538 => pub(crate) classc_esc_cnt: ReadWrite), + /// Current escalation state of Class C. See also !!CLASSC_ESC_CNT. + (0x053c => pub(crate) classc_state: ReadWrite), + /// Lock bit for Class D configuration. + (0x0540 => pub(crate) classd_regwen: ReadWrite), + /// Escalation control register for alert Class D. Can not be modified if !!CLASSD_REGWEN is + /// false. + (0x0544 => pub(crate) classd_ctrl_shadowed: ReadWrite), + /// Clear enable for escalation protocol of Class D alerts. + (0x0548 => pub(crate) classd_clr_regwen: ReadWrite), + /// Clear for escalation protocol of Class D. + (0x054c => pub(crate) classd_clr_shadowed: ReadWrite), + /// Current accumulation value for alert Class D. Software can clear this register + (0x0550 => pub(crate) classd_accum_cnt: ReadWrite), + /// Accumulation threshold value for alert Class D. + (0x0554 => pub(crate) classd_accum_thresh_shadowed: ReadWrite), + /// Interrupt timeout in cycles. + (0x0558 => pub(crate) classd_timeout_cyc_shadowed: ReadWrite), + /// Crashdump trigger configuration for Class D. + (0x055c => pub(crate) classd_crashdump_trigger_shadowed: ReadWrite), + /// Duration of escalation phase 0 for Class D. + (0x0560 => pub(crate) classd_phase0_cyc_shadowed: ReadWrite), + /// Duration of escalation phase 1 for Class D. + (0x0564 => pub(crate) classd_phase1_cyc_shadowed: ReadWrite), + /// Duration of escalation phase 2 for Class D. + (0x0568 => pub(crate) classd_phase2_cyc_shadowed: ReadWrite), + /// Duration of escalation phase 3 for Class D. + (0x056c => pub(crate) classd_phase3_cyc_shadowed: ReadWrite), + /// Escalation counter in cycles for Class D. + (0x0570 => pub(crate) classd_esc_cnt: ReadWrite), + /// Current escalation state of Class D. See also !!CLASSD_ESC_CNT. + (0x0574 => pub(crate) classd_state: ReadWrite), + (0x0578 => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + CLASSA OFFSET(0) NUMBITS(1) [], + CLASSB OFFSET(1) NUMBITS(1) [], + CLASSC OFFSET(2) NUMBITS(1) [], + CLASSD OFFSET(3) NUMBITS(1) [], + ], + pub(crate) PING_TIMER_REGWEN [ + PING_TIMER_REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) PING_TIMEOUT_CYC_SHADOWED [ + PING_TIMEOUT_CYC_SHADOWED OFFSET(0) NUMBITS(16) [], + ], + pub(crate) PING_TIMER_EN_SHADOWED [ + PING_TIMER_EN_SHADOWED OFFSET(0) NUMBITS(1) [], + ], + pub(crate) ALERT_REGWEN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) ALERT_EN_SHADOWED [ + EN_A_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) ALERT_CLASS_SHADOWED [ + CLASS_A_0 OFFSET(0) NUMBITS(2) [ + CLASSA = 0, + CLASSB = 1, + CLASSC = 2, + CLASSD = 3, + ], + ], + pub(crate) ALERT_CAUSE [ + A_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) LOC_ALERT_REGWEN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) LOC_ALERT_EN_SHADOWED [ + EN_LA_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) LOC_ALERT_CLASS_SHADOWED [ + CLASS_LA_0 OFFSET(0) NUMBITS(2) [ + CLASSA = 0, + CLASSB = 1, + CLASSC = 2, + CLASSD = 3, + ], + ], + pub(crate) LOC_ALERT_CAUSE [ + LA_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CLASSA_REGWEN [ + CLASSA_REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CLASSA_CTRL_SHADOWED [ + EN OFFSET(0) NUMBITS(1) [], + LOCK OFFSET(1) NUMBITS(1) [], + EN_E0 OFFSET(2) NUMBITS(1) [], + EN_E1 OFFSET(3) NUMBITS(1) [], + EN_E2 OFFSET(4) NUMBITS(1) [], + EN_E3 OFFSET(5) NUMBITS(1) [], + MAP_E0 OFFSET(6) NUMBITS(2) [], + MAP_E1 OFFSET(8) NUMBITS(2) [], + MAP_E2 OFFSET(10) NUMBITS(2) [], + MAP_E3 OFFSET(12) NUMBITS(2) [], + ], + pub(crate) CLASSA_CLR_REGWEN [ + CLASSA_CLR_REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CLASSA_CLR_SHADOWED [ + CLASSA_CLR_SHADOWED OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CLASSA_ACCUM_CNT [ + CLASSA_ACCUM_CNT OFFSET(0) NUMBITS(16) [], + ], + pub(crate) CLASSA_ACCUM_THRESH_SHADOWED [ + CLASSA_ACCUM_THRESH_SHADOWED OFFSET(0) NUMBITS(16) [], + ], + pub(crate) CLASSA_TIMEOUT_CYC_SHADOWED [ + CLASSA_TIMEOUT_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSA_CRASHDUMP_TRIGGER_SHADOWED [ + CLASSA_CRASHDUMP_TRIGGER_SHADOWED OFFSET(0) NUMBITS(2) [], + ], + pub(crate) CLASSA_PHASE0_CYC_SHADOWED [ + CLASSA_PHASE0_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSA_PHASE1_CYC_SHADOWED [ + CLASSA_PHASE1_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSA_PHASE2_CYC_SHADOWED [ + CLASSA_PHASE2_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSA_PHASE3_CYC_SHADOWED [ + CLASSA_PHASE3_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSA_ESC_CNT [ + CLASSA_ESC_CNT OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSA_STATE [ + CLASSA_STATE OFFSET(0) NUMBITS(3) [ + IDLE = 0, + TIMEOUT = 1, + FSMERROR = 2, + TERMINAL = 3, + PHASE0 = 4, + PHASE1 = 5, + PHASE2 = 6, + PHASE3 = 7, + ], + ], + pub(crate) CLASSB_REGWEN [ + CLASSB_REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CLASSB_CTRL_SHADOWED [ + EN OFFSET(0) NUMBITS(1) [], + LOCK OFFSET(1) NUMBITS(1) [], + EN_E0 OFFSET(2) NUMBITS(1) [], + EN_E1 OFFSET(3) NUMBITS(1) [], + EN_E2 OFFSET(4) NUMBITS(1) [], + EN_E3 OFFSET(5) NUMBITS(1) [], + MAP_E0 OFFSET(6) NUMBITS(2) [], + MAP_E1 OFFSET(8) NUMBITS(2) [], + MAP_E2 OFFSET(10) NUMBITS(2) [], + MAP_E3 OFFSET(12) NUMBITS(2) [], + ], + pub(crate) CLASSB_CLR_REGWEN [ + CLASSB_CLR_REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CLASSB_CLR_SHADOWED [ + CLASSB_CLR_SHADOWED OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CLASSB_ACCUM_CNT [ + CLASSB_ACCUM_CNT OFFSET(0) NUMBITS(16) [], + ], + pub(crate) CLASSB_ACCUM_THRESH_SHADOWED [ + CLASSB_ACCUM_THRESH_SHADOWED OFFSET(0) NUMBITS(16) [], + ], + pub(crate) CLASSB_TIMEOUT_CYC_SHADOWED [ + CLASSB_TIMEOUT_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSB_CRASHDUMP_TRIGGER_SHADOWED [ + CLASSB_CRASHDUMP_TRIGGER_SHADOWED OFFSET(0) NUMBITS(2) [], + ], + pub(crate) CLASSB_PHASE0_CYC_SHADOWED [ + CLASSB_PHASE0_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSB_PHASE1_CYC_SHADOWED [ + CLASSB_PHASE1_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSB_PHASE2_CYC_SHADOWED [ + CLASSB_PHASE2_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSB_PHASE3_CYC_SHADOWED [ + CLASSB_PHASE3_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSB_ESC_CNT [ + CLASSB_ESC_CNT OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSB_STATE [ + CLASSB_STATE OFFSET(0) NUMBITS(3) [ + IDLE = 0, + TIMEOUT = 1, + FSMERROR = 2, + TERMINAL = 3, + PHASE0 = 4, + PHASE1 = 5, + PHASE2 = 6, + PHASE3 = 7, + ], + ], + pub(crate) CLASSC_REGWEN [ + CLASSC_REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CLASSC_CTRL_SHADOWED [ + EN OFFSET(0) NUMBITS(1) [], + LOCK OFFSET(1) NUMBITS(1) [], + EN_E0 OFFSET(2) NUMBITS(1) [], + EN_E1 OFFSET(3) NUMBITS(1) [], + EN_E2 OFFSET(4) NUMBITS(1) [], + EN_E3 OFFSET(5) NUMBITS(1) [], + MAP_E0 OFFSET(6) NUMBITS(2) [], + MAP_E1 OFFSET(8) NUMBITS(2) [], + MAP_E2 OFFSET(10) NUMBITS(2) [], + MAP_E3 OFFSET(12) NUMBITS(2) [], + ], + pub(crate) CLASSC_CLR_REGWEN [ + CLASSC_CLR_REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CLASSC_CLR_SHADOWED [ + CLASSC_CLR_SHADOWED OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CLASSC_ACCUM_CNT [ + CLASSC_ACCUM_CNT OFFSET(0) NUMBITS(16) [], + ], + pub(crate) CLASSC_ACCUM_THRESH_SHADOWED [ + CLASSC_ACCUM_THRESH_SHADOWED OFFSET(0) NUMBITS(16) [], + ], + pub(crate) CLASSC_TIMEOUT_CYC_SHADOWED [ + CLASSC_TIMEOUT_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSC_CRASHDUMP_TRIGGER_SHADOWED [ + CLASSC_CRASHDUMP_TRIGGER_SHADOWED OFFSET(0) NUMBITS(2) [], + ], + pub(crate) CLASSC_PHASE0_CYC_SHADOWED [ + CLASSC_PHASE0_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSC_PHASE1_CYC_SHADOWED [ + CLASSC_PHASE1_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSC_PHASE2_CYC_SHADOWED [ + CLASSC_PHASE2_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSC_PHASE3_CYC_SHADOWED [ + CLASSC_PHASE3_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSC_ESC_CNT [ + CLASSC_ESC_CNT OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSC_STATE [ + CLASSC_STATE OFFSET(0) NUMBITS(3) [ + IDLE = 0, + TIMEOUT = 1, + FSMERROR = 2, + TERMINAL = 3, + PHASE0 = 4, + PHASE1 = 5, + PHASE2 = 6, + PHASE3 = 7, + ], + ], + pub(crate) CLASSD_REGWEN [ + CLASSD_REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CLASSD_CTRL_SHADOWED [ + EN OFFSET(0) NUMBITS(1) [], + LOCK OFFSET(1) NUMBITS(1) [], + EN_E0 OFFSET(2) NUMBITS(1) [], + EN_E1 OFFSET(3) NUMBITS(1) [], + EN_E2 OFFSET(4) NUMBITS(1) [], + EN_E3 OFFSET(5) NUMBITS(1) [], + MAP_E0 OFFSET(6) NUMBITS(2) [], + MAP_E1 OFFSET(8) NUMBITS(2) [], + MAP_E2 OFFSET(10) NUMBITS(2) [], + MAP_E3 OFFSET(12) NUMBITS(2) [], + ], + pub(crate) CLASSD_CLR_REGWEN [ + CLASSD_CLR_REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CLASSD_CLR_SHADOWED [ + CLASSD_CLR_SHADOWED OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CLASSD_ACCUM_CNT [ + CLASSD_ACCUM_CNT OFFSET(0) NUMBITS(16) [], + ], + pub(crate) CLASSD_ACCUM_THRESH_SHADOWED [ + CLASSD_ACCUM_THRESH_SHADOWED OFFSET(0) NUMBITS(16) [], + ], + pub(crate) CLASSD_TIMEOUT_CYC_SHADOWED [ + CLASSD_TIMEOUT_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSD_CRASHDUMP_TRIGGER_SHADOWED [ + CLASSD_CRASHDUMP_TRIGGER_SHADOWED OFFSET(0) NUMBITS(2) [], + ], + pub(crate) CLASSD_PHASE0_CYC_SHADOWED [ + CLASSD_PHASE0_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSD_PHASE1_CYC_SHADOWED [ + CLASSD_PHASE1_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSD_PHASE2_CYC_SHADOWED [ + CLASSD_PHASE2_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSD_PHASE3_CYC_SHADOWED [ + CLASSD_PHASE3_CYC_SHADOWED OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSD_ESC_CNT [ + CLASSD_ESC_CNT OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CLASSD_STATE [ + CLASSD_STATE OFFSET(0) NUMBITS(3) [ + IDLE = 0, + TIMEOUT = 1, + FSMERROR = 2, + TERMINAL = 3, + PHASE0 = 4, + PHASE1 = 5, + PHASE2 = 6, + PHASE3 = 7, + ], + ], +]; + +// End generated register constants for alert_handler diff --git a/chips/earlgrey/src/registers/ast_regs.rs b/chips/earlgrey/src/registers/ast_regs.rs new file mode 100644 index 0000000000..493551a517 --- /dev/null +++ b/chips/earlgrey/src/registers/ast_regs.rs @@ -0,0 +1,231 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for ast. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/top_earlgrey/ip/ast/data/ast.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of registers in the Array-B +pub const AST_PARAM_NUM_REGS_B: u32 = 5; +/// Number of USB valid beacon pulses for clock to re-calibrate +pub const AST_PARAM_NUM_USB_BEACON_PULSES: u32 = 8; +/// Register width +pub const AST_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub AstRegisters { + /// AST Register 0 for OTP/ROM Write Testing + (0x0000 => pub(crate) rega0: ReadWrite), + /// AST 1 Register for OTP/ROM Write Testing + (0x0004 => pub(crate) rega1: ReadWrite), + /// AST 2 Register for OTP/ROM Write Testing + (0x0008 => pub(crate) rega2: ReadWrite), + /// AST 3 Register for OTP/ROM Write Testing + (0x000c => pub(crate) rega3: ReadWrite), + /// AST 4 Register for OTP/ROM Write Testing + (0x0010 => pub(crate) rega4: ReadWrite), + /// AST 5 Register for OTP/ROM Write Testing + (0x0014 => pub(crate) rega5: ReadWrite), + /// AST 6 Register for OTP/ROM Write Testing + (0x0018 => pub(crate) rega6: ReadWrite), + /// AST 7 Register for OTP/ROM Write Testing + (0x001c => pub(crate) rega7: ReadWrite), + /// AST 8 Register for OTP/ROM Write Testing + (0x0020 => pub(crate) rega8: ReadWrite), + /// AST 9 Register for OTP/ROM Write Testing + (0x0024 => pub(crate) rega9: ReadWrite), + /// AST 10 Register for OTP/ROM Write Testing + (0x0028 => pub(crate) rega10: ReadWrite), + /// AST 11 Register for OTP/ROM Write Testing + (0x002c => pub(crate) rega11: ReadWrite), + /// AST 13 Register for OTP/ROM Write Testing + (0x0030 => pub(crate) rega12: ReadWrite), + /// AST 13 Register for OTP/ROM Write Testing + (0x0034 => pub(crate) rega13: ReadWrite), + /// AST 14 Register for OTP/ROM Write Testing + (0x0038 => pub(crate) rega14: ReadWrite), + /// AST 15 Register for OTP/ROM Write Testing + (0x003c => pub(crate) rega15: ReadWrite), + /// AST 16 Register for OTP/ROM Write Testing + (0x0040 => pub(crate) rega16: ReadWrite), + /// AST 17 Register for OTP/ROM Write Testing + (0x0044 => pub(crate) rega17: ReadWrite), + /// AST 18 Register for OTP/ROM Write Testing + (0x0048 => pub(crate) rega18: ReadWrite), + /// AST 19 Register for OTP/ROM Write Testing + (0x004c => pub(crate) rega19: ReadWrite), + /// AST 20 Register for OTP/ROM Write Testing + (0x0050 => pub(crate) rega20: ReadWrite), + /// AST 21 Register for OTP/ROM Write Testing + (0x0054 => pub(crate) rega21: ReadWrite), + /// AST 22 Register for OTP/ROM Write Testing + (0x0058 => pub(crate) rega22: ReadWrite), + /// AST 23 Register for OTP/ROM Write Testing + (0x005c => pub(crate) rega23: ReadWrite), + /// AST 24 Register for OTP/ROM Write Testing + (0x0060 => pub(crate) rega24: ReadWrite), + /// AST 25 Register for OTP/ROM Write Testing + (0x0064 => pub(crate) rega25: ReadWrite), + /// AST 26 Register for OTP/ROM Write Testing + (0x0068 => pub(crate) rega26: ReadWrite), + /// AST 27 Register for OTP/ROM Write Testing + (0x006c => pub(crate) rega27: ReadWrite), + /// AST 28 Register for OTP/ROM Write Testing + (0x0070 => pub(crate) rega28: ReadWrite), + /// AST 29 Register for OTP/ROM Write Testing + (0x0074 => pub(crate) rega29: ReadWrite), + /// AST 30 Register for OTP/ROM Write Testing + (0x0078 => pub(crate) rega30: ReadWrite), + /// AST 31 Register for OTP/ROM Write Testing + (0x007c => pub(crate) rega31: ReadWrite), + /// AST 32 Register for OTP/ROM Write Testing + (0x0080 => pub(crate) rega32: ReadWrite), + /// AST 33 Register for OTP/ROM Write Testing + (0x0084 => pub(crate) rega33: ReadWrite), + /// AST 34 Register for OTP/ROM Write Testing + (0x0088 => pub(crate) rega34: ReadWrite), + /// AST 35 Register for OTP/ROM Write Testing + (0x008c => pub(crate) rega35: ReadWrite), + /// AST 36 Register for OTP/ROM Write Testing + (0x0090 => pub(crate) rega36: ReadWrite), + /// AST 37 Register for OTP/ROM Write Testing + (0x0094 => pub(crate) rega37: ReadWrite), + /// AST Last Register for OTP/ROM Write Testing + (0x0098 => pub(crate) regal: ReadWrite), + (0x009c => _reserved1), + /// AST Registers Array-B to set address space size + (0x0200 => pub(crate) regb: [ReadWrite; 5]), + (0x0214 => @END), + } +} + +register_bitfields![u32, + pub(crate) REGA0 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA1 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA2 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA3 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA4 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA5 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA6 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA7 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA8 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA9 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA10 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA11 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA12 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA13 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA14 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA15 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA16 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA17 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA18 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA19 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA20 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA21 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA22 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA23 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA24 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA25 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA26 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA27 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA28 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA29 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA30 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA31 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA32 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA33 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA34 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA35 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA36 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGA37 [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGAL [ + REG32 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REGB [ + REG32_0 OFFSET(0) NUMBITS(32) [], + ], +]; + +// End generated register constants for ast diff --git a/chips/earlgrey/src/registers/clkmgr_regs.rs b/chips/earlgrey/src/registers/clkmgr_regs.rs new file mode 100644 index 0000000000..91b787f10e --- /dev/null +++ b/chips/earlgrey/src/registers/clkmgr_regs.rs @@ -0,0 +1,172 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for clkmgr. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/top_earlgrey/ip/clkmgr/data/autogen/clkmgr.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of clock groups +pub const CLKMGR_PARAM_NUM_GROUPS: u32 = 7; +/// Number of SW gateable clocks +pub const CLKMGR_PARAM_NUM_SW_GATEABLE_CLOCKS: u32 = 4; +/// Number of hintable clocks +pub const CLKMGR_PARAM_NUM_HINTABLE_CLOCKS: u32 = 4; +/// Number of alerts +pub const CLKMGR_PARAM_NUM_ALERTS: u32 = 2; +/// Register width +pub const CLKMGR_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub ClkmgrRegisters { + /// Alert Test Register + (0x0000 => pub(crate) alert_test: ReadWrite), + /// External clock control write enable + (0x0004 => pub(crate) extclk_ctrl_regwen: ReadWrite), + /// Select external clock + (0x0008 => pub(crate) extclk_ctrl: ReadWrite), + /// Status of requested external clock switch + (0x000c => pub(crate) extclk_status: ReadWrite), + /// Jitter write enable + (0x0010 => pub(crate) jitter_regwen: ReadWrite), + /// Enable jittery clock + (0x0014 => pub(crate) jitter_enable: ReadWrite), + /// Clock enable for software gateable clocks. + (0x0018 => pub(crate) clk_enables: ReadWrite), + /// Clock hint for software gateable transactional clocks during active mode. + (0x001c => pub(crate) clk_hints: ReadWrite), + /// Since the final state of !!CLK_HINTS is not always determined by software, + (0x0020 => pub(crate) clk_hints_status: ReadWrite), + /// Measurement control write enable + (0x0024 => pub(crate) measure_ctrl_regwen: ReadWrite), + /// Enable for measurement control + (0x0028 => pub(crate) io_meas_ctrl_en: ReadWrite), + /// Configuration controls for io measurement. + (0x002c => pub(crate) io_meas_ctrl_shadowed: ReadWrite), + /// Enable for measurement control + (0x0030 => pub(crate) io_div2_meas_ctrl_en: ReadWrite), + /// Configuration controls for io_div2 measurement. + (0x0034 => pub(crate) io_div2_meas_ctrl_shadowed: ReadWrite), + /// Enable for measurement control + (0x0038 => pub(crate) io_div4_meas_ctrl_en: ReadWrite), + /// Configuration controls for io_div4 measurement. + (0x003c => pub(crate) io_div4_meas_ctrl_shadowed: ReadWrite), + /// Enable for measurement control + (0x0040 => pub(crate) main_meas_ctrl_en: ReadWrite), + /// Configuration controls for main measurement. + (0x0044 => pub(crate) main_meas_ctrl_shadowed: ReadWrite), + /// Enable for measurement control + (0x0048 => pub(crate) usb_meas_ctrl_en: ReadWrite), + /// Configuration controls for usb measurement. + (0x004c => pub(crate) usb_meas_ctrl_shadowed: ReadWrite), + /// Recoverable Error code + (0x0050 => pub(crate) recov_err_code: ReadWrite), + /// Error code + (0x0054 => pub(crate) fatal_err_code: ReadWrite), + (0x0058 => @END), + } +} + +register_bitfields![u32, + pub(crate) ALERT_TEST [ + RECOV_FAULT OFFSET(0) NUMBITS(1) [], + FATAL_FAULT OFFSET(1) NUMBITS(1) [], + ], + pub(crate) EXTCLK_CTRL_REGWEN [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) EXTCLK_CTRL [ + SEL OFFSET(0) NUMBITS(4) [], + HI_SPEED_SEL OFFSET(4) NUMBITS(4) [], + ], + pub(crate) EXTCLK_STATUS [ + ACK OFFSET(0) NUMBITS(4) [], + ], + pub(crate) JITTER_REGWEN [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) JITTER_ENABLE [ + VAL OFFSET(0) NUMBITS(4) [], + ], + pub(crate) CLK_ENABLES [ + CLK_IO_DIV4_PERI_EN OFFSET(0) NUMBITS(1) [], + CLK_IO_DIV2_PERI_EN OFFSET(1) NUMBITS(1) [], + CLK_IO_PERI_EN OFFSET(2) NUMBITS(1) [], + CLK_USB_PERI_EN OFFSET(3) NUMBITS(1) [], + ], + pub(crate) CLK_HINTS [ + CLK_MAIN_AES_HINT OFFSET(0) NUMBITS(1) [], + CLK_MAIN_HMAC_HINT OFFSET(1) NUMBITS(1) [], + CLK_MAIN_KMAC_HINT OFFSET(2) NUMBITS(1) [], + CLK_MAIN_OTBN_HINT OFFSET(3) NUMBITS(1) [], + ], + pub(crate) CLK_HINTS_STATUS [ + CLK_MAIN_AES_VAL OFFSET(0) NUMBITS(1) [], + CLK_MAIN_HMAC_VAL OFFSET(1) NUMBITS(1) [], + CLK_MAIN_KMAC_VAL OFFSET(2) NUMBITS(1) [], + CLK_MAIN_OTBN_VAL OFFSET(3) NUMBITS(1) [], + ], + pub(crate) MEASURE_CTRL_REGWEN [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) IO_MEAS_CTRL_EN [ + EN OFFSET(0) NUMBITS(4) [], + ], + pub(crate) IO_MEAS_CTRL_SHADOWED [ + HI OFFSET(0) NUMBITS(10) [], + LO OFFSET(10) NUMBITS(10) [], + ], + pub(crate) IO_DIV2_MEAS_CTRL_EN [ + EN OFFSET(0) NUMBITS(4) [], + ], + pub(crate) IO_DIV2_MEAS_CTRL_SHADOWED [ + HI OFFSET(0) NUMBITS(9) [], + LO OFFSET(9) NUMBITS(9) [], + ], + pub(crate) IO_DIV4_MEAS_CTRL_EN [ + EN OFFSET(0) NUMBITS(4) [], + ], + pub(crate) IO_DIV4_MEAS_CTRL_SHADOWED [ + HI OFFSET(0) NUMBITS(8) [], + LO OFFSET(8) NUMBITS(8) [], + ], + pub(crate) MAIN_MEAS_CTRL_EN [ + EN OFFSET(0) NUMBITS(4) [], + ], + pub(crate) MAIN_MEAS_CTRL_SHADOWED [ + HI OFFSET(0) NUMBITS(10) [], + LO OFFSET(10) NUMBITS(10) [], + ], + pub(crate) USB_MEAS_CTRL_EN [ + EN OFFSET(0) NUMBITS(4) [], + ], + pub(crate) USB_MEAS_CTRL_SHADOWED [ + HI OFFSET(0) NUMBITS(9) [], + LO OFFSET(9) NUMBITS(9) [], + ], + pub(crate) RECOV_ERR_CODE [ + SHADOW_UPDATE_ERR OFFSET(0) NUMBITS(1) [], + IO_MEASURE_ERR OFFSET(1) NUMBITS(1) [], + IO_DIV2_MEASURE_ERR OFFSET(2) NUMBITS(1) [], + IO_DIV4_MEASURE_ERR OFFSET(3) NUMBITS(1) [], + MAIN_MEASURE_ERR OFFSET(4) NUMBITS(1) [], + USB_MEASURE_ERR OFFSET(5) NUMBITS(1) [], + IO_TIMEOUT_ERR OFFSET(6) NUMBITS(1) [], + IO_DIV2_TIMEOUT_ERR OFFSET(7) NUMBITS(1) [], + IO_DIV4_TIMEOUT_ERR OFFSET(8) NUMBITS(1) [], + MAIN_TIMEOUT_ERR OFFSET(9) NUMBITS(1) [], + USB_TIMEOUT_ERR OFFSET(10) NUMBITS(1) [], + ], + pub(crate) FATAL_ERR_CODE [ + REG_INTG OFFSET(0) NUMBITS(1) [], + IDLE_CNT OFFSET(1) NUMBITS(1) [], + SHADOW_STORAGE_ERR OFFSET(2) NUMBITS(1) [], + ], +]; + +// End generated register constants for clkmgr diff --git a/chips/earlgrey/src/registers/flash_ctrl_regs.rs b/chips/earlgrey/src/registers/flash_ctrl_regs.rs new file mode 100644 index 0000000000..897895ad81 --- /dev/null +++ b/chips/earlgrey/src/registers/flash_ctrl_regs.rs @@ -0,0 +1,429 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for flash_ctrl. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/top_earlgrey/ip/flash_ctrl/data/autogen/flash_ctrl.hjson +use kernel::utilities::registers::ReadOnly; +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::WriteOnly; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of flash banks +pub const FLASH_CTRL_PARAM_REG_NUM_BANKS: u32 = 2; +/// Number of pages per bank +pub const FLASH_CTRL_PARAM_REG_PAGES_PER_BANK: u32 = 256; +/// Program resolution window in bytes +pub const FLASH_CTRL_PARAM_REG_BUS_PGM_RES_BYTES: u32 = 64; +/// Number of bits needed to represent the pages within a bank +pub const FLASH_CTRL_PARAM_REG_PAGE_WIDTH: u32 = 8; +/// Number of bits needed to represent the number of banks +pub const FLASH_CTRL_PARAM_REG_BANK_WIDTH: u32 = 1; +/// Number of configurable flash regions +pub const FLASH_CTRL_PARAM_NUM_REGIONS: u32 = 8; +/// Number of info partition types +pub const FLASH_CTRL_PARAM_NUM_INFO_TYPES: u32 = 3; +/// Number of configurable flash info pages for info type 0 +pub const FLASH_CTRL_PARAM_NUM_INFOS0: u32 = 10; +/// Number of configurable flash info pages for info type 1 +pub const FLASH_CTRL_PARAM_NUM_INFOS1: u32 = 1; +/// Number of configurable flash info pages for info type 2 +pub const FLASH_CTRL_PARAM_NUM_INFOS2: u32 = 2; +/// Number of words per page +pub const FLASH_CTRL_PARAM_WORDS_PER_PAGE: u32 = 256; +/// Number of bytes per word +pub const FLASH_CTRL_PARAM_BYTES_PER_WORD: u32 = 8; +/// Number of bytes per page +pub const FLASH_CTRL_PARAM_BYTES_PER_PAGE: u32 = 2048; +/// Number of bytes per bank +pub const FLASH_CTRL_PARAM_BYTES_PER_BANK: u32 = 524288; +/// Maximum depth for read / program fifos +pub const FLASH_CTRL_PARAM_MAX_FIFO_DEPTH: u32 = 16; +/// Maximum depth for read / program fifos +pub const FLASH_CTRL_PARAM_MAX_FIFO_WIDTH: u32 = 5; +/// Number of alerts +pub const FLASH_CTRL_PARAM_NUM_ALERTS: u32 = 5; +/// Register width +pub const FLASH_CTRL_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub FlashCtrlRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// Disable flash functionality + (0x0010 => pub(crate) dis: ReadWrite), + /// Controls whether flash can be used for code execution fetches + (0x0014 => pub(crate) exec: ReadWrite), + /// Controller init register + (0x0018 => pub(crate) init: ReadWrite), + /// Controls the configurability of the !!CONTROL register. + (0x001c => pub(crate) ctrl_regwen: ReadWrite), + /// Control register + (0x0020 => pub(crate) control: ReadWrite), + /// Address for flash operation + (0x0024 => pub(crate) addr: ReadWrite), + /// Enable different program types + (0x0028 => pub(crate) prog_type_en: ReadWrite), + /// Suspend erase + (0x002c => pub(crate) erase_suspend: ReadWrite), + /// Memory region registers configuration enable. + (0x0030 => pub(crate) region_cfg_regwen: [ReadWrite; 8]), + /// Memory property configuration for data partition + (0x0050 => pub(crate) mp_region_cfg: [ReadWrite; 8]), + /// Memory base and size configuration for data partition + (0x0070 => pub(crate) mp_region: [ReadWrite; 8]), + /// Default region properties + (0x0090 => pub(crate) default_region: ReadWrite), + /// Memory region registers configuration enable. + (0x0094 => pub(crate) bank0_info0_regwen: [ReadWrite; 10]), + /// Memory property configuration for info partition in bank0, + (0x00bc => pub(crate) bank0_info0_page_cfg: [ReadWrite; 10]), + /// Memory region registers configuration enable. + (0x00e4 => pub(crate) bank0_info1_regwen: [ReadWrite; 1]), + /// Memory property configuration for info partition in bank0, + (0x00e8 => pub(crate) bank0_info1_page_cfg: [ReadWrite; 1]), + /// Memory region registers configuration enable. + (0x00ec => pub(crate) bank0_info2_regwen: [ReadWrite; 2]), + /// Memory property configuration for info partition in bank0, + (0x00f4 => pub(crate) bank0_info2_page_cfg: [ReadWrite; 2]), + /// Memory region registers configuration enable. + (0x00fc => pub(crate) bank1_info0_regwen: [ReadWrite; 10]), + /// Memory property configuration for info partition in bank1, + (0x0124 => pub(crate) bank1_info0_page_cfg: [ReadWrite; 10]), + /// Memory region registers configuration enable. + (0x014c => pub(crate) bank1_info1_regwen: [ReadWrite; 1]), + /// Memory property configuration for info partition in bank1, + (0x0150 => pub(crate) bank1_info1_page_cfg: [ReadWrite; 1]), + /// Memory region registers configuration enable. + (0x0154 => pub(crate) bank1_info2_regwen: [ReadWrite; 2]), + /// Memory property configuration for info partition in bank1, + (0x015c => pub(crate) bank1_info2_page_cfg: [ReadWrite; 2]), + /// HW interface info configuration rule overrides + (0x0164 => pub(crate) hw_info_cfg_override: ReadWrite), + /// Bank configuration registers configuration enable. + (0x0168 => pub(crate) bank_cfg_regwen: ReadWrite), + /// Memory properties bank configuration + (0x016c => pub(crate) mp_bank_cfg_shadowed: [ReadWrite; 1]), + /// Flash Operation Status + (0x0170 => pub(crate) op_status: ReadWrite), + /// Flash Controller Status + (0x0174 => pub(crate) status: ReadWrite), + /// Current flash fsm state + (0x0178 => pub(crate) debug_state: ReadWrite), + /// Flash error code register. + (0x017c => pub(crate) err_code: ReadWrite), + /// This register tabulates standard fault status of the flash. + (0x0180 => pub(crate) std_fault_status: ReadWrite), + /// This register tabulates customized fault status of the flash. + (0x0184 => pub(crate) fault_status: ReadWrite), + /// Synchronous error address + (0x0188 => pub(crate) err_addr: ReadWrite), + /// Total number of single bit ECC error count + (0x018c => pub(crate) ecc_single_err_cnt: [ReadWrite; 1]), + /// Latest address of ECC single err + (0x0190 => pub(crate) ecc_single_err_addr: [ReadWrite; 2]), + /// Phy alert configuration + (0x0198 => pub(crate) phy_alert_cfg: ReadWrite), + /// Flash Phy Status + (0x019c => pub(crate) phy_status: ReadWrite), + /// Flash Controller Scratch + (0x01a0 => pub(crate) scratch: ReadWrite), + /// Programmable depth where FIFOs should generate interrupts + (0x01a4 => pub(crate) fifo_lvl: ReadWrite), + /// Reset for flash controller FIFOs + (0x01a8 => pub(crate) fifo_rst: ReadWrite), + /// Current program and read fifo depth + (0x01ac => pub(crate) curr_fifo_lvl: ReadWrite), + /// Memory area: Flash program FIFO. + (0x01b0 => pub(crate) prog_fifo: [WriteOnly; 1]), + /// Memory area: Flash read FIFO. + (0x01b4 => pub(crate) rd_fifo: [ReadOnly; 1]), + (0x01b8 => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + PROG_EMPTY OFFSET(0) NUMBITS(1) [], + PROG_LVL OFFSET(1) NUMBITS(1) [], + RD_FULL OFFSET(2) NUMBITS(1) [], + RD_LVL OFFSET(3) NUMBITS(1) [], + OP_DONE OFFSET(4) NUMBITS(1) [], + CORR_ERR OFFSET(5) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + RECOV_ERR OFFSET(0) NUMBITS(1) [], + FATAL_STD_ERR OFFSET(1) NUMBITS(1) [], + FATAL_ERR OFFSET(2) NUMBITS(1) [], + FATAL_PRIM_FLASH_ALERT OFFSET(3) NUMBITS(1) [], + RECOV_PRIM_FLASH_ALERT OFFSET(4) NUMBITS(1) [], + ], + pub(crate) DIS [ + VAL OFFSET(0) NUMBITS(4) [], + ], + pub(crate) EXEC [ + EN OFFSET(0) NUMBITS(32) [], + ], + pub(crate) INIT [ + VAL OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CTRL_REGWEN [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CONTROL [ + START OFFSET(0) NUMBITS(1) [], + OP OFFSET(4) NUMBITS(2) [ + READ = 0, + PROG = 1, + ERASE = 2, + ], + PROG_SEL OFFSET(6) NUMBITS(1) [ + NORMAL_PROGRAM = 0, + PROGRAM_REPAIR = 1, + ], + ERASE_SEL OFFSET(7) NUMBITS(1) [ + PAGE_ERASE = 0, + BANK_ERASE = 1, + ], + PARTITION_SEL OFFSET(8) NUMBITS(1) [], + INFO_SEL OFFSET(9) NUMBITS(2) [], + NUM OFFSET(16) NUMBITS(12) [], + ], + pub(crate) ADDR [ + START OFFSET(0) NUMBITS(20) [], + ], + pub(crate) PROG_TYPE_EN [ + NORMAL OFFSET(0) NUMBITS(1) [], + REPAIR OFFSET(1) NUMBITS(1) [], + ], + pub(crate) ERASE_SUSPEND [ + REQ OFFSET(0) NUMBITS(1) [], + ], + pub(crate) REGION_CFG_REGWEN [ + REGION_0 OFFSET(0) NUMBITS(1) [ + REGION_LOCKED = 0, + REGION_ENABLED = 1, + ], + ], + pub(crate) MP_REGION_CFG [ + EN_0 OFFSET(0) NUMBITS(4) [], + RD_EN_0 OFFSET(4) NUMBITS(4) [], + PROG_EN_0 OFFSET(8) NUMBITS(4) [], + ERASE_EN_0 OFFSET(12) NUMBITS(4) [], + SCRAMBLE_EN_0 OFFSET(16) NUMBITS(4) [], + ECC_EN_0 OFFSET(20) NUMBITS(4) [], + HE_EN_0 OFFSET(24) NUMBITS(4) [], + ], + pub(crate) MP_REGION [ + BASE_0 OFFSET(0) NUMBITS(9) [], + SIZE_0 OFFSET(9) NUMBITS(10) [], + ], + pub(crate) DEFAULT_REGION [ + RD_EN OFFSET(0) NUMBITS(4) [], + PROG_EN OFFSET(4) NUMBITS(4) [], + ERASE_EN OFFSET(8) NUMBITS(4) [], + SCRAMBLE_EN OFFSET(12) NUMBITS(4) [], + ECC_EN OFFSET(16) NUMBITS(4) [], + HE_EN OFFSET(20) NUMBITS(4) [], + ], + pub(crate) BANK0_INFO0_REGWEN [ + REGION_0 OFFSET(0) NUMBITS(1) [ + PAGE_LOCKED = 0, + PAGE_ENABLED = 1, + ], + ], + pub(crate) BANK0_INFO0_PAGE_CFG [ + EN_0 OFFSET(0) NUMBITS(4) [], + RD_EN_0 OFFSET(4) NUMBITS(4) [], + PROG_EN_0 OFFSET(8) NUMBITS(4) [], + ERASE_EN_0 OFFSET(12) NUMBITS(4) [], + SCRAMBLE_EN_0 OFFSET(16) NUMBITS(4) [], + ECC_EN_0 OFFSET(20) NUMBITS(4) [], + HE_EN_0 OFFSET(24) NUMBITS(4) [], + ], + pub(crate) BANK0_INFO1_REGWEN [ + REGION_0 OFFSET(0) NUMBITS(1) [ + PAGE_LOCKED = 0, + PAGE_ENABLED = 1, + ], + ], + pub(crate) BANK0_INFO1_PAGE_CFG [ + EN_0 OFFSET(0) NUMBITS(4) [], + RD_EN_0 OFFSET(4) NUMBITS(4) [], + PROG_EN_0 OFFSET(8) NUMBITS(4) [], + ERASE_EN_0 OFFSET(12) NUMBITS(4) [], + SCRAMBLE_EN_0 OFFSET(16) NUMBITS(4) [], + ECC_EN_0 OFFSET(20) NUMBITS(4) [], + HE_EN_0 OFFSET(24) NUMBITS(4) [], + ], + pub(crate) BANK0_INFO2_REGWEN [ + REGION_0 OFFSET(0) NUMBITS(1) [ + PAGE_LOCKED = 0, + PAGE_ENABLED = 1, + ], + ], + pub(crate) BANK0_INFO2_PAGE_CFG [ + EN_0 OFFSET(0) NUMBITS(4) [], + RD_EN_0 OFFSET(4) NUMBITS(4) [], + PROG_EN_0 OFFSET(8) NUMBITS(4) [], + ERASE_EN_0 OFFSET(12) NUMBITS(4) [], + SCRAMBLE_EN_0 OFFSET(16) NUMBITS(4) [], + ECC_EN_0 OFFSET(20) NUMBITS(4) [], + HE_EN_0 OFFSET(24) NUMBITS(4) [], + ], + pub(crate) BANK1_INFO0_REGWEN [ + REGION_0 OFFSET(0) NUMBITS(1) [ + PAGE_LOCKED = 0, + PAGE_ENABLED = 1, + ], + ], + pub(crate) BANK1_INFO0_PAGE_CFG [ + EN_0 OFFSET(0) NUMBITS(4) [], + RD_EN_0 OFFSET(4) NUMBITS(4) [], + PROG_EN_0 OFFSET(8) NUMBITS(4) [], + ERASE_EN_0 OFFSET(12) NUMBITS(4) [], + SCRAMBLE_EN_0 OFFSET(16) NUMBITS(4) [], + ECC_EN_0 OFFSET(20) NUMBITS(4) [], + HE_EN_0 OFFSET(24) NUMBITS(4) [], + ], + pub(crate) BANK1_INFO1_REGWEN [ + REGION_0 OFFSET(0) NUMBITS(1) [ + PAGE_LOCKED = 0, + PAGE_ENABLED = 1, + ], + ], + pub(crate) BANK1_INFO1_PAGE_CFG [ + EN_0 OFFSET(0) NUMBITS(4) [], + RD_EN_0 OFFSET(4) NUMBITS(4) [], + PROG_EN_0 OFFSET(8) NUMBITS(4) [], + ERASE_EN_0 OFFSET(12) NUMBITS(4) [], + SCRAMBLE_EN_0 OFFSET(16) NUMBITS(4) [], + ECC_EN_0 OFFSET(20) NUMBITS(4) [], + HE_EN_0 OFFSET(24) NUMBITS(4) [], + ], + pub(crate) BANK1_INFO2_REGWEN [ + REGION_0 OFFSET(0) NUMBITS(1) [ + PAGE_LOCKED = 0, + PAGE_ENABLED = 1, + ], + ], + pub(crate) BANK1_INFO2_PAGE_CFG [ + EN_0 OFFSET(0) NUMBITS(4) [], + RD_EN_0 OFFSET(4) NUMBITS(4) [], + PROG_EN_0 OFFSET(8) NUMBITS(4) [], + ERASE_EN_0 OFFSET(12) NUMBITS(4) [], + SCRAMBLE_EN_0 OFFSET(16) NUMBITS(4) [], + ECC_EN_0 OFFSET(20) NUMBITS(4) [], + HE_EN_0 OFFSET(24) NUMBITS(4) [], + ], + pub(crate) HW_INFO_CFG_OVERRIDE [ + SCRAMBLE_DIS OFFSET(0) NUMBITS(4) [], + ECC_DIS OFFSET(4) NUMBITS(4) [], + ], + pub(crate) BANK_CFG_REGWEN [ + BANK OFFSET(0) NUMBITS(1) [ + BANK_LOCKED = 0, + BANK_ENABLED = 1, + ], + ], + pub(crate) MP_BANK_CFG_SHADOWED [ + ERASE_EN_0 OFFSET(0) NUMBITS(1) [], + ERASE_EN_1 OFFSET(1) NUMBITS(1) [], + ], + pub(crate) OP_STATUS [ + DONE OFFSET(0) NUMBITS(1) [], + ERR OFFSET(1) NUMBITS(1) [], + ], + pub(crate) STATUS [ + RD_FULL OFFSET(0) NUMBITS(1) [], + RD_EMPTY OFFSET(1) NUMBITS(1) [], + PROG_FULL OFFSET(2) NUMBITS(1) [], + PROG_EMPTY OFFSET(3) NUMBITS(1) [], + INIT_WIP OFFSET(4) NUMBITS(1) [], + INITIALIZED OFFSET(5) NUMBITS(1) [], + ], + pub(crate) DEBUG_STATE [ + LCMGR_STATE OFFSET(0) NUMBITS(11) [], + ], + pub(crate) ERR_CODE [ + OP_ERR OFFSET(0) NUMBITS(1) [], + MP_ERR OFFSET(1) NUMBITS(1) [], + RD_ERR OFFSET(2) NUMBITS(1) [], + PROG_ERR OFFSET(3) NUMBITS(1) [], + PROG_WIN_ERR OFFSET(4) NUMBITS(1) [], + PROG_TYPE_ERR OFFSET(5) NUMBITS(1) [], + UPDATE_ERR OFFSET(6) NUMBITS(1) [], + MACRO_ERR OFFSET(7) NUMBITS(1) [], + ], + pub(crate) STD_FAULT_STATUS [ + REG_INTG_ERR OFFSET(0) NUMBITS(1) [], + PROG_INTG_ERR OFFSET(1) NUMBITS(1) [], + LCMGR_ERR OFFSET(2) NUMBITS(1) [], + LCMGR_INTG_ERR OFFSET(3) NUMBITS(1) [], + ARB_FSM_ERR OFFSET(4) NUMBITS(1) [], + STORAGE_ERR OFFSET(5) NUMBITS(1) [], + PHY_FSM_ERR OFFSET(6) NUMBITS(1) [], + CTRL_CNT_ERR OFFSET(7) NUMBITS(1) [], + FIFO_ERR OFFSET(8) NUMBITS(1) [], + ], + pub(crate) FAULT_STATUS [ + OP_ERR OFFSET(0) NUMBITS(1) [], + MP_ERR OFFSET(1) NUMBITS(1) [], + RD_ERR OFFSET(2) NUMBITS(1) [], + PROG_ERR OFFSET(3) NUMBITS(1) [], + PROG_WIN_ERR OFFSET(4) NUMBITS(1) [], + PROG_TYPE_ERR OFFSET(5) NUMBITS(1) [], + SEED_ERR OFFSET(6) NUMBITS(1) [], + PHY_RELBL_ERR OFFSET(7) NUMBITS(1) [], + PHY_STORAGE_ERR OFFSET(8) NUMBITS(1) [], + SPURIOUS_ACK OFFSET(9) NUMBITS(1) [], + ARB_ERR OFFSET(10) NUMBITS(1) [], + HOST_GNT_ERR OFFSET(11) NUMBITS(1) [], + ], + pub(crate) ERR_ADDR [ + ERR_ADDR OFFSET(0) NUMBITS(20) [], + ], + pub(crate) ECC_SINGLE_ERR_CNT [ + ECC_SINGLE_ERR_CNT_0 OFFSET(0) NUMBITS(8) [], + ECC_SINGLE_ERR_CNT_1 OFFSET(8) NUMBITS(8) [], + ], + pub(crate) ECC_SINGLE_ERR_ADDR [ + ECC_SINGLE_ERR_ADDR_0 OFFSET(0) NUMBITS(20) [], + ], + pub(crate) PHY_ALERT_CFG [ + ALERT_ACK OFFSET(0) NUMBITS(1) [], + ALERT_TRIG OFFSET(1) NUMBITS(1) [], + ], + pub(crate) PHY_STATUS [ + INIT_WIP OFFSET(0) NUMBITS(1) [], + PROG_NORMAL_AVAIL OFFSET(1) NUMBITS(1) [], + PROG_REPAIR_AVAIL OFFSET(2) NUMBITS(1) [], + ], + pub(crate) SCRATCH [ + DATA OFFSET(0) NUMBITS(32) [], + ], + pub(crate) FIFO_LVL [ + PROG OFFSET(0) NUMBITS(5) [], + RD OFFSET(8) NUMBITS(5) [], + ], + pub(crate) FIFO_RST [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CURR_FIFO_LVL [ + PROG OFFSET(0) NUMBITS(5) [], + RD OFFSET(8) NUMBITS(5) [], + ], +]; + +// End generated register constants for flash_ctrl diff --git a/chips/earlgrey/src/registers/mod.rs b/chips/earlgrey/src/registers/mod.rs new file mode 100644 index 0000000000..c6420d22a4 --- /dev/null +++ b/chips/earlgrey/src/registers/mod.rs @@ -0,0 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +pub mod alert_handler_regs; +pub mod ast_regs; +pub mod clkmgr_regs; +pub mod flash_ctrl_regs; +pub mod pinmux_regs; +pub mod pwrmgr_regs; +pub mod rstmgr_regs; +pub mod rv_plic_regs; +pub mod sensor_ctrl_regs; +pub mod top_earlgrey; diff --git a/chips/earlgrey/src/registers/pinmux_regs.rs b/chips/earlgrey/src/registers/pinmux_regs.rs new file mode 100644 index 0000000000..ab8f7ee4f3 --- /dev/null +++ b/chips/earlgrey/src/registers/pinmux_regs.rs @@ -0,0 +1,250 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for pinmux. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/top_earlgrey/ip/pinmux/data/autogen/pinmux.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Pad attribute data width +pub const PINMUX_PARAM_ATTR_DW: u32 = 13; +/// Number of muxed peripheral inputs +pub const PINMUX_PARAM_N_MIO_PERIPH_IN: u32 = 57; +/// Number of muxed peripheral outputs +pub const PINMUX_PARAM_N_MIO_PERIPH_OUT: u32 = 75; +/// Number of muxed IO pads +pub const PINMUX_PARAM_N_MIO_PADS: u32 = 47; +/// Number of dedicated IO pads +pub const PINMUX_PARAM_N_DIO_PADS: u32 = 16; +/// Number of wakeup detectors +pub const PINMUX_PARAM_N_WKUP_DETECT: u32 = 8; +/// Number of wakeup counter bits +pub const PINMUX_PARAM_WKUP_CNT_WIDTH: u32 = 8; +/// Number of alerts +pub const PINMUX_PARAM_NUM_ALERTS: u32 = 1; +/// Register width +pub const PINMUX_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub PinmuxRegisters { + /// Alert Test Register + (0x0000 => pub(crate) alert_test: ReadWrite), + /// Register write enable for MIO peripheral input selects. + (0x0004 => pub(crate) mio_periph_insel_regwen: [ReadWrite; 57]), + /// For each peripheral input, this selects the muxable pad input. + (0x00e8 => pub(crate) mio_periph_insel: [ReadWrite; 57]), + /// Register write enable for MIO output selects. + (0x01cc => pub(crate) mio_outsel_regwen: [ReadWrite; 47]), + /// For each muxable pad, this selects the peripheral output. + (0x0288 => pub(crate) mio_outsel: [ReadWrite; 47]), + /// Register write enable for MIO PAD attributes. + (0x0344 => pub(crate) mio_pad_attr_regwen: [ReadWrite; 47]), + /// Muxed pad attributes. + (0x0400 => pub(crate) mio_pad_attr: [ReadWrite; 47]), + /// Register write enable for DIO PAD attributes. + (0x04bc => pub(crate) dio_pad_attr_regwen: [ReadWrite; 16]), + /// Dedicated pad attributes. + (0x04fc => pub(crate) dio_pad_attr: [ReadWrite; 16]), + /// Register indicating whether the corresponding pad is in sleep mode. + (0x053c => pub(crate) mio_pad_sleep_status: [ReadWrite; 2]), + /// Register write enable for MIO sleep value configuration. + (0x0544 => pub(crate) mio_pad_sleep_regwen: [ReadWrite; 47]), + /// Enables the sleep mode of the corresponding muxed pad. + (0x0600 => pub(crate) mio_pad_sleep_en: [ReadWrite; 47]), + /// Defines sleep behavior of the corresponding muxed pad. + (0x06bc => pub(crate) mio_pad_sleep_mode: [ReadWrite; 47]), + /// Register indicating whether the corresponding pad is in sleep mode. + (0x0778 => pub(crate) dio_pad_sleep_status: [ReadWrite; 1]), + /// Register write enable for DIO sleep value configuration. + (0x077c => pub(crate) dio_pad_sleep_regwen: [ReadWrite; 16]), + /// Enables the sleep mode of the corresponding dedicated pad. + (0x07bc => pub(crate) dio_pad_sleep_en: [ReadWrite; 16]), + /// Defines sleep behavior of the corresponding dedicated pad. + (0x07fc => pub(crate) dio_pad_sleep_mode: [ReadWrite; 16]), + /// Register write enable for wakeup detectors. + (0x083c => pub(crate) wkup_detector_regwen: [ReadWrite; 8]), + /// Enables for the wakeup detectors. + (0x085c => pub(crate) wkup_detector_en: [ReadWrite; 8]), + /// Configuration of wakeup condition detectors. + (0x087c => pub(crate) wkup_detector: [ReadWrite; 8]), + /// Counter thresholds for wakeup condition detectors. + (0x089c => pub(crate) wkup_detector_cnt_th: [ReadWrite; 8]), + /// Pad selects for pad wakeup condition detectors. + (0x08bc => pub(crate) wkup_detector_padsel: [ReadWrite; 8]), + /// Cause registers for wakeup detectors. + (0x08dc => pub(crate) wkup_cause: [ReadWrite; 1]), + (0x08e0 => @END), + } +} + +register_bitfields![u32, + pub(crate) ALERT_TEST [ + FATAL_FAULT OFFSET(0) NUMBITS(1) [], + ], + pub(crate) MIO_PERIPH_INSEL_REGWEN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) MIO_PERIPH_INSEL [ + IN_0 OFFSET(0) NUMBITS(6) [], + ], + pub(crate) MIO_OUTSEL_REGWEN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) MIO_OUTSEL [ + OUT_0 OFFSET(0) NUMBITS(7) [], + ], + pub(crate) MIO_PAD_ATTR_REGWEN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) MIO_PAD_ATTR [ + INVERT_0 OFFSET(0) NUMBITS(1) [], + VIRTUAL_OD_EN_0 OFFSET(1) NUMBITS(1) [], + PULL_EN_0 OFFSET(2) NUMBITS(1) [], + PULL_SELECT_0 OFFSET(3) NUMBITS(1) [ + PULL_DOWN = 0, + PULL_UP = 1, + ], + KEEPER_EN_0 OFFSET(4) NUMBITS(1) [], + SCHMITT_EN_0 OFFSET(5) NUMBITS(1) [], + OD_EN_0 OFFSET(6) NUMBITS(1) [], + SLEW_RATE_0 OFFSET(16) NUMBITS(2) [], + DRIVE_STRENGTH_0 OFFSET(20) NUMBITS(4) [], + ], + pub(crate) DIO_PAD_ATTR_REGWEN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) DIO_PAD_ATTR [ + INVERT_0 OFFSET(0) NUMBITS(1) [], + VIRTUAL_OD_EN_0 OFFSET(1) NUMBITS(1) [], + PULL_EN_0 OFFSET(2) NUMBITS(1) [], + PULL_SELECT_0 OFFSET(3) NUMBITS(1) [ + PULL_DOWN = 0, + PULL_UP = 1, + ], + KEEPER_EN_0 OFFSET(4) NUMBITS(1) [], + SCHMITT_EN_0 OFFSET(5) NUMBITS(1) [], + OD_EN_0 OFFSET(6) NUMBITS(1) [], + SLEW_RATE_0 OFFSET(16) NUMBITS(2) [], + DRIVE_STRENGTH_0 OFFSET(20) NUMBITS(4) [], + ], + pub(crate) MIO_PAD_SLEEP_STATUS [ + EN_0 OFFSET(0) NUMBITS(1) [], + EN_1 OFFSET(1) NUMBITS(1) [], + EN_2 OFFSET(2) NUMBITS(1) [], + EN_3 OFFSET(3) NUMBITS(1) [], + EN_4 OFFSET(4) NUMBITS(1) [], + EN_5 OFFSET(5) NUMBITS(1) [], + EN_6 OFFSET(6) NUMBITS(1) [], + EN_7 OFFSET(7) NUMBITS(1) [], + EN_8 OFFSET(8) NUMBITS(1) [], + EN_9 OFFSET(9) NUMBITS(1) [], + EN_10 OFFSET(10) NUMBITS(1) [], + EN_11 OFFSET(11) NUMBITS(1) [], + EN_12 OFFSET(12) NUMBITS(1) [], + EN_13 OFFSET(13) NUMBITS(1) [], + EN_14 OFFSET(14) NUMBITS(1) [], + EN_15 OFFSET(15) NUMBITS(1) [], + EN_16 OFFSET(16) NUMBITS(1) [], + EN_17 OFFSET(17) NUMBITS(1) [], + EN_18 OFFSET(18) NUMBITS(1) [], + EN_19 OFFSET(19) NUMBITS(1) [], + EN_20 OFFSET(20) NUMBITS(1) [], + EN_21 OFFSET(21) NUMBITS(1) [], + EN_22 OFFSET(22) NUMBITS(1) [], + EN_23 OFFSET(23) NUMBITS(1) [], + EN_24 OFFSET(24) NUMBITS(1) [], + EN_25 OFFSET(25) NUMBITS(1) [], + EN_26 OFFSET(26) NUMBITS(1) [], + EN_27 OFFSET(27) NUMBITS(1) [], + EN_28 OFFSET(28) NUMBITS(1) [], + EN_29 OFFSET(29) NUMBITS(1) [], + EN_30 OFFSET(30) NUMBITS(1) [], + EN_31 OFFSET(31) NUMBITS(1) [], + ], + pub(crate) MIO_PAD_SLEEP_REGWEN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) MIO_PAD_SLEEP_EN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) MIO_PAD_SLEEP_MODE [ + OUT_0 OFFSET(0) NUMBITS(2) [ + TIE_LOW = 0, + TIE_HIGH = 1, + HIGH_Z = 2, + KEEP = 3, + ], + ], + pub(crate) DIO_PAD_SLEEP_STATUS [ + EN_0 OFFSET(0) NUMBITS(1) [], + EN_1 OFFSET(1) NUMBITS(1) [], + EN_2 OFFSET(2) NUMBITS(1) [], + EN_3 OFFSET(3) NUMBITS(1) [], + EN_4 OFFSET(4) NUMBITS(1) [], + EN_5 OFFSET(5) NUMBITS(1) [], + EN_6 OFFSET(6) NUMBITS(1) [], + EN_7 OFFSET(7) NUMBITS(1) [], + EN_8 OFFSET(8) NUMBITS(1) [], + EN_9 OFFSET(9) NUMBITS(1) [], + EN_10 OFFSET(10) NUMBITS(1) [], + EN_11 OFFSET(11) NUMBITS(1) [], + EN_12 OFFSET(12) NUMBITS(1) [], + EN_13 OFFSET(13) NUMBITS(1) [], + EN_14 OFFSET(14) NUMBITS(1) [], + EN_15 OFFSET(15) NUMBITS(1) [], + ], + pub(crate) DIO_PAD_SLEEP_REGWEN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) DIO_PAD_SLEEP_EN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) DIO_PAD_SLEEP_MODE [ + OUT_0 OFFSET(0) NUMBITS(2) [ + TIE_LOW = 0, + TIE_HIGH = 1, + HIGH_Z = 2, + KEEP = 3, + ], + ], + pub(crate) WKUP_DETECTOR_REGWEN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) WKUP_DETECTOR_EN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) WKUP_DETECTOR [ + MODE_0 OFFSET(0) NUMBITS(3) [ + POSEDGE = 0, + NEGEDGE = 1, + EDGE = 2, + TIMEDHIGH = 3, + TIMEDLOW = 4, + ], + FILTER_0 OFFSET(3) NUMBITS(1) [], + MIODIO_0 OFFSET(4) NUMBITS(1) [], + ], + pub(crate) WKUP_DETECTOR_CNT_TH [ + TH_0 OFFSET(0) NUMBITS(8) [], + ], + pub(crate) WKUP_DETECTOR_PADSEL [ + SEL_0 OFFSET(0) NUMBITS(6) [], + ], + pub(crate) WKUP_CAUSE [ + CAUSE_0 OFFSET(0) NUMBITS(1) [], + CAUSE_1 OFFSET(1) NUMBITS(1) [], + CAUSE_2 OFFSET(2) NUMBITS(1) [], + CAUSE_3 OFFSET(3) NUMBITS(1) [], + CAUSE_4 OFFSET(4) NUMBITS(1) [], + CAUSE_5 OFFSET(5) NUMBITS(1) [], + CAUSE_6 OFFSET(6) NUMBITS(1) [], + CAUSE_7 OFFSET(7) NUMBITS(1) [], + ], +]; + +// End generated register constants for pinmux diff --git a/chips/earlgrey/src/registers/pwrmgr_regs.rs b/chips/earlgrey/src/registers/pwrmgr_regs.rs new file mode 100644 index 0000000000..56abe82035 --- /dev/null +++ b/chips/earlgrey/src/registers/pwrmgr_regs.rs @@ -0,0 +1,173 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for pwrmgr. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/top_earlgrey/ip/pwrmgr/data/autogen/pwrmgr.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of wakeups +pub const PWRMGR_PARAM_NUM_WKUPS: u32 = 6; +/// Vector index for sysrst_ctrl_aon wkup_req, applies for WAKEUP_EN, WAKE_STATUS and WAKE_INFO +pub const PWRMGR_PARAM_SYSRST_CTRL_AON_WKUP_REQ_IDX: u32 = 0; +/// Vector index for adc_ctrl_aon wkup_req, applies for WAKEUP_EN, WAKE_STATUS and WAKE_INFO +pub const PWRMGR_PARAM_ADC_CTRL_AON_WKUP_REQ_IDX: u32 = 1; +/// Vector index for pinmux_aon pin_wkup_req, applies for WAKEUP_EN, WAKE_STATUS and WAKE_INFO +pub const PWRMGR_PARAM_PINMUX_AON_PIN_WKUP_REQ_IDX: u32 = 2; +/// Vector index for pinmux_aon usb_wkup_req, applies for WAKEUP_EN, WAKE_STATUS and WAKE_INFO +pub const PWRMGR_PARAM_PINMUX_AON_USB_WKUP_REQ_IDX: u32 = 3; +/// Vector index for aon_timer_aon wkup_req, applies for WAKEUP_EN, WAKE_STATUS and WAKE_INFO +pub const PWRMGR_PARAM_AON_TIMER_AON_WKUP_REQ_IDX: u32 = 4; +/// Vector index for sensor_ctrl wkup_req, applies for WAKEUP_EN, WAKE_STATUS and WAKE_INFO +pub const PWRMGR_PARAM_SENSOR_CTRL_WKUP_REQ_IDX: u32 = 5; +/// Number of peripheral reset requets +pub const PWRMGR_PARAM_NUM_RST_REQS: u32 = 2; +/// Number of pwrmgr internal reset requets +pub const PWRMGR_PARAM_NUM_INT_RST_REQS: u32 = 2; +/// Number of debug reset requets +pub const PWRMGR_PARAM_NUM_DEBUG_RST_REQS: u32 = 1; +/// Reset req idx for MainPwr +pub const PWRMGR_PARAM_RESET_MAIN_PWR_IDX: u32 = 2; +/// Reset req idx for Esc +pub const PWRMGR_PARAM_RESET_ESC_IDX: u32 = 3; +/// Reset req idx for Ndm +pub const PWRMGR_PARAM_RESET_NDM_IDX: u32 = 4; +/// Number of alerts +pub const PWRMGR_PARAM_NUM_ALERTS: u32 = 1; +/// Register width +pub const PWRMGR_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub PwrmgrRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// Controls the configurability of the !!CONTROL register. + (0x0010 => pub(crate) ctrl_cfg_regwen: ReadWrite), + /// Control register + (0x0014 => pub(crate) control: ReadWrite), + /// The configuration registers CONTROL, WAKEUP_EN, RESET_EN are all written in the + (0x0018 => pub(crate) cfg_cdc_sync: ReadWrite), + /// Configuration enable for wakeup_en register + (0x001c => pub(crate) wakeup_en_regwen: ReadWrite), + /// Bit mask for enabled wakeups + (0x0020 => pub(crate) wakeup_en: [ReadWrite; 1]), + /// A read only register of all current wake requests post enable mask + (0x0024 => pub(crate) wake_status: [ReadWrite; 1]), + /// Configuration enable for reset_en register + (0x0028 => pub(crate) reset_en_regwen: ReadWrite), + /// Bit mask for enabled reset requests + (0x002c => pub(crate) reset_en: [ReadWrite; 1]), + /// A read only register of all current reset requests post enable mask + (0x0030 => pub(crate) reset_status: [ReadWrite; 1]), + /// A read only register of escalation reset request + (0x0034 => pub(crate) escalate_reset_status: ReadWrite), + /// Indicates which functions caused the chip to wakeup + (0x0038 => pub(crate) wake_info_capture_dis: ReadWrite), + /// Indicates which functions caused the chip to wakeup. + (0x003c => pub(crate) wake_info: ReadWrite), + /// A read only register that shows the existing faults + (0x0040 => pub(crate) fault_status: ReadWrite), + (0x0044 => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + WAKEUP OFFSET(0) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + FATAL_FAULT OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CTRL_CFG_REGWEN [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CONTROL [ + LOW_POWER_HINT OFFSET(0) NUMBITS(1) [ + NONE = 0, + LOW_POWER = 1, + ], + CORE_CLK_EN OFFSET(4) NUMBITS(1) [ + DISABLED = 0, + ENABLED = 1, + ], + IO_CLK_EN OFFSET(5) NUMBITS(1) [ + DISABLED = 0, + ENABLED = 1, + ], + USB_CLK_EN_LP OFFSET(6) NUMBITS(1) [ + DISABLED = 0, + ENABLED = 1, + ], + USB_CLK_EN_ACTIVE OFFSET(7) NUMBITS(1) [ + DISABLED = 0, + ENABLED = 1, + ], + MAIN_PD_N OFFSET(8) NUMBITS(1) [ + POWER_DOWN = 0, + POWER_UP = 1, + ], + ], + pub(crate) CFG_CDC_SYNC [ + SYNC OFFSET(0) NUMBITS(1) [], + ], + pub(crate) WAKEUP_EN_REGWEN [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) WAKEUP_EN [ + EN_0 OFFSET(0) NUMBITS(1) [], + EN_1 OFFSET(1) NUMBITS(1) [], + EN_2 OFFSET(2) NUMBITS(1) [], + EN_3 OFFSET(3) NUMBITS(1) [], + EN_4 OFFSET(4) NUMBITS(1) [], + EN_5 OFFSET(5) NUMBITS(1) [], + ], + pub(crate) WAKE_STATUS [ + VAL_0 OFFSET(0) NUMBITS(1) [], + VAL_1 OFFSET(1) NUMBITS(1) [], + VAL_2 OFFSET(2) NUMBITS(1) [], + VAL_3 OFFSET(3) NUMBITS(1) [], + VAL_4 OFFSET(4) NUMBITS(1) [], + VAL_5 OFFSET(5) NUMBITS(1) [], + ], + pub(crate) RESET_EN_REGWEN [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) RESET_EN [ + EN_0 OFFSET(0) NUMBITS(1) [], + EN_1 OFFSET(1) NUMBITS(1) [], + ], + pub(crate) RESET_STATUS [ + VAL_0 OFFSET(0) NUMBITS(1) [], + VAL_1 OFFSET(1) NUMBITS(1) [], + ], + pub(crate) ESCALATE_RESET_STATUS [ + VAL OFFSET(0) NUMBITS(1) [], + ], + pub(crate) WAKE_INFO_CAPTURE_DIS [ + VAL OFFSET(0) NUMBITS(1) [], + ], + pub(crate) WAKE_INFO [ + REASONS OFFSET(0) NUMBITS(6) [], + FALL_THROUGH OFFSET(6) NUMBITS(1) [], + ABORT OFFSET(7) NUMBITS(1) [], + ], + pub(crate) FAULT_STATUS [ + REG_INTG_ERR OFFSET(0) NUMBITS(1) [], + ESC_TIMEOUT OFFSET(1) NUMBITS(1) [], + MAIN_PD_GLITCH OFFSET(2) NUMBITS(1) [], + ], +]; + +// End generated register constants for pwrmgr diff --git a/chips/earlgrey/src/registers/rstmgr_regs.rs b/chips/earlgrey/src/registers/rstmgr_regs.rs new file mode 100644 index 0000000000..cdc5c16048 --- /dev/null +++ b/chips/earlgrey/src/registers/rstmgr_regs.rs @@ -0,0 +1,116 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for rstmgr. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/top_earlgrey/ip/rstmgr/data/autogen/rstmgr.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Read width for crash info +pub const RSTMGR_PARAM_RD_WIDTH: u32 = 32; +/// Index width for crash info +pub const RSTMGR_PARAM_IDX_WIDTH: u32 = 4; +/// Number of hardware reset requests, inclusive of debug resets and pwrmgr's internal resets +pub const RSTMGR_PARAM_NUM_HW_RESETS: u32 = 5; +/// Number of software resets +pub const RSTMGR_PARAM_NUM_SW_RESETS: u32 = 8; +/// Number of total reset requests, inclusive of hw/sw, por and low power exit +pub const RSTMGR_PARAM_NUM_TOTAL_RESETS: u32 = 8; +/// Number of alerts +pub const RSTMGR_PARAM_NUM_ALERTS: u32 = 2; +/// Register width +pub const RSTMGR_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub RstmgrRegisters { + /// Alert Test Register + (0x0000 => pub(crate) alert_test: ReadWrite), + /// Software requested system reset. + (0x0004 => pub(crate) reset_req: ReadWrite), + /// Device reset reason. + (0x0008 => pub(crate) reset_info: ReadWrite), + /// Alert write enable + (0x000c => pub(crate) alert_regwen: ReadWrite), + /// Alert info dump controls. + (0x0010 => pub(crate) alert_info_ctrl: ReadWrite), + /// Alert info dump attributes. + (0x0014 => pub(crate) alert_info_attr: ReadWrite), + /// Alert dump information prior to last reset. + (0x0018 => pub(crate) alert_info: ReadWrite), + /// Cpu write enable + (0x001c => pub(crate) cpu_regwen: ReadWrite), + /// Cpu info dump controls. + (0x0020 => pub(crate) cpu_info_ctrl: ReadWrite), + /// Cpu info dump attributes. + (0x0024 => pub(crate) cpu_info_attr: ReadWrite), + /// Cpu dump information prior to last reset. + (0x0028 => pub(crate) cpu_info: ReadWrite), + /// Register write enable for software controllable resets. + (0x002c => pub(crate) sw_rst_regwen: [ReadWrite; 8]), + /// Software controllable resets. + (0x004c => pub(crate) sw_rst_ctrl_n: [ReadWrite; 8]), + /// A bit vector of all the errors that have occurred in reset manager + (0x006c => pub(crate) err_code: ReadWrite), + (0x0070 => @END), + } +} + +register_bitfields![u32, + pub(crate) ALERT_TEST [ + FATAL_FAULT OFFSET(0) NUMBITS(1) [], + FATAL_CNSTY_FAULT OFFSET(1) NUMBITS(1) [], + ], + pub(crate) RESET_REQ [ + VAL OFFSET(0) NUMBITS(4) [], + ], + pub(crate) RESET_INFO [ + POR OFFSET(0) NUMBITS(1) [], + LOW_POWER_EXIT OFFSET(1) NUMBITS(1) [], + SW_RESET OFFSET(2) NUMBITS(1) [], + HW_REQ OFFSET(3) NUMBITS(5) [], + ], + pub(crate) ALERT_REGWEN [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) ALERT_INFO_CTRL [ + EN OFFSET(0) NUMBITS(1) [], + INDEX OFFSET(4) NUMBITS(4) [], + ], + pub(crate) ALERT_INFO_ATTR [ + CNT_AVAIL OFFSET(0) NUMBITS(4) [], + ], + pub(crate) ALERT_INFO [ + VALUE OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CPU_REGWEN [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CPU_INFO_CTRL [ + EN OFFSET(0) NUMBITS(1) [], + INDEX OFFSET(4) NUMBITS(4) [], + ], + pub(crate) CPU_INFO_ATTR [ + CNT_AVAIL OFFSET(0) NUMBITS(4) [], + ], + pub(crate) CPU_INFO [ + VALUE OFFSET(0) NUMBITS(32) [], + ], + pub(crate) SW_RST_REGWEN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) SW_RST_CTRL_N [ + VAL_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) ERR_CODE [ + REG_INTG_ERR OFFSET(0) NUMBITS(1) [], + RESET_CONSISTENCY_ERR OFFSET(1) NUMBITS(1) [], + FSM_ERR OFFSET(2) NUMBITS(1) [], + ], +]; + +// End generated register constants for rstmgr diff --git a/chips/earlgrey/src/registers/rv_plic_regs.rs b/chips/earlgrey/src/registers/rv_plic_regs.rs new file mode 100644 index 0000000000..73f90cfd96 --- /dev/null +++ b/chips/earlgrey/src/registers/rv_plic_regs.rs @@ -0,0 +1,1056 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for rv_plic. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/top_earlgrey/ip_autogen/rv_plic/data/rv_plic.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of interrupt sources +pub const RV_PLIC_PARAM_NUM_SRC: u32 = 185; +/// Number of Targets (Harts) +pub const RV_PLIC_PARAM_NUM_TARGET: u32 = 1; +/// Width of priority signals +pub const RV_PLIC_PARAM_PRIO_WIDTH: u32 = 2; +/// Number of alerts +pub const RV_PLIC_PARAM_NUM_ALERTS: u32 = 1; +/// Register width +pub const RV_PLIC_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub RvPlicRegisters { + /// Interrupt Source 0 Priority + (0x0000 => pub(crate) prio0: ReadWrite), + /// Interrupt Source 1 Priority + (0x0004 => pub(crate) prio1: ReadWrite), + /// Interrupt Source 2 Priority + (0x0008 => pub(crate) prio2: ReadWrite), + /// Interrupt Source 3 Priority + (0x000c => pub(crate) prio3: ReadWrite), + /// Interrupt Source 4 Priority + (0x0010 => pub(crate) prio4: ReadWrite), + /// Interrupt Source 5 Priority + (0x0014 => pub(crate) prio5: ReadWrite), + /// Interrupt Source 6 Priority + (0x0018 => pub(crate) prio6: ReadWrite), + /// Interrupt Source 7 Priority + (0x001c => pub(crate) prio7: ReadWrite), + /// Interrupt Source 8 Priority + (0x0020 => pub(crate) prio8: ReadWrite), + /// Interrupt Source 9 Priority + (0x0024 => pub(crate) prio9: ReadWrite), + /// Interrupt Source 10 Priority + (0x0028 => pub(crate) prio10: ReadWrite), + /// Interrupt Source 11 Priority + (0x002c => pub(crate) prio11: ReadWrite), + /// Interrupt Source 12 Priority + (0x0030 => pub(crate) prio12: ReadWrite), + /// Interrupt Source 13 Priority + (0x0034 => pub(crate) prio13: ReadWrite), + /// Interrupt Source 14 Priority + (0x0038 => pub(crate) prio14: ReadWrite), + /// Interrupt Source 15 Priority + (0x003c => pub(crate) prio15: ReadWrite), + /// Interrupt Source 16 Priority + (0x0040 => pub(crate) prio16: ReadWrite), + /// Interrupt Source 17 Priority + (0x0044 => pub(crate) prio17: ReadWrite), + /// Interrupt Source 18 Priority + (0x0048 => pub(crate) prio18: ReadWrite), + /// Interrupt Source 19 Priority + (0x004c => pub(crate) prio19: ReadWrite), + /// Interrupt Source 20 Priority + (0x0050 => pub(crate) prio20: ReadWrite), + /// Interrupt Source 21 Priority + (0x0054 => pub(crate) prio21: ReadWrite), + /// Interrupt Source 22 Priority + (0x0058 => pub(crate) prio22: ReadWrite), + /// Interrupt Source 23 Priority + (0x005c => pub(crate) prio23: ReadWrite), + /// Interrupt Source 24 Priority + (0x0060 => pub(crate) prio24: ReadWrite), + /// Interrupt Source 25 Priority + (0x0064 => pub(crate) prio25: ReadWrite), + /// Interrupt Source 26 Priority + (0x0068 => pub(crate) prio26: ReadWrite), + /// Interrupt Source 27 Priority + (0x006c => pub(crate) prio27: ReadWrite), + /// Interrupt Source 28 Priority + (0x0070 => pub(crate) prio28: ReadWrite), + /// Interrupt Source 29 Priority + (0x0074 => pub(crate) prio29: ReadWrite), + /// Interrupt Source 30 Priority + (0x0078 => pub(crate) prio30: ReadWrite), + /// Interrupt Source 31 Priority + (0x007c => pub(crate) prio31: ReadWrite), + /// Interrupt Source 32 Priority + (0x0080 => pub(crate) prio32: ReadWrite), + /// Interrupt Source 33 Priority + (0x0084 => pub(crate) prio33: ReadWrite), + /// Interrupt Source 34 Priority + (0x0088 => pub(crate) prio34: ReadWrite), + /// Interrupt Source 35 Priority + (0x008c => pub(crate) prio35: ReadWrite), + /// Interrupt Source 36 Priority + (0x0090 => pub(crate) prio36: ReadWrite), + /// Interrupt Source 37 Priority + (0x0094 => pub(crate) prio37: ReadWrite), + /// Interrupt Source 38 Priority + (0x0098 => pub(crate) prio38: ReadWrite), + /// Interrupt Source 39 Priority + (0x009c => pub(crate) prio39: ReadWrite), + /// Interrupt Source 40 Priority + (0x00a0 => pub(crate) prio40: ReadWrite), + /// Interrupt Source 41 Priority + (0x00a4 => pub(crate) prio41: ReadWrite), + /// Interrupt Source 42 Priority + (0x00a8 => pub(crate) prio42: ReadWrite), + /// Interrupt Source 43 Priority + (0x00ac => pub(crate) prio43: ReadWrite), + /// Interrupt Source 44 Priority + (0x00b0 => pub(crate) prio44: ReadWrite), + /// Interrupt Source 45 Priority + (0x00b4 => pub(crate) prio45: ReadWrite), + /// Interrupt Source 46 Priority + (0x00b8 => pub(crate) prio46: ReadWrite), + /// Interrupt Source 47 Priority + (0x00bc => pub(crate) prio47: ReadWrite), + /// Interrupt Source 48 Priority + (0x00c0 => pub(crate) prio48: ReadWrite), + /// Interrupt Source 49 Priority + (0x00c4 => pub(crate) prio49: ReadWrite), + /// Interrupt Source 50 Priority + (0x00c8 => pub(crate) prio50: ReadWrite), + /// Interrupt Source 51 Priority + (0x00cc => pub(crate) prio51: ReadWrite), + /// Interrupt Source 52 Priority + (0x00d0 => pub(crate) prio52: ReadWrite), + /// Interrupt Source 53 Priority + (0x00d4 => pub(crate) prio53: ReadWrite), + /// Interrupt Source 54 Priority + (0x00d8 => pub(crate) prio54: ReadWrite), + /// Interrupt Source 55 Priority + (0x00dc => pub(crate) prio55: ReadWrite), + /// Interrupt Source 56 Priority + (0x00e0 => pub(crate) prio56: ReadWrite), + /// Interrupt Source 57 Priority + (0x00e4 => pub(crate) prio57: ReadWrite), + /// Interrupt Source 58 Priority + (0x00e8 => pub(crate) prio58: ReadWrite), + /// Interrupt Source 59 Priority + (0x00ec => pub(crate) prio59: ReadWrite), + /// Interrupt Source 60 Priority + (0x00f0 => pub(crate) prio60: ReadWrite), + /// Interrupt Source 61 Priority + (0x00f4 => pub(crate) prio61: ReadWrite), + /// Interrupt Source 62 Priority + (0x00f8 => pub(crate) prio62: ReadWrite), + /// Interrupt Source 63 Priority + (0x00fc => pub(crate) prio63: ReadWrite), + /// Interrupt Source 64 Priority + (0x0100 => pub(crate) prio64: ReadWrite), + /// Interrupt Source 65 Priority + (0x0104 => pub(crate) prio65: ReadWrite), + /// Interrupt Source 66 Priority + (0x0108 => pub(crate) prio66: ReadWrite), + /// Interrupt Source 67 Priority + (0x010c => pub(crate) prio67: ReadWrite), + /// Interrupt Source 68 Priority + (0x0110 => pub(crate) prio68: ReadWrite), + /// Interrupt Source 69 Priority + (0x0114 => pub(crate) prio69: ReadWrite), + /// Interrupt Source 70 Priority + (0x0118 => pub(crate) prio70: ReadWrite), + /// Interrupt Source 71 Priority + (0x011c => pub(crate) prio71: ReadWrite), + /// Interrupt Source 72 Priority + (0x0120 => pub(crate) prio72: ReadWrite), + /// Interrupt Source 73 Priority + (0x0124 => pub(crate) prio73: ReadWrite), + /// Interrupt Source 74 Priority + (0x0128 => pub(crate) prio74: ReadWrite), + /// Interrupt Source 75 Priority + (0x012c => pub(crate) prio75: ReadWrite), + /// Interrupt Source 76 Priority + (0x0130 => pub(crate) prio76: ReadWrite), + /// Interrupt Source 77 Priority + (0x0134 => pub(crate) prio77: ReadWrite), + /// Interrupt Source 78 Priority + (0x0138 => pub(crate) prio78: ReadWrite), + /// Interrupt Source 79 Priority + (0x013c => pub(crate) prio79: ReadWrite), + /// Interrupt Source 80 Priority + (0x0140 => pub(crate) prio80: ReadWrite), + /// Interrupt Source 81 Priority + (0x0144 => pub(crate) prio81: ReadWrite), + /// Interrupt Source 82 Priority + (0x0148 => pub(crate) prio82: ReadWrite), + /// Interrupt Source 83 Priority + (0x014c => pub(crate) prio83: ReadWrite), + /// Interrupt Source 84 Priority + (0x0150 => pub(crate) prio84: ReadWrite), + /// Interrupt Source 85 Priority + (0x0154 => pub(crate) prio85: ReadWrite), + /// Interrupt Source 86 Priority + (0x0158 => pub(crate) prio86: ReadWrite), + /// Interrupt Source 87 Priority + (0x015c => pub(crate) prio87: ReadWrite), + /// Interrupt Source 88 Priority + (0x0160 => pub(crate) prio88: ReadWrite), + /// Interrupt Source 89 Priority + (0x0164 => pub(crate) prio89: ReadWrite), + /// Interrupt Source 90 Priority + (0x0168 => pub(crate) prio90: ReadWrite), + /// Interrupt Source 91 Priority + (0x016c => pub(crate) prio91: ReadWrite), + /// Interrupt Source 92 Priority + (0x0170 => pub(crate) prio92: ReadWrite), + /// Interrupt Source 93 Priority + (0x0174 => pub(crate) prio93: ReadWrite), + /// Interrupt Source 94 Priority + (0x0178 => pub(crate) prio94: ReadWrite), + /// Interrupt Source 95 Priority + (0x017c => pub(crate) prio95: ReadWrite), + /// Interrupt Source 96 Priority + (0x0180 => pub(crate) prio96: ReadWrite), + /// Interrupt Source 97 Priority + (0x0184 => pub(crate) prio97: ReadWrite), + /// Interrupt Source 98 Priority + (0x0188 => pub(crate) prio98: ReadWrite), + /// Interrupt Source 99 Priority + (0x018c => pub(crate) prio99: ReadWrite), + /// Interrupt Source 100 Priority + (0x0190 => pub(crate) prio100: ReadWrite), + /// Interrupt Source 101 Priority + (0x0194 => pub(crate) prio101: ReadWrite), + /// Interrupt Source 102 Priority + (0x0198 => pub(crate) prio102: ReadWrite), + /// Interrupt Source 103 Priority + (0x019c => pub(crate) prio103: ReadWrite), + /// Interrupt Source 104 Priority + (0x01a0 => pub(crate) prio104: ReadWrite), + /// Interrupt Source 105 Priority + (0x01a4 => pub(crate) prio105: ReadWrite), + /// Interrupt Source 106 Priority + (0x01a8 => pub(crate) prio106: ReadWrite), + /// Interrupt Source 107 Priority + (0x01ac => pub(crate) prio107: ReadWrite), + /// Interrupt Source 108 Priority + (0x01b0 => pub(crate) prio108: ReadWrite), + /// Interrupt Source 109 Priority + (0x01b4 => pub(crate) prio109: ReadWrite), + /// Interrupt Source 110 Priority + (0x01b8 => pub(crate) prio110: ReadWrite), + /// Interrupt Source 111 Priority + (0x01bc => pub(crate) prio111: ReadWrite), + /// Interrupt Source 112 Priority + (0x01c0 => pub(crate) prio112: ReadWrite), + /// Interrupt Source 113 Priority + (0x01c4 => pub(crate) prio113: ReadWrite), + /// Interrupt Source 114 Priority + (0x01c8 => pub(crate) prio114: ReadWrite), + /// Interrupt Source 115 Priority + (0x01cc => pub(crate) prio115: ReadWrite), + /// Interrupt Source 116 Priority + (0x01d0 => pub(crate) prio116: ReadWrite), + /// Interrupt Source 117 Priority + (0x01d4 => pub(crate) prio117: ReadWrite), + /// Interrupt Source 118 Priority + (0x01d8 => pub(crate) prio118: ReadWrite), + /// Interrupt Source 119 Priority + (0x01dc => pub(crate) prio119: ReadWrite), + /// Interrupt Source 120 Priority + (0x01e0 => pub(crate) prio120: ReadWrite), + /// Interrupt Source 121 Priority + (0x01e4 => pub(crate) prio121: ReadWrite), + /// Interrupt Source 122 Priority + (0x01e8 => pub(crate) prio122: ReadWrite), + /// Interrupt Source 123 Priority + (0x01ec => pub(crate) prio123: ReadWrite), + /// Interrupt Source 124 Priority + (0x01f0 => pub(crate) prio124: ReadWrite), + /// Interrupt Source 125 Priority + (0x01f4 => pub(crate) prio125: ReadWrite), + /// Interrupt Source 126 Priority + (0x01f8 => pub(crate) prio126: ReadWrite), + /// Interrupt Source 127 Priority + (0x01fc => pub(crate) prio127: ReadWrite), + /// Interrupt Source 128 Priority + (0x0200 => pub(crate) prio128: ReadWrite), + /// Interrupt Source 129 Priority + (0x0204 => pub(crate) prio129: ReadWrite), + /// Interrupt Source 130 Priority + (0x0208 => pub(crate) prio130: ReadWrite), + /// Interrupt Source 131 Priority + (0x020c => pub(crate) prio131: ReadWrite), + /// Interrupt Source 132 Priority + (0x0210 => pub(crate) prio132: ReadWrite), + /// Interrupt Source 133 Priority + (0x0214 => pub(crate) prio133: ReadWrite), + /// Interrupt Source 134 Priority + (0x0218 => pub(crate) prio134: ReadWrite), + /// Interrupt Source 135 Priority + (0x021c => pub(crate) prio135: ReadWrite), + /// Interrupt Source 136 Priority + (0x0220 => pub(crate) prio136: ReadWrite), + /// Interrupt Source 137 Priority + (0x0224 => pub(crate) prio137: ReadWrite), + /// Interrupt Source 138 Priority + (0x0228 => pub(crate) prio138: ReadWrite), + /// Interrupt Source 139 Priority + (0x022c => pub(crate) prio139: ReadWrite), + /// Interrupt Source 140 Priority + (0x0230 => pub(crate) prio140: ReadWrite), + /// Interrupt Source 141 Priority + (0x0234 => pub(crate) prio141: ReadWrite), + /// Interrupt Source 142 Priority + (0x0238 => pub(crate) prio142: ReadWrite), + /// Interrupt Source 143 Priority + (0x023c => pub(crate) prio143: ReadWrite), + /// Interrupt Source 144 Priority + (0x0240 => pub(crate) prio144: ReadWrite), + /// Interrupt Source 145 Priority + (0x0244 => pub(crate) prio145: ReadWrite), + /// Interrupt Source 146 Priority + (0x0248 => pub(crate) prio146: ReadWrite), + /// Interrupt Source 147 Priority + (0x024c => pub(crate) prio147: ReadWrite), + /// Interrupt Source 148 Priority + (0x0250 => pub(crate) prio148: ReadWrite), + /// Interrupt Source 149 Priority + (0x0254 => pub(crate) prio149: ReadWrite), + /// Interrupt Source 150 Priority + (0x0258 => pub(crate) prio150: ReadWrite), + /// Interrupt Source 151 Priority + (0x025c => pub(crate) prio151: ReadWrite), + /// Interrupt Source 152 Priority + (0x0260 => pub(crate) prio152: ReadWrite), + /// Interrupt Source 153 Priority + (0x0264 => pub(crate) prio153: ReadWrite), + /// Interrupt Source 154 Priority + (0x0268 => pub(crate) prio154: ReadWrite), + /// Interrupt Source 155 Priority + (0x026c => pub(crate) prio155: ReadWrite), + /// Interrupt Source 156 Priority + (0x0270 => pub(crate) prio156: ReadWrite), + /// Interrupt Source 157 Priority + (0x0274 => pub(crate) prio157: ReadWrite), + /// Interrupt Source 158 Priority + (0x0278 => pub(crate) prio158: ReadWrite), + /// Interrupt Source 159 Priority + (0x027c => pub(crate) prio159: ReadWrite), + /// Interrupt Source 160 Priority + (0x0280 => pub(crate) prio160: ReadWrite), + /// Interrupt Source 161 Priority + (0x0284 => pub(crate) prio161: ReadWrite), + /// Interrupt Source 162 Priority + (0x0288 => pub(crate) prio162: ReadWrite), + /// Interrupt Source 163 Priority + (0x028c => pub(crate) prio163: ReadWrite), + /// Interrupt Source 164 Priority + (0x0290 => pub(crate) prio164: ReadWrite), + /// Interrupt Source 165 Priority + (0x0294 => pub(crate) prio165: ReadWrite), + /// Interrupt Source 166 Priority + (0x0298 => pub(crate) prio166: ReadWrite), + /// Interrupt Source 167 Priority + (0x029c => pub(crate) prio167: ReadWrite), + /// Interrupt Source 168 Priority + (0x02a0 => pub(crate) prio168: ReadWrite), + /// Interrupt Source 169 Priority + (0x02a4 => pub(crate) prio169: ReadWrite), + /// Interrupt Source 170 Priority + (0x02a8 => pub(crate) prio170: ReadWrite), + /// Interrupt Source 171 Priority + (0x02ac => pub(crate) prio171: ReadWrite), + /// Interrupt Source 172 Priority + (0x02b0 => pub(crate) prio172: ReadWrite), + /// Interrupt Source 173 Priority + (0x02b4 => pub(crate) prio173: ReadWrite), + /// Interrupt Source 174 Priority + (0x02b8 => pub(crate) prio174: ReadWrite), + /// Interrupt Source 175 Priority + (0x02bc => pub(crate) prio175: ReadWrite), + /// Interrupt Source 176 Priority + (0x02c0 => pub(crate) prio176: ReadWrite), + /// Interrupt Source 177 Priority + (0x02c4 => pub(crate) prio177: ReadWrite), + /// Interrupt Source 178 Priority + (0x02c8 => pub(crate) prio178: ReadWrite), + /// Interrupt Source 179 Priority + (0x02cc => pub(crate) prio179: ReadWrite), + /// Interrupt Source 180 Priority + (0x02d0 => pub(crate) prio180: ReadWrite), + /// Interrupt Source 181 Priority + (0x02d4 => pub(crate) prio181: ReadWrite), + /// Interrupt Source 182 Priority + (0x02d8 => pub(crate) prio182: ReadWrite), + /// Interrupt Source 183 Priority + (0x02dc => pub(crate) prio183: ReadWrite), + /// Interrupt Source 184 Priority + (0x02e0 => pub(crate) prio184: ReadWrite), + (0x02e4 => _reserved1), + /// Interrupt Pending + (0x1000 => pub(crate) ip: [ReadWrite; 6]), + (0x1018 => _reserved2), + /// Interrupt Enable for Target 0 + (0x2000 => pub(crate) ie0: [ReadWrite; 6]), + (0x2018 => _reserved3), + /// Threshold of priority for Target 0 + (0x200000 => pub(crate) threshold0: ReadWrite), + /// Claim interrupt by read, complete interrupt by write for Target 0. + (0x200004 => pub(crate) cc0: ReadWrite), + (0x200008 => _reserved4), + /// msip for Hart 0. + (0x4000000 => pub(crate) msip0: ReadWrite), + (0x4000004 => _reserved5), + /// Alert Test Register. + (0x4004000 => pub(crate) alert_test: ReadWrite), + (0x4004004 => @END), + } +} + +register_bitfields![u32, + pub(crate) PRIO0 [ + PRIO0 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO1 [ + PRIO1 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO2 [ + PRIO2 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO3 [ + PRIO3 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO4 [ + PRIO4 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO5 [ + PRIO5 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO6 [ + PRIO6 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO7 [ + PRIO7 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO8 [ + PRIO8 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO9 [ + PRIO9 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO10 [ + PRIO10 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO11 [ + PRIO11 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO12 [ + PRIO12 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO13 [ + PRIO13 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO14 [ + PRIO14 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO15 [ + PRIO15 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO16 [ + PRIO16 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO17 [ + PRIO17 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO18 [ + PRIO18 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO19 [ + PRIO19 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO20 [ + PRIO20 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO21 [ + PRIO21 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO22 [ + PRIO22 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO23 [ + PRIO23 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO24 [ + PRIO24 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO25 [ + PRIO25 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO26 [ + PRIO26 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO27 [ + PRIO27 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO28 [ + PRIO28 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO29 [ + PRIO29 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO30 [ + PRIO30 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO31 [ + PRIO31 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO32 [ + PRIO32 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO33 [ + PRIO33 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO34 [ + PRIO34 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO35 [ + PRIO35 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO36 [ + PRIO36 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO37 [ + PRIO37 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO38 [ + PRIO38 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO39 [ + PRIO39 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO40 [ + PRIO40 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO41 [ + PRIO41 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO42 [ + PRIO42 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO43 [ + PRIO43 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO44 [ + PRIO44 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO45 [ + PRIO45 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO46 [ + PRIO46 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO47 [ + PRIO47 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO48 [ + PRIO48 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO49 [ + PRIO49 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO50 [ + PRIO50 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO51 [ + PRIO51 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO52 [ + PRIO52 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO53 [ + PRIO53 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO54 [ + PRIO54 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO55 [ + PRIO55 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO56 [ + PRIO56 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO57 [ + PRIO57 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO58 [ + PRIO58 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO59 [ + PRIO59 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO60 [ + PRIO60 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO61 [ + PRIO61 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO62 [ + PRIO62 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO63 [ + PRIO63 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO64 [ + PRIO64 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO65 [ + PRIO65 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO66 [ + PRIO66 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO67 [ + PRIO67 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO68 [ + PRIO68 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO69 [ + PRIO69 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO70 [ + PRIO70 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO71 [ + PRIO71 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO72 [ + PRIO72 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO73 [ + PRIO73 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO74 [ + PRIO74 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO75 [ + PRIO75 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO76 [ + PRIO76 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO77 [ + PRIO77 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO78 [ + PRIO78 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO79 [ + PRIO79 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO80 [ + PRIO80 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO81 [ + PRIO81 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO82 [ + PRIO82 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO83 [ + PRIO83 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO84 [ + PRIO84 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO85 [ + PRIO85 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO86 [ + PRIO86 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO87 [ + PRIO87 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO88 [ + PRIO88 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO89 [ + PRIO89 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO90 [ + PRIO90 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO91 [ + PRIO91 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO92 [ + PRIO92 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO93 [ + PRIO93 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO94 [ + PRIO94 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO95 [ + PRIO95 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO96 [ + PRIO96 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO97 [ + PRIO97 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO98 [ + PRIO98 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO99 [ + PRIO99 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO100 [ + PRIO100 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO101 [ + PRIO101 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO102 [ + PRIO102 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO103 [ + PRIO103 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO104 [ + PRIO104 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO105 [ + PRIO105 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO106 [ + PRIO106 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO107 [ + PRIO107 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO108 [ + PRIO108 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO109 [ + PRIO109 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO110 [ + PRIO110 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO111 [ + PRIO111 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO112 [ + PRIO112 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO113 [ + PRIO113 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO114 [ + PRIO114 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO115 [ + PRIO115 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO116 [ + PRIO116 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO117 [ + PRIO117 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO118 [ + PRIO118 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO119 [ + PRIO119 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO120 [ + PRIO120 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO121 [ + PRIO121 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO122 [ + PRIO122 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO123 [ + PRIO123 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO124 [ + PRIO124 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO125 [ + PRIO125 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO126 [ + PRIO126 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO127 [ + PRIO127 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO128 [ + PRIO128 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO129 [ + PRIO129 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO130 [ + PRIO130 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO131 [ + PRIO131 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO132 [ + PRIO132 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO133 [ + PRIO133 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO134 [ + PRIO134 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO135 [ + PRIO135 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO136 [ + PRIO136 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO137 [ + PRIO137 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO138 [ + PRIO138 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO139 [ + PRIO139 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO140 [ + PRIO140 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO141 [ + PRIO141 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO142 [ + PRIO142 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO143 [ + PRIO143 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO144 [ + PRIO144 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO145 [ + PRIO145 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO146 [ + PRIO146 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO147 [ + PRIO147 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO148 [ + PRIO148 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO149 [ + PRIO149 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO150 [ + PRIO150 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO151 [ + PRIO151 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO152 [ + PRIO152 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO153 [ + PRIO153 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO154 [ + PRIO154 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO155 [ + PRIO155 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO156 [ + PRIO156 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO157 [ + PRIO157 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO158 [ + PRIO158 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO159 [ + PRIO159 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO160 [ + PRIO160 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO161 [ + PRIO161 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO162 [ + PRIO162 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO163 [ + PRIO163 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO164 [ + PRIO164 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO165 [ + PRIO165 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO166 [ + PRIO166 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO167 [ + PRIO167 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO168 [ + PRIO168 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO169 [ + PRIO169 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO170 [ + PRIO170 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO171 [ + PRIO171 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO172 [ + PRIO172 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO173 [ + PRIO173 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO174 [ + PRIO174 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO175 [ + PRIO175 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO176 [ + PRIO176 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO177 [ + PRIO177 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO178 [ + PRIO178 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO179 [ + PRIO179 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO180 [ + PRIO180 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO181 [ + PRIO181 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO182 [ + PRIO182 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO183 [ + PRIO183 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) PRIO184 [ + PRIO184 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) IP [ + P_0 OFFSET(0) NUMBITS(1) [], + P_1 OFFSET(1) NUMBITS(1) [], + P_2 OFFSET(2) NUMBITS(1) [], + P_3 OFFSET(3) NUMBITS(1) [], + P_4 OFFSET(4) NUMBITS(1) [], + P_5 OFFSET(5) NUMBITS(1) [], + P_6 OFFSET(6) NUMBITS(1) [], + P_7 OFFSET(7) NUMBITS(1) [], + P_8 OFFSET(8) NUMBITS(1) [], + P_9 OFFSET(9) NUMBITS(1) [], + P_10 OFFSET(10) NUMBITS(1) [], + P_11 OFFSET(11) NUMBITS(1) [], + P_12 OFFSET(12) NUMBITS(1) [], + P_13 OFFSET(13) NUMBITS(1) [], + P_14 OFFSET(14) NUMBITS(1) [], + P_15 OFFSET(15) NUMBITS(1) [], + P_16 OFFSET(16) NUMBITS(1) [], + P_17 OFFSET(17) NUMBITS(1) [], + P_18 OFFSET(18) NUMBITS(1) [], + P_19 OFFSET(19) NUMBITS(1) [], + P_20 OFFSET(20) NUMBITS(1) [], + P_21 OFFSET(21) NUMBITS(1) [], + P_22 OFFSET(22) NUMBITS(1) [], + P_23 OFFSET(23) NUMBITS(1) [], + P_24 OFFSET(24) NUMBITS(1) [], + P_25 OFFSET(25) NUMBITS(1) [], + P_26 OFFSET(26) NUMBITS(1) [], + P_27 OFFSET(27) NUMBITS(1) [], + P_28 OFFSET(28) NUMBITS(1) [], + P_29 OFFSET(29) NUMBITS(1) [], + P_30 OFFSET(30) NUMBITS(1) [], + P_31 OFFSET(31) NUMBITS(1) [], + ], + pub(crate) IE0 [ + E_0 OFFSET(0) NUMBITS(1) [], + E_1 OFFSET(1) NUMBITS(1) [], + E_2 OFFSET(2) NUMBITS(1) [], + E_3 OFFSET(3) NUMBITS(1) [], + E_4 OFFSET(4) NUMBITS(1) [], + E_5 OFFSET(5) NUMBITS(1) [], + E_6 OFFSET(6) NUMBITS(1) [], + E_7 OFFSET(7) NUMBITS(1) [], + E_8 OFFSET(8) NUMBITS(1) [], + E_9 OFFSET(9) NUMBITS(1) [], + E_10 OFFSET(10) NUMBITS(1) [], + E_11 OFFSET(11) NUMBITS(1) [], + E_12 OFFSET(12) NUMBITS(1) [], + E_13 OFFSET(13) NUMBITS(1) [], + E_14 OFFSET(14) NUMBITS(1) [], + E_15 OFFSET(15) NUMBITS(1) [], + E_16 OFFSET(16) NUMBITS(1) [], + E_17 OFFSET(17) NUMBITS(1) [], + E_18 OFFSET(18) NUMBITS(1) [], + E_19 OFFSET(19) NUMBITS(1) [], + E_20 OFFSET(20) NUMBITS(1) [], + E_21 OFFSET(21) NUMBITS(1) [], + E_22 OFFSET(22) NUMBITS(1) [], + E_23 OFFSET(23) NUMBITS(1) [], + E_24 OFFSET(24) NUMBITS(1) [], + E_25 OFFSET(25) NUMBITS(1) [], + E_26 OFFSET(26) NUMBITS(1) [], + E_27 OFFSET(27) NUMBITS(1) [], + E_28 OFFSET(28) NUMBITS(1) [], + E_29 OFFSET(29) NUMBITS(1) [], + E_30 OFFSET(30) NUMBITS(1) [], + E_31 OFFSET(31) NUMBITS(1) [], + ], + pub(crate) THRESHOLD0 [ + THRESHOLD0 OFFSET(0) NUMBITS(2) [], + ], + pub(crate) CC0 [ + CC0 OFFSET(0) NUMBITS(8) [], + ], + pub(crate) MSIP0 [ + MSIP0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + FATAL_FAULT OFFSET(0) NUMBITS(1) [], + ], +]; + +// End generated register constants for rv_plic diff --git a/chips/earlgrey/src/registers/sensor_ctrl_regs.rs b/chips/earlgrey/src/registers/sensor_ctrl_regs.rs new file mode 100644 index 0000000000..76f69a5399 --- /dev/null +++ b/chips/earlgrey/src/registers/sensor_ctrl_regs.rs @@ -0,0 +1,123 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for sensor_ctrl. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/top_earlgrey/ip/sensor_ctrl/data/sensor_ctrl.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of alert events from ast +pub const SENSOR_CTRL_PARAM_NUM_ALERT_EVENTS: u32 = 11; +/// Number of local events +pub const SENSOR_CTRL_PARAM_NUM_LOCAL_EVENTS: u32 = 1; +/// Number of alerts sent from sensor control +pub const SENSOR_CTRL_PARAM_NUM_ALERTS: u32 = 2; +/// Number of IO rails +pub const SENSOR_CTRL_PARAM_NUM_IO_RAILS: u32 = 2; +/// Register width +pub const SENSOR_CTRL_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub SensorCtrlRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// Controls the configurability of !!FATAL_ALERT_EN register. + (0x0010 => pub(crate) cfg_regwen: ReadWrite), + /// Alert trigger test + (0x0014 => pub(crate) alert_trig: [ReadWrite; 1]), + /// Each bit marks a corresponding alert as fatal or recoverable. + (0x0018 => pub(crate) fatal_alert_en: [ReadWrite; 1]), + /// Each bit represents a recoverable alert that has been triggered by AST. + (0x001c => pub(crate) recov_alert: [ReadWrite; 1]), + /// Each bit represents a fatal alert that has been triggered by AST. + (0x0020 => pub(crate) fatal_alert: [ReadWrite; 1]), + /// Status readback for ast + (0x0024 => pub(crate) status: ReadWrite), + (0x0028 => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + IO_STATUS_CHANGE OFFSET(0) NUMBITS(1) [], + INIT_STATUS_CHANGE OFFSET(1) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + RECOV_ALERT OFFSET(0) NUMBITS(1) [], + FATAL_ALERT OFFSET(1) NUMBITS(1) [], + ], + pub(crate) CFG_REGWEN [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) ALERT_TRIG [ + VAL_0 OFFSET(0) NUMBITS(1) [], + VAL_1 OFFSET(1) NUMBITS(1) [], + VAL_2 OFFSET(2) NUMBITS(1) [], + VAL_3 OFFSET(3) NUMBITS(1) [], + VAL_4 OFFSET(4) NUMBITS(1) [], + VAL_5 OFFSET(5) NUMBITS(1) [], + VAL_6 OFFSET(6) NUMBITS(1) [], + VAL_7 OFFSET(7) NUMBITS(1) [], + VAL_8 OFFSET(8) NUMBITS(1) [], + VAL_9 OFFSET(9) NUMBITS(1) [], + VAL_10 OFFSET(10) NUMBITS(1) [], + ], + pub(crate) FATAL_ALERT_EN [ + VAL_0 OFFSET(0) NUMBITS(1) [], + VAL_1 OFFSET(1) NUMBITS(1) [], + VAL_2 OFFSET(2) NUMBITS(1) [], + VAL_3 OFFSET(3) NUMBITS(1) [], + VAL_4 OFFSET(4) NUMBITS(1) [], + VAL_5 OFFSET(5) NUMBITS(1) [], + VAL_6 OFFSET(6) NUMBITS(1) [], + VAL_7 OFFSET(7) NUMBITS(1) [], + VAL_8 OFFSET(8) NUMBITS(1) [], + VAL_9 OFFSET(9) NUMBITS(1) [], + VAL_10 OFFSET(10) NUMBITS(1) [], + ], + pub(crate) RECOV_ALERT [ + VAL_0 OFFSET(0) NUMBITS(1) [], + VAL_1 OFFSET(1) NUMBITS(1) [], + VAL_2 OFFSET(2) NUMBITS(1) [], + VAL_3 OFFSET(3) NUMBITS(1) [], + VAL_4 OFFSET(4) NUMBITS(1) [], + VAL_5 OFFSET(5) NUMBITS(1) [], + VAL_6 OFFSET(6) NUMBITS(1) [], + VAL_7 OFFSET(7) NUMBITS(1) [], + VAL_8 OFFSET(8) NUMBITS(1) [], + VAL_9 OFFSET(9) NUMBITS(1) [], + VAL_10 OFFSET(10) NUMBITS(1) [], + ], + pub(crate) FATAL_ALERT [ + VAL_0 OFFSET(0) NUMBITS(1) [], + VAL_1 OFFSET(1) NUMBITS(1) [], + VAL_2 OFFSET(2) NUMBITS(1) [], + VAL_3 OFFSET(3) NUMBITS(1) [], + VAL_4 OFFSET(4) NUMBITS(1) [], + VAL_5 OFFSET(5) NUMBITS(1) [], + VAL_6 OFFSET(6) NUMBITS(1) [], + VAL_7 OFFSET(7) NUMBITS(1) [], + VAL_8 OFFSET(8) NUMBITS(1) [], + VAL_9 OFFSET(9) NUMBITS(1) [], + VAL_10 OFFSET(10) NUMBITS(1) [], + VAL_11 OFFSET(11) NUMBITS(1) [], + ], + pub(crate) STATUS [ + AST_INIT_DONE OFFSET(0) NUMBITS(1) [], + IO_POK OFFSET(1) NUMBITS(2) [], + ], +]; + +// End generated register constants for sensor_ctrl diff --git a/chips/earlgrey/src/registers/top_earlgrey.rs b/chips/earlgrey/src/registers/top_earlgrey.rs new file mode 100644 index 0000000000..4ec1a25bc1 --- /dev/null +++ b/chips/earlgrey/src/registers/top_earlgrey.rs @@ -0,0 +1,3254 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:18:57.529279 + +// This file was generated automatically. +// Please do not modify content of this file directly. +// File generated by using template: "toplevel.rs.tpl" +// To regenerate this file follow OpenTitan topgen documentations. + +#![allow(dead_code)] + +//! This file contains enums and consts for use within the Rust codebase. +//! +//! These definitions are for information that depends on the top-specific chip +//! configuration, which includes: +//! - Device Memory Information (for Peripherals and Memory) +//! - PLIC Interrupt ID Names and Source Mappings +//! - Alert ID Names and Source Mappings +//! - Pinmux Pin/Select Names +//! - Power Manager Wakeups + +/// Peripheral base address for uart0 in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const UART0_BASE_ADDR: usize = 0x40000000; + +/// Peripheral size for uart0 in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #UART0_BASE_ADDR and +/// `UART0_BASE_ADDR + UART0_SIZE_BYTES`. +pub const UART0_SIZE_BYTES: usize = 0x40; + +/// Peripheral base address for uart1 in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const UART1_BASE_ADDR: usize = 0x40010000; + +/// Peripheral size for uart1 in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #UART1_BASE_ADDR and +/// `UART1_BASE_ADDR + UART1_SIZE_BYTES`. +pub const UART1_SIZE_BYTES: usize = 0x40; + +/// Peripheral base address for uart2 in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const UART2_BASE_ADDR: usize = 0x40020000; + +/// Peripheral size for uart2 in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #UART2_BASE_ADDR and +/// `UART2_BASE_ADDR + UART2_SIZE_BYTES`. +pub const UART2_SIZE_BYTES: usize = 0x40; + +/// Peripheral base address for uart3 in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const UART3_BASE_ADDR: usize = 0x40030000; + +/// Peripheral size for uart3 in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #UART3_BASE_ADDR and +/// `UART3_BASE_ADDR + UART3_SIZE_BYTES`. +pub const UART3_SIZE_BYTES: usize = 0x40; + +/// Peripheral base address for gpio in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const GPIO_BASE_ADDR: usize = 0x40040000; + +/// Peripheral size for gpio in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #GPIO_BASE_ADDR and +/// `GPIO_BASE_ADDR + GPIO_SIZE_BYTES`. +pub const GPIO_SIZE_BYTES: usize = 0x40; + +/// Peripheral base address for spi_device in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const SPI_DEVICE_BASE_ADDR: usize = 0x40050000; + +/// Peripheral size for spi_device in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #SPI_DEVICE_BASE_ADDR and +/// `SPI_DEVICE_BASE_ADDR + SPI_DEVICE_SIZE_BYTES`. +pub const SPI_DEVICE_SIZE_BYTES: usize = 0x2000; + +/// Peripheral base address for i2c0 in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const I2C0_BASE_ADDR: usize = 0x40080000; + +/// Peripheral size for i2c0 in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #I2C0_BASE_ADDR and +/// `I2C0_BASE_ADDR + I2C0_SIZE_BYTES`. +pub const I2C0_SIZE_BYTES: usize = 0x80; + +/// Peripheral base address for i2c1 in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const I2C1_BASE_ADDR: usize = 0x40090000; + +/// Peripheral size for i2c1 in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #I2C1_BASE_ADDR and +/// `I2C1_BASE_ADDR + I2C1_SIZE_BYTES`. +pub const I2C1_SIZE_BYTES: usize = 0x80; + +/// Peripheral base address for i2c2 in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const I2C2_BASE_ADDR: usize = 0x400A0000; + +/// Peripheral size for i2c2 in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #I2C2_BASE_ADDR and +/// `I2C2_BASE_ADDR + I2C2_SIZE_BYTES`. +pub const I2C2_SIZE_BYTES: usize = 0x80; + +/// Peripheral base address for pattgen in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const PATTGEN_BASE_ADDR: usize = 0x400E0000; + +/// Peripheral size for pattgen in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #PATTGEN_BASE_ADDR and +/// `PATTGEN_BASE_ADDR + PATTGEN_SIZE_BYTES`. +pub const PATTGEN_SIZE_BYTES: usize = 0x40; + +/// Peripheral base address for rv_timer in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const RV_TIMER_BASE_ADDR: usize = 0x40100000; + +/// Peripheral size for rv_timer in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #RV_TIMER_BASE_ADDR and +/// `RV_TIMER_BASE_ADDR + RV_TIMER_SIZE_BYTES`. +pub const RV_TIMER_SIZE_BYTES: usize = 0x200; + +/// Peripheral base address for core device on otp_ctrl in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const OTP_CTRL_CORE_BASE_ADDR: usize = 0x40130000; + +/// Peripheral size for core device on otp_ctrl in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #OTP_CTRL_CORE_BASE_ADDR and +/// `OTP_CTRL_CORE_BASE_ADDR + OTP_CTRL_CORE_SIZE_BYTES`. +pub const OTP_CTRL_CORE_SIZE_BYTES: usize = 0x2000; + +/// Peripheral base address for prim device on otp_ctrl in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const OTP_CTRL_PRIM_BASE_ADDR: usize = 0x40132000; + +/// Peripheral size for prim device on otp_ctrl in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #OTP_CTRL_PRIM_BASE_ADDR and +/// `OTP_CTRL_PRIM_BASE_ADDR + OTP_CTRL_PRIM_SIZE_BYTES`. +pub const OTP_CTRL_PRIM_SIZE_BYTES: usize = 0x20; + +/// Peripheral base address for lc_ctrl in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const LC_CTRL_BASE_ADDR: usize = 0x40140000; + +/// Peripheral size for lc_ctrl in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #LC_CTRL_BASE_ADDR and +/// `LC_CTRL_BASE_ADDR + LC_CTRL_SIZE_BYTES`. +pub const LC_CTRL_SIZE_BYTES: usize = 0x100; + +/// Peripheral base address for alert_handler in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const ALERT_HANDLER_BASE_ADDR: usize = 0x40150000; + +/// Peripheral size for alert_handler in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #ALERT_HANDLER_BASE_ADDR and +/// `ALERT_HANDLER_BASE_ADDR + ALERT_HANDLER_SIZE_BYTES`. +pub const ALERT_HANDLER_SIZE_BYTES: usize = 0x800; + +/// Peripheral base address for spi_host0 in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const SPI_HOST0_BASE_ADDR: usize = 0x40300000; + +/// Peripheral size for spi_host0 in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #SPI_HOST0_BASE_ADDR and +/// `SPI_HOST0_BASE_ADDR + SPI_HOST0_SIZE_BYTES`. +pub const SPI_HOST0_SIZE_BYTES: usize = 0x40; + +/// Peripheral base address for spi_host1 in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const SPI_HOST1_BASE_ADDR: usize = 0x40310000; + +/// Peripheral size for spi_host1 in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #SPI_HOST1_BASE_ADDR and +/// `SPI_HOST1_BASE_ADDR + SPI_HOST1_SIZE_BYTES`. +pub const SPI_HOST1_SIZE_BYTES: usize = 0x40; + +/// Peripheral base address for usbdev in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const USBDEV_BASE_ADDR: usize = 0x40320000; + +/// Peripheral size for usbdev in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #USBDEV_BASE_ADDR and +/// `USBDEV_BASE_ADDR + USBDEV_SIZE_BYTES`. +pub const USBDEV_SIZE_BYTES: usize = 0x1000; + +/// Peripheral base address for pwrmgr_aon in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const PWRMGR_AON_BASE_ADDR: usize = 0x40400000; + +/// Peripheral size for pwrmgr_aon in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #PWRMGR_AON_BASE_ADDR and +/// `PWRMGR_AON_BASE_ADDR + PWRMGR_AON_SIZE_BYTES`. +pub const PWRMGR_AON_SIZE_BYTES: usize = 0x80; + +/// Peripheral base address for rstmgr_aon in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const RSTMGR_AON_BASE_ADDR: usize = 0x40410000; + +/// Peripheral size for rstmgr_aon in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #RSTMGR_AON_BASE_ADDR and +/// `RSTMGR_AON_BASE_ADDR + RSTMGR_AON_SIZE_BYTES`. +pub const RSTMGR_AON_SIZE_BYTES: usize = 0x80; + +/// Peripheral base address for clkmgr_aon in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const CLKMGR_AON_BASE_ADDR: usize = 0x40420000; + +/// Peripheral size for clkmgr_aon in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #CLKMGR_AON_BASE_ADDR and +/// `CLKMGR_AON_BASE_ADDR + CLKMGR_AON_SIZE_BYTES`. +pub const CLKMGR_AON_SIZE_BYTES: usize = 0x80; + +/// Peripheral base address for sysrst_ctrl_aon in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const SYSRST_CTRL_AON_BASE_ADDR: usize = 0x40430000; + +/// Peripheral size for sysrst_ctrl_aon in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #SYSRST_CTRL_AON_BASE_ADDR and +/// `SYSRST_CTRL_AON_BASE_ADDR + SYSRST_CTRL_AON_SIZE_BYTES`. +pub const SYSRST_CTRL_AON_SIZE_BYTES: usize = 0x100; + +/// Peripheral base address for adc_ctrl_aon in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const ADC_CTRL_AON_BASE_ADDR: usize = 0x40440000; + +/// Peripheral size for adc_ctrl_aon in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #ADC_CTRL_AON_BASE_ADDR and +/// `ADC_CTRL_AON_BASE_ADDR + ADC_CTRL_AON_SIZE_BYTES`. +pub const ADC_CTRL_AON_SIZE_BYTES: usize = 0x80; + +/// Peripheral base address for pwm_aon in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const PWM_AON_BASE_ADDR: usize = 0x40450000; + +/// Peripheral size for pwm_aon in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #PWM_AON_BASE_ADDR and +/// `PWM_AON_BASE_ADDR + PWM_AON_SIZE_BYTES`. +pub const PWM_AON_SIZE_BYTES: usize = 0x80; + +/// Peripheral base address for pinmux_aon in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const PINMUX_AON_BASE_ADDR: usize = 0x40460000; + +/// Peripheral size for pinmux_aon in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #PINMUX_AON_BASE_ADDR and +/// `PINMUX_AON_BASE_ADDR + PINMUX_AON_SIZE_BYTES`. +pub const PINMUX_AON_SIZE_BYTES: usize = 0x1000; + +/// Peripheral base address for aon_timer_aon in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const AON_TIMER_AON_BASE_ADDR: usize = 0x40470000; + +/// Peripheral size for aon_timer_aon in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #AON_TIMER_AON_BASE_ADDR and +/// `AON_TIMER_AON_BASE_ADDR + AON_TIMER_AON_SIZE_BYTES`. +pub const AON_TIMER_AON_SIZE_BYTES: usize = 0x40; + +/// Peripheral base address for ast in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const AST_BASE_ADDR: usize = 0x40480000; + +/// Peripheral size for ast in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #AST_BASE_ADDR and +/// `AST_BASE_ADDR + AST_SIZE_BYTES`. +pub const AST_SIZE_BYTES: usize = 0x400; + +/// Peripheral base address for sensor_ctrl in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const SENSOR_CTRL_BASE_ADDR: usize = 0x40490000; + +/// Peripheral size for sensor_ctrl in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #SENSOR_CTRL_BASE_ADDR and +/// `SENSOR_CTRL_BASE_ADDR + SENSOR_CTRL_SIZE_BYTES`. +pub const SENSOR_CTRL_SIZE_BYTES: usize = 0x40; + +/// Peripheral base address for regs device on sram_ctrl_ret_aon in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const SRAM_CTRL_RET_AON_REGS_BASE_ADDR: usize = 0x40500000; + +/// Peripheral size for regs device on sram_ctrl_ret_aon in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #SRAM_CTRL_RET_AON_REGS_BASE_ADDR and +/// `SRAM_CTRL_RET_AON_REGS_BASE_ADDR + SRAM_CTRL_RET_AON_REGS_SIZE_BYTES`. +pub const SRAM_CTRL_RET_AON_REGS_SIZE_BYTES: usize = 0x20; + +/// Peripheral base address for ram device on sram_ctrl_ret_aon in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const SRAM_CTRL_RET_AON_RAM_BASE_ADDR: usize = 0x40600000; + +/// Peripheral size for ram device on sram_ctrl_ret_aon in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #SRAM_CTRL_RET_AON_RAM_BASE_ADDR and +/// `SRAM_CTRL_RET_AON_RAM_BASE_ADDR + SRAM_CTRL_RET_AON_RAM_SIZE_BYTES`. +pub const SRAM_CTRL_RET_AON_RAM_SIZE_BYTES: usize = 0x1000; + +/// Peripheral base address for core device on flash_ctrl in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const FLASH_CTRL_CORE_BASE_ADDR: usize = 0x41000000; + +/// Peripheral size for core device on flash_ctrl in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #FLASH_CTRL_CORE_BASE_ADDR and +/// `FLASH_CTRL_CORE_BASE_ADDR + FLASH_CTRL_CORE_SIZE_BYTES`. +pub const FLASH_CTRL_CORE_SIZE_BYTES: usize = 0x200; + +/// Peripheral base address for prim device on flash_ctrl in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const FLASH_CTRL_PRIM_BASE_ADDR: usize = 0x41008000; + +/// Peripheral size for prim device on flash_ctrl in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #FLASH_CTRL_PRIM_BASE_ADDR and +/// `FLASH_CTRL_PRIM_BASE_ADDR + FLASH_CTRL_PRIM_SIZE_BYTES`. +pub const FLASH_CTRL_PRIM_SIZE_BYTES: usize = 0x80; + +/// Peripheral base address for mem device on flash_ctrl in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const FLASH_CTRL_MEM_BASE_ADDR: usize = 0x20000000; + +/// Peripheral size for mem device on flash_ctrl in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #FLASH_CTRL_MEM_BASE_ADDR and +/// `FLASH_CTRL_MEM_BASE_ADDR + FLASH_CTRL_MEM_SIZE_BYTES`. +pub const FLASH_CTRL_MEM_SIZE_BYTES: usize = 0x100000; + +/// Peripheral base address for regs device on rv_dm in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const RV_DM_REGS_BASE_ADDR: usize = 0x41200000; + +/// Peripheral size for regs device on rv_dm in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #RV_DM_REGS_BASE_ADDR and +/// `RV_DM_REGS_BASE_ADDR + RV_DM_REGS_SIZE_BYTES`. +pub const RV_DM_REGS_SIZE_BYTES: usize = 0x4; + +/// Peripheral base address for mem device on rv_dm in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const RV_DM_MEM_BASE_ADDR: usize = 0x10000; + +/// Peripheral size for mem device on rv_dm in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #RV_DM_MEM_BASE_ADDR and +/// `RV_DM_MEM_BASE_ADDR + RV_DM_MEM_SIZE_BYTES`. +pub const RV_DM_MEM_SIZE_BYTES: usize = 0x1000; + +/// Peripheral base address for rv_plic in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const RV_PLIC_BASE_ADDR: usize = 0x48000000; + +/// Peripheral size for rv_plic in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #RV_PLIC_BASE_ADDR and +/// `RV_PLIC_BASE_ADDR + RV_PLIC_SIZE_BYTES`. +pub const RV_PLIC_SIZE_BYTES: usize = 0x8000000; + +/// Peripheral base address for aes in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const AES_BASE_ADDR: usize = 0x41100000; + +/// Peripheral size for aes in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #AES_BASE_ADDR and +/// `AES_BASE_ADDR + AES_SIZE_BYTES`. +pub const AES_SIZE_BYTES: usize = 0x100; + +/// Peripheral base address for hmac in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const HMAC_BASE_ADDR: usize = 0x41110000; + +/// Peripheral size for hmac in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #HMAC_BASE_ADDR and +/// `HMAC_BASE_ADDR + HMAC_SIZE_BYTES`. +pub const HMAC_SIZE_BYTES: usize = 0x1000; + +/// Peripheral base address for kmac in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const KMAC_BASE_ADDR: usize = 0x41120000; + +/// Peripheral size for kmac in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #KMAC_BASE_ADDR and +/// `KMAC_BASE_ADDR + KMAC_SIZE_BYTES`. +pub const KMAC_SIZE_BYTES: usize = 0x1000; + +/// Peripheral base address for otbn in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const OTBN_BASE_ADDR: usize = 0x41130000; + +/// Peripheral size for otbn in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #OTBN_BASE_ADDR and +/// `OTBN_BASE_ADDR + OTBN_SIZE_BYTES`. +pub const OTBN_SIZE_BYTES: usize = 0x10000; + +/// Peripheral base address for keymgr in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const KEYMGR_BASE_ADDR: usize = 0x41140000; + +/// Peripheral size for keymgr in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #KEYMGR_BASE_ADDR and +/// `KEYMGR_BASE_ADDR + KEYMGR_SIZE_BYTES`. +pub const KEYMGR_SIZE_BYTES: usize = 0x100; + +/// Peripheral base address for csrng in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const CSRNG_BASE_ADDR: usize = 0x41150000; + +/// Peripheral size for csrng in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #CSRNG_BASE_ADDR and +/// `CSRNG_BASE_ADDR + CSRNG_SIZE_BYTES`. +pub const CSRNG_SIZE_BYTES: usize = 0x80; + +/// Peripheral base address for entropy_src in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const ENTROPY_SRC_BASE_ADDR: usize = 0x41160000; + +/// Peripheral size for entropy_src in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #ENTROPY_SRC_BASE_ADDR and +/// `ENTROPY_SRC_BASE_ADDR + ENTROPY_SRC_SIZE_BYTES`. +pub const ENTROPY_SRC_SIZE_BYTES: usize = 0x100; + +/// Peripheral base address for edn0 in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const EDN0_BASE_ADDR: usize = 0x41170000; + +/// Peripheral size for edn0 in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #EDN0_BASE_ADDR and +/// `EDN0_BASE_ADDR + EDN0_SIZE_BYTES`. +pub const EDN0_SIZE_BYTES: usize = 0x80; + +/// Peripheral base address for edn1 in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const EDN1_BASE_ADDR: usize = 0x41180000; + +/// Peripheral size for edn1 in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #EDN1_BASE_ADDR and +/// `EDN1_BASE_ADDR + EDN1_SIZE_BYTES`. +pub const EDN1_SIZE_BYTES: usize = 0x80; + +/// Peripheral base address for regs device on sram_ctrl_main in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const SRAM_CTRL_MAIN_REGS_BASE_ADDR: usize = 0x411C0000; + +/// Peripheral size for regs device on sram_ctrl_main in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #SRAM_CTRL_MAIN_REGS_BASE_ADDR and +/// `SRAM_CTRL_MAIN_REGS_BASE_ADDR + SRAM_CTRL_MAIN_REGS_SIZE_BYTES`. +pub const SRAM_CTRL_MAIN_REGS_SIZE_BYTES: usize = 0x20; + +/// Peripheral base address for ram device on sram_ctrl_main in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const SRAM_CTRL_MAIN_RAM_BASE_ADDR: usize = 0x10000000; + +/// Peripheral size for ram device on sram_ctrl_main in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #SRAM_CTRL_MAIN_RAM_BASE_ADDR and +/// `SRAM_CTRL_MAIN_RAM_BASE_ADDR + SRAM_CTRL_MAIN_RAM_SIZE_BYTES`. +pub const SRAM_CTRL_MAIN_RAM_SIZE_BYTES: usize = 0x20000; + +/// Peripheral base address for regs device on rom_ctrl in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const ROM_CTRL_REGS_BASE_ADDR: usize = 0x411E0000; + +/// Peripheral size for regs device on rom_ctrl in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #ROM_CTRL_REGS_BASE_ADDR and +/// `ROM_CTRL_REGS_BASE_ADDR + ROM_CTRL_REGS_SIZE_BYTES`. +pub const ROM_CTRL_REGS_SIZE_BYTES: usize = 0x80; + +/// Peripheral base address for rom device on rom_ctrl in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const ROM_CTRL_ROM_BASE_ADDR: usize = 0x8000; + +/// Peripheral size for rom device on rom_ctrl in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #ROM_CTRL_ROM_BASE_ADDR and +/// `ROM_CTRL_ROM_BASE_ADDR + ROM_CTRL_ROM_SIZE_BYTES`. +pub const ROM_CTRL_ROM_SIZE_BYTES: usize = 0x8000; + +/// Peripheral base address for cfg device on rv_core_ibex in top earlgrey. +/// +/// This should be used with #mmio_region_from_addr to access the memory-mapped +/// registers associated with the peripheral (usually via a DIF). +pub const RV_CORE_IBEX_CFG_BASE_ADDR: usize = 0x411F0000; + +/// Peripheral size for cfg device on rv_core_ibex in top earlgrey. +/// +/// This is the size (in bytes) of the peripheral's reserved memory area. All +/// memory-mapped registers associated with this peripheral should have an +/// address between #RV_CORE_IBEX_CFG_BASE_ADDR and +/// `RV_CORE_IBEX_CFG_BASE_ADDR + RV_CORE_IBEX_CFG_SIZE_BYTES`. +pub const RV_CORE_IBEX_CFG_SIZE_BYTES: usize = 0x100; + +/// Memory base address for ram_ret_aon in top earlgrey. +pub const RAM_RET_AON_BASE_ADDR: usize = 0x40600000; + +/// Memory size for ram_ret_aon in top earlgrey. +pub const RAM_RET_AON_SIZE_BYTES: usize = 0x1000; + +/// Memory base address for eflash in top earlgrey. +pub const EFLASH_BASE_ADDR: usize = 0x20000000; + +/// Memory size for eflash in top earlgrey. +pub const EFLASH_SIZE_BYTES: usize = 0x100000; + +/// Memory base address for ram_main in top earlgrey. +pub const RAM_MAIN_BASE_ADDR: usize = 0x10000000; + +/// Memory size for ram_main in top earlgrey. +pub const RAM_MAIN_SIZE_BYTES: usize = 0x20000; + +/// Memory base address for rom in top earlgrey. +pub const ROM_BASE_ADDR: usize = 0x8000; + +/// Memory size for rom in top earlgrey. +pub const ROM_SIZE_BYTES: usize = 0x8000; + +/// PLIC Interrupt Source Peripheral. +/// +/// Enumeration used to determine which peripheral asserted the corresponding +/// interrupt. +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum PlicPeripheral { + /// Unknown Peripheral + Unknown = 0, + /// uart0 + Uart0 = 1, + /// uart1 + Uart1 = 2, + /// uart2 + Uart2 = 3, + /// uart3 + Uart3 = 4, + /// gpio + Gpio = 5, + /// spi_device + SpiDevice = 6, + /// i2c0 + I2c0 = 7, + /// i2c1 + I2c1 = 8, + /// i2c2 + I2c2 = 9, + /// pattgen + Pattgen = 10, + /// rv_timer + RvTimer = 11, + /// otp_ctrl + OtpCtrl = 12, + /// alert_handler + AlertHandler = 13, + /// spi_host0 + SpiHost0 = 14, + /// spi_host1 + SpiHost1 = 15, + /// usbdev + Usbdev = 16, + /// pwrmgr_aon + PwrmgrAon = 17, + /// sysrst_ctrl_aon + SysrstCtrlAon = 18, + /// adc_ctrl_aon + AdcCtrlAon = 19, + /// aon_timer_aon + AonTimerAon = 20, + /// sensor_ctrl + SensorCtrl = 21, + /// flash_ctrl + FlashCtrl = 22, + /// hmac + Hmac = 23, + /// kmac + Kmac = 24, + /// otbn + Otbn = 25, + /// keymgr + Keymgr = 26, + /// csrng + Csrng = 27, + /// entropy_src + EntropySrc = 28, + /// edn0 + Edn0 = 29, + /// edn1 + Edn1 = 30, +} + +impl TryFrom for PlicPeripheral { + type Error = u32; + fn try_from(val: u32) -> Result { + match val { + 0 => Ok(Self::Unknown), + 1 => Ok(Self::Uart0), + 2 => Ok(Self::Uart1), + 3 => Ok(Self::Uart2), + 4 => Ok(Self::Uart3), + 5 => Ok(Self::Gpio), + 6 => Ok(Self::SpiDevice), + 7 => Ok(Self::I2c0), + 8 => Ok(Self::I2c1), + 9 => Ok(Self::I2c2), + 10 => Ok(Self::Pattgen), + 11 => Ok(Self::RvTimer), + 12 => Ok(Self::OtpCtrl), + 13 => Ok(Self::AlertHandler), + 14 => Ok(Self::SpiHost0), + 15 => Ok(Self::SpiHost1), + 16 => Ok(Self::Usbdev), + 17 => Ok(Self::PwrmgrAon), + 18 => Ok(Self::SysrstCtrlAon), + 19 => Ok(Self::AdcCtrlAon), + 20 => Ok(Self::AonTimerAon), + 21 => Ok(Self::SensorCtrl), + 22 => Ok(Self::FlashCtrl), + 23 => Ok(Self::Hmac), + 24 => Ok(Self::Kmac), + 25 => Ok(Self::Otbn), + 26 => Ok(Self::Keymgr), + 27 => Ok(Self::Csrng), + 28 => Ok(Self::EntropySrc), + 29 => Ok(Self::Edn0), + 30 => Ok(Self::Edn1), + _ => Err(val), + } + } +} + +/// PLIC Interrupt Source. +/// +/// Enumeration of all PLIC interrupt sources. The interrupt sources belonging to +/// the same peripheral are guaranteed to be consecutive. +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum PlicIrqId { + /// No Interrupt + None = 0, + /// uart0_tx_watermark + Uart0TxWatermark = 1, + /// uart0_rx_watermark + Uart0RxWatermark = 2, + /// uart0_tx_empty + Uart0TxEmpty = 3, + /// uart0_rx_overflow + Uart0RxOverflow = 4, + /// uart0_rx_frame_err + Uart0RxFrameErr = 5, + /// uart0_rx_break_err + Uart0RxBreakErr = 6, + /// uart0_rx_timeout + Uart0RxTimeout = 7, + /// uart0_rx_parity_err + Uart0RxParityErr = 8, + /// uart1_tx_watermark + Uart1TxWatermark = 9, + /// uart1_rx_watermark + Uart1RxWatermark = 10, + /// uart1_tx_empty + Uart1TxEmpty = 11, + /// uart1_rx_overflow + Uart1RxOverflow = 12, + /// uart1_rx_frame_err + Uart1RxFrameErr = 13, + /// uart1_rx_break_err + Uart1RxBreakErr = 14, + /// uart1_rx_timeout + Uart1RxTimeout = 15, + /// uart1_rx_parity_err + Uart1RxParityErr = 16, + /// uart2_tx_watermark + Uart2TxWatermark = 17, + /// uart2_rx_watermark + Uart2RxWatermark = 18, + /// uart2_tx_empty + Uart2TxEmpty = 19, + /// uart2_rx_overflow + Uart2RxOverflow = 20, + /// uart2_rx_frame_err + Uart2RxFrameErr = 21, + /// uart2_rx_break_err + Uart2RxBreakErr = 22, + /// uart2_rx_timeout + Uart2RxTimeout = 23, + /// uart2_rx_parity_err + Uart2RxParityErr = 24, + /// uart3_tx_watermark + Uart3TxWatermark = 25, + /// uart3_rx_watermark + Uart3RxWatermark = 26, + /// uart3_tx_empty + Uart3TxEmpty = 27, + /// uart3_rx_overflow + Uart3RxOverflow = 28, + /// uart3_rx_frame_err + Uart3RxFrameErr = 29, + /// uart3_rx_break_err + Uart3RxBreakErr = 30, + /// uart3_rx_timeout + Uart3RxTimeout = 31, + /// uart3_rx_parity_err + Uart3RxParityErr = 32, + /// gpio_gpio 0 + GpioGpio0 = 33, + /// gpio_gpio 1 + GpioGpio1 = 34, + /// gpio_gpio 2 + GpioGpio2 = 35, + /// gpio_gpio 3 + GpioGpio3 = 36, + /// gpio_gpio 4 + GpioGpio4 = 37, + /// gpio_gpio 5 + GpioGpio5 = 38, + /// gpio_gpio 6 + GpioGpio6 = 39, + /// gpio_gpio 7 + GpioGpio7 = 40, + /// gpio_gpio 8 + GpioGpio8 = 41, + /// gpio_gpio 9 + GpioGpio9 = 42, + /// gpio_gpio 10 + GpioGpio10 = 43, + /// gpio_gpio 11 + GpioGpio11 = 44, + /// gpio_gpio 12 + GpioGpio12 = 45, + /// gpio_gpio 13 + GpioGpio13 = 46, + /// gpio_gpio 14 + GpioGpio14 = 47, + /// gpio_gpio 15 + GpioGpio15 = 48, + /// gpio_gpio 16 + GpioGpio16 = 49, + /// gpio_gpio 17 + GpioGpio17 = 50, + /// gpio_gpio 18 + GpioGpio18 = 51, + /// gpio_gpio 19 + GpioGpio19 = 52, + /// gpio_gpio 20 + GpioGpio20 = 53, + /// gpio_gpio 21 + GpioGpio21 = 54, + /// gpio_gpio 22 + GpioGpio22 = 55, + /// gpio_gpio 23 + GpioGpio23 = 56, + /// gpio_gpio 24 + GpioGpio24 = 57, + /// gpio_gpio 25 + GpioGpio25 = 58, + /// gpio_gpio 26 + GpioGpio26 = 59, + /// gpio_gpio 27 + GpioGpio27 = 60, + /// gpio_gpio 28 + GpioGpio28 = 61, + /// gpio_gpio 29 + GpioGpio29 = 62, + /// gpio_gpio 30 + GpioGpio30 = 63, + /// gpio_gpio 31 + GpioGpio31 = 64, + /// spi_device_generic_rx_full + SpiDeviceGenericRxFull = 65, + /// spi_device_generic_rx_watermark + SpiDeviceGenericRxWatermark = 66, + /// spi_device_generic_tx_watermark + SpiDeviceGenericTxWatermark = 67, + /// spi_device_generic_rx_error + SpiDeviceGenericRxError = 68, + /// spi_device_generic_rx_overflow + SpiDeviceGenericRxOverflow = 69, + /// spi_device_generic_tx_underflow + SpiDeviceGenericTxUnderflow = 70, + /// spi_device_upload_cmdfifo_not_empty + SpiDeviceUploadCmdfifoNotEmpty = 71, + /// spi_device_upload_payload_not_empty + SpiDeviceUploadPayloadNotEmpty = 72, + /// spi_device_upload_payload_overflow + SpiDeviceUploadPayloadOverflow = 73, + /// spi_device_readbuf_watermark + SpiDeviceReadbufWatermark = 74, + /// spi_device_readbuf_flip + SpiDeviceReadbufFlip = 75, + /// spi_device_tpm_header_not_empty + SpiDeviceTpmHeaderNotEmpty = 76, + /// i2c0_fmt_threshold + I2c0FmtThreshold = 77, + /// i2c0_rx_threshold + I2c0RxThreshold = 78, + /// i2c0_fmt_overflow + I2c0FmtOverflow = 79, + /// i2c0_rx_overflow + I2c0RxOverflow = 80, + /// i2c0_nak + I2c0Nak = 81, + /// i2c0_scl_interference + I2c0SclInterference = 82, + /// i2c0_sda_interference + I2c0SdaInterference = 83, + /// i2c0_stretch_timeout + I2c0StretchTimeout = 84, + /// i2c0_sda_unstable + I2c0SdaUnstable = 85, + /// i2c0_cmd_complete + I2c0CmdComplete = 86, + /// i2c0_tx_stretch + I2c0TxStretch = 87, + /// i2c0_tx_overflow + I2c0TxOverflow = 88, + /// i2c0_acq_full + I2c0AcqFull = 89, + /// i2c0_unexp_stop + I2c0UnexpStop = 90, + /// i2c0_host_timeout + I2c0HostTimeout = 91, + /// i2c1_fmt_threshold + I2c1FmtThreshold = 92, + /// i2c1_rx_threshold + I2c1RxThreshold = 93, + /// i2c1_fmt_overflow + I2c1FmtOverflow = 94, + /// i2c1_rx_overflow + I2c1RxOverflow = 95, + /// i2c1_nak + I2c1Nak = 96, + /// i2c1_scl_interference + I2c1SclInterference = 97, + /// i2c1_sda_interference + I2c1SdaInterference = 98, + /// i2c1_stretch_timeout + I2c1StretchTimeout = 99, + /// i2c1_sda_unstable + I2c1SdaUnstable = 100, + /// i2c1_cmd_complete + I2c1CmdComplete = 101, + /// i2c1_tx_stretch + I2c1TxStretch = 102, + /// i2c1_tx_overflow + I2c1TxOverflow = 103, + /// i2c1_acq_full + I2c1AcqFull = 104, + /// i2c1_unexp_stop + I2c1UnexpStop = 105, + /// i2c1_host_timeout + I2c1HostTimeout = 106, + /// i2c2_fmt_threshold + I2c2FmtThreshold = 107, + /// i2c2_rx_threshold + I2c2RxThreshold = 108, + /// i2c2_fmt_overflow + I2c2FmtOverflow = 109, + /// i2c2_rx_overflow + I2c2RxOverflow = 110, + /// i2c2_nak + I2c2Nak = 111, + /// i2c2_scl_interference + I2c2SclInterference = 112, + /// i2c2_sda_interference + I2c2SdaInterference = 113, + /// i2c2_stretch_timeout + I2c2StretchTimeout = 114, + /// i2c2_sda_unstable + I2c2SdaUnstable = 115, + /// i2c2_cmd_complete + I2c2CmdComplete = 116, + /// i2c2_tx_stretch + I2c2TxStretch = 117, + /// i2c2_tx_overflow + I2c2TxOverflow = 118, + /// i2c2_acq_full + I2c2AcqFull = 119, + /// i2c2_unexp_stop + I2c2UnexpStop = 120, + /// i2c2_host_timeout + I2c2HostTimeout = 121, + /// pattgen_done_ch0 + PattgenDoneCh0 = 122, + /// pattgen_done_ch1 + PattgenDoneCh1 = 123, + /// rv_timer_timer_expired_hart0_timer0 + RvTimerTimerExpiredHart0Timer0 = 124, + /// otp_ctrl_otp_operation_done + OtpCtrlOtpOperationDone = 125, + /// otp_ctrl_otp_error + OtpCtrlOtpError = 126, + /// alert_handler_classa + AlertHandlerClassa = 127, + /// alert_handler_classb + AlertHandlerClassb = 128, + /// alert_handler_classc + AlertHandlerClassc = 129, + /// alert_handler_classd + AlertHandlerClassd = 130, + /// spi_host0_error + SpiHost0Error = 131, + /// spi_host0_spi_event + SpiHost0SpiEvent = 132, + /// spi_host1_error + SpiHost1Error = 133, + /// spi_host1_spi_event + SpiHost1SpiEvent = 134, + /// usbdev_pkt_received + UsbdevPktReceived = 135, + /// usbdev_pkt_sent + UsbdevPktSent = 136, + /// usbdev_disconnected + UsbdevDisconnected = 137, + /// usbdev_host_lost + UsbdevHostLost = 138, + /// usbdev_link_reset + UsbdevLinkReset = 139, + /// usbdev_link_suspend + UsbdevLinkSuspend = 140, + /// usbdev_link_resume + UsbdevLinkResume = 141, + /// usbdev_av_empty + UsbdevAvEmpty = 142, + /// usbdev_rx_full + UsbdevRxFull = 143, + /// usbdev_av_overflow + UsbdevAvOverflow = 144, + /// usbdev_link_in_err + UsbdevLinkInErr = 145, + /// usbdev_rx_crc_err + UsbdevRxCrcErr = 146, + /// usbdev_rx_pid_err + UsbdevRxPidErr = 147, + /// usbdev_rx_bitstuff_err + UsbdevRxBitstuffErr = 148, + /// usbdev_frame + UsbdevFrame = 149, + /// usbdev_powered + UsbdevPowered = 150, + /// usbdev_link_out_err + UsbdevLinkOutErr = 151, + /// pwrmgr_aon_wakeup + PwrmgrAonWakeup = 152, + /// sysrst_ctrl_aon_event_detected + SysrstCtrlAonEventDetected = 153, + /// adc_ctrl_aon_match_done + AdcCtrlAonMatchDone = 154, + /// aon_timer_aon_wkup_timer_expired + AonTimerAonWkupTimerExpired = 155, + /// aon_timer_aon_wdog_timer_bark + AonTimerAonWdogTimerBark = 156, + /// sensor_ctrl_io_status_change + SensorCtrlIoStatusChange = 157, + /// sensor_ctrl_init_status_change + SensorCtrlInitStatusChange = 158, + /// flash_ctrl_prog_empty + FlashCtrlProgEmpty = 159, + /// flash_ctrl_prog_lvl + FlashCtrlProgLvl = 160, + /// flash_ctrl_rd_full + FlashCtrlRdFull = 161, + /// flash_ctrl_rd_lvl + FlashCtrlRdLvl = 162, + /// flash_ctrl_op_done + FlashCtrlOpDone = 163, + /// flash_ctrl_corr_err + FlashCtrlCorrErr = 164, + /// hmac_hmac_done + HmacHmacDone = 165, + /// hmac_fifo_empty + HmacFifoEmpty = 166, + /// hmac_hmac_err + HmacHmacErr = 167, + /// kmac_kmac_done + KmacKmacDone = 168, + /// kmac_fifo_empty + KmacFifoEmpty = 169, + /// kmac_kmac_err + KmacKmacErr = 170, + /// otbn_done + OtbnDone = 171, + /// keymgr_op_done + KeymgrOpDone = 172, + /// csrng_cs_cmd_req_done + CsrngCsCmdReqDone = 173, + /// csrng_cs_entropy_req + CsrngCsEntropyReq = 174, + /// csrng_cs_hw_inst_exc + CsrngCsHwInstExc = 175, + /// csrng_cs_fatal_err + CsrngCsFatalErr = 176, + /// entropy_src_es_entropy_valid + EntropySrcEsEntropyValid = 177, + /// entropy_src_es_health_test_failed + EntropySrcEsHealthTestFailed = 178, + /// entropy_src_es_observe_fifo_ready + EntropySrcEsObserveFifoReady = 179, + /// entropy_src_es_fatal_err + EntropySrcEsFatalErr = 180, + /// edn0_edn_cmd_req_done + Edn0EdnCmdReqDone = 181, + /// edn0_edn_fatal_err + Edn0EdnFatalErr = 182, + /// edn1_edn_cmd_req_done + Edn1EdnCmdReqDone = 183, + /// edn1_edn_fatal_err + Edn1EdnFatalErr = 184, +} + +impl TryFrom for PlicIrqId { + type Error = u32; + fn try_from(val: u32) -> Result { + match val { + 0 => Ok(Self::None), + 1 => Ok(Self::Uart0TxWatermark), + 2 => Ok(Self::Uart0RxWatermark), + 3 => Ok(Self::Uart0TxEmpty), + 4 => Ok(Self::Uart0RxOverflow), + 5 => Ok(Self::Uart0RxFrameErr), + 6 => Ok(Self::Uart0RxBreakErr), + 7 => Ok(Self::Uart0RxTimeout), + 8 => Ok(Self::Uart0RxParityErr), + 9 => Ok(Self::Uart1TxWatermark), + 10 => Ok(Self::Uart1RxWatermark), + 11 => Ok(Self::Uart1TxEmpty), + 12 => Ok(Self::Uart1RxOverflow), + 13 => Ok(Self::Uart1RxFrameErr), + 14 => Ok(Self::Uart1RxBreakErr), + 15 => Ok(Self::Uart1RxTimeout), + 16 => Ok(Self::Uart1RxParityErr), + 17 => Ok(Self::Uart2TxWatermark), + 18 => Ok(Self::Uart2RxWatermark), + 19 => Ok(Self::Uart2TxEmpty), + 20 => Ok(Self::Uart2RxOverflow), + 21 => Ok(Self::Uart2RxFrameErr), + 22 => Ok(Self::Uart2RxBreakErr), + 23 => Ok(Self::Uart2RxTimeout), + 24 => Ok(Self::Uart2RxParityErr), + 25 => Ok(Self::Uart3TxWatermark), + 26 => Ok(Self::Uart3RxWatermark), + 27 => Ok(Self::Uart3TxEmpty), + 28 => Ok(Self::Uart3RxOverflow), + 29 => Ok(Self::Uart3RxFrameErr), + 30 => Ok(Self::Uart3RxBreakErr), + 31 => Ok(Self::Uart3RxTimeout), + 32 => Ok(Self::Uart3RxParityErr), + 33 => Ok(Self::GpioGpio0), + 34 => Ok(Self::GpioGpio1), + 35 => Ok(Self::GpioGpio2), + 36 => Ok(Self::GpioGpio3), + 37 => Ok(Self::GpioGpio4), + 38 => Ok(Self::GpioGpio5), + 39 => Ok(Self::GpioGpio6), + 40 => Ok(Self::GpioGpio7), + 41 => Ok(Self::GpioGpio8), + 42 => Ok(Self::GpioGpio9), + 43 => Ok(Self::GpioGpio10), + 44 => Ok(Self::GpioGpio11), + 45 => Ok(Self::GpioGpio12), + 46 => Ok(Self::GpioGpio13), + 47 => Ok(Self::GpioGpio14), + 48 => Ok(Self::GpioGpio15), + 49 => Ok(Self::GpioGpio16), + 50 => Ok(Self::GpioGpio17), + 51 => Ok(Self::GpioGpio18), + 52 => Ok(Self::GpioGpio19), + 53 => Ok(Self::GpioGpio20), + 54 => Ok(Self::GpioGpio21), + 55 => Ok(Self::GpioGpio22), + 56 => Ok(Self::GpioGpio23), + 57 => Ok(Self::GpioGpio24), + 58 => Ok(Self::GpioGpio25), + 59 => Ok(Self::GpioGpio26), + 60 => Ok(Self::GpioGpio27), + 61 => Ok(Self::GpioGpio28), + 62 => Ok(Self::GpioGpio29), + 63 => Ok(Self::GpioGpio30), + 64 => Ok(Self::GpioGpio31), + 65 => Ok(Self::SpiDeviceGenericRxFull), + 66 => Ok(Self::SpiDeviceGenericRxWatermark), + 67 => Ok(Self::SpiDeviceGenericTxWatermark), + 68 => Ok(Self::SpiDeviceGenericRxError), + 69 => Ok(Self::SpiDeviceGenericRxOverflow), + 70 => Ok(Self::SpiDeviceGenericTxUnderflow), + 71 => Ok(Self::SpiDeviceUploadCmdfifoNotEmpty), + 72 => Ok(Self::SpiDeviceUploadPayloadNotEmpty), + 73 => Ok(Self::SpiDeviceUploadPayloadOverflow), + 74 => Ok(Self::SpiDeviceReadbufWatermark), + 75 => Ok(Self::SpiDeviceReadbufFlip), + 76 => Ok(Self::SpiDeviceTpmHeaderNotEmpty), + 77 => Ok(Self::I2c0FmtThreshold), + 78 => Ok(Self::I2c0RxThreshold), + 79 => Ok(Self::I2c0FmtOverflow), + 80 => Ok(Self::I2c0RxOverflow), + 81 => Ok(Self::I2c0Nak), + 82 => Ok(Self::I2c0SclInterference), + 83 => Ok(Self::I2c0SdaInterference), + 84 => Ok(Self::I2c0StretchTimeout), + 85 => Ok(Self::I2c0SdaUnstable), + 86 => Ok(Self::I2c0CmdComplete), + 87 => Ok(Self::I2c0TxStretch), + 88 => Ok(Self::I2c0TxOverflow), + 89 => Ok(Self::I2c0AcqFull), + 90 => Ok(Self::I2c0UnexpStop), + 91 => Ok(Self::I2c0HostTimeout), + 92 => Ok(Self::I2c1FmtThreshold), + 93 => Ok(Self::I2c1RxThreshold), + 94 => Ok(Self::I2c1FmtOverflow), + 95 => Ok(Self::I2c1RxOverflow), + 96 => Ok(Self::I2c1Nak), + 97 => Ok(Self::I2c1SclInterference), + 98 => Ok(Self::I2c1SdaInterference), + 99 => Ok(Self::I2c1StretchTimeout), + 100 => Ok(Self::I2c1SdaUnstable), + 101 => Ok(Self::I2c1CmdComplete), + 102 => Ok(Self::I2c1TxStretch), + 103 => Ok(Self::I2c1TxOverflow), + 104 => Ok(Self::I2c1AcqFull), + 105 => Ok(Self::I2c1UnexpStop), + 106 => Ok(Self::I2c1HostTimeout), + 107 => Ok(Self::I2c2FmtThreshold), + 108 => Ok(Self::I2c2RxThreshold), + 109 => Ok(Self::I2c2FmtOverflow), + 110 => Ok(Self::I2c2RxOverflow), + 111 => Ok(Self::I2c2Nak), + 112 => Ok(Self::I2c2SclInterference), + 113 => Ok(Self::I2c2SdaInterference), + 114 => Ok(Self::I2c2StretchTimeout), + 115 => Ok(Self::I2c2SdaUnstable), + 116 => Ok(Self::I2c2CmdComplete), + 117 => Ok(Self::I2c2TxStretch), + 118 => Ok(Self::I2c2TxOverflow), + 119 => Ok(Self::I2c2AcqFull), + 120 => Ok(Self::I2c2UnexpStop), + 121 => Ok(Self::I2c2HostTimeout), + 122 => Ok(Self::PattgenDoneCh0), + 123 => Ok(Self::PattgenDoneCh1), + 124 => Ok(Self::RvTimerTimerExpiredHart0Timer0), + 125 => Ok(Self::OtpCtrlOtpOperationDone), + 126 => Ok(Self::OtpCtrlOtpError), + 127 => Ok(Self::AlertHandlerClassa), + 128 => Ok(Self::AlertHandlerClassb), + 129 => Ok(Self::AlertHandlerClassc), + 130 => Ok(Self::AlertHandlerClassd), + 131 => Ok(Self::SpiHost0Error), + 132 => Ok(Self::SpiHost0SpiEvent), + 133 => Ok(Self::SpiHost1Error), + 134 => Ok(Self::SpiHost1SpiEvent), + 135 => Ok(Self::UsbdevPktReceived), + 136 => Ok(Self::UsbdevPktSent), + 137 => Ok(Self::UsbdevDisconnected), + 138 => Ok(Self::UsbdevHostLost), + 139 => Ok(Self::UsbdevLinkReset), + 140 => Ok(Self::UsbdevLinkSuspend), + 141 => Ok(Self::UsbdevLinkResume), + 142 => Ok(Self::UsbdevAvEmpty), + 143 => Ok(Self::UsbdevRxFull), + 144 => Ok(Self::UsbdevAvOverflow), + 145 => Ok(Self::UsbdevLinkInErr), + 146 => Ok(Self::UsbdevRxCrcErr), + 147 => Ok(Self::UsbdevRxPidErr), + 148 => Ok(Self::UsbdevRxBitstuffErr), + 149 => Ok(Self::UsbdevFrame), + 150 => Ok(Self::UsbdevPowered), + 151 => Ok(Self::UsbdevLinkOutErr), + 152 => Ok(Self::PwrmgrAonWakeup), + 153 => Ok(Self::SysrstCtrlAonEventDetected), + 154 => Ok(Self::AdcCtrlAonMatchDone), + 155 => Ok(Self::AonTimerAonWkupTimerExpired), + 156 => Ok(Self::AonTimerAonWdogTimerBark), + 157 => Ok(Self::SensorCtrlIoStatusChange), + 158 => Ok(Self::SensorCtrlInitStatusChange), + 159 => Ok(Self::FlashCtrlProgEmpty), + 160 => Ok(Self::FlashCtrlProgLvl), + 161 => Ok(Self::FlashCtrlRdFull), + 162 => Ok(Self::FlashCtrlRdLvl), + 163 => Ok(Self::FlashCtrlOpDone), + 164 => Ok(Self::FlashCtrlCorrErr), + 165 => Ok(Self::HmacHmacDone), + 166 => Ok(Self::HmacFifoEmpty), + 167 => Ok(Self::HmacHmacErr), + 168 => Ok(Self::KmacKmacDone), + 169 => Ok(Self::KmacFifoEmpty), + 170 => Ok(Self::KmacKmacErr), + 171 => Ok(Self::OtbnDone), + 172 => Ok(Self::KeymgrOpDone), + 173 => Ok(Self::CsrngCsCmdReqDone), + 174 => Ok(Self::CsrngCsEntropyReq), + 175 => Ok(Self::CsrngCsHwInstExc), + 176 => Ok(Self::CsrngCsFatalErr), + 177 => Ok(Self::EntropySrcEsEntropyValid), + 178 => Ok(Self::EntropySrcEsHealthTestFailed), + 179 => Ok(Self::EntropySrcEsObserveFifoReady), + 180 => Ok(Self::EntropySrcEsFatalErr), + 181 => Ok(Self::Edn0EdnCmdReqDone), + 182 => Ok(Self::Edn0EdnFatalErr), + 183 => Ok(Self::Edn1EdnCmdReqDone), + 184 => Ok(Self::Edn1EdnFatalErr), + _ => Err(val), + } + } +} + +/// PLIC Interrupt Target. +/// +/// Enumeration used to determine which set of IE, CC, threshold registers to +/// access for a given interrupt target. +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum PlicTarget { + /// Ibex Core 0 + Ibex0 = 0, +} + +/// Alert Handler Source Peripheral. +/// +/// Enumeration used to determine which peripheral asserted the corresponding +/// alert. +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum AlertPeripheral { + /// uart0 + Uart0 = 0, + /// uart1 + Uart1 = 1, + /// uart2 + Uart2 = 2, + /// uart3 + Uart3 = 3, + /// gpio + Gpio = 4, + /// spi_device + SpiDevice = 5, + /// i2c0 + I2c0 = 6, + /// i2c1 + I2c1 = 7, + /// i2c2 + I2c2 = 8, + /// pattgen + Pattgen = 9, + /// rv_timer + RvTimer = 10, + /// otp_ctrl + OtpCtrl = 11, + /// lc_ctrl + LcCtrl = 12, + /// spi_host0 + SpiHost0 = 13, + /// spi_host1 + SpiHost1 = 14, + /// usbdev + Usbdev = 15, + /// pwrmgr_aon + PwrmgrAon = 16, + /// rstmgr_aon + RstmgrAon = 17, + /// clkmgr_aon + ClkmgrAon = 18, + /// sysrst_ctrl_aon + SysrstCtrlAon = 19, + /// adc_ctrl_aon + AdcCtrlAon = 20, + /// pwm_aon + PwmAon = 21, + /// pinmux_aon + PinmuxAon = 22, + /// aon_timer_aon + AonTimerAon = 23, + /// sensor_ctrl + SensorCtrl = 24, + /// sram_ctrl_ret_aon + SramCtrlRetAon = 25, + /// flash_ctrl + FlashCtrl = 26, + /// rv_dm + RvDm = 27, + /// rv_plic + RvPlic = 28, + /// aes + Aes = 29, + /// hmac + Hmac = 30, + /// kmac + Kmac = 31, + /// otbn + Otbn = 32, + /// keymgr + Keymgr = 33, + /// csrng + Csrng = 34, + /// entropy_src + EntropySrc = 35, + /// edn0 + Edn0 = 36, + /// edn1 + Edn1 = 37, + /// sram_ctrl_main + SramCtrlMain = 38, + /// rom_ctrl + RomCtrl = 39, + /// rv_core_ibex + RvCoreIbex = 40, +} + +/// Alert Handler Alert Source. +/// +/// Enumeration of all Alert Handler Alert Sources. The alert sources belonging to +/// the same peripheral are guaranteed to be consecutive. +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum AlertId { + /// uart0_fatal_fault + Uart0FatalFault = 0, + /// uart1_fatal_fault + Uart1FatalFault = 1, + /// uart2_fatal_fault + Uart2FatalFault = 2, + /// uart3_fatal_fault + Uart3FatalFault = 3, + /// gpio_fatal_fault + GpioFatalFault = 4, + /// spi_device_fatal_fault + SpiDeviceFatalFault = 5, + /// i2c0_fatal_fault + I2c0FatalFault = 6, + /// i2c1_fatal_fault + I2c1FatalFault = 7, + /// i2c2_fatal_fault + I2c2FatalFault = 8, + /// pattgen_fatal_fault + PattgenFatalFault = 9, + /// rv_timer_fatal_fault + RvTimerFatalFault = 10, + /// otp_ctrl_fatal_macro_error + OtpCtrlFatalMacroError = 11, + /// otp_ctrl_fatal_check_error + OtpCtrlFatalCheckError = 12, + /// otp_ctrl_fatal_bus_integ_error + OtpCtrlFatalBusIntegError = 13, + /// otp_ctrl_fatal_prim_otp_alert + OtpCtrlFatalPrimOtpAlert = 14, + /// otp_ctrl_recov_prim_otp_alert + OtpCtrlRecovPrimOtpAlert = 15, + /// lc_ctrl_fatal_prog_error + LcCtrlFatalProgError = 16, + /// lc_ctrl_fatal_state_error + LcCtrlFatalStateError = 17, + /// lc_ctrl_fatal_bus_integ_error + LcCtrlFatalBusIntegError = 18, + /// spi_host0_fatal_fault + SpiHost0FatalFault = 19, + /// spi_host1_fatal_fault + SpiHost1FatalFault = 20, + /// usbdev_fatal_fault + UsbdevFatalFault = 21, + /// pwrmgr_aon_fatal_fault + PwrmgrAonFatalFault = 22, + /// rstmgr_aon_fatal_fault + RstmgrAonFatalFault = 23, + /// rstmgr_aon_fatal_cnsty_fault + RstmgrAonFatalCnstyFault = 24, + /// clkmgr_aon_recov_fault + ClkmgrAonRecovFault = 25, + /// clkmgr_aon_fatal_fault + ClkmgrAonFatalFault = 26, + /// sysrst_ctrl_aon_fatal_fault + SysrstCtrlAonFatalFault = 27, + /// adc_ctrl_aon_fatal_fault + AdcCtrlAonFatalFault = 28, + /// pwm_aon_fatal_fault + PwmAonFatalFault = 29, + /// pinmux_aon_fatal_fault + PinmuxAonFatalFault = 30, + /// aon_timer_aon_fatal_fault + AonTimerAonFatalFault = 31, + /// sensor_ctrl_recov_alert + SensorCtrlRecovAlert = 32, + /// sensor_ctrl_fatal_alert + SensorCtrlFatalAlert = 33, + /// sram_ctrl_ret_aon_fatal_error + SramCtrlRetAonFatalError = 34, + /// flash_ctrl_recov_err + FlashCtrlRecovErr = 35, + /// flash_ctrl_fatal_std_err + FlashCtrlFatalStdErr = 36, + /// flash_ctrl_fatal_err + FlashCtrlFatalErr = 37, + /// flash_ctrl_fatal_prim_flash_alert + FlashCtrlFatalPrimFlashAlert = 38, + /// flash_ctrl_recov_prim_flash_alert + FlashCtrlRecovPrimFlashAlert = 39, + /// rv_dm_fatal_fault + RvDmFatalFault = 40, + /// rv_plic_fatal_fault + RvPlicFatalFault = 41, + /// aes_recov_ctrl_update_err + AesRecovCtrlUpdateErr = 42, + /// aes_fatal_fault + AesFatalFault = 43, + /// hmac_fatal_fault + HmacFatalFault = 44, + /// kmac_recov_operation_err + KmacRecovOperationErr = 45, + /// kmac_fatal_fault_err + KmacFatalFaultErr = 46, + /// otbn_fatal + OtbnFatal = 47, + /// otbn_recov + OtbnRecov = 48, + /// keymgr_recov_operation_err + KeymgrRecovOperationErr = 49, + /// keymgr_fatal_fault_err + KeymgrFatalFaultErr = 50, + /// csrng_recov_alert + CsrngRecovAlert = 51, + /// csrng_fatal_alert + CsrngFatalAlert = 52, + /// entropy_src_recov_alert + EntropySrcRecovAlert = 53, + /// entropy_src_fatal_alert + EntropySrcFatalAlert = 54, + /// edn0_recov_alert + Edn0RecovAlert = 55, + /// edn0_fatal_alert + Edn0FatalAlert = 56, + /// edn1_recov_alert + Edn1RecovAlert = 57, + /// edn1_fatal_alert + Edn1FatalAlert = 58, + /// sram_ctrl_main_fatal_error + SramCtrlMainFatalError = 59, + /// rom_ctrl_fatal + RomCtrlFatal = 60, + /// rv_core_ibex_fatal_sw_err + RvCoreIbexFatalSwErr = 61, + /// rv_core_ibex_recov_sw_err + RvCoreIbexRecovSwErr = 62, + /// rv_core_ibex_fatal_hw_err + RvCoreIbexFatalHwErr = 63, + /// rv_core_ibex_recov_hw_err + RvCoreIbexRecovHwErr = 64, +} + +impl TryFrom for AlertId { + type Error = u32; + fn try_from(val: u32) -> Result { + match val { + 0 => Ok(Self::Uart0FatalFault), + 1 => Ok(Self::Uart1FatalFault), + 2 => Ok(Self::Uart2FatalFault), + 3 => Ok(Self::Uart3FatalFault), + 4 => Ok(Self::GpioFatalFault), + 5 => Ok(Self::SpiDeviceFatalFault), + 6 => Ok(Self::I2c0FatalFault), + 7 => Ok(Self::I2c1FatalFault), + 8 => Ok(Self::I2c2FatalFault), + 9 => Ok(Self::PattgenFatalFault), + 10 => Ok(Self::RvTimerFatalFault), + 11 => Ok(Self::OtpCtrlFatalMacroError), + 12 => Ok(Self::OtpCtrlFatalCheckError), + 13 => Ok(Self::OtpCtrlFatalBusIntegError), + 14 => Ok(Self::OtpCtrlFatalPrimOtpAlert), + 15 => Ok(Self::OtpCtrlRecovPrimOtpAlert), + 16 => Ok(Self::LcCtrlFatalProgError), + 17 => Ok(Self::LcCtrlFatalStateError), + 18 => Ok(Self::LcCtrlFatalBusIntegError), + 19 => Ok(Self::SpiHost0FatalFault), + 20 => Ok(Self::SpiHost1FatalFault), + 21 => Ok(Self::UsbdevFatalFault), + 22 => Ok(Self::PwrmgrAonFatalFault), + 23 => Ok(Self::RstmgrAonFatalFault), + 24 => Ok(Self::RstmgrAonFatalCnstyFault), + 25 => Ok(Self::ClkmgrAonRecovFault), + 26 => Ok(Self::ClkmgrAonFatalFault), + 27 => Ok(Self::SysrstCtrlAonFatalFault), + 28 => Ok(Self::AdcCtrlAonFatalFault), + 29 => Ok(Self::PwmAonFatalFault), + 30 => Ok(Self::PinmuxAonFatalFault), + 31 => Ok(Self::AonTimerAonFatalFault), + 32 => Ok(Self::SensorCtrlRecovAlert), + 33 => Ok(Self::SensorCtrlFatalAlert), + 34 => Ok(Self::SramCtrlRetAonFatalError), + 35 => Ok(Self::FlashCtrlRecovErr), + 36 => Ok(Self::FlashCtrlFatalStdErr), + 37 => Ok(Self::FlashCtrlFatalErr), + 38 => Ok(Self::FlashCtrlFatalPrimFlashAlert), + 39 => Ok(Self::FlashCtrlRecovPrimFlashAlert), + 40 => Ok(Self::RvDmFatalFault), + 41 => Ok(Self::RvPlicFatalFault), + 42 => Ok(Self::AesRecovCtrlUpdateErr), + 43 => Ok(Self::AesFatalFault), + 44 => Ok(Self::HmacFatalFault), + 45 => Ok(Self::KmacRecovOperationErr), + 46 => Ok(Self::KmacFatalFaultErr), + 47 => Ok(Self::OtbnFatal), + 48 => Ok(Self::OtbnRecov), + 49 => Ok(Self::KeymgrRecovOperationErr), + 50 => Ok(Self::KeymgrFatalFaultErr), + 51 => Ok(Self::CsrngRecovAlert), + 52 => Ok(Self::CsrngFatalAlert), + 53 => Ok(Self::EntropySrcRecovAlert), + 54 => Ok(Self::EntropySrcFatalAlert), + 55 => Ok(Self::Edn0RecovAlert), + 56 => Ok(Self::Edn0FatalAlert), + 57 => Ok(Self::Edn1RecovAlert), + 58 => Ok(Self::Edn1FatalAlert), + 59 => Ok(Self::SramCtrlMainFatalError), + 60 => Ok(Self::RomCtrlFatal), + 61 => Ok(Self::RvCoreIbexFatalSwErr), + 62 => Ok(Self::RvCoreIbexRecovSwErr), + 63 => Ok(Self::RvCoreIbexFatalHwErr), + 64 => Ok(Self::RvCoreIbexRecovHwErr), + _ => Err(val), + } + } +} + +/// PLIC Interrupt Source to Peripheral Map +/// +/// This array is a mapping from `PlicIrqId` to +/// `PlicPeripheral`. +pub const PLIC_INTERRUPT_FOR_PERIPHERAL: [PlicPeripheral; 185] = [ + // None -> PlicPeripheral::Unknown + PlicPeripheral::Unknown, + // Uart0TxWatermark -> PlicPeripheral::Uart0 + PlicPeripheral::Uart0, + // Uart0RxWatermark -> PlicPeripheral::Uart0 + PlicPeripheral::Uart0, + // Uart0TxEmpty -> PlicPeripheral::Uart0 + PlicPeripheral::Uart0, + // Uart0RxOverflow -> PlicPeripheral::Uart0 + PlicPeripheral::Uart0, + // Uart0RxFrameErr -> PlicPeripheral::Uart0 + PlicPeripheral::Uart0, + // Uart0RxBreakErr -> PlicPeripheral::Uart0 + PlicPeripheral::Uart0, + // Uart0RxTimeout -> PlicPeripheral::Uart0 + PlicPeripheral::Uart0, + // Uart0RxParityErr -> PlicPeripheral::Uart0 + PlicPeripheral::Uart0, + // Uart1TxWatermark -> PlicPeripheral::Uart1 + PlicPeripheral::Uart1, + // Uart1RxWatermark -> PlicPeripheral::Uart1 + PlicPeripheral::Uart1, + // Uart1TxEmpty -> PlicPeripheral::Uart1 + PlicPeripheral::Uart1, + // Uart1RxOverflow -> PlicPeripheral::Uart1 + PlicPeripheral::Uart1, + // Uart1RxFrameErr -> PlicPeripheral::Uart1 + PlicPeripheral::Uart1, + // Uart1RxBreakErr -> PlicPeripheral::Uart1 + PlicPeripheral::Uart1, + // Uart1RxTimeout -> PlicPeripheral::Uart1 + PlicPeripheral::Uart1, + // Uart1RxParityErr -> PlicPeripheral::Uart1 + PlicPeripheral::Uart1, + // Uart2TxWatermark -> PlicPeripheral::Uart2 + PlicPeripheral::Uart2, + // Uart2RxWatermark -> PlicPeripheral::Uart2 + PlicPeripheral::Uart2, + // Uart2TxEmpty -> PlicPeripheral::Uart2 + PlicPeripheral::Uart2, + // Uart2RxOverflow -> PlicPeripheral::Uart2 + PlicPeripheral::Uart2, + // Uart2RxFrameErr -> PlicPeripheral::Uart2 + PlicPeripheral::Uart2, + // Uart2RxBreakErr -> PlicPeripheral::Uart2 + PlicPeripheral::Uart2, + // Uart2RxTimeout -> PlicPeripheral::Uart2 + PlicPeripheral::Uart2, + // Uart2RxParityErr -> PlicPeripheral::Uart2 + PlicPeripheral::Uart2, + // Uart3TxWatermark -> PlicPeripheral::Uart3 + PlicPeripheral::Uart3, + // Uart3RxWatermark -> PlicPeripheral::Uart3 + PlicPeripheral::Uart3, + // Uart3TxEmpty -> PlicPeripheral::Uart3 + PlicPeripheral::Uart3, + // Uart3RxOverflow -> PlicPeripheral::Uart3 + PlicPeripheral::Uart3, + // Uart3RxFrameErr -> PlicPeripheral::Uart3 + PlicPeripheral::Uart3, + // Uart3RxBreakErr -> PlicPeripheral::Uart3 + PlicPeripheral::Uart3, + // Uart3RxTimeout -> PlicPeripheral::Uart3 + PlicPeripheral::Uart3, + // Uart3RxParityErr -> PlicPeripheral::Uart3 + PlicPeripheral::Uart3, + // GpioGpio0 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio1 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio2 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio3 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio4 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio5 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio6 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio7 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio8 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio9 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio10 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio11 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio12 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio13 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio14 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio15 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio16 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio17 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio18 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio19 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio20 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio21 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio22 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio23 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio24 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio25 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio26 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio27 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio28 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio29 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio30 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // GpioGpio31 -> PlicPeripheral::Gpio + PlicPeripheral::Gpio, + // SpiDeviceGenericRxFull -> PlicPeripheral::SpiDevice + PlicPeripheral::SpiDevice, + // SpiDeviceGenericRxWatermark -> PlicPeripheral::SpiDevice + PlicPeripheral::SpiDevice, + // SpiDeviceGenericTxWatermark -> PlicPeripheral::SpiDevice + PlicPeripheral::SpiDevice, + // SpiDeviceGenericRxError -> PlicPeripheral::SpiDevice + PlicPeripheral::SpiDevice, + // SpiDeviceGenericRxOverflow -> PlicPeripheral::SpiDevice + PlicPeripheral::SpiDevice, + // SpiDeviceGenericTxUnderflow -> PlicPeripheral::SpiDevice + PlicPeripheral::SpiDevice, + // SpiDeviceUploadCmdfifoNotEmpty -> PlicPeripheral::SpiDevice + PlicPeripheral::SpiDevice, + // SpiDeviceUploadPayloadNotEmpty -> PlicPeripheral::SpiDevice + PlicPeripheral::SpiDevice, + // SpiDeviceUploadPayloadOverflow -> PlicPeripheral::SpiDevice + PlicPeripheral::SpiDevice, + // SpiDeviceReadbufWatermark -> PlicPeripheral::SpiDevice + PlicPeripheral::SpiDevice, + // SpiDeviceReadbufFlip -> PlicPeripheral::SpiDevice + PlicPeripheral::SpiDevice, + // SpiDeviceTpmHeaderNotEmpty -> PlicPeripheral::SpiDevice + PlicPeripheral::SpiDevice, + // I2c0FmtThreshold -> PlicPeripheral::I2c0 + PlicPeripheral::I2c0, + // I2c0RxThreshold -> PlicPeripheral::I2c0 + PlicPeripheral::I2c0, + // I2c0FmtOverflow -> PlicPeripheral::I2c0 + PlicPeripheral::I2c0, + // I2c0RxOverflow -> PlicPeripheral::I2c0 + PlicPeripheral::I2c0, + // I2c0Nak -> PlicPeripheral::I2c0 + PlicPeripheral::I2c0, + // I2c0SclInterference -> PlicPeripheral::I2c0 + PlicPeripheral::I2c0, + // I2c0SdaInterference -> PlicPeripheral::I2c0 + PlicPeripheral::I2c0, + // I2c0StretchTimeout -> PlicPeripheral::I2c0 + PlicPeripheral::I2c0, + // I2c0SdaUnstable -> PlicPeripheral::I2c0 + PlicPeripheral::I2c0, + // I2c0CmdComplete -> PlicPeripheral::I2c0 + PlicPeripheral::I2c0, + // I2c0TxStretch -> PlicPeripheral::I2c0 + PlicPeripheral::I2c0, + // I2c0TxOverflow -> PlicPeripheral::I2c0 + PlicPeripheral::I2c0, + // I2c0AcqFull -> PlicPeripheral::I2c0 + PlicPeripheral::I2c0, + // I2c0UnexpStop -> PlicPeripheral::I2c0 + PlicPeripheral::I2c0, + // I2c0HostTimeout -> PlicPeripheral::I2c0 + PlicPeripheral::I2c0, + // I2c1FmtThreshold -> PlicPeripheral::I2c1 + PlicPeripheral::I2c1, + // I2c1RxThreshold -> PlicPeripheral::I2c1 + PlicPeripheral::I2c1, + // I2c1FmtOverflow -> PlicPeripheral::I2c1 + PlicPeripheral::I2c1, + // I2c1RxOverflow -> PlicPeripheral::I2c1 + PlicPeripheral::I2c1, + // I2c1Nak -> PlicPeripheral::I2c1 + PlicPeripheral::I2c1, + // I2c1SclInterference -> PlicPeripheral::I2c1 + PlicPeripheral::I2c1, + // I2c1SdaInterference -> PlicPeripheral::I2c1 + PlicPeripheral::I2c1, + // I2c1StretchTimeout -> PlicPeripheral::I2c1 + PlicPeripheral::I2c1, + // I2c1SdaUnstable -> PlicPeripheral::I2c1 + PlicPeripheral::I2c1, + // I2c1CmdComplete -> PlicPeripheral::I2c1 + PlicPeripheral::I2c1, + // I2c1TxStretch -> PlicPeripheral::I2c1 + PlicPeripheral::I2c1, + // I2c1TxOverflow -> PlicPeripheral::I2c1 + PlicPeripheral::I2c1, + // I2c1AcqFull -> PlicPeripheral::I2c1 + PlicPeripheral::I2c1, + // I2c1UnexpStop -> PlicPeripheral::I2c1 + PlicPeripheral::I2c1, + // I2c1HostTimeout -> PlicPeripheral::I2c1 + PlicPeripheral::I2c1, + // I2c2FmtThreshold -> PlicPeripheral::I2c2 + PlicPeripheral::I2c2, + // I2c2RxThreshold -> PlicPeripheral::I2c2 + PlicPeripheral::I2c2, + // I2c2FmtOverflow -> PlicPeripheral::I2c2 + PlicPeripheral::I2c2, + // I2c2RxOverflow -> PlicPeripheral::I2c2 + PlicPeripheral::I2c2, + // I2c2Nak -> PlicPeripheral::I2c2 + PlicPeripheral::I2c2, + // I2c2SclInterference -> PlicPeripheral::I2c2 + PlicPeripheral::I2c2, + // I2c2SdaInterference -> PlicPeripheral::I2c2 + PlicPeripheral::I2c2, + // I2c2StretchTimeout -> PlicPeripheral::I2c2 + PlicPeripheral::I2c2, + // I2c2SdaUnstable -> PlicPeripheral::I2c2 + PlicPeripheral::I2c2, + // I2c2CmdComplete -> PlicPeripheral::I2c2 + PlicPeripheral::I2c2, + // I2c2TxStretch -> PlicPeripheral::I2c2 + PlicPeripheral::I2c2, + // I2c2TxOverflow -> PlicPeripheral::I2c2 + PlicPeripheral::I2c2, + // I2c2AcqFull -> PlicPeripheral::I2c2 + PlicPeripheral::I2c2, + // I2c2UnexpStop -> PlicPeripheral::I2c2 + PlicPeripheral::I2c2, + // I2c2HostTimeout -> PlicPeripheral::I2c2 + PlicPeripheral::I2c2, + // PattgenDoneCh0 -> PlicPeripheral::Pattgen + PlicPeripheral::Pattgen, + // PattgenDoneCh1 -> PlicPeripheral::Pattgen + PlicPeripheral::Pattgen, + // RvTimerTimerExpiredHart0Timer0 -> PlicPeripheral::RvTimer + PlicPeripheral::RvTimer, + // OtpCtrlOtpOperationDone -> PlicPeripheral::OtpCtrl + PlicPeripheral::OtpCtrl, + // OtpCtrlOtpError -> PlicPeripheral::OtpCtrl + PlicPeripheral::OtpCtrl, + // AlertHandlerClassa -> PlicPeripheral::AlertHandler + PlicPeripheral::AlertHandler, + // AlertHandlerClassb -> PlicPeripheral::AlertHandler + PlicPeripheral::AlertHandler, + // AlertHandlerClassc -> PlicPeripheral::AlertHandler + PlicPeripheral::AlertHandler, + // AlertHandlerClassd -> PlicPeripheral::AlertHandler + PlicPeripheral::AlertHandler, + // SpiHost0Error -> PlicPeripheral::SpiHost0 + PlicPeripheral::SpiHost0, + // SpiHost0SpiEvent -> PlicPeripheral::SpiHost0 + PlicPeripheral::SpiHost0, + // SpiHost1Error -> PlicPeripheral::SpiHost1 + PlicPeripheral::SpiHost1, + // SpiHost1SpiEvent -> PlicPeripheral::SpiHost1 + PlicPeripheral::SpiHost1, + // UsbdevPktReceived -> PlicPeripheral::Usbdev + PlicPeripheral::Usbdev, + // UsbdevPktSent -> PlicPeripheral::Usbdev + PlicPeripheral::Usbdev, + // UsbdevDisconnected -> PlicPeripheral::Usbdev + PlicPeripheral::Usbdev, + // UsbdevHostLost -> PlicPeripheral::Usbdev + PlicPeripheral::Usbdev, + // UsbdevLinkReset -> PlicPeripheral::Usbdev + PlicPeripheral::Usbdev, + // UsbdevLinkSuspend -> PlicPeripheral::Usbdev + PlicPeripheral::Usbdev, + // UsbdevLinkResume -> PlicPeripheral::Usbdev + PlicPeripheral::Usbdev, + // UsbdevAvEmpty -> PlicPeripheral::Usbdev + PlicPeripheral::Usbdev, + // UsbdevRxFull -> PlicPeripheral::Usbdev + PlicPeripheral::Usbdev, + // UsbdevAvOverflow -> PlicPeripheral::Usbdev + PlicPeripheral::Usbdev, + // UsbdevLinkInErr -> PlicPeripheral::Usbdev + PlicPeripheral::Usbdev, + // UsbdevRxCrcErr -> PlicPeripheral::Usbdev + PlicPeripheral::Usbdev, + // UsbdevRxPidErr -> PlicPeripheral::Usbdev + PlicPeripheral::Usbdev, + // UsbdevRxBitstuffErr -> PlicPeripheral::Usbdev + PlicPeripheral::Usbdev, + // UsbdevFrame -> PlicPeripheral::Usbdev + PlicPeripheral::Usbdev, + // UsbdevPowered -> PlicPeripheral::Usbdev + PlicPeripheral::Usbdev, + // UsbdevLinkOutErr -> PlicPeripheral::Usbdev + PlicPeripheral::Usbdev, + // PwrmgrAonWakeup -> PlicPeripheral::PwrmgrAon + PlicPeripheral::PwrmgrAon, + // SysrstCtrlAonEventDetected -> PlicPeripheral::SysrstCtrlAon + PlicPeripheral::SysrstCtrlAon, + // AdcCtrlAonMatchDone -> PlicPeripheral::AdcCtrlAon + PlicPeripheral::AdcCtrlAon, + // AonTimerAonWkupTimerExpired -> PlicPeripheral::AonTimerAon + PlicPeripheral::AonTimerAon, + // AonTimerAonWdogTimerBark -> PlicPeripheral::AonTimerAon + PlicPeripheral::AonTimerAon, + // SensorCtrlIoStatusChange -> PlicPeripheral::SensorCtrl + PlicPeripheral::SensorCtrl, + // SensorCtrlInitStatusChange -> PlicPeripheral::SensorCtrl + PlicPeripheral::SensorCtrl, + // FlashCtrlProgEmpty -> PlicPeripheral::FlashCtrl + PlicPeripheral::FlashCtrl, + // FlashCtrlProgLvl -> PlicPeripheral::FlashCtrl + PlicPeripheral::FlashCtrl, + // FlashCtrlRdFull -> PlicPeripheral::FlashCtrl + PlicPeripheral::FlashCtrl, + // FlashCtrlRdLvl -> PlicPeripheral::FlashCtrl + PlicPeripheral::FlashCtrl, + // FlashCtrlOpDone -> PlicPeripheral::FlashCtrl + PlicPeripheral::FlashCtrl, + // FlashCtrlCorrErr -> PlicPeripheral::FlashCtrl + PlicPeripheral::FlashCtrl, + // HmacHmacDone -> PlicPeripheral::Hmac + PlicPeripheral::Hmac, + // HmacFifoEmpty -> PlicPeripheral::Hmac + PlicPeripheral::Hmac, + // HmacHmacErr -> PlicPeripheral::Hmac + PlicPeripheral::Hmac, + // KmacKmacDone -> PlicPeripheral::Kmac + PlicPeripheral::Kmac, + // KmacFifoEmpty -> PlicPeripheral::Kmac + PlicPeripheral::Kmac, + // KmacKmacErr -> PlicPeripheral::Kmac + PlicPeripheral::Kmac, + // OtbnDone -> PlicPeripheral::Otbn + PlicPeripheral::Otbn, + // KeymgrOpDone -> PlicPeripheral::Keymgr + PlicPeripheral::Keymgr, + // CsrngCsCmdReqDone -> PlicPeripheral::Csrng + PlicPeripheral::Csrng, + // CsrngCsEntropyReq -> PlicPeripheral::Csrng + PlicPeripheral::Csrng, + // CsrngCsHwInstExc -> PlicPeripheral::Csrng + PlicPeripheral::Csrng, + // CsrngCsFatalErr -> PlicPeripheral::Csrng + PlicPeripheral::Csrng, + // EntropySrcEsEntropyValid -> PlicPeripheral::EntropySrc + PlicPeripheral::EntropySrc, + // EntropySrcEsHealthTestFailed -> PlicPeripheral::EntropySrc + PlicPeripheral::EntropySrc, + // EntropySrcEsObserveFifoReady -> PlicPeripheral::EntropySrc + PlicPeripheral::EntropySrc, + // EntropySrcEsFatalErr -> PlicPeripheral::EntropySrc + PlicPeripheral::EntropySrc, + // Edn0EdnCmdReqDone -> PlicPeripheral::Edn0 + PlicPeripheral::Edn0, + // Edn0EdnFatalErr -> PlicPeripheral::Edn0 + PlicPeripheral::Edn0, + // Edn1EdnCmdReqDone -> PlicPeripheral::Edn1 + PlicPeripheral::Edn1, + // Edn1EdnFatalErr -> PlicPeripheral::Edn1 + PlicPeripheral::Edn1, +]; + +/// Alert Handler Alert Source to Peripheral Map +/// +/// This array is a mapping from `AlertId` to +/// `AlertPeripheral`. +pub const ALERT_FOR_PERIPHERAL: [AlertPeripheral; 65] = [ + // Uart0FatalFault -> AlertPeripheral::Uart0 + AlertPeripheral::Uart0, + // Uart1FatalFault -> AlertPeripheral::Uart1 + AlertPeripheral::Uart1, + // Uart2FatalFault -> AlertPeripheral::Uart2 + AlertPeripheral::Uart2, + // Uart3FatalFault -> AlertPeripheral::Uart3 + AlertPeripheral::Uart3, + // GpioFatalFault -> AlertPeripheral::Gpio + AlertPeripheral::Gpio, + // SpiDeviceFatalFault -> AlertPeripheral::SpiDevice + AlertPeripheral::SpiDevice, + // I2c0FatalFault -> AlertPeripheral::I2c0 + AlertPeripheral::I2c0, + // I2c1FatalFault -> AlertPeripheral::I2c1 + AlertPeripheral::I2c1, + // I2c2FatalFault -> AlertPeripheral::I2c2 + AlertPeripheral::I2c2, + // PattgenFatalFault -> AlertPeripheral::Pattgen + AlertPeripheral::Pattgen, + // RvTimerFatalFault -> AlertPeripheral::RvTimer + AlertPeripheral::RvTimer, + // OtpCtrlFatalMacroError -> AlertPeripheral::OtpCtrl + AlertPeripheral::OtpCtrl, + // OtpCtrlFatalCheckError -> AlertPeripheral::OtpCtrl + AlertPeripheral::OtpCtrl, + // OtpCtrlFatalBusIntegError -> AlertPeripheral::OtpCtrl + AlertPeripheral::OtpCtrl, + // OtpCtrlFatalPrimOtpAlert -> AlertPeripheral::OtpCtrl + AlertPeripheral::OtpCtrl, + // OtpCtrlRecovPrimOtpAlert -> AlertPeripheral::OtpCtrl + AlertPeripheral::OtpCtrl, + // LcCtrlFatalProgError -> AlertPeripheral::LcCtrl + AlertPeripheral::LcCtrl, + // LcCtrlFatalStateError -> AlertPeripheral::LcCtrl + AlertPeripheral::LcCtrl, + // LcCtrlFatalBusIntegError -> AlertPeripheral::LcCtrl + AlertPeripheral::LcCtrl, + // SpiHost0FatalFault -> AlertPeripheral::SpiHost0 + AlertPeripheral::SpiHost0, + // SpiHost1FatalFault -> AlertPeripheral::SpiHost1 + AlertPeripheral::SpiHost1, + // UsbdevFatalFault -> AlertPeripheral::Usbdev + AlertPeripheral::Usbdev, + // PwrmgrAonFatalFault -> AlertPeripheral::PwrmgrAon + AlertPeripheral::PwrmgrAon, + // RstmgrAonFatalFault -> AlertPeripheral::RstmgrAon + AlertPeripheral::RstmgrAon, + // RstmgrAonFatalCnstyFault -> AlertPeripheral::RstmgrAon + AlertPeripheral::RstmgrAon, + // ClkmgrAonRecovFault -> AlertPeripheral::ClkmgrAon + AlertPeripheral::ClkmgrAon, + // ClkmgrAonFatalFault -> AlertPeripheral::ClkmgrAon + AlertPeripheral::ClkmgrAon, + // SysrstCtrlAonFatalFault -> AlertPeripheral::SysrstCtrlAon + AlertPeripheral::SysrstCtrlAon, + // AdcCtrlAonFatalFault -> AlertPeripheral::AdcCtrlAon + AlertPeripheral::AdcCtrlAon, + // PwmAonFatalFault -> AlertPeripheral::PwmAon + AlertPeripheral::PwmAon, + // PinmuxAonFatalFault -> AlertPeripheral::PinmuxAon + AlertPeripheral::PinmuxAon, + // AonTimerAonFatalFault -> AlertPeripheral::AonTimerAon + AlertPeripheral::AonTimerAon, + // SensorCtrlRecovAlert -> AlertPeripheral::SensorCtrl + AlertPeripheral::SensorCtrl, + // SensorCtrlFatalAlert -> AlertPeripheral::SensorCtrl + AlertPeripheral::SensorCtrl, + // SramCtrlRetAonFatalError -> AlertPeripheral::SramCtrlRetAon + AlertPeripheral::SramCtrlRetAon, + // FlashCtrlRecovErr -> AlertPeripheral::FlashCtrl + AlertPeripheral::FlashCtrl, + // FlashCtrlFatalStdErr -> AlertPeripheral::FlashCtrl + AlertPeripheral::FlashCtrl, + // FlashCtrlFatalErr -> AlertPeripheral::FlashCtrl + AlertPeripheral::FlashCtrl, + // FlashCtrlFatalPrimFlashAlert -> AlertPeripheral::FlashCtrl + AlertPeripheral::FlashCtrl, + // FlashCtrlRecovPrimFlashAlert -> AlertPeripheral::FlashCtrl + AlertPeripheral::FlashCtrl, + // RvDmFatalFault -> AlertPeripheral::RvDm + AlertPeripheral::RvDm, + // RvPlicFatalFault -> AlertPeripheral::RvPlic + AlertPeripheral::RvPlic, + // AesRecovCtrlUpdateErr -> AlertPeripheral::Aes + AlertPeripheral::Aes, + // AesFatalFault -> AlertPeripheral::Aes + AlertPeripheral::Aes, + // HmacFatalFault -> AlertPeripheral::Hmac + AlertPeripheral::Hmac, + // KmacRecovOperationErr -> AlertPeripheral::Kmac + AlertPeripheral::Kmac, + // KmacFatalFaultErr -> AlertPeripheral::Kmac + AlertPeripheral::Kmac, + // OtbnFatal -> AlertPeripheral::Otbn + AlertPeripheral::Otbn, + // OtbnRecov -> AlertPeripheral::Otbn + AlertPeripheral::Otbn, + // KeymgrRecovOperationErr -> AlertPeripheral::Keymgr + AlertPeripheral::Keymgr, + // KeymgrFatalFaultErr -> AlertPeripheral::Keymgr + AlertPeripheral::Keymgr, + // CsrngRecovAlert -> AlertPeripheral::Csrng + AlertPeripheral::Csrng, + // CsrngFatalAlert -> AlertPeripheral::Csrng + AlertPeripheral::Csrng, + // EntropySrcRecovAlert -> AlertPeripheral::EntropySrc + AlertPeripheral::EntropySrc, + // EntropySrcFatalAlert -> AlertPeripheral::EntropySrc + AlertPeripheral::EntropySrc, + // Edn0RecovAlert -> AlertPeripheral::Edn0 + AlertPeripheral::Edn0, + // Edn0FatalAlert -> AlertPeripheral::Edn0 + AlertPeripheral::Edn0, + // Edn1RecovAlert -> AlertPeripheral::Edn1 + AlertPeripheral::Edn1, + // Edn1FatalAlert -> AlertPeripheral::Edn1 + AlertPeripheral::Edn1, + // SramCtrlMainFatalError -> AlertPeripheral::SramCtrlMain + AlertPeripheral::SramCtrlMain, + // RomCtrlFatal -> AlertPeripheral::RomCtrl + AlertPeripheral::RomCtrl, + // RvCoreIbexFatalSwErr -> AlertPeripheral::RvCoreIbex + AlertPeripheral::RvCoreIbex, + // RvCoreIbexRecovSwErr -> AlertPeripheral::RvCoreIbex + AlertPeripheral::RvCoreIbex, + // RvCoreIbexFatalHwErr -> AlertPeripheral::RvCoreIbex + AlertPeripheral::RvCoreIbex, + // RvCoreIbexRecovHwErr -> AlertPeripheral::RvCoreIbex + AlertPeripheral::RvCoreIbex, +]; + +// PERIPH_INSEL ranges from 0 to NUM_MIO_PADS + 2 -1} +// 0 and 1 are tied to value 0 and 1 +pub const NUM_MIO_PADS: usize = 47; +pub const NUM_DIO_PADS: usize = 16; + +pub const PINMUX_MIO_PERIPH_INSEL_IDX_OFFSET: usize = 2; +pub const PINMUX_PERIPH_OUTSEL_IDX_OFFSET: usize = 3; + +/// Pinmux Peripheral Input. +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum PinmuxPeripheralIn { + /// Peripheral Input 0 + GpioGpio0 = 0, + /// Peripheral Input 1 + GpioGpio1 = 1, + /// Peripheral Input 2 + GpioGpio2 = 2, + /// Peripheral Input 3 + GpioGpio3 = 3, + /// Peripheral Input 4 + GpioGpio4 = 4, + /// Peripheral Input 5 + GpioGpio5 = 5, + /// Peripheral Input 6 + GpioGpio6 = 6, + /// Peripheral Input 7 + GpioGpio7 = 7, + /// Peripheral Input 8 + GpioGpio8 = 8, + /// Peripheral Input 9 + GpioGpio9 = 9, + /// Peripheral Input 10 + GpioGpio10 = 10, + /// Peripheral Input 11 + GpioGpio11 = 11, + /// Peripheral Input 12 + GpioGpio12 = 12, + /// Peripheral Input 13 + GpioGpio13 = 13, + /// Peripheral Input 14 + GpioGpio14 = 14, + /// Peripheral Input 15 + GpioGpio15 = 15, + /// Peripheral Input 16 + GpioGpio16 = 16, + /// Peripheral Input 17 + GpioGpio17 = 17, + /// Peripheral Input 18 + GpioGpio18 = 18, + /// Peripheral Input 19 + GpioGpio19 = 19, + /// Peripheral Input 20 + GpioGpio20 = 20, + /// Peripheral Input 21 + GpioGpio21 = 21, + /// Peripheral Input 22 + GpioGpio22 = 22, + /// Peripheral Input 23 + GpioGpio23 = 23, + /// Peripheral Input 24 + GpioGpio24 = 24, + /// Peripheral Input 25 + GpioGpio25 = 25, + /// Peripheral Input 26 + GpioGpio26 = 26, + /// Peripheral Input 27 + GpioGpio27 = 27, + /// Peripheral Input 28 + GpioGpio28 = 28, + /// Peripheral Input 29 + GpioGpio29 = 29, + /// Peripheral Input 30 + GpioGpio30 = 30, + /// Peripheral Input 31 + GpioGpio31 = 31, + /// Peripheral Input 32 + I2c0Sda = 32, + /// Peripheral Input 33 + I2c0Scl = 33, + /// Peripheral Input 34 + I2c1Sda = 34, + /// Peripheral Input 35 + I2c1Scl = 35, + /// Peripheral Input 36 + I2c2Sda = 36, + /// Peripheral Input 37 + I2c2Scl = 37, + /// Peripheral Input 38 + SpiHost1Sd0 = 38, + /// Peripheral Input 39 + SpiHost1Sd1 = 39, + /// Peripheral Input 40 + SpiHost1Sd2 = 40, + /// Peripheral Input 41 + SpiHost1Sd3 = 41, + /// Peripheral Input 42 + Uart0Rx = 42, + /// Peripheral Input 43 + Uart1Rx = 43, + /// Peripheral Input 44 + Uart2Rx = 44, + /// Peripheral Input 45 + Uart3Rx = 45, + /// Peripheral Input 46 + SpiDeviceTpmCsb = 46, + /// Peripheral Input 47 + FlashCtrlTck = 47, + /// Peripheral Input 48 + FlashCtrlTms = 48, + /// Peripheral Input 49 + FlashCtrlTdi = 49, + /// Peripheral Input 50 + SysrstCtrlAonAcPresent = 50, + /// Peripheral Input 51 + SysrstCtrlAonKey0In = 51, + /// Peripheral Input 52 + SysrstCtrlAonKey1In = 52, + /// Peripheral Input 53 + SysrstCtrlAonKey2In = 53, + /// Peripheral Input 54 + SysrstCtrlAonPwrbIn = 54, + /// Peripheral Input 55 + SysrstCtrlAonLidOpen = 55, + /// Peripheral Input 56 + UsbdevSense = 56, +} + +impl TryFrom for PinmuxPeripheralIn { + type Error = u32; + fn try_from(val: u32) -> Result { + match val { + 0 => Ok(Self::GpioGpio0), + 1 => Ok(Self::GpioGpio1), + 2 => Ok(Self::GpioGpio2), + 3 => Ok(Self::GpioGpio3), + 4 => Ok(Self::GpioGpio4), + 5 => Ok(Self::GpioGpio5), + 6 => Ok(Self::GpioGpio6), + 7 => Ok(Self::GpioGpio7), + 8 => Ok(Self::GpioGpio8), + 9 => Ok(Self::GpioGpio9), + 10 => Ok(Self::GpioGpio10), + 11 => Ok(Self::GpioGpio11), + 12 => Ok(Self::GpioGpio12), + 13 => Ok(Self::GpioGpio13), + 14 => Ok(Self::GpioGpio14), + 15 => Ok(Self::GpioGpio15), + 16 => Ok(Self::GpioGpio16), + 17 => Ok(Self::GpioGpio17), + 18 => Ok(Self::GpioGpio18), + 19 => Ok(Self::GpioGpio19), + 20 => Ok(Self::GpioGpio20), + 21 => Ok(Self::GpioGpio21), + 22 => Ok(Self::GpioGpio22), + 23 => Ok(Self::GpioGpio23), + 24 => Ok(Self::GpioGpio24), + 25 => Ok(Self::GpioGpio25), + 26 => Ok(Self::GpioGpio26), + 27 => Ok(Self::GpioGpio27), + 28 => Ok(Self::GpioGpio28), + 29 => Ok(Self::GpioGpio29), + 30 => Ok(Self::GpioGpio30), + 31 => Ok(Self::GpioGpio31), + 32 => Ok(Self::I2c0Sda), + 33 => Ok(Self::I2c0Scl), + 34 => Ok(Self::I2c1Sda), + 35 => Ok(Self::I2c1Scl), + 36 => Ok(Self::I2c2Sda), + 37 => Ok(Self::I2c2Scl), + 38 => Ok(Self::SpiHost1Sd0), + 39 => Ok(Self::SpiHost1Sd1), + 40 => Ok(Self::SpiHost1Sd2), + 41 => Ok(Self::SpiHost1Sd3), + 42 => Ok(Self::Uart0Rx), + 43 => Ok(Self::Uart1Rx), + 44 => Ok(Self::Uart2Rx), + 45 => Ok(Self::Uart3Rx), + 46 => Ok(Self::SpiDeviceTpmCsb), + 47 => Ok(Self::FlashCtrlTck), + 48 => Ok(Self::FlashCtrlTms), + 49 => Ok(Self::FlashCtrlTdi), + 50 => Ok(Self::SysrstCtrlAonAcPresent), + 51 => Ok(Self::SysrstCtrlAonKey0In), + 52 => Ok(Self::SysrstCtrlAonKey1In), + 53 => Ok(Self::SysrstCtrlAonKey2In), + 54 => Ok(Self::SysrstCtrlAonPwrbIn), + 55 => Ok(Self::SysrstCtrlAonLidOpen), + 56 => Ok(Self::UsbdevSense), + _ => Err(val), + } + } +} + +/// Pinmux MIO Input Selector. +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum PinmuxInsel { + /// Tie constantly to zero + ConstantZero = 0, + /// Tie constantly to one + ConstantOne = 1, + /// MIO Pad 0 + Ioa0 = 2, + /// MIO Pad 1 + Ioa1 = 3, + /// MIO Pad 2 + Ioa2 = 4, + /// MIO Pad 3 + Ioa3 = 5, + /// MIO Pad 4 + Ioa4 = 6, + /// MIO Pad 5 + Ioa5 = 7, + /// MIO Pad 6 + Ioa6 = 8, + /// MIO Pad 7 + Ioa7 = 9, + /// MIO Pad 8 + Ioa8 = 10, + /// MIO Pad 9 + Iob0 = 11, + /// MIO Pad 10 + Iob1 = 12, + /// MIO Pad 11 + Iob2 = 13, + /// MIO Pad 12 + Iob3 = 14, + /// MIO Pad 13 + Iob4 = 15, + /// MIO Pad 14 + Iob5 = 16, + /// MIO Pad 15 + Iob6 = 17, + /// MIO Pad 16 + Iob7 = 18, + /// MIO Pad 17 + Iob8 = 19, + /// MIO Pad 18 + Iob9 = 20, + /// MIO Pad 19 + Iob10 = 21, + /// MIO Pad 20 + Iob11 = 22, + /// MIO Pad 21 + Iob12 = 23, + /// MIO Pad 22 + Ioc0 = 24, + /// MIO Pad 23 + Ioc1 = 25, + /// MIO Pad 24 + Ioc2 = 26, + /// MIO Pad 25 + Ioc3 = 27, + /// MIO Pad 26 + Ioc4 = 28, + /// MIO Pad 27 + Ioc5 = 29, + /// MIO Pad 28 + Ioc6 = 30, + /// MIO Pad 29 + Ioc7 = 31, + /// MIO Pad 30 + Ioc8 = 32, + /// MIO Pad 31 + Ioc9 = 33, + /// MIO Pad 32 + Ioc10 = 34, + /// MIO Pad 33 + Ioc11 = 35, + /// MIO Pad 34 + Ioc12 = 36, + /// MIO Pad 35 + Ior0 = 37, + /// MIO Pad 36 + Ior1 = 38, + /// MIO Pad 37 + Ior2 = 39, + /// MIO Pad 38 + Ior3 = 40, + /// MIO Pad 39 + Ior4 = 41, + /// MIO Pad 40 + Ior5 = 42, + /// MIO Pad 41 + Ior6 = 43, + /// MIO Pad 42 + Ior7 = 44, + /// MIO Pad 43 + Ior10 = 45, + /// MIO Pad 44 + Ior11 = 46, + /// MIO Pad 45 + Ior12 = 47, + /// MIO Pad 46 + Ior13 = 48, +} + +impl TryFrom for PinmuxInsel { + type Error = u32; + fn try_from(val: u32) -> Result { + match val { + 0 => Ok(Self::ConstantZero), + 1 => Ok(Self::ConstantOne), + 2 => Ok(Self::Ioa0), + 3 => Ok(Self::Ioa1), + 4 => Ok(Self::Ioa2), + 5 => Ok(Self::Ioa3), + 6 => Ok(Self::Ioa4), + 7 => Ok(Self::Ioa5), + 8 => Ok(Self::Ioa6), + 9 => Ok(Self::Ioa7), + 10 => Ok(Self::Ioa8), + 11 => Ok(Self::Iob0), + 12 => Ok(Self::Iob1), + 13 => Ok(Self::Iob2), + 14 => Ok(Self::Iob3), + 15 => Ok(Self::Iob4), + 16 => Ok(Self::Iob5), + 17 => Ok(Self::Iob6), + 18 => Ok(Self::Iob7), + 19 => Ok(Self::Iob8), + 20 => Ok(Self::Iob9), + 21 => Ok(Self::Iob10), + 22 => Ok(Self::Iob11), + 23 => Ok(Self::Iob12), + 24 => Ok(Self::Ioc0), + 25 => Ok(Self::Ioc1), + 26 => Ok(Self::Ioc2), + 27 => Ok(Self::Ioc3), + 28 => Ok(Self::Ioc4), + 29 => Ok(Self::Ioc5), + 30 => Ok(Self::Ioc6), + 31 => Ok(Self::Ioc7), + 32 => Ok(Self::Ioc8), + 33 => Ok(Self::Ioc9), + 34 => Ok(Self::Ioc10), + 35 => Ok(Self::Ioc11), + 36 => Ok(Self::Ioc12), + 37 => Ok(Self::Ior0), + 38 => Ok(Self::Ior1), + 39 => Ok(Self::Ior2), + 40 => Ok(Self::Ior3), + 41 => Ok(Self::Ior4), + 42 => Ok(Self::Ior5), + 43 => Ok(Self::Ior6), + 44 => Ok(Self::Ior7), + 45 => Ok(Self::Ior10), + 46 => Ok(Self::Ior11), + 47 => Ok(Self::Ior12), + 48 => Ok(Self::Ior13), + _ => Err(val), + } + } +} + +/// Pinmux MIO Output. +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum PinmuxMioOut { + /// MIO Pad 0 + Ioa0 = 0, + /// MIO Pad 1 + Ioa1 = 1, + /// MIO Pad 2 + Ioa2 = 2, + /// MIO Pad 3 + Ioa3 = 3, + /// MIO Pad 4 + Ioa4 = 4, + /// MIO Pad 5 + Ioa5 = 5, + /// MIO Pad 6 + Ioa6 = 6, + /// MIO Pad 7 + Ioa7 = 7, + /// MIO Pad 8 + Ioa8 = 8, + /// MIO Pad 9 + Iob0 = 9, + /// MIO Pad 10 + Iob1 = 10, + /// MIO Pad 11 + Iob2 = 11, + /// MIO Pad 12 + Iob3 = 12, + /// MIO Pad 13 + Iob4 = 13, + /// MIO Pad 14 + Iob5 = 14, + /// MIO Pad 15 + Iob6 = 15, + /// MIO Pad 16 + Iob7 = 16, + /// MIO Pad 17 + Iob8 = 17, + /// MIO Pad 18 + Iob9 = 18, + /// MIO Pad 19 + Iob10 = 19, + /// MIO Pad 20 + Iob11 = 20, + /// MIO Pad 21 + Iob12 = 21, + /// MIO Pad 22 + Ioc0 = 22, + /// MIO Pad 23 + Ioc1 = 23, + /// MIO Pad 24 + Ioc2 = 24, + /// MIO Pad 25 + Ioc3 = 25, + /// MIO Pad 26 + Ioc4 = 26, + /// MIO Pad 27 + Ioc5 = 27, + /// MIO Pad 28 + Ioc6 = 28, + /// MIO Pad 29 + Ioc7 = 29, + /// MIO Pad 30 + Ioc8 = 30, + /// MIO Pad 31 + Ioc9 = 31, + /// MIO Pad 32 + Ioc10 = 32, + /// MIO Pad 33 + Ioc11 = 33, + /// MIO Pad 34 + Ioc12 = 34, + /// MIO Pad 35 + Ior0 = 35, + /// MIO Pad 36 + Ior1 = 36, + /// MIO Pad 37 + Ior2 = 37, + /// MIO Pad 38 + Ior3 = 38, + /// MIO Pad 39 + Ior4 = 39, + /// MIO Pad 40 + Ior5 = 40, + /// MIO Pad 41 + Ior6 = 41, + /// MIO Pad 42 + Ior7 = 42, + /// MIO Pad 43 + Ior10 = 43, + /// MIO Pad 44 + Ior11 = 44, + /// MIO Pad 45 + Ior12 = 45, + /// MIO Pad 46 + Ior13 = 46, +} + +impl TryFrom for PinmuxMioOut { + type Error = u32; + fn try_from(val: u32) -> Result { + match val { + 0 => Ok(Self::Ioa0), + 1 => Ok(Self::Ioa1), + 2 => Ok(Self::Ioa2), + 3 => Ok(Self::Ioa3), + 4 => Ok(Self::Ioa4), + 5 => Ok(Self::Ioa5), + 6 => Ok(Self::Ioa6), + 7 => Ok(Self::Ioa7), + 8 => Ok(Self::Ioa8), + 9 => Ok(Self::Iob0), + 10 => Ok(Self::Iob1), + 11 => Ok(Self::Iob2), + 12 => Ok(Self::Iob3), + 13 => Ok(Self::Iob4), + 14 => Ok(Self::Iob5), + 15 => Ok(Self::Iob6), + 16 => Ok(Self::Iob7), + 17 => Ok(Self::Iob8), + 18 => Ok(Self::Iob9), + 19 => Ok(Self::Iob10), + 20 => Ok(Self::Iob11), + 21 => Ok(Self::Iob12), + 22 => Ok(Self::Ioc0), + 23 => Ok(Self::Ioc1), + 24 => Ok(Self::Ioc2), + 25 => Ok(Self::Ioc3), + 26 => Ok(Self::Ioc4), + 27 => Ok(Self::Ioc5), + 28 => Ok(Self::Ioc6), + 29 => Ok(Self::Ioc7), + 30 => Ok(Self::Ioc8), + 31 => Ok(Self::Ioc9), + 32 => Ok(Self::Ioc10), + 33 => Ok(Self::Ioc11), + 34 => Ok(Self::Ioc12), + 35 => Ok(Self::Ior0), + 36 => Ok(Self::Ior1), + 37 => Ok(Self::Ior2), + 38 => Ok(Self::Ior3), + 39 => Ok(Self::Ior4), + 40 => Ok(Self::Ior5), + 41 => Ok(Self::Ior6), + 42 => Ok(Self::Ior7), + 43 => Ok(Self::Ior10), + 44 => Ok(Self::Ior11), + 45 => Ok(Self::Ior12), + 46 => Ok(Self::Ior13), + _ => Err(val), + } + } +} + +/// Pinmux Peripheral Output Selector. +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum PinmuxOutsel { + /// Tie constantly to zero + ConstantZero = 0, + /// Tie constantly to one + ConstantOne = 1, + /// Tie constantly to high-Z + ConstantHighZ = 2, + /// Peripheral Output 0 + GpioGpio0 = 3, + /// Peripheral Output 1 + GpioGpio1 = 4, + /// Peripheral Output 2 + GpioGpio2 = 5, + /// Peripheral Output 3 + GpioGpio3 = 6, + /// Peripheral Output 4 + GpioGpio4 = 7, + /// Peripheral Output 5 + GpioGpio5 = 8, + /// Peripheral Output 6 + GpioGpio6 = 9, + /// Peripheral Output 7 + GpioGpio7 = 10, + /// Peripheral Output 8 + GpioGpio8 = 11, + /// Peripheral Output 9 + GpioGpio9 = 12, + /// Peripheral Output 10 + GpioGpio10 = 13, + /// Peripheral Output 11 + GpioGpio11 = 14, + /// Peripheral Output 12 + GpioGpio12 = 15, + /// Peripheral Output 13 + GpioGpio13 = 16, + /// Peripheral Output 14 + GpioGpio14 = 17, + /// Peripheral Output 15 + GpioGpio15 = 18, + /// Peripheral Output 16 + GpioGpio16 = 19, + /// Peripheral Output 17 + GpioGpio17 = 20, + /// Peripheral Output 18 + GpioGpio18 = 21, + /// Peripheral Output 19 + GpioGpio19 = 22, + /// Peripheral Output 20 + GpioGpio20 = 23, + /// Peripheral Output 21 + GpioGpio21 = 24, + /// Peripheral Output 22 + GpioGpio22 = 25, + /// Peripheral Output 23 + GpioGpio23 = 26, + /// Peripheral Output 24 + GpioGpio24 = 27, + /// Peripheral Output 25 + GpioGpio25 = 28, + /// Peripheral Output 26 + GpioGpio26 = 29, + /// Peripheral Output 27 + GpioGpio27 = 30, + /// Peripheral Output 28 + GpioGpio28 = 31, + /// Peripheral Output 29 + GpioGpio29 = 32, + /// Peripheral Output 30 + GpioGpio30 = 33, + /// Peripheral Output 31 + GpioGpio31 = 34, + /// Peripheral Output 32 + I2c0Sda = 35, + /// Peripheral Output 33 + I2c0Scl = 36, + /// Peripheral Output 34 + I2c1Sda = 37, + /// Peripheral Output 35 + I2c1Scl = 38, + /// Peripheral Output 36 + I2c2Sda = 39, + /// Peripheral Output 37 + I2c2Scl = 40, + /// Peripheral Output 38 + SpiHost1Sd0 = 41, + /// Peripheral Output 39 + SpiHost1Sd1 = 42, + /// Peripheral Output 40 + SpiHost1Sd2 = 43, + /// Peripheral Output 41 + SpiHost1Sd3 = 44, + /// Peripheral Output 42 + Uart0Tx = 45, + /// Peripheral Output 43 + Uart1Tx = 46, + /// Peripheral Output 44 + Uart2Tx = 47, + /// Peripheral Output 45 + Uart3Tx = 48, + /// Peripheral Output 46 + PattgenPda0Tx = 49, + /// Peripheral Output 47 + PattgenPcl0Tx = 50, + /// Peripheral Output 48 + PattgenPda1Tx = 51, + /// Peripheral Output 49 + PattgenPcl1Tx = 52, + /// Peripheral Output 50 + SpiHost1Sck = 53, + /// Peripheral Output 51 + SpiHost1Csb = 54, + /// Peripheral Output 52 + FlashCtrlTdo = 55, + /// Peripheral Output 53 + SensorCtrlAstDebugOut0 = 56, + /// Peripheral Output 54 + SensorCtrlAstDebugOut1 = 57, + /// Peripheral Output 55 + SensorCtrlAstDebugOut2 = 58, + /// Peripheral Output 56 + SensorCtrlAstDebugOut3 = 59, + /// Peripheral Output 57 + SensorCtrlAstDebugOut4 = 60, + /// Peripheral Output 58 + SensorCtrlAstDebugOut5 = 61, + /// Peripheral Output 59 + SensorCtrlAstDebugOut6 = 62, + /// Peripheral Output 60 + SensorCtrlAstDebugOut7 = 63, + /// Peripheral Output 61 + SensorCtrlAstDebugOut8 = 64, + /// Peripheral Output 62 + PwmAonPwm0 = 65, + /// Peripheral Output 63 + PwmAonPwm1 = 66, + /// Peripheral Output 64 + PwmAonPwm2 = 67, + /// Peripheral Output 65 + PwmAonPwm3 = 68, + /// Peripheral Output 66 + PwmAonPwm4 = 69, + /// Peripheral Output 67 + PwmAonPwm5 = 70, + /// Peripheral Output 68 + OtpCtrlTest0 = 71, + /// Peripheral Output 69 + SysrstCtrlAonBatDisable = 72, + /// Peripheral Output 70 + SysrstCtrlAonKey0Out = 73, + /// Peripheral Output 71 + SysrstCtrlAonKey1Out = 74, + /// Peripheral Output 72 + SysrstCtrlAonKey2Out = 75, + /// Peripheral Output 73 + SysrstCtrlAonPwrbOut = 76, + /// Peripheral Output 74 + SysrstCtrlAonZ3Wakeup = 77, +} + +impl TryFrom for PinmuxOutsel { + type Error = u32; + fn try_from(val: u32) -> Result { + match val { + 0 => Ok(Self::ConstantZero), + 1 => Ok(Self::ConstantOne), + 2 => Ok(Self::ConstantHighZ), + 3 => Ok(Self::GpioGpio0), + 4 => Ok(Self::GpioGpio1), + 5 => Ok(Self::GpioGpio2), + 6 => Ok(Self::GpioGpio3), + 7 => Ok(Self::GpioGpio4), + 8 => Ok(Self::GpioGpio5), + 9 => Ok(Self::GpioGpio6), + 10 => Ok(Self::GpioGpio7), + 11 => Ok(Self::GpioGpio8), + 12 => Ok(Self::GpioGpio9), + 13 => Ok(Self::GpioGpio10), + 14 => Ok(Self::GpioGpio11), + 15 => Ok(Self::GpioGpio12), + 16 => Ok(Self::GpioGpio13), + 17 => Ok(Self::GpioGpio14), + 18 => Ok(Self::GpioGpio15), + 19 => Ok(Self::GpioGpio16), + 20 => Ok(Self::GpioGpio17), + 21 => Ok(Self::GpioGpio18), + 22 => Ok(Self::GpioGpio19), + 23 => Ok(Self::GpioGpio20), + 24 => Ok(Self::GpioGpio21), + 25 => Ok(Self::GpioGpio22), + 26 => Ok(Self::GpioGpio23), + 27 => Ok(Self::GpioGpio24), + 28 => Ok(Self::GpioGpio25), + 29 => Ok(Self::GpioGpio26), + 30 => Ok(Self::GpioGpio27), + 31 => Ok(Self::GpioGpio28), + 32 => Ok(Self::GpioGpio29), + 33 => Ok(Self::GpioGpio30), + 34 => Ok(Self::GpioGpio31), + 35 => Ok(Self::I2c0Sda), + 36 => Ok(Self::I2c0Scl), + 37 => Ok(Self::I2c1Sda), + 38 => Ok(Self::I2c1Scl), + 39 => Ok(Self::I2c2Sda), + 40 => Ok(Self::I2c2Scl), + 41 => Ok(Self::SpiHost1Sd0), + 42 => Ok(Self::SpiHost1Sd1), + 43 => Ok(Self::SpiHost1Sd2), + 44 => Ok(Self::SpiHost1Sd3), + 45 => Ok(Self::Uart0Tx), + 46 => Ok(Self::Uart1Tx), + 47 => Ok(Self::Uart2Tx), + 48 => Ok(Self::Uart3Tx), + 49 => Ok(Self::PattgenPda0Tx), + 50 => Ok(Self::PattgenPcl0Tx), + 51 => Ok(Self::PattgenPda1Tx), + 52 => Ok(Self::PattgenPcl1Tx), + 53 => Ok(Self::SpiHost1Sck), + 54 => Ok(Self::SpiHost1Csb), + 55 => Ok(Self::FlashCtrlTdo), + 56 => Ok(Self::SensorCtrlAstDebugOut0), + 57 => Ok(Self::SensorCtrlAstDebugOut1), + 58 => Ok(Self::SensorCtrlAstDebugOut2), + 59 => Ok(Self::SensorCtrlAstDebugOut3), + 60 => Ok(Self::SensorCtrlAstDebugOut4), + 61 => Ok(Self::SensorCtrlAstDebugOut5), + 62 => Ok(Self::SensorCtrlAstDebugOut6), + 63 => Ok(Self::SensorCtrlAstDebugOut7), + 64 => Ok(Self::SensorCtrlAstDebugOut8), + 65 => Ok(Self::PwmAonPwm0), + 66 => Ok(Self::PwmAonPwm1), + 67 => Ok(Self::PwmAonPwm2), + 68 => Ok(Self::PwmAonPwm3), + 69 => Ok(Self::PwmAonPwm4), + 70 => Ok(Self::PwmAonPwm5), + 71 => Ok(Self::OtpCtrlTest0), + 72 => Ok(Self::SysrstCtrlAonBatDisable), + 73 => Ok(Self::SysrstCtrlAonKey0Out), + 74 => Ok(Self::SysrstCtrlAonKey1Out), + 75 => Ok(Self::SysrstCtrlAonKey2Out), + 76 => Ok(Self::SysrstCtrlAonPwrbOut), + 77 => Ok(Self::SysrstCtrlAonZ3Wakeup), + _ => Err(val), + } + } +} + +/// Dedicated Pad Selects +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum DirectPads { + UsbdevUsbDp = 0, + UsbdevUsbDn = 1, + SpiHost0Sd0 = 2, + SpiHost0Sd1 = 3, + SpiHost0Sd2 = 4, + SpiHost0Sd3 = 5, + SpiDeviceSd0 = 6, + SpiDeviceSd1 = 7, + SpiDeviceSd2 = 8, + SpiDeviceSd3 = 9, + SysrstCtrlAonEcRstL = 10, + SysrstCtrlAonFlashWpL = 11, + SpiDeviceSck = 12, + SpiDeviceCsb = 13, + SpiHost0Sck = 14, + SpiHost0Csb = 15, +} + +impl TryFrom for DirectPads { + type Error = u32; + fn try_from(val: u32) -> Result { + match val { + 0 => Ok(Self::UsbdevUsbDp), + 1 => Ok(Self::UsbdevUsbDn), + 2 => Ok(Self::SpiHost0Sd0), + 3 => Ok(Self::SpiHost0Sd1), + 4 => Ok(Self::SpiHost0Sd2), + 5 => Ok(Self::SpiHost0Sd3), + 6 => Ok(Self::SpiDeviceSd0), + 7 => Ok(Self::SpiDeviceSd1), + 8 => Ok(Self::SpiDeviceSd2), + 9 => Ok(Self::SpiDeviceSd3), + 10 => Ok(Self::SysrstCtrlAonEcRstL), + 11 => Ok(Self::SysrstCtrlAonFlashWpL), + 12 => Ok(Self::SpiDeviceSck), + 13 => Ok(Self::SpiDeviceCsb), + 14 => Ok(Self::SpiHost0Sck), + 15 => Ok(Self::SpiHost0Csb), + _ => Err(val), + } + } +} + +/// Muxed Pad Selects +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum MuxedPads { + Ioa0 = 0, + Ioa1 = 1, + Ioa2 = 2, + Ioa3 = 3, + Ioa4 = 4, + Ioa5 = 5, + Ioa6 = 6, + Ioa7 = 7, + Ioa8 = 8, + Iob0 = 9, + Iob1 = 10, + Iob2 = 11, + Iob3 = 12, + Iob4 = 13, + Iob5 = 14, + Iob6 = 15, + Iob7 = 16, + Iob8 = 17, + Iob9 = 18, + Iob10 = 19, + Iob11 = 20, + Iob12 = 21, + Ioc0 = 22, + Ioc1 = 23, + Ioc2 = 24, + Ioc3 = 25, + Ioc4 = 26, + Ioc5 = 27, + Ioc6 = 28, + Ioc7 = 29, + Ioc8 = 30, + Ioc9 = 31, + Ioc10 = 32, + Ioc11 = 33, + Ioc12 = 34, + Ior0 = 35, + Ior1 = 36, + Ior2 = 37, + Ior3 = 38, + Ior4 = 39, + Ior5 = 40, + Ior6 = 41, + Ior7 = 42, + Ior10 = 43, + Ior11 = 44, + Ior12 = 45, + Ior13 = 46, +} + +impl TryFrom for MuxedPads { + type Error = u32; + fn try_from(val: u32) -> Result { + match val { + 0 => Ok(Self::Ioa0), + 1 => Ok(Self::Ioa1), + 2 => Ok(Self::Ioa2), + 3 => Ok(Self::Ioa3), + 4 => Ok(Self::Ioa4), + 5 => Ok(Self::Ioa5), + 6 => Ok(Self::Ioa6), + 7 => Ok(Self::Ioa7), + 8 => Ok(Self::Ioa8), + 9 => Ok(Self::Iob0), + 10 => Ok(Self::Iob1), + 11 => Ok(Self::Iob2), + 12 => Ok(Self::Iob3), + 13 => Ok(Self::Iob4), + 14 => Ok(Self::Iob5), + 15 => Ok(Self::Iob6), + 16 => Ok(Self::Iob7), + 17 => Ok(Self::Iob8), + 18 => Ok(Self::Iob9), + 19 => Ok(Self::Iob10), + 20 => Ok(Self::Iob11), + 21 => Ok(Self::Iob12), + 22 => Ok(Self::Ioc0), + 23 => Ok(Self::Ioc1), + 24 => Ok(Self::Ioc2), + 25 => Ok(Self::Ioc3), + 26 => Ok(Self::Ioc4), + 27 => Ok(Self::Ioc5), + 28 => Ok(Self::Ioc6), + 29 => Ok(Self::Ioc7), + 30 => Ok(Self::Ioc8), + 31 => Ok(Self::Ioc9), + 32 => Ok(Self::Ioc10), + 33 => Ok(Self::Ioc11), + 34 => Ok(Self::Ioc12), + 35 => Ok(Self::Ior0), + 36 => Ok(Self::Ior1), + 37 => Ok(Self::Ior2), + 38 => Ok(Self::Ior3), + 39 => Ok(Self::Ior4), + 40 => Ok(Self::Ior5), + 41 => Ok(Self::Ior6), + 42 => Ok(Self::Ior7), + 43 => Ok(Self::Ior10), + 44 => Ok(Self::Ior11), + 45 => Ok(Self::Ior12), + 46 => Ok(Self::Ior13), + _ => Err(val), + } + } +} + +/// Power Manager Wakeup Signals +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum PowerManagerWakeUps { + SysrstCtrlAonWkupReq = 0, + AdcCtrlAonWkupReq = 1, + PinmuxAonPinWkupReq = 2, + PinmuxAonUsbWkupReq = 3, + AonTimerAonWkupReq = 4, + SensorCtrlWkupReq = 5, +} + +/// Reset Manager Software Controlled Resets +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum ResetManagerSwResets { + SpiDevice = 0, + SpiHost0 = 1, + SpiHost1 = 2, + Usb = 3, + UsbAon = 4, + I2c0 = 5, + I2c1 = 6, + I2c2 = 7, +} + +/// Power Manager Reset Request Signals +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum PowerManagerResetRequests { + SysrstCtrlAonRstReq = 0, + AonTimerAonAonTimerRstReq = 1, +} + +/// Clock Manager Software-Controlled ("Gated") Clocks. +/// +/// The Software has full control over these clocks. +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum GateableClocks { + /// Clock clk_io_div4_peri in group peri + IoDiv4Peri = 0, + /// Clock clk_io_div2_peri in group peri + IoDiv2Peri = 1, + /// Clock clk_io_peri in group peri + IoPeri = 2, + /// Clock clk_usb_peri in group peri + UsbPeri = 3, +} + +/// Clock Manager Software-Hinted Clocks. +/// +/// The Software has partial control over these clocks. It can ask them to stop, +/// but the clock manager is in control of whether the clock actually is stopped. +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum HintableClocks { + /// Clock clk_main_aes in group trans + MainAes = 0, + /// Clock clk_main_hmac in group trans + MainHmac = 1, + /// Clock clk_main_kmac in group trans + MainKmac = 2, + /// Clock clk_main_otbn in group trans + MainOtbn = 3, +} + +/// MMIO Region +/// +/// MMIO region excludes any memory that is separate from the module +/// configuration space, i.e. ROM, main SRAM, and flash are excluded but +/// retention SRAM, spi_device memory, or usbdev memory are included. +pub const MMIO_BASE_ADDR: usize = 0x40000000; +pub const MMIO_SIZE_BYTES: usize = 0x10000000; diff --git a/chips/earlgrey/src/spi_host.rs b/chips/earlgrey/src/spi_host.rs new file mode 100644 index 0000000000..47da1ceb00 --- /dev/null +++ b/chips/earlgrey/src/spi_host.rs @@ -0,0 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::registers::top_earlgrey::{SPI_HOST0_BASE_ADDR, SPI_HOST1_BASE_ADDR}; +use kernel::utilities::StaticRef; +use lowrisc::spi_host::SpiHostRegisters; + +pub const SPIHOST0_BASE: StaticRef = + unsafe { StaticRef::new(SPI_HOST0_BASE_ADDR as *const SpiHostRegisters) }; + +pub const SPIHOST1_BASE: StaticRef = + unsafe { StaticRef::new(SPI_HOST1_BASE_ADDR as *const SpiHostRegisters) }; diff --git a/chips/earlgrey/src/timer.rs b/chips/earlgrey/src/timer.rs index 5b3d3b1238..fcba5b61b7 100644 --- a/chips/earlgrey/src/timer.rs +++ b/chips/earlgrey/src/timer.rs @@ -1,6 +1,12 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Timer driver. -use crate::chip_config::CONFIG; +use crate::chip_config::EarlGreyConfig; +use crate::registers::top_earlgrey::RV_TIMER_BASE_ADDR; +use core::marker::PhantomData; use kernel::hil::time::{self, Ticks64}; use kernel::utilities::cells::OptionalCell; use kernel::utilities::registers::interfaces::{Readable, Writeable}; @@ -9,8 +15,6 @@ use kernel::utilities::StaticRef; use kernel::ErrorCode; use rv32i::machine_timer::MachineTimer; -const PRESCALE: u16 = ((CONFIG.cpu_freq / 10_000) - 1) as u16; // 10Khz - /// 10KHz `Frequency` #[derive(Debug)] pub struct Freq10KHz; @@ -24,20 +28,15 @@ register_structs! { pub TimerRegisters { (0x000 => alert_test: WriteOnly), (0x004 => ctrl: ReadWrite), - (0x008 => _reserved), - - (0x100 => config: ReadWrite), - - (0x104 => value_low: ReadWrite), - (0x108 => value_high: ReadWrite), - - (0x10c => compare_low: ReadWrite), - (0x110 => compare_high: ReadWrite), - - (0x114 => intr_enable: ReadWrite), - (0x118 => intr_state: ReadWrite), - (0x11c => intr_test: WriteOnly), + (0x100 => intr_enable: ReadWrite), + (0x104 => intr_state: ReadWrite), + (0x108 => intr_test: WriteOnly), + (0x10C => config: ReadWrite), + (0x110 => value_low: ReadWrite), + (0x114 => value_high: ReadWrite), + (0x118 => compare_low: ReadWrite), + (0x11C => compare_high: ReadWrite), (0x120 => @END), } } @@ -46,23 +45,24 @@ register_bitfields![u32, ctrl [ enable OFFSET(0) NUMBITS(1) [] ], + intr [ + timer0 OFFSET(0) NUMBITS(1) [] + ], config [ prescale OFFSET(0) NUMBITS(12) [], step OFFSET(16) NUMBITS(8) [] ], - intr [ - timer0 OFFSET(0) NUMBITS(1) [] - ] ]; -pub struct RvTimer<'a> { +pub struct RvTimer<'a, CFG: EarlGreyConfig> { registers: StaticRef, alarm_client: OptionalCell<&'a dyn time::AlarmClient>, overflow_client: OptionalCell<&'a dyn time::OverflowClient>, mtimer: MachineTimer<'a>, + _cfg: PhantomData, } -impl<'a> RvTimer<'a> { +impl<'a, CFG: EarlGreyConfig> RvTimer<'a, CFG> { pub fn new() -> Self { Self { registers: TIMER_BASE, @@ -74,14 +74,17 @@ impl<'a> RvTimer<'a> { &TIMER_BASE.value_low, &TIMER_BASE.value_high, ), + _cfg: PhantomData, } } pub fn setup(&self) { + let prescale: u16 = ((CFG::CPU_FREQ / 10_000) - 1) as u16; // 10Khz + let regs = self.registers; // Set proper prescaler and the like regs.config - .write(config::prescale.val(PRESCALE as u32) + config::step.val(1u32)); + .write(config::prescale.val(prescale as u32) + config::step.val(1u32)); regs.compare_high.set(0); regs.value_low.set(0xFFFF_0000); regs.intr_enable.write(intr::timer0::CLEAR); @@ -98,7 +101,7 @@ impl<'a> RvTimer<'a> { } } -impl time::Time for RvTimer<'_> { +impl time::Time for RvTimer<'_, CFG> { type Frequency = Freq10KHz; type Ticks = Ticks64; @@ -107,7 +110,7 @@ impl time::Time for RvTimer<'_> { } } -impl<'a> time::Counter<'a> for RvTimer<'a> { +impl<'a, CFG: EarlGreyConfig> time::Counter<'a> for RvTimer<'a, CFG> { fn set_overflow_client(&self, client: &'a dyn time::OverflowClient) { self.overflow_client.set(client); } @@ -131,7 +134,7 @@ impl<'a> time::Counter<'a> for RvTimer<'a> { } } -impl<'a> time::Alarm<'a> for RvTimer<'a> { +impl<'a, CFG: EarlGreyConfig> time::Alarm<'a> for RvTimer<'a, CFG> { fn set_alarm_client(&self, client: &'a dyn time::AlarmClient) { self.alarm_client.set(client); } @@ -162,4 +165,4 @@ impl<'a> time::Alarm<'a> for RvTimer<'a> { } const TIMER_BASE: StaticRef = - unsafe { StaticRef::new(0x4010_0000 as *const TimerRegisters) }; + unsafe { StaticRef::new(RV_TIMER_BASE_ADDR as *const TimerRegisters) }; diff --git a/chips/earlgrey/src/uart.rs b/chips/earlgrey/src/uart.rs index 67f1534bca..e9b2c1f83d 100644 --- a/chips/earlgrey/src/uart.rs +++ b/chips/earlgrey/src/uart.rs @@ -1,10 +1,12 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::utilities::StaticRef; +use lowrisc::registers::uart_regs::UartRegisters; pub use lowrisc::uart::Uart; -use lowrisc::uart::UartRegisters; - -use crate::chip_config::CONFIG; -pub const UART0_BAUDRATE: u32 = CONFIG.uart_baudrate; +use crate::registers::top_earlgrey::UART0_BASE_ADDR; pub const UART0_BASE: StaticRef = - unsafe { StaticRef::new(0x4000_0000 as *const UartRegisters) }; + unsafe { StaticRef::new(UART0_BASE_ADDR as *const UartRegisters) }; diff --git a/chips/earlgrey/src/usbdev.rs b/chips/earlgrey/src/usbdev.rs index 5d8ed0381d..db8100cfd1 100644 --- a/chips/earlgrey/src/usbdev.rs +++ b/chips/earlgrey/src/usbdev.rs @@ -1,6 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::registers::top_earlgrey::USBDEV_BASE_ADDR; use kernel::utilities::StaticRef; pub use lowrisc::usbdev::Usb; use lowrisc::usbdev::UsbRegisters; pub const USB0_BASE: StaticRef = - unsafe { StaticRef::new(0x4015_0000 as *const UsbRegisters) }; + unsafe { StaticRef::new(USBDEV_BASE_ADDR as *const UsbRegisters) }; diff --git a/chips/esp32-c3/Cargo.toml b/chips/esp32-c3/Cargo.toml index d6e92a3e7f..e3b8db681c 100644 --- a/chips/esp32-c3/Cargo.toml +++ b/chips/esp32-c3/Cargo.toml @@ -1,11 +1,18 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "esp32-c3" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] esp32 = { path = "../esp32" } rv32i = { path = "../../arch/rv32i" } kernel = { path = "../../kernel" } + +[lints] +workspace = true diff --git a/chips/esp32-c3/src/chip.rs b/chips/esp32-c3/src/chip.rs index abad6fe988..7a000c20fd 100644 --- a/chips/esp32-c3/src/chip.rs +++ b/chips/esp32-c3/src/chip.rs @@ -1,18 +1,23 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! High-level setup and interrupt mapping for the chip. use core::fmt::Write; +use core::ptr::addr_of; -use kernel; use kernel::platform::chip::{Chip, InterruptService}; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; use kernel::utilities::StaticRef; use rv32i::csr::{self, mcause, mtvec::mtvec, CSR}; -use rv32i::pmp::PMP; +use rv32i::pmp::{simple::SimplePMP, PMPUserMPU}; use rv32i::syscall::SysCall; use crate::intc::{Intc, IntcRegisters}; use crate::interrupts; +use crate::rng; use crate::sysreg; use crate::timg; @@ -21,9 +26,9 @@ pub const INTC_BASE: StaticRef = pub static mut INTC: Intc = Intc::new(INTC_BASE); -pub struct Esp32C3<'a, I: InterruptService<()> + 'a> { +pub struct Esp32C3<'a, I: InterruptService + 'a> { userspace_kernel_boundary: SysCall, - pub pmp: PMP<8>, + pub pmp: PMPUserMPU<8, SimplePMP<16>>, intc: &'a Intc, pic_interrupt_service: &'a I, } @@ -35,6 +40,7 @@ pub struct Esp32C3DefaultPeripherals<'a> { pub gpio: esp32::gpio::Port<'a>, pub rtc_cntl: esp32::rtc_cntl::RtcCntl, pub sysreg: sysreg::SysReg, + pub rng: rng::Rng<'a>, } impl<'a> Esp32C3DefaultPeripherals<'a> { @@ -46,41 +52,37 @@ impl<'a> Esp32C3DefaultPeripherals<'a> { gpio: esp32::gpio::Port::new(), rtc_cntl: esp32::rtc_cntl::RtcCntl::new(esp32::rtc_cntl::RTC_CNTL_BASE), sysreg: sysreg::SysReg::new(), + rng: rng::Rng::new(), } } + + pub fn init(&'static self) { + kernel::deferred_call::DeferredCallClient::register(&self.rng); + } } -impl<'a> InterruptService<()> for Esp32C3DefaultPeripherals<'a> { +impl<'a> InterruptService for Esp32C3DefaultPeripherals<'a> { unsafe fn service_interrupt(&self, interrupt: u32) -> bool { match interrupt { - interrupts::IRQ_UART0 => { - self.uart0.handle_interrupt(); - } - interrupts::IRQ_TIMER1 => { - self.timg0.handle_interrupt(); - } - interrupts::IRQ_TIMER2 => { - self.timg1.handle_interrupt(); - } - interrupts::IRQ_GPIO | interrupts::IRQ_GPIO_NMI => { - self.gpio.handle_interrupt(); - } + interrupts::IRQ_UART0 => self.uart0.handle_interrupt(), + + interrupts::IRQ_TIMER1 => self.timg0.handle_interrupt(), + interrupts::IRQ_TIMER2 => self.timg1.handle_interrupt(), + + interrupts::IRQ_GPIO | interrupts::IRQ_GPIO_NMI => self.gpio.handle_interrupt(), + _ => return false, } true } - - unsafe fn service_deferred_call(&self, _: ()) -> bool { - false - } } -impl<'a, I: InterruptService<()> + 'a> Esp32C3<'a, I> { +impl<'a, I: InterruptService + 'a> Esp32C3<'a, I> { pub unsafe fn new(pic_interrupt_service: &'a I) -> Self { Self { userspace_kernel_boundary: SysCall::new(), - pmp: PMP::new(), - intc: &INTC, + pmp: PMPUserMPU::new(SimplePMP::new().unwrap()), + intc: &*addr_of!(INTC), pic_interrupt_service, } } @@ -106,18 +108,10 @@ impl<'a, I: InterruptService<()> + 'a> Esp32C3<'a, I> { } } -impl<'a, I: InterruptService<()> + 'a> Chip for Esp32C3<'a, I> { - type MPU = PMP<8>; +impl<'a, I: InterruptService + 'a> Chip for Esp32C3<'a, I> { + type MPU = PMPUserMPU<8, SimplePMP<16>>; type UserspaceKernelBoundary = SysCall; - fn mpu(&self) -> &Self::MPU { - &self.pmp - } - - fn userspace_kernel_boundary(&self) -> &SysCall { - &self.userspace_kernel_boundary - } - fn service_pending_interrupts(&self) { loop { if self.intc.get_saved_interrupts().is_some() { @@ -138,6 +132,14 @@ impl<'a, I: InterruptService<()> + 'a> Chip for Esp32C3<'a, I> { self.intc.get_saved_interrupts().is_some() } + fn mpu(&self) -> &Self::MPU { + &self.pmp + } + + fn userspace_kernel_boundary(&self) -> &SysCall { + &self.userspace_kernel_boundary + } + fn sleep(&self) { unsafe { rv32i::support::wfi(); @@ -286,7 +288,7 @@ pub unsafe fn configure_trap_handler() { // Mock implementation for crate tests that does not include the section // specifier, as the test will not use our linker script, and the host // compilation environment may not allow the section name. -#[cfg(not(any(target_arch = "riscv32", target_os = "none")))] +#[cfg(not(all(target_arch = "riscv32", target_os = "none")))] pub extern "C" fn _start_trap_vectored() { use core::hint::unreachable_unchecked; unsafe { @@ -295,15 +297,18 @@ pub extern "C" fn _start_trap_vectored() { } #[cfg(all(target_arch = "riscv32", target_os = "none"))] -#[link_section = ".riscv.trap_vectored"] -#[export_name = "_start_trap_vectored"] -#[naked] -pub extern "C" fn _start_trap_vectored() -> ! { - unsafe { - // Below are 32 (non-compressed) jumps to cover the entire possible - // range of vectored traps. - asm!( - " +extern "C" { + pub fn _start_trap_vectored(); +} + +#[cfg(all(target_arch = "riscv32", target_os = "none"))] +// Below are 32 (non-compressed) jumps to cover the entire possible +// range of vectored traps. +core::arch::global_asm!( + " + .section .riscv.trap_vectored, \"ax\" + .globl _start_trap_vectored + _start_trap_vectored: j _start_trap j _start_trap j _start_trap @@ -335,8 +340,5 @@ pub extern "C" fn _start_trap_vectored() -> ! { j _start_trap j _start_trap j _start_trap - ", - options(noreturn) - ); - } -} + " +); diff --git a/chips/esp32-c3/src/intc.rs b/chips/esp32-c3/src/intc.rs index adb8425b7e..ae458d10ae 100644 --- a/chips/esp32-c3/src/intc.rs +++ b/chips/esp32-c3/src/intc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Platform Level Interrupt Control peripheral driver. use crate::interrupts; diff --git a/chips/esp32-c3/src/interrupts.rs b/chips/esp32-c3/src/interrupts.rs index d4a9c03728..bc3ac47c2a 100644 --- a/chips/esp32-c3/src/interrupts.rs +++ b/chips/esp32-c3/src/interrupts.rs @@ -1,4 +1,8 @@ -//! Named interrupts for the ESP32C3 chip. +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Named interrupts for the ESP32-C3 chip. //! This matches what the HAL uses #![allow(dead_code)] diff --git a/chips/esp32-c3/src/lib.rs b/chips/esp32-c3/src/lib.rs index f81fc99c90..c9e18041af 100644 --- a/chips/esp32-c3/src/lib.rs +++ b/chips/esp32-c3/src/lib.rs @@ -1,6 +1,9 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Drivers and chip support for ESP32-C3. -#![feature(const_fn_trait_bound, naked_functions, asm)] #![no_std] #![crate_name = "esp32_c3"] #![crate_type = "rlib"] @@ -8,6 +11,7 @@ pub mod chip; pub mod intc; pub mod interrupts; +pub mod rng; pub mod sysreg; pub mod timg { diff --git a/chips/esp32-c3/src/rng.rs b/chips/esp32-c3/src/rng.rs new file mode 100644 index 0000000000..3037427240 --- /dev/null +++ b/chips/esp32-c3/src/rng.rs @@ -0,0 +1,79 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; +use kernel::hil::entropy::{self, Client32, Continue, Entropy32}; +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::registers::interfaces::Readable; +use kernel::utilities::registers::{register_structs, ReadOnly}; +use kernel::utilities::StaticRef; +use kernel::ErrorCode; + +const RNG_DATA_REG: StaticRef = + unsafe { StaticRef::new(0x6002_60B0 as *const RngRegister) }; + +register_structs! { + pub RngRegister { + (0x0 => data: ReadOnly), + (0x4 => @END), + } +} + +pub struct Rng<'a> { + register: StaticRef, + client: OptionalCell<&'a dyn entropy::Client32>, + value: OptionalCell, + deferred_call: DeferredCall, +} + +impl<'a> Rng<'_> { + pub fn new() -> Rng<'a> { + Rng { + register: RNG_DATA_REG, + client: OptionalCell::empty(), + value: OptionalCell::empty(), + deferred_call: DeferredCall::new(), + } + } +} + +impl<'a> DeferredCallClient for Rng<'a> { + fn register(&'static self) { + self.deferred_call.register(self); + } + + fn handle_deferred_call(&self) { + self.value.set(self.register.data.get()); + self.client.map(|client| { + if let Continue::More = client.entropy_available(&mut RngIter(self), Ok(())) { + self.deferred_call.set(); + }; + }); + } +} + +impl<'a> Entropy32<'a> for Rng<'a> { + fn get(&self) -> Result<(), ErrorCode> { + self.deferred_call.set(); + Ok(()) + } + + fn cancel(&self) -> Result<(), ErrorCode> { + Err(ErrorCode::NOSUPPORT) + } + + fn set_client(&self, client: &'a dyn Client32) { + self.client.set(client); + } +} + +struct RngIter<'a, 'b: 'a>(&'a Rng<'b>); + +impl Iterator for RngIter<'_, '_> { + type Item = u32; + + fn next(&mut self) -> Option { + self.0.value.take() + } +} diff --git a/chips/esp32-c3/src/sysreg.rs b/chips/esp32-c3/src/sysreg.rs index 33b687cb47..e9b4f81f7c 100644 --- a/chips/esp32-c3/src/sysreg.rs +++ b/chips/esp32-c3/src/sysreg.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SysReg driver. use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; diff --git a/chips/esp32/Cargo.toml b/chips/esp32/Cargo.toml index e633b0d73c..b0bc71094c 100644 --- a/chips/esp32/Cargo.toml +++ b/chips/esp32/Cargo.toml @@ -1,8 +1,15 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "esp32" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] kernel = { path = "../../kernel" } + +[lints] +workspace = true diff --git a/chips/esp32/src/gpio.rs b/chips/esp32/src/gpio.rs index d8fbd23f02..9e7786e4d1 100644 --- a/chips/esp32/src/gpio.rs +++ b/chips/esp32/src/gpio.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! GPIO driver. use core::ops::{Index, IndexMut}; @@ -119,7 +123,12 @@ register_bitfields![u32, register_bitfields![u32, IO_MUX_GPIO [ FILTER_EN OFFSET(15) NUMBITS(1) [], - MCU_SEL OFFSET(12) NUMBITS(3) [], + MCU_SEL OFFSET(12) NUMBITS(3) [ + FUN_0 = 0, + FUN_1 = 1, + FUN_2 = 2, + FUN_3 = 3 + ], FUN_IE OFFSET(9) NUMBITS(1) [], FUN_WPU OFFSET(8) NUMBITS(1) [], FUN_WPD OFFSET(7) NUMBITS(1) [], @@ -211,6 +220,12 @@ impl gpio::Configure for GpioPin<'_> { } fn make_output(&self) -> gpio::Configuration { + // Setting peripheral index 128 causes GPIO_OUT_REG and GPIO_ENABLE_REG to enable for the given pin + self.registers.func_out_sel_cfg[self.pin.shift].set(0x80); + + // IO Mux function 1 on all pins is GPIO, no alternate peripherals (see table 5-2 in ESP32 datasheet) + self.iomux_registers.gpio[self.pin.shift].modify(IO_MUX_GPIO::MCU_SEL::FUN_1); + self.registers .enable_w1ts .set(self.pin.mask << self.pin.shift); @@ -229,7 +244,7 @@ impl gpio::Configure for GpioPin<'_> { } fn disable_input(&self) -> gpio::Configuration { - /* We can't do this from the GPIO contorller. + /* We can't do this from the GPIO controller. * It does look like the IO Mux is capable of this * though. */ diff --git a/chips/esp32/src/lib.rs b/chips/esp32/src/lib.rs index 7ced1c0220..b299d40e62 100644 --- a/chips/esp32/src/lib.rs +++ b/chips/esp32/src/lib.rs @@ -1,6 +1,9 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Drivers and chip support for Espressif ESP32 boards. -#![feature(const_fn_trait_bound)] #![no_std] #![crate_name = "esp32"] #![crate_type = "rlib"] diff --git a/chips/esp32/src/rtc_cntl.rs b/chips/esp32/src/rtc_cntl.rs index db78a5be12..9cfe1e44b0 100644 --- a/chips/esp32/src/rtc_cntl.rs +++ b/chips/esp32/src/rtc_cntl.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Low Power Management driver. use kernel::platform::watchdog::WatchDog; @@ -13,7 +17,7 @@ register_structs! { (0x000 => options0: ReadWrite), (0x004 => slp_timer0: ReadWrite), (0x008 => slp_timer1: ReadWrite), - (0x00C => time_updaet: ReadWrite), + (0x00C => time_update: ReadWrite), (0x010 => time_low0: ReadWrite), (0x014 => time_high0: ReadWrite), (0x018 => state0: ReadWrite), @@ -38,7 +42,7 @@ register_structs! { (0x064 => ext_wakeup_conf: ReadWrite), (0x068 => slp_reject_conf: ReadWrite), (0x06C => cpu_period_conf: ReadWrite), - (0x070 => clk_conf: ReadWrite), + (0x070 => clk_conf: ReadWrite), (0x074 => slow_clk_conf: ReadWrite), (0x078 => sdio_conf: ReadWrite), (0x07C => bias_conf: ReadWrite), @@ -63,7 +67,6 @@ register_structs! { (0x0C8 => low_power_st: ReadWrite), (0x0CC => daig0: ReadWrite), (0x0D0 => pad_hold: ReadWrite), - (0x0D4 => _reserved0), (0x10C => fib_sel: ReadWrite), (0x110 => @END), @@ -71,6 +74,9 @@ register_structs! { } register_bitfields![u32, + CLK_CONF [ + DIG_FOSC_EN OFFSET(10) NUMBITS(1) [], + ], WDTCONFIG0 [ CHIP_RESET_EN OFFSET(8) NUMBITS(1) [], PAUSE_INSLEEP OFFSET(9) NUMBITS(1) [], @@ -101,7 +107,7 @@ pub struct RtcCntl { registers: StaticRef, } -impl<'a> RtcCntl { +impl RtcCntl { pub const fn new(base: StaticRef) -> RtcCntl { Self { registers: base } } @@ -150,6 +156,10 @@ impl<'a> RtcCntl { self.registers.swd_conf.modify(SWD_CONF::AUTO_FEED::SET); self.disable_sw_wdt_access(); } + + pub fn enable_fosc(&self) { + self.registers.clk_conf.modify(CLK_CONF::DIG_FOSC_EN::SET); + } } impl WatchDog for RtcCntl { diff --git a/chips/esp32/src/timg.rs b/chips/esp32/src/timg.rs index 56ae2ec9a9..e6e63177cd 100644 --- a/chips/esp32/src/timg.rs +++ b/chips/esp32/src/timg.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! TimG Group driver. use core::marker::PhantomData; @@ -289,6 +293,6 @@ impl<'a, F: time::Frequency, const C3: bool> Alarm<'a> for TimG<'a, F, C3> { } fn minimum_dt(&self) -> Self::Ticks { - Self::Ticks::from(1 as u64) + Self::Ticks::from(1_u64) } } diff --git a/chips/esp32/src/uart.rs b/chips/esp32/src/uart.rs index 37248bb825..5d33fd9937 100644 --- a/chips/esp32/src/uart.rs +++ b/chips/esp32/src/uart.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! UART driver. use core::cell::Cell; @@ -221,7 +225,7 @@ pub struct UartParams { } impl<'a> Uart<'a> { - pub const fn new(base: StaticRef) -> Uart<'a> { + pub fn new(base: StaticRef) -> Uart<'a> { Uart { registers: base, tx_client: OptionalCell::empty(), @@ -427,16 +431,16 @@ impl<'a> hil::uart::Transmit<'a> for Uart<'a> { } } - fn transmit_abort(&self) -> Result<(), ErrorCode> { + fn transmit_word(&self, _word: u32) -> Result<(), ErrorCode> { Err(ErrorCode::FAIL) } - fn transmit_word(&self, _word: u32) -> Result<(), ErrorCode> { + fn transmit_abort(&self) -> Result<(), ErrorCode> { Err(ErrorCode::FAIL) } } -/* UART receive is not implemented yet, mostly due to a lack of tests avaliable */ +/* UART receive is not implemented yet, mostly due to a lack of tests available */ impl<'a> hil::uart::Receive<'a> for Uart<'a> { fn set_receive_client(&self, client: &'a dyn hil::uart::ReceiveClient) { self.rx_client.set(client); @@ -462,11 +466,11 @@ impl<'a> hil::uart::Receive<'a> for Uart<'a> { Ok(()) } - fn receive_abort(&self) -> Result<(), ErrorCode> { + fn receive_word(&self) -> Result<(), ErrorCode> { Err(ErrorCode::FAIL) } - fn receive_word(&self) -> Result<(), ErrorCode> { + fn receive_abort(&self) -> Result<(), ErrorCode> { Err(ErrorCode::FAIL) } } diff --git a/chips/imxrt10xx/Cargo.toml b/chips/imxrt10xx/Cargo.toml index 500e9e1cda..095d8b84e0 100644 --- a/chips/imxrt10xx/Cargo.toml +++ b/chips/imxrt10xx/Cargo.toml @@ -1,10 +1,17 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "imxrt10xx" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] cortexm7 = { path = "../../arch/cortex-m7" } enum_primitive = { path = "../../libraries/enum_primitive" } kernel = { path = "../../kernel" } + +[lints] +workspace = true diff --git a/chips/imxrt10xx/src/ccm.rs b/chips/imxrt10xx/src/ccm.rs index 393c29e202..b1e1637978 100644 --- a/chips/imxrt10xx/src/ccm.rs +++ b/chips/imxrt10xx/src/ccm.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::platform::chip::ClockInterface; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; use kernel::utilities::registers::{register_bitfields, register_structs, ReadOnly, ReadWrite}; @@ -214,7 +218,7 @@ impl Ccm { } pub fn set_low_power_mode(&self) { - self.registers.clpcr.modify(CLPCR::LPM.val(0b00 as u32)); + self.registers.clpcr.modify(CLPCR::LPM.val(0b00_u32)); } // Iomuxc_snvs clock @@ -223,8 +227,8 @@ impl Ccm { } pub fn enable_iomuxc_snvs_clock(&self) { - self.registers.ccgr[2].modify(CCGR::CG2.val(0b01 as u32)); - self.registers.ccgr[3].modify(CCGR::CG15.val(0b01 as u32)); + self.registers.ccgr[2].modify(CCGR::CG2.val(0b01_u32)); + self.registers.ccgr[3].modify(CCGR::CG15.val(0b01_u32)); } pub fn disable_iomuxc_snvs_clock(&self) { @@ -238,8 +242,8 @@ impl Ccm { } pub fn enable_iomuxc_clock(&self) { - self.registers.ccgr[4].modify(CCGR::CG0.val(0b01 as u32)); - self.registers.ccgr[4].modify(CCGR::CG1.val(0b01 as u32)); + self.registers.ccgr[4].modify(CCGR::CG0.val(0b01_u32)); + self.registers.ccgr[4].modify(CCGR::CG1.val(0b01_u32)); } pub fn disable_iomuxc_clock(&self) { @@ -253,7 +257,7 @@ impl Ccm { } pub fn enable_gpio1_clock(&self) { - self.registers.ccgr[1].modify(CCGR::CG13.val(0b11 as u32)) + self.registers.ccgr[1].modify(CCGR::CG13.val(0b11_u32)) } pub fn disable_gpio1_clock(&self) { @@ -266,7 +270,7 @@ impl Ccm { } pub fn enable_gpio2_clock(&self) { - self.registers.ccgr[0].modify(CCGR::CG15.val(0b11 as u32)) + self.registers.ccgr[0].modify(CCGR::CG15.val(0b11_u32)) } pub fn disable_gpio2_clock(&self) { @@ -279,7 +283,7 @@ impl Ccm { } pub fn enable_gpio3_clock(&self) { - self.registers.ccgr[2].modify(CCGR::CG13.val(0b11 as u32)) + self.registers.ccgr[2].modify(CCGR::CG13.val(0b11_u32)) } pub fn disable_gpio3_clock(&self) { @@ -292,7 +296,7 @@ impl Ccm { } pub fn enable_gpio4_clock(&self) { - self.registers.ccgr[3].modify(CCGR::CG6.val(0b11 as u32)) + self.registers.ccgr[3].modify(CCGR::CG6.val(0b11_u32)) } pub fn disable_gpio4_clock(&self) { @@ -305,7 +309,7 @@ impl Ccm { } pub fn enable_gpio5_clock(&self) { - self.registers.ccgr[1].modify(CCGR::CG15.val(0b11 as u32)) + self.registers.ccgr[1].modify(CCGR::CG15.val(0b11_u32)) } pub fn disable_gpio5_clock(&self) { @@ -318,8 +322,8 @@ impl Ccm { } pub fn enable_gpt1_clock(&self) { - self.registers.ccgr[1].modify(CCGR::CG10.val(0b11 as u32)); - self.registers.ccgr[1].modify(CCGR::CG11.val(0b11 as u32)); + self.registers.ccgr[1].modify(CCGR::CG10.val(0b11_u32)); + self.registers.ccgr[1].modify(CCGR::CG11.val(0b11_u32)); } pub fn disable_gpt1_clock(&self) { @@ -333,8 +337,8 @@ impl Ccm { } pub fn enable_gpt2_clock(&self) { - self.registers.ccgr[0].modify(CCGR::CG12.val(0b11 as u32)); - self.registers.ccgr[0].modify(CCGR::CG13.val(0b11 as u32)); + self.registers.ccgr[0].modify(CCGR::CG12.val(0b11_u32)); + self.registers.ccgr[0].modify(CCGR::CG13.val(0b11_u32)); } pub fn disable_gpt2_clock(&self) { @@ -348,7 +352,7 @@ impl Ccm { } pub fn enable_lpi2c1_clock(&self) { - self.registers.ccgr[2].modify(CCGR::CG3.val(0b11 as u32)); + self.registers.ccgr[2].modify(CCGR::CG3.val(0b11_u32)); } pub fn disable_lpi2c1_clock(&self) { @@ -361,7 +365,7 @@ impl Ccm { } pub fn enable_lpuart1_clock(&self) { - self.registers.ccgr[5].modify(CCGR::CG12.val(0b11 as u32)); + self.registers.ccgr[5].modify(CCGR::CG12.val(0b11_u32)); } pub fn disable_lpuart1_clock(&self) { @@ -374,7 +378,7 @@ impl Ccm { } pub fn enable_lpuart2_clock(&self) { - self.registers.ccgr[0].modify(CCGR::CG14.val(0b11 as u32)); + self.registers.ccgr[0].modify(CCGR::CG14.val(0b11_u32)); } pub fn disable_lpuart2_clock(&self) { @@ -412,14 +416,14 @@ impl Ccm { let divider = divider.max(1).min(1 << 6) - 1; self.registers .cscdr1 - .modify(CSCDR1::UART_CLK_PODF.val(divider as u32)); + .modify(CSCDR1::UART_CLK_PODF.val(divider)); } /// Returns the UART clock divider /// /// The return is a value bound by [1, 2^6]. pub fn uart_clock_podf(&self) -> u32 { - (self.registers.cscdr1.read(CSCDR1::UART_CLK_PODF) + 1) as u32 + self.registers.cscdr1.read(CSCDR1::UART_CLK_PODF) + 1 } // // PERCLK diff --git a/chips/imxrt10xx/src/ccm_analog.rs b/chips/imxrt10xx/src/ccm_analog.rs index 649e5747e8..aa1569ee30 100644 --- a/chips/imxrt10xx/src/ccm_analog.rs +++ b/chips/imxrt10xx/src/ccm_analog.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! CCM Analog peripheral // Generated following diff --git a/chips/imxrt10xx/src/chip.rs b/chips/imxrt10xx/src/chip.rs index f79050d515..2c300cb301 100644 --- a/chips/imxrt10xx/src/chip.rs +++ b/chips/imxrt10xx/src/chip.rs @@ -1,19 +1,23 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Chip trait setup. use core::fmt::Write; -use cortexm7; +use cortexm7::{CortexM7, CortexMVariant}; use kernel::debug; use kernel::platform::chip::{Chip, InterruptService}; use crate::nvic; -pub struct Imxrt10xx + 'static> { +pub struct Imxrt10xx { mpu: cortexm7::mpu::MPU, userspace_kernel_boundary: cortexm7::syscall::SysCall, interrupt_service: &'static I, } -impl + 'static> Imxrt10xx { +impl Imxrt10xx { pub unsafe fn new(interrupt_service: &'static I) -> Self { Imxrt10xx { mpu: cortexm7::mpu::MPU::new(), @@ -39,7 +43,7 @@ pub struct Imxrt10xxDefaultPeripherals { } impl Imxrt10xxDefaultPeripherals { - pub const fn new(ccm: &'static crate::ccm::Ccm) -> Self { + pub fn new(ccm: &'static crate::ccm::Ccm) -> Self { Self { iomuxc: crate::iomuxc::Iomuxc::new(), iomuxc_snvs: crate::iomuxc_snvs::IomuxcSnvs::new(), @@ -57,7 +61,7 @@ impl Imxrt10xxDefaultPeripherals { } } -impl InterruptService<()> for Imxrt10xxDefaultPeripherals { +impl InterruptService for Imxrt10xxDefaultPeripherals { unsafe fn service_interrupt(&self, interrupt: u32) -> bool { match interrupt { nvic::LPUART1 => self.lpuart1.handle_interrupt(), @@ -96,13 +100,9 @@ impl InterruptService<()> for Imxrt10xxDefaultPeripherals { } true } - - unsafe fn service_deferred_call(&self, _: ()) -> bool { - false - } } -impl + 'static> Chip for Imxrt10xx { +impl Chip for Imxrt10xx { type MPU = cortexm7::mpu::MPU; type UserspaceKernelBoundary = cortexm7::syscall::SysCall; @@ -149,6 +149,6 @@ impl + 'static> Chip for Imxrt10xx { } unsafe fn print_state(&self, write: &mut dyn Write) { - cortexm7::print_cortexm7_state(write); + CortexM7::print_cortexm_state(write); } } diff --git a/chips/imxrt10xx/src/dcdc.rs b/chips/imxrt10xx/src/dcdc.rs index a0ef77fe80..6c81f3dbb5 100644 --- a/chips/imxrt10xx/src/dcdc.rs +++ b/chips/imxrt10xx/src/dcdc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! DCDC Converter use kernel::platform::chip::ClockInterface; diff --git a/chips/imxrt10xx/src/dma.rs b/chips/imxrt10xx/src/dma.rs index 56cf312f78..016db75415 100644 --- a/chips/imxrt10xx/src/dma.rs +++ b/chips/imxrt10xx/src/dma.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Direct Memory Access (DMA) channels and multiplexer //! //! ## DMAMUX Channel Configuration Options @@ -582,7 +586,7 @@ impl DmaChannel { TransferAttributes::SSIZE.val(T::DATA_TRANSFER_ID) + TransferAttributes::SMOD.val(0), ); tcd.nbytes.set(mem::size_of::() as u32); - tcd.slast.set((-1 * (buffer.len() as i32)) as u32); + tcd.slast.set((-(buffer.len() as i32)) as u32); let iterations: u16 = buffer.len() as u16; tcd.biter.set(iterations); tcd.citer.set(iterations); @@ -600,7 +604,7 @@ impl DmaChannel { TransferAttributes::DSIZE.val(T::DATA_TRANSFER_ID) + TransferAttributes::DMOD.val(0), ); tcd.nbytes.set(mem::size_of::() as u32); - tcd.dlast_sga.set((-1 * (buffer.len() as i32)) as u32); + tcd.dlast_sga.set((-(buffer.len() as i32)) as u32); let iterations: u16 = buffer.len() as u16; tcd.biter.set(iterations); tcd.citer.set(iterations); diff --git a/chips/imxrt10xx/src/gpio.rs b/chips/imxrt10xx/src/gpio.rs index 8ffa58f985..dc67b3a399 100644 --- a/chips/imxrt10xx/src/gpio.rs +++ b/chips/imxrt10xx/src/gpio.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! i.MX RT GPIO driver //! //! # Design @@ -94,8 +98,8 @@ enum_from_primitive! { /// Creates a GPIO ID /// -/// Low 6 bits are the GPIO offset; the '17' in GPIO2[17] -/// Next 3 bits are the GPIO port; the '2' in GPIO2[17] (base 0 index, 2 -> 1) +/// Low 6 bits are the GPIO offset; the '17' in `GPIO2[17]` +/// Next 3 bits are the GPIO port; the '2' in `GPIO2[17]` (base 0 index, 2 -> 1) const fn gpio_id(port: GpioPort, offset: u16) -> u16 { ((port as u16) << 6) | offset & 0x3F } @@ -551,7 +555,7 @@ pub struct Pin<'a> { trait U32Ext { fn set_bit(self, offset: usize) -> Self; fn clear_bit(self, offset: usize) -> Self; - fn is_bit_set(self, offset: usize) -> bool; + fn is_bit_set(&self, offset: usize) -> bool; } impl U32Ext for u32 { @@ -564,7 +568,7 @@ impl U32Ext for u32 { self & !(1 << offset) } #[inline(always)] - fn is_bit_set(self, offset: usize) -> bool { + fn is_bit_set(&self, offset: usize) -> bool { (self & (1 << offset)) != 0 } } @@ -646,7 +650,7 @@ impl<'a> Pin<'a> { } fn set_edge_sensitive(&self, sensitive: hil::gpio::InterruptEdge) { - use hil::gpio::InterruptEdge::*; + use hil::gpio::InterruptEdge::{EitherEdge, FallingEdge, RisingEdge}; const RISING_EDGE_SENSITIVE: u32 = 0b10; const FALLING_EDGE_SENSITIVE: u32 = 0b11; diff --git a/chips/imxrt10xx/src/gpt.rs b/chips/imxrt10xx/src/gpt.rs index bab2b9a6ba..1587b82072 100644 --- a/chips/imxrt10xx/src/gpt.rs +++ b/chips/imxrt10xx/src/gpt.rs @@ -1,5 +1,8 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::sync::atomic::{AtomicU32, Ordering}; -use cortexm7; use cortexm7::support::atomic; use kernel::hil; use kernel::hil::time::{Ticks, Ticks32, Time}; @@ -257,7 +260,7 @@ impl<'a, S> Gpt<'a, S> { while self.registers.cr.is_set(CR::SWR) {} // Clear the GPT status register - self.registers.sr.set(31 as u32); + self.registers.sr.set(31_u32); // Enable free run mode self.registers.cr.modify(CR::FRR::SET); @@ -282,11 +285,11 @@ impl<'a, S> Gpt<'a, S> { // We will use the ipg_clk_highfreq provided by perclk_clk_root, // which runs at 24.75 MHz. Before calling set_alarm, we assume clock // to GPT1 has been enabled. - self.registers.cr.modify(CR::CLKSRC.val(0x2 as u32)); + self.registers.cr.modify(CR::CLKSRC.val(0x2_u32)); // We do not prescale the value for the moment. We will do so // after we will set the ARM_PLL1 CLK accordingly. - self.registers.pr.modify(PR::PRESCALER.val(0 as u32)); + self.registers.pr.modify(PR::PRESCALER.val(0_u32)); self.set_frequency(IMXRT1050_IPG_CLOCK_HZ / divider as u32); } diff --git a/chips/imxrt10xx/src/iomuxc.rs b/chips/imxrt10xx/src/iomuxc.rs index ce2cca32f0..a53bf6ea43 100644 --- a/chips/imxrt10xx/src/iomuxc.rs +++ b/chips/imxrt10xx/src/iomuxc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use enum_primitive::cast::FromPrimitive; use enum_primitive::enum_from_primitive; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; diff --git a/chips/imxrt10xx/src/iomuxc_snvs.rs b/chips/imxrt10xx/src/iomuxc_snvs.rs index 9f6471dc4a..7ffb8c2b53 100644 --- a/chips/imxrt10xx/src/iomuxc_snvs.rs +++ b/chips/imxrt10xx/src/iomuxc_snvs.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use crate::iomuxc::{DriveStrength, MuxMode, OpenDrainEn, PullKeepEn, PullUpDown, Sion, Speed}; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; use kernel::utilities::registers::{register_bitfields, ReadWrite}; diff --git a/chips/imxrt10xx/src/lib.rs b/chips/imxrt10xx/src/lib.rs index 8db7e8aae5..e4fb850181 100644 --- a/chips/imxrt10xx/src/lib.rs +++ b/chips/imxrt10xx/src/lib.rs @@ -1,10 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Peripheral implementations for the IMXRT1050 and IMXRT1060 MCUs. //! //! imxrt1050 chip: #![crate_name = "imxrt10xx"] #![crate_type = "rlib"] -#![feature(const_fn_trait_bound)] #![no_std] pub mod chip; @@ -22,10 +25,7 @@ pub mod iomuxc_snvs; pub mod lpi2c; pub mod lpuart; -use cortexm7::{ - generic_isr, hard_fault_handler, initialize_ram_jump_to_main, svc_handler, systick_handler, - unhandled_interrupt, -}; +use cortexm7::{initialize_ram_jump_to_main, unhandled_interrupt, CortexM7, CortexMVariant}; extern "C" { // _estack is not really a function, but it makes the types work @@ -42,20 +42,20 @@ extern "C" { pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ _estack, initialize_ram_jump_to_main, - unhandled_interrupt, // NMI - hard_fault_handler, // Hard Fault - unhandled_interrupt, // MemManage - unhandled_interrupt, // BusFault - unhandled_interrupt, // UsageFault + unhandled_interrupt, // NMI + CortexM7::HARD_FAULT_HANDLER, // Hard Fault + unhandled_interrupt, // MemManage + unhandled_interrupt, // BusFault + unhandled_interrupt, // UsageFault unhandled_interrupt, unhandled_interrupt, unhandled_interrupt, unhandled_interrupt, - svc_handler, // SVC - unhandled_interrupt, // DebugMon + CortexM7::SVC_HANDLER, // SVC + unhandled_interrupt, // DebugMon unhandled_interrupt, - unhandled_interrupt, // PendSV - systick_handler, // SysTick + unhandled_interrupt, // PendSV + CortexM7::SYSTICK_HANDLER, // SysTick ]; // imxrt 1050 has total of 160 interrupts @@ -63,175 +63,173 @@ pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ // used Ensures that the symbol is kept until the final binary #[cfg_attr(all(target_arch = "arm", target_os = "none"), used)] pub static IRQS: [unsafe extern "C" fn(); 160] = [ - generic_isr, // eDMA (0) - generic_isr, // eDMA (1) - generic_isr, // eDMA (2) - generic_isr, // eDMA (3) - generic_isr, // eDMA (4) - generic_isr, // eDMA (5) - generic_isr, // eDMA (6) - generic_isr, // eDMA (7) - generic_isr, // eDMA (8) - generic_isr, // eDMA (9) - generic_isr, // eDMA (10) - generic_isr, // eDMA (11) - generic_isr, // eDMA (12) - generic_isr, // eDMA (13) - generic_isr, // eDMA (14) - generic_isr, // eDMA (15) - generic_isr, // Error_interrupt (16) - generic_isr, // CM7 (17) - generic_isr, // CM7 (18) - generic_isr, // CM7 (19) - generic_isr, // LPUART1 (20) - generic_isr, // LPUART2 (21) - generic_isr, // LPUART3 (22) - generic_isr, // LPUART4 (23) - generic_isr, // LPUART5 (24) - generic_isr, // LPUART6 (25) - generic_isr, // LPUART7 (26) - generic_isr, // LPUART8 (27) - generic_isr, // LPI2C1 (28) - generic_isr, // LPI2C2 (29) - generic_isr, // LPI2C3 (30) - generic_isr, // LPI2C4 (31) - generic_isr, // LPSPI1 (32) - generic_isr, // LPSPI2 (33) - generic_isr, // LPSPI3 (34) - generic_isr, // LPSPI4 (35) - generic_isr, // FLEXCAN1 (36) - generic_isr, // FLEXCAN2 (37) - generic_isr, // CM7 (38) - generic_isr, // KPP (39) - generic_isr, // TSC_DIG (40) - generic_isr, // GPR_IRQ (41) - generic_isr, // LCDIF (42) - generic_isr, // CSI (43) - generic_isr, // PXP (44) - generic_isr, // WDOG2 (45) - generic_isr, // SNVS_HP_WRAPPER (46) - generic_isr, // SNVS_HP_WRAPPER (47) - generic_isr, // SNVS_HP_WRAPPER / SNVS_LP_WRAPPER (48) - generic_isr, // CSU (49) - generic_isr, // DCP (50) - generic_isr, // DCP (51) - generic_isr, // DCP (52) - generic_isr, // TRNG (53) - generic_isr, // Reserved (54) - generic_isr, // BEE (55) - generic_isr, // SAI1 (56) - generic_isr, // SAI2 (57) - generic_isr, // SAI3 (58) - generic_isr, // SAI3 (59) - generic_isr, // SPDIF (60) - generic_isr, // PMU (61) - generic_isr, // Reserved (62) - generic_isr, // Temperature Monitor (63) - generic_isr, // Temperature Monitor (64) - generic_isr, // USB PHY (65) - generic_isr, // USB PHY (66) - generic_isr, // ADC1 (67) - generic_isr, // ADC2 (68) - generic_isr, // DCDC (69) - generic_isr, // Reserved (70) - generic_isr, // Reserved (71) - generic_isr, // GPIO1 (72) - generic_isr, // GPIO1 (73) - generic_isr, // GPIO1 (74) - generic_isr, // GPIO1 (75) - generic_isr, // GPIO1 (76) - generic_isr, // GPIO1 (77) - generic_isr, // GPIO1 (78) - generic_isr, // GPIO1 (79) - generic_isr, // GPIO1_1 (80) - generic_isr, // GPIO1_2 (81) - generic_isr, // GPIO2_1 (82) - generic_isr, // GPIO2_2 (83) - generic_isr, // GPIO3_1 (84) - generic_isr, // GPIO3_2 (85) - generic_isr, // GPIO4_1 (86) - generic_isr, // GPIO4_2 (87) - generic_isr, // GPIO5_1 (88) - generic_isr, // GPIO5_2 (89) - generic_isr, // FLEXIO1 (90) - generic_isr, // FLEXIO2 (91) - generic_isr, // WDOG1 (92) - generic_isr, // RTWDOG (93) - generic_isr, // EWM (94) - generic_isr, // CCM (95) - generic_isr, // CCM (96) - generic_isr, // GPC (97) - generic_isr, // SRC (98) - generic_isr, // Reserved (99) - generic_isr, // GPT1 (100) - generic_isr, // GPT2 (101) - generic_isr, // FLEXPWM1 (102) - generic_isr, // FLEXPWM1 (103) - generic_isr, // FLEXPWM1 (104) - generic_isr, // FLEXPWM1 (105) - generic_isr, // FLEXPWM1 (106) - generic_isr, // Reserved (107) - generic_isr, // FLEXSPI (108) - generic_isr, // SEMC (109) - generic_isr, // USDHC1 (110) - generic_isr, // USDHC2 (111) - generic_isr, // USB (112) - generic_isr, // USB (113) - generic_isr, // ENET (114) - generic_isr, // ENET (115) - generic_isr, // XBAR1 (116) - generic_isr, // XBAR1 (117) - generic_isr, // ADC_ETC (118) - generic_isr, // ADC_ETC (119) - generic_isr, // ADC_ETC (120) - generic_isr, // ADC_ETC (121) - generic_isr, // PIT (122) - generic_isr, // ACMP (123) - generic_isr, // ACMP (124) - generic_isr, // ACMP (125) - generic_isr, // ACMP (126) - generic_isr, // Reserved (127) - generic_isr, // Reserved (128) - generic_isr, // ENC1 (129) - generic_isr, // ENC2 (130) - generic_isr, // ENC3 (131) - generic_isr, // ENC4 (132) - generic_isr, // QTIMER1 (133) - generic_isr, // QTIMER2 (134) - generic_isr, // QTIMER3 (135) - generic_isr, // QTIMER4 (136) - generic_isr, // FLEXPWM2 (137) - generic_isr, // FLEXPWM2 (138) - generic_isr, // FLEXPWM2 (139) - generic_isr, // FLEXPWM2 (140) - generic_isr, // FLEXPWM2 (141) - generic_isr, // FLEXPWM3 (142) - generic_isr, // FLEXPWM3 (143) - generic_isr, // FLEXPWM3 (144) - generic_isr, // FLEXPWM3 (145) - generic_isr, // FLEXPWM3 (146) - generic_isr, // FLEXPWM4 (147) - generic_isr, // FLEXPWM4 (148) - generic_isr, // FLEXPWM4 (149) - generic_isr, // FLEXPWM4 (150) - generic_isr, // FLEXPWM4 (151) - generic_isr, // Reserved (152) - generic_isr, // Reserved (153) - generic_isr, // Reserved (154) - generic_isr, // Reserved (155) - generic_isr, // Reserved (156) - generic_isr, // Reserved (157) - generic_isr, // Reserved (158) - generic_isr, // Reserved (159) + CortexM7::GENERIC_ISR, // eDMA (0) + CortexM7::GENERIC_ISR, // eDMA (1) + CortexM7::GENERIC_ISR, // eDMA (2) + CortexM7::GENERIC_ISR, // eDMA (3) + CortexM7::GENERIC_ISR, // eDMA (4) + CortexM7::GENERIC_ISR, // eDMA (5) + CortexM7::GENERIC_ISR, // eDMA (6) + CortexM7::GENERIC_ISR, // eDMA (7) + CortexM7::GENERIC_ISR, // eDMA (8) + CortexM7::GENERIC_ISR, // eDMA (9) + CortexM7::GENERIC_ISR, // eDMA (10) + CortexM7::GENERIC_ISR, // eDMA (11) + CortexM7::GENERIC_ISR, // eDMA (12) + CortexM7::GENERIC_ISR, // eDMA (13) + CortexM7::GENERIC_ISR, // eDMA (14) + CortexM7::GENERIC_ISR, // eDMA (15) + CortexM7::GENERIC_ISR, // Error_interrupt (16) + CortexM7::GENERIC_ISR, // CM7 (17) + CortexM7::GENERIC_ISR, // CM7 (18) + CortexM7::GENERIC_ISR, // CM7 (19) + CortexM7::GENERIC_ISR, // LPUART1 (20) + CortexM7::GENERIC_ISR, // LPUART2 (21) + CortexM7::GENERIC_ISR, // LPUART3 (22) + CortexM7::GENERIC_ISR, // LPUART4 (23) + CortexM7::GENERIC_ISR, // LPUART5 (24) + CortexM7::GENERIC_ISR, // LPUART6 (25) + CortexM7::GENERIC_ISR, // LPUART7 (26) + CortexM7::GENERIC_ISR, // LPUART8 (27) + CortexM7::GENERIC_ISR, // LPI2C1 (28) + CortexM7::GENERIC_ISR, // LPI2C2 (29) + CortexM7::GENERIC_ISR, // LPI2C3 (30) + CortexM7::GENERIC_ISR, // LPI2C4 (31) + CortexM7::GENERIC_ISR, // LPSPI1 (32) + CortexM7::GENERIC_ISR, // LPSPI2 (33) + CortexM7::GENERIC_ISR, // LPSPI3 (34) + CortexM7::GENERIC_ISR, // LPSPI4 (35) + CortexM7::GENERIC_ISR, // FLEXCAN1 (36) + CortexM7::GENERIC_ISR, // FLEXCAN2 (37) + CortexM7::GENERIC_ISR, // CM7 (38) + CortexM7::GENERIC_ISR, // KPP (39) + CortexM7::GENERIC_ISR, // TSC_DIG (40) + CortexM7::GENERIC_ISR, // GPR_IRQ (41) + CortexM7::GENERIC_ISR, // LCDIF (42) + CortexM7::GENERIC_ISR, // CSI (43) + CortexM7::GENERIC_ISR, // PXP (44) + CortexM7::GENERIC_ISR, // WDOG2 (45) + CortexM7::GENERIC_ISR, // SNVS_HP_WRAPPER (46) + CortexM7::GENERIC_ISR, // SNVS_HP_WRAPPER (47) + CortexM7::GENERIC_ISR, // SNVS_HP_WRAPPER / SNVS_LP_WRAPPER (48) + CortexM7::GENERIC_ISR, // CSU (49) + CortexM7::GENERIC_ISR, // DCP (50) + CortexM7::GENERIC_ISR, // DCP (51) + CortexM7::GENERIC_ISR, // DCP (52) + CortexM7::GENERIC_ISR, // TRNG (53) + CortexM7::GENERIC_ISR, // Reserved (54) + CortexM7::GENERIC_ISR, // BEE (55) + CortexM7::GENERIC_ISR, // SAI1 (56) + CortexM7::GENERIC_ISR, // SAI2 (57) + CortexM7::GENERIC_ISR, // SAI3 (58) + CortexM7::GENERIC_ISR, // SAI3 (59) + CortexM7::GENERIC_ISR, // SPDIF (60) + CortexM7::GENERIC_ISR, // PMU (61) + CortexM7::GENERIC_ISR, // Reserved (62) + CortexM7::GENERIC_ISR, // Temperature Monitor (63) + CortexM7::GENERIC_ISR, // Temperature Monitor (64) + CortexM7::GENERIC_ISR, // USB PHY (65) + CortexM7::GENERIC_ISR, // USB PHY (66) + CortexM7::GENERIC_ISR, // ADC1 (67) + CortexM7::GENERIC_ISR, // ADC2 (68) + CortexM7::GENERIC_ISR, // DCDC (69) + CortexM7::GENERIC_ISR, // Reserved (70) + CortexM7::GENERIC_ISR, // Reserved (71) + CortexM7::GENERIC_ISR, // GPIO1 (72) + CortexM7::GENERIC_ISR, // GPIO1 (73) + CortexM7::GENERIC_ISR, // GPIO1 (74) + CortexM7::GENERIC_ISR, // GPIO1 (75) + CortexM7::GENERIC_ISR, // GPIO1 (76) + CortexM7::GENERIC_ISR, // GPIO1 (77) + CortexM7::GENERIC_ISR, // GPIO1 (78) + CortexM7::GENERIC_ISR, // GPIO1 (79) + CortexM7::GENERIC_ISR, // GPIO1_1 (80) + CortexM7::GENERIC_ISR, // GPIO1_2 (81) + CortexM7::GENERIC_ISR, // GPIO2_1 (82) + CortexM7::GENERIC_ISR, // GPIO2_2 (83) + CortexM7::GENERIC_ISR, // GPIO3_1 (84) + CortexM7::GENERIC_ISR, // GPIO3_2 (85) + CortexM7::GENERIC_ISR, // GPIO4_1 (86) + CortexM7::GENERIC_ISR, // GPIO4_2 (87) + CortexM7::GENERIC_ISR, // GPIO5_1 (88) + CortexM7::GENERIC_ISR, // GPIO5_2 (89) + CortexM7::GENERIC_ISR, // FLEXIO1 (90) + CortexM7::GENERIC_ISR, // FLEXIO2 (91) + CortexM7::GENERIC_ISR, // WDOG1 (92) + CortexM7::GENERIC_ISR, // RTWDOG (93) + CortexM7::GENERIC_ISR, // EWM (94) + CortexM7::GENERIC_ISR, // CCM (95) + CortexM7::GENERIC_ISR, // CCM (96) + CortexM7::GENERIC_ISR, // GPC (97) + CortexM7::GENERIC_ISR, // SRC (98) + CortexM7::GENERIC_ISR, // Reserved (99) + CortexM7::GENERIC_ISR, // GPT1 (100) + CortexM7::GENERIC_ISR, // GPT2 (101) + CortexM7::GENERIC_ISR, // FLEXPWM1 (102) + CortexM7::GENERIC_ISR, // FLEXPWM1 (103) + CortexM7::GENERIC_ISR, // FLEXPWM1 (104) + CortexM7::GENERIC_ISR, // FLEXPWM1 (105) + CortexM7::GENERIC_ISR, // FLEXPWM1 (106) + CortexM7::GENERIC_ISR, // Reserved (107) + CortexM7::GENERIC_ISR, // FLEXSPI (108) + CortexM7::GENERIC_ISR, // SEMC (109) + CortexM7::GENERIC_ISR, // USDHC1 (110) + CortexM7::GENERIC_ISR, // USDHC2 (111) + CortexM7::GENERIC_ISR, // USB (112) + CortexM7::GENERIC_ISR, // USB (113) + CortexM7::GENERIC_ISR, // ENET (114) + CortexM7::GENERIC_ISR, // ENET (115) + CortexM7::GENERIC_ISR, // XBAR1 (116) + CortexM7::GENERIC_ISR, // XBAR1 (117) + CortexM7::GENERIC_ISR, // ADC_ETC (118) + CortexM7::GENERIC_ISR, // ADC_ETC (119) + CortexM7::GENERIC_ISR, // ADC_ETC (120) + CortexM7::GENERIC_ISR, // ADC_ETC (121) + CortexM7::GENERIC_ISR, // PIT (122) + CortexM7::GENERIC_ISR, // ACMP (123) + CortexM7::GENERIC_ISR, // ACMP (124) + CortexM7::GENERIC_ISR, // ACMP (125) + CortexM7::GENERIC_ISR, // ACMP (126) + CortexM7::GENERIC_ISR, // Reserved (127) + CortexM7::GENERIC_ISR, // Reserved (128) + CortexM7::GENERIC_ISR, // ENC1 (129) + CortexM7::GENERIC_ISR, // ENC2 (130) + CortexM7::GENERIC_ISR, // ENC3 (131) + CortexM7::GENERIC_ISR, // ENC4 (132) + CortexM7::GENERIC_ISR, // QTIMER1 (133) + CortexM7::GENERIC_ISR, // QTIMER2 (134) + CortexM7::GENERIC_ISR, // QTIMER3 (135) + CortexM7::GENERIC_ISR, // QTIMER4 (136) + CortexM7::GENERIC_ISR, // FLEXPWM2 (137) + CortexM7::GENERIC_ISR, // FLEXPWM2 (138) + CortexM7::GENERIC_ISR, // FLEXPWM2 (139) + CortexM7::GENERIC_ISR, // FLEXPWM2 (140) + CortexM7::GENERIC_ISR, // FLEXPWM2 (141) + CortexM7::GENERIC_ISR, // FLEXPWM3 (142) + CortexM7::GENERIC_ISR, // FLEXPWM3 (143) + CortexM7::GENERIC_ISR, // FLEXPWM3 (144) + CortexM7::GENERIC_ISR, // FLEXPWM3 (145) + CortexM7::GENERIC_ISR, // FLEXPWM3 (146) + CortexM7::GENERIC_ISR, // FLEXPWM4 (147) + CortexM7::GENERIC_ISR, // FLEXPWM4 (148) + CortexM7::GENERIC_ISR, // FLEXPWM4 (149) + CortexM7::GENERIC_ISR, // FLEXPWM4 (150) + CortexM7::GENERIC_ISR, // FLEXPWM4 (151) + CortexM7::GENERIC_ISR, // Reserved (152) + CortexM7::GENERIC_ISR, // Reserved (153) + CortexM7::GENERIC_ISR, // Reserved (154) + CortexM7::GENERIC_ISR, // Reserved (155) + CortexM7::GENERIC_ISR, // Reserved (156) + CortexM7::GENERIC_ISR, // Reserved (157) + CortexM7::GENERIC_ISR, // Reserved (158) + CortexM7::GENERIC_ISR, // Reserved (159) ]; pub unsafe fn init() { cortexm7::nvic::disable_all(); cortexm7::nvic::clear_all_pending(); - cortexm7::scb::set_vector_table_offset( - &BASE_VECTORS as *const [unsafe extern "C" fn(); 16] as *const (), - ); + cortexm7::scb::set_vector_table_offset(core::ptr::addr_of!(BASE_VECTORS) as *const ()); cortexm7::nvic::enable_all(); } diff --git a/chips/imxrt10xx/src/lpi2c.rs b/chips/imxrt10xx/src/lpi2c.rs index b8d1f617ad..583b4d9bfa 100644 --- a/chips/imxrt10xx/src/lpi2c.rs +++ b/chips/imxrt10xx/src/lpi2c.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::cell::Cell; use kernel::debug; @@ -438,10 +442,10 @@ pub struct Lpi2c<'a> { master_client: OptionalCell<&'a dyn hil::i2c::I2CHwMasterClient>, buffer: TakeCell<'static, [u8]>, - tx_position: Cell, - rx_position: Cell, - tx_len: Cell, - rx_len: Cell, + tx_position: Cell, + rx_position: Cell, + tx_len: Cell, + rx_len: Cell, slave_address: Cell, @@ -463,13 +467,13 @@ enum Lpi2cStatus { } impl<'a> Lpi2c<'a> { - pub const fn new_lpi2c1(ccm: &'a ccm::Ccm) -> Self { + pub fn new_lpi2c1(ccm: &'a ccm::Ccm) -> Self { Lpi2c::new( LPI2C1_BASE, Lpi2cClock(ccm::PeripheralClock::ccgr2(ccm, ccm::HCLK2::LPI2C1)), ) } - const fn new(base_addr: StaticRef, clock: Lpi2cClock<'a>) -> Self { + fn new(base_addr: StaticRef, clock: Lpi2cClock<'a>) -> Self { Self { registers: base_addr, clock, @@ -535,7 +539,7 @@ impl<'a> Lpi2c<'a> { pub fn send_byte(&self) -> bool { if self.buffer.is_some() && self.tx_position.get() < self.tx_len.get() { self.buffer.map(|buf| { - let byte = buf[self.tx_position.get() as usize]; + let byte = buf[self.tx_position.get()]; self.registers.mtdr.write(MTDR::DATA.val(byte as u32)); self.tx_position.set(self.tx_position.get() + 1); }); @@ -550,7 +554,7 @@ impl<'a> Lpi2c<'a> { let byte = self.registers.mrdr.read(MRDR::DATA) as u8; if self.buffer.is_some() && self.rx_position.get() < self.rx_len.get() { self.buffer.map(|buf| { - buf[self.rx_position.get() as usize] = byte; + buf[self.rx_position.get()] = byte; self.rx_position.set(self.rx_position.get() + 1); }); true @@ -699,9 +703,9 @@ impl<'a> Lpi2c<'a> { // setting slave address self.registers.mier.modify(MIER::EPIE::CLEAR); - self.registers - .mtdr - .write(MTDR::CMD.val(100) + MTDR::DATA.val((self.slave_address.get() << 1 + 1) as u32)); + self.registers.mtdr.write( + MTDR::CMD.val(100) + MTDR::DATA.val((self.slave_address.get() << (1 + 1)) as u32), + ); self.registers.mcfgr1.modify(MCFGR1::PINCFG::CLEAR); self.registers @@ -710,8 +714,8 @@ impl<'a> Lpi2c<'a> { } } -impl i2c::I2CMaster for Lpi2c<'_> { - fn set_master_client(&self, master_client: &'static dyn I2CHwMasterClient) { +impl<'a> i2c::I2CMaster<'a> for Lpi2c<'a> { + fn set_master_client(&self, master_client: &'a dyn I2CHwMasterClient) { self.master_client.replace(master_client); } fn enable(&self) { @@ -726,8 +730,8 @@ impl i2c::I2CMaster for Lpi2c<'_> { &self, addr: u8, data: &'static mut [u8], - write_len: u8, - read_len: u8, + write_len: usize, + read_len: usize, ) -> Result<(), (i2c::Error, &'static mut [u8])> { if self.status.get() == Lpi2cStatus::Idle { self.reset(); @@ -748,7 +752,7 @@ impl i2c::I2CMaster for Lpi2c<'_> { &self, addr: u8, data: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (i2c::Error, &'static mut [u8])> { if self.status.get() == Lpi2cStatus::Idle { self.reset(); @@ -768,7 +772,7 @@ impl i2c::I2CMaster for Lpi2c<'_> { &self, addr: u8, buffer: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (i2c::Error, &'static mut [u8])> { if self.status.get() == Lpi2cStatus::Idle { self.reset(); diff --git a/chips/imxrt10xx/src/lpuart.rs b/chips/imxrt10xx/src/lpuart.rs index 4c95f55e06..1c6a1e37b4 100644 --- a/chips/imxrt10xx/src/lpuart.rs +++ b/chips/imxrt10xx/src/lpuart.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::cell::Cell; use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; @@ -339,7 +343,7 @@ pub struct Lpuart<'a> { } impl<'a> Lpuart<'a> { - pub const fn new_lpuart1(ccm: &'a ccm::Ccm) -> Self { + pub fn new_lpuart1(ccm: &'a ccm::Ccm) -> Self { Lpuart::new( LPUART1_BASE, LpuartClock(ccm::PeripheralClock::ccgr5(ccm, ccm::HCLK5::LPUART1)), @@ -348,7 +352,7 @@ impl<'a> Lpuart<'a> { ) } - pub const fn new_lpuart2(ccm: &'a ccm::Ccm) -> Self { + pub fn new_lpuart2(ccm: &'a ccm::Ccm) -> Self { Lpuart::new( LPUART2_BASE, LpuartClock(ccm::PeripheralClock::ccgr0(ccm, ccm::HCLK0::LPUART2)), @@ -357,7 +361,7 @@ impl<'a> Lpuart<'a> { ) } - const fn new( + fn new( base_addr: StaticRef, clock: LpuartClock<'a>, tx_dma_source: dma::DmaHardwareSource, @@ -365,7 +369,7 @@ impl<'a> Lpuart<'a> { ) -> Lpuart<'a> { Lpuart { registers: base_addr, - clock: clock, + clock, tx_client: OptionalCell::empty(), rx_client: OptionalCell::empty(), @@ -391,7 +395,7 @@ impl<'a> Lpuart<'a> { dma_channel.set_client(self, self.tx_dma_source); unsafe { // Safety: pointing to static memory - dma_channel.set_destination(&self.registers.data as *const _ as *const u8); + dma_channel.set_destination(core::ptr::addr_of!(self.registers.data) as *const u8); } dma_channel.set_interrupt_on_completion(true); dma_channel.set_disable_on_completion(true); @@ -403,7 +407,7 @@ impl<'a> Lpuart<'a> { dma_channel.set_client(self, self.rx_dma_source); unsafe { // Safety: pointing to static memory - dma_channel.set_source(&self.registers.data as *const _ as *const u8); + dma_channel.set_source(core::ptr::addr_of!(self.registers.data) as *const u8); } dma_channel.set_interrupt_on_completion(true); dma_channel.set_disable_on_completion(true); @@ -424,7 +428,7 @@ impl<'a> Lpuart<'a> { pub fn set_baud(&self) { // Set the Baud Rate Modulo Divisor - self.registers.baud.modify(BAUD::SBR.val(139 as u32)); + self.registers.baud.modify(BAUD::SBR.val(139_u32)); } // for use by panic in io.rs @@ -802,7 +806,7 @@ impl<'a> hil::uart::Configure for Lpuart<'a> { if params.baud_rate != 115200 || params.stop_bits != hil::uart::StopBits::One || params.parity != hil::uart::Parity::None - || params.hw_flow_control != false + || params.hw_flow_control || params.width != hil::uart::Width::Eight { panic!( @@ -819,10 +823,10 @@ impl<'a> hil::uart::Configure for Lpuart<'a> { self.registers.baud.modify(BAUD::BOTHEDGE::SET); // Set Oversampling Ratio to 5 (the value written is -1) - self.registers.baud.modify(BAUD::OSR.val(0b100 as u32)); + self.registers.baud.modify(BAUD::OSR.val(0b100_u32)); // Set the Baud Rate Modulo Divisor - self.registers.baud.modify(BAUD::SBR.val(139 as u32)); + self.registers.baud.modify(BAUD::SBR.val(139_u32)); // Set bit count and parity mode self.registers.baud.modify(BAUD::M10::CLEAR); diff --git a/chips/imxrt10xx/src/nvic.rs b/chips/imxrt10xx/src/nvic.rs index feb55d0b41..43c9728900 100644 --- a/chips/imxrt10xx/src/nvic.rs +++ b/chips/imxrt10xx/src/nvic.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Named constants for NVIC ids // Enabling only the needed constants diff --git a/chips/litex/Cargo.toml b/chips/litex/Cargo.toml index 6518229105..de00dabd3f 100644 --- a/chips/litex/Cargo.toml +++ b/chips/litex/Cargo.toml @@ -1,9 +1,16 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "litex" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] tock-registers = { path = "../../libraries/tock-register-interface" } kernel = { path = "../../kernel" } + +[lints] +workspace = true diff --git a/chips/litex/src/event_manager.rs b/chips/litex/src/event_manager.rs index ba85387261..d4c0cd39b2 100644 --- a/chips/litex/src/event_manager.rs +++ b/chips/litex/src/event_manager.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! LiteX Event Manager //! //! Documentation on the different LiteX event source, which all diff --git a/chips/litex/src/gpio.rs b/chips/litex/src/gpio.rs index 9255ca6727..652edeb5b8 100644 --- a/chips/litex/src/gpio.rs +++ b/chips/litex/src/gpio.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! LiteX Tristate GPIO controller //! //! Hardware source and documentation available at @@ -37,7 +41,7 @@ pub struct LiteXGPIORegisters { } impl LiteXGPIORegisters { - fn ev<'a>(&'a self) -> LiteXGPIOEV<'a, R> { + fn ev(&self) -> LiteXGPIOEV<'_, R> { LiteXGPIOEV::::new( &self.gpio_ev_status, &self.gpio_ev_pending, diff --git a/chips/litex/src/led_controller.rs b/chips/litex/src/led_controller.rs index aa18991476..638b4f7349 100644 --- a/chips/litex/src/led_controller.rs +++ b/chips/litex/src/led_controller.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! LiteX led controller (`LedChaser` core) //! //! Hardware source and documentation available at @@ -65,7 +69,7 @@ impl LiteXLedController { /// instance for the requested LED already exists. Call /// [`LiteXLed::destroy`] (or drop the [`LiteXLed`]) to be create /// a new instance for this LED. - pub fn get_led<'a>(&'a self, index: usize) -> Option> { + pub fn get_led(&self, index: usize) -> Option> { if index < self.led_count() && (self.led_references.get() & (1 << index)) == 0 { self.led_references .set(self.led_references.get() | (1 << index)); @@ -85,7 +89,7 @@ impl LiteXLedController { /// This function only checks whether the requested LEDs is within /// the controller's range of available LEDs, but *NOT* whether /// there already is a different reference to the same LED. - pub unsafe fn panic_led<'a>(&'a self, index: usize) -> Option> { + pub unsafe fn panic_led(&self, index: usize) -> Option> { if index < self.led_count() { Some(LiteXLed::new(self, index)) } else { diff --git a/chips/litex/src/lib.rs b/chips/litex/src/lib.rs index 028f3b9a9b..7c159b3208 100644 --- a/chips/litex/src/lib.rs +++ b/chips/litex/src/lib.rs @@ -1,6 +1,9 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Drivers and support modules for LiteX SoCs -#![feature(const_fn_trait_bound)] #![no_std] #![crate_name = "litex"] #![crate_type = "rlib"] diff --git a/chips/litex/src/liteeth.rs b/chips/litex/src/liteeth.rs index e53ccf3284..d2bd9e71c4 100644 --- a/chips/litex/src/liteeth.rs +++ b/chips/litex/src/liteeth.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! LiteX LiteEth peripheral //! //! The hardware source and any documentation can be found in the @@ -27,16 +31,6 @@ type LiteEthRXEV<'a, R> = LiteXEventManager< >; type LiteEthTXEV<'a, R> = LiteEthRXEV<'a, R>; -#[repr(C)] -pub struct LiteEthPhyRegisters { - /// ETHPHY_CRG_RESET - reset: R::WriteOnly8, - /// ETHPHY_MDIO_W - mdio_w: R::ReadWrite8, //, - /// ETHPHY_MDIO_R - mdio_r: R::ReadOnly8, //, -} - #[repr(C)] pub struct LiteEthMacRegisters { /// ETHMAC_SRAM_WRITER_SLOT @@ -71,11 +65,11 @@ pub struct LiteEthMacRegisters { } impl LiteEthMacRegisters { - fn rx_ev<'a>(&'a self) -> LiteEthRXEV<'a, R> { + fn rx_ev(&self) -> LiteEthRXEV<'_, R> { LiteEthRXEV::::new(&self.rx_ev_status, &self.rx_ev_pending, &self.rx_ev_enable) } - fn tx_ev<'a>(&'a self) -> LiteEthTXEV<'a, R> { + fn tx_ev(&self) -> LiteEthTXEV<'_, R> { LiteEthTXEV::::new(&self.tx_ev_status, &self.tx_ev_pending, &self.tx_ev_enable) } } @@ -155,7 +149,7 @@ impl<'a, R: LiteXSoCRegisterConfiguration> LiteEth<'a, R> { self.initialized.set(true); } - unsafe fn get_slot_buffer<'s>(&'s self, tx: bool, slot_id: usize) -> Option<&'s mut [u8]> { + unsafe fn get_slot_buffer(&self, tx: bool, slot_id: usize) -> Option<&mut [u8]> { if (tx && slot_id > self.tx_slots) || (!tx && slot_id > self.rx_slots) { return None; } diff --git a/chips/litex/src/litex_registers.rs b/chips/litex/src/litex_registers.rs index 89a8d74307..6ff1e36262 100644 --- a/chips/litex/src/litex_registers.rs +++ b/chips/litex/src/litex_registers.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! LiteX register abstraction types //! //! LiteX is able to generate vastly different SoC with different @@ -14,7 +18,7 @@ //! //! The different register types of a specific SoC configuration are //! combined using a -//! [`LiteXSoCRegisterConfiguration`](crate::litex_registers::LiteXSoCRegisterConfiguration) +//! [`LiteXSoCRegisterConfiguration`] //! structure, which can be used to adapt the register interfaces of //! peripherals to the different configurations. //! @@ -186,7 +190,7 @@ pub trait Read { fn is_set(&self, field: Field) -> bool; /// Check if any specified parts of a field match - fn matches_any(&self, field: FieldValue) -> bool; + fn any_matching_bits_set(&self, field: FieldValue) -> bool; /// Check if all specified parts of a field match fn matches_all(&self, field: FieldValue) -> bool; @@ -273,8 +277,8 @@ where } #[inline] - fn matches_any(&self, field: FieldValue) -> bool { - field.matches_any(self.get()) + fn any_matching_bits_set(&self, field: FieldValue) -> bool { + field.any_matching_bits_set(self.get()) } #[inline] diff --git a/chips/litex/src/timer.rs b/chips/litex/src/timer.rs index 243d3b09f4..4f91cec01a 100644 --- a/chips/litex/src/timer.rs +++ b/chips/litex/src/timer.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! LiteX timer core //! //! Hardware source and documentation available at @@ -61,7 +65,7 @@ pub struct LiteXTimerRegisters { /// /// This register is only present if the SoC was configured with /// `timer_update = True`. Therefore, it's only indirectly - /// accessed by the [`LiteXTimerUptime`](LiteXTimerUptime) struct, + /// accessed by the [`LiteXTimerUptime`] struct, /// which a board will need to construct separately. uptime_latch: R::ReadWrite8, /// Latched uptime since power-up (in `sys_clk` cycles) @@ -70,13 +74,13 @@ pub struct LiteXTimerRegisters { /// /// This register is only present if the SoC was configured with /// `timer_update = True`. Therefore, it's only indirectly - /// accessed by the [`LiteXTimerUptime`](LiteXTimerUptime) struct, + /// accessed by the [`LiteXTimerUptime`] struct, /// which a board will need to construct separately. uptime: R::ReadOnly64, } impl LiteXTimerRegisters { - fn ev<'a>(&'a self) -> LiteXTimerEV<'a, R> { + fn ev(&self) -> LiteXTimerEV<'_, R> { LiteXTimerEV::::new(&self.ev_status, &self.ev_pending, &self.ev_enable) } } @@ -180,7 +184,7 @@ impl LiteXTimer<'_, R, F> { /// /// Clients should use the [`LiteXTimerUptime`] wrapper instead, /// which exposes this value as part of their - /// [`Time::now`](Time::now) implementation. + /// [`Time::now`] implementation. unsafe fn uptime(&self) -> Ticks64 { WriteRegWrapper::wrap(&self.registers.uptime_latch).write(uptime_latch::latch_value::SET); self.registers.uptime.get().into() @@ -344,7 +348,7 @@ impl<'a, R: LiteXSoCRegisterConfiguration, F: Frequency> Timer<'a> for LiteXTime /// LiteX does not have an [`Alarm`] compatible hardware peripheral, /// so an [`Alarm`] is emulated using a repeatedly set [`LiteXTimer`], /// comparing the current time against the [`LiteXTimerUptime`] (which -/// is also exposed as [`Time::now`](Time::now). +/// is also exposed as [`Time::now`]. pub struct LiteXAlarm<'t, 'c, R: LiteXSoCRegisterConfiguration, F: Frequency> { uptime: &'t LiteXTimerUptime<'t, R, F>, timer: &'t LiteXTimer<'t, R, F>, @@ -362,7 +366,7 @@ impl<'t, 'c, R: LiteXSoCRegisterConfiguration, F: Frequency> LiteXAlarm<'t, 'c, uptime, timer, alarm_client: OptionalCell::empty(), - reference_time: Cell::new((0 as u32).into()), + reference_time: Cell::new((0_u32).into()), alarm_time: OptionalCell::empty(), } } @@ -404,7 +408,7 @@ impl<'t, 'c, R: LiteXSoCRegisterConfiguration, F: Frequency> LiteXAlarm<'t, 'c, } else { // Trigger an interrupt one tick from now, which will // call this function again - self.timer.oneshot((1 as u32).into()); + self.timer.oneshot((1_u32).into()); } } else { // It's not yet time to call the client, set the timer @@ -460,7 +464,7 @@ impl<'t, 'c, R: LiteXSoCRegisterConfiguration, F: Frequency> Alarm<'c> fn get_alarm(&self) -> Self::Ticks { // Undefined at boot, so 0 - self.alarm_time.unwrap_or((0 as u32).into()) + self.alarm_time.unwrap_or((0_u32).into()) } fn is_armed(&self) -> bool { @@ -475,7 +479,7 @@ impl<'t, 'c, R: LiteXSoCRegisterConfiguration, F: Frequency> Alarm<'c> } fn minimum_dt(&self) -> Self::Ticks { - (1 as u32).into() + (1_u32).into() } } diff --git a/chips/litex/src/uart.rs b/chips/litex/src/uart.rs index 4b9d75f0f7..9b3a35f633 100644 --- a/chips/litex/src/uart.rs +++ b/chips/litex/src/uart.rs @@ -1,12 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! LiteX UART core //! //! Hardware source and documentation available at //! [`litex/soc/cores/uart.py`](https://github.com/enjoy-digital/litex/blob/master/litex/soc/cores/uart.py). use core::cell::Cell; -use kernel::dynamic_deferred_call::{ - DeferredCallHandle, DynamicDeferredCall, DynamicDeferredCallClient, -}; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; use kernel::hil::uart; use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::utilities::StaticRef; @@ -61,7 +63,7 @@ pub struct LiteXUartRegisters { impl LiteXUartRegisters { /// Create an event manager instance for the UART events - fn ev<'a>(&'a self) -> LiteXUartEV<'a, R> { + fn ev(&self) -> LiteXUartEV<'_, R> { LiteXUartEV::::new(&self.ev_status, &self.ev_pending, &self.ev_enable) } } @@ -99,15 +101,14 @@ pub struct LiteXUart<'a, R: LiteXSoCRegisterConfiguration> { rx_progress: Cell, rx_aborted: Cell, rx_deferred_call: Cell, - deferred_caller: &'static DynamicDeferredCall, - deferred_handle: OptionalCell, + deferred_call: DeferredCall, + initialized: Cell, } impl<'a, R: LiteXSoCRegisterConfiguration> LiteXUart<'a, R> { - pub const fn new( + pub fn new( uart_base: StaticRef>, phy_args: Option<(StaticRef>, u32)>, - deferred_caller: &'static DynamicDeferredCall, ) -> LiteXUart<'a, R> { LiteXUart { uart_regs: uart_base, @@ -124,14 +125,14 @@ impl<'a, R: LiteXSoCRegisterConfiguration> LiteXUart<'a, R> { rx_progress: Cell::new(0), rx_aborted: Cell::new(false), rx_deferred_call: Cell::new(false), - deferred_caller, - deferred_handle: OptionalCell::empty(), + deferred_call: DeferredCall::new(), + initialized: Cell::new(false), } } - pub fn initialize(&self, deferred_call_handle: DeferredCallHandle) { + pub fn initialize(&self) { self.uart_regs.ev().disable_all(); - self.deferred_handle.set(deferred_call_handle); + self.initialized.set(true); } pub fn transmit_sync(&self, bytes: &[u8]) { @@ -267,7 +268,7 @@ impl<'a, R: LiteXSoCRegisterConfiguration> LiteXUart<'a, R> { if progress < len { // If we haven't transmitted all data, we _must_ have // reached the fifo-limit - assert!(fifo_full == true); + assert!(fifo_full); // Place all information and buffers back for the next // call to `resume_tx`, triggered by an interrupt. @@ -304,7 +305,7 @@ impl uart::Configure for LiteXUart<'_, R> { if params.width != uart::Width::Eight || params.parity != uart::Parity::None || params.stop_bits != uart::StopBits::One - || params.hw_flow_control == true + || params.hw_flow_control { Err(ErrorCode::NOSUPPORT) } else if params.baud_rate == 0 || params.baud_rate > system_clock { @@ -336,7 +337,7 @@ impl<'a, R: LiteXSoCRegisterConfiguration> uart::Transmit<'a> for LiteXUart<'a, tx_len: usize, ) -> Result<(), (ErrorCode, &'static mut [u8])> { // Make sure the UART is initialized - assert!(self.deferred_handle.is_some()); + assert!(self.initialized.get()); if tx_buffer.len() < tx_len { return Err((ErrorCode::SIZE, tx_buffer)); @@ -397,8 +398,7 @@ impl<'a, R: LiteXSoCRegisterConfiguration> uart::Transmit<'a> for LiteXUart<'a, assert!(progress == tx_len); self.tx_deferred_call.set(true); - self.deferred_handle - .map(|handle| self.deferred_caller.set(*handle)); + self.deferred_call.set(); } // If fifo_full == true, we will get an interrupt @@ -408,7 +408,7 @@ impl<'a, R: LiteXSoCRegisterConfiguration> uart::Transmit<'a> for LiteXUart<'a, fn transmit_word(&self, _word: u32) -> Result<(), ErrorCode> { // Make sure the UART is initialized - assert!(self.deferred_handle.is_some()); + assert!(self.initialized.get()); Err(ErrorCode::FAIL) } @@ -421,15 +421,14 @@ impl<'a, R: LiteXSoCRegisterConfiguration> uart::Transmit<'a> for LiteXUart<'a, // `deferred_tx_abort` if `tx_aborted` is set // Make sure the UART is initialized - assert!(self.deferred_handle.is_some()); + assert!(self.initialized.get()); self.uart_regs.ev().disable_event(EVENT_MANAGER_INDEX_TX); if self.tx_buffer.is_some() { self.tx_aborted.set(true); self.tx_deferred_call.set(true); - self.deferred_handle - .map(|handle| self.deferred_caller.set(*handle)); + self.deferred_call.set(); Err(ErrorCode::BUSY) } else { @@ -449,7 +448,7 @@ impl<'a, R: LiteXSoCRegisterConfiguration> uart::Receive<'a> for LiteXUart<'a, R rx_len: usize, ) -> Result<(), (ErrorCode, &'static mut [u8])> { // Make sure the UART is initialized - assert!(self.deferred_handle.is_some()); + assert!(self.initialized.get()); if rx_len > rx_buffer.len() { return Err((ErrorCode::SIZE, rx_buffer)); @@ -484,8 +483,7 @@ impl<'a, R: LiteXSoCRegisterConfiguration> uart::Receive<'a> for LiteXUart<'a, R // instead! Otherwise we risk double-delivery of the // interrupt _and_ the deferred call self.rx_deferred_call.set(true); - self.deferred_handle - .map(|handle| self.deferred_caller.set(*handle)); + self.deferred_call.set(); } else { // We do _not_ clear any pending data in the FIFO by // acknowledging previous events @@ -497,13 +495,13 @@ impl<'a, R: LiteXSoCRegisterConfiguration> uart::Receive<'a> for LiteXUart<'a, R fn receive_word(&self) -> Result<(), ErrorCode> { // Make sure the UART is initialized - assert!(self.deferred_handle.is_some()); + assert!(self.initialized.get()); Err(ErrorCode::FAIL) } fn receive_abort(&self) -> Result<(), ErrorCode> { // Make sure the UART is initialized - assert!(self.deferred_handle.is_some()); + assert!(self.initialized.get()); // Disable RX events self.uart_regs.ev().disable_event(EVENT_MANAGER_INDEX_RX); @@ -513,8 +511,7 @@ impl<'a, R: LiteXSoCRegisterConfiguration> uart::Receive<'a> for LiteXUart<'a, R // call self.rx_aborted.set(true); self.rx_deferred_call.set(true); - self.deferred_handle - .map(|handle| self.deferred_caller.set(*handle)); + self.deferred_call.set(); Err(ErrorCode::BUSY) } else { @@ -523,8 +520,12 @@ impl<'a, R: LiteXSoCRegisterConfiguration> uart::Receive<'a> for LiteXUart<'a, R } } -impl<'a, R: LiteXSoCRegisterConfiguration> DynamicDeferredCallClient for LiteXUart<'a, R> { - fn call(&self, _handle: DeferredCallHandle) { +impl<'a, R: LiteXSoCRegisterConfiguration> DeferredCallClient for LiteXUart<'a, R> { + fn register(&'static self) { + self.deferred_call.register(self) + } + + fn handle_deferred_call(&self) { // Are we currently in a TX or RX transaction? if self.tx_deferred_call.get() { self.tx_deferred_call.set(false); diff --git a/chips/litex_vexriscv/Cargo.toml b/chips/litex_vexriscv/Cargo.toml index 6ca310d2cb..e467b43602 100644 --- a/chips/litex_vexriscv/Cargo.toml +++ b/chips/litex_vexriscv/Cargo.toml @@ -1,10 +1,17 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "litex_vexriscv" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] rv32i = { path = "../../arch/rv32i" } kernel = { path = "../../kernel" } litex = { path = "../litex" } + +[lints] +workspace = true diff --git a/chips/litex_vexriscv/src/chip.rs b/chips/litex_vexriscv/src/chip.rs index 5e103fe800..5ade29692c 100644 --- a/chips/litex_vexriscv/src/chip.rs +++ b/chips/litex_vexriscv/src/chip.rs @@ -1,12 +1,16 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! High-level setup and interrupt mapping for the chip. use core::fmt::Write; -use kernel; +use core::ptr::addr_of; use kernel::debug; use kernel::platform::chip::InterruptService; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; use rv32i::csr::{mcause, mie::mie, CSR}; -use rv32i::pmp::PMP; +use rv32i::pmp::{kernel_protection::KernelProtectionPMP, PMPUserMPU}; use rv32i::syscall::SysCall; use crate::interrupt_controller::VexRiscvInterruptController; @@ -18,21 +22,25 @@ static mut INTERRUPT_CONTROLLER: VexRiscvInterruptController = VexRiscvInterrupt // The VexRiscv "Secure" variant of // [pythondata-cpu-vexriscv](https://github.com/litex-hub/pythondata-cpu-vexriscv) // has 16 PMP slots -pub struct LiteXVexRiscv> { +pub struct LiteXVexRiscv { soc_identifier: &'static str, userspace_kernel_boundary: SysCall, interrupt_controller: &'static VexRiscvInterruptController, - pmp: PMP<8>, + pmp_mpu: PMPUserMPU<4, KernelProtectionPMP<16>>, interrupt_service: &'static I, } -impl> LiteXVexRiscv { - pub unsafe fn new(soc_identifier: &'static str, interrupt_service: &'static I) -> Self { +impl LiteXVexRiscv { + pub unsafe fn new( + soc_identifier: &'static str, + interrupt_service: &'static I, + pmp: KernelProtectionPMP<16>, + ) -> Self { Self { soc_identifier, userspace_kernel_boundary: SysCall::new(), - interrupt_controller: &INTERRUPT_CONTROLLER, - pmp: PMP::new(), + interrupt_controller: &*addr_of!(INTERRUPT_CONTROLLER), + pmp_mpu: PMPUserMPU::new(pmp), interrupt_service, } } @@ -51,12 +59,12 @@ impl> LiteXVexRiscv { } } -impl> kernel::platform::chip::Chip for LiteXVexRiscv { - type MPU = PMP<8>; +impl kernel::platform::chip::Chip for LiteXVexRiscv { + type MPU = PMPUserMPU<4, KernelProtectionPMP<16>>; type UserspaceKernelBoundary = SysCall; fn mpu(&self) -> &Self::MPU { - &self.pmp + &self.pmp_mpu } fn userspace_kernel_boundary(&self) -> &SysCall { @@ -99,6 +107,7 @@ impl> kernel::platform::chip::Chip for LiteXVe self.soc_identifier, )); rv32i::print_riscv_state(writer); + let _ = writer.write_fmt(format_args!("{}", self.pmp_mpu.pmp)); } } diff --git a/chips/litex_vexriscv/src/interrupt_controller.rs b/chips/litex_vexriscv/src/interrupt_controller.rs index 0709a2725e..4abc24d50a 100644 --- a/chips/litex_vexriscv/src/interrupt_controller.rs +++ b/chips/litex_vexriscv/src/interrupt_controller.rs @@ -1,7 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! VexRiscv-specific interrupt controller implementation use core::cell::Cell; -use core::mem::size_of; /// Rust wrapper around the raw CSR-based VexRiscv interrupt /// controller @@ -42,7 +45,7 @@ impl VexRiscvInterruptController { /// having a higher priority. pub fn next_saved(&self) -> Option { let saved_interrupts: usize = self.saved_interrupts.get(); - let interrupt_bits = size_of::() * 8; + let interrupt_bits = usize::BITS as usize; // If there are no interrupts pending (saved_interrupts == 0), // usize::trailing_zeros will return usize::BITS, in which @@ -99,7 +102,7 @@ mod vexriscv_irq_raw { /// defined in litex/soc/cores/cpu/vexriscv/csr-defs.h const CSR_IRQ_PENDING: usize = 0xFC0; - #[cfg(not(any(target_arch = "riscv32", target_os = "none")))] + #[cfg(not(all(target_arch = "riscv32", target_os = "none")))] pub unsafe fn irq_getmask() -> usize { 0 } @@ -107,19 +110,25 @@ mod vexriscv_irq_raw { #[cfg(all(target_arch = "riscv32", target_os = "none"))] pub unsafe fn irq_getmask() -> usize { let mask: usize; - asm!("csrr {mask}, {csr}", mask = out(reg) mask, csr = const CSR_IRQ_MASK); + use core::arch::asm; + // TODO: If asm_const is stabilized, transition back to below + //asm!("csrr {mask}, {csr}", mask = out(reg) mask, csr = const CSR_IRQ_MASK); + asm!("csrr {mask}, 0xBC0", mask = out(reg) mask); mask } - #[cfg(not(any(target_arch = "riscv32", target_os = "none")))] + #[cfg(not(all(target_arch = "riscv32", target_os = "none")))] pub unsafe fn irq_setmask(_mask: usize) {} #[cfg(all(target_arch = "riscv32", target_os = "none"))] pub unsafe fn irq_setmask(mask: usize) { - asm!("csrw {csr}, {mask}", csr = const CSR_IRQ_MASK, mask = in(reg) mask); + use core::arch::asm; + // TODO: If asm_const is stabilized, transition back to below + //asm!("csrw {csr}, {mask}", csr = const CSR_IRQ_MASK, mask = in(reg) mask); + asm!("csrw 0xBC0, {mask}", mask = in(reg) mask); } - #[cfg(not(any(target_arch = "riscv32", target_os = "none")))] + #[cfg(not(all(target_arch = "riscv32", target_os = "none")))] pub unsafe fn irq_pending() -> usize { 0 } @@ -127,7 +136,10 @@ mod vexriscv_irq_raw { #[cfg(all(target_arch = "riscv32", target_os = "none"))] pub unsafe fn irq_pending() -> usize { let pending: usize; - asm!("csrr {pending}, {csr}", pending = out(reg) pending, csr = const CSR_IRQ_PENDING); + use core::arch::asm; + // TODO: If asm_const is stabilized, transition back to below + //asm!("csrr {pending}, {csr}", pending = out(reg) pending, csr = const CSR_IRQ_PENDING); + asm!("csrr {pending}, 0xFC0", pending = out(reg) pending); pending } } diff --git a/chips/litex_vexriscv/src/lib.rs b/chips/litex_vexriscv/src/lib.rs index 849e02a5a2..9df0d8651a 100644 --- a/chips/litex_vexriscv/src/lib.rs +++ b/chips/litex_vexriscv/src/lib.rs @@ -1,6 +1,9 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! LiteX SoCs based around a VexRiscv CPU -#![feature(asm, asm_const, const_fn_trait_bound)] #![no_std] #![crate_name = "litex_vexriscv"] #![crate_type = "rlib"] diff --git a/chips/lowrisc/Cargo.toml b/chips/lowrisc/Cargo.toml index 196f5937bf..7c26e23df6 100644 --- a/chips/lowrisc/Cargo.toml +++ b/chips/lowrisc/Cargo.toml @@ -1,9 +1,16 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "lowrisc" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] rv32i = { path = "../../arch/rv32i" } kernel = { path = "../../kernel" } + +[lints] +workspace = true diff --git a/chips/lowrisc/src/aon_timer.rs b/chips/lowrisc/src/aon_timer.rs new file mode 100644 index 0000000000..4fa5fae545 --- /dev/null +++ b/chips/lowrisc/src/aon_timer.rs @@ -0,0 +1,207 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! AON/Watchdog Timer Driver + +use kernel::platform; +use kernel::utilities::registers::interfaces::{Readable, Writeable}; +use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite, WriteOnly}; +use kernel::utilities::StaticRef; + +// Based on the latest commit of OpenTitan supported by tock: +// Refer: https://github.com/lowRISC/opentitan/blob/217a0168ba118503c166a9587819e3811eeb0c0c/hw/ip/aon_timer/rtl/aon_timer_reg_pkg.sv#L136 +register_structs! { + pub AonTimerRegisters { + //AON_TIMER: Alert Test Register + (0x000 => alert_test: WriteOnly), + //AON_TIMER: Wakeup Timer Control Register + (0x004 => wkup_ctrl: ReadWrite), + //AON_TIMER: Wakeup Timer Threshold Register + (0x008 => wkup_thold: ReadWrite), + //AON_TIMER: Wakeup Timer Count Register + (0x00C => wkup_count: ReadWrite), + //AON_TIMER: Watchdog Timer Write Enable Register [rw0c] + (0x010 => wdog_regwen: ReadWrite), + //AON_TIMER: Watchdog Timer Control register + (0x014 => wdog_ctrl: ReadWrite), + //AON_TIMER: Watchdog Timer Bark Threshold Register + (0x018 => wdog_bark_thold: ReadWrite), + //AON_TIMER: Watchdog Timer Bite Threshold Register + (0x01C => wdog_bite_thold: ReadWrite), + //AON_TIMER: Watchdog Timer Count Register + (0x020 => wdog_count: ReadWrite), + //AON_TIMER: Interrupt State Register [rw1c] + (0x024 => intr_state: ReadWrite), + //AON_TIMER: Interrupt Test Reigster + (0x028 => intr_test: WriteOnly), + //AON_TIMER: Wakeup Request Status [rw0c] + (0x02C => wkup_cause: ReadWrite), + (0x030 => @END), + } +} + +register_bitfields![u32, + ALERT_TEST[ + FATAL_FAULT OFFSET(0) NUMBITS(1) [] + ], + WKUP_CTRL[ + ENABLE OFFSET(0) NUMBITS(1) [], + PRESCALER OFFSET(1) NUMBITS(12) [] + ], + THRESHOLD[ + THRESHOLD OFFSET(0) NUMBITS(32) [] + ], + WKUP_COUNT[ + COUNT OFFSET(0) NUMBITS(32) [] + ], + WDOG_REGWEN[ + REGWEN OFFSET(0) NUMBITS(1) [] + ], + WDOG_CTRL[ + ENABLE OFFSET(0) NUMBITS(1) [], + PAUSE_IN_SLEEP OFFSET(1) NUMBITS(1) [] + ], + WDOG_COUNT[ + COUNT OFFSET(0) NUMBITS(32) [], + ], + INTR[ + WKUP_TIMER_EXPIRED OFFSET(0) NUMBITS(1) [], + WDOG_TIMER_BARK OFFSET(1) NUMBITS(1) [] + ], + WKUP_CAUSE[ + CAUSE OFFSET(0) NUMBITS(1) [], + ] +]; + +pub struct AonTimer { + registers: StaticRef, + aon_clk_freq: u32, //Hz, this differs for FPGA/Verilator +} + +impl AonTimer { + pub const fn new(base: StaticRef, aon_clk_freq: u32) -> AonTimer { + AonTimer { + registers: base, + aon_clk_freq, + } + } + + /// Reset both watch dog and wake up timer count values. + fn reset_timers(&self) { + let regs = self.registers; + regs.wkup_count.set(0x00); + regs.wdog_count.set(0x00); + } + + /// Start the watchdog counter with pause in sleep + /// i.e wdog timer is paused when system is sleeping + fn wdog_start_count(&self) { + self.registers + .wdog_ctrl + .write(WDOG_CTRL::ENABLE::SET + WDOG_CTRL::PAUSE_IN_SLEEP::SET); + } + + /// Program the desired thresholds in WKUP_THOLD, WDOG_BARK_THOLD and WDOG_BITE_THOLD + fn set_wdog_thresh(&self) { + let regs = self.registers; + // Watchdog period may need to be revised with kernel changes/updates + // since the watchdog is `tickled()` at the start of every kernel loop + // see: https://github.com/tock/tock/blob/eb3f7ce59434b7ac1b77ef1ab7dd2afad1a62ac5/kernel/src/kernel.rs#L448 + let bark_cycles = self.ms_to_cycles(500); + // ~1000ms bite period + let bite_cycles = bark_cycles.saturating_mul(2); + + regs.wdog_bark_thold + .write(THRESHOLD::THRESHOLD.val(bark_cycles)); + regs.wdog_bite_thold + .write(THRESHOLD::THRESHOLD.val(bite_cycles)); + } + + // Reset watch dog timer + fn wdog_pet(&self) { + self.registers.wdog_count.set(0x00); + } + + fn wdog_suspend(&self) { + self.registers.wdog_ctrl.write(WDOG_CTRL::ENABLE::CLEAR); + } + + fn wdog_resume(&self) { + self.registers.wdog_ctrl.write(WDOG_CTRL::ENABLE::SET); + } + + /// Locks further config to WDOG until next system reset + fn lock_wdog_conf(&self) { + self.registers.wdog_regwen.write(WDOG_REGWEN::REGWEN::SET) + } + + /// Convert microseconds to cycles + fn ms_to_cycles(&self, ms: u32) -> u32 { + // 250kHZ CW130 or 125kHz Verilator (as specified in chip config) + ms.saturating_mul(self.aon_clk_freq).saturating_div(1000) + } + + fn reset_wkup_count(&self) { + self.registers.wkup_count.set(0x00); + } + + pub fn handle_interrupt(&self) { + let regs = self.registers; + let intr = self.registers.intr_state.extract(); + + if intr.is_set(INTR::WKUP_TIMER_EXPIRED) { + // Wake up timer has expired, sw must ack and clear + regs.wkup_cause.set(0x00); + regs.wkup_count.set(0x00); // To avoid re-triggers + self.reset_wkup_count(); + // RW1C, clear the interrupt + regs.intr_state.write(INTR::WKUP_TIMER_EXPIRED::SET); + } + + if intr.is_set(INTR::WDOG_TIMER_BARK) { + // Clear the bark (RW1C) and pet doggo + regs.intr_state.write(INTR::WDOG_TIMER_BARK::SET); + self.wdog_pet(); + } + } +} + +impl platform::watchdog::WatchDog for AonTimer { + /// The always-on timer will run on a ~125KHz (Verilator) or ~250kHz clock. + /// The timers themselves are 32b wide, giving a maximum timeout + /// window of roughly ~6 hours. For the wakeup timer, the pre-scaler + /// extends the maximum timeout to ~1000 days. + /// + /// The AON HW_IP has a watchdog and a wake-up timer (counts independantly of eachother), + /// although struct `AonTimer` implements the wakeup timer functionality, + /// we only start and use the watchdog in the code below. + fn setup(&self) { + // 1. Clear Timers + self.reset_timers(); + + // 2. Set thresholds. + self.set_wdog_thresh(); + + // 3. Commence gaurd duty... + self.wdog_start_count(); + + // 4. Lock watchdog config + // Preventing firmware from accidentally or maliciously disabling the watchdog, + // until next system reset. + self.lock_wdog_conf(); + } + + fn tickle(&self) { + // Nothing to worry about, good dog... + self.wdog_pet(); + } + + fn suspend(&self) { + self.wdog_suspend(); + } + + fn resume(&self) { + self.wdog_resume(); + } +} diff --git a/chips/lowrisc/src/csrng.rs b/chips/lowrisc/src/csrng.rs index 424187a50b..ab2a6cdda7 100644 --- a/chips/lowrisc/src/csrng.rs +++ b/chips/lowrisc/src/csrng.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Support for the CSRNG hardware block on OpenTitan //! //! @@ -21,15 +25,15 @@ register_structs! { (0x14 => ctrl: ReadWrite), (0x18 => cmd_req: WriteOnly), (0x1C => sw_cmd_sts: ReadOnly), - (0x20 => genbits_vld: ReadOnly), + (0x20 => genbits_vld: ReadOnly), (0x24 => genbits: ReadOnly), (0x28 => int_state_num: ReadWrite), (0x2C => int_state_val: ReadOnly), (0x30 => hw_exc_sts: ReadWrite), - (0x34 => err_code: ReadOnly), - (0x38 => err_code_test: ReadWrite), - (0x3C => sel_tracking_sm: WriteOnly), - (0x40 => tracking_sm_obs: ReadOnly), + (0x34 => recov_alert_sts: ReadWrite), + (0x38 => err_code: ReadOnly), + (0x3C => err_code_test: ReadWrite), + (0x40 => main_sm_state: ReadOnly), (0x44 => @END), } } @@ -46,12 +50,17 @@ register_bitfields![u32, ], CTRL [ ENABLE OFFSET(0) NUMBITS(4) [ - ENABLE = 0xA, + ENABLE = 0x6, + DISABLE = 0x9, ], SW_APP_ENABLE OFFSET(4) NUMBITS(4) [ - ENABLE = 0xA, + ENABLE = 0x6, + DISABLE = 0x9, + ], + READ_INT_STATE OFFSET(8) NUMBITS(4) [ + ENABLE = 0x6, + DISABLE = 0x9, ], - READ_INT_STATE OFFSET(4) NUMBITS(4) [], ], COMMAND [ ACMD OFFSET(0) NUMBITS(4) [ @@ -62,8 +71,14 @@ register_bitfields![u32, UNINSTANTIATE = 5, ], CLEN OFFSET(4) NUMBITS(4) [], - FLAGS OFFSET(8) NUMBITS(4) [], - GLEN OFFSET(12) NUMBITS(19) [], + FLAGS OFFSET(8) NUMBITS(4) [ + INSTANTIATE_SOURCE_XOR_SEED = 0x9, + INSTANTIATE_ZERO_ADDITIONAL_SEED = 0x6, + ], + GLEN OFFSET(12) NUMBITS(13) [], + ], + GENBIT_VLD [ + GENBITS_VLD OFFSET(0) NUMBITS(1) [], ], SW_CMD_STS [ CMD_RDY OFFSET(0) NUMBITS(1) [], @@ -71,6 +86,8 @@ register_bitfields![u32, ], ]; +pub const TWO_UNITS_OF_128BIT_ENTROPY: u32 = 0x02; + pub struct CsRng<'a> { registers: StaticRef, @@ -83,11 +100,10 @@ impl Iterator for CsRngIter<'_, '_> { type Item = u32; fn next(&mut self) -> Option { - let ret = self.0.registers.genbits.get(); - if ret == 0 { - None + if self.0.registers.genbits_vld.is_set(GENBIT_VLD::GENBITS_VLD) { + Some(self.0.registers.genbits.get()) } else { - Some(ret) + None } } } @@ -158,6 +174,20 @@ impl<'a> CsRng<'a> { } } } + + /// Wait for the IP to be ready for a new command, if we take too long and timeout + /// return BUSY. This MUST be checked prior to issuing new commands. + /// CMD_RDY: is set when the command interface is ready to accept a command. + fn wait_for_cmd_ready(&self) -> Result<(), ErrorCode> { + for _i in 0..10000 { + if self.registers.sw_cmd_sts.is_set(SW_CMD_STS::CMD_RDY) { + // We are ready to issue a command + return Ok(()); + } + } + // Timed out + Err(ErrorCode::BUSY) + } } impl<'a> Entropy32<'a> for CsRng<'a> { @@ -173,22 +203,40 @@ impl<'a> Entropy32<'a> for CsRng<'a> { return Err(ErrorCode::FAIL); } - self.registers - .ctrl - .write(CTRL::ENABLE::ENABLE + CTRL::SW_APP_ENABLE::ENABLE); + self.registers.ctrl.write( + CTRL::ENABLE::ENABLE + CTRL::READ_INT_STATE::ENABLE + CTRL::SW_APP_ENABLE::ENABLE, + ); - self.registers - .cmd_req - .write(COMMAND::ACMD::INSTANTIATE + COMMAND::CLEN.val(0)); - while !self.registers.sw_cmd_sts.is_set(SW_CMD_STS::CMD_RDY) {} + // Check if IP ready for new command + match self.wait_for_cmd_ready() { + Ok(()) => {} + Err(e) => return Err(e), + } + + // Init IP + self.registers.cmd_req.write( + COMMAND::ACMD::INSTANTIATE + + COMMAND::FLAGS::INSTANTIATE_ZERO_ADDITIONAL_SEED + + COMMAND::CLEN.val(0x00) + + COMMAND::GLEN.val(0x00), + ); + + // Check if IP ready for new command + match self.wait_for_cmd_ready() { + Ok(()) => {} + Err(e) => return Err(e), + } self.disable_interrupts(); self.enable_interrupts(); // Get 256 bits of entropy - self.registers - .cmd_req - .write(COMMAND::ACMD::GENERATE + COMMAND::GLEN.val(0x2)); + self.registers.cmd_req.write( + COMMAND::ACMD::GENERATE + + COMMAND::FLAGS.val(0) + + COMMAND::CLEN.val(0x00) + + COMMAND::GLEN.val(TWO_UNITS_OF_128BIT_ENTROPY), + ); Ok(()) } diff --git a/chips/lowrisc/src/flash_ctrl.rs b/chips/lowrisc/src/flash_ctrl.rs index b1287e3a78..5e39eb8441 100644 --- a/chips/lowrisc/src/flash_ctrl.rs +++ b/chips/lowrisc/src/flash_ctrl.rs @@ -1,15 +1,18 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Flash Controller use core::cell::Cell; use core::ops::{Index, IndexMut}; +use kernel::hil; use kernel::utilities::cells::OptionalCell; use kernel::utilities::cells::TakeCell; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; use kernel::utilities::registers::{ register_bitfields, register_structs, ReadOnly, ReadWrite, WriteOnly, }; - -use kernel::hil; use kernel::utilities::StaticRef; use kernel::ErrorCode; @@ -28,42 +31,45 @@ register_structs! { (0x028 => prog_type_en: ReadWrite), (0x02c => erase_suspend: ReadWrite), (0x030 => region_cfg_regwen: [ReadWrite; 8]), - (0x050 => mp_region_cfg_shadowed: [ReadWrite; 8]), - (0x070 => default_region_shadowed: ReadWrite), - - (0x074 => bank0_info0_regwen: [ReadWrite; 10]), - (0x09C => bank0_info0_page_cfg_shadowed: [ReadWrite; 10]), - (0x0C4 => bank0_info1_regwen: ReadWrite), - (0x0C8 => bank0_info1_page_cfg_shadowed: ReadWrite), - (0x0CC => bank0_info2_regwen: [ReadWrite; 2]), - (0x0D4 => bank0_info2_page_cfg_shadowed: [ReadWrite; 2]), - - (0x0DC => bank1_info0_regwen: [ReadWrite; 10]), - (0x104 => bank1_info0_page_cfg_shadowed: [ReadWrite; 10]), - (0x12C => bank1_info1_regwen: ReadWrite), - (0x130 => bank1_info1_page_cfg_shadowed: ReadWrite), - (0x134 => bank1_info2_regwen: [ReadWrite; 2]), - (0x13C => bank1_info2_page_cfg_shadowed: [ReadWrite; 2]), - - (0x144 => bank_cfg_regwen: ReadWrite), - (0x148 => mp_bank_cfg_shadowed: ReadWrite), - (0x14C => op_status: ReadWrite), - (0x150 => status: ReadOnly), - (0x154 => err_code: ReadOnly), - (0x158 => fault_status: ReadOnly), - (0x15C => err_addr: ReadOnly), - (0x160 => ecc_single_err_cnt: ReadOnly), - (0x164 => ecc_single_addr: [ReadOnly; 2]), - (0x16C => phy_err_cfg_regwen: ReadOnly), - (0x170 => phy_err_cfg: ReadOnly), - (0x174 => phy_alert_cfg: ReadOnly), - (0x178 => phy_status: ReadOnly), - (0x17C => scratch: ReadWrite), - (0x180 => fifo_lvl: ReadWrite), - (0x184 => fifo_rst: ReadWrite), - (0x188 => prog_fifo: WriteOnly), - (0x18C => rd_fifo: ReadOnly), - (0x190=> @END), + (0x050 => mp_region_cfg: [ReadWrite; 8]), + (0x070 => mp_region: [ReadWrite; 8]), + (0x090 => default_region: ReadWrite), + + (0x094 => bank0_info0_regwen: [ReadWrite; 10]), + (0x0BC => bank0_info0_page_cfg: [ReadWrite; 10]), + (0x0E4 => bank0_info1_regwen: ReadWrite), + (0x0E8 => bank0_info1_page_cfg: ReadWrite), + (0x0EC => bank0_info2_regwen: [ReadWrite; 2]), + (0x0F4 => bank0_info2_page_cfg: [ReadWrite; 2]), + + (0x0FC => bank1_info0_regwen: [ReadWrite; 10]), + (0x124 => bank1_info0_page_cfg: [ReadWrite; 10]), + (0x14C => bank1_info1_regwen: ReadWrite), + (0x150 => bank1_info1_page_cfg: ReadWrite), + (0x154 => bank1_info2_regwen: [ReadWrite; 2]), + (0x15C => bank1_info2_page_cfg: [ReadWrite; 2]), + + (0x164 => hw_info_cfg_override: ReadWrite), + (0x168 => bank_cfg_regwen: ReadWrite), + (0x16C => mp_bank_cfg_shadowed: ReadWrite), + (0x170 => op_status: ReadWrite), + (0x174 => status: ReadOnly), + (0x178 => debug_state: ReadOnly), + (0x17C => err_code: ReadWrite), + (0x180 => std_fault_status: ReadOnly), + (0x184 => fault_status: ReadOnly), + (0x188 => err_addr: ReadOnly), + (0x18C => ecc_single_err_cnt: ReadOnly), + (0x190 => ecc_single_addr: [ReadOnly; 2]), + (0x198 => phy_alert_cfg: ReadOnly), + (0x19C => phy_status: ReadOnly), + (0x1A0 => scratch: ReadWrite), + (0x1A4 => fifo_lvl: ReadWrite), + (0x1A8 => fifo_rst: ReadWrite), + (0x1AC => curr_fifo_lvl: WriteOnly), + (0x1B0 => prog_fifo: WriteOnly), + (0x1B4 => rd_fifo: ReadOnly), + (0x1B8=> @END), } } @@ -108,6 +114,15 @@ register_bitfields![u32, INFO_SEL OFFSET(9) NUMBITS(2) [], NUM OFFSET(16) NUMBITS(12) [] ], + ERR_CODE [ + OP_ERR OFFSET(0) NUMBITS(1) [], + MP_ERR OFFSET(1) NUMBITS(1) [], + RD_ERR OFFSET(2) NUMBITS(1) [], + PROG_ERR OFFSET(3) NUMBITS(1) [], + PROG_WIN_ERR OFFSET(4) NUMBITS(1) [], + PROG_TYPE_ERR OFFSET(5) NUMBITS(1) [], + UPDATE_ERR OFFSET(6) NUMBITS(1) [], + ], PROG_TYPE_EN [ NORMAL OFFSET(0) NUMBITS(1) [], REPAIR OFFSET(1) NUMBITS(1) [], @@ -119,18 +134,48 @@ register_bitfields![u32, START OFFSET(0) NUMBITS(32) [] ], REGION_CFG_REGWEN [ - REGION OFFSET(0) NUMBITS(1) [] + REGION OFFSET(0) NUMBITS(1) [ + // Once locked, region cannot be modified till next reset + Locked = 0, + // Region can be configured. + Enabled = 1, + ] ], MP_REGION_CFG [ - EN OFFSET(0) NUMBITS(1) [], - RD_EN OFFSET(1) NUMBITS(1) [], - PROG_EN OFFSET(2) NUMBITS(1) [], - ERASE_EN OFFSET(3) NUMBITS(1) [], - SCRAMBLE_EN OFFSET(4) NUMBITS(1) [], - ECC_EN OFFSET(5) NUMBITS(1) [], - HE_EN OFFSET(6) NUMBITS(1) [], - BASE OFFSET(8) NUMBITS(8) [], - SIZE OFFSET(20) NUMBITS(8) [] + // These config register fields require a special value of + // 0x6 (0110) to set, or 0x9 (1001) to reset + EN OFFSET(0) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], + RD_EN OFFSET(4) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], + PROG_EN OFFSET(8) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], + ERASE_EN OFFSET(12) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], + SCRAMBLE_EN OFFSET(16) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], + ECC_EN OFFSET(20) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], + HE_EN OFFSET(24) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], + ], + MP_REGION [ + BASE OFFSET(0) NUMBITS(9) [], + SIZE OFFSET(10) NUMBITS(10) [] ], BANK_INFO_REGWEN [ REGION OFFSET(0) NUMBITS(1) [ @@ -139,24 +184,63 @@ register_bitfields![u32, ] ], BANK_INFO_PAGE_CFG [ - EN OFFSET(0) NUMBITS(1) [], - RD_EN OFFSET(1) NUMBITS(1) [], - PROG_EN OFFSET(2) NUMBITS(1) [], - ERASE_EN OFFSET(3) NUMBITS(1) [], - SCRAMBLE_EN OFFSET(4) NUMBITS(1) [], - ECC_EN OFFSET(5) NUMBITS(1) [], - HE_EN OFFSET(6) NUMBITS(1) [], + EN OFFSET(0) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], + RD_EN OFFSET(4) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], + PROG_EN OFFSET(8) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], + ERASE_EN OFFSET(12) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], + SCRAMBLE_EN OFFSET(16) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], + ECC_EN OFFSET(20) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], + HE_EN OFFSET(24) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], ], BANK_CFG_REGWEN [ BANK OFFSET(0) NUMBITS(1) [] ], DEFAULT_REGION [ - RD_EN OFFSET(0) NUMBITS(1) [], - PROG_EN OFFSET(1) NUMBITS(1) [], - ERASE_EN OFFSET(2) NUMBITS(1) [], - SCRAMBLE_EN OFFSET(3) NUMBITS(1) [], - ECC_EN OFFSET(4) NUMBITS(1) [], - HE_EN OFFSET(5) NUMBITS(1) [], + RD_EN OFFSET(0) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], + PROG_EN OFFSET(4) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], + ERASE_EN OFFSET(8) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], + SCRAMBLE_EN OFFSET(12) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], + ECC_EN OFFSET(16) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], + HE_EN OFFSET(20) NUMBITS(4) [ + Set = 0x6, + Clear = 0x9, + ], ], MP_BANK_CFG [ ERASE_EN_0 OFFSET(0) NUMBITS(1) [], @@ -190,15 +274,43 @@ register_bitfields![u32, ] ]; -pub const PAGE_SIZE: usize = 64; - -pub struct LowRiscPage(pub [u8; PAGE_SIZE as usize]); +pub const PAGE_SIZE: usize = 2048; +pub const FLASH_ADDR_OFFSET: usize = 0x20000000; +pub const FLASH_WORD_SIZE: usize = 8; +pub const FLASH_PAGES_PER_BANK: usize = 256; +pub const FLASH_NUM_BANKS: usize = 2; +pub const FLASH_MAX_PAGES: usize = FLASH_NUM_BANKS * FLASH_PAGES_PER_BANK; +pub const FLASH_NUM_BUSWORDS_PER_BANK: usize = PAGE_SIZE / 4; +pub const FLASH_MP_MAX_CFGS: usize = 8; +// The programming windows size in words (32bit) +pub const FLASH_PROG_WINDOW_SIZE: usize = 16; +pub const FLASH_PROG_WINDOW_MASK: u32 = 0xFFFFFFF0; + +pub struct LowRiscPage(pub [u8; PAGE_SIZE]); + +/// Defines region permissions for flash memory protection. +/// To be used when requesting the flash controller to set +/// specific permissions for a regions, or when reading +/// the existing permission associated with a region. +#[derive(PartialEq, Debug)] +pub struct FlashMPConfig { + /// Region can be read. + pub read_en: bool, + /// Region can be programmed. + pub write_en: bool, + /// Region can be erased + pub erase_en: bool, + /// Region is scramble enabled + pub scramble_en: bool, + /// Region has ECC enabled + pub ecc_en: bool, + /// Region is high endurance enabled + pub he_en: bool, +} impl Default for LowRiscPage { fn default() -> Self { - Self { - 0: [0; PAGE_SIZE as usize], - } + Self([0; PAGE_SIZE]) } } @@ -249,11 +361,12 @@ pub struct FlashCtrl<'a> { read_index: Cell, write_buf: TakeCell<'static, LowRiscPage>, write_index: Cell, + write_word_addr: Cell, region_num: FlashRegion, } impl<'a> FlashCtrl<'a> { - pub const fn new(base: StaticRef, region_num: FlashRegion) -> Self { + pub fn new(base: StaticRef, region_num: FlashRegion) -> Self { FlashCtrl { registers: base, flash_client: OptionalCell::empty(), @@ -263,6 +376,7 @@ impl<'a> FlashCtrl<'a> { read_index: Cell::new(0), write_buf: TakeCell::empty(), write_index: Cell::new(0), + write_word_addr: Cell::new(0), region_num, } } @@ -285,68 +399,132 @@ impl<'a> FlashCtrl<'a> { self.registers.intr_state.set(0xFFFF_FFFF); } - fn configure_data_partition(&self, num: FlashRegion) { - for _ in 0..2 { - self.registers.default_region_shadowed.write( - DEFAULT_REGION::RD_EN::SET - + DEFAULT_REGION::PROG_EN::SET - + DEFAULT_REGION::ERASE_EN::SET, - ); + /// Calculates and returns the max num words that can be programmed without + /// crossing the programming resolution window boundaries, which + /// occur at every FLASH_PROG_WINDOW_SIZE words. Note, when setting + /// the CONTROL::NUM, write (ret_val - 1). + fn calculate_max_prog_len(&self, word_addr: u32, rem_bytes: u32) -> u32 { + // Calculate and return the max window limit possible for this transaction in words + let window_limit = + ((word_addr + FLASH_PROG_WINDOW_SIZE as u32) & FLASH_PROG_WINDOW_MASK) - word_addr; + let words_to_write = rem_bytes / 4; + + if words_to_write < window_limit { + words_to_write + } else { + window_limit } + } - for _ in 0..2 { - self.registers.mp_region_cfg_shadowed[num as usize].write( - MP_REGION_CFG::BASE.val(256) - + MP_REGION_CFG::SIZE.val(0x1) - + MP_REGION_CFG::RD_EN::SET - + MP_REGION_CFG::PROG_EN::SET - + MP_REGION_CFG::ERASE_EN::SET - + MP_REGION_CFG::SCRAMBLE_EN::CLEAR - + MP_REGION_CFG::ECC_EN::CLEAR - + MP_REGION_CFG::EN::SET, + fn configure_data_partition(&self, num: FlashRegion) -> Result<(), ErrorCode> { + self.registers.default_region.write( + DEFAULT_REGION::RD_EN::Set + + DEFAULT_REGION::PROG_EN::Set + + DEFAULT_REGION::ERASE_EN::Set, + ); + + if let Some(mp_region_cfg) = self.registers.mp_region_cfg.get(num as usize) { + mp_region_cfg.write( + MP_REGION_CFG::RD_EN::Set + + MP_REGION_CFG::PROG_EN::Set + + MP_REGION_CFG::ERASE_EN::Set + + MP_REGION_CFG::SCRAMBLE_EN::Clear + + MP_REGION_CFG::ECC_EN::Clear + + MP_REGION_CFG::EN::Clear, ); + + if let Some(mp_region) = self.registers.mp_region.get(num as usize) { + // Size and base are stored in different registers + mp_region.write( + MP_REGION::BASE.val(FLASH_PAGES_PER_BANK as u32) + MP_REGION::SIZE.val(0x1), + ); + } else { + return Err(ErrorCode::INVAL); + } + + // Enable MP Region + mp_region_cfg.modify(MP_REGION_CFG::EN::Set); + } else { + return Err(ErrorCode::INVAL); } + self.data_configured.set(true); + Ok(()) } - fn configure_info_partition(&self, bank: FlashBank, num: FlashRegion) { - for _ in 0..2 { - if bank == FlashBank::BANK0 { - self.registers.bank0_info0_page_cfg_shadowed[num as usize].write( - BANK_INFO_PAGE_CFG::RD_EN::SET - + BANK_INFO_PAGE_CFG::PROG_EN::SET - + BANK_INFO_PAGE_CFG::ERASE_EN::SET - + BANK_INFO_PAGE_CFG::SCRAMBLE_EN::CLEAR - + BANK_INFO_PAGE_CFG::ECC_EN::CLEAR - + BANK_INFO_PAGE_CFG::EN::SET, + fn configure_info_partition(&self, bank: FlashBank, num: FlashRegion) -> Result<(), ErrorCode> { + if bank == FlashBank::BANK0 { + if let Some(bank0_info0_page_cfg) = + self.registers.bank0_info0_page_cfg.get(num as usize) + { + bank0_info0_page_cfg.write( + BANK_INFO_PAGE_CFG::RD_EN::Set + + BANK_INFO_PAGE_CFG::PROG_EN::Set + + BANK_INFO_PAGE_CFG::ERASE_EN::Set + + BANK_INFO_PAGE_CFG::SCRAMBLE_EN::Set + + BANK_INFO_PAGE_CFG::ECC_EN::Set + + BANK_INFO_PAGE_CFG::EN::Clear, ); - } else if bank == FlashBank::BANK1 { - self.registers.bank1_info0_page_cfg_shadowed[num as usize].write( - BANK_INFO_PAGE_CFG::RD_EN::SET - + BANK_INFO_PAGE_CFG::PROG_EN::SET - + BANK_INFO_PAGE_CFG::ERASE_EN::SET - + BANK_INFO_PAGE_CFG::SCRAMBLE_EN::CLEAR - + BANK_INFO_PAGE_CFG::ECC_EN::CLEAR - + BANK_INFO_PAGE_CFG::EN::SET, + bank0_info0_page_cfg.modify(BANK_INFO_PAGE_CFG::EN::Set); + } else { + return Err(ErrorCode::INVAL); + } + } else if bank == FlashBank::BANK1 { + if let Some(bank1_info0_page_cfg) = + self.registers.bank1_info0_page_cfg.get(num as usize) + { + bank1_info0_page_cfg.write( + BANK_INFO_PAGE_CFG::RD_EN::Set + + BANK_INFO_PAGE_CFG::PROG_EN::Set + + BANK_INFO_PAGE_CFG::ERASE_EN::Set + + BANK_INFO_PAGE_CFG::SCRAMBLE_EN::Set + + BANK_INFO_PAGE_CFG::ECC_EN::Set + + BANK_INFO_PAGE_CFG::EN::Clear, ); + bank1_info0_page_cfg.modify(BANK_INFO_PAGE_CFG::EN::Set); } else { - panic!("Unsupported bank"); + return Err(ErrorCode::INVAL); } + } else { + return Err(ErrorCode::INVAL); } self.info_configured.set(true); + Ok(()) + } + + /// Reset the internal FIFOs, used for when recovering from + /// errors. + pub fn reset_fifos(&self) { + // This field is active high, and will hold the FIFO + // in reset for as long as it is held. + self.registers.fifo_rst.write(FIFO_RST::EN::SET); + self.registers.fifo_rst.write(FIFO_RST::EN::CLEAR); } pub fn handle_interrupt(&self) { let irqs = self.registers.intr_state.extract(); + // MP faults don't seem to trigger any errors in intr_state, + // so lets check for them here. + let mp_fault = self.registers.err_code.is_set(ERR_CODE::MP_ERR); self.disable_interrupts(); - if irqs.is_set(INTR::OP_ERROR) { + if irqs.is_set(INTR::OP_ERROR) || mp_fault { + self.registers.op_status.set(0); + // RW1C Clear any pending errors + self.registers.err_code.set(0xFFFF_FFFF); + self.reset_fifos(); + let read_buf = self.read_buf.take(); + let error = if mp_fault { + hil::flash::Error::FlashMemoryProtectionError + } else { + hil::flash::Error::FlashError + }; if let Some(buf) = read_buf { // We were doing a read self.flash_client.map(move |client| { - client.read_complete(buf, hil::flash::Error::FlashError); + client.read_complete(buf, Err(error)); }); } @@ -354,7 +532,14 @@ impl<'a> FlashCtrl<'a> { if let Some(buf) = write_buf { // We were doing a write self.flash_client.map(move |client| { - client.write_complete(buf, hil::flash::Error::FlashError); + client.write_complete(buf, Err(error)); + }); + } + + if self.registers.control.matches_all(CONTROL::OP::ERASE) { + // We were doing an erase + self.flash_client.map(move |client| { + client.erase_complete(Err(error)); }); } } @@ -380,10 +565,35 @@ impl<'a> FlashCtrl<'a> { if irqs.is_set(INTR::PROG_EMPTY) { self.write_buf.map(|buf| { - // Write the data in until we are full - while !self.registers.status.is_set(STATUS::PROG_FULL) - && self.write_index.get() < buf.0.len() - { + let transaction_word_len = self.calculate_max_prog_len( + self.write_word_addr.get() as u32, + (buf.0.len() - self.write_index.get()) as u32, + ); + + let mut words_written = 0; + + // Issue program command to the controller + self.registers.control.write( + CONTROL::OP::PROG + + CONTROL::PARTITION_SEL::DATA + + CONTROL::INFO_SEL::CLEAR + + CONTROL::NUM.val(transaction_word_len - 1) + + CONTROL::START::CLEAR, + ); + + // Set the address + self.registers + .addr + .write(ADDR::START.val(self.write_word_addr.get().saturating_mul(4) as u32)); + + // Start the transaction + self.registers.control.modify(CONTROL::START::SET); + + for i in 0..transaction_word_len { + if self.registers.status.is_set(STATUS::PROG_FULL) { + words_written = i; + break; + } let buf_offset = self.write_index.get(); let data: u32 = buf[buf_offset] as u32 | (buf[buf_offset + 1] as u32) << 8 @@ -393,7 +603,12 @@ impl<'a> FlashCtrl<'a> { self.registers.prog_fifo.set(data); self.write_index.set(buf_offset + 4); + // loop only semi-inclusive + words_written = i + 1; } + + self.write_word_addr + .set(self.write_word_addr.get() + words_written as usize); self.enable_interrupts(); }); } @@ -404,9 +619,10 @@ impl<'a> FlashCtrl<'a> { if let Some(buf) = read_buf { // We were doing a read if self.read_index.get() >= buf.0.len() { + self.registers.op_status.set(0); // We have all of the data, call the client self.flash_client.map(move |client| { - client.read_complete(buf, hil::flash::Error::CommandComplete); + client.read_complete(buf, Ok(())); }); } else { // Still waiting on data, keep waiting @@ -419,9 +635,10 @@ impl<'a> FlashCtrl<'a> { if let Some(buf) = write_buf { // We were doing a write if self.write_index.get() >= buf.0.len() { + self.registers.op_status.set(0); // We sent all of the data, call the client self.flash_client.map(move |client| { - client.write_complete(buf, hil::flash::Error::CommandComplete); + client.write_complete(buf, Ok(())); }); } else { // Still writing data, keep trying @@ -431,11 +648,287 @@ impl<'a> FlashCtrl<'a> { } } else if self.registers.control.matches_all(CONTROL::OP::ERASE) { self.flash_client.map(move |client| { - client.erase_complete(hil::flash::Error::CommandComplete); + client.erase_complete(Ok(())); }); } } } + + // *** Public API for interfacing to flash memory protection *** + + /// Helper function to convert an address space into page numbers for the flash controller. + /// + /// Returns `Ok(start_page_num, n_pages_used)` on successful conversion + /// Returns `Returns [`NOSUPPORT`](ErrorCode::NOSUPPORT)` if address space is not supported + /// or is out of supported bounds + /// + /// # Arguments + /// + /// * `start_addr` - Starting address to be converted to a page number + /// Note: This is the absolute address, i.e `FLASH_ADDR_OFFSET` and onwards + /// * `end_addr` - End address to be converted to a page number + /// Note: This is the absolute address, i.e `FLASH_ADDR_OFFSET` and onwards + fn mp_addr_to_page_range( + &self, + mut start_addr: usize, + mut end_addr: usize, + ) -> Result<(usize, usize), ErrorCode> { + if start_addr >= end_addr { + return Err(ErrorCode::NOSUPPORT); + } + + // Offset Absolute addresses into flash relative addresses + // i.e 0x20000000 -> 0x00, where 0x00 is the first word in Bank 0, Page 0. + if let Some(addr) = start_addr.checked_sub(FLASH_ADDR_OFFSET) { + start_addr = addr; + } else { + return Err(ErrorCode::NOSUPPORT); + } + if let Some(addr) = end_addr.checked_sub(FLASH_ADDR_OFFSET) { + end_addr = addr; + } else { + return Err(ErrorCode::NOSUPPORT); + } + + let start_page_num = start_addr.saturating_div(PAGE_SIZE); + let end_page_num = end_addr.saturating_div(PAGE_SIZE); + + if start_page_num >= FLASH_MAX_PAGES || end_page_num >= FLASH_MAX_PAGES { + // The address space does not fall within the flash layout + return Err(ErrorCode::NOSUPPORT); + } + + // Find the pages utilized by the addr space, at-least one + // page must be used, even if both start/end addresses fall on the same page. + let n_pages_used: usize = end_page_num.saturating_sub(start_page_num).max(1); + + // Pages numbers are 0 indexed, + // For example, if start_page_num is 0 and n_pages_used is 1, + // then the mp region is defined by page 0. + // If start_page_num is 0 and n_pages_used is 2, then the region is defined by pages 0 and 1. + Ok((start_page_num, n_pages_used)) + } + + /// Setup the specified flash memory protection configuration + /// + /// Returns `Ok(())` on successfully applying the requested configuration + /// Returns `[`NOSUPPORT`](ErrorCode::NOSUPPORT)` if address space is not supported, + /// or the `region_num` does not exist + /// + /// # Arguments + /// + /// * `start_addr` - Starting address that bounds the start of this region. + /// Note: This is the absolute address, i.e `FLASH_ADDR_OFFSET` and onwards + /// * `end_addr` - End address that bounds the end of this region + /// Note: This is the absolute address, i.e `FLASH_ADDR_OFFSET` and onwards + /// * `region_num` - The configuration region number associated with this region. + /// This associates the specified permissions to a configuration region, + /// the number of simultaneous configs supported + /// should be requested by `mp_get_num_regions()` + /// * `mp_perms` - Specifies the permissions to set + /// + /// # Examples + /// + /// Usage: + /// + /// ```ignore + /// peripherals + /// .flash_ctrl + /// .mp_set_region_perms(0x0, text_end_addr as usize, 5, &mp_cfg) + /// ``` + /// + /// The snippet reads as: + /// Allow access controls as specified by mp_cfg, for the address space bounded by + /// `start_addr=0x0` to `end_addr=text_end_addr` and associate this cfg to `region_num=5`. + /// + /// If a user would want to modify this region (assuming it wasn't locked), you can index into it with the + /// associated `region_num`, in this case `5`. + pub fn mp_set_region_perms( + &self, + start_addr: usize, + end_addr: usize, + region_num: usize, + mp_perms: &FlashMPConfig, + ) -> Result<(), ErrorCode> { + let (page_number, num_pages) = self.mp_addr_to_page_range(start_addr, end_addr)?; + + if region_num > FlashRegion::REGION7 as usize || page_number >= FLASH_MAX_PAGES { + return Err(ErrorCode::NOSUPPORT); + } + + // Number of pages exceeds the number of remaining pages from `page_number` + if num_pages > FLASH_MAX_PAGES - page_number { + return Err(ErrorCode::NOSUPPORT); + } + + let regs = self.registers; + + if !regs.region_cfg_regwen[region_num].is_set(REGION_CFG_REGWEN::REGION) { + // Region locked, cannot modify until next reset + return Err(ErrorCode::NOSUPPORT); + } + + // Clear any existing permissions (reset state) + self.registers.mp_region_cfg[region_num].write( + MP_REGION_CFG::EN::Clear + + MP_REGION_CFG::RD_EN::Clear + + MP_REGION_CFG::PROG_EN::Clear + + MP_REGION_CFG::ERASE_EN::Clear + + MP_REGION_CFG::SCRAMBLE_EN::Clear + + MP_REGION_CFG::ECC_EN::Clear + + MP_REGION_CFG::HE_EN::Clear, + ); + + // Set the specified permissions + if mp_perms.read_en { + self.registers.mp_region_cfg[region_num].modify(MP_REGION_CFG::RD_EN::Set); + } + + if mp_perms.write_en { + self.registers.mp_region_cfg[region_num].modify(MP_REGION_CFG::PROG_EN::Set); + } + + if mp_perms.erase_en { + self.registers.mp_region_cfg[region_num].modify(MP_REGION_CFG::ERASE_EN::Set); + } + + if mp_perms.scramble_en { + self.registers.mp_region_cfg[region_num].modify(MP_REGION_CFG::SCRAMBLE_EN::Set); + } + + if mp_perms.ecc_en { + self.registers.mp_region_cfg[region_num].modify(MP_REGION_CFG::ECC_EN::Set); + } + + if mp_perms.he_en { + self.registers.mp_region_cfg[region_num].modify(MP_REGION_CFG::HE_EN::Set); + } + + // Set the page-range for the cfg to be set + // For example, if base is 0 and size is 1, then the region is defined by page 0. + // If base is 0 and size is 2, then the region is defined by pages 0 and 1. + regs.mp_region[region_num] + .write(MP_REGION::BASE.val(page_number as u32) + MP_REGION::SIZE.val(num_pages as u32)); + + // Activate protection region with specified permissions + self.registers.mp_region_cfg[region_num].modify(MP_REGION_CFG::EN::Set); + + Ok(()) + } + + /// Read the flash memory protection configuration bounded by the specified region + /// + /// Returns `[`FlashMPConfig`](lowrisc::flash_ctrl::FlashMPConfig)` on success, with the permissions + /// specified by this region + /// Returns `[`NOSUPPORT`](ErrorCode::NOSUPPORT)` if the `region_num` does not exist + /// + /// # Arguments + /// + /// * `region_num` - The configuration region number associated with this region. + /// This associates the specified permissions to a configuration region. + pub fn mp_read_region_perms(&self, region_num: usize) -> Result { + if region_num > FlashRegion::REGION7 as usize { + return Err(ErrorCode::NOSUPPORT); + } + + let mp_cfg = self.registers.mp_region_cfg[region_num].extract(); + + let mut cfg = FlashMPConfig { + read_en: false, + write_en: false, + erase_en: false, + scramble_en: false, + ecc_en: false, + he_en: false, + }; + + if mp_cfg.matches_all(MP_REGION_CFG::RD_EN::Set) { + cfg.read_en = true; + } + + if mp_cfg.matches_all(MP_REGION_CFG::PROG_EN::Set) { + cfg.write_en = true; + } + + if mp_cfg.matches_all(MP_REGION_CFG::ERASE_EN::Set) { + cfg.erase_en = true; + } + + if mp_cfg.matches_all(MP_REGION_CFG::SCRAMBLE_EN::Set) { + cfg.scramble_en = true; + } + + if mp_cfg.matches_all(MP_REGION_CFG::ECC_EN::Set) { + cfg.ecc_en = true; + } + + if mp_cfg.matches_all(MP_REGION_CFG::HE_EN::Set) { + cfg.he_en = true; + } + + Ok(cfg) + } + + /// Get the number of configuration regions supported by this hardware + /// + /// Returns `Ok(FLASH_MP_MAX_CFGS)` where FLASH_MP_MAX_CFGS is the number of + /// cfg regions supported + /// + /// Note: Indexing starts with 0, this returns the total + /// number of configuration registers. + pub fn mp_get_num_regions(&self) -> Result { + Ok(FLASH_MP_MAX_CFGS as u32) + } + + /// Check if the specified `region_num` is locked by hardware + /// + /// Returns `Ok(bool)` on success, if true the region is locked till next reset, + /// if false, it is unlocked. + /// Returns `[`NOSUPPORT`](ErrorCode::NOSUPPORT)` if the `region_num` does not exist + /// + /// # Arguments + /// + /// * `region_num` - The configuration region number associated with this region. + /// This associates the specified permissions to a configuration region. + pub fn mp_is_region_locked(&self, region_num: usize) -> Result { + if region_num > FlashRegion::REGION7 as usize { + return Err(ErrorCode::NOSUPPORT); + } + + if !self.registers.region_cfg_regwen[region_num].is_set(REGION_CFG_REGWEN::REGION) { + // Region locked until next reset + return Ok(true); + } + // Region enabled and can be modified + Ok(false) + } + + /// Lock the configuration + /// Locks the config bounded by `region_num` + /// such that no further modifications can be made until the next system reset. + /// + /// Returns `[`NOSUPPORT`](ErrorCode::NOSUPPORT)` if the `region_num` does not exist + /// Returns `[`ALREADY`](ErrorCode::ALREADY)` if the `region_num` region is already locked + /// Returns `Ok(())` on successfully locking the region + /// + /// # Arguments + /// + /// * `region_num` - The configuration region number associated with this region. + /// This associates the specified permissions to a configuration region. + pub fn mp_lock_region_cfg(&self, region_num: usize) -> Result<(), ErrorCode> { + if region_num > FlashRegion::REGION7 as usize { + return Err(ErrorCode::NOSUPPORT); + } + + if !self.registers.region_cfg_regwen[region_num].is_set(REGION_CFG_REGWEN::REGION) { + // Region already locked + return Err(ErrorCode::ALREADY); + } + + self.registers.region_cfg_regwen[region_num].write(REGION_CFG_REGWEN::REGION::Locked); + + Ok(()) + } } impl> hil::flash::HasClient<'static, C> for FlashCtrl<'_> { @@ -447,29 +940,41 @@ impl> hil::flash::HasClient<'static, C> for FlashCtr impl hil::flash::Flash for FlashCtrl<'_> { type Page = LowRiscPage; + /// The flash controller will truncate to the closest, lower word aligned address. + /// For example, if 0x13 is supplied, the controller will perform a read at address 0x10. fn read_page( &self, page_number: usize, buf: &'static mut Self::Page, ) -> Result<(), (ErrorCode, &'static mut Self::Page)> { - let addr = page_number * PAGE_SIZE; - - if !self.data_configured.get() { - // If we aren't configured yet, configure now - self.configure_data_partition(self.region_num); + if page_number >= FLASH_MAX_PAGES { + return Err((ErrorCode::INVAL, buf)); } + let addr = page_number.saturating_mul(PAGE_SIZE); + if !self.info_configured.get() { + // The info partitions have no default access. Specifically set up a region. + if let Err(e) = self.configure_info_partition(FlashBank::BANK1, self.region_num) { + return Err((e, buf)); + } + } + + if !self.data_configured.get() { // If we aren't configured yet, configure now - self.configure_info_partition(FlashBank::BANK1, self.region_num); + if let Err(e) = self.configure_data_partition(self.region_num) { + return Err((e, buf)); + } } // Enable interrupts and set the FIFO level self.enable_interrupts(); self.registers.fifo_lvl.modify(FIFO_LVL::RD.val(0xF)); - // Set the address - self.registers.addr.write(ADDR::START.val(addr as u32)); + // Check control status before we commit + if !self.registers.ctrl_regwen.is_set(CTRL_REGWEN::EN) { + return Err((ErrorCode::BUSY, buf)); + } // Save the buffer self.read_buf.replace(buf); @@ -479,30 +984,68 @@ impl hil::flash::Flash for FlashCtrl<'_> { self.registers.control.write( CONTROL::OP::READ + CONTROL::PARTITION_SEL::DATA - + CONTROL::NUM.val(((PAGE_SIZE / 4) - 1) as u32) - + CONTROL::START::SET, + + CONTROL::INFO_SEL::SET + + CONTROL::NUM.val((FLASH_NUM_BUSWORDS_PER_BANK - 1) as u32) + + CONTROL::START::CLEAR, ); + // Set the address + self.registers.addr.write(ADDR::START.val(addr as u32)); + + // Start Transaction + self.registers.control.modify(CONTROL::START::SET); + Ok(()) } + /// The flash controller will truncate to the closest, lower word aligned address. + /// For example, if 0x13 is supplied, the controller will perform a write at address 0x10. + /// the controller does not have read modified write support. fn write_page( &self, page_number: usize, buf: &'static mut Self::Page, ) -> Result<(), (ErrorCode, &'static mut Self::Page)> { - let addr = page_number * PAGE_SIZE; + if page_number >= FLASH_MAX_PAGES { + return Err((ErrorCode::INVAL, buf)); + } + let addr = page_number.saturating_mul(PAGE_SIZE); - if !self.data_configured.get() { + if !self.info_configured.get() { // If we aren't configured yet, configure now - self.configure_data_partition(self.region_num); + // The info partitions have no default access. Specifically set up a region. + if let Err(e) = self.configure_info_partition(FlashBank::BANK1, self.region_num) { + return Err((e, buf)); + } } - if !self.info_configured.get() { + if !self.data_configured.get() { // If we aren't configured yet, configure now - self.configure_info_partition(FlashBank::BANK1, self.region_num); + if let Err(e) = self.configure_data_partition(self.region_num) { + return Err((e, buf)); + } + } + + // Check control status before we commit + if !self.registers.ctrl_regwen.is_set(CTRL_REGWEN::EN) { + return Err((ErrorCode::BUSY, buf)); } + // Writes should not cross programming resolution window boundaries, which + // occur at every FLASH_PROG_WINDOW_SIZE words. + let word_address = addr / 4; + + let transaction_word_len = + self.calculate_max_prog_len(word_address as u32, buf.0.len() as u32); + + self.registers.control.write( + CONTROL::OP::PROG + + CONTROL::PARTITION_SEL::DATA + + CONTROL::INFO_SEL::CLEAR + + CONTROL::NUM.val(transaction_word_len - 1) + + CONTROL::START::CLEAR, + ); + // Set the address self.registers.addr.write(ADDR::START.val(addr as u32)); @@ -510,17 +1053,16 @@ impl hil::flash::Flash for FlashCtrl<'_> { self.write_index.set(0); // Start the transaction - self.registers.control.write( - CONTROL::OP::PROG - + CONTROL::PARTITION_SEL::DATA - + CONTROL::NUM.val(((PAGE_SIZE / 4) - 1) as u32) - + CONTROL::START::SET, - ); + self.registers.control.modify(CONTROL::START::SET); + + let mut words_written = 0; // Write the data until we are full or have written all the data - while !self.registers.status.is_set(STATUS::PROG_FULL) - && self.write_index.get() < (buf.0.len() - 4) - { + for i in 0..transaction_word_len { + if self.registers.status.is_set(STATUS::PROG_FULL) { + words_written = i; + break; + } let buf_offset = self.write_index.get(); let data: u32 = buf[buf_offset] as u32 | (buf[buf_offset + 1] as u32) << 8 @@ -530,29 +1072,45 @@ impl hil::flash::Flash for FlashCtrl<'_> { self.registers.prog_fifo.set(data); self.write_index.set(buf_offset + 4); + // loop only semi-inclusive + words_written = i + 1; } + self.write_word_addr + .set((addr / 4) + words_written as usize); + // Save the buffer self.write_buf.replace(buf); - // Enable interrupts and set the FIFO level + // Enable interrupts and set the FIFO level (interrupt when fully drained) self.enable_interrupts(); - self.registers.fifo_lvl.modify(FIFO_LVL::PROG.val(0xF)); + self.registers.fifo_lvl.modify(FIFO_LVL::PROG.val(0x00)); Ok(()) } + /// The controller will truncate to the closest lower page aligned address. + /// Similarly for bank erases, the controller will truncate to the closest + /// lower bank aligned address. fn erase_page(&self, page_number: usize) -> Result<(), ErrorCode> { - let addr = page_number * PAGE_SIZE; + if page_number >= FLASH_MAX_PAGES { + return Err(ErrorCode::INVAL); + } + let addr = page_number.saturating_mul(PAGE_SIZE); if !self.data_configured.get() { // If we aren't configured yet, configure now - self.configure_data_partition(self.region_num); + self.configure_data_partition(self.region_num)?; } if !self.info_configured.get() { // If we aren't configured yet, configure now - self.configure_info_partition(FlashBank::BANK1, self.region_num); + self.configure_info_partition(FlashBank::BANK1, self.region_num)?; + } + + // Check control status before we commit + if !self.registers.ctrl_regwen.is_set(CTRL_REGWEN::EN) { + return Err(ErrorCode::BUSY); } // Disable bank erase @@ -575,7 +1133,6 @@ impl hil::flash::Flash for FlashCtrl<'_> { + CONTROL::PARTITION_SEL::DATA + CONTROL::START::SET, ); - Ok(()) } } diff --git a/chips/lowrisc/src/gpio.rs b/chips/lowrisc/src/gpio.rs index 61a3486b48..e3bb53de83 100644 --- a/chips/lowrisc/src/gpio.rs +++ b/chips/lowrisc/src/gpio.rs @@ -1,6 +1,9 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! General Purpose Input/Output driver. -use crate::padctrl; use kernel::hil::gpio; use kernel::utilities::cells::OptionalCell; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; @@ -72,23 +75,25 @@ register_bitfields![u32, ] ]; -pub struct GpioPin<'a> { +pub type GpioBitfield = Field; + +pub struct GpioPin<'a, PAD> { gpio_registers: StaticRef, - padctrl_registers: StaticRef, + padctl: PAD, pin: Field, client: OptionalCell<&'a dyn gpio::Client>, } -impl<'a> GpioPin<'a> { +impl<'a, PAD> GpioPin<'a, PAD> { pub const fn new( gpio_base: StaticRef, - padctrl_base: StaticRef, + padctl: PAD, pin: Field, - ) -> GpioPin<'a> { + ) -> GpioPin<'a, PAD> { GpioPin { gpio_registers: gpio_base, - padctrl_registers: padctrl_base, - pin: pin, + padctl, + pin, client: OptionalCell::empty(), } } @@ -101,7 +106,7 @@ impl<'a> GpioPin<'a> { upper: &ReadWrite, ) { let shift = field.shift; - let bit = if val { 1u32 } else { 0u32 }; + let bit = u32::from(val); if shift < 16 { lower.write(mask_half::data.val(bit << shift) + mask_half::mask.val(1u32 << shift)); } else { @@ -124,89 +129,65 @@ impl<'a> GpioPin<'a> { } } -impl gpio::Configure for GpioPin<'_> { +impl gpio::Configure for GpioPin<'_, PAD> { fn configuration(&self) -> gpio::Configuration { - match self.gpio_registers.direct_oe.is_set(self.pin) { - true => gpio::Configuration::InputOutput, - false => gpio::Configuration::Input, + match ( + self.padctl.configuration(), + self.gpio_registers.direct_oe.is_set(self.pin), + ) { + (gpio::Configuration::InputOutput, true) => gpio::Configuration::InputOutput, + (gpio::Configuration::InputOutput, false) => gpio::Configuration::Input, + (gpio::Configuration::Input, false) => gpio::Configuration::Input, + // This is configuration error we can't enable ouput + // for GPIO pin connect to input only pad. + (gpio::Configuration::Input, true) => gpio::Configuration::Function, + // We curently dont support output only GPIO + // OT register have only output_enable flag. + (gpio::Configuration::Output, _) => gpio::Configuration::Function, + (conf, _) => conf, } } fn set_floating_state(&self, mode: gpio::FloatingState) { - // There is unfortunately no documentation about how these - // registers map to the actual GPIOs, so just write all of them. - match mode { - gpio::FloatingState::PullUp => { - self.padctrl_registers.dio_pads.write( - padctrl::DIO_PADS::ATTR0_PULL_UP::SET - + padctrl::DIO_PADS::ATTR1_PULL_UP::SET - + padctrl::DIO_PADS::ATTR2_PULL_UP::SET - + padctrl::DIO_PADS::ATTR3_PULL_UP::SET, - ); - } - gpio::FloatingState::PullDown => { - self.padctrl_registers.dio_pads.write( - padctrl::DIO_PADS::ATTR0_PULL_DOWN::SET - + padctrl::DIO_PADS::ATTR1_PULL_DOWN::SET - + padctrl::DIO_PADS::ATTR2_PULL_DOWN::SET - + padctrl::DIO_PADS::ATTR3_PULL_DOWN::SET, - ); - } - gpio::FloatingState::PullNone => { - self.padctrl_registers.dio_pads.write( - padctrl::DIO_PADS::ATTR0_OPEN_DRAIN::SET - + padctrl::DIO_PADS::ATTR1_OPEN_DRAIN::SET - + padctrl::DIO_PADS::ATTR2_OPEN_DRAIN::SET - + padctrl::DIO_PADS::ATTR3_OPEN_DRAIN::SET, - ); - } - } + self.padctl.set_floating_state(mode); } fn floating_state(&self) -> gpio::FloatingState { - if self - .padctrl_registers - .dio_pads - .is_set(padctrl::DIO_PADS::ATTR0_PULL_UP) - { - gpio::FloatingState::PullUp - } else if self - .padctrl_registers - .dio_pads - .is_set(padctrl::DIO_PADS::ATTR0_PULL_DOWN) - { - gpio::FloatingState::PullDown - } else { - gpio::FloatingState::PullNone - } + self.padctl.floating_state() } fn deactivate_to_low_power(&self) { self.disable_input(); self.disable_output(); + self.padctl.deactivate_to_low_power(); } fn make_output(&self) -> gpio::Configuration { - GpioPin::half_set( - true, - self.pin, - &self.gpio_registers.masked_oe_lower, - &self.gpio_registers.masked_oe_upper, - ); - gpio::Configuration::InputOutput + // Re-connect in case we make output after switching from LowPower state. + if let gpio::Configuration::InputOutput = self.padctl.make_output() { + Self::half_set( + true, + self.pin, + &self.gpio_registers.masked_oe_lower, + &self.gpio_registers.masked_oe_upper, + ); + } + self.configuration() } fn disable_output(&self) -> gpio::Configuration { - GpioPin::half_set( + Self::half_set( false, self.pin, &self.gpio_registers.masked_oe_lower, &self.gpio_registers.masked_oe_upper, ); - gpio::Configuration::Input + self.configuration() } fn make_input(&self) -> gpio::Configuration { + // Re-connect in case we make input after switching from LowPower state. + self.padctl.make_input(); self.configuration() } @@ -215,18 +196,18 @@ impl gpio::Configure for GpioPin<'_> { } } -impl gpio::Input for GpioPin<'_> { +impl gpio::Input for GpioPin<'_, PAD> { fn read(&self) -> bool { self.gpio_registers.data_in.is_set(self.pin) } } -impl gpio::Output for GpioPin<'_> { +impl gpio::Output for GpioPin<'_, PAD> { fn toggle(&self) -> bool { let pin = self.pin; let new_state = !self.gpio_registers.direct_out.is_set(pin); - GpioPin::half_set( + Self::half_set( new_state, self.pin, &self.gpio_registers.masked_out_lower, @@ -236,7 +217,7 @@ impl gpio::Output for GpioPin<'_> { } fn set(&self) { - GpioPin::half_set( + Self::half_set( true, self.pin, &self.gpio_registers.masked_out_lower, @@ -245,7 +226,7 @@ impl gpio::Output for GpioPin<'_> { } fn clear(&self) { - GpioPin::half_set( + Self::half_set( false, self.pin, &self.gpio_registers.masked_out_lower, @@ -254,7 +235,7 @@ impl gpio::Output for GpioPin<'_> { } } -impl<'a> gpio::Interrupt<'a> for GpioPin<'a> { +impl<'a, PAD> gpio::Interrupt<'a> for GpioPin<'a, PAD> { fn set_client(&self, client: &'a dyn gpio::Client) { self.client.set(client); } diff --git a/chips/lowrisc/src/hmac.rs b/chips/lowrisc/src/hmac.rs index 5c6e336e1b..ad222cb918 100644 --- a/chips/lowrisc/src/hmac.rs +++ b/chips/lowrisc/src/hmac.rs @@ -1,10 +1,17 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SHA256 HMAC (Hash-based Message Authentication Code). use core::cell::Cell; +use core::ops::Index; use kernel::hil; -use kernel::hil::digest::{self, DigestHash}; +use kernel::hil::digest::{self, DigestData, DigestHash}; use kernel::utilities::cells::OptionalCell; -use kernel::utilities::leasable_buffer::LeasableBuffer; +use kernel::utilities::leasable_buffer::SubSlice; +use kernel::utilities::leasable_buffer::SubSliceMut; +use kernel::utilities::leasable_buffer::SubSliceMutImmut; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; use kernel::utilities::registers::{ register_bitfields, register_structs, ReadOnly, ReadWrite, WriteOnly, @@ -70,85 +77,91 @@ register_bitfields![u32, pub struct Hmac<'a> { registers: StaticRef, - - client: OptionalCell<&'a dyn hil::digest::Client<'a, 32>>, - - data: Cell>>, - data_len: Cell, - data_index: Cell, - + client: OptionalCell<&'a dyn hil::digest::Client<32>>, + data: Cell>>, verify: Cell, - digest: Cell>, + cancelled: Cell, + busy: Cell, } impl Hmac<'_> { - pub const fn new(base: StaticRef) -> Self { + pub fn new(base: StaticRef) -> Self { Hmac { registers: base, client: OptionalCell::empty(), data: Cell::new(None), - data_len: Cell::new(0), - data_index: Cell::new(0), verify: Cell::new(false), digest: Cell::new(None), + cancelled: Cell::new(false), + busy: Cell::new(false), } } - fn data_progress(&self) -> bool { + fn process(&self, data: &dyn Index, count: usize) -> usize { let regs = self.registers; - let idx = self.data_index.get(); - let len = self.data_len.get(); - - self.data.take().map_or(false, |buf| { - let slice = buf.take(); + for i in 0..(count / 4) { + if regs.status.is_set(STATUS::FIFO_FULL) { + return i * 4; + } - if idx < len { - let data_len = len - idx; + let data_idx = i * 4; - for i in 0..(data_len / 4) { - if regs.status.is_set(STATUS::FIFO_FULL) { - self.data.set(Some(LeasableBuffer::new(slice))); - return false; - } + let mut d = (data[data_idx + 3] as u32) << 0; + d |= (data[data_idx + 2] as u32) << 8; + d |= (data[data_idx + 1] as u32) << 16; + d |= (data[data_idx + 0] as u32) << 24; - let data_idx = idx + i * 4; + regs.msg_fifo.set(d); + } - let mut d = (slice[data_idx + 3] as u32) << 0; - d |= (slice[data_idx + 2] as u32) << 8; - d |= (slice[data_idx + 1] as u32) << 16; - d |= (slice[data_idx + 0] as u32) << 24; + if (count % 4) != 0 { + for i in 0..(count % 4) { + let data_idx = (count - (count % 4)) + i; + regs.msg_fifo_8.set(data[data_idx]); + } + } + count + } - regs.msg_fifo.set(d); - self.data_index.set(data_idx + 4); + // Return true if processing more data, false if the buffer + // is completely processed. + fn data_progress(&self) -> bool { + self.data.take().map_or(false, |buf| match buf { + SubSliceMutImmut::Immutable(mut b) => { + if b.len() == 0 { + self.data.set(Some(SubSliceMutImmut::Immutable(b))); + false + } else { + let count = self.process(&b, b.len()); + b.slice(count..); + self.data.set(Some(SubSliceMutImmut::Immutable(b))); + true } - - if (data_len % 4) != 0 { - let idx = self.data_index.get(); - - for i in 0..(data_len % 4) { - let data_idx = idx + i; - - regs.msg_fifo_8.set(slice[data_idx]); - self.data_index.set(data_idx + 1) - } + } + SubSliceMutImmut::Mutable(mut b) => { + if b.len() == 0 { + self.data.set(Some(SubSliceMutImmut::Mutable(b))); + false + } else { + let count = self.process(&b, b.len()); + b.slice(count..); + self.data.set(Some(SubSliceMutImmut::Mutable(b))); + true } } - self.data.set(Some(LeasableBuffer::new(slice))); - true }) } pub fn handle_interrupt(&self) { let regs = self.registers; let intrs = regs.intr_state.extract(); - regs.intr_enable.modify( INTR_ENABLE::HMAC_DONE::CLEAR + INTR_ENABLE::FIFO_EMPTY::CLEAR + INTR_ENABLE::HMAC_ERR::CLEAR, ); - + self.busy.set(false); if intrs.is_set(INTR_STATE::HMAC_DONE) { self.client.map(|client| { let digest = self.digest.take().unwrap(); @@ -172,7 +185,15 @@ impl Hmac<'_> { } } - client.verification_done(Ok(equal), digest); + if self.cancelled.get() { + self.clear_data(); + self.cancelled.set(false); + client.verification_done(Err(ErrorCode::CANCEL), digest); + } else { + self.clear_data(); + self.cancelled.set(false); + client.verification_done(Ok(equal), digest); + } } else { for i in 0..8 { let d = regs.digest[i].get().to_ne_bytes(); @@ -184,25 +205,38 @@ impl Hmac<'_> { digest[idx + 2] = d[2]; digest[idx + 3] = d[3]; } - - client.hash_done(Ok(()), digest); + if self.cancelled.get() { + self.clear_data(); + self.cancelled.set(false); + client.hash_done(Err(ErrorCode::CANCEL), digest); + } else { + self.clear_data(); + self.cancelled.set(false); + client.hash_done(Ok(()), digest); + } } }); } else if intrs.is_set(INTR_STATE::FIFO_EMPTY) { // Clear the FIFO empty interrupt regs.intr_state.modify(INTR_STATE::FIFO_EMPTY::SET); - - if self.data_progress() { + let rval = if self.cancelled.get() { + self.cancelled.set(false); + Err(ErrorCode::CANCEL) + } else { + Ok(()) + }; + if !self.data_progress() { + // False means we are done self.client.map(move |client| { - self.data.take().map(|buf| { - let slice = buf.take(); - client.add_data_done(Ok(()), slice); + self.data.take().map(|buf| match buf { + SubSliceMutImmut::Mutable(b) => client.add_mut_data_done(rval, b), + SubSliceMutImmut::Immutable(b) => client.add_data_done(rval, b), }) }); - // Make sure we don't get any more FIFO empty interrupts regs.intr_enable.modify(INTR_ENABLE::FIFO_EMPTY::CLEAR); } else { + // Processing more data // Enable interrupts regs.intr_enable.modify(INTR_ENABLE::FIFO_EMPTY::SET); } @@ -210,10 +244,16 @@ impl Hmac<'_> { regs.intr_state.modify(INTR_STATE::HMAC_ERR::SET); self.client.map(|client| { + let errval = if self.cancelled.get() { + self.cancelled.set(false); + ErrorCode::CANCEL + } else { + ErrorCode::FAIL + }; if self.verify.get() { - client.hash_done(Err(ErrorCode::FAIL), self.digest.take().unwrap()); + client.hash_done(Err(errval), self.digest.take().unwrap()); } else { - client.hash_done(Err(ErrorCode::FAIL), self.digest.take().unwrap()); + client.hash_done(Err(errval), self.digest.take().unwrap()); } }); } @@ -223,38 +263,65 @@ impl Hmac<'_> { impl<'a> hil::digest::DigestData<'a, 32> for Hmac<'a> { fn add_data( &self, - data: LeasableBuffer<'static, u8>, - ) -> Result { - let regs = self.registers; - - regs.cmd.modify(CMD::START::SET); + data: SubSlice<'static, u8>, + ) -> Result<(), (ErrorCode, SubSlice<'static, u8>)> { + if self.busy.get() { + Err((ErrorCode::BUSY, data)) + } else { + self.busy.set(true); + self.data.set(Some(SubSliceMutImmut::Immutable(data))); + + let regs = self.registers; + regs.cmd.modify(CMD::START::SET); + // Clear the FIFO empty interrupt + regs.intr_state.modify(INTR_STATE::FIFO_EMPTY::SET); + // Enable interrupts + regs.intr_enable.modify(INTR_ENABLE::FIFO_EMPTY::SET); + let ret = self.data_progress(); - // Clear the FIFO empty interrupt - regs.intr_state.modify(INTR_STATE::FIFO_EMPTY::SET); + if ret { + regs.intr_test.modify(INTR_TEST::FIFO_EMPTY::SET); + } - // Enable interrupts - regs.intr_enable.modify(INTR_ENABLE::FIFO_EMPTY::SET); + Ok(()) + } + } - // Set the length and data index of the data to write - self.data_len.set(data.len()); - self.data.set(Some(data)); - self.data_index.set(0); + fn add_mut_data( + &self, + data: SubSliceMut<'static, u8>, + ) -> Result<(), (ErrorCode, SubSliceMut<'static, u8>)> { + if self.busy.get() { + Err((ErrorCode::BUSY, data)) + } else { + self.busy.set(true); + self.data.set(Some(SubSliceMutImmut::Mutable(data))); + + let regs = self.registers; + regs.cmd.modify(CMD::START::SET); + // Clear the FIFO empty interrupt + regs.intr_state.modify(INTR_STATE::FIFO_EMPTY::SET); + // Enable interrupts + regs.intr_enable.modify(INTR_ENABLE::FIFO_EMPTY::SET); + let ret = self.data_progress(); - // Call the process function, this will start an async fill method - let ret = self.data_progress(); + if ret { + regs.intr_test.modify(INTR_TEST::FIFO_EMPTY::SET); + } - if ret { - regs.intr_test.modify(INTR_TEST::FIFO_EMPTY::SET); + Ok(()) } - - Ok(self.data_len.get()) } fn clear_data(&self) { let regs = self.registers; - regs.cmd.modify(CMD::START::CLEAR); - regs.wipe_secret.set(1 as u32); + regs.wipe_secret.set(1_u32); + self.cancelled.set(true); + } + + fn set_data_client(&'a self, _client: &'a (dyn digest::ClientData<32> + 'a)) { + unimplemented!() } } @@ -273,11 +340,15 @@ impl<'a> hil::digest::DigestHash<'a, 32> for Hmac<'a> { // Start the process regs.cmd.modify(CMD::PROCESS::SET); - + self.busy.set(true); self.digest.set(Some(digest)); Ok(()) } + + fn set_hash_client(&'a self, _client: &'a (dyn digest::ClientHash<32> + 'a)) { + unimplemented!() + } } impl<'a> hil::digest::DigestVerify<'a, 32> for Hmac<'a> { @@ -289,16 +360,23 @@ impl<'a> hil::digest::DigestVerify<'a, 32> for Hmac<'a> { self.run(compare) } + + fn set_verify_client(&'a self, _client: &'a (dyn digest::ClientVerify<32> + 'a)) { + unimplemented!() + } } impl<'a> hil::digest::Digest<'a, 32> for Hmac<'a> { - fn set_client(&'a self, client: &'a dyn digest::Client<'a, 32>) { + fn set_client(&'a self, client: &'a dyn digest::Client<32>) { self.client.set(client); } } -impl hil::digest::HMACSha256 for Hmac<'_> { +impl hil::digest::HmacSha256 for Hmac<'_> { fn set_mode_hmacsha256(&self, key: &[u8]) -> Result<(), ErrorCode> { + if self.busy.get() { + return Err(ErrorCode::BUSY); + } let regs = self.registers; let mut key_idx = 0; @@ -314,12 +392,12 @@ impl hil::digest::HMACSha256 for Hmac<'_> { for i in 0..(key.len() / 4) { let idx = i * 4; - let mut k = key[idx + 3] as u32; - k |= (key[i * 4 + 2] as u32) << 8; - k |= (key[i * 4 + 1] as u32) << 16; - k |= (key[i * 4 + 0] as u32) << 24; + let mut k = *key.get(idx + 3).ok_or(ErrorCode::INVAL)? as u32; + k |= (*key.get(i * 4 + 2).ok_or(ErrorCode::INVAL)? as u32) << 8; + k |= (*key.get(i * 4 + 1).ok_or(ErrorCode::INVAL)? as u32) << 16; + k |= (*key.get(i * 4 + 0).ok_or(ErrorCode::INVAL)? as u32) << 24; - regs.key[i as usize].set(k); + regs.key.get(i).ok_or(ErrorCode::INVAL)?.set(k); key_idx = i + 1; } @@ -327,28 +405,28 @@ impl hil::digest::HMACSha256 for Hmac<'_> { let mut k = 0; for i in 0..(key.len() % 4) { - k = k | (key[key_idx * 4 + i] as u32) << (8 * (3 - i)); + k |= (*key.get(key_idx * 4 + 1).ok_or(ErrorCode::INVAL)? as u32) << (8 * (3 - i)); } - regs.key[key_idx].set(k); - key_idx = key_idx + 1; + regs.key.get(key_idx).ok_or(ErrorCode::INVAL)?.set(k); + key_idx += 1; } for i in key_idx..8 { - regs.key[i as usize].set(0); + regs.key.get(i).ok_or(ErrorCode::INVAL)?.set(0); } Ok(()) } } -impl hil::digest::HMACSha384 for Hmac<'_> { +impl hil::digest::HmacSha384 for Hmac<'_> { fn set_mode_hmacsha384(&self, _key: &[u8]) -> Result<(), ErrorCode> { Err(ErrorCode::NOSUPPORT) } } -impl hil::digest::HMACSha512 for Hmac<'_> { +impl hil::digest::HmacSha512 for Hmac<'_> { fn set_mode_hmacsha512(&self, _key: &[u8]) -> Result<(), ErrorCode> { Err(ErrorCode::NOSUPPORT) } @@ -356,6 +434,9 @@ impl hil::digest::HMACSha512 for Hmac<'_> { impl hil::digest::Sha256 for Hmac<'_> { fn set_mode_sha256(&self) -> Result<(), ErrorCode> { + if self.busy.get() { + return Err(ErrorCode::BUSY); + } let regs = self.registers; // Ensure the SHA is setup diff --git a/chips/lowrisc/src/i2c.rs b/chips/lowrisc/src/i2c.rs index bc945bc647..be27e3197b 100644 --- a/chips/lowrisc/src/i2c.rs +++ b/chips/lowrisc/src/i2c.rs @@ -1,129 +1,21 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! I2C Master Driver +use crate::registers::i2c_regs::{ + CTRL, FDATA, FIFO_CTRL, INTR, RDATA, STATUS, TIMING0, TIMING1, TIMING2, TIMING3, TIMING4, +}; use core::cell::Cell; use kernel::hil; use kernel::hil::i2c; use kernel::utilities::cells::OptionalCell; use kernel::utilities::cells::TakeCell; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; -use kernel::utilities::registers::{ - register_bitfields, register_structs, ReadOnly, ReadWrite, WriteOnly, -}; use kernel::utilities::StaticRef; -register_structs! { - pub I2cRegisters { - (0x00 => intr_state: ReadWrite), - (0x04 => intr_enable: ReadWrite), - (0x08 => intr_test: WriteOnly), - (0x0C => ctrl: ReadWrite), - (0x10 => status: ReadOnly), - (0x14 => rdata: ReadOnly), - (0x18 => fdata: WriteOnly), - (0x1C => fifo_ctrl: ReadWrite), - (0x20 => fifo_status: ReadOnly), - (0x24 => ovrd: ReadWrite), - (0x28 => val: ReadOnly), - (0x2C => timing0: ReadWrite), - (0x30 => timing1: ReadWrite), - (0x34 => timing2: ReadWrite), - (0x38 => timing3: ReadWrite), - (0x3C => timing4: ReadWrite), - (0x40 => timeout_ctrl: ReadWrite), - (0x44 => @END), - } -} - -register_bitfields![u32, - INTR [ - FMT_WATERMARK OFFSET(0) NUMBITS(1) [], - RX_WATERMARK OFFSET(1) NUMBITS(1) [], - FMT_OVERFLOW OFFSET(2) NUMBITS(1) [], - RX_OVERFLOW OFFSET(3) NUMBITS(1) [], - NAK OFFSET(4) NUMBITS(1) [], - SCL_INTERFERENCE OFFSET(5) NUMBITS(1) [], - SDA_INTERFERENCE OFFSET(6) NUMBITS(1) [], - STRETCH_TIMEOUT OFFSET(7) NUMBITS(1) [], - SDA_UNSTABLE OFFSET(8) NUMBITS(1) [] - ], - CTRL [ - ENABLEHOST OFFSET(0) NUMBITS(1) [] - ], - STATUS [ - FMTFULL OFFSET(0) NUMBITS(1) [], - RXFULL OFFSET(1) NUMBITS(1) [], - FMTEMPTY OFFSET(2) NUMBITS(1) [], - HOSTIDLE OFFSET(3) NUMBITS(1) [], - TARGETIDLE OFFSET(4) NUMBITS(1) [], - RXEMPTY OFFSET(5) NUMBITS(1) [] - ], - RDATA [ - RDATA OFFSET(0) NUMBITS(8) [] - ], - FDATA [ - FBYTE OFFSET(0) NUMBITS(8) [], - START OFFSET(8) NUMBITS(1) [], - STOP OFFSET(9) NUMBITS(1) [], - READ OFFSET(10) NUMBITS(1) [], - RCONT OFFSET(11) NUMBITS(1) [], - NAKOK OFFSET(12) NUMBITS(1) [] - ], - FIFO_CTRL [ - RXRST OFFSET(0) NUMBITS(1) [], - FMTRST OFFSET(1) NUMBITS(1) [], - RXILVL OFFSET(2) NUMBITS(3) [ - RXLVL1 = 0, - RXLVL4 = 1, - RXLVL8 = 2, - RXLVL16 = 3, - RXLVL30 = 4 - ], - FMTILVL OFFSET(5) NUMBITS(3) [ - FMTLVL1 = 0, - FMTLVL4 = 1, - FMTLVL8 = 2, - FMTLVL16 = 3, - FMTLVL30 = 4 - ] - ], - FIFO_STATUS [ - FMTLVL OFFSET(0) NUMBITS(6) [], - RXLVL OFFSET(16) NUMBITS(6) [] - ], - OVRD [ - TXOVRDEN OFFSET(0) NUMBITS(1) [], - SCLVAL OFFSET(1) NUMBITS(1) [], - SDAVAL OFFSET(2) NUMBITS(1) [] - ], - VAL [ - SCL_RX OFFSET(0) NUMBITS(1) [], - SDA_RX OFFSET(1) NUMBITS(1) [] - ], - TIMING0 [ - THIGH OFFSET(0) NUMBITS(16) [], - TLOW OFFSET(16) NUMBITS(16) [] - ], - TIMING1 [ - T_R OFFSET(0) NUMBITS(16) [], - T_F OFFSET(16) NUMBITS(16) [] - ], - TIMING2 [ - TSU_STA OFFSET(0) NUMBITS(16) [], - THD_STA OFFSET(16) NUMBITS(16) [] - ], - TIMING3 [ - TSU_DAT OFFSET(0) NUMBITS(16) [], - THD_DAT OFFSET(16) NUMBITS(16) [] - ], - TIMING4 [ - TSU_STO OFFSET(0) NUMBITS(16) [], - T_BUF OFFSET(16) NUMBITS(16) [] - ], - TIMEOUT_CTRL [ - VAL OFFSET(0) NUMBITS(31) [], - EN OFFSET(31) NUMBITS(1) [] - ] -]; +pub use crate::registers::i2c_regs::I2cRegisters; pub struct I2c<'a> { registers: StaticRef, @@ -145,7 +37,7 @@ pub struct I2c<'a> { } impl<'a> I2c<'_> { - pub const fn new(base: StaticRef, clock_period_nanos: u32) -> I2c<'a> { + pub fn new(base: StaticRef, clock_period_nanos: u32) -> I2c<'a> { I2c { registers: base, clock_period_nanos, @@ -165,8 +57,8 @@ impl<'a> I2c<'_> { // Clear all interrupts regs.intr_state.modify( - INTR::FMT_WATERMARK::SET - + INTR::RX_WATERMARK::SET + INTR::FMT_THRESHOLD::SET + + INTR::RX_THRESHOLD::SET + INTR::FMT_OVERFLOW::SET + INTR::RX_OVERFLOW::SET + INTR::NAK::SET @@ -176,7 +68,7 @@ impl<'a> I2c<'_> { + INTR::SDA_UNSTABLE::SET, ); - if irqs.is_set(INTR::FMT_WATERMARK) { + if irqs.is_set(INTR::FMT_THRESHOLD) { // FMT Watermark if self.slave_read_address.get() != 0 { self.write_read_data(); @@ -185,7 +77,7 @@ impl<'a> I2c<'_> { } } - if irqs.is_set(INTR::RX_WATERMARK) { + if irqs.is_set(INTR::RX_THRESHOLD) { // RX Watermark self.read_data(); } @@ -226,14 +118,14 @@ impl<'a> I2c<'_> { let len = self.read_len.get(); self.buffer.map(|buf| { - for i in data_popped..len { + for i in self.read_index.get()..len { if regs.status.is_set(STATUS::RXEMPTY) { // The RX buffer is empty data_popped = i; break; } // Read the data - buf[i as usize] = regs.rdata.read(RDATA::RDATA) as u8; + buf[i] = regs.rdata.read(RDATA::RDATA) as u8; data_popped = i; } @@ -243,7 +135,7 @@ impl<'a> I2c<'_> { client.command_complete(self.buffer.take().unwrap(), Ok(())); }); } else { - self.read_index.set(data_popped as usize + 1); + self.read_index.set(data_popped + 1); // Update the FIFO depth if len - data_popped > 8 { @@ -263,14 +155,15 @@ impl<'a> I2c<'_> { let len = self.write_len.get(); self.buffer.map(|buf| { - for i in data_pushed..(len - 1) { + for i in self.write_index.get()..(len - 1) { if regs.status.read(STATUS::FMTFULL) != 0 { // The FMT buffer is full data_pushed = i; break; } // Send the data - regs.fdata.write(FDATA::FBYTE.val(buf[i as usize] as u32)); + regs.fdata + .write(FDATA::FBYTE.val(*buf.get(i).unwrap_or(&0) as u32)); data_pushed = i; } @@ -278,7 +171,7 @@ impl<'a> I2c<'_> { if regs.status.read(STATUS::FMTFULL) == 0 && data_pushed == (len - 1) { // Send the last byte with the stop signal regs.fdata - .write(FDATA::FBYTE.val(buf[len as usize] as u32) + FDATA::STOP::SET); + .write(FDATA::FBYTE.val(*buf.get(len).unwrap_or(&0) as u32) + FDATA::STOP::SET); data_pushed = len; } @@ -289,7 +182,7 @@ impl<'a> I2c<'_> { client.command_complete(self.buffer.take().unwrap(), Ok(())); }); } else { - self.write_index.set(data_pushed as usize + 1); + self.write_index.set(data_pushed + 1); // Update the FIFO depth if len - data_pushed > 8 { @@ -309,14 +202,16 @@ impl<'a> I2c<'_> { let len = self.write_len.get(); self.buffer.map(|buf| { - for i in data_pushed..(len - 1) { + let start_index = data_pushed; + for i in start_index..(len - 1) { if regs.status.read(STATUS::FMTFULL) != 0 { // The FMT buffer is full data_pushed = i; break; } // Send the data - regs.fdata.write(FDATA::FBYTE.val(buf[i as usize] as u32)); + regs.fdata + .write(FDATA::FBYTE.val(*buf.get(i).unwrap_or(&0) as u32)); data_pushed = i; } @@ -324,7 +219,7 @@ impl<'a> I2c<'_> { if regs.status.read(STATUS::FMTFULL) == 0 && data_pushed == (len - 1) { // Send the last byte with the stop signal regs.fdata - .write(FDATA::FBYTE.val(buf[len as usize] as u32) + FDATA::STOP::SET); + .write(FDATA::FBYTE.val(*buf.get(len).unwrap_or(&0) as u32) + FDATA::STOP::SET); data_pushed = len; } @@ -340,7 +235,7 @@ impl<'a> I2c<'_> { self.read_data(); } else { - self.write_index.set(data_pushed as usize + 1); + self.write_index.set(data_pushed + 1); // Update the FIFO depth if len - data_pushed > 8 { @@ -355,7 +250,7 @@ impl<'a> I2c<'_> { } } -impl<'a> hil::i2c::I2CMaster for I2c<'a> { +impl<'a> hil::i2c::I2CMaster<'a> for I2c<'a> { fn set_master_client(&self, master_client: &'a dyn i2c::I2CHwMasterClient) { self.master_client.set(master_client); } @@ -368,8 +263,8 @@ impl<'a> hil::i2c::I2CMaster for I2c<'a> { // Enable all interrupts regs.intr_enable.modify( - INTR::FMT_WATERMARK::SET - + INTR::RX_WATERMARK::SET + INTR::FMT_THRESHOLD::SET + + INTR::RX_THRESHOLD::SET + INTR::FMT_OVERFLOW::SET + INTR::RX_OVERFLOW::SET + INTR::NAK::SET @@ -393,8 +288,8 @@ impl<'a> hil::i2c::I2CMaster for I2c<'a> { &self, addr: u8, data: &'static mut [u8], - write_len: u8, - read_len: u8, + write_len: usize, + read_len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { let regs = self.registers; @@ -427,8 +322,8 @@ impl<'a> hil::i2c::I2CMaster for I2c<'a> { // Save all the data and offsets we still need to send and receive self.slave_read_address.set(addr); self.buffer.replace(data); - self.write_len.set(write_len as usize); - self.read_len.set(read_len as usize); + self.write_len.set(write_len); + self.read_len.set(read_len); self.write_index.set(0); self.read_index.set(0); @@ -441,7 +336,7 @@ impl<'a> hil::i2c::I2CMaster for I2c<'a> { &self, addr: u8, data: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { let regs = self.registers; @@ -466,7 +361,7 @@ impl<'a> hil::i2c::I2CMaster for I2c<'a> { // Save all the data and offsets we still need to send self.slave_read_address.set(0); self.buffer.replace(data); - self.write_len.set(len as usize); + self.write_len.set(len); self.write_index.set(0); self.write_data(); @@ -478,7 +373,7 @@ impl<'a> hil::i2c::I2CMaster for I2c<'a> { &self, addr: u8, buffer: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { let regs = self.registers; @@ -503,7 +398,7 @@ impl<'a> hil::i2c::I2CMaster for I2c<'a> { // Save all the data and offsets we still need to read self.slave_read_address.set(0); self.buffer.replace(buffer); - self.read_len.set(len as usize); + self.read_len.set(len); self.read_index.set(0); self.read_data(); diff --git a/chips/lowrisc/src/lib.rs b/chips/lowrisc/src/lib.rs index 1d507ce138..a3680388f5 100644 --- a/chips/lowrisc/src/lib.rs +++ b/chips/lowrisc/src/lib.rs @@ -1,12 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementations for generic LowRISC peripherals. -#![feature(const_fn_trait_bound)] -// Feature required with newer versions of rustc (at least 2020-10-25). -#![feature(const_mut_refs)] #![no_std] #![crate_name = "lowrisc"] #![crate_type = "rlib"] +pub mod aon_timer; pub mod csrng; pub mod flash_ctrl; pub mod gpio; @@ -15,6 +17,9 @@ pub mod i2c; pub mod otbn; pub mod padctrl; pub mod pwrmgr; +pub mod registers; +pub mod rsa; +pub mod spi_host; pub mod uart; pub mod usbdev; pub mod virtual_otbn; diff --git a/chips/lowrisc/src/otbn.rs b/chips/lowrisc/src/otbn.rs index b9062caef1..d89e58477a 100644 --- a/chips/lowrisc/src/otbn.rs +++ b/chips/lowrisc/src/otbn.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! OTBN Control use core::cell::Cell; @@ -29,11 +33,12 @@ register_structs! { (0x1C => err_bits: ReadOnly), (0x20 => fatal_alert_cause: ReadOnly), (0x24 => insn_cnt: ReadWrite), - (0x28 => _reserved0), + (0x28 => load_checksum: ReadWrite), + (0x2C => _reserved0), (0x4000 => imem: [ReadWrite; 1024]), (0x5000 => _reserved1), - (0x8000 => dmem: [ReadWrite; 1024]), - (0x9000 => @END), + (0x8000 => dmem: [ReadWrite; 768]), + (0x8C00 => @END), } } @@ -47,9 +52,9 @@ register_bitfields![u32, ], CMD [ CMD OFFSET(0) NUMBITS(8) [ - EXECUTE = 0x01, - SEC_WIPE_DMEM = 0x02, - SEC_WIPE_IMEM = 0x03, + EXECUTE = 0xD8, + SEC_WIPE_DMEM = 0xC3, + SEC_WIPE_IMEM = 0x1E, ], ], CTRL [ @@ -61,6 +66,7 @@ register_bitfields![u32, BUSY_EXECUTE = 0x01, BUSY_SEC_WIPE_DMEM = 0x02, BUSY_SEC_WIPE_IMEM = 0x03, + BUSY_SEC_WIPE_INT = 0x04, LOCKED = 0xFF, ], ], @@ -70,22 +76,27 @@ register_bitfields![u32, CALL_STACK OFFSET(2) NUMBITS(1) [], ILLEGAL_INSN OFFSET(3) NUMBITS(1) [], LOOP_BIT OFFSET(4) NUMBITS(1) [], + KEY_INVAL OFFSET(5) NUMBITS(1) [], + RND_REP_CHK_FAIL OFFSET(6) NUMBITS(1) [], + RND_FIPS_CHK_FAIL OFFSET(7) NUMBITS(1) [], IMEM_INTG_VIOLATION OFFSET(16) NUMBITS(1) [], DMEM_INTG_VIOLATION OFFSET(17) NUMBITS(1) [], REG_INTG_VIOLATION OFFSET(18) NUMBITS(1) [], BUS_INTG_VIOLATION OFFSET(19) NUMBITS(1) [], - ILLEGAL_BUS_ACCESS OFFSET(20) NUMBITS(1) [], - LIFECYCLE_ESCALATION OFFSET(21) NUMBITS(1) [], - FATAL_SOFTWARE OFFSET(22) NUMBITS(1) [], + BAD_INTERNAL_STATE OFFSET(20) NUMBITS(1) [], + ILLEGAL_BUS_ACCESS OFFSET(21) NUMBITS(1) [], + LIFECYCLE_ESCALATION OFFSET(22) NUMBITS(1) [], + FATAL_SOFTWARE OFFSET(23) NUMBITS(1) [], ], FATAL_ALERT_CAUSE [ IMEM_INTG_VIOLATION OFFSET(0) NUMBITS(1) [], DMEM_INTG_VIOLATION OFFSET(1) NUMBITS(1) [], REG_INTG_VIOLATION OFFSET(2) NUMBITS(1) [], BUS_INTG_VIOLATION OFFSET(3) NUMBITS(1) [], - ILLEGAL_BUS_ACCESS OFFSET(4) NUMBITS(1) [], - LIFECYCLE_ESCALATION OFFSET(5) NUMBITS(1) [], - FATAL_SOFTWARE OFFSET(6) NUMBITS(1) [], + BAD_INTERNAL_STATE OFFSET(4) NUMBITS(1) [], + ILLEGAL_BUS_ACCESS OFFSET(5) NUMBITS(1) [], + LIFECYCLE_ESCALATION OFFSET(6) NUMBITS(1) [], + FATAL_SOFTWARE OFFSET(7) NUMBITS(1) [], ], ]; @@ -99,7 +110,7 @@ pub struct Otbn<'a> { } impl<'a> Otbn<'a> { - pub const fn new(base: StaticRef) -> Self { + pub fn new(base: StaticRef) -> Self { Otbn { registers: base, client: OptionalCell::empty(), @@ -127,7 +138,7 @@ impl<'a> Otbn<'a> { for i in 0..(out_buf.len() / 4) { let idx = i * 4; - let d = self.registers.dmem[self.copy_address.get() + i] + let d = self.registers.dmem[self.copy_address.get() / 4 + i] .get() .to_ne_bytes(); @@ -160,6 +171,10 @@ impl<'a> Otbn<'a> { // OTBN is performing an operation, we can't make any changes return Err(ErrorCode::BUSY); } + // Instruction memory is too large to fit + if (input.len() / 4) > self.registers.imem.len() { + return Err(ErrorCode::SIZE); + } for i in 0..(input.len() / 4) { let idx = i * 4; diff --git a/chips/lowrisc/src/padctrl.rs b/chips/lowrisc/src/padctrl.rs index 5a626d6f66..c03664de24 100644 --- a/chips/lowrisc/src/padctrl.rs +++ b/chips/lowrisc/src/padctrl.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! General Purpose Input/Output driver. use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite}; diff --git a/chips/lowrisc/src/pwrmgr.rs b/chips/lowrisc/src/pwrmgr.rs index 43e2f623ec..b3f9e5c2c6 100644 --- a/chips/lowrisc/src/pwrmgr.rs +++ b/chips/lowrisc/src/pwrmgr.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Power Mangement for LowRISC use kernel::utilities::registers::interfaces::{Readable, Writeable}; diff --git a/chips/lowrisc/src/registers/README.md b/chips/lowrisc/src/registers/README.md new file mode 100644 index 0000000000..786ff2ba24 --- /dev/null +++ b/chips/lowrisc/src/registers/README.md @@ -0,0 +1,16 @@ +# lowRISC Register Definitions + +The files in this directory are auto-generated register definitions for +lowRISC peripherals. These files were auto-generated from the OpenTitan +codebase as follows: + +```bash +$ cd $OPENTITAN_TREE +$ git checkout earlgrey_es +$ bazel build //sw/device/tock:tock_lowrisc_registers +$ tar -C $TOCK_TREE -xvf bazel-bin/sw/device/tock/tock_lowrisc_registers.tar +``` + +Note: the existence of a file in this directory does not necessarily mean +that a tock peripheral implementation exists. These files are _only_ +the register definitions. diff --git a/chips/lowrisc/src/registers/adc_ctrl_regs.rs b/chips/lowrisc/src/registers/adc_ctrl_regs.rs new file mode 100644 index 0000000000..0e24164457 --- /dev/null +++ b/chips/lowrisc/src/registers/adc_ctrl_regs.rs @@ -0,0 +1,120 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for adc_ctrl. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/adc_ctrl/data/adc_ctrl.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number for ADC filters +pub const ADC_CTRL_PARAM_NUM_ADC_FILTER: u32 = 8; +/// Number for ADC channels +pub const ADC_CTRL_PARAM_NUM_ADC_CHANNEL: u32 = 2; +/// Number of alerts +pub const ADC_CTRL_PARAM_NUM_ALERTS: u32 = 1; +/// Register width +pub const ADC_CTRL_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub AdcCtrlRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// ADC enable control register + (0x0010 => pub(crate) adc_en_ctl: ReadWrite), + /// ADC PowerDown(PD) control register + (0x0014 => pub(crate) adc_pd_ctl: ReadWrite), + /// ADC Low-Power(LP) sample control register + (0x0018 => pub(crate) adc_lp_sample_ctl: ReadWrite), + /// ADC sample control register + (0x001c => pub(crate) adc_sample_ctl: ReadWrite), + /// ADC FSM reset control + (0x0020 => pub(crate) adc_fsm_rst: ReadWrite), + /// ADC channel0 filter range + (0x0024 => pub(crate) adc_chn0_filter_ctl: [ReadWrite; 8]), + /// ADC channel1 filter range + (0x0044 => pub(crate) adc_chn1_filter_ctl: [ReadWrite; 8]), + /// ADC value sampled on channel + (0x0064 => pub(crate) adc_chn_val: [ReadWrite; 2]), + /// Enable filter matches as wakeups + (0x006c => pub(crate) adc_wakeup_ctl: ReadWrite), + /// Adc filter match status + (0x0070 => pub(crate) filter_status: ReadWrite), + /// Interrupt enable controls. + (0x0074 => pub(crate) adc_intr_ctl: ReadWrite), + /// Debug cable internal status + (0x0078 => pub(crate) adc_intr_status: ReadWrite), + (0x007c => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + MATCH_DONE OFFSET(0) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + FATAL_FAULT OFFSET(0) NUMBITS(1) [], + ], + pub(crate) ADC_EN_CTL [ + ADC_ENABLE OFFSET(0) NUMBITS(1) [], + ONESHOT_MODE OFFSET(1) NUMBITS(1) [], + ], + pub(crate) ADC_PD_CTL [ + LP_MODE OFFSET(0) NUMBITS(1) [], + PWRUP_TIME OFFSET(4) NUMBITS(4) [], + WAKEUP_TIME OFFSET(8) NUMBITS(24) [], + ], + pub(crate) ADC_LP_SAMPLE_CTL [ + LP_SAMPLE_CNT OFFSET(0) NUMBITS(8) [], + ], + pub(crate) ADC_SAMPLE_CTL [ + NP_SAMPLE_CNT OFFSET(0) NUMBITS(16) [], + ], + pub(crate) ADC_FSM_RST [ + RST_EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) ADC_CHN0_FILTER_CTL [ + MIN_V_0 OFFSET(2) NUMBITS(10) [], + COND_0 OFFSET(12) NUMBITS(1) [], + MAX_V_0 OFFSET(18) NUMBITS(10) [], + EN_0 OFFSET(31) NUMBITS(1) [], + ], + pub(crate) ADC_CHN1_FILTER_CTL [ + MIN_V_0 OFFSET(2) NUMBITS(10) [], + COND_0 OFFSET(12) NUMBITS(1) [], + MAX_V_0 OFFSET(18) NUMBITS(10) [], + EN_0 OFFSET(31) NUMBITS(1) [], + ], + pub(crate) ADC_CHN_VAL [ + ADC_CHN_VALUE_EXT_0 OFFSET(0) NUMBITS(2) [], + ADC_CHN_VALUE_0 OFFSET(2) NUMBITS(10) [], + ADC_CHN_VALUE_INTR_EXT_0 OFFSET(16) NUMBITS(2) [], + ADC_CHN_VALUE_INTR_0 OFFSET(18) NUMBITS(10) [], + ], + pub(crate) ADC_WAKEUP_CTL [ + EN OFFSET(0) NUMBITS(8) [], + ], + pub(crate) FILTER_STATUS [ + COND OFFSET(0) NUMBITS(8) [], + ], + pub(crate) ADC_INTR_CTL [ + EN OFFSET(0) NUMBITS(9) [], + ], + pub(crate) ADC_INTR_STATUS [ + FILTER_MATCH OFFSET(0) NUMBITS(8) [], + ONESHOT OFFSET(8) NUMBITS(1) [], + ], +]; + +// End generated register constants for adc_ctrl diff --git a/chips/lowrisc/src/registers/aes_regs.rs b/chips/lowrisc/src/registers/aes_regs.rs new file mode 100644 index 0000000000..4fdc9258e2 --- /dev/null +++ b/chips/lowrisc/src/registers/aes_regs.rs @@ -0,0 +1,123 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for aes. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/aes/data/aes.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number registers for key +pub const AES_PARAM_NUM_REGS_KEY: u32 = 8; +/// Number registers for initialization vector +pub const AES_PARAM_NUM_REGS_IV: u32 = 4; +/// Number registers for input and output data +pub const AES_PARAM_NUM_REGS_DATA: u32 = 4; +/// Number of alerts +pub const AES_PARAM_NUM_ALERTS: u32 = 2; +/// Register width +pub const AES_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub AesRegisters { + /// Alert Test Register + (0x0000 => pub(crate) alert_test: ReadWrite), + /// Initial Key Registers Share 0. + (0x0004 => pub(crate) key_share0: [ReadWrite; 8]), + /// Initial Key Registers Share 1. + (0x0024 => pub(crate) key_share1: [ReadWrite; 8]), + /// Initialization Vector Registers. + (0x0044 => pub(crate) iv: [ReadWrite; 4]), + /// Input Data Registers. + (0x0054 => pub(crate) data_in: [ReadWrite; 4]), + /// Output Data Register. + (0x0064 => pub(crate) data_out: [ReadWrite; 4]), + /// Control Register. + (0x0074 => pub(crate) ctrl_shadowed: ReadWrite), + /// Auxiliary Control Register. + (0x0078 => pub(crate) ctrl_aux_shadowed: ReadWrite), + /// Lock bit for Auxiliary Control Register. + (0x007c => pub(crate) ctrl_aux_regwen: ReadWrite), + /// Trigger Register. + (0x0080 => pub(crate) trigger: ReadWrite), + /// Status Register + (0x0084 => pub(crate) status: ReadWrite), + (0x0088 => @END), + } +} + +register_bitfields![u32, + pub(crate) ALERT_TEST [ + RECOV_CTRL_UPDATE_ERR OFFSET(0) NUMBITS(1) [], + FATAL_FAULT OFFSET(1) NUMBITS(1) [], + ], + pub(crate) KEY_SHARE0 [ + KEY_SHARE0_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) KEY_SHARE1 [ + KEY_SHARE1_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) IV [ + IV_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) DATA_IN [ + DATA_IN_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) DATA_OUT [ + DATA_OUT_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CTRL_SHADOWED [ + OPERATION OFFSET(0) NUMBITS(2) [ + AES_ENC = 1, + AES_DEC = 2, + ], + MODE OFFSET(2) NUMBITS(6) [ + AES_ECB = 1, + AES_CBC = 2, + AES_CFB = 4, + AES_OFB = 8, + AES_CTR = 16, + AES_NONE = 32, + ], + KEY_LEN OFFSET(8) NUMBITS(3) [ + AES_128 = 1, + AES_192 = 2, + AES_256 = 4, + ], + SIDELOAD OFFSET(11) NUMBITS(1) [], + PRNG_RESEED_RATE OFFSET(12) NUMBITS(3) [ + PER_1 = 1, + PER_64 = 2, + PER_8K = 4, + ], + MANUAL_OPERATION OFFSET(15) NUMBITS(1) [], + ], + pub(crate) CTRL_AUX_SHADOWED [ + KEY_TOUCH_FORCES_RESEED OFFSET(0) NUMBITS(1) [], + FORCE_MASKS OFFSET(1) NUMBITS(1) [], + ], + pub(crate) CTRL_AUX_REGWEN [ + CTRL_AUX_REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) TRIGGER [ + START OFFSET(0) NUMBITS(1) [], + KEY_IV_DATA_IN_CLEAR OFFSET(1) NUMBITS(1) [], + DATA_OUT_CLEAR OFFSET(2) NUMBITS(1) [], + PRNG_RESEED OFFSET(3) NUMBITS(1) [], + ], + pub(crate) STATUS [ + IDLE OFFSET(0) NUMBITS(1) [], + STALL OFFSET(1) NUMBITS(1) [], + OUTPUT_LOST OFFSET(2) NUMBITS(1) [], + OUTPUT_VALID OFFSET(3) NUMBITS(1) [], + INPUT_READY OFFSET(4) NUMBITS(1) [], + ALERT_RECOV_CTRL_UPDATE_ERR OFFSET(5) NUMBITS(1) [], + ALERT_FATAL_FAULT OFFSET(6) NUMBITS(1) [], + ], +]; + +// End generated register constants for aes diff --git a/chips/lowrisc/src/registers/aon_timer_regs.rs b/chips/lowrisc/src/registers/aon_timer_regs.rs new file mode 100644 index 0000000000..f251d50a4f --- /dev/null +++ b/chips/lowrisc/src/registers/aon_timer_regs.rs @@ -0,0 +1,92 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for aon_timer. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/aon_timer/data/aon_timer.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of alerts +pub const AON_TIMER_PARAM_NUM_ALERTS: u32 = 1; +/// Register width +pub const AON_TIMER_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub AonTimerRegisters { + /// Alert Test Register + (0x0000 => pub(crate) alert_test: ReadWrite), + /// Wakeup Timer Control register + (0x0004 => pub(crate) wkup_ctrl: ReadWrite), + /// Wakeup Timer Threshold Register + (0x0008 => pub(crate) wkup_thold: ReadWrite), + /// Wakeup Timer Count Register + (0x000c => pub(crate) wkup_count: ReadWrite), + /// Watchdog Timer Write Enable Register + (0x0010 => pub(crate) wdog_regwen: ReadWrite), + /// Watchdog Timer Control register + (0x0014 => pub(crate) wdog_ctrl: ReadWrite), + /// Watchdog Timer Bark Threshold Register + (0x0018 => pub(crate) wdog_bark_thold: ReadWrite), + /// Watchdog Timer Bite Threshold Register + (0x001c => pub(crate) wdog_bite_thold: ReadWrite), + /// Watchdog Timer Count Register + (0x0020 => pub(crate) wdog_count: ReadWrite), + /// Interrupt State Register + (0x0024 => pub(crate) intr_state: ReadWrite), + /// Interrupt Test Register + (0x0028 => pub(crate) intr_test: ReadWrite), + /// Wakeup request status + (0x002c => pub(crate) wkup_cause: ReadWrite), + (0x0030 => @END), + } +} + +register_bitfields![u32, + pub(crate) ALERT_TEST [ + FATAL_FAULT OFFSET(0) NUMBITS(1) [], + ], + pub(crate) WKUP_CTRL [ + ENABLE OFFSET(0) NUMBITS(1) [], + PRESCALER OFFSET(1) NUMBITS(12) [], + ], + pub(crate) WKUP_THOLD [ + THRESHOLD OFFSET(0) NUMBITS(32) [], + ], + pub(crate) WKUP_COUNT [ + COUNT OFFSET(0) NUMBITS(32) [], + ], + pub(crate) WDOG_REGWEN [ + REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) WDOG_CTRL [ + ENABLE OFFSET(0) NUMBITS(1) [], + PAUSE_IN_SLEEP OFFSET(1) NUMBITS(1) [], + ], + pub(crate) WDOG_BARK_THOLD [ + THRESHOLD OFFSET(0) NUMBITS(32) [], + ], + pub(crate) WDOG_BITE_THOLD [ + THRESHOLD OFFSET(0) NUMBITS(32) [], + ], + pub(crate) WDOG_COUNT [ + COUNT OFFSET(0) NUMBITS(32) [], + ], + pub(crate) INTR_STATE [ + WKUP_TIMER_EXPIRED OFFSET(0) NUMBITS(1) [], + WDOG_TIMER_BARK OFFSET(1) NUMBITS(1) [], + ], + pub(crate) INTR_TEST [ + WKUP_TIMER_EXPIRED OFFSET(0) NUMBITS(1) [], + WDOG_TIMER_BARK OFFSET(1) NUMBITS(1) [], + ], + pub(crate) WKUP_CAUSE [ + CAUSE OFFSET(0) NUMBITS(1) [], + ], +]; + +// End generated register constants for aon_timer diff --git a/chips/lowrisc/src/registers/clkmgr_regs.rs b/chips/lowrisc/src/registers/clkmgr_regs.rs new file mode 100644 index 0000000000..365fe72c91 --- /dev/null +++ b/chips/lowrisc/src/registers/clkmgr_regs.rs @@ -0,0 +1,46 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for clkmgr. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/clkmgr/data/clkmgr.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of clock groups +pub const CLKMGR_PARAM_NUM_GROUPS: u32 = 7; +/// Register width +pub const CLKMGR_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub ClkmgrRegisters { + /// Clock enable for software gateable clocks. + (0x0000 => pub(crate) clk_enables: ReadWrite), + /// Clock hint for software gateable clocks. + (0x0004 => pub(crate) clk_hints: ReadWrite), + /// Since the final state of !!CLK_HINTS is not always determined by software, + (0x0008 => pub(crate) clk_hints_status: ReadWrite), + (0x000c => @END), + } +} + +register_bitfields![u32, + pub(crate) CLK_ENABLES [ + CLK_FIXED_PERI_EN OFFSET(0) NUMBITS(1) [], + CLK_USB_48MHZ_PERI_EN OFFSET(1) NUMBITS(1) [], + ], + pub(crate) CLK_HINTS [ + CLK_MAIN_AES_HINT OFFSET(0) NUMBITS(1) [], + CLK_MAIN_HMAC_HINT OFFSET(1) NUMBITS(1) [], + ], + pub(crate) CLK_HINTS_STATUS [ + CLK_MAIN_AES_VAL OFFSET(0) NUMBITS(1) [], + CLK_MAIN_HMAC_VAL OFFSET(1) NUMBITS(1) [], + ], +]; + +// End generated register constants for clkmgr diff --git a/chips/lowrisc/src/registers/csrng_regs.rs b/chips/lowrisc/src/registers/csrng_regs.rs new file mode 100644 index 0000000000..67325d8141 --- /dev/null +++ b/chips/lowrisc/src/registers/csrng_regs.rs @@ -0,0 +1,146 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for csrng. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/csrng/data/csrng.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of alerts +pub const CSRNG_PARAM_NUM_ALERTS: u32 = 2; +/// Register width +pub const CSRNG_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub CsrngRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// Register write enable for all control registers + (0x0010 => pub(crate) regwen: ReadWrite), + /// Control register + (0x0014 => pub(crate) ctrl: ReadWrite), + /// Command request register + (0x0018 => pub(crate) cmd_req: ReadWrite), + /// Application interface command status register + (0x001c => pub(crate) sw_cmd_sts: ReadWrite), + /// Generate bits returned valid register + (0x0020 => pub(crate) genbits_vld: ReadWrite), + /// Generate bits returned register + (0x0024 => pub(crate) genbits: ReadWrite), + /// Internal state number register + (0x0028 => pub(crate) int_state_num: ReadWrite), + /// Internal state read access register + (0x002c => pub(crate) int_state_val: ReadWrite), + /// Hardware instance exception status register + (0x0030 => pub(crate) hw_exc_sts: ReadWrite), + /// Recoverable alert status register + (0x0034 => pub(crate) recov_alert_sts: ReadWrite), + /// Hardware detection of error conditions status register + (0x0038 => pub(crate) err_code: ReadWrite), + /// Test error conditions register + (0x003c => pub(crate) err_code_test: ReadWrite), + /// Main state machine state debug register + (0x0040 => pub(crate) main_sm_state: ReadWrite), + (0x0044 => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + CS_CMD_REQ_DONE OFFSET(0) NUMBITS(1) [], + CS_ENTROPY_REQ OFFSET(1) NUMBITS(1) [], + CS_HW_INST_EXC OFFSET(2) NUMBITS(1) [], + CS_FATAL_ERR OFFSET(3) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + RECOV_ALERT OFFSET(0) NUMBITS(1) [], + FATAL_ALERT OFFSET(1) NUMBITS(1) [], + ], + pub(crate) REGWEN [ + REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CTRL [ + ENABLE OFFSET(0) NUMBITS(4) [], + SW_APP_ENABLE OFFSET(4) NUMBITS(4) [], + READ_INT_STATE OFFSET(8) NUMBITS(4) [], + ], + pub(crate) CMD_REQ [ + CMD_REQ OFFSET(0) NUMBITS(32) [], + ], + pub(crate) SW_CMD_STS [ + CMD_RDY OFFSET(0) NUMBITS(1) [], + CMD_STS OFFSET(1) NUMBITS(1) [], + ], + pub(crate) GENBITS_VLD [ + GENBITS_VLD OFFSET(0) NUMBITS(1) [], + GENBITS_FIPS OFFSET(1) NUMBITS(1) [], + ], + pub(crate) GENBITS [ + GENBITS OFFSET(0) NUMBITS(32) [], + ], + pub(crate) INT_STATE_NUM [ + INT_STATE_NUM OFFSET(0) NUMBITS(4) [], + ], + pub(crate) INT_STATE_VAL [ + INT_STATE_VAL OFFSET(0) NUMBITS(32) [], + ], + pub(crate) HW_EXC_STS [ + HW_EXC_STS OFFSET(0) NUMBITS(16) [], + ], + pub(crate) RECOV_ALERT_STS [ + ENABLE_FIELD_ALERT OFFSET(0) NUMBITS(1) [], + SW_APP_ENABLE_FIELD_ALERT OFFSET(1) NUMBITS(1) [], + READ_INT_STATE_FIELD_ALERT OFFSET(2) NUMBITS(1) [], + ACMD_FLAG0_FIELD_ALERT OFFSET(3) NUMBITS(1) [], + CS_BUS_CMP_ALERT OFFSET(12) NUMBITS(1) [], + CS_MAIN_SM_ALERT OFFSET(13) NUMBITS(1) [], + ], + pub(crate) ERR_CODE [ + SFIFO_CMD_ERR OFFSET(0) NUMBITS(1) [], + SFIFO_GENBITS_ERR OFFSET(1) NUMBITS(1) [], + SFIFO_CMDREQ_ERR OFFSET(2) NUMBITS(1) [], + SFIFO_RCSTAGE_ERR OFFSET(3) NUMBITS(1) [], + SFIFO_KEYVRC_ERR OFFSET(4) NUMBITS(1) [], + SFIFO_UPDREQ_ERR OFFSET(5) NUMBITS(1) [], + SFIFO_BENCREQ_ERR OFFSET(6) NUMBITS(1) [], + SFIFO_BENCACK_ERR OFFSET(7) NUMBITS(1) [], + SFIFO_PDATA_ERR OFFSET(8) NUMBITS(1) [], + SFIFO_FINAL_ERR OFFSET(9) NUMBITS(1) [], + SFIFO_GBENCACK_ERR OFFSET(10) NUMBITS(1) [], + SFIFO_GRCSTAGE_ERR OFFSET(11) NUMBITS(1) [], + SFIFO_GGENREQ_ERR OFFSET(12) NUMBITS(1) [], + SFIFO_GADSTAGE_ERR OFFSET(13) NUMBITS(1) [], + SFIFO_GGENBITS_ERR OFFSET(14) NUMBITS(1) [], + SFIFO_BLKENC_ERR OFFSET(15) NUMBITS(1) [], + CMD_STAGE_SM_ERR OFFSET(20) NUMBITS(1) [], + MAIN_SM_ERR OFFSET(21) NUMBITS(1) [], + DRBG_GEN_SM_ERR OFFSET(22) NUMBITS(1) [], + DRBG_UPDBE_SM_ERR OFFSET(23) NUMBITS(1) [], + DRBG_UPDOB_SM_ERR OFFSET(24) NUMBITS(1) [], + AES_CIPHER_SM_ERR OFFSET(25) NUMBITS(1) [], + CMD_GEN_CNT_ERR OFFSET(26) NUMBITS(1) [], + FIFO_WRITE_ERR OFFSET(28) NUMBITS(1) [], + FIFO_READ_ERR OFFSET(29) NUMBITS(1) [], + FIFO_STATE_ERR OFFSET(30) NUMBITS(1) [], + ], + pub(crate) ERR_CODE_TEST [ + ERR_CODE_TEST OFFSET(0) NUMBITS(5) [], + ], + pub(crate) MAIN_SM_STATE [ + MAIN_SM_STATE OFFSET(0) NUMBITS(8) [], + ], +]; + +// End generated register constants for csrng diff --git a/chips/lowrisc/src/registers/edn_regs.rs b/chips/lowrisc/src/registers/edn_regs.rs new file mode 100644 index 0000000000..4474ed7e39 --- /dev/null +++ b/chips/lowrisc/src/registers/edn_regs.rs @@ -0,0 +1,126 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for edn. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/edn/data/edn.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of alerts +pub const EDN_PARAM_NUM_ALERTS: u32 = 2; +/// Register width +pub const EDN_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub EdnRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// Register write enable for all control registers + (0x0010 => pub(crate) regwen: ReadWrite), + /// EDN control register + (0x0014 => pub(crate) ctrl: ReadWrite), + /// EDN boot instantiate command register + (0x0018 => pub(crate) boot_ins_cmd: ReadWrite), + /// EDN boot generate command register + (0x001c => pub(crate) boot_gen_cmd: ReadWrite), + /// EDN csrng app command request register + (0x0020 => pub(crate) sw_cmd_req: ReadWrite), + /// EDN command status register + (0x0024 => pub(crate) sw_cmd_sts: ReadWrite), + /// EDN csrng reseed command register + (0x0028 => pub(crate) reseed_cmd: ReadWrite), + /// EDN csrng generate command register + (0x002c => pub(crate) generate_cmd: ReadWrite), + /// EDN maximum number of requests between reseeds register + (0x0030 => pub(crate) max_num_reqs_between_reseeds: ReadWrite), + /// Recoverable alert status register + (0x0034 => pub(crate) recov_alert_sts: ReadWrite), + /// Hardware detection of fatal error conditions status register + (0x0038 => pub(crate) err_code: ReadWrite), + /// Test error conditions register + (0x003c => pub(crate) err_code_test: ReadWrite), + /// Main state machine state observation register + (0x0040 => pub(crate) main_sm_state: ReadWrite), + (0x0044 => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + EDN_CMD_REQ_DONE OFFSET(0) NUMBITS(1) [], + EDN_FATAL_ERR OFFSET(1) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + RECOV_ALERT OFFSET(0) NUMBITS(1) [], + FATAL_ALERT OFFSET(1) NUMBITS(1) [], + ], + pub(crate) REGWEN [ + REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CTRL [ + EDN_ENABLE OFFSET(0) NUMBITS(4) [], + BOOT_REQ_MODE OFFSET(4) NUMBITS(4) [], + AUTO_REQ_MODE OFFSET(8) NUMBITS(4) [], + CMD_FIFO_RST OFFSET(12) NUMBITS(4) [], + ], + pub(crate) BOOT_INS_CMD [ + BOOT_INS_CMD OFFSET(0) NUMBITS(32) [], + ], + pub(crate) BOOT_GEN_CMD [ + BOOT_GEN_CMD OFFSET(0) NUMBITS(32) [], + ], + pub(crate) SW_CMD_REQ [ + SW_CMD_REQ OFFSET(0) NUMBITS(32) [], + ], + pub(crate) SW_CMD_STS [ + CMD_RDY OFFSET(0) NUMBITS(1) [], + CMD_STS OFFSET(1) NUMBITS(1) [], + ], + pub(crate) RESEED_CMD [ + RESEED_CMD OFFSET(0) NUMBITS(32) [], + ], + pub(crate) GENERATE_CMD [ + GENERATE_CMD OFFSET(0) NUMBITS(32) [], + ], + pub(crate) MAX_NUM_REQS_BETWEEN_RESEEDS [ + MAX_NUM_REQS_BETWEEN_RESEEDS OFFSET(0) NUMBITS(32) [], + ], + pub(crate) RECOV_ALERT_STS [ + EDN_ENABLE_FIELD_ALERT OFFSET(0) NUMBITS(1) [], + BOOT_REQ_MODE_FIELD_ALERT OFFSET(1) NUMBITS(1) [], + AUTO_REQ_MODE_FIELD_ALERT OFFSET(2) NUMBITS(1) [], + CMD_FIFO_RST_FIELD_ALERT OFFSET(3) NUMBITS(1) [], + EDN_BUS_CMP_ALERT OFFSET(12) NUMBITS(1) [], + ], + pub(crate) ERR_CODE [ + SFIFO_RESCMD_ERR OFFSET(0) NUMBITS(1) [], + SFIFO_GENCMD_ERR OFFSET(1) NUMBITS(1) [], + SFIFO_OUTPUT_ERR OFFSET(2) NUMBITS(1) [], + EDN_ACK_SM_ERR OFFSET(20) NUMBITS(1) [], + EDN_MAIN_SM_ERR OFFSET(21) NUMBITS(1) [], + EDN_CNTR_ERR OFFSET(22) NUMBITS(1) [], + FIFO_WRITE_ERR OFFSET(28) NUMBITS(1) [], + FIFO_READ_ERR OFFSET(29) NUMBITS(1) [], + FIFO_STATE_ERR OFFSET(30) NUMBITS(1) [], + ], + pub(crate) ERR_CODE_TEST [ + ERR_CODE_TEST OFFSET(0) NUMBITS(5) [], + ], + pub(crate) MAIN_SM_STATE [ + MAIN_SM_STATE OFFSET(0) NUMBITS(9) [], + ], +]; + +// End generated register constants for edn diff --git a/chips/lowrisc/src/registers/entropy_src_regs.rs b/chips/lowrisc/src/registers/entropy_src_regs.rs new file mode 100644 index 0000000000..537b0c2fcb --- /dev/null +++ b/chips/lowrisc/src/registers/entropy_src_regs.rs @@ -0,0 +1,378 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for entropy_src. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/entropy_src/data/entropy_src.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of alerts +pub const ENTROPY_SRC_PARAM_NUM_ALERTS: u32 = 2; +/// Register width +pub const ENTROPY_SRC_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub EntropySrcRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// Register write enable for module enable register + (0x0010 => pub(crate) me_regwen: ReadWrite), + /// Register write enable for control and threshold registers + (0x0014 => pub(crate) sw_regupd: ReadWrite), + /// Register write enable for all control registers + (0x0018 => pub(crate) regwen: ReadWrite), + /// Revision register + (0x001c => pub(crate) rev: ReadWrite), + /// Module enable register + (0x0020 => pub(crate) module_enable: ReadWrite), + /// Configuration register + (0x0024 => pub(crate) conf: ReadWrite), + /// Entropy control register + (0x0028 => pub(crate) entropy_control: ReadWrite), + /// Entropy data bits + (0x002c => pub(crate) entropy_data: ReadWrite), + /// Health test windows register + (0x0030 => pub(crate) health_test_windows: ReadWrite), + /// Repetition count test thresholds register + (0x0034 => pub(crate) repcnt_thresholds: ReadWrite), + /// Repetition count symbol test thresholds register + (0x0038 => pub(crate) repcnts_thresholds: ReadWrite), + /// Adaptive proportion test high thresholds register + (0x003c => pub(crate) adaptp_hi_thresholds: ReadWrite), + /// Adaptive proportion test low thresholds register + (0x0040 => pub(crate) adaptp_lo_thresholds: ReadWrite), + /// Bucket test thresholds register + (0x0044 => pub(crate) bucket_thresholds: ReadWrite), + /// Markov test high thresholds register + (0x0048 => pub(crate) markov_hi_thresholds: ReadWrite), + /// Markov test low thresholds register + (0x004c => pub(crate) markov_lo_thresholds: ReadWrite), + /// External health test high thresholds register + (0x0050 => pub(crate) extht_hi_thresholds: ReadWrite), + /// External health test low thresholds register + (0x0054 => pub(crate) extht_lo_thresholds: ReadWrite), + /// Repetition count test high watermarks register + (0x0058 => pub(crate) repcnt_hi_watermarks: ReadWrite), + /// Repetition count symbol test high watermarks register + (0x005c => pub(crate) repcnts_hi_watermarks: ReadWrite), + /// Adaptive proportion test high watermarks register + (0x0060 => pub(crate) adaptp_hi_watermarks: ReadWrite), + /// Adaptive proportion test low watermarks register + (0x0064 => pub(crate) adaptp_lo_watermarks: ReadWrite), + /// External health test high watermarks register + (0x0068 => pub(crate) extht_hi_watermarks: ReadWrite), + /// External health test low watermarks register + (0x006c => pub(crate) extht_lo_watermarks: ReadWrite), + /// Bucket test high watermarks register + (0x0070 => pub(crate) bucket_hi_watermarks: ReadWrite), + /// Markov test high watermarks register + (0x0074 => pub(crate) markov_hi_watermarks: ReadWrite), + /// Markov test low watermarks register + (0x0078 => pub(crate) markov_lo_watermarks: ReadWrite), + /// Repetition count test failure counter register + (0x007c => pub(crate) repcnt_total_fails: ReadWrite), + /// Repetition count symbol test failure counter register + (0x0080 => pub(crate) repcnts_total_fails: ReadWrite), + /// Adaptive proportion high test failure counter register + (0x0084 => pub(crate) adaptp_hi_total_fails: ReadWrite), + /// Adaptive proportion low test failure counter register + (0x0088 => pub(crate) adaptp_lo_total_fails: ReadWrite), + /// Bucket test failure counter register + (0x008c => pub(crate) bucket_total_fails: ReadWrite), + /// Markov high test failure counter register + (0x0090 => pub(crate) markov_hi_total_fails: ReadWrite), + /// Markov low test failure counter register + (0x0094 => pub(crate) markov_lo_total_fails: ReadWrite), + /// External health test high threshold failure counter register + (0x0098 => pub(crate) extht_hi_total_fails: ReadWrite), + /// External health test low threshold failure counter register + (0x009c => pub(crate) extht_lo_total_fails: ReadWrite), + /// Alert threshold register + (0x00a0 => pub(crate) alert_threshold: ReadWrite), + /// Alert summary failure counts register + (0x00a4 => pub(crate) alert_summary_fail_counts: ReadWrite), + /// Alert failure counts register + (0x00a8 => pub(crate) alert_fail_counts: ReadWrite), + /// External health test alert failure counts register + (0x00ac => pub(crate) extht_fail_counts: ReadWrite), + /// Firmware override control register + (0x00b0 => pub(crate) fw_ov_control: ReadWrite), + /// Firmware override sha3 block start control register + (0x00b4 => pub(crate) fw_ov_sha3_start: ReadWrite), + /// Firmware override FIFO write full status register + (0x00b8 => pub(crate) fw_ov_wr_fifo_full: ReadWrite), + /// Firmware override observe FIFO overflow status + (0x00bc => pub(crate) fw_ov_rd_fifo_overflow: ReadWrite), + /// Firmware override observe FIFO read register + (0x00c0 => pub(crate) fw_ov_rd_data: ReadWrite), + /// Firmware override FIFO write register + (0x00c4 => pub(crate) fw_ov_wr_data: ReadWrite), + /// Observe FIFO threshold register + (0x00c8 => pub(crate) observe_fifo_thresh: ReadWrite), + /// Observe FIFO depth register + (0x00cc => pub(crate) observe_fifo_depth: ReadWrite), + /// Debug status register + (0x00d0 => pub(crate) debug_status: ReadWrite), + /// Recoverable alert status register + (0x00d4 => pub(crate) recov_alert_sts: ReadWrite), + /// Hardware detection of error conditions status register + (0x00d8 => pub(crate) err_code: ReadWrite), + /// Test error conditions register + (0x00dc => pub(crate) err_code_test: ReadWrite), + /// Main state machine state debug register + (0x00e0 => pub(crate) main_sm_state: ReadWrite), + (0x00e4 => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + ES_ENTROPY_VALID OFFSET(0) NUMBITS(1) [], + ES_HEALTH_TEST_FAILED OFFSET(1) NUMBITS(1) [], + ES_OBSERVE_FIFO_READY OFFSET(2) NUMBITS(1) [], + ES_FATAL_ERR OFFSET(3) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + RECOV_ALERT OFFSET(0) NUMBITS(1) [], + FATAL_ALERT OFFSET(1) NUMBITS(1) [], + ], + pub(crate) ME_REGWEN [ + ME_REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) SW_REGUPD [ + SW_REGUPD OFFSET(0) NUMBITS(1) [], + ], + pub(crate) REGWEN [ + REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) REV [ + ABI_REVISION OFFSET(0) NUMBITS(8) [], + HW_REVISION OFFSET(8) NUMBITS(8) [], + CHIP_TYPE OFFSET(16) NUMBITS(8) [], + ], + pub(crate) MODULE_ENABLE [ + MODULE_ENABLE OFFSET(0) NUMBITS(4) [], + ], + pub(crate) CONF [ + FIPS_ENABLE OFFSET(0) NUMBITS(4) [], + ENTROPY_DATA_REG_ENABLE OFFSET(4) NUMBITS(4) [], + THRESHOLD_SCOPE OFFSET(12) NUMBITS(4) [], + RNG_BIT_ENABLE OFFSET(20) NUMBITS(4) [], + RNG_BIT_SEL OFFSET(24) NUMBITS(2) [], + ], + pub(crate) ENTROPY_CONTROL [ + ES_ROUTE OFFSET(0) NUMBITS(4) [], + ES_TYPE OFFSET(4) NUMBITS(4) [], + ], + pub(crate) ENTROPY_DATA [ + ENTROPY_DATA OFFSET(0) NUMBITS(32) [], + ], + pub(crate) HEALTH_TEST_WINDOWS [ + FIPS_WINDOW OFFSET(0) NUMBITS(16) [], + BYPASS_WINDOW OFFSET(16) NUMBITS(16) [], + ], + pub(crate) REPCNT_THRESHOLDS [ + FIPS_THRESH OFFSET(0) NUMBITS(16) [], + BYPASS_THRESH OFFSET(16) NUMBITS(16) [], + ], + pub(crate) REPCNTS_THRESHOLDS [ + FIPS_THRESH OFFSET(0) NUMBITS(16) [], + BYPASS_THRESH OFFSET(16) NUMBITS(16) [], + ], + pub(crate) ADAPTP_HI_THRESHOLDS [ + FIPS_THRESH OFFSET(0) NUMBITS(16) [], + BYPASS_THRESH OFFSET(16) NUMBITS(16) [], + ], + pub(crate) ADAPTP_LO_THRESHOLDS [ + FIPS_THRESH OFFSET(0) NUMBITS(16) [], + BYPASS_THRESH OFFSET(16) NUMBITS(16) [], + ], + pub(crate) BUCKET_THRESHOLDS [ + FIPS_THRESH OFFSET(0) NUMBITS(16) [], + BYPASS_THRESH OFFSET(16) NUMBITS(16) [], + ], + pub(crate) MARKOV_HI_THRESHOLDS [ + FIPS_THRESH OFFSET(0) NUMBITS(16) [], + BYPASS_THRESH OFFSET(16) NUMBITS(16) [], + ], + pub(crate) MARKOV_LO_THRESHOLDS [ + FIPS_THRESH OFFSET(0) NUMBITS(16) [], + BYPASS_THRESH OFFSET(16) NUMBITS(16) [], + ], + pub(crate) EXTHT_HI_THRESHOLDS [ + FIPS_THRESH OFFSET(0) NUMBITS(16) [], + BYPASS_THRESH OFFSET(16) NUMBITS(16) [], + ], + pub(crate) EXTHT_LO_THRESHOLDS [ + FIPS_THRESH OFFSET(0) NUMBITS(16) [], + BYPASS_THRESH OFFSET(16) NUMBITS(16) [], + ], + pub(crate) REPCNT_HI_WATERMARKS [ + FIPS_WATERMARK OFFSET(0) NUMBITS(16) [], + BYPASS_WATERMARK OFFSET(16) NUMBITS(16) [], + ], + pub(crate) REPCNTS_HI_WATERMARKS [ + FIPS_WATERMARK OFFSET(0) NUMBITS(16) [], + BYPASS_WATERMARK OFFSET(16) NUMBITS(16) [], + ], + pub(crate) ADAPTP_HI_WATERMARKS [ + FIPS_WATERMARK OFFSET(0) NUMBITS(16) [], + BYPASS_WATERMARK OFFSET(16) NUMBITS(16) [], + ], + pub(crate) ADAPTP_LO_WATERMARKS [ + FIPS_WATERMARK OFFSET(0) NUMBITS(16) [], + BYPASS_WATERMARK OFFSET(16) NUMBITS(16) [], + ], + pub(crate) EXTHT_HI_WATERMARKS [ + FIPS_WATERMARK OFFSET(0) NUMBITS(16) [], + BYPASS_WATERMARK OFFSET(16) NUMBITS(16) [], + ], + pub(crate) EXTHT_LO_WATERMARKS [ + FIPS_WATERMARK OFFSET(0) NUMBITS(16) [], + BYPASS_WATERMARK OFFSET(16) NUMBITS(16) [], + ], + pub(crate) BUCKET_HI_WATERMARKS [ + FIPS_WATERMARK OFFSET(0) NUMBITS(16) [], + BYPASS_WATERMARK OFFSET(16) NUMBITS(16) [], + ], + pub(crate) MARKOV_HI_WATERMARKS [ + FIPS_WATERMARK OFFSET(0) NUMBITS(16) [], + BYPASS_WATERMARK OFFSET(16) NUMBITS(16) [], + ], + pub(crate) MARKOV_LO_WATERMARKS [ + FIPS_WATERMARK OFFSET(0) NUMBITS(16) [], + BYPASS_WATERMARK OFFSET(16) NUMBITS(16) [], + ], + pub(crate) REPCNT_TOTAL_FAILS [ + REPCNT_TOTAL_FAILS OFFSET(0) NUMBITS(32) [], + ], + pub(crate) REPCNTS_TOTAL_FAILS [ + REPCNTS_TOTAL_FAILS OFFSET(0) NUMBITS(32) [], + ], + pub(crate) ADAPTP_HI_TOTAL_FAILS [ + ADAPTP_HI_TOTAL_FAILS OFFSET(0) NUMBITS(32) [], + ], + pub(crate) ADAPTP_LO_TOTAL_FAILS [ + ADAPTP_LO_TOTAL_FAILS OFFSET(0) NUMBITS(32) [], + ], + pub(crate) BUCKET_TOTAL_FAILS [ + BUCKET_TOTAL_FAILS OFFSET(0) NUMBITS(32) [], + ], + pub(crate) MARKOV_HI_TOTAL_FAILS [ + MARKOV_HI_TOTAL_FAILS OFFSET(0) NUMBITS(32) [], + ], + pub(crate) MARKOV_LO_TOTAL_FAILS [ + MARKOV_LO_TOTAL_FAILS OFFSET(0) NUMBITS(32) [], + ], + pub(crate) EXTHT_HI_TOTAL_FAILS [ + EXTHT_HI_TOTAL_FAILS OFFSET(0) NUMBITS(32) [], + ], + pub(crate) EXTHT_LO_TOTAL_FAILS [ + EXTHT_LO_TOTAL_FAILS OFFSET(0) NUMBITS(32) [], + ], + pub(crate) ALERT_THRESHOLD [ + ALERT_THRESHOLD OFFSET(0) NUMBITS(16) [], + ALERT_THRESHOLD_INV OFFSET(16) NUMBITS(16) [], + ], + pub(crate) ALERT_SUMMARY_FAIL_COUNTS [ + ANY_FAIL_COUNT OFFSET(0) NUMBITS(16) [], + ], + pub(crate) ALERT_FAIL_COUNTS [ + REPCNT_FAIL_COUNT OFFSET(4) NUMBITS(4) [], + ADAPTP_HI_FAIL_COUNT OFFSET(8) NUMBITS(4) [], + ADAPTP_LO_FAIL_COUNT OFFSET(12) NUMBITS(4) [], + BUCKET_FAIL_COUNT OFFSET(16) NUMBITS(4) [], + MARKOV_HI_FAIL_COUNT OFFSET(20) NUMBITS(4) [], + MARKOV_LO_FAIL_COUNT OFFSET(24) NUMBITS(4) [], + REPCNTS_FAIL_COUNT OFFSET(28) NUMBITS(4) [], + ], + pub(crate) EXTHT_FAIL_COUNTS [ + EXTHT_HI_FAIL_COUNT OFFSET(0) NUMBITS(4) [], + EXTHT_LO_FAIL_COUNT OFFSET(4) NUMBITS(4) [], + ], + pub(crate) FW_OV_CONTROL [ + FW_OV_MODE OFFSET(0) NUMBITS(4) [], + FW_OV_ENTROPY_INSERT OFFSET(4) NUMBITS(4) [], + ], + pub(crate) FW_OV_SHA3_START [ + FW_OV_INSERT_START OFFSET(0) NUMBITS(4) [], + ], + pub(crate) FW_OV_WR_FIFO_FULL [ + FW_OV_WR_FIFO_FULL OFFSET(0) NUMBITS(1) [], + ], + pub(crate) FW_OV_RD_FIFO_OVERFLOW [ + FW_OV_RD_FIFO_OVERFLOW OFFSET(0) NUMBITS(1) [], + ], + pub(crate) FW_OV_RD_DATA [ + FW_OV_RD_DATA OFFSET(0) NUMBITS(32) [], + ], + pub(crate) FW_OV_WR_DATA [ + FW_OV_WR_DATA OFFSET(0) NUMBITS(32) [], + ], + pub(crate) OBSERVE_FIFO_THRESH [ + OBSERVE_FIFO_THRESH OFFSET(0) NUMBITS(7) [], + ], + pub(crate) OBSERVE_FIFO_DEPTH [ + OBSERVE_FIFO_DEPTH OFFSET(0) NUMBITS(7) [], + ], + pub(crate) DEBUG_STATUS [ + ENTROPY_FIFO_DEPTH OFFSET(0) NUMBITS(3) [], + SHA3_FSM OFFSET(3) NUMBITS(3) [], + SHA3_BLOCK_PR OFFSET(6) NUMBITS(1) [], + SHA3_SQUEEZING OFFSET(7) NUMBITS(1) [], + SHA3_ABSORBED OFFSET(8) NUMBITS(1) [], + SHA3_ERR OFFSET(9) NUMBITS(1) [], + MAIN_SM_IDLE OFFSET(16) NUMBITS(1) [], + MAIN_SM_BOOT_DONE OFFSET(17) NUMBITS(1) [], + ], + pub(crate) RECOV_ALERT_STS [ + FIPS_ENABLE_FIELD_ALERT OFFSET(0) NUMBITS(1) [], + ENTROPY_DATA_REG_EN_FIELD_ALERT OFFSET(1) NUMBITS(1) [], + MODULE_ENABLE_FIELD_ALERT OFFSET(2) NUMBITS(1) [], + THRESHOLD_SCOPE_FIELD_ALERT OFFSET(3) NUMBITS(1) [], + RNG_BIT_ENABLE_FIELD_ALERT OFFSET(5) NUMBITS(1) [], + FW_OV_SHA3_START_FIELD_ALERT OFFSET(7) NUMBITS(1) [], + FW_OV_MODE_FIELD_ALERT OFFSET(8) NUMBITS(1) [], + FW_OV_ENTROPY_INSERT_FIELD_ALERT OFFSET(9) NUMBITS(1) [], + ES_ROUTE_FIELD_ALERT OFFSET(10) NUMBITS(1) [], + ES_TYPE_FIELD_ALERT OFFSET(11) NUMBITS(1) [], + ES_MAIN_SM_ALERT OFFSET(12) NUMBITS(1) [], + ES_BUS_CMP_ALERT OFFSET(13) NUMBITS(1) [], + ES_THRESH_CFG_ALERT OFFSET(14) NUMBITS(1) [], + ES_FW_OV_WR_ALERT OFFSET(15) NUMBITS(1) [], + ES_FW_OV_DISABLE_ALERT OFFSET(16) NUMBITS(1) [], + ], + pub(crate) ERR_CODE [ + SFIFO_ESRNG_ERR OFFSET(0) NUMBITS(1) [], + SFIFO_OBSERVE_ERR OFFSET(1) NUMBITS(1) [], + SFIFO_ESFINAL_ERR OFFSET(2) NUMBITS(1) [], + ES_ACK_SM_ERR OFFSET(20) NUMBITS(1) [], + ES_MAIN_SM_ERR OFFSET(21) NUMBITS(1) [], + ES_CNTR_ERR OFFSET(22) NUMBITS(1) [], + SHA3_STATE_ERR OFFSET(23) NUMBITS(1) [], + SHA3_RST_STORAGE_ERR OFFSET(24) NUMBITS(1) [], + FIFO_WRITE_ERR OFFSET(28) NUMBITS(1) [], + FIFO_READ_ERR OFFSET(29) NUMBITS(1) [], + FIFO_STATE_ERR OFFSET(30) NUMBITS(1) [], + ], + pub(crate) ERR_CODE_TEST [ + ERR_CODE_TEST OFFSET(0) NUMBITS(5) [], + ], + pub(crate) MAIN_SM_STATE [ + MAIN_SM_STATE OFFSET(0) NUMBITS(9) [], + ], +]; + +// End generated register constants for entropy_src diff --git a/chips/lowrisc/src/registers/flash_ctrl_regs.rs b/chips/lowrisc/src/registers/flash_ctrl_regs.rs new file mode 100644 index 0000000000..e45c8d22ed --- /dev/null +++ b/chips/lowrisc/src/registers/flash_ctrl_regs.rs @@ -0,0 +1,429 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for flash_ctrl. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/flash_ctrl/data/flash_ctrl.hjson +use kernel::utilities::registers::ReadOnly; +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::WriteOnly; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of flash banks +pub const FLASH_CTRL_PARAM_REG_NUM_BANKS: u32 = 2; +/// Number of pages per bank +pub const FLASH_CTRL_PARAM_REG_PAGES_PER_BANK: u32 = 256; +/// Program resolution window in bytes +pub const FLASH_CTRL_PARAM_REG_BUS_PGM_RES_BYTES: u32 = 64; +/// Number of bits needed to represent the pages within a bank +pub const FLASH_CTRL_PARAM_REG_PAGE_WIDTH: u32 = 8; +/// Number of bits needed to represent the number of banks +pub const FLASH_CTRL_PARAM_REG_BANK_WIDTH: u32 = 1; +/// Number of configurable flash regions +pub const FLASH_CTRL_PARAM_NUM_REGIONS: u32 = 8; +/// Number of info partition types +pub const FLASH_CTRL_PARAM_NUM_INFO_TYPES: u32 = 3; +/// Number of configurable flash info pages for info type 0 +pub const FLASH_CTRL_PARAM_NUM_INFOS0: u32 = 10; +/// Number of configurable flash info pages for info type 1 +pub const FLASH_CTRL_PARAM_NUM_INFOS1: u32 = 1; +/// Number of configurable flash info pages for info type 2 +pub const FLASH_CTRL_PARAM_NUM_INFOS2: u32 = 2; +/// Number of words per page +pub const FLASH_CTRL_PARAM_WORDS_PER_PAGE: u32 = 256; +/// Number of bytes per word +pub const FLASH_CTRL_PARAM_BYTES_PER_WORD: u32 = 8; +/// Number of bytes per page +pub const FLASH_CTRL_PARAM_BYTES_PER_PAGE: u32 = 2048; +/// Number of bytes per bank +pub const FLASH_CTRL_PARAM_BYTES_PER_BANK: u32 = 524288; +/// Maximum depth for read / program fifos +pub const FLASH_CTRL_PARAM_MAX_FIFO_DEPTH: u32 = 16; +/// Maximum depth for read / program fifos +pub const FLASH_CTRL_PARAM_MAX_FIFO_WIDTH: u32 = 5; +/// Number of alerts +pub const FLASH_CTRL_PARAM_NUM_ALERTS: u32 = 5; +/// Register width +pub const FLASH_CTRL_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub FlashCtrlRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// Disable flash functionality + (0x0010 => pub(crate) dis: ReadWrite), + /// Controls whether flash can be used for code execution fetches + (0x0014 => pub(crate) exec: ReadWrite), + /// Controller init register + (0x0018 => pub(crate) init: ReadWrite), + /// Controls the configurability of the !!CONTROL register. + (0x001c => pub(crate) ctrl_regwen: ReadWrite), + /// Control register + (0x0020 => pub(crate) control: ReadWrite), + /// Address for flash operation + (0x0024 => pub(crate) addr: ReadWrite), + /// Enable different program types + (0x0028 => pub(crate) prog_type_en: ReadWrite), + /// Suspend erase + (0x002c => pub(crate) erase_suspend: ReadWrite), + /// Memory region registers configuration enable. + (0x0030 => pub(crate) region_cfg_regwen: [ReadWrite; 8]), + /// Memory property configuration for data partition + (0x0050 => pub(crate) mp_region_cfg: [ReadWrite; 8]), + /// Memory base and size configuration for data partition + (0x0070 => pub(crate) mp_region: [ReadWrite; 8]), + /// Default region properties + (0x0090 => pub(crate) default_region: ReadWrite), + /// Memory region registers configuration enable. + (0x0094 => pub(crate) bank0_info0_regwen: [ReadWrite; 10]), + /// Memory property configuration for info partition in bank0, + (0x00bc => pub(crate) bank0_info0_page_cfg: [ReadWrite; 10]), + /// Memory region registers configuration enable. + (0x00e4 => pub(crate) bank0_info1_regwen: [ReadWrite; 1]), + /// Memory property configuration for info partition in bank0, + (0x00e8 => pub(crate) bank0_info1_page_cfg: [ReadWrite; 1]), + /// Memory region registers configuration enable. + (0x00ec => pub(crate) bank0_info2_regwen: [ReadWrite; 2]), + /// Memory property configuration for info partition in bank0, + (0x00f4 => pub(crate) bank0_info2_page_cfg: [ReadWrite; 2]), + /// Memory region registers configuration enable. + (0x00fc => pub(crate) bank1_info0_regwen: [ReadWrite; 10]), + /// Memory property configuration for info partition in bank1, + (0x0124 => pub(crate) bank1_info0_page_cfg: [ReadWrite; 10]), + /// Memory region registers configuration enable. + (0x014c => pub(crate) bank1_info1_regwen: [ReadWrite; 1]), + /// Memory property configuration for info partition in bank1, + (0x0150 => pub(crate) bank1_info1_page_cfg: [ReadWrite; 1]), + /// Memory region registers configuration enable. + (0x0154 => pub(crate) bank1_info2_regwen: [ReadWrite; 2]), + /// Memory property configuration for info partition in bank1, + (0x015c => pub(crate) bank1_info2_page_cfg: [ReadWrite; 2]), + /// HW interface info configuration rule overrides + (0x0164 => pub(crate) hw_info_cfg_override: ReadWrite), + /// Bank configuration registers configuration enable. + (0x0168 => pub(crate) bank_cfg_regwen: ReadWrite), + /// Memory properties bank configuration + (0x016c => pub(crate) mp_bank_cfg_shadowed: [ReadWrite; 1]), + /// Flash Operation Status + (0x0170 => pub(crate) op_status: ReadWrite), + /// Flash Controller Status + (0x0174 => pub(crate) status: ReadWrite), + /// Current flash fsm state + (0x0178 => pub(crate) debug_state: ReadWrite), + /// Flash error code register. + (0x017c => pub(crate) err_code: ReadWrite), + /// This register tabulates standard fault status of the flash. + (0x0180 => pub(crate) std_fault_status: ReadWrite), + /// This register tabulates customized fault status of the flash. + (0x0184 => pub(crate) fault_status: ReadWrite), + /// Synchronous error address + (0x0188 => pub(crate) err_addr: ReadWrite), + /// Total number of single bit ECC error count + (0x018c => pub(crate) ecc_single_err_cnt: [ReadWrite; 1]), + /// Latest address of ECC single err + (0x0190 => pub(crate) ecc_single_err_addr: [ReadWrite; 2]), + /// Phy alert configuration + (0x0198 => pub(crate) phy_alert_cfg: ReadWrite), + /// Flash Phy Status + (0x019c => pub(crate) phy_status: ReadWrite), + /// Flash Controller Scratch + (0x01a0 => pub(crate) scratch: ReadWrite), + /// Programmable depth where FIFOs should generate interrupts + (0x01a4 => pub(crate) fifo_lvl: ReadWrite), + /// Reset for flash controller FIFOs + (0x01a8 => pub(crate) fifo_rst: ReadWrite), + /// Current program and read fifo depth + (0x01ac => pub(crate) curr_fifo_lvl: ReadWrite), + /// Memory area: Flash program FIFO. + (0x01b0 => pub(crate) prog_fifo: [WriteOnly; 1]), + /// Memory area: Flash read FIFO. + (0x01b4 => pub(crate) rd_fifo: [ReadOnly; 1]), + (0x01b8 => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + PROG_EMPTY OFFSET(0) NUMBITS(1) [], + PROG_LVL OFFSET(1) NUMBITS(1) [], + RD_FULL OFFSET(2) NUMBITS(1) [], + RD_LVL OFFSET(3) NUMBITS(1) [], + OP_DONE OFFSET(4) NUMBITS(1) [], + CORR_ERR OFFSET(5) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + RECOV_ERR OFFSET(0) NUMBITS(1) [], + FATAL_STD_ERR OFFSET(1) NUMBITS(1) [], + FATAL_ERR OFFSET(2) NUMBITS(1) [], + FATAL_PRIM_FLASH_ALERT OFFSET(3) NUMBITS(1) [], + RECOV_PRIM_FLASH_ALERT OFFSET(4) NUMBITS(1) [], + ], + pub(crate) DIS [ + VAL OFFSET(0) NUMBITS(4) [], + ], + pub(crate) EXEC [ + EN OFFSET(0) NUMBITS(32) [], + ], + pub(crate) INIT [ + VAL OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CTRL_REGWEN [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CONTROL [ + START OFFSET(0) NUMBITS(1) [], + OP OFFSET(4) NUMBITS(2) [ + READ = 0, + PROG = 1, + ERASE = 2, + ], + PROG_SEL OFFSET(6) NUMBITS(1) [ + NORMAL_PROGRAM = 0, + PROGRAM_REPAIR = 1, + ], + ERASE_SEL OFFSET(7) NUMBITS(1) [ + PAGE_ERASE = 0, + BANK_ERASE = 1, + ], + PARTITION_SEL OFFSET(8) NUMBITS(1) [], + INFO_SEL OFFSET(9) NUMBITS(2) [], + NUM OFFSET(16) NUMBITS(12) [], + ], + pub(crate) ADDR [ + START OFFSET(0) NUMBITS(20) [], + ], + pub(crate) PROG_TYPE_EN [ + NORMAL OFFSET(0) NUMBITS(1) [], + REPAIR OFFSET(1) NUMBITS(1) [], + ], + pub(crate) ERASE_SUSPEND [ + REQ OFFSET(0) NUMBITS(1) [], + ], + pub(crate) REGION_CFG_REGWEN [ + REGION_0 OFFSET(0) NUMBITS(1) [ + REGION_LOCKED = 0, + REGION_ENABLED = 1, + ], + ], + pub(crate) MP_REGION_CFG [ + EN_0 OFFSET(0) NUMBITS(4) [], + RD_EN_0 OFFSET(4) NUMBITS(4) [], + PROG_EN_0 OFFSET(8) NUMBITS(4) [], + ERASE_EN_0 OFFSET(12) NUMBITS(4) [], + SCRAMBLE_EN_0 OFFSET(16) NUMBITS(4) [], + ECC_EN_0 OFFSET(20) NUMBITS(4) [], + HE_EN_0 OFFSET(24) NUMBITS(4) [], + ], + pub(crate) MP_REGION [ + BASE_0 OFFSET(0) NUMBITS(9) [], + SIZE_0 OFFSET(9) NUMBITS(10) [], + ], + pub(crate) DEFAULT_REGION [ + RD_EN OFFSET(0) NUMBITS(4) [], + PROG_EN OFFSET(4) NUMBITS(4) [], + ERASE_EN OFFSET(8) NUMBITS(4) [], + SCRAMBLE_EN OFFSET(12) NUMBITS(4) [], + ECC_EN OFFSET(16) NUMBITS(4) [], + HE_EN OFFSET(20) NUMBITS(4) [], + ], + pub(crate) BANK0_INFO0_REGWEN [ + REGION_0 OFFSET(0) NUMBITS(1) [ + PAGE_LOCKED = 0, + PAGE_ENABLED = 1, + ], + ], + pub(crate) BANK0_INFO0_PAGE_CFG [ + EN_0 OFFSET(0) NUMBITS(4) [], + RD_EN_0 OFFSET(4) NUMBITS(4) [], + PROG_EN_0 OFFSET(8) NUMBITS(4) [], + ERASE_EN_0 OFFSET(12) NUMBITS(4) [], + SCRAMBLE_EN_0 OFFSET(16) NUMBITS(4) [], + ECC_EN_0 OFFSET(20) NUMBITS(4) [], + HE_EN_0 OFFSET(24) NUMBITS(4) [], + ], + pub(crate) BANK0_INFO1_REGWEN [ + REGION_0 OFFSET(0) NUMBITS(1) [ + PAGE_LOCKED = 0, + PAGE_ENABLED = 1, + ], + ], + pub(crate) BANK0_INFO1_PAGE_CFG [ + EN_0 OFFSET(0) NUMBITS(4) [], + RD_EN_0 OFFSET(4) NUMBITS(4) [], + PROG_EN_0 OFFSET(8) NUMBITS(4) [], + ERASE_EN_0 OFFSET(12) NUMBITS(4) [], + SCRAMBLE_EN_0 OFFSET(16) NUMBITS(4) [], + ECC_EN_0 OFFSET(20) NUMBITS(4) [], + HE_EN_0 OFFSET(24) NUMBITS(4) [], + ], + pub(crate) BANK0_INFO2_REGWEN [ + REGION_0 OFFSET(0) NUMBITS(1) [ + PAGE_LOCKED = 0, + PAGE_ENABLED = 1, + ], + ], + pub(crate) BANK0_INFO2_PAGE_CFG [ + EN_0 OFFSET(0) NUMBITS(4) [], + RD_EN_0 OFFSET(4) NUMBITS(4) [], + PROG_EN_0 OFFSET(8) NUMBITS(4) [], + ERASE_EN_0 OFFSET(12) NUMBITS(4) [], + SCRAMBLE_EN_0 OFFSET(16) NUMBITS(4) [], + ECC_EN_0 OFFSET(20) NUMBITS(4) [], + HE_EN_0 OFFSET(24) NUMBITS(4) [], + ], + pub(crate) BANK1_INFO0_REGWEN [ + REGION_0 OFFSET(0) NUMBITS(1) [ + PAGE_LOCKED = 0, + PAGE_ENABLED = 1, + ], + ], + pub(crate) BANK1_INFO0_PAGE_CFG [ + EN_0 OFFSET(0) NUMBITS(4) [], + RD_EN_0 OFFSET(4) NUMBITS(4) [], + PROG_EN_0 OFFSET(8) NUMBITS(4) [], + ERASE_EN_0 OFFSET(12) NUMBITS(4) [], + SCRAMBLE_EN_0 OFFSET(16) NUMBITS(4) [], + ECC_EN_0 OFFSET(20) NUMBITS(4) [], + HE_EN_0 OFFSET(24) NUMBITS(4) [], + ], + pub(crate) BANK1_INFO1_REGWEN [ + REGION_0 OFFSET(0) NUMBITS(1) [ + PAGE_LOCKED = 0, + PAGE_ENABLED = 1, + ], + ], + pub(crate) BANK1_INFO1_PAGE_CFG [ + EN_0 OFFSET(0) NUMBITS(4) [], + RD_EN_0 OFFSET(4) NUMBITS(4) [], + PROG_EN_0 OFFSET(8) NUMBITS(4) [], + ERASE_EN_0 OFFSET(12) NUMBITS(4) [], + SCRAMBLE_EN_0 OFFSET(16) NUMBITS(4) [], + ECC_EN_0 OFFSET(20) NUMBITS(4) [], + HE_EN_0 OFFSET(24) NUMBITS(4) [], + ], + pub(crate) BANK1_INFO2_REGWEN [ + REGION_0 OFFSET(0) NUMBITS(1) [ + PAGE_LOCKED = 0, + PAGE_ENABLED = 1, + ], + ], + pub(crate) BANK1_INFO2_PAGE_CFG [ + EN_0 OFFSET(0) NUMBITS(4) [], + RD_EN_0 OFFSET(4) NUMBITS(4) [], + PROG_EN_0 OFFSET(8) NUMBITS(4) [], + ERASE_EN_0 OFFSET(12) NUMBITS(4) [], + SCRAMBLE_EN_0 OFFSET(16) NUMBITS(4) [], + ECC_EN_0 OFFSET(20) NUMBITS(4) [], + HE_EN_0 OFFSET(24) NUMBITS(4) [], + ], + pub(crate) HW_INFO_CFG_OVERRIDE [ + SCRAMBLE_DIS OFFSET(0) NUMBITS(4) [], + ECC_DIS OFFSET(4) NUMBITS(4) [], + ], + pub(crate) BANK_CFG_REGWEN [ + BANK OFFSET(0) NUMBITS(1) [ + BANK_LOCKED = 0, + BANK_ENABLED = 1, + ], + ], + pub(crate) MP_BANK_CFG_SHADOWED [ + ERASE_EN_0 OFFSET(0) NUMBITS(1) [], + ERASE_EN_1 OFFSET(1) NUMBITS(1) [], + ], + pub(crate) OP_STATUS [ + DONE OFFSET(0) NUMBITS(1) [], + ERR OFFSET(1) NUMBITS(1) [], + ], + pub(crate) STATUS [ + RD_FULL OFFSET(0) NUMBITS(1) [], + RD_EMPTY OFFSET(1) NUMBITS(1) [], + PROG_FULL OFFSET(2) NUMBITS(1) [], + PROG_EMPTY OFFSET(3) NUMBITS(1) [], + INIT_WIP OFFSET(4) NUMBITS(1) [], + INITIALIZED OFFSET(5) NUMBITS(1) [], + ], + pub(crate) DEBUG_STATE [ + LCMGR_STATE OFFSET(0) NUMBITS(11) [], + ], + pub(crate) ERR_CODE [ + OP_ERR OFFSET(0) NUMBITS(1) [], + MP_ERR OFFSET(1) NUMBITS(1) [], + RD_ERR OFFSET(2) NUMBITS(1) [], + PROG_ERR OFFSET(3) NUMBITS(1) [], + PROG_WIN_ERR OFFSET(4) NUMBITS(1) [], + PROG_TYPE_ERR OFFSET(5) NUMBITS(1) [], + UPDATE_ERR OFFSET(6) NUMBITS(1) [], + MACRO_ERR OFFSET(7) NUMBITS(1) [], + ], + pub(crate) STD_FAULT_STATUS [ + REG_INTG_ERR OFFSET(0) NUMBITS(1) [], + PROG_INTG_ERR OFFSET(1) NUMBITS(1) [], + LCMGR_ERR OFFSET(2) NUMBITS(1) [], + LCMGR_INTG_ERR OFFSET(3) NUMBITS(1) [], + ARB_FSM_ERR OFFSET(4) NUMBITS(1) [], + STORAGE_ERR OFFSET(5) NUMBITS(1) [], + PHY_FSM_ERR OFFSET(6) NUMBITS(1) [], + CTRL_CNT_ERR OFFSET(7) NUMBITS(1) [], + FIFO_ERR OFFSET(8) NUMBITS(1) [], + ], + pub(crate) FAULT_STATUS [ + OP_ERR OFFSET(0) NUMBITS(1) [], + MP_ERR OFFSET(1) NUMBITS(1) [], + RD_ERR OFFSET(2) NUMBITS(1) [], + PROG_ERR OFFSET(3) NUMBITS(1) [], + PROG_WIN_ERR OFFSET(4) NUMBITS(1) [], + PROG_TYPE_ERR OFFSET(5) NUMBITS(1) [], + SEED_ERR OFFSET(6) NUMBITS(1) [], + PHY_RELBL_ERR OFFSET(7) NUMBITS(1) [], + PHY_STORAGE_ERR OFFSET(8) NUMBITS(1) [], + SPURIOUS_ACK OFFSET(9) NUMBITS(1) [], + ARB_ERR OFFSET(10) NUMBITS(1) [], + HOST_GNT_ERR OFFSET(11) NUMBITS(1) [], + ], + pub(crate) ERR_ADDR [ + ERR_ADDR OFFSET(0) NUMBITS(20) [], + ], + pub(crate) ECC_SINGLE_ERR_CNT [ + ECC_SINGLE_ERR_CNT_0 OFFSET(0) NUMBITS(8) [], + ECC_SINGLE_ERR_CNT_1 OFFSET(8) NUMBITS(8) [], + ], + pub(crate) ECC_SINGLE_ERR_ADDR [ + ECC_SINGLE_ERR_ADDR_0 OFFSET(0) NUMBITS(20) [], + ], + pub(crate) PHY_ALERT_CFG [ + ALERT_ACK OFFSET(0) NUMBITS(1) [], + ALERT_TRIG OFFSET(1) NUMBITS(1) [], + ], + pub(crate) PHY_STATUS [ + INIT_WIP OFFSET(0) NUMBITS(1) [], + PROG_NORMAL_AVAIL OFFSET(1) NUMBITS(1) [], + PROG_REPAIR_AVAIL OFFSET(2) NUMBITS(1) [], + ], + pub(crate) SCRATCH [ + DATA OFFSET(0) NUMBITS(32) [], + ], + pub(crate) FIFO_LVL [ + PROG OFFSET(0) NUMBITS(5) [], + RD OFFSET(8) NUMBITS(5) [], + ], + pub(crate) FIFO_RST [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CURR_FIFO_LVL [ + PROG OFFSET(0) NUMBITS(5) [], + RD OFFSET(8) NUMBITS(5) [], + ], +]; + +// End generated register constants for flash_ctrl diff --git a/chips/lowrisc/src/registers/gpio_regs.rs b/chips/lowrisc/src/registers/gpio_regs.rs new file mode 100644 index 0000000000..8d4350abbc --- /dev/null +++ b/chips/lowrisc/src/registers/gpio_regs.rs @@ -0,0 +1,157 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for gpio. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/gpio/data/gpio.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of alerts +pub const GPIO_PARAM_NUM_ALERTS: u32 = 1; +/// Register width +pub const GPIO_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub GpioRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// GPIO Input data read value + (0x0010 => pub(crate) data_in: ReadWrite), + /// GPIO direct output data write value + (0x0014 => pub(crate) direct_out: ReadWrite), + /// GPIO write data lower with mask. + (0x0018 => pub(crate) masked_out_lower: ReadWrite), + /// GPIO write data upper with mask. + (0x001c => pub(crate) masked_out_upper: ReadWrite), + /// GPIO Output Enable. + (0x0020 => pub(crate) direct_oe: ReadWrite), + /// GPIO write Output Enable lower with mask. + (0x0024 => pub(crate) masked_oe_lower: ReadWrite), + /// GPIO write Output Enable upper with mask. + (0x0028 => pub(crate) masked_oe_upper: ReadWrite), + /// GPIO interrupt enable for GPIO, rising edge. + (0x002c => pub(crate) intr_ctrl_en_rising: ReadWrite), + /// GPIO interrupt enable for GPIO, falling edge. + (0x0030 => pub(crate) intr_ctrl_en_falling: ReadWrite), + /// GPIO interrupt enable for GPIO, level high. + (0x0034 => pub(crate) intr_ctrl_en_lvlhigh: ReadWrite), + /// GPIO interrupt enable for GPIO, level low. + (0x0038 => pub(crate) intr_ctrl_en_lvllow: ReadWrite), + /// filter enable for GPIO input bits. + (0x003c => pub(crate) ctrl_en_input_filter: ReadWrite), + (0x0040 => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + GPIO_0 OFFSET(0) NUMBITS(1) [], + GPIO_1 OFFSET(1) NUMBITS(1) [], + GPIO_2 OFFSET(2) NUMBITS(1) [], + GPIO_3 OFFSET(3) NUMBITS(1) [], + GPIO_4 OFFSET(4) NUMBITS(1) [], + GPIO_5 OFFSET(5) NUMBITS(1) [], + GPIO_6 OFFSET(6) NUMBITS(1) [], + GPIO_7 OFFSET(7) NUMBITS(1) [], + GPIO_8 OFFSET(8) NUMBITS(1) [], + GPIO_9 OFFSET(9) NUMBITS(1) [], + GPIO_10 OFFSET(10) NUMBITS(1) [], + GPIO_11 OFFSET(11) NUMBITS(1) [], + GPIO_12 OFFSET(12) NUMBITS(1) [], + GPIO_13 OFFSET(13) NUMBITS(1) [], + GPIO_14 OFFSET(14) NUMBITS(1) [], + GPIO_15 OFFSET(15) NUMBITS(1) [], + GPIO_16 OFFSET(16) NUMBITS(1) [], + GPIO_17 OFFSET(17) NUMBITS(1) [], + GPIO_18 OFFSET(18) NUMBITS(1) [], + GPIO_19 OFFSET(19) NUMBITS(1) [], + GPIO_20 OFFSET(20) NUMBITS(1) [], + GPIO_21 OFFSET(21) NUMBITS(1) [], + GPIO_22 OFFSET(22) NUMBITS(1) [], + GPIO_23 OFFSET(23) NUMBITS(1) [], + GPIO_24 OFFSET(24) NUMBITS(1) [], + GPIO_25 OFFSET(25) NUMBITS(1) [], + GPIO_26 OFFSET(26) NUMBITS(1) [], + GPIO_27 OFFSET(27) NUMBITS(1) [], + GPIO_28 OFFSET(28) NUMBITS(1) [], + GPIO_29 OFFSET(29) NUMBITS(1) [], + GPIO_30 OFFSET(30) NUMBITS(1) [], + GPIO_31 OFFSET(31) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + FATAL_FAULT OFFSET(0) NUMBITS(1) [], + ], + pub(crate) DATA_IN [ + DATA_IN OFFSET(0) NUMBITS(32) [], + ], + pub(crate) DIRECT_OUT [ + DIRECT_OUT OFFSET(0) NUMBITS(32) [], + ], + pub(crate) MASKED_OUT_LOWER [ + DATA OFFSET(0) NUMBITS(16) [], + MASK OFFSET(16) NUMBITS(16) [], + ], + pub(crate) MASKED_OUT_UPPER [ + DATA OFFSET(0) NUMBITS(16) [], + MASK OFFSET(16) NUMBITS(16) [], + ], + pub(crate) DIRECT_OE [ + DIRECT_OE_0 OFFSET(0) NUMBITS(1) [], + DIRECT_OE_1 OFFSET(1) NUMBITS(1) [], + DIRECT_OE_2 OFFSET(2) NUMBITS(1) [], + DIRECT_OE_3 OFFSET(3) NUMBITS(1) [], + DIRECT_OE_4 OFFSET(4) NUMBITS(1) [], + DIRECT_OE_5 OFFSET(5) NUMBITS(1) [], + DIRECT_OE_6 OFFSET(6) NUMBITS(1) [], + DIRECT_OE_7 OFFSET(7) NUMBITS(1) [], + DIRECT_OE_8 OFFSET(8) NUMBITS(1) [], + DIRECT_OE_9 OFFSET(9) NUMBITS(1) [], + DIRECT_OE_10 OFFSET(10) NUMBITS(1) [], + DIRECT_OE_11 OFFSET(11) NUMBITS(1) [], + DIRECT_OE_12 OFFSET(12) NUMBITS(1) [], + DIRECT_OE_13 OFFSET(13) NUMBITS(1) [], + DIRECT_OE_14 OFFSET(14) NUMBITS(1) [], + DIRECT_OE_15 OFFSET(15) NUMBITS(1) [], + DIRECT_OE_16 OFFSET(16) NUMBITS(1) [], + DIRECT_OE_17 OFFSET(17) NUMBITS(1) [], + DIRECT_OE_18 OFFSET(18) NUMBITS(1) [], + DIRECT_OE_19 OFFSET(19) NUMBITS(1) [], + DIRECT_OE_20 OFFSET(20) NUMBITS(1) [], + DIRECT_OE_21 OFFSET(21) NUMBITS(1) [], + DIRECT_OE_22 OFFSET(22) NUMBITS(1) [], + DIRECT_OE_23 OFFSET(23) NUMBITS(1) [], + DIRECT_OE_24 OFFSET(24) NUMBITS(1) [], + DIRECT_OE_25 OFFSET(25) NUMBITS(1) [], + DIRECT_OE_26 OFFSET(26) NUMBITS(1) [], + DIRECT_OE_27 OFFSET(27) NUMBITS(1) [], + DIRECT_OE_28 OFFSET(28) NUMBITS(1) [], + DIRECT_OE_29 OFFSET(29) NUMBITS(1) [], + DIRECT_OE_30 OFFSET(30) NUMBITS(1) [], + DIRECT_OE_31 OFFSET(31) NUMBITS(1) [], + ], + pub(crate) MASKED_OE_LOWER [ + DATA OFFSET(0) NUMBITS(16) [], + MASK OFFSET(16) NUMBITS(16) [], + ], + pub(crate) MASKED_OE_UPPER [ + DATA OFFSET(0) NUMBITS(16) [], + MASK OFFSET(16) NUMBITS(16) [], + ], + pub(crate) CTRL_EN_INPUT_FILTER [ + CTRL_EN_INPUT_FILTER OFFSET(0) NUMBITS(32) [], + ], +]; + +// End generated register constants for gpio diff --git a/chips/lowrisc/src/registers/hmac_regs.rs b/chips/lowrisc/src/registers/hmac_regs.rs new file mode 100644 index 0000000000..69fa812240 --- /dev/null +++ b/chips/lowrisc/src/registers/hmac_regs.rs @@ -0,0 +1,104 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for hmac. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/hmac/data/hmac.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::WriteOnly; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of words for digest/ key +pub const HMAC_PARAM_NUM_WORDS: u32 = 8; +/// Number of alerts +pub const HMAC_PARAM_NUM_ALERTS: u32 = 1; +/// Register width +pub const HMAC_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub HmacRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// HMAC Configuration register. + (0x0010 => pub(crate) cfg: ReadWrite), + /// HMAC command register + (0x0014 => pub(crate) cmd: ReadWrite), + /// HMAC Status register + (0x0018 => pub(crate) status: ReadWrite), + /// HMAC Error Code + (0x001c => pub(crate) err_code: ReadWrite), + /// Randomize internal secret registers. + (0x0020 => pub(crate) wipe_secret: ReadWrite), + /// HMAC Secret Key + (0x0024 => pub(crate) key: [ReadWrite; 8]), + /// Digest output. If HMAC is disabled, the register shows result of SHA256 + (0x0044 => pub(crate) digest: [ReadWrite; 8]), + /// Received Message Length calculated by the HMAC in bits [31:0] + (0x0064 => pub(crate) msg_length_lower: ReadWrite), + /// Received Message Length calculated by the HMAC in bits [63:32] + (0x0068 => pub(crate) msg_length_upper: ReadWrite), + (0x006c => _reserved1), + /// Memory area: Message FIFO. Any write to this window will be appended to the FIFO. Only the + /// lower [1:0] bits of the address matter to writes within the window (for correctly dealing + /// with non 32-bit writes) + (0x0800 => pub(crate) msg_fifo: [WriteOnly; 512]), + (0x1000 => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + HMAC_DONE OFFSET(0) NUMBITS(1) [], + FIFO_EMPTY OFFSET(1) NUMBITS(1) [], + HMAC_ERR OFFSET(2) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + FATAL_FAULT OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CFG [ + HMAC_EN OFFSET(0) NUMBITS(1) [], + SHA_EN OFFSET(1) NUMBITS(1) [], + ENDIAN_SWAP OFFSET(2) NUMBITS(1) [], + DIGEST_SWAP OFFSET(3) NUMBITS(1) [], + ], + pub(crate) CMD [ + HASH_START OFFSET(0) NUMBITS(1) [], + HASH_PROCESS OFFSET(1) NUMBITS(1) [], + ], + pub(crate) STATUS [ + FIFO_EMPTY OFFSET(0) NUMBITS(1) [], + FIFO_FULL OFFSET(1) NUMBITS(1) [], + FIFO_DEPTH OFFSET(4) NUMBITS(5) [], + ], + pub(crate) ERR_CODE [ + ERR_CODE OFFSET(0) NUMBITS(32) [], + ], + pub(crate) WIPE_SECRET [ + SECRET OFFSET(0) NUMBITS(32) [], + ], + pub(crate) KEY [ + KEY_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) DIGEST [ + DIGEST_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) MSG_LENGTH_LOWER [ + V OFFSET(0) NUMBITS(32) [], + ], + pub(crate) MSG_LENGTH_UPPER [ + V OFFSET(0) NUMBITS(32) [], + ], +]; + +// End generated register constants for hmac diff --git a/chips/lowrisc/src/registers/i2c_regs.rs b/chips/lowrisc/src/registers/i2c_regs.rs new file mode 100644 index 0000000000..ddaf81775d --- /dev/null +++ b/chips/lowrisc/src/registers/i2c_regs.rs @@ -0,0 +1,202 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for i2c. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/i2c/data/i2c.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Depth of FMT, RX, TX, and ACQ FIFOs +pub const I2C_PARAM_FIFO_DEPTH: u32 = 64; +/// Number of alerts +pub const I2C_PARAM_NUM_ALERTS: u32 = 1; +/// Register width +pub const I2C_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub I2cRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// I2C Control Register + (0x0010 => pub(crate) ctrl: ReadWrite), + /// I2C Live Status Register + (0x0014 => pub(crate) status: ReadWrite), + /// I2C Read Data + (0x0018 => pub(crate) rdata: ReadWrite), + /// I2C Format Data + (0x001c => pub(crate) fdata: ReadWrite), + /// I2C FIFO control register + (0x0020 => pub(crate) fifo_ctrl: ReadWrite), + /// I2C FIFO status register + (0x0024 => pub(crate) fifo_status: ReadWrite), + /// I2C Override Control Register + (0x0028 => pub(crate) ovrd: ReadWrite), + /// Oversampled RX values + (0x002c => pub(crate) val: ReadWrite), + /// Detailed I2C Timings (directly corresponding to table 10 in the I2C Specification). + (0x0030 => pub(crate) timing0: ReadWrite), + /// Detailed I2C Timings (directly corresponding to table 10 in the I2C Specification). + (0x0034 => pub(crate) timing1: ReadWrite), + /// Detailed I2C Timings (directly corresponding to table 10 in the I2C Specification). + (0x0038 => pub(crate) timing2: ReadWrite), + /// Detailed I2C Timings (directly corresponding to table 10, in the I2C Specification). + (0x003c => pub(crate) timing3: ReadWrite), + /// Detailed I2C Timings (directly corresponding to table 10, in the I2C Specification). + (0x0040 => pub(crate) timing4: ReadWrite), + /// I2C clock stretching timeout control + (0x0044 => pub(crate) timeout_ctrl: ReadWrite), + /// I2C target address and mask pairs + (0x0048 => pub(crate) target_id: ReadWrite), + /// I2C target acquired data + (0x004c => pub(crate) acqdata: ReadWrite), + /// I2C target transmit data + (0x0050 => pub(crate) txdata: ReadWrite), + /// I2C host clock generation timeout value (in units of input clock frequency) + (0x0054 => pub(crate) host_timeout_ctrl: ReadWrite), + (0x0058 => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + FMT_THRESHOLD OFFSET(0) NUMBITS(1) [], + RX_THRESHOLD OFFSET(1) NUMBITS(1) [], + FMT_OVERFLOW OFFSET(2) NUMBITS(1) [], + RX_OVERFLOW OFFSET(3) NUMBITS(1) [], + NAK OFFSET(4) NUMBITS(1) [], + SCL_INTERFERENCE OFFSET(5) NUMBITS(1) [], + SDA_INTERFERENCE OFFSET(6) NUMBITS(1) [], + STRETCH_TIMEOUT OFFSET(7) NUMBITS(1) [], + SDA_UNSTABLE OFFSET(8) NUMBITS(1) [], + CMD_COMPLETE OFFSET(9) NUMBITS(1) [], + TX_STRETCH OFFSET(10) NUMBITS(1) [], + TX_OVERFLOW OFFSET(11) NUMBITS(1) [], + ACQ_FULL OFFSET(12) NUMBITS(1) [], + UNEXP_STOP OFFSET(13) NUMBITS(1) [], + HOST_TIMEOUT OFFSET(14) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + FATAL_FAULT OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CTRL [ + ENABLEHOST OFFSET(0) NUMBITS(1) [], + ENABLETARGET OFFSET(1) NUMBITS(1) [], + LLPBK OFFSET(2) NUMBITS(1) [], + ], + pub(crate) STATUS [ + FMTFULL OFFSET(0) NUMBITS(1) [], + RXFULL OFFSET(1) NUMBITS(1) [], + FMTEMPTY OFFSET(2) NUMBITS(1) [], + HOSTIDLE OFFSET(3) NUMBITS(1) [], + TARGETIDLE OFFSET(4) NUMBITS(1) [], + RXEMPTY OFFSET(5) NUMBITS(1) [], + TXFULL OFFSET(6) NUMBITS(1) [], + ACQFULL OFFSET(7) NUMBITS(1) [], + TXEMPTY OFFSET(8) NUMBITS(1) [], + ACQEMPTY OFFSET(9) NUMBITS(1) [], + ], + pub(crate) RDATA [ + RDATA OFFSET(0) NUMBITS(8) [], + ], + pub(crate) FDATA [ + FBYTE OFFSET(0) NUMBITS(8) [], + START OFFSET(8) NUMBITS(1) [], + STOP OFFSET(9) NUMBITS(1) [], + READ OFFSET(10) NUMBITS(1) [], + RCONT OFFSET(11) NUMBITS(1) [], + NAKOK OFFSET(12) NUMBITS(1) [], + ], + pub(crate) FIFO_CTRL [ + RXRST OFFSET(0) NUMBITS(1) [], + FMTRST OFFSET(1) NUMBITS(1) [], + RXILVL OFFSET(2) NUMBITS(3) [ + RXLVL1 = 0, + RXLVL4 = 1, + RXLVL8 = 2, + RXLVL16 = 3, + RXLVL30 = 4, + ], + FMTILVL OFFSET(5) NUMBITS(2) [ + FMTLVL1 = 0, + FMTLVL4 = 1, + FMTLVL8 = 2, + FMTLVL16 = 3, + ], + ACQRST OFFSET(7) NUMBITS(1) [], + TXRST OFFSET(8) NUMBITS(1) [], + ], + pub(crate) FIFO_STATUS [ + FMTLVL OFFSET(0) NUMBITS(7) [], + TXLVL OFFSET(8) NUMBITS(7) [], + RXLVL OFFSET(16) NUMBITS(7) [], + ACQLVL OFFSET(24) NUMBITS(7) [], + ], + pub(crate) OVRD [ + TXOVRDEN OFFSET(0) NUMBITS(1) [], + SCLVAL OFFSET(1) NUMBITS(1) [], + SDAVAL OFFSET(2) NUMBITS(1) [], + ], + pub(crate) VAL [ + SCL_RX OFFSET(0) NUMBITS(16) [], + SDA_RX OFFSET(16) NUMBITS(16) [], + ], + pub(crate) TIMING0 [ + THIGH OFFSET(0) NUMBITS(16) [], + TLOW OFFSET(16) NUMBITS(16) [], + ], + pub(crate) TIMING1 [ + T_R OFFSET(0) NUMBITS(16) [], + T_F OFFSET(16) NUMBITS(16) [], + ], + pub(crate) TIMING2 [ + TSU_STA OFFSET(0) NUMBITS(16) [], + THD_STA OFFSET(16) NUMBITS(16) [], + ], + pub(crate) TIMING3 [ + TSU_DAT OFFSET(0) NUMBITS(16) [], + THD_DAT OFFSET(16) NUMBITS(16) [], + ], + pub(crate) TIMING4 [ + TSU_STO OFFSET(0) NUMBITS(16) [], + T_BUF OFFSET(16) NUMBITS(16) [], + ], + pub(crate) TIMEOUT_CTRL [ + VAL OFFSET(0) NUMBITS(31) [], + EN OFFSET(31) NUMBITS(1) [], + ], + pub(crate) TARGET_ID [ + ADDRESS0 OFFSET(0) NUMBITS(7) [], + MASK0 OFFSET(7) NUMBITS(7) [], + ADDRESS1 OFFSET(14) NUMBITS(7) [], + MASK1 OFFSET(21) NUMBITS(7) [], + ], + pub(crate) ACQDATA [ + ABYTE OFFSET(0) NUMBITS(8) [], + SIGNAL OFFSET(8) NUMBITS(2) [ + NONE = 0, + START = 1, + STOP = 2, + RESTART = 3, + ], + ], + pub(crate) TXDATA [ + TXDATA OFFSET(0) NUMBITS(8) [], + ], + pub(crate) HOST_TIMEOUT_CTRL [ + HOST_TIMEOUT_CTRL OFFSET(0) NUMBITS(32) [], + ], +]; + +// End generated register constants for i2c diff --git a/chips/lowrisc/src/registers/keymgr_regs.rs b/chips/lowrisc/src/registers/keymgr_regs.rs new file mode 100644 index 0000000000..34e8d8a1f9 --- /dev/null +++ b/chips/lowrisc/src/registers/keymgr_regs.rs @@ -0,0 +1,229 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for keymgr. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/keymgr/data/keymgr.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of Registers for SW inputs (Salt) +pub const KEYMGR_PARAM_NUM_SALT_REG: u32 = 8; +/// Number of Registers for SW inputs (SW binding) +pub const KEYMGR_PARAM_NUM_SW_BINDING_REG: u32 = 8; +/// Number of Registers for SW outputs +pub const KEYMGR_PARAM_NUM_OUT_REG: u32 = 8; +/// Number of Registers for key version +pub const KEYMGR_PARAM_NUM_KEY_VERSION: u32 = 1; +/// Number of alerts +pub const KEYMGR_PARAM_NUM_ALERTS: u32 = 2; +/// Register width +pub const KEYMGR_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub KeymgrRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// Key manager configuration enable + (0x0010 => pub(crate) cfg_regwen: ReadWrite), + /// Key manager operation start + (0x0014 => pub(crate) start: ReadWrite), + /// Key manager operation controls + (0x0018 => pub(crate) control_shadowed: ReadWrite), + /// sideload key slots clear + (0x001c => pub(crate) sideload_clear: ReadWrite), + /// regwen for reseed interval + (0x0020 => pub(crate) reseed_interval_regwen: ReadWrite), + /// Reseed interval for key manager entropy reseed + (0x0024 => pub(crate) reseed_interval_shadowed: ReadWrite), + /// Register write enable for SOFTWARE_BINDING + (0x0028 => pub(crate) sw_binding_regwen: ReadWrite), + /// Software binding input to sealing portion of the key manager. + (0x002c => pub(crate) sealing_sw_binding: [ReadWrite; 8]), + /// Software binding input to the attestation portion of the key manager. + (0x004c => pub(crate) attest_sw_binding: [ReadWrite; 8]), + /// Salt value used as part of output generation + (0x006c => pub(crate) salt: [ReadWrite; 8]), + /// Version used as part of output generation + (0x008c => pub(crate) key_version: [ReadWrite; 1]), + /// Register write enable for MAX_CREATOR_KEY_VERSION + (0x0090 => pub(crate) max_creator_key_ver_regwen: ReadWrite), + /// Max creator key version + (0x0094 => pub(crate) max_creator_key_ver_shadowed: ReadWrite), + /// Register write enable for MAX_OWNER_INT_KEY_VERSION + (0x0098 => pub(crate) max_owner_int_key_ver_regwen: ReadWrite), + /// Max owner intermediate key version + (0x009c => pub(crate) max_owner_int_key_ver_shadowed: ReadWrite), + /// Register write enable for MAX_OWNER_KEY_VERSION + (0x00a0 => pub(crate) max_owner_key_ver_regwen: ReadWrite), + /// Max owner key version + (0x00a4 => pub(crate) max_owner_key_ver_shadowed: ReadWrite), + /// Key manager software output. + (0x00a8 => pub(crate) sw_share0_output: [ReadWrite; 8]), + /// Key manager software output. + (0x00c8 => pub(crate) sw_share1_output: [ReadWrite; 8]), + /// Key manager working state. + (0x00e8 => pub(crate) working_state: ReadWrite), + /// Key manager status. + (0x00ec => pub(crate) op_status: ReadWrite), + /// Key manager error code. + (0x00f0 => pub(crate) err_code: ReadWrite), + /// This register represents both synchronous and asynchronous fatal faults. + (0x00f4 => pub(crate) fault_status: ReadWrite), + /// The register holds some debug information that may be convenient if keymgr + (0x00f8 => pub(crate) debug: ReadWrite), + (0x00fc => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + OP_DONE OFFSET(0) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + RECOV_OPERATION_ERR OFFSET(0) NUMBITS(1) [], + FATAL_FAULT_ERR OFFSET(1) NUMBITS(1) [], + ], + pub(crate) CFG_REGWEN [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) START [ + EN OFFSET(0) NUMBITS(1) [ + VALID_STATE = 1, + ], + ], + pub(crate) CONTROL_SHADOWED [ + OPERATION OFFSET(4) NUMBITS(3) [ + ADVANCE = 0, + GENERATE_ID = 1, + GENERATE_SW_OUTPUT = 2, + GENERATE_HW_OUTPUT = 3, + DISABLE = 4, + ], + CDI_SEL OFFSET(7) NUMBITS(1) [ + SEALING_CDI = 0, + ATTESTATION_CDI = 1, + ], + DEST_SEL OFFSET(12) NUMBITS(2) [ + NONE = 0, + AES = 1, + KMAC = 2, + OTBN = 3, + ], + ], + pub(crate) SIDELOAD_CLEAR [ + VAL OFFSET(0) NUMBITS(3) [ + NONE = 0, + AES = 1, + KMAC = 2, + OTBN = 3, + ], + ], + pub(crate) RESEED_INTERVAL_REGWEN [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) RESEED_INTERVAL_SHADOWED [ + VAL OFFSET(0) NUMBITS(16) [], + ], + pub(crate) SW_BINDING_REGWEN [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) SEALING_SW_BINDING [ + VAL_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) ATTEST_SW_BINDING [ + VAL_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) SALT [ + VAL_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) KEY_VERSION [ + VAL_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) MAX_CREATOR_KEY_VER_REGWEN [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) MAX_CREATOR_KEY_VER_SHADOWED [ + VAL OFFSET(0) NUMBITS(32) [], + ], + pub(crate) MAX_OWNER_INT_KEY_VER_REGWEN [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) MAX_OWNER_INT_KEY_VER_SHADOWED [ + VAL OFFSET(0) NUMBITS(32) [], + ], + pub(crate) MAX_OWNER_KEY_VER_REGWEN [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) MAX_OWNER_KEY_VER_SHADOWED [ + VAL OFFSET(0) NUMBITS(32) [], + ], + pub(crate) SW_SHARE0_OUTPUT [ + VAL_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) SW_SHARE1_OUTPUT [ + VAL_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) WORKING_STATE [ + STATE OFFSET(0) NUMBITS(3) [ + RESET = 0, + INIT = 1, + CREATOR_ROOT_KEY = 2, + OWNER_INTERMEDIATE_KEY = 3, + OWNER_KEY = 4, + DISABLED = 5, + INVALID = 6, + ], + ], + pub(crate) OP_STATUS [ + STATUS OFFSET(0) NUMBITS(2) [ + IDLE = 0, + WIP = 1, + DONE_SUCCESS = 2, + DONE_ERROR = 3, + ], + ], + pub(crate) ERR_CODE [ + INVALID_OP OFFSET(0) NUMBITS(1) [], + INVALID_KMAC_INPUT OFFSET(1) NUMBITS(1) [], + INVALID_SHADOW_UPDATE OFFSET(2) NUMBITS(1) [], + ], + pub(crate) FAULT_STATUS [ + CMD OFFSET(0) NUMBITS(1) [], + KMAC_FSM OFFSET(1) NUMBITS(1) [], + KMAC_DONE OFFSET(2) NUMBITS(1) [], + KMAC_OP OFFSET(3) NUMBITS(1) [], + KMAC_OUT OFFSET(4) NUMBITS(1) [], + REGFILE_INTG OFFSET(5) NUMBITS(1) [], + SHADOW OFFSET(6) NUMBITS(1) [], + CTRL_FSM_INTG OFFSET(7) NUMBITS(1) [], + CTRL_FSM_CHK OFFSET(8) NUMBITS(1) [], + CTRL_FSM_CNT OFFSET(9) NUMBITS(1) [], + RESEED_CNT OFFSET(10) NUMBITS(1) [], + SIDE_CTRL_FSM OFFSET(11) NUMBITS(1) [], + SIDE_CTRL_SEL OFFSET(12) NUMBITS(1) [], + KEY_ECC OFFSET(13) NUMBITS(1) [], + ], + pub(crate) DEBUG [ + INVALID_CREATOR_SEED OFFSET(0) NUMBITS(1) [], + INVALID_OWNER_SEED OFFSET(1) NUMBITS(1) [], + INVALID_DEV_ID OFFSET(2) NUMBITS(1) [], + INVALID_HEALTH_STATE OFFSET(3) NUMBITS(1) [], + INVALID_KEY_VERSION OFFSET(4) NUMBITS(1) [], + INVALID_KEY OFFSET(5) NUMBITS(1) [], + INVALID_DIGEST OFFSET(6) NUMBITS(1) [], + ], +]; + +// End generated register constants for keymgr diff --git a/chips/lowrisc/src/registers/kmac_regs.rs b/chips/lowrisc/src/registers/kmac_regs.rs new file mode 100644 index 0000000000..3ca21104cc --- /dev/null +++ b/chips/lowrisc/src/registers/kmac_regs.rs @@ -0,0 +1,175 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for kmac. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/kmac/data/kmac.hjson +use kernel::utilities::registers::ReadOnly; +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::WriteOnly; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of words for the secret key +pub const KMAC_PARAM_NUM_WORDS_KEY: u32 = 16; +/// Number of words for Encoded NsPrefix. +pub const KMAC_PARAM_NUM_WORDS_PREFIX: u32 = 11; +/// Number of entries in the message FIFO. Must match kmac_pkg::MsgFifoDepth. +pub const KMAC_PARAM_NUM_ENTRIES_MSG_FIFO: u32 = 10; +/// Number of bytes in a single entry of the message FIFO. Must match kmac_pkg::MsgWidth. +pub const KMAC_PARAM_NUM_BYTES_MSG_FIFO_ENTRY: u32 = 8; +/// Number of words for the LFSR seed used for entropy generation +pub const KMAC_PARAM_NUM_SEEDS_ENTROPY_LFSR: u32 = 5; +/// Number of alerts +pub const KMAC_PARAM_NUM_ALERTS: u32 = 2; +/// Register width +pub const KMAC_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub KmacRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// Controls the configurability of !!CFG_SHADOWED register. + (0x0010 => pub(crate) cfg_regwen: ReadWrite), + /// KMAC Configuration register. + (0x0014 => pub(crate) cfg_shadowed: ReadWrite), + /// KMAC/ SHA3 command register. + (0x0018 => pub(crate) cmd: ReadWrite), + /// KMAC/SHA3 Status register. + (0x001c => pub(crate) status: ReadWrite), + /// Entropy Timer Periods. + (0x0020 => pub(crate) entropy_period: ReadWrite), + /// Entropy Refresh Counter + (0x0024 => pub(crate) entropy_refresh_hash_cnt: ReadWrite), + /// Entropy Refresh Threshold + (0x0028 => pub(crate) entropy_refresh_threshold_shadowed: ReadWrite), + /// Entropy Seed + (0x002c => pub(crate) entropy_seed: [ReadWrite; 5]), + /// KMAC Secret Key + (0x0040 => pub(crate) key_share0: [ReadWrite; 16]), + /// KMAC Secret Key, 2nd share. + (0x0080 => pub(crate) key_share1: [ReadWrite; 16]), + /// Secret Key length in bit. + (0x00c0 => pub(crate) key_len: ReadWrite), + /// cSHAKE Prefix register. + (0x00c4 => pub(crate) prefix: [ReadWrite; 11]), + /// KMAC/SHA3 Error Code + (0x00f0 => pub(crate) err_code: ReadWrite), + (0x00f4 => _reserved1), + /// Memory area: Keccak State (1600 bit) memory. + (0x0400 => pub(crate) state: [ReadOnly; 128]), + (0x0600 => _reserved2), + /// Memory area: Message FIFO. + (0x0800 => pub(crate) msg_fifo: [WriteOnly; 512]), + (0x1000 => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + KMAC_DONE OFFSET(0) NUMBITS(1) [], + FIFO_EMPTY OFFSET(1) NUMBITS(1) [], + KMAC_ERR OFFSET(2) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + RECOV_OPERATION_ERR OFFSET(0) NUMBITS(1) [], + FATAL_FAULT_ERR OFFSET(1) NUMBITS(1) [], + ], + pub(crate) CFG_REGWEN [ + EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CFG_SHADOWED [ + KMAC_EN OFFSET(0) NUMBITS(1) [], + KSTRENGTH OFFSET(1) NUMBITS(3) [ + L128 = 0, + L224 = 1, + L256 = 2, + L384 = 3, + L512 = 4, + ], + MODE OFFSET(4) NUMBITS(2) [ + SHA3 = 0, + SHAKE = 2, + CSHAKE = 3, + ], + MSG_ENDIANNESS OFFSET(8) NUMBITS(1) [], + STATE_ENDIANNESS OFFSET(9) NUMBITS(1) [], + SIDELOAD OFFSET(12) NUMBITS(1) [], + ENTROPY_MODE OFFSET(16) NUMBITS(2) [ + IDLE_MODE = 0, + EDN_MODE = 1, + SW_MODE = 2, + ], + ENTROPY_FAST_PROCESS OFFSET(19) NUMBITS(1) [], + MSG_MASK OFFSET(20) NUMBITS(1) [], + ENTROPY_READY OFFSET(24) NUMBITS(1) [], + ERR_PROCESSED OFFSET(25) NUMBITS(1) [], + EN_UNSUPPORTED_MODESTRENGTH OFFSET(26) NUMBITS(1) [], + ], + pub(crate) CMD [ + CMD OFFSET(0) NUMBITS(6) [ + START = 29, + PROCESS = 46, + RUN = 49, + DONE = 22, + ], + ENTROPY_REQ OFFSET(8) NUMBITS(1) [], + HASH_CNT_CLR OFFSET(9) NUMBITS(1) [], + ], + pub(crate) STATUS [ + SHA3_IDLE OFFSET(0) NUMBITS(1) [], + SHA3_ABSORB OFFSET(1) NUMBITS(1) [], + SHA3_SQUEEZE OFFSET(2) NUMBITS(1) [], + FIFO_DEPTH OFFSET(8) NUMBITS(5) [], + FIFO_EMPTY OFFSET(14) NUMBITS(1) [], + FIFO_FULL OFFSET(15) NUMBITS(1) [], + ALERT_FATAL_FAULT OFFSET(16) NUMBITS(1) [], + ALERT_RECOV_CTRL_UPDATE_ERR OFFSET(17) NUMBITS(1) [], + ], + pub(crate) ENTROPY_PERIOD [ + PRESCALER OFFSET(0) NUMBITS(10) [], + WAIT_TIMER OFFSET(16) NUMBITS(16) [], + ], + pub(crate) ENTROPY_REFRESH_HASH_CNT [ + HASH_CNT OFFSET(0) NUMBITS(10) [], + ], + pub(crate) ENTROPY_REFRESH_THRESHOLD_SHADOWED [ + THRESHOLD OFFSET(0) NUMBITS(10) [], + ], + pub(crate) ENTROPY_SEED [ + SEED_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) KEY_SHARE0 [ + KEY_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) KEY_SHARE1 [ + KEY_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) KEY_LEN [ + LEN OFFSET(0) NUMBITS(3) [ + KEY128 = 0, + KEY192 = 1, + KEY256 = 2, + KEY384 = 3, + KEY512 = 4, + ], + ], + pub(crate) PREFIX [ + PREFIX_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) ERR_CODE [ + ERR_CODE OFFSET(0) NUMBITS(32) [], + ], +]; + +// End generated register constants for kmac diff --git a/chips/lowrisc/src/registers/lc_ctrl_regs.rs b/chips/lowrisc/src/registers/lc_ctrl_regs.rs new file mode 100644 index 0000000000..4096a20d26 --- /dev/null +++ b/chips/lowrisc/src/registers/lc_ctrl_regs.rs @@ -0,0 +1,207 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for lc_ctrl. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/lc_ctrl/data/lc_ctrl.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Width of SiliconCreatorId revision field. +pub const LC_CTRL_PARAM_SILICON_CREATOR_ID_WIDTH: u32 = 16; +/// Width of ProductId revision field. +pub const LC_CTRL_PARAM_PRODUCT_ID_WIDTH: u32 = 16; +/// Width of RevisionId revision field. +pub const LC_CTRL_PARAM_REVISION_ID_WIDTH: u32 = 8; +/// Number of 32bit words in a token. +pub const LC_CTRL_PARAM_NUM_TOKEN_WORDS: u32 = 4; +/// Number of life cycle state enum bits. +pub const LC_CTRL_PARAM_CSR_LC_STATE_WIDTH: u32 = 30; +/// Number of life cycle transition counter bits. +pub const LC_CTRL_PARAM_CSR_LC_COUNT_WIDTH: u32 = 5; +/// Number of life cycle id state enum bits. +pub const LC_CTRL_PARAM_CSR_LC_ID_STATE_WIDTH: u32 = 32; +/// Number of vendor/test-specific OTP control bits. +pub const LC_CTRL_PARAM_CSR_OTP_TEST_CTRL_WIDTH: u32 = 32; +/// Number of vendor/test-specific OTP status bits. +pub const LC_CTRL_PARAM_CSR_OTP_TEST_STATUS_WIDTH: u32 = 32; +/// Number of 32bit words in the Device ID. +pub const LC_CTRL_PARAM_NUM_DEVICE_ID_WORDS: u32 = 8; +/// Number of 32bit words in the manufacturing state. +pub const LC_CTRL_PARAM_NUM_MANUF_STATE_WORDS: u32 = 8; +/// Number of alerts +pub const LC_CTRL_PARAM_NUM_ALERTS: u32 = 3; +/// Register width +pub const LC_CTRL_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub LcCtrlRegisters { + /// Alert Test Register + (0x0000 => pub(crate) alert_test: ReadWrite), + /// life cycle status register. Note that all errors are terminal and require a reset cycle. + (0x0004 => pub(crate) status: ReadWrite), + /// Register write enable for the hardware mutex register. + (0x0008 => pub(crate) claim_transition_if_regwen: ReadWrite), + /// Hardware mutex to claim exclusive access to the transition interface. + (0x000c => pub(crate) claim_transition_if: ReadWrite), + /// Register write enable for all transition interface registers. + (0x0010 => pub(crate) transition_regwen: ReadWrite), + /// Command register for state transition requests. + (0x0014 => pub(crate) transition_cmd: ReadWrite), + /// Control register for state transition requests. + (0x0018 => pub(crate) transition_ctrl: ReadWrite), + /// 128bit token for conditional transitions. + (0x001c => pub(crate) transition_token: [ReadWrite; 4]), + /// This register exposes the decoded life cycle state. + (0x002c => pub(crate) transition_target: ReadWrite), + /// Test/vendor-specific settings for the OTP macro wrapper. + (0x0030 => pub(crate) otp_vendor_test_ctrl: ReadWrite), + /// Test/vendor-specific settings for the OTP macro wrapper. + (0x0034 => pub(crate) otp_vendor_test_status: ReadWrite), + /// This register exposes the decoded life cycle state. + (0x0038 => pub(crate) lc_state: ReadWrite), + /// This register exposes the state of the decoded life cycle transition counter. + (0x003c => pub(crate) lc_transition_cnt: ReadWrite), + /// This register exposes the id state of the device. + (0x0040 => pub(crate) lc_id_state: ReadWrite), + /// This register holds the SILICON_CREATOR_ID and the PRODUCT_ID. + (0x0044 => pub(crate) hw_revision0: ReadWrite), + /// This register holds the REVISION_ID. + (0x0048 => pub(crate) hw_revision1: ReadWrite), + /// This is the 256bit DEVICE_ID value that is stored in the HW_CFG partition in OTP. + (0x004c => pub(crate) device_id: [ReadWrite; 8]), + /// This is a 256bit field used for keeping track of the manufacturing state. + (0x006c => pub(crate) manuf_state: [ReadWrite; 8]), + (0x008c => @END), + } +} + +register_bitfields![u32, + pub(crate) ALERT_TEST [ + FATAL_PROG_ERROR OFFSET(0) NUMBITS(1) [], + FATAL_STATE_ERROR OFFSET(1) NUMBITS(1) [], + FATAL_BUS_INTEG_ERROR OFFSET(2) NUMBITS(1) [], + ], + pub(crate) STATUS [ + INITIALIZED OFFSET(0) NUMBITS(1) [], + READY OFFSET(1) NUMBITS(1) [], + EXT_CLOCK_SWITCHED OFFSET(2) NUMBITS(1) [], + TRANSITION_SUCCESSFUL OFFSET(3) NUMBITS(1) [], + TRANSITION_COUNT_ERROR OFFSET(4) NUMBITS(1) [], + TRANSITION_ERROR OFFSET(5) NUMBITS(1) [], + TOKEN_ERROR OFFSET(6) NUMBITS(1) [], + FLASH_RMA_ERROR OFFSET(7) NUMBITS(1) [], + OTP_ERROR OFFSET(8) NUMBITS(1) [], + STATE_ERROR OFFSET(9) NUMBITS(1) [], + BUS_INTEG_ERROR OFFSET(10) NUMBITS(1) [], + OTP_PARTITION_ERROR OFFSET(11) NUMBITS(1) [], + ], + pub(crate) CLAIM_TRANSITION_IF_REGWEN [ + CLAIM_TRANSITION_IF_REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CLAIM_TRANSITION_IF [ + MUTEX OFFSET(0) NUMBITS(8) [], + ], + pub(crate) TRANSITION_REGWEN [ + TRANSITION_REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) TRANSITION_CMD [ + START OFFSET(0) NUMBITS(1) [], + ], + pub(crate) TRANSITION_CTRL [ + EXT_CLOCK_EN OFFSET(0) NUMBITS(1) [], + VOLATILE_RAW_UNLOCK OFFSET(1) NUMBITS(1) [], + ], + pub(crate) TRANSITION_TOKEN [ + TRANSITION_TOKEN_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) TRANSITION_TARGET [ + STATE OFFSET(0) NUMBITS(30) [ + RAW = 0, + TEST_UNLOCKED0 = 34636833, + TEST_LOCKED0 = 69273666, + TEST_UNLOCKED1 = 103910499, + TEST_LOCKED1 = 138547332, + TEST_UNLOCKED2 = 173184165, + TEST_LOCKED2 = 207820998, + TEST_UNLOCKED3 = 242457831, + TEST_LOCKED3 = 277094664, + TEST_UNLOCKED4 = 311731497, + TEST_LOCKED4 = 346368330, + TEST_UNLOCKED5 = 381005163, + TEST_LOCKED5 = 415641996, + TEST_UNLOCKED6 = 450278829, + TEST_LOCKED6 = 484915662, + TEST_UNLOCKED7 = 519552495, + DEV = 554189328, + PROD = 588826161, + PROD_END = 623462994, + RMA = 658099827, + SCRAP = 692736660, + ], + ], + pub(crate) OTP_VENDOR_TEST_CTRL [ + OTP_VENDOR_TEST_CTRL OFFSET(0) NUMBITS(32) [], + ], + pub(crate) OTP_VENDOR_TEST_STATUS [ + OTP_VENDOR_TEST_STATUS OFFSET(0) NUMBITS(32) [], + ], + pub(crate) LC_STATE [ + STATE OFFSET(0) NUMBITS(30) [ + RAW = 0, + TEST_UNLOCKED0 = 34636833, + TEST_LOCKED0 = 69273666, + TEST_UNLOCKED1 = 103910499, + TEST_LOCKED1 = 138547332, + TEST_UNLOCKED2 = 173184165, + TEST_LOCKED2 = 207820998, + TEST_UNLOCKED3 = 242457831, + TEST_LOCKED3 = 277094664, + TEST_UNLOCKED4 = 311731497, + TEST_LOCKED4 = 346368330, + TEST_UNLOCKED5 = 381005163, + TEST_LOCKED5 = 415641996, + TEST_UNLOCKED6 = 450278829, + TEST_LOCKED6 = 484915662, + TEST_UNLOCKED7 = 519552495, + DEV = 554189328, + PROD = 588826161, + PROD_END = 623462994, + RMA = 658099827, + SCRAP = 692736660, + POST_TRANSITION = 727373493, + ESCALATE = 762010326, + INVALID = 796647159, + ], + ], + pub(crate) LC_TRANSITION_CNT [ + CNT OFFSET(0) NUMBITS(5) [], + ], + pub(crate) LC_ID_STATE [ + STATE OFFSET(0) NUMBITS(32) [ + BLANK = 0, + PERSONALIZED = 286331153, + INVALID = 572662306, + ], + ], + pub(crate) HW_REVISION0 [ + PRODUCT_ID OFFSET(0) NUMBITS(16) [], + SILICON_CREATOR_ID OFFSET(16) NUMBITS(16) [], + ], + pub(crate) HW_REVISION1 [ + REVISION_ID OFFSET(0) NUMBITS(8) [], + RESERVED OFFSET(8) NUMBITS(24) [], + ], + pub(crate) DEVICE_ID [ + DEVICE_ID_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) MANUF_STATE [ + MANUF_STATE_0 OFFSET(0) NUMBITS(32) [], + ], +]; + +// End generated register constants for lc_ctrl diff --git a/chips/lowrisc/src/registers/mod.rs b/chips/lowrisc/src/registers/mod.rs new file mode 100644 index 0000000000..120bf8fafd --- /dev/null +++ b/chips/lowrisc/src/registers/mod.rs @@ -0,0 +1,32 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +pub mod adc_ctrl_regs; +pub mod aes_regs; +pub mod aon_timer_regs; +pub mod clkmgr_regs; +pub mod csrng_regs; +pub mod edn_regs; +pub mod entropy_src_regs; +pub mod flash_ctrl_regs; +pub mod gpio_regs; +pub mod hmac_regs; +pub mod i2c_regs; +pub mod keymgr_regs; +pub mod kmac_regs; +pub mod lc_ctrl_regs; +pub mod otbn_regs; +pub mod otp_ctrl_regs; +pub mod pattgen_regs; +pub mod pinmux_regs; +pub mod pwm_regs; +pub mod rom_ctrl_regs; +pub mod rv_core_ibex_regs; +pub mod rv_timer_regs; +pub mod spi_device_regs; +pub mod spi_host_regs; +pub mod sram_ctrl_regs; +pub mod sysrst_ctrl_regs; +pub mod uart_regs; +pub mod usbdev_regs; diff --git a/chips/lowrisc/src/registers/otbn_regs.rs b/chips/lowrisc/src/registers/otbn_regs.rs new file mode 100644 index 0000000000..024eda1079 --- /dev/null +++ b/chips/lowrisc/src/registers/otbn_regs.rs @@ -0,0 +1,107 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for otbn. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/otbn/data/otbn.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of alerts +pub const OTBN_PARAM_NUM_ALERTS: u32 = 2; +/// Register width +pub const OTBN_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub OtbnRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// Command Register + (0x0010 => pub(crate) cmd: ReadWrite), + /// Control Register + (0x0014 => pub(crate) ctrl: ReadWrite), + /// Status Register + (0x0018 => pub(crate) status: ReadWrite), + /// Operation Result Register + (0x001c => pub(crate) err_bits: ReadWrite), + /// Fatal Alert Cause Register + (0x0020 => pub(crate) fatal_alert_cause: ReadWrite), + /// Instruction Count Register + (0x0024 => pub(crate) insn_cnt: ReadWrite), + /// A 32-bit CRC checksum of data written to memory + (0x0028 => pub(crate) load_checksum: ReadWrite), + (0x002c => _reserved1), + /// Memory area: Instruction Memory Access + (0x4000 => pub(crate) imem: [ReadWrite; 1024]), + (0x5000 => _reserved2), + /// Memory area: Data Memory Access + (0x8000 => pub(crate) dmem: [ReadWrite; 768]), + (0x8c00 => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + DONE OFFSET(0) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + FATAL OFFSET(0) NUMBITS(1) [], + RECOV OFFSET(1) NUMBITS(1) [], + ], + pub(crate) CMD [ + CMD OFFSET(0) NUMBITS(8) [], + ], + pub(crate) CTRL [ + SOFTWARE_ERRS_FATAL OFFSET(0) NUMBITS(1) [], + ], + pub(crate) STATUS [ + STATUS OFFSET(0) NUMBITS(8) [], + ], + pub(crate) ERR_BITS [ + BAD_DATA_ADDR OFFSET(0) NUMBITS(1) [], + BAD_INSN_ADDR OFFSET(1) NUMBITS(1) [], + CALL_STACK OFFSET(2) NUMBITS(1) [], + ILLEGAL_INSN OFFSET(3) NUMBITS(1) [], + LOOP OFFSET(4) NUMBITS(1) [], + KEY_INVALID OFFSET(5) NUMBITS(1) [], + RND_REP_CHK_FAIL OFFSET(6) NUMBITS(1) [], + RND_FIPS_CHK_FAIL OFFSET(7) NUMBITS(1) [], + IMEM_INTG_VIOLATION OFFSET(16) NUMBITS(1) [], + DMEM_INTG_VIOLATION OFFSET(17) NUMBITS(1) [], + REG_INTG_VIOLATION OFFSET(18) NUMBITS(1) [], + BUS_INTG_VIOLATION OFFSET(19) NUMBITS(1) [], + BAD_INTERNAL_STATE OFFSET(20) NUMBITS(1) [], + ILLEGAL_BUS_ACCESS OFFSET(21) NUMBITS(1) [], + LIFECYCLE_ESCALATION OFFSET(22) NUMBITS(1) [], + FATAL_SOFTWARE OFFSET(23) NUMBITS(1) [], + ], + pub(crate) FATAL_ALERT_CAUSE [ + IMEM_INTG_VIOLATION OFFSET(0) NUMBITS(1) [], + DMEM_INTG_VIOLATION OFFSET(1) NUMBITS(1) [], + REG_INTG_VIOLATION OFFSET(2) NUMBITS(1) [], + BUS_INTG_VIOLATION OFFSET(3) NUMBITS(1) [], + BAD_INTERNAL_STATE OFFSET(4) NUMBITS(1) [], + ILLEGAL_BUS_ACCESS OFFSET(5) NUMBITS(1) [], + LIFECYCLE_ESCALATION OFFSET(6) NUMBITS(1) [], + FATAL_SOFTWARE OFFSET(7) NUMBITS(1) [], + ], + pub(crate) INSN_CNT [ + INSN_CNT OFFSET(0) NUMBITS(32) [], + ], + pub(crate) LOAD_CHECKSUM [ + CHECKSUM OFFSET(0) NUMBITS(32) [], + ], +]; + +// End generated register constants for otbn diff --git a/chips/lowrisc/src/registers/otp_ctrl_regs.rs b/chips/lowrisc/src/registers/otp_ctrl_regs.rs new file mode 100644 index 0000000000..9b2e69581d --- /dev/null +++ b/chips/lowrisc/src/registers/otp_ctrl_regs.rs @@ -0,0 +1,629 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for otp_ctrl. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/otp_ctrl/data/otp_ctrl.hjson +use kernel::utilities::registers::ReadOnly; +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of key slots +pub const OTP_CTRL_PARAM_NUM_SRAM_KEY_REQ_SLOTS: u32 = 3; +/// Width of the OTP byte address. +pub const OTP_CTRL_PARAM_OTP_BYTE_ADDR_WIDTH: u32 = 11; +/// Number of error register entries. +pub const OTP_CTRL_PARAM_NUM_ERROR_ENTRIES: u32 = 10; +/// Number of 32bit words in the DAI. +pub const OTP_CTRL_PARAM_NUM_DAI_WORDS: u32 = 2; +/// Size of the digest fields in 32bit words. +pub const OTP_CTRL_PARAM_NUM_DIGEST_WORDS: u32 = 2; +/// Size of the TL-UL window in 32bit words. Note that the effective partition size is smaller +/// than that. +pub const OTP_CTRL_PARAM_NUM_SW_CFG_WINDOW_WORDS: u32 = 512; +/// Number of partitions +pub const OTP_CTRL_PARAM_NUM_PART: u32 = 8; +/// Offset of the VENDOR_TEST partition +pub const OTP_CTRL_PARAM_VENDOR_TEST_OFFSET: usize = 0; +/// Size of the VENDOR_TEST partition +pub const OTP_CTRL_PARAM_VENDOR_TEST_SIZE: u32 = 64; +/// Offset of SCRATCH +pub const OTP_CTRL_PARAM_SCRATCH_OFFSET: usize = 0; +/// Size of SCRATCH +pub const OTP_CTRL_PARAM_SCRATCH_SIZE: u32 = 56; +/// Offset of VENDOR_TEST_DIGEST +pub const OTP_CTRL_PARAM_VENDOR_TEST_DIGEST_OFFSET: usize = 56; +/// Size of VENDOR_TEST_DIGEST +pub const OTP_CTRL_PARAM_VENDOR_TEST_DIGEST_SIZE: u32 = 8; +/// Offset of the CREATOR_SW_CFG partition +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_OFFSET: usize = 64; +/// Size of the CREATOR_SW_CFG partition +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_SIZE: u32 = 800; +/// Offset of CREATOR_SW_CFG_AST_CFG +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_AST_CFG_OFFSET: usize = 64; +/// Size of CREATOR_SW_CFG_AST_CFG +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_AST_CFG_SIZE: u32 = 156; +/// Offset of CREATOR_SW_CFG_AST_INIT_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_AST_INIT_EN_OFFSET: usize = 220; +/// Size of CREATOR_SW_CFG_AST_INIT_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_AST_INIT_EN_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_ROM_EXT_SKU +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_ROM_EXT_SKU_OFFSET: usize = 224; +/// Size of CREATOR_SW_CFG_ROM_EXT_SKU +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_ROM_EXT_SKU_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_SIGVERIFY_RSA_MOD_EXP_IBEX_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_SIGVERIFY_RSA_MOD_EXP_IBEX_EN_OFFSET: usize = 228; +/// Size of CREATOR_SW_CFG_SIGVERIFY_RSA_MOD_EXP_IBEX_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_SIGVERIFY_RSA_MOD_EXP_IBEX_EN_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_SIGVERIFY_RSA_KEY_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_SIGVERIFY_RSA_KEY_EN_OFFSET: usize = 232; +/// Size of CREATOR_SW_CFG_SIGVERIFY_RSA_KEY_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_SIGVERIFY_RSA_KEY_EN_SIZE: u32 = 8; +/// Offset of CREATOR_SW_CFG_SIGVERIFY_SPX_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_SIGVERIFY_SPX_EN_OFFSET: usize = 240; +/// Size of CREATOR_SW_CFG_SIGVERIFY_SPX_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_SIGVERIFY_SPX_EN_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_SIGVERIFY_SPX_KEY_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_SIGVERIFY_SPX_KEY_EN_OFFSET: usize = 244; +/// Size of CREATOR_SW_CFG_SIGVERIFY_SPX_KEY_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_SIGVERIFY_SPX_KEY_EN_SIZE: u32 = 8; +/// Offset of CREATOR_SW_CFG_FLASH_DATA_DEFAULT_CFG +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_FLASH_DATA_DEFAULT_CFG_OFFSET: usize = 252; +/// Size of CREATOR_SW_CFG_FLASH_DATA_DEFAULT_CFG +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_FLASH_DATA_DEFAULT_CFG_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_FLASH_INFO_BOOT_DATA_CFG +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_FLASH_INFO_BOOT_DATA_CFG_OFFSET: usize = 256; +/// Size of CREATOR_SW_CFG_FLASH_INFO_BOOT_DATA_CFG +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_FLASH_INFO_BOOT_DATA_CFG_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_FLASH_HW_INFO_CFG_OVERRIDE +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_FLASH_HW_INFO_CFG_OVERRIDE_OFFSET: usize = 260; +/// Size of CREATOR_SW_CFG_FLASH_HW_INFO_CFG_OVERRIDE +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_FLASH_HW_INFO_CFG_OVERRIDE_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_RNG_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_EN_OFFSET: usize = 264; +/// Size of CREATOR_SW_CFG_RNG_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_EN_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_JITTER_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_JITTER_EN_OFFSET: usize = 268; +/// Size of CREATOR_SW_CFG_JITTER_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_JITTER_EN_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_RET_RAM_RESET_MASK +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RET_RAM_RESET_MASK_OFFSET: usize = 272; +/// Size of CREATOR_SW_CFG_RET_RAM_RESET_MASK +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RET_RAM_RESET_MASK_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_MANUF_STATE +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_MANUF_STATE_OFFSET: usize = 276; +/// Size of CREATOR_SW_CFG_MANUF_STATE +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_MANUF_STATE_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_ROM_EXEC_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_ROM_EXEC_EN_OFFSET: usize = 280; +/// Size of CREATOR_SW_CFG_ROM_EXEC_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_ROM_EXEC_EN_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_CPUCTRL +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_CPUCTRL_OFFSET: usize = 284; +/// Size of CREATOR_SW_CFG_CPUCTRL +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_CPUCTRL_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_MIN_SEC_VER_ROM_EXT +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_MIN_SEC_VER_ROM_EXT_OFFSET: usize = 288; +/// Size of CREATOR_SW_CFG_MIN_SEC_VER_ROM_EXT +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_MIN_SEC_VER_ROM_EXT_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_MIN_SEC_VER_BL0 +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_MIN_SEC_VER_BL0_OFFSET: usize = 292; +/// Size of CREATOR_SW_CFG_MIN_SEC_VER_BL0 +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_MIN_SEC_VER_BL0_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_DEFAULT_BOOT_DATA_IN_PROD_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_DEFAULT_BOOT_DATA_IN_PROD_EN_OFFSET: usize = 296; +/// Size of CREATOR_SW_CFG_DEFAULT_BOOT_DATA_IN_PROD_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_DEFAULT_BOOT_DATA_IN_PROD_EN_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_RMA_SPIN_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RMA_SPIN_EN_OFFSET: usize = 300; +/// Size of CREATOR_SW_CFG_RMA_SPIN_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RMA_SPIN_EN_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_RMA_SPIN_CYCLES +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RMA_SPIN_CYCLES_OFFSET: usize = 304; +/// Size of CREATOR_SW_CFG_RMA_SPIN_CYCLES +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RMA_SPIN_CYCLES_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_RNG_REPCNT_THRESHOLDS +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_REPCNT_THRESHOLDS_OFFSET: usize = 308; +/// Size of CREATOR_SW_CFG_RNG_REPCNT_THRESHOLDS +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_REPCNT_THRESHOLDS_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_RNG_REPCNTS_THRESHOLDS +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_REPCNTS_THRESHOLDS_OFFSET: usize = 312; +/// Size of CREATOR_SW_CFG_RNG_REPCNTS_THRESHOLDS +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_REPCNTS_THRESHOLDS_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_RNG_ADAPTP_HI_THRESHOLDS +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_ADAPTP_HI_THRESHOLDS_OFFSET: usize = 316; +/// Size of CREATOR_SW_CFG_RNG_ADAPTP_HI_THRESHOLDS +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_ADAPTP_HI_THRESHOLDS_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_RNG_ADAPTP_LO_THRESHOLDS +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_ADAPTP_LO_THRESHOLDS_OFFSET: usize = 320; +/// Size of CREATOR_SW_CFG_RNG_ADAPTP_LO_THRESHOLDS +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_ADAPTP_LO_THRESHOLDS_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_RNG_BUCKET_THRESHOLDS +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_BUCKET_THRESHOLDS_OFFSET: usize = 324; +/// Size of CREATOR_SW_CFG_RNG_BUCKET_THRESHOLDS +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_BUCKET_THRESHOLDS_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_RNG_MARKOV_HI_THRESHOLDS +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_MARKOV_HI_THRESHOLDS_OFFSET: usize = 328; +/// Size of CREATOR_SW_CFG_RNG_MARKOV_HI_THRESHOLDS +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_MARKOV_HI_THRESHOLDS_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_RNG_MARKOV_LO_THRESHOLDS +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_MARKOV_LO_THRESHOLDS_OFFSET: usize = 332; +/// Size of CREATOR_SW_CFG_RNG_MARKOV_LO_THRESHOLDS +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_MARKOV_LO_THRESHOLDS_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_RNG_EXTHT_HI_THRESHOLDS +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_EXTHT_HI_THRESHOLDS_OFFSET: usize = 336; +/// Size of CREATOR_SW_CFG_RNG_EXTHT_HI_THRESHOLDS +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_EXTHT_HI_THRESHOLDS_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_RNG_EXTHT_LO_THRESHOLDS +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_EXTHT_LO_THRESHOLDS_OFFSET: usize = 340; +/// Size of CREATOR_SW_CFG_RNG_EXTHT_LO_THRESHOLDS +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_EXTHT_LO_THRESHOLDS_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_RNG_ALERT_THRESHOLD +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_ALERT_THRESHOLD_OFFSET: usize = 344; +/// Size of CREATOR_SW_CFG_RNG_ALERT_THRESHOLD +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_ALERT_THRESHOLD_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_RNG_HEALTH_CONFIG_DIGEST +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_HEALTH_CONFIG_DIGEST_OFFSET: usize = 348; +/// Size of CREATOR_SW_CFG_RNG_HEALTH_CONFIG_DIGEST +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_RNG_HEALTH_CONFIG_DIGEST_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_SRAM_KEY_RENEW_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_SRAM_KEY_RENEW_EN_OFFSET: usize = 352; +/// Size of CREATOR_SW_CFG_SRAM_KEY_RENEW_EN +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_SRAM_KEY_RENEW_EN_SIZE: u32 = 4; +/// Offset of CREATOR_SW_CFG_DIGEST +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_DIGEST_OFFSET: usize = 856; +/// Size of CREATOR_SW_CFG_DIGEST +pub const OTP_CTRL_PARAM_CREATOR_SW_CFG_DIGEST_SIZE: u32 = 8; +/// Offset of the OWNER_SW_CFG partition +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_OFFSET: usize = 864; +/// Size of the OWNER_SW_CFG partition +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_SIZE: u32 = 800; +/// Offset of OWNER_SW_CFG_ROM_ERROR_REPORTING +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ERROR_REPORTING_OFFSET: usize = 864; +/// Size of OWNER_SW_CFG_ROM_ERROR_REPORTING +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ERROR_REPORTING_SIZE: u32 = 4; +/// Offset of OWNER_SW_CFG_ROM_BOOTSTRAP_DIS +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_BOOTSTRAP_DIS_OFFSET: usize = 868; +/// Size of OWNER_SW_CFG_ROM_BOOTSTRAP_DIS +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_BOOTSTRAP_DIS_SIZE: u32 = 4; +/// Offset of OWNER_SW_CFG_ROM_ALERT_CLASS_EN +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_CLASS_EN_OFFSET: usize = 872; +/// Size of OWNER_SW_CFG_ROM_ALERT_CLASS_EN +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_CLASS_EN_SIZE: u32 = 4; +/// Offset of OWNER_SW_CFG_ROM_ALERT_ESCALATION +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_ESCALATION_OFFSET: usize = 876; +/// Size of OWNER_SW_CFG_ROM_ALERT_ESCALATION +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_ESCALATION_SIZE: u32 = 4; +/// Offset of OWNER_SW_CFG_ROM_ALERT_CLASSIFICATION +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_CLASSIFICATION_OFFSET: usize = 880; +/// Size of OWNER_SW_CFG_ROM_ALERT_CLASSIFICATION +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_CLASSIFICATION_SIZE: u32 = 320; +/// Offset of OWNER_SW_CFG_ROM_LOCAL_ALERT_CLASSIFICATION +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_LOCAL_ALERT_CLASSIFICATION_OFFSET: usize = 1200; +/// Size of OWNER_SW_CFG_ROM_LOCAL_ALERT_CLASSIFICATION +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_LOCAL_ALERT_CLASSIFICATION_SIZE: u32 = 64; +/// Offset of OWNER_SW_CFG_ROM_ALERT_ACCUM_THRESH +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_ACCUM_THRESH_OFFSET: usize = 1264; +/// Size of OWNER_SW_CFG_ROM_ALERT_ACCUM_THRESH +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_ACCUM_THRESH_SIZE: u32 = 16; +/// Offset of OWNER_SW_CFG_ROM_ALERT_TIMEOUT_CYCLES +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_TIMEOUT_CYCLES_OFFSET: usize = 1280; +/// Size of OWNER_SW_CFG_ROM_ALERT_TIMEOUT_CYCLES +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_TIMEOUT_CYCLES_SIZE: u32 = 16; +/// Offset of OWNER_SW_CFG_ROM_ALERT_PHASE_CYCLES +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_PHASE_CYCLES_OFFSET: usize = 1296; +/// Size of OWNER_SW_CFG_ROM_ALERT_PHASE_CYCLES +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_PHASE_CYCLES_SIZE: u32 = 64; +/// Offset of OWNER_SW_CFG_ROM_ALERT_DIGEST_PROD +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_DIGEST_PROD_OFFSET: usize = 1360; +/// Size of OWNER_SW_CFG_ROM_ALERT_DIGEST_PROD +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_DIGEST_PROD_SIZE: u32 = 4; +/// Offset of OWNER_SW_CFG_ROM_ALERT_DIGEST_PROD_END +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_DIGEST_PROD_END_OFFSET: usize = 1364; +/// Size of OWNER_SW_CFG_ROM_ALERT_DIGEST_PROD_END +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_DIGEST_PROD_END_SIZE: u32 = 4; +/// Offset of OWNER_SW_CFG_ROM_ALERT_DIGEST_DEV +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_DIGEST_DEV_OFFSET: usize = 1368; +/// Size of OWNER_SW_CFG_ROM_ALERT_DIGEST_DEV +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_DIGEST_DEV_SIZE: u32 = 4; +/// Offset of OWNER_SW_CFG_ROM_ALERT_DIGEST_RMA +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_DIGEST_RMA_OFFSET: usize = 1372; +/// Size of OWNER_SW_CFG_ROM_ALERT_DIGEST_RMA +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_ALERT_DIGEST_RMA_SIZE: u32 = 4; +/// Offset of OWNER_SW_CFG_ROM_WATCHDOG_BITE_THRESHOLD_CYCLES +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_WATCHDOG_BITE_THRESHOLD_CYCLES_OFFSET: usize = 1376; +/// Size of OWNER_SW_CFG_ROM_WATCHDOG_BITE_THRESHOLD_CYCLES +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_WATCHDOG_BITE_THRESHOLD_CYCLES_SIZE: u32 = 4; +/// Offset of OWNER_SW_CFG_ROM_KEYMGR_ROM_EXT_MEAS_EN +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_KEYMGR_ROM_EXT_MEAS_EN_OFFSET: usize = 1380; +/// Size of OWNER_SW_CFG_ROM_KEYMGR_ROM_EXT_MEAS_EN +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_KEYMGR_ROM_EXT_MEAS_EN_SIZE: u32 = 4; +/// Offset of OWNER_SW_CFG_MANUF_STATE +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_MANUF_STATE_OFFSET: usize = 1384; +/// Size of OWNER_SW_CFG_MANUF_STATE +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_MANUF_STATE_SIZE: u32 = 4; +/// Offset of OWNER_SW_CFG_ROM_RSTMGR_INFO_EN +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_RSTMGR_INFO_EN_OFFSET: usize = 1388; +/// Size of OWNER_SW_CFG_ROM_RSTMGR_INFO_EN +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_ROM_RSTMGR_INFO_EN_SIZE: u32 = 4; +/// Offset of OWNER_SW_CFG_DIGEST +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_DIGEST_OFFSET: usize = 1656; +/// Size of OWNER_SW_CFG_DIGEST +pub const OTP_CTRL_PARAM_OWNER_SW_CFG_DIGEST_SIZE: u32 = 8; +/// Offset of the HW_CFG partition +pub const OTP_CTRL_PARAM_HW_CFG_OFFSET: usize = 1664; +/// Size of the HW_CFG partition +pub const OTP_CTRL_PARAM_HW_CFG_SIZE: u32 = 80; +/// Offset of DEVICE_ID +pub const OTP_CTRL_PARAM_DEVICE_ID_OFFSET: usize = 1664; +/// Size of DEVICE_ID +pub const OTP_CTRL_PARAM_DEVICE_ID_SIZE: u32 = 32; +/// Offset of MANUF_STATE +pub const OTP_CTRL_PARAM_MANUF_STATE_OFFSET: usize = 1696; +/// Size of MANUF_STATE +pub const OTP_CTRL_PARAM_MANUF_STATE_SIZE: u32 = 32; +/// Offset of EN_SRAM_IFETCH +pub const OTP_CTRL_PARAM_EN_SRAM_IFETCH_OFFSET: usize = 1728; +/// Size of EN_SRAM_IFETCH +pub const OTP_CTRL_PARAM_EN_SRAM_IFETCH_SIZE: u32 = 1; +/// Offset of EN_CSRNG_SW_APP_READ +pub const OTP_CTRL_PARAM_EN_CSRNG_SW_APP_READ_OFFSET: usize = 1729; +/// Size of EN_CSRNG_SW_APP_READ +pub const OTP_CTRL_PARAM_EN_CSRNG_SW_APP_READ_SIZE: u32 = 1; +/// Offset of EN_ENTROPY_SRC_FW_READ +pub const OTP_CTRL_PARAM_EN_ENTROPY_SRC_FW_READ_OFFSET: usize = 1730; +/// Size of EN_ENTROPY_SRC_FW_READ +pub const OTP_CTRL_PARAM_EN_ENTROPY_SRC_FW_READ_SIZE: u32 = 1; +/// Offset of EN_ENTROPY_SRC_FW_OVER +pub const OTP_CTRL_PARAM_EN_ENTROPY_SRC_FW_OVER_OFFSET: usize = 1731; +/// Size of EN_ENTROPY_SRC_FW_OVER +pub const OTP_CTRL_PARAM_EN_ENTROPY_SRC_FW_OVER_SIZE: u32 = 1; +/// Offset of HW_CFG_DIGEST +pub const OTP_CTRL_PARAM_HW_CFG_DIGEST_OFFSET: usize = 1736; +/// Size of HW_CFG_DIGEST +pub const OTP_CTRL_PARAM_HW_CFG_DIGEST_SIZE: u32 = 8; +/// Offset of the SECRET0 partition +pub const OTP_CTRL_PARAM_SECRET0_OFFSET: usize = 1744; +/// Size of the SECRET0 partition +pub const OTP_CTRL_PARAM_SECRET0_SIZE: u32 = 40; +/// Offset of TEST_UNLOCK_TOKEN +pub const OTP_CTRL_PARAM_TEST_UNLOCK_TOKEN_OFFSET: usize = 1744; +/// Size of TEST_UNLOCK_TOKEN +pub const OTP_CTRL_PARAM_TEST_UNLOCK_TOKEN_SIZE: u32 = 16; +/// Offset of TEST_EXIT_TOKEN +pub const OTP_CTRL_PARAM_TEST_EXIT_TOKEN_OFFSET: usize = 1760; +/// Size of TEST_EXIT_TOKEN +pub const OTP_CTRL_PARAM_TEST_EXIT_TOKEN_SIZE: u32 = 16; +/// Offset of SECRET0_DIGEST +pub const OTP_CTRL_PARAM_SECRET0_DIGEST_OFFSET: usize = 1776; +/// Size of SECRET0_DIGEST +pub const OTP_CTRL_PARAM_SECRET0_DIGEST_SIZE: u32 = 8; +/// Offset of the SECRET1 partition +pub const OTP_CTRL_PARAM_SECRET1_OFFSET: usize = 1784; +/// Size of the SECRET1 partition +pub const OTP_CTRL_PARAM_SECRET1_SIZE: u32 = 88; +/// Offset of FLASH_ADDR_KEY_SEED +pub const OTP_CTRL_PARAM_FLASH_ADDR_KEY_SEED_OFFSET: usize = 1784; +/// Size of FLASH_ADDR_KEY_SEED +pub const OTP_CTRL_PARAM_FLASH_ADDR_KEY_SEED_SIZE: u32 = 32; +/// Offset of FLASH_DATA_KEY_SEED +pub const OTP_CTRL_PARAM_FLASH_DATA_KEY_SEED_OFFSET: usize = 1816; +/// Size of FLASH_DATA_KEY_SEED +pub const OTP_CTRL_PARAM_FLASH_DATA_KEY_SEED_SIZE: u32 = 32; +/// Offset of SRAM_DATA_KEY_SEED +pub const OTP_CTRL_PARAM_SRAM_DATA_KEY_SEED_OFFSET: usize = 1848; +/// Size of SRAM_DATA_KEY_SEED +pub const OTP_CTRL_PARAM_SRAM_DATA_KEY_SEED_SIZE: u32 = 16; +/// Offset of SECRET1_DIGEST +pub const OTP_CTRL_PARAM_SECRET1_DIGEST_OFFSET: usize = 1864; +/// Size of SECRET1_DIGEST +pub const OTP_CTRL_PARAM_SECRET1_DIGEST_SIZE: u32 = 8; +/// Offset of the SECRET2 partition +pub const OTP_CTRL_PARAM_SECRET2_OFFSET: usize = 1872; +/// Size of the SECRET2 partition +pub const OTP_CTRL_PARAM_SECRET2_SIZE: u32 = 88; +/// Offset of RMA_TOKEN +pub const OTP_CTRL_PARAM_RMA_TOKEN_OFFSET: usize = 1872; +/// Size of RMA_TOKEN +pub const OTP_CTRL_PARAM_RMA_TOKEN_SIZE: u32 = 16; +/// Offset of CREATOR_ROOT_KEY_SHARE0 +pub const OTP_CTRL_PARAM_CREATOR_ROOT_KEY_SHARE0_OFFSET: usize = 1888; +/// Size of CREATOR_ROOT_KEY_SHARE0 +pub const OTP_CTRL_PARAM_CREATOR_ROOT_KEY_SHARE0_SIZE: u32 = 32; +/// Offset of CREATOR_ROOT_KEY_SHARE1 +pub const OTP_CTRL_PARAM_CREATOR_ROOT_KEY_SHARE1_OFFSET: usize = 1920; +/// Size of CREATOR_ROOT_KEY_SHARE1 +pub const OTP_CTRL_PARAM_CREATOR_ROOT_KEY_SHARE1_SIZE: u32 = 32; +/// Offset of SECRET2_DIGEST +pub const OTP_CTRL_PARAM_SECRET2_DIGEST_OFFSET: usize = 1952; +/// Size of SECRET2_DIGEST +pub const OTP_CTRL_PARAM_SECRET2_DIGEST_SIZE: u32 = 8; +/// Offset of the LIFE_CYCLE partition +pub const OTP_CTRL_PARAM_LIFE_CYCLE_OFFSET: usize = 1960; +/// Size of the LIFE_CYCLE partition +pub const OTP_CTRL_PARAM_LIFE_CYCLE_SIZE: u32 = 88; +/// Offset of LC_TRANSITION_CNT +pub const OTP_CTRL_PARAM_LC_TRANSITION_CNT_OFFSET: usize = 1960; +/// Size of LC_TRANSITION_CNT +pub const OTP_CTRL_PARAM_LC_TRANSITION_CNT_SIZE: u32 = 48; +/// Offset of LC_STATE +pub const OTP_CTRL_PARAM_LC_STATE_OFFSET: usize = 2008; +/// Size of LC_STATE +pub const OTP_CTRL_PARAM_LC_STATE_SIZE: u32 = 40; +/// Number of alerts +pub const OTP_CTRL_PARAM_NUM_ALERTS: u32 = 5; +/// Register width +pub const OTP_CTRL_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub OtpCtrlRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// OTP status register. + (0x0010 => pub(crate) status: ReadWrite), + /// This register holds information about error conditions that occurred in the agents + (0x0014 => pub(crate) err_code: [ReadWrite; 1]), + /// Register write enable for all direct access interface registers. + (0x0018 => pub(crate) direct_access_regwen: ReadWrite), + /// Command register for direct accesses. + (0x001c => pub(crate) direct_access_cmd: ReadWrite), + /// Address register for direct accesses. + (0x0020 => pub(crate) direct_access_address: ReadWrite), + /// Write data for direct accesses. + (0x0024 => pub(crate) direct_access_wdata: [ReadWrite; 2]), + /// Read data for direct accesses. + (0x002c => pub(crate) direct_access_rdata: [ReadWrite; 2]), + /// Register write enable for !!CHECK_TRIGGER. + (0x0034 => pub(crate) check_trigger_regwen: ReadWrite), + /// Command register for direct accesses. + (0x0038 => pub(crate) check_trigger: ReadWrite), + /// Register write enable for !!INTEGRITY_CHECK_PERIOD and !!CONSISTENCY_CHECK_PERIOD. + (0x003c => pub(crate) check_regwen: ReadWrite), + /// Timeout value for the integrity and consistency checks. + (0x0040 => pub(crate) check_timeout: ReadWrite), + /// This value specifies the maximum period that can be generated pseudo-randomly. + (0x0044 => pub(crate) integrity_check_period: ReadWrite), + /// This value specifies the maximum period that can be generated pseudo-randomly. + (0x0048 => pub(crate) consistency_check_period: ReadWrite), + /// Runtime read lock for the VENDOR_TEST partition. + (0x004c => pub(crate) vendor_test_read_lock: ReadWrite), + /// Runtime read lock for the CREATOR_SW_CFG partition. + (0x0050 => pub(crate) creator_sw_cfg_read_lock: ReadWrite), + /// Runtime read lock for the OWNER_SW_CFG partition. + (0x0054 => pub(crate) owner_sw_cfg_read_lock: ReadWrite), + /// Integrity digest for the VENDOR_TEST partition. + (0x0058 => pub(crate) vendor_test_digest: [ReadWrite; 2]), + /// Integrity digest for the CREATOR_SW_CFG partition. + (0x0060 => pub(crate) creator_sw_cfg_digest: [ReadWrite; 2]), + /// Integrity digest for the OWNER_SW_CFG partition. + (0x0068 => pub(crate) owner_sw_cfg_digest: [ReadWrite; 2]), + /// Integrity digest for the HW_CFG partition. + (0x0070 => pub(crate) hw_cfg_digest: [ReadWrite; 2]), + /// Integrity digest for the SECRET0 partition. + (0x0078 => pub(crate) secret0_digest: [ReadWrite; 2]), + /// Integrity digest for the SECRET1 partition. + (0x0080 => pub(crate) secret1_digest: [ReadWrite; 2]), + /// Integrity digest for the SECRET2 partition. + (0x0088 => pub(crate) secret2_digest: [ReadWrite; 2]), + (0x0090 => _reserved1), + /// Memory area: Any read to this window directly maps to the corresponding offset in the creator + /// and owner software + (0x1000 => pub(crate) sw_cfg_window: [ReadOnly; 512]), + (0x1800 => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + OTP_OPERATION_DONE OFFSET(0) NUMBITS(1) [], + OTP_ERROR OFFSET(1) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + FATAL_MACRO_ERROR OFFSET(0) NUMBITS(1) [], + FATAL_CHECK_ERROR OFFSET(1) NUMBITS(1) [], + FATAL_BUS_INTEG_ERROR OFFSET(2) NUMBITS(1) [], + FATAL_PRIM_OTP_ALERT OFFSET(3) NUMBITS(1) [], + RECOV_PRIM_OTP_ALERT OFFSET(4) NUMBITS(1) [], + ], + pub(crate) STATUS [ + VENDOR_TEST_ERROR OFFSET(0) NUMBITS(1) [], + CREATOR_SW_CFG_ERROR OFFSET(1) NUMBITS(1) [], + OWNER_SW_CFG_ERROR OFFSET(2) NUMBITS(1) [], + HW_CFG_ERROR OFFSET(3) NUMBITS(1) [], + SECRET0_ERROR OFFSET(4) NUMBITS(1) [], + SECRET1_ERROR OFFSET(5) NUMBITS(1) [], + SECRET2_ERROR OFFSET(6) NUMBITS(1) [], + LIFE_CYCLE_ERROR OFFSET(7) NUMBITS(1) [], + DAI_ERROR OFFSET(8) NUMBITS(1) [], + LCI_ERROR OFFSET(9) NUMBITS(1) [], + TIMEOUT_ERROR OFFSET(10) NUMBITS(1) [], + LFSR_FSM_ERROR OFFSET(11) NUMBITS(1) [], + SCRAMBLING_FSM_ERROR OFFSET(12) NUMBITS(1) [], + KEY_DERIV_FSM_ERROR OFFSET(13) NUMBITS(1) [], + BUS_INTEG_ERROR OFFSET(14) NUMBITS(1) [], + DAI_IDLE OFFSET(15) NUMBITS(1) [], + CHECK_PENDING OFFSET(16) NUMBITS(1) [], + ], + pub(crate) ERR_CODE [ + ERR_CODE_0 OFFSET(0) NUMBITS(3) [ + NO_ERROR = 0, + MACRO_ERROR = 1, + MACRO_ECC_CORR_ERROR = 2, + MACRO_ECC_UNCORR_ERROR = 3, + MACRO_WRITE_BLANK_ERROR = 4, + ACCESS_ERROR = 5, + CHECK_FAIL_ERROR = 6, + FSM_STATE_ERROR = 7, + ], + ERR_CODE_1 OFFSET(3) NUMBITS(3) [ + NO_ERROR = 0, + MACRO_ERROR = 1, + MACRO_ECC_CORR_ERROR = 2, + MACRO_ECC_UNCORR_ERROR = 3, + MACRO_WRITE_BLANK_ERROR = 4, + ACCESS_ERROR = 5, + CHECK_FAIL_ERROR = 6, + FSM_STATE_ERROR = 7, + ], + ERR_CODE_2 OFFSET(6) NUMBITS(3) [ + NO_ERROR = 0, + MACRO_ERROR = 1, + MACRO_ECC_CORR_ERROR = 2, + MACRO_ECC_UNCORR_ERROR = 3, + MACRO_WRITE_BLANK_ERROR = 4, + ACCESS_ERROR = 5, + CHECK_FAIL_ERROR = 6, + FSM_STATE_ERROR = 7, + ], + ERR_CODE_3 OFFSET(9) NUMBITS(3) [ + NO_ERROR = 0, + MACRO_ERROR = 1, + MACRO_ECC_CORR_ERROR = 2, + MACRO_ECC_UNCORR_ERROR = 3, + MACRO_WRITE_BLANK_ERROR = 4, + ACCESS_ERROR = 5, + CHECK_FAIL_ERROR = 6, + FSM_STATE_ERROR = 7, + ], + ERR_CODE_4 OFFSET(12) NUMBITS(3) [ + NO_ERROR = 0, + MACRO_ERROR = 1, + MACRO_ECC_CORR_ERROR = 2, + MACRO_ECC_UNCORR_ERROR = 3, + MACRO_WRITE_BLANK_ERROR = 4, + ACCESS_ERROR = 5, + CHECK_FAIL_ERROR = 6, + FSM_STATE_ERROR = 7, + ], + ERR_CODE_5 OFFSET(15) NUMBITS(3) [ + NO_ERROR = 0, + MACRO_ERROR = 1, + MACRO_ECC_CORR_ERROR = 2, + MACRO_ECC_UNCORR_ERROR = 3, + MACRO_WRITE_BLANK_ERROR = 4, + ACCESS_ERROR = 5, + CHECK_FAIL_ERROR = 6, + FSM_STATE_ERROR = 7, + ], + ERR_CODE_6 OFFSET(18) NUMBITS(3) [ + NO_ERROR = 0, + MACRO_ERROR = 1, + MACRO_ECC_CORR_ERROR = 2, + MACRO_ECC_UNCORR_ERROR = 3, + MACRO_WRITE_BLANK_ERROR = 4, + ACCESS_ERROR = 5, + CHECK_FAIL_ERROR = 6, + FSM_STATE_ERROR = 7, + ], + ERR_CODE_7 OFFSET(21) NUMBITS(3) [ + NO_ERROR = 0, + MACRO_ERROR = 1, + MACRO_ECC_CORR_ERROR = 2, + MACRO_ECC_UNCORR_ERROR = 3, + MACRO_WRITE_BLANK_ERROR = 4, + ACCESS_ERROR = 5, + CHECK_FAIL_ERROR = 6, + FSM_STATE_ERROR = 7, + ], + ERR_CODE_8 OFFSET(24) NUMBITS(3) [ + NO_ERROR = 0, + MACRO_ERROR = 1, + MACRO_ECC_CORR_ERROR = 2, + MACRO_ECC_UNCORR_ERROR = 3, + MACRO_WRITE_BLANK_ERROR = 4, + ACCESS_ERROR = 5, + CHECK_FAIL_ERROR = 6, + FSM_STATE_ERROR = 7, + ], + ERR_CODE_9 OFFSET(27) NUMBITS(3) [ + NO_ERROR = 0, + MACRO_ERROR = 1, + MACRO_ECC_CORR_ERROR = 2, + MACRO_ECC_UNCORR_ERROR = 3, + MACRO_WRITE_BLANK_ERROR = 4, + ACCESS_ERROR = 5, + CHECK_FAIL_ERROR = 6, + FSM_STATE_ERROR = 7, + ], + ], + pub(crate) DIRECT_ACCESS_REGWEN [ + DIRECT_ACCESS_REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) DIRECT_ACCESS_CMD [ + RD OFFSET(0) NUMBITS(1) [], + WR OFFSET(1) NUMBITS(1) [], + DIGEST OFFSET(2) NUMBITS(1) [], + ], + pub(crate) DIRECT_ACCESS_ADDRESS [ + DIRECT_ACCESS_ADDRESS OFFSET(0) NUMBITS(11) [], + ], + pub(crate) DIRECT_ACCESS_WDATA [ + DIRECT_ACCESS_WDATA_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) DIRECT_ACCESS_RDATA [ + DIRECT_ACCESS_RDATA_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CHECK_TRIGGER_REGWEN [ + CHECK_TRIGGER_REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CHECK_TRIGGER [ + INTEGRITY OFFSET(0) NUMBITS(1) [], + CONSISTENCY OFFSET(1) NUMBITS(1) [], + ], + pub(crate) CHECK_REGWEN [ + CHECK_REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CHECK_TIMEOUT [ + CHECK_TIMEOUT OFFSET(0) NUMBITS(32) [], + ], + pub(crate) INTEGRITY_CHECK_PERIOD [ + INTEGRITY_CHECK_PERIOD OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CONSISTENCY_CHECK_PERIOD [ + CONSISTENCY_CHECK_PERIOD OFFSET(0) NUMBITS(32) [], + ], + pub(crate) VENDOR_TEST_READ_LOCK [ + VENDOR_TEST_READ_LOCK OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CREATOR_SW_CFG_READ_LOCK [ + CREATOR_SW_CFG_READ_LOCK OFFSET(0) NUMBITS(1) [], + ], + pub(crate) OWNER_SW_CFG_READ_LOCK [ + OWNER_SW_CFG_READ_LOCK OFFSET(0) NUMBITS(1) [], + ], + pub(crate) VENDOR_TEST_DIGEST [ + VENDOR_TEST_DIGEST_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CREATOR_SW_CFG_DIGEST [ + CREATOR_SW_CFG_DIGEST_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) OWNER_SW_CFG_DIGEST [ + OWNER_SW_CFG_DIGEST_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) HW_CFG_DIGEST [ + HW_CFG_DIGEST_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) SECRET0_DIGEST [ + SECRET0_DIGEST_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) SECRET1_DIGEST [ + SECRET1_DIGEST_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) SECRET2_DIGEST [ + SECRET2_DIGEST_0 OFFSET(0) NUMBITS(32) [], + ], +]; + +// End generated register constants for otp_ctrl diff --git a/chips/lowrisc/src/registers/pattgen_regs.rs b/chips/lowrisc/src/registers/pattgen_regs.rs new file mode 100644 index 0000000000..61399cf9f0 --- /dev/null +++ b/chips/lowrisc/src/registers/pattgen_regs.rs @@ -0,0 +1,82 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for pattgen. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/pattgen/data/pattgen.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of data registers per each channel +pub const PATTGEN_PARAM_NUM_REGS_DATA: u32 = 2; +/// Number of alerts +pub const PATTGEN_PARAM_NUM_ALERTS: u32 = 1; +/// Register width +pub const PATTGEN_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub PattgenRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// PATTGEN control register + (0x0010 => pub(crate) ctrl: ReadWrite), + /// PATTGEN pre-divider register for Channel 0 + (0x0014 => pub(crate) prediv_ch0: ReadWrite), + /// PATTGEN pre-divider register for Channel 1 + (0x0018 => pub(crate) prediv_ch1: ReadWrite), + /// PATTGEN seed pattern multi-registers for Channel 0. + (0x001c => pub(crate) data_ch0: [ReadWrite; 2]), + /// PATTGEN seed pattern multi-registers for Channel 1. + (0x0024 => pub(crate) data_ch1: [ReadWrite; 2]), + /// PATTGEN pattern length + (0x002c => pub(crate) size: ReadWrite), + (0x0030 => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + DONE_CH0 OFFSET(0) NUMBITS(1) [], + DONE_CH1 OFFSET(1) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + FATAL_FAULT OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CTRL [ + ENABLE_CH0 OFFSET(0) NUMBITS(1) [], + ENABLE_CH1 OFFSET(1) NUMBITS(1) [], + POLARITY_CH0 OFFSET(2) NUMBITS(1) [], + POLARITY_CH1 OFFSET(3) NUMBITS(1) [], + ], + pub(crate) PREDIV_CH0 [ + CLK_RATIO OFFSET(0) NUMBITS(32) [], + ], + pub(crate) PREDIV_CH1 [ + CLK_RATIO OFFSET(0) NUMBITS(32) [], + ], + pub(crate) DATA_CH0 [ + DATA_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) DATA_CH1 [ + DATA_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) SIZE [ + LEN_CH0 OFFSET(0) NUMBITS(6) [], + REPS_CH0 OFFSET(6) NUMBITS(10) [], + LEN_CH1 OFFSET(16) NUMBITS(6) [], + REPS_CH1 OFFSET(22) NUMBITS(10) [], + ], +]; + +// End generated register constants for pattgen diff --git a/chips/lowrisc/src/registers/pinmux_regs.rs b/chips/lowrisc/src/registers/pinmux_regs.rs new file mode 100644 index 0000000000..6eef08fef8 --- /dev/null +++ b/chips/lowrisc/src/registers/pinmux_regs.rs @@ -0,0 +1,250 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for pinmux. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/pinmux/data/pinmux.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Pad attribute data width +pub const PINMUX_PARAM_ATTR_DW: u32 = 10; +/// Number of muxed peripheral inputs +pub const PINMUX_PARAM_N_MIO_PERIPH_IN: u32 = 33; +/// Number of muxed peripheral outputs +pub const PINMUX_PARAM_N_MIO_PERIPH_OUT: u32 = 32; +/// Number of muxed IO pads +pub const PINMUX_PARAM_N_MIO_PADS: u32 = 32; +/// Number of dedicated IO pads +pub const PINMUX_PARAM_N_DIO_PADS: u32 = 16; +/// Number of wakeup detectors +pub const PINMUX_PARAM_N_WKUP_DETECT: u32 = 8; +/// Number of wakeup counter bits +pub const PINMUX_PARAM_WKUP_CNT_WIDTH: u32 = 8; +/// Number of alerts +pub const PINMUX_PARAM_NUM_ALERTS: u32 = 1; +/// Register width +pub const PINMUX_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub PinmuxRegisters { + /// Alert Test Register + (0x0000 => pub(crate) alert_test: ReadWrite), + /// Register write enable for MIO peripheral input selects. + (0x0004 => pub(crate) mio_periph_insel_regwen: [ReadWrite; 33]), + /// For each peripheral input, this selects the muxable pad input. + (0x0088 => pub(crate) mio_periph_insel: [ReadWrite; 33]), + /// Register write enable for MIO output selects. + (0x010c => pub(crate) mio_outsel_regwen: [ReadWrite; 32]), + /// For each muxable pad, this selects the peripheral output. + (0x018c => pub(crate) mio_outsel: [ReadWrite; 32]), + /// Register write enable for MIO PAD attributes. + (0x020c => pub(crate) mio_pad_attr_regwen: [ReadWrite; 32]), + /// Muxed pad attributes. + (0x028c => pub(crate) mio_pad_attr: [ReadWrite; 32]), + /// Register write enable for DIO PAD attributes. + (0x030c => pub(crate) dio_pad_attr_regwen: [ReadWrite; 16]), + /// Dedicated pad attributes. + (0x034c => pub(crate) dio_pad_attr: [ReadWrite; 16]), + /// Register indicating whether the corresponding pad is in sleep mode. + (0x038c => pub(crate) mio_pad_sleep_status: [ReadWrite; 1]), + /// Register write enable for MIO sleep value configuration. + (0x0390 => pub(crate) mio_pad_sleep_regwen: [ReadWrite; 32]), + /// Enables the sleep mode of the corresponding muxed pad. + (0x0410 => pub(crate) mio_pad_sleep_en: [ReadWrite; 32]), + /// Defines sleep behavior of the corresponding muxed pad. + (0x0490 => pub(crate) mio_pad_sleep_mode: [ReadWrite; 32]), + /// Register indicating whether the corresponding pad is in sleep mode. + (0x0510 => pub(crate) dio_pad_sleep_status: [ReadWrite; 1]), + /// Register write enable for DIO sleep value configuration. + (0x0514 => pub(crate) dio_pad_sleep_regwen: [ReadWrite; 16]), + /// Enables the sleep mode of the corresponding dedicated pad. + (0x0554 => pub(crate) dio_pad_sleep_en: [ReadWrite; 16]), + /// Defines sleep behavior of the corresponding dedicated pad. + (0x0594 => pub(crate) dio_pad_sleep_mode: [ReadWrite; 16]), + /// Register write enable for wakeup detectors. + (0x05d4 => pub(crate) wkup_detector_regwen: [ReadWrite; 8]), + /// Enables for the wakeup detectors. + (0x05f4 => pub(crate) wkup_detector_en: [ReadWrite; 8]), + /// Configuration of wakeup condition detectors. + (0x0614 => pub(crate) wkup_detector: [ReadWrite; 8]), + /// Counter thresholds for wakeup condition detectors. + (0x0634 => pub(crate) wkup_detector_cnt_th: [ReadWrite; 8]), + /// Pad selects for pad wakeup condition detectors. + (0x0654 => pub(crate) wkup_detector_padsel: [ReadWrite; 8]), + /// Cause registers for wakeup detectors. + (0x0674 => pub(crate) wkup_cause: [ReadWrite; 1]), + (0x0678 => @END), + } +} + +register_bitfields![u32, + pub(crate) ALERT_TEST [ + FATAL_FAULT OFFSET(0) NUMBITS(1) [], + ], + pub(crate) MIO_PERIPH_INSEL_REGWEN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) MIO_PERIPH_INSEL [ + IN_0 OFFSET(0) NUMBITS(6) [], + ], + pub(crate) MIO_OUTSEL_REGWEN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) MIO_OUTSEL [ + OUT_0 OFFSET(0) NUMBITS(6) [], + ], + pub(crate) MIO_PAD_ATTR_REGWEN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) MIO_PAD_ATTR [ + INVERT_0 OFFSET(0) NUMBITS(1) [], + VIRTUAL_OD_EN_0 OFFSET(1) NUMBITS(1) [], + PULL_EN_0 OFFSET(2) NUMBITS(1) [], + PULL_SELECT_0 OFFSET(3) NUMBITS(1) [ + PULL_DOWN = 0, + PULL_UP = 1, + ], + KEEPER_EN_0 OFFSET(4) NUMBITS(1) [], + SCHMITT_EN_0 OFFSET(5) NUMBITS(1) [], + OD_EN_0 OFFSET(6) NUMBITS(1) [], + SLEW_RATE_0 OFFSET(16) NUMBITS(2) [], + DRIVE_STRENGTH_0 OFFSET(20) NUMBITS(4) [], + ], + pub(crate) DIO_PAD_ATTR_REGWEN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) DIO_PAD_ATTR [ + INVERT_0 OFFSET(0) NUMBITS(1) [], + VIRTUAL_OD_EN_0 OFFSET(1) NUMBITS(1) [], + PULL_EN_0 OFFSET(2) NUMBITS(1) [], + PULL_SELECT_0 OFFSET(3) NUMBITS(1) [ + PULL_DOWN = 0, + PULL_UP = 1, + ], + KEEPER_EN_0 OFFSET(4) NUMBITS(1) [], + SCHMITT_EN_0 OFFSET(5) NUMBITS(1) [], + OD_EN_0 OFFSET(6) NUMBITS(1) [], + SLEW_RATE_0 OFFSET(16) NUMBITS(2) [], + DRIVE_STRENGTH_0 OFFSET(20) NUMBITS(4) [], + ], + pub(crate) MIO_PAD_SLEEP_STATUS [ + EN_0 OFFSET(0) NUMBITS(1) [], + EN_1 OFFSET(1) NUMBITS(1) [], + EN_2 OFFSET(2) NUMBITS(1) [], + EN_3 OFFSET(3) NUMBITS(1) [], + EN_4 OFFSET(4) NUMBITS(1) [], + EN_5 OFFSET(5) NUMBITS(1) [], + EN_6 OFFSET(6) NUMBITS(1) [], + EN_7 OFFSET(7) NUMBITS(1) [], + EN_8 OFFSET(8) NUMBITS(1) [], + EN_9 OFFSET(9) NUMBITS(1) [], + EN_10 OFFSET(10) NUMBITS(1) [], + EN_11 OFFSET(11) NUMBITS(1) [], + EN_12 OFFSET(12) NUMBITS(1) [], + EN_13 OFFSET(13) NUMBITS(1) [], + EN_14 OFFSET(14) NUMBITS(1) [], + EN_15 OFFSET(15) NUMBITS(1) [], + EN_16 OFFSET(16) NUMBITS(1) [], + EN_17 OFFSET(17) NUMBITS(1) [], + EN_18 OFFSET(18) NUMBITS(1) [], + EN_19 OFFSET(19) NUMBITS(1) [], + EN_20 OFFSET(20) NUMBITS(1) [], + EN_21 OFFSET(21) NUMBITS(1) [], + EN_22 OFFSET(22) NUMBITS(1) [], + EN_23 OFFSET(23) NUMBITS(1) [], + EN_24 OFFSET(24) NUMBITS(1) [], + EN_25 OFFSET(25) NUMBITS(1) [], + EN_26 OFFSET(26) NUMBITS(1) [], + EN_27 OFFSET(27) NUMBITS(1) [], + EN_28 OFFSET(28) NUMBITS(1) [], + EN_29 OFFSET(29) NUMBITS(1) [], + EN_30 OFFSET(30) NUMBITS(1) [], + EN_31 OFFSET(31) NUMBITS(1) [], + ], + pub(crate) MIO_PAD_SLEEP_REGWEN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) MIO_PAD_SLEEP_EN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) MIO_PAD_SLEEP_MODE [ + OUT_0 OFFSET(0) NUMBITS(2) [ + TIE_LOW = 0, + TIE_HIGH = 1, + HIGH_Z = 2, + KEEP = 3, + ], + ], + pub(crate) DIO_PAD_SLEEP_STATUS [ + EN_0 OFFSET(0) NUMBITS(1) [], + EN_1 OFFSET(1) NUMBITS(1) [], + EN_2 OFFSET(2) NUMBITS(1) [], + EN_3 OFFSET(3) NUMBITS(1) [], + EN_4 OFFSET(4) NUMBITS(1) [], + EN_5 OFFSET(5) NUMBITS(1) [], + EN_6 OFFSET(6) NUMBITS(1) [], + EN_7 OFFSET(7) NUMBITS(1) [], + EN_8 OFFSET(8) NUMBITS(1) [], + EN_9 OFFSET(9) NUMBITS(1) [], + EN_10 OFFSET(10) NUMBITS(1) [], + EN_11 OFFSET(11) NUMBITS(1) [], + EN_12 OFFSET(12) NUMBITS(1) [], + EN_13 OFFSET(13) NUMBITS(1) [], + EN_14 OFFSET(14) NUMBITS(1) [], + EN_15 OFFSET(15) NUMBITS(1) [], + ], + pub(crate) DIO_PAD_SLEEP_REGWEN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) DIO_PAD_SLEEP_EN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) DIO_PAD_SLEEP_MODE [ + OUT_0 OFFSET(0) NUMBITS(2) [ + TIE_LOW = 0, + TIE_HIGH = 1, + HIGH_Z = 2, + KEEP = 3, + ], + ], + pub(crate) WKUP_DETECTOR_REGWEN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) WKUP_DETECTOR_EN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) WKUP_DETECTOR [ + MODE_0 OFFSET(0) NUMBITS(3) [ + POSEDGE = 0, + NEGEDGE = 1, + EDGE = 2, + TIMEDHIGH = 3, + TIMEDLOW = 4, + ], + FILTER_0 OFFSET(3) NUMBITS(1) [], + MIODIO_0 OFFSET(4) NUMBITS(1) [], + ], + pub(crate) WKUP_DETECTOR_CNT_TH [ + TH_0 OFFSET(0) NUMBITS(8) [], + ], + pub(crate) WKUP_DETECTOR_PADSEL [ + SEL_0 OFFSET(0) NUMBITS(6) [], + ], + pub(crate) WKUP_CAUSE [ + CAUSE_0 OFFSET(0) NUMBITS(1) [], + CAUSE_1 OFFSET(1) NUMBITS(1) [], + CAUSE_2 OFFSET(2) NUMBITS(1) [], + CAUSE_3 OFFSET(3) NUMBITS(1) [], + CAUSE_4 OFFSET(4) NUMBITS(1) [], + CAUSE_5 OFFSET(5) NUMBITS(1) [], + CAUSE_6 OFFSET(6) NUMBITS(1) [], + CAUSE_7 OFFSET(7) NUMBITS(1) [], + ], +]; + +// End generated register constants for pinmux diff --git a/chips/lowrisc/src/registers/pwm_regs.rs b/chips/lowrisc/src/registers/pwm_regs.rs new file mode 100644 index 0000000000..666591d5eb --- /dev/null +++ b/chips/lowrisc/src/registers/pwm_regs.rs @@ -0,0 +1,86 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for pwm. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/pwm/data/pwm.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of PWM outputs +pub const PWM_PARAM_N_OUTPUTS: u32 = 6; +/// Number of alerts +pub const PWM_PARAM_NUM_ALERTS: u32 = 1; +/// Register width +pub const PWM_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub PwmRegisters { + /// Alert Test Register + (0x0000 => pub(crate) alert_test: ReadWrite), + /// Register write enable for all control registers + (0x0004 => pub(crate) regwen: ReadWrite), + /// Configuration register + (0x0008 => pub(crate) cfg: ReadWrite), + /// Enable PWM operation for each channel + (0x000c => pub(crate) pwm_en: [ReadWrite; 1]), + /// Invert the PWM output for each channel + (0x0010 => pub(crate) invert: [ReadWrite; 1]), + /// Basic PWM Channel Parameters + (0x0014 => pub(crate) pwm_param: [ReadWrite; 6]), + /// Controls the duty_cycle of each channel. + (0x002c => pub(crate) duty_cycle: [ReadWrite; 6]), + /// Hardware controlled blink/heartbeat parameters. + (0x0044 => pub(crate) blink_param: [ReadWrite; 6]), + (0x005c => @END), + } +} + +register_bitfields![u32, + pub(crate) ALERT_TEST [ + FATAL_FAULT OFFSET(0) NUMBITS(1) [], + ], + pub(crate) REGWEN [ + REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CFG [ + CLK_DIV OFFSET(0) NUMBITS(27) [], + DC_RESN OFFSET(27) NUMBITS(4) [], + CNTR_EN OFFSET(31) NUMBITS(1) [], + ], + pub(crate) PWM_EN [ + EN_0 OFFSET(0) NUMBITS(1) [], + EN_1 OFFSET(1) NUMBITS(1) [], + EN_2 OFFSET(2) NUMBITS(1) [], + EN_3 OFFSET(3) NUMBITS(1) [], + EN_4 OFFSET(4) NUMBITS(1) [], + EN_5 OFFSET(5) NUMBITS(1) [], + ], + pub(crate) INVERT [ + INVERT_0 OFFSET(0) NUMBITS(1) [], + INVERT_1 OFFSET(1) NUMBITS(1) [], + INVERT_2 OFFSET(2) NUMBITS(1) [], + INVERT_3 OFFSET(3) NUMBITS(1) [], + INVERT_4 OFFSET(4) NUMBITS(1) [], + INVERT_5 OFFSET(5) NUMBITS(1) [], + ], + pub(crate) PWM_PARAM [ + PHASE_DELAY_0 OFFSET(0) NUMBITS(16) [], + HTBT_EN_0 OFFSET(30) NUMBITS(1) [], + BLINK_EN_0 OFFSET(31) NUMBITS(1) [], + ], + pub(crate) DUTY_CYCLE [ + A_0 OFFSET(0) NUMBITS(16) [], + B_0 OFFSET(16) NUMBITS(16) [], + ], + pub(crate) BLINK_PARAM [ + X_0 OFFSET(0) NUMBITS(16) [], + Y_0 OFFSET(16) NUMBITS(16) [], + ], +]; + +// End generated register constants for pwm diff --git a/chips/lowrisc/src/registers/rom_ctrl_regs.rs b/chips/lowrisc/src/registers/rom_ctrl_regs.rs new file mode 100644 index 0000000000..b10c46aaf1 --- /dev/null +++ b/chips/lowrisc/src/registers/rom_ctrl_regs.rs @@ -0,0 +1,49 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for rom_ctrl. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/rom_ctrl/data/rom_ctrl.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of alerts +pub const ROM_CTRL_PARAM_NUM_ALERTS: u32 = 1; +/// Register width +pub const ROM_CTRL_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub RomCtrlRegisters { + /// Alert Test Register + (0x0000 => pub(crate) alert_test: ReadWrite), + /// The cause of a fatal alert. + (0x0004 => pub(crate) fatal_alert_cause: ReadWrite), + /// The digest computed from the contents of ROM + (0x0008 => pub(crate) digest: [ReadWrite; 8]), + /// The expected digest, stored in the top words of ROM + (0x0028 => pub(crate) exp_digest: [ReadWrite; 8]), + (0x0048 => @END), + } +} + +register_bitfields![u32, + pub(crate) ALERT_TEST [ + FATAL OFFSET(0) NUMBITS(1) [], + ], + pub(crate) FATAL_ALERT_CAUSE [ + CHECKER_ERROR OFFSET(0) NUMBITS(1) [], + INTEGRITY_ERROR OFFSET(1) NUMBITS(1) [], + ], + pub(crate) DIGEST [ + DIGEST_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) EXP_DIGEST [ + DIGEST_0 OFFSET(0) NUMBITS(32) [], + ], +]; + +// End generated register constants for rom_ctrl diff --git a/chips/lowrisc/src/registers/rv_core_ibex_regs.rs b/chips/lowrisc/src/registers/rv_core_ibex_regs.rs new file mode 100644 index 0000000000..50878e2763 --- /dev/null +++ b/chips/lowrisc/src/registers/rv_core_ibex_regs.rs @@ -0,0 +1,137 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for rv_core_ibex. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/rv_core_ibex/data/rv_core_ibex.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of software triggerable alerts +pub const RV_CORE_IBEX_PARAM_NUM_SW_ALERTS: u32 = 2; +/// Number of translatable regions per ibex bus +pub const RV_CORE_IBEX_PARAM_NUM_REGIONS: u32 = 2; +/// Number of scratch words maintained. +pub const RV_CORE_IBEX_PARAM_NUM_SCRATCH_WORDS: u32 = 8; +/// Number of alerts +pub const RV_CORE_IBEX_PARAM_NUM_ALERTS: u32 = 4; +/// Register width +pub const RV_CORE_IBEX_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub RvCoreIbexRegisters { + /// Alert Test Register + (0x0000 => pub(crate) alert_test: ReadWrite), + /// Software recoverable error + (0x0004 => pub(crate) sw_recov_err: ReadWrite), + /// Software fatal error + (0x0008 => pub(crate) sw_fatal_err: ReadWrite), + /// Ibus address control regwen. + (0x000c => pub(crate) ibus_regwen: [ReadWrite; 2]), + /// Enable Ibus address matching + (0x0014 => pub(crate) ibus_addr_en: [ReadWrite; 2]), + /// Matching region programming for ibus. + (0x001c => pub(crate) ibus_addr_matching: [ReadWrite; 2]), + /// The remap address after a match has been made. + (0x0024 => pub(crate) ibus_remap_addr: [ReadWrite; 2]), + /// Dbus address control regwen. + (0x002c => pub(crate) dbus_regwen: [ReadWrite; 2]), + /// Enable dbus address matching + (0x0034 => pub(crate) dbus_addr_en: [ReadWrite; 2]), + /// See !!IBUS_ADDR_MATCHING_0 for detailed description. + (0x003c => pub(crate) dbus_addr_matching: [ReadWrite; 2]), + /// See !!IBUS_REMAP_ADDR_0 for a detailed description. + (0x0044 => pub(crate) dbus_remap_addr: [ReadWrite; 2]), + /// Enable mask for NMI. + (0x004c => pub(crate) nmi_enable: ReadWrite), + /// Current NMI state + (0x0050 => pub(crate) nmi_state: ReadWrite), + /// error status + (0x0054 => pub(crate) err_status: ReadWrite), + /// Random data from EDN + (0x0058 => pub(crate) rnd_data: ReadWrite), + /// Status of random data in !!RND_DATA + (0x005c => pub(crate) rnd_status: ReadWrite), + /// FPGA build timestamp info. + (0x0060 => pub(crate) fpga_info: ReadWrite), + (0x0064 => _reserved1), + /// Memory area: Exposed tlul window for DV only purposes. + (0x0080 => pub(crate) dv_sim_window: [ReadWrite; 8]), + (0x00a0 => @END), + } +} + +register_bitfields![u32, + pub(crate) ALERT_TEST [ + FATAL_SW_ERR OFFSET(0) NUMBITS(1) [], + RECOV_SW_ERR OFFSET(1) NUMBITS(1) [], + FATAL_HW_ERR OFFSET(2) NUMBITS(1) [], + RECOV_HW_ERR OFFSET(3) NUMBITS(1) [], + ], + pub(crate) SW_RECOV_ERR [ + VAL OFFSET(0) NUMBITS(4) [], + ], + pub(crate) SW_FATAL_ERR [ + VAL OFFSET(0) NUMBITS(4) [], + ], + pub(crate) IBUS_REGWEN [ + EN_0 OFFSET(0) NUMBITS(1) [ + LOCKED = 0, + ENABLED = 1, + ], + ], + pub(crate) IBUS_ADDR_EN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) IBUS_ADDR_MATCHING [ + VAL_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) IBUS_REMAP_ADDR [ + VAL_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) DBUS_REGWEN [ + EN_0 OFFSET(0) NUMBITS(1) [ + LOCKED = 0, + ENABLED = 1, + ], + ], + pub(crate) DBUS_ADDR_EN [ + EN_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) DBUS_ADDR_MATCHING [ + VAL_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) DBUS_REMAP_ADDR [ + VAL_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) NMI_ENABLE [ + ALERT_EN OFFSET(0) NUMBITS(1) [], + WDOG_EN OFFSET(1) NUMBITS(1) [], + ], + pub(crate) NMI_STATE [ + ALERT OFFSET(0) NUMBITS(1) [], + WDOG OFFSET(1) NUMBITS(1) [], + ], + pub(crate) ERR_STATUS [ + REG_INTG_ERR OFFSET(0) NUMBITS(1) [], + FATAL_INTG_ERR OFFSET(8) NUMBITS(1) [], + FATAL_CORE_ERR OFFSET(9) NUMBITS(1) [], + RECOV_CORE_ERR OFFSET(10) NUMBITS(1) [], + ], + pub(crate) RND_DATA [ + DATA OFFSET(0) NUMBITS(32) [], + ], + pub(crate) RND_STATUS [ + RND_DATA_VALID OFFSET(0) NUMBITS(1) [], + RND_DATA_FIPS OFFSET(1) NUMBITS(1) [], + ], + pub(crate) FPGA_INFO [ + VAL OFFSET(0) NUMBITS(32) [], + ], +]; + +// End generated register constants for rv_core_ibex diff --git a/chips/lowrisc/src/registers/rv_timer_regs.rs b/chips/lowrisc/src/registers/rv_timer_regs.rs new file mode 100644 index 0000000000..b9e60bed40 --- /dev/null +++ b/chips/lowrisc/src/registers/rv_timer_regs.rs @@ -0,0 +1,84 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for rv_timer. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/rv_timer/data/rv_timer.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of harts +pub const RV_TIMER_PARAM_N_HARTS: u32 = 1; +/// Number of timers per Hart +pub const RV_TIMER_PARAM_N_TIMERS: u32 = 1; +/// Number of alerts +pub const RV_TIMER_PARAM_NUM_ALERTS: u32 = 1; +/// Register width +pub const RV_TIMER_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub RvTimerRegisters { + /// Alert Test Register + (0x0000 => pub(crate) alert_test: ReadWrite), + /// Control register + (0x0004 => pub(crate) ctrl: [ReadWrite; 1]), + (0x0008 => _reserved1), + /// Interrupt Enable + (0x0100 => pub(crate) intr_enable0: [ReadWrite; 1]), + /// Interrupt Status + (0x0104 => pub(crate) intr_state0: [ReadWrite; 1]), + /// Interrupt test register + (0x0108 => pub(crate) intr_test0: [ReadWrite; 1]), + /// Configuration for Hart 0 + (0x010c => pub(crate) cfg0: ReadWrite), + /// Timer value Lower + (0x0110 => pub(crate) timer_v_lower0: ReadWrite), + /// Timer value Upper + (0x0114 => pub(crate) timer_v_upper0: ReadWrite), + /// Timer value Lower + (0x0118 => pub(crate) compare_lower0_0: ReadWrite), + /// Timer value Upper + (0x011c => pub(crate) compare_upper0_0: ReadWrite), + (0x0120 => @END), + } +} + +register_bitfields![u32, + pub(crate) ALERT_TEST [ + FATAL_FAULT OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CTRL [ + ACTIVE_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) INTR_ENABLE0 [ + IE_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) INTR_STATE0 [ + IS_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) INTR_TEST0 [ + T_0 OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CFG0 [ + PRESCALE OFFSET(0) NUMBITS(12) [], + STEP OFFSET(16) NUMBITS(8) [], + ], + pub(crate) TIMER_V_LOWER0 [ + V OFFSET(0) NUMBITS(32) [], + ], + pub(crate) TIMER_V_UPPER0 [ + V OFFSET(0) NUMBITS(32) [], + ], + pub(crate) COMPARE_LOWER0_0 [ + V OFFSET(0) NUMBITS(32) [], + ], + pub(crate) COMPARE_UPPER0_0 [ + V OFFSET(0) NUMBITS(32) [], + ], +]; + +// End generated register constants for rv_timer diff --git a/chips/lowrisc/src/registers/spi_device_regs.rs b/chips/lowrisc/src/registers/spi_device_regs.rs new file mode 100644 index 0000000000..cb10d67c4b --- /dev/null +++ b/chips/lowrisc/src/registers/spi_device_regs.rs @@ -0,0 +1,381 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for spi_device. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/spi_device/data/spi_device.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of alerts +pub const SPI_DEVICE_PARAM_NUM_ALERTS: u32 = 1; +/// Register width +pub const SPI_DEVICE_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub SpiDeviceRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// Control register + (0x0010 => pub(crate) control: ReadWrite), + /// Configuration Register + (0x0014 => pub(crate) cfg: ReadWrite), + /// RX/ TX FIFO levels. + (0x0018 => pub(crate) fifo_level: ReadWrite), + /// RX/ TX Async FIFO levels between main clk and spi clock + (0x001c => pub(crate) async_fifo_level: ReadWrite), + /// SPI Device status register + (0x0020 => pub(crate) status: ReadWrite), + /// Receiver FIFO (SRAM) pointers + (0x0024 => pub(crate) rxf_ptr: ReadWrite), + /// Transmitter FIFO (SRAM) pointers + (0x0028 => pub(crate) txf_ptr: ReadWrite), + /// Receiver FIFO (SRAM) Addresses + (0x002c => pub(crate) rxf_addr: ReadWrite), + /// Transmitter FIFO (SRAM) Addresses + (0x0030 => pub(crate) txf_addr: ReadWrite), + /// Intercept Passthrough datapath. + (0x0034 => pub(crate) intercept_en: ReadWrite), + /// Last Read Address + (0x0038 => pub(crate) last_read_addr: ReadWrite), + /// SPI Flash Status register. + (0x003c => pub(crate) flash_status: ReadWrite), + /// JEDEC Continuation Code configuration register. + (0x0040 => pub(crate) jedec_cc: ReadWrite), + /// JEDEC ID register. + (0x0044 => pub(crate) jedec_id: ReadWrite), + /// Read Buffer threshold register. + (0x0048 => pub(crate) read_threshold: ReadWrite), + /// Mailbox Base address register. + (0x004c => pub(crate) mailbox_addr: ReadWrite), + /// Upload module status register. + (0x0050 => pub(crate) upload_status: ReadWrite), + /// Upload module status 2 register. + (0x0054 => pub(crate) upload_status2: ReadWrite), + /// Command Fifo Read Port. + (0x0058 => pub(crate) upload_cmdfifo: ReadWrite), + /// Address Fifo Read Port. + (0x005c => pub(crate) upload_addrfifo: ReadWrite), + /// Command Filter + (0x0060 => pub(crate) cmd_filter: [ReadWrite; 8]), + /// Address Swap Mask register. + (0x0080 => pub(crate) addr_swap_mask: ReadWrite), + /// The address value for the address swap feature. + (0x0084 => pub(crate) addr_swap_data: ReadWrite), + /// Write Data Swap in the passthrough mode. + (0x0088 => pub(crate) payload_swap_mask: ReadWrite), + /// Write Data Swap in the passthrough mode. + (0x008c => pub(crate) payload_swap_data: ReadWrite), + /// Command Info register. + (0x0090 => pub(crate) cmd_info: [ReadWrite; 24]), + /// Opcode for EN4B. + (0x00f0 => pub(crate) cmd_info_en4b: ReadWrite), + /// Opcode for EX4B + (0x00f4 => pub(crate) cmd_info_ex4b: ReadWrite), + /// Opcode for Write Enable (WREN) + (0x00f8 => pub(crate) cmd_info_wren: ReadWrite), + /// Opcode for Write Disable (WRDI) + (0x00fc => pub(crate) cmd_info_wrdi: ReadWrite), + (0x0100 => _reserved1), + /// TPM HWIP Capability register. + (0x0800 => pub(crate) tpm_cap: ReadWrite), + /// TPM Configuration register. + (0x0804 => pub(crate) tpm_cfg: ReadWrite), + /// TPM submodule state register. + (0x0808 => pub(crate) tpm_status: ReadWrite), + /// TPM_ACCESS_x register. + (0x080c => pub(crate) tpm_access: [ReadWrite; 2]), + /// TPM_STS_x register. + (0x0814 => pub(crate) tpm_sts: ReadWrite), + /// TPM_INTF_CAPABILITY + (0x0818 => pub(crate) tpm_intf_capability: ReadWrite), + /// TPM_INT_ENABLE + (0x081c => pub(crate) tpm_int_enable: ReadWrite), + /// TPM_INT_VECTOR + (0x0820 => pub(crate) tpm_int_vector: ReadWrite), + /// TPM_INT_STATUS + (0x0824 => pub(crate) tpm_int_status: ReadWrite), + /// TPM_DID/ TPM_VID register + (0x0828 => pub(crate) tpm_did_vid: ReadWrite), + /// TPM_RID + (0x082c => pub(crate) tpm_rid: ReadWrite), + /// TPM Command and Address buffer + (0x0830 => pub(crate) tpm_cmd_addr: ReadWrite), + /// TPM Read command return data FIFO. + (0x0834 => pub(crate) tpm_read_fifo: ReadWrite), + /// TPM Write command received data FIFO. + (0x0838 => pub(crate) tpm_write_fifo: ReadWrite), + (0x083c => _reserved2), + /// Memory area: SPI internal buffer. + (0x1000 => pub(crate) buffer: [ReadWrite; 1024]), + (0x2000 => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + GENERIC_RX_FULL OFFSET(0) NUMBITS(1) [], + GENERIC_RX_WATERMARK OFFSET(1) NUMBITS(1) [], + GENERIC_TX_WATERMARK OFFSET(2) NUMBITS(1) [], + GENERIC_RX_ERROR OFFSET(3) NUMBITS(1) [], + GENERIC_RX_OVERFLOW OFFSET(4) NUMBITS(1) [], + GENERIC_TX_UNDERFLOW OFFSET(5) NUMBITS(1) [], + UPLOAD_CMDFIFO_NOT_EMPTY OFFSET(6) NUMBITS(1) [], + UPLOAD_PAYLOAD_NOT_EMPTY OFFSET(7) NUMBITS(1) [], + UPLOAD_PAYLOAD_OVERFLOW OFFSET(8) NUMBITS(1) [], + READBUF_WATERMARK OFFSET(9) NUMBITS(1) [], + READBUF_FLIP OFFSET(10) NUMBITS(1) [], + TPM_HEADER_NOT_EMPTY OFFSET(11) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + FATAL_FAULT OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CONTROL [ + ABORT OFFSET(0) NUMBITS(1) [], + MODE OFFSET(4) NUMBITS(2) [ + FWMODE = 0, + FLASHMODE = 1, + PASSTHROUGH = 2, + ], + RST_TXFIFO OFFSET(16) NUMBITS(1) [], + RST_RXFIFO OFFSET(17) NUMBITS(1) [], + SRAM_CLK_EN OFFSET(31) NUMBITS(1) [], + ], + pub(crate) CFG [ + CPOL OFFSET(0) NUMBITS(1) [], + CPHA OFFSET(1) NUMBITS(1) [], + TX_ORDER OFFSET(2) NUMBITS(1) [], + RX_ORDER OFFSET(3) NUMBITS(1) [], + TIMER_V OFFSET(8) NUMBITS(8) [], + ADDR_4B_EN OFFSET(16) NUMBITS(1) [], + MAILBOX_EN OFFSET(24) NUMBITS(1) [], + ], + pub(crate) FIFO_LEVEL [ + RXLVL OFFSET(0) NUMBITS(16) [], + TXLVL OFFSET(16) NUMBITS(16) [], + ], + pub(crate) ASYNC_FIFO_LEVEL [ + RXLVL OFFSET(0) NUMBITS(8) [], + TXLVL OFFSET(16) NUMBITS(8) [], + ], + pub(crate) STATUS [ + RXF_FULL OFFSET(0) NUMBITS(1) [], + RXF_EMPTY OFFSET(1) NUMBITS(1) [], + TXF_FULL OFFSET(2) NUMBITS(1) [], + TXF_EMPTY OFFSET(3) NUMBITS(1) [], + ABORT_DONE OFFSET(4) NUMBITS(1) [], + CSB OFFSET(5) NUMBITS(1) [], + TPM_CSB OFFSET(6) NUMBITS(1) [], + ], + pub(crate) RXF_PTR [ + RPTR OFFSET(0) NUMBITS(16) [], + WPTR OFFSET(16) NUMBITS(16) [], + ], + pub(crate) TXF_PTR [ + RPTR OFFSET(0) NUMBITS(16) [], + WPTR OFFSET(16) NUMBITS(16) [], + ], + pub(crate) RXF_ADDR [ + BASE OFFSET(0) NUMBITS(16) [], + LIMIT OFFSET(16) NUMBITS(16) [], + ], + pub(crate) TXF_ADDR [ + BASE OFFSET(0) NUMBITS(16) [], + LIMIT OFFSET(16) NUMBITS(16) [], + ], + pub(crate) INTERCEPT_EN [ + STATUS OFFSET(0) NUMBITS(1) [], + JEDEC OFFSET(1) NUMBITS(1) [], + SFDP OFFSET(2) NUMBITS(1) [], + MBX OFFSET(3) NUMBITS(1) [], + ], + pub(crate) LAST_READ_ADDR [ + ADDR OFFSET(0) NUMBITS(32) [], + ], + pub(crate) FLASH_STATUS [ + BUSY OFFSET(0) NUMBITS(1) [], + STATUS OFFSET(1) NUMBITS(23) [], + ], + pub(crate) JEDEC_CC [ + CC OFFSET(0) NUMBITS(8) [], + NUM_CC OFFSET(8) NUMBITS(8) [], + ], + pub(crate) JEDEC_ID [ + ID OFFSET(0) NUMBITS(16) [], + MF OFFSET(16) NUMBITS(8) [], + ], + pub(crate) READ_THRESHOLD [ + THRESHOLD OFFSET(0) NUMBITS(10) [], + ], + pub(crate) MAILBOX_ADDR [ + ADDR OFFSET(0) NUMBITS(32) [], + ], + pub(crate) UPLOAD_STATUS [ + CMDFIFO_DEPTH OFFSET(0) NUMBITS(5) [], + CMDFIFO_NOTEMPTY OFFSET(7) NUMBITS(1) [], + ADDRFIFO_DEPTH OFFSET(8) NUMBITS(5) [], + ADDRFIFO_NOTEMPTY OFFSET(15) NUMBITS(1) [], + ], + pub(crate) UPLOAD_STATUS2 [ + PAYLOAD_DEPTH OFFSET(0) NUMBITS(9) [], + PAYLOAD_START_IDX OFFSET(16) NUMBITS(8) [], + ], + pub(crate) UPLOAD_CMDFIFO [ + DATA OFFSET(0) NUMBITS(8) [], + ], + pub(crate) UPLOAD_ADDRFIFO [ + DATA OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CMD_FILTER [ + FILTER_0 OFFSET(0) NUMBITS(1) [], + FILTER_1 OFFSET(1) NUMBITS(1) [], + FILTER_2 OFFSET(2) NUMBITS(1) [], + FILTER_3 OFFSET(3) NUMBITS(1) [], + FILTER_4 OFFSET(4) NUMBITS(1) [], + FILTER_5 OFFSET(5) NUMBITS(1) [], + FILTER_6 OFFSET(6) NUMBITS(1) [], + FILTER_7 OFFSET(7) NUMBITS(1) [], + FILTER_8 OFFSET(8) NUMBITS(1) [], + FILTER_9 OFFSET(9) NUMBITS(1) [], + FILTER_10 OFFSET(10) NUMBITS(1) [], + FILTER_11 OFFSET(11) NUMBITS(1) [], + FILTER_12 OFFSET(12) NUMBITS(1) [], + FILTER_13 OFFSET(13) NUMBITS(1) [], + FILTER_14 OFFSET(14) NUMBITS(1) [], + FILTER_15 OFFSET(15) NUMBITS(1) [], + FILTER_16 OFFSET(16) NUMBITS(1) [], + FILTER_17 OFFSET(17) NUMBITS(1) [], + FILTER_18 OFFSET(18) NUMBITS(1) [], + FILTER_19 OFFSET(19) NUMBITS(1) [], + FILTER_20 OFFSET(20) NUMBITS(1) [], + FILTER_21 OFFSET(21) NUMBITS(1) [], + FILTER_22 OFFSET(22) NUMBITS(1) [], + FILTER_23 OFFSET(23) NUMBITS(1) [], + FILTER_24 OFFSET(24) NUMBITS(1) [], + FILTER_25 OFFSET(25) NUMBITS(1) [], + FILTER_26 OFFSET(26) NUMBITS(1) [], + FILTER_27 OFFSET(27) NUMBITS(1) [], + FILTER_28 OFFSET(28) NUMBITS(1) [], + FILTER_29 OFFSET(29) NUMBITS(1) [], + FILTER_30 OFFSET(30) NUMBITS(1) [], + FILTER_31 OFFSET(31) NUMBITS(1) [], + ], + pub(crate) ADDR_SWAP_MASK [ + MASK OFFSET(0) NUMBITS(32) [], + ], + pub(crate) ADDR_SWAP_DATA [ + DATA OFFSET(0) NUMBITS(32) [], + ], + pub(crate) PAYLOAD_SWAP_MASK [ + MASK OFFSET(0) NUMBITS(32) [], + ], + pub(crate) PAYLOAD_SWAP_DATA [ + DATA OFFSET(0) NUMBITS(32) [], + ], + pub(crate) CMD_INFO [ + OPCODE_0 OFFSET(0) NUMBITS(8) [], + ADDR_MODE_0 OFFSET(8) NUMBITS(2) [ + ADDRDISABLED = 0, + ADDRCFG = 1, + ADDR3B = 2, + ADDR4B = 3, + ], + ADDR_SWAP_EN_0 OFFSET(10) NUMBITS(1) [], + MBYTE_EN_0 OFFSET(11) NUMBITS(1) [], + DUMMY_SIZE_0 OFFSET(12) NUMBITS(3) [], + DUMMY_EN_0 OFFSET(15) NUMBITS(1) [], + PAYLOAD_EN_0 OFFSET(16) NUMBITS(4) [], + PAYLOAD_DIR_0 OFFSET(20) NUMBITS(1) [ + PAYLOADIN = 0, + PAYLOADOUT = 1, + ], + PAYLOAD_SWAP_EN_0 OFFSET(21) NUMBITS(1) [], + UPLOAD_0 OFFSET(24) NUMBITS(1) [], + BUSY_0 OFFSET(25) NUMBITS(1) [], + VALID_0 OFFSET(31) NUMBITS(1) [], + ], + pub(crate) CMD_INFO_EN4B [ + OPCODE OFFSET(0) NUMBITS(8) [], + VALID OFFSET(31) NUMBITS(1) [], + ], + pub(crate) CMD_INFO_EX4B [ + OPCODE OFFSET(0) NUMBITS(8) [], + VALID OFFSET(31) NUMBITS(1) [], + ], + pub(crate) CMD_INFO_WREN [ + OPCODE OFFSET(0) NUMBITS(8) [], + VALID OFFSET(31) NUMBITS(1) [], + ], + pub(crate) CMD_INFO_WRDI [ + OPCODE OFFSET(0) NUMBITS(8) [], + VALID OFFSET(31) NUMBITS(1) [], + ], + pub(crate) TPM_CAP [ + REV OFFSET(0) NUMBITS(8) [], + LOCALITY OFFSET(8) NUMBITS(1) [], + MAX_WR_SIZE OFFSET(16) NUMBITS(3) [], + MAX_RD_SIZE OFFSET(20) NUMBITS(3) [], + ], + pub(crate) TPM_CFG [ + EN OFFSET(0) NUMBITS(1) [], + TPM_MODE OFFSET(1) NUMBITS(1) [], + HW_REG_DIS OFFSET(2) NUMBITS(1) [], + TPM_REG_CHK_DIS OFFSET(3) NUMBITS(1) [], + INVALID_LOCALITY OFFSET(4) NUMBITS(1) [], + ], + pub(crate) TPM_STATUS [ + CMDADDR_NOTEMPTY OFFSET(0) NUMBITS(1) [], + WRFIFO_DEPTH OFFSET(16) NUMBITS(7) [], + ], + pub(crate) TPM_ACCESS [ + ACCESS_0 OFFSET(0) NUMBITS(8) [], + ACCESS_1 OFFSET(8) NUMBITS(8) [], + ACCESS_2 OFFSET(16) NUMBITS(8) [], + ACCESS_3 OFFSET(24) NUMBITS(8) [], + ], + pub(crate) TPM_STS [ + STS OFFSET(0) NUMBITS(32) [], + ], + pub(crate) TPM_INTF_CAPABILITY [ + INTF_CAPABILITY OFFSET(0) NUMBITS(32) [], + ], + pub(crate) TPM_INT_ENABLE [ + INT_ENABLE OFFSET(0) NUMBITS(32) [], + ], + pub(crate) TPM_INT_VECTOR [ + INT_VECTOR OFFSET(0) NUMBITS(8) [], + ], + pub(crate) TPM_INT_STATUS [ + INT_STATUS OFFSET(0) NUMBITS(32) [], + ], + pub(crate) TPM_DID_VID [ + VID OFFSET(0) NUMBITS(16) [], + DID OFFSET(16) NUMBITS(16) [], + ], + pub(crate) TPM_RID [ + RID OFFSET(0) NUMBITS(8) [], + ], + pub(crate) TPM_CMD_ADDR [ + ADDR OFFSET(0) NUMBITS(24) [], + CMD OFFSET(24) NUMBITS(8) [], + ], + pub(crate) TPM_READ_FIFO [ + VALUE OFFSET(0) NUMBITS(32) [], + ], + pub(crate) TPM_WRITE_FIFO [ + VALUE OFFSET(0) NUMBITS(8) [], + ], +]; + +// End generated register constants for spi_device diff --git a/chips/lowrisc/src/registers/spi_host_regs.rs b/chips/lowrisc/src/registers/spi_host_regs.rs new file mode 100644 index 0000000000..d16dc29e0f --- /dev/null +++ b/chips/lowrisc/src/registers/spi_host_regs.rs @@ -0,0 +1,138 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for spi_host. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/spi_host/data/spi_host.hjson +use kernel::utilities::registers::ReadOnly; +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::WriteOnly; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// The number of active-low chip select (cs_n) lines to create. +pub const SPI_HOST_PARAM_NUM_C_S: u32 = 1; +/// The size of the Tx FIFO (in words) +pub const SPI_HOST_PARAM_TX_DEPTH: u32 = 72; +/// The size of the Rx FIFO (in words) +pub const SPI_HOST_PARAM_RX_DEPTH: u32 = 64; +/// The size of the Cmd FIFO (one segment descriptor per entry) +pub const SPI_HOST_PARAM_CMD_DEPTH: u32 = 4; +/// Number of alerts +pub const SPI_HOST_PARAM_NUM_ALERTS: u32 = 1; +/// Register width +pub const SPI_HOST_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub SpiHostRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// Control register + (0x0010 => pub(crate) control: ReadWrite), + /// Status register + (0x0014 => pub(crate) status: ReadWrite), + /// Configuration options register. + (0x0018 => pub(crate) configopts: [ReadWrite; 1]), + /// Chip-Select ID + (0x001c => pub(crate) csid: ReadWrite), + /// Command Register + (0x0020 => pub(crate) command: ReadWrite), + /// Memory area: SPI Receive Data. + (0x0024 => pub(crate) rxdata: [ReadOnly; 1]), + /// Memory area: SPI Transmit Data. + (0x0028 => pub(crate) txdata: [WriteOnly; 1]), + /// Controls which classes of errors raise an interrupt. + (0x002c => pub(crate) error_enable: ReadWrite), + /// Indicates that any errors that have occurred. + (0x0030 => pub(crate) error_status: ReadWrite), + /// Controls which classes of SPI events raise an interrupt. + (0x0034 => pub(crate) event_enable: ReadWrite), + (0x0038 => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + ERROR OFFSET(0) NUMBITS(1) [], + SPI_EVENT OFFSET(1) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + FATAL_FAULT OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CONTROL [ + RX_WATERMARK OFFSET(0) NUMBITS(8) [], + TX_WATERMARK OFFSET(8) NUMBITS(8) [], + OUTPUT_EN OFFSET(29) NUMBITS(1) [], + SW_RST OFFSET(30) NUMBITS(1) [], + SPIEN OFFSET(31) NUMBITS(1) [], + ], + pub(crate) STATUS [ + TXQD OFFSET(0) NUMBITS(8) [], + RXQD OFFSET(8) NUMBITS(8) [], + CMDQD OFFSET(16) NUMBITS(4) [], + RXWM OFFSET(20) NUMBITS(1) [], + BYTEORDER OFFSET(22) NUMBITS(1) [], + RXSTALL OFFSET(23) NUMBITS(1) [], + RXEMPTY OFFSET(24) NUMBITS(1) [], + RXFULL OFFSET(25) NUMBITS(1) [], + TXWM OFFSET(26) NUMBITS(1) [], + TXSTALL OFFSET(27) NUMBITS(1) [], + TXEMPTY OFFSET(28) NUMBITS(1) [], + TXFULL OFFSET(29) NUMBITS(1) [], + ACTIVE OFFSET(30) NUMBITS(1) [], + READY OFFSET(31) NUMBITS(1) [], + ], + pub(crate) CONFIGOPTS [ + CLKDIV_0 OFFSET(0) NUMBITS(16) [], + CSNIDLE_0 OFFSET(16) NUMBITS(4) [], + CSNTRAIL_0 OFFSET(20) NUMBITS(4) [], + CSNLEAD_0 OFFSET(24) NUMBITS(4) [], + FULLCYC_0 OFFSET(29) NUMBITS(1) [], + CPHA_0 OFFSET(30) NUMBITS(1) [], + CPOL_0 OFFSET(31) NUMBITS(1) [], + ], + pub(crate) CSID [ + CSID OFFSET(0) NUMBITS(32) [], + ], + pub(crate) COMMAND [ + LEN OFFSET(0) NUMBITS(9) [], + CSAAT OFFSET(9) NUMBITS(1) [], + SPEED OFFSET(10) NUMBITS(2) [], + DIRECTION OFFSET(12) NUMBITS(2) [], + ], + pub(crate) ERROR_ENABLE [ + CMDBUSY OFFSET(0) NUMBITS(1) [], + OVERFLOW OFFSET(1) NUMBITS(1) [], + UNDERFLOW OFFSET(2) NUMBITS(1) [], + CMDINVAL OFFSET(3) NUMBITS(1) [], + CSIDINVAL OFFSET(4) NUMBITS(1) [], + ], + pub(crate) ERROR_STATUS [ + CMDBUSY OFFSET(0) NUMBITS(1) [], + OVERFLOW OFFSET(1) NUMBITS(1) [], + UNDERFLOW OFFSET(2) NUMBITS(1) [], + CMDINVAL OFFSET(3) NUMBITS(1) [], + CSIDINVAL OFFSET(4) NUMBITS(1) [], + ACCESSINVAL OFFSET(5) NUMBITS(1) [], + ], + pub(crate) EVENT_ENABLE [ + RXFULL OFFSET(0) NUMBITS(1) [], + TXEMPTY OFFSET(1) NUMBITS(1) [], + RXWM OFFSET(2) NUMBITS(1) [], + TXWM OFFSET(3) NUMBITS(1) [], + READY OFFSET(4) NUMBITS(1) [], + IDLE OFFSET(5) NUMBITS(1) [], + ], +]; + +// End generated register constants for spi_host diff --git a/chips/lowrisc/src/registers/sram_ctrl_regs.rs b/chips/lowrisc/src/registers/sram_ctrl_regs.rs new file mode 100644 index 0000000000..347e01c4f8 --- /dev/null +++ b/chips/lowrisc/src/registers/sram_ctrl_regs.rs @@ -0,0 +1,64 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for sram_ctrl. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/sram_ctrl/data/sram_ctrl.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of alerts +pub const SRAM_CTRL_PARAM_NUM_ALERTS: u32 = 1; +/// Register width +pub const SRAM_CTRL_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub SramCtrlRegisters { + /// Alert Test Register + (0x0000 => pub(crate) alert_test: ReadWrite), + /// SRAM status register. + (0x0004 => pub(crate) status: ReadWrite), + /// Lock register for execution enable register. + (0x0008 => pub(crate) exec_regwen: ReadWrite), + /// Sram execution enable. + (0x000c => pub(crate) exec: ReadWrite), + /// Lock register for control register. + (0x0010 => pub(crate) ctrl_regwen: ReadWrite), + /// SRAM ctrl register. + (0x0014 => pub(crate) ctrl: ReadWrite), + (0x0018 => @END), + } +} + +register_bitfields![u32, + pub(crate) ALERT_TEST [ + FATAL_ERROR OFFSET(0) NUMBITS(1) [], + ], + pub(crate) STATUS [ + BUS_INTEG_ERROR OFFSET(0) NUMBITS(1) [], + INIT_ERROR OFFSET(1) NUMBITS(1) [], + ESCALATED OFFSET(2) NUMBITS(1) [], + SCR_KEY_VALID OFFSET(3) NUMBITS(1) [], + SCR_KEY_SEED_VALID OFFSET(4) NUMBITS(1) [], + INIT_DONE OFFSET(5) NUMBITS(1) [], + ], + pub(crate) EXEC_REGWEN [ + EXEC_REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) EXEC [ + EN OFFSET(0) NUMBITS(4) [], + ], + pub(crate) CTRL_REGWEN [ + CTRL_REGWEN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CTRL [ + RENEW_SCR_KEY OFFSET(0) NUMBITS(1) [], + INIT OFFSET(1) NUMBITS(1) [], + ], +]; + +// End generated register constants for sram_ctrl diff --git a/chips/lowrisc/src/registers/sysrst_ctrl_regs.rs b/chips/lowrisc/src/registers/sysrst_ctrl_regs.rs new file mode 100644 index 0000000000..7327f9458f --- /dev/null +++ b/chips/lowrisc/src/registers/sysrst_ctrl_regs.rs @@ -0,0 +1,262 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for sysrst_ctrl. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/sysrst_ctrl/data/sysrst_ctrl.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of keyboard combos +pub const SYSRST_CTRL_PARAM_NUM_COMBO: u32 = 4; +/// Number of timer bits +pub const SYSRST_CTRL_PARAM_TIMER_WIDTH: u32 = 16; +/// Number of detection timer bits +pub const SYSRST_CTRL_PARAM_DET_TIMER_WIDTH: u32 = 32; +/// Number of alerts +pub const SYSRST_CTRL_PARAM_NUM_ALERTS: u32 = 1; +/// Register width +pub const SYSRST_CTRL_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub SysrstCtrlRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// Configuration write enable control register + (0x0010 => pub(crate) regwen: ReadWrite), + /// EC reset control register + (0x0014 => pub(crate) ec_rst_ctl: ReadWrite), + /// Ultra low power AC debounce control register + (0x0018 => pub(crate) ulp_ac_debounce_ctl: ReadWrite), + /// Ultra low power lid debounce control register + (0x001c => pub(crate) ulp_lid_debounce_ctl: ReadWrite), + /// Ultra low power pwrb debounce control register + (0x0020 => pub(crate) ulp_pwrb_debounce_ctl: ReadWrite), + /// Ultra low power control register + (0x0024 => pub(crate) ulp_ctl: ReadWrite), + /// Ultra low power status + (0x0028 => pub(crate) ulp_status: ReadWrite), + /// wakeup status + (0x002c => pub(crate) wkup_status: ReadWrite), + /// configure key input output invert property + (0x0030 => pub(crate) key_invert_ctl: ReadWrite), + /// This register determines which override values are allowed for a given output. + (0x0034 => pub(crate) pin_allowed_ctl: ReadWrite), + /// Enables the override function for a specific pin. + (0x0038 => pub(crate) pin_out_ctl: ReadWrite), + /// Sets the pin override value. Note that only the values + (0x003c => pub(crate) pin_out_value: ReadWrite), + /// For SW to read the sysrst_ctrl inputs like GPIO + (0x0040 => pub(crate) pin_in_value: ReadWrite), + /// Define the keys or inputs that can trigger the interrupt + (0x0044 => pub(crate) key_intr_ctl: ReadWrite), + /// Debounce timer control register for key-triggered interrupt + (0x0048 => pub(crate) key_intr_debounce_ctl: ReadWrite), + /// Debounce timer control register for pwrb_in H2L transition + (0x004c => pub(crate) auto_block_debounce_ctl: ReadWrite), + /// configure the key outputs to auto-override and their value + (0x0050 => pub(crate) auto_block_out_ctl: ReadWrite), + /// To define the keys that define the pre-condition of the combo + (0x0054 => pub(crate) com_pre_sel_ctl: [ReadWrite; 4]), + /// To define the duration that the combo pre-condition should be pressed + (0x0064 => pub(crate) com_pre_det_ctl: [ReadWrite; 4]), + /// To define the keys that trigger the combo + (0x0074 => pub(crate) com_sel_ctl: [ReadWrite; 4]), + /// To define the duration that the combo should be pressed + (0x0084 => pub(crate) com_det_ctl: [ReadWrite; 4]), + /// To define the actions once the combo is detected + (0x0094 => pub(crate) com_out_ctl: [ReadWrite; 4]), + /// Combo interrupt source. These registers will only be set if the + (0x00a4 => pub(crate) combo_intr_status: ReadWrite), + /// key interrupt source + (0x00a8 => pub(crate) key_intr_status: ReadWrite), + (0x00ac => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + EVENT_DETECTED OFFSET(0) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + FATAL_FAULT OFFSET(0) NUMBITS(1) [], + ], + pub(crate) REGWEN [ + WRITE_EN OFFSET(0) NUMBITS(1) [], + ], + pub(crate) EC_RST_CTL [ + EC_RST_PULSE OFFSET(0) NUMBITS(16) [], + ], + pub(crate) ULP_AC_DEBOUNCE_CTL [ + ULP_AC_DEBOUNCE_TIMER OFFSET(0) NUMBITS(16) [], + ], + pub(crate) ULP_LID_DEBOUNCE_CTL [ + ULP_LID_DEBOUNCE_TIMER OFFSET(0) NUMBITS(16) [], + ], + pub(crate) ULP_PWRB_DEBOUNCE_CTL [ + ULP_PWRB_DEBOUNCE_TIMER OFFSET(0) NUMBITS(16) [], + ], + pub(crate) ULP_CTL [ + ULP_ENABLE OFFSET(0) NUMBITS(1) [], + ], + pub(crate) ULP_STATUS [ + ULP_WAKEUP OFFSET(0) NUMBITS(1) [], + ], + pub(crate) WKUP_STATUS [ + WAKEUP_STS OFFSET(0) NUMBITS(1) [], + ], + pub(crate) KEY_INVERT_CTL [ + KEY0_IN OFFSET(0) NUMBITS(1) [], + KEY0_OUT OFFSET(1) NUMBITS(1) [], + KEY1_IN OFFSET(2) NUMBITS(1) [], + KEY1_OUT OFFSET(3) NUMBITS(1) [], + KEY2_IN OFFSET(4) NUMBITS(1) [], + KEY2_OUT OFFSET(5) NUMBITS(1) [], + PWRB_IN OFFSET(6) NUMBITS(1) [], + PWRB_OUT OFFSET(7) NUMBITS(1) [], + AC_PRESENT OFFSET(8) NUMBITS(1) [], + BAT_DISABLE OFFSET(9) NUMBITS(1) [], + LID_OPEN OFFSET(10) NUMBITS(1) [], + Z3_WAKEUP OFFSET(11) NUMBITS(1) [], + ], + pub(crate) PIN_ALLOWED_CTL [ + BAT_DISABLE_0 OFFSET(0) NUMBITS(1) [], + EC_RST_L_0 OFFSET(1) NUMBITS(1) [], + PWRB_OUT_0 OFFSET(2) NUMBITS(1) [], + KEY0_OUT_0 OFFSET(3) NUMBITS(1) [], + KEY1_OUT_0 OFFSET(4) NUMBITS(1) [], + KEY2_OUT_0 OFFSET(5) NUMBITS(1) [], + Z3_WAKEUP_0 OFFSET(6) NUMBITS(1) [], + FLASH_WP_L_0 OFFSET(7) NUMBITS(1) [], + BAT_DISABLE_1 OFFSET(8) NUMBITS(1) [], + EC_RST_L_1 OFFSET(9) NUMBITS(1) [], + PWRB_OUT_1 OFFSET(10) NUMBITS(1) [], + KEY0_OUT_1 OFFSET(11) NUMBITS(1) [], + KEY1_OUT_1 OFFSET(12) NUMBITS(1) [], + KEY2_OUT_1 OFFSET(13) NUMBITS(1) [], + Z3_WAKEUP_1 OFFSET(14) NUMBITS(1) [], + FLASH_WP_L_1 OFFSET(15) NUMBITS(1) [], + ], + pub(crate) PIN_OUT_CTL [ + BAT_DISABLE OFFSET(0) NUMBITS(1) [], + EC_RST_L OFFSET(1) NUMBITS(1) [], + PWRB_OUT OFFSET(2) NUMBITS(1) [], + KEY0_OUT OFFSET(3) NUMBITS(1) [], + KEY1_OUT OFFSET(4) NUMBITS(1) [], + KEY2_OUT OFFSET(5) NUMBITS(1) [], + Z3_WAKEUP OFFSET(6) NUMBITS(1) [], + FLASH_WP_L OFFSET(7) NUMBITS(1) [], + ], + pub(crate) PIN_OUT_VALUE [ + BAT_DISABLE OFFSET(0) NUMBITS(1) [], + EC_RST_L OFFSET(1) NUMBITS(1) [], + PWRB_OUT OFFSET(2) NUMBITS(1) [], + KEY0_OUT OFFSET(3) NUMBITS(1) [], + KEY1_OUT OFFSET(4) NUMBITS(1) [], + KEY2_OUT OFFSET(5) NUMBITS(1) [], + Z3_WAKEUP OFFSET(6) NUMBITS(1) [], + FLASH_WP_L OFFSET(7) NUMBITS(1) [], + ], + pub(crate) PIN_IN_VALUE [ + PWRB_IN OFFSET(0) NUMBITS(1) [], + KEY0_IN OFFSET(1) NUMBITS(1) [], + KEY1_IN OFFSET(2) NUMBITS(1) [], + KEY2_IN OFFSET(3) NUMBITS(1) [], + LID_OPEN OFFSET(4) NUMBITS(1) [], + AC_PRESENT OFFSET(5) NUMBITS(1) [], + EC_RST_L OFFSET(6) NUMBITS(1) [], + FLASH_WP_L OFFSET(7) NUMBITS(1) [], + ], + pub(crate) KEY_INTR_CTL [ + PWRB_IN_H2L OFFSET(0) NUMBITS(1) [], + KEY0_IN_H2L OFFSET(1) NUMBITS(1) [], + KEY1_IN_H2L OFFSET(2) NUMBITS(1) [], + KEY2_IN_H2L OFFSET(3) NUMBITS(1) [], + AC_PRESENT_H2L OFFSET(4) NUMBITS(1) [], + EC_RST_L_H2L OFFSET(5) NUMBITS(1) [], + FLASH_WP_L_H2L OFFSET(6) NUMBITS(1) [], + PWRB_IN_L2H OFFSET(7) NUMBITS(1) [], + KEY0_IN_L2H OFFSET(8) NUMBITS(1) [], + KEY1_IN_L2H OFFSET(9) NUMBITS(1) [], + KEY2_IN_L2H OFFSET(10) NUMBITS(1) [], + AC_PRESENT_L2H OFFSET(11) NUMBITS(1) [], + EC_RST_L_L2H OFFSET(12) NUMBITS(1) [], + FLASH_WP_L_L2H OFFSET(13) NUMBITS(1) [], + ], + pub(crate) KEY_INTR_DEBOUNCE_CTL [ + DEBOUNCE_TIMER OFFSET(0) NUMBITS(16) [], + ], + pub(crate) AUTO_BLOCK_DEBOUNCE_CTL [ + DEBOUNCE_TIMER OFFSET(0) NUMBITS(16) [], + AUTO_BLOCK_ENABLE OFFSET(16) NUMBITS(1) [], + ], + pub(crate) AUTO_BLOCK_OUT_CTL [ + KEY0_OUT_SEL OFFSET(0) NUMBITS(1) [], + KEY1_OUT_SEL OFFSET(1) NUMBITS(1) [], + KEY2_OUT_SEL OFFSET(2) NUMBITS(1) [], + KEY0_OUT_VALUE OFFSET(4) NUMBITS(1) [], + KEY1_OUT_VALUE OFFSET(5) NUMBITS(1) [], + KEY2_OUT_VALUE OFFSET(6) NUMBITS(1) [], + ], + pub(crate) COM_PRE_SEL_CTL [ + KEY0_IN_SEL_0 OFFSET(0) NUMBITS(1) [], + KEY1_IN_SEL_0 OFFSET(1) NUMBITS(1) [], + KEY2_IN_SEL_0 OFFSET(2) NUMBITS(1) [], + PWRB_IN_SEL_0 OFFSET(3) NUMBITS(1) [], + AC_PRESENT_SEL_0 OFFSET(4) NUMBITS(1) [], + ], + pub(crate) COM_PRE_DET_CTL [ + PRECONDITION_TIMER_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) COM_SEL_CTL [ + KEY0_IN_SEL_0 OFFSET(0) NUMBITS(1) [], + KEY1_IN_SEL_0 OFFSET(1) NUMBITS(1) [], + KEY2_IN_SEL_0 OFFSET(2) NUMBITS(1) [], + PWRB_IN_SEL_0 OFFSET(3) NUMBITS(1) [], + AC_PRESENT_SEL_0 OFFSET(4) NUMBITS(1) [], + ], + pub(crate) COM_DET_CTL [ + DETECTION_TIMER_0 OFFSET(0) NUMBITS(32) [], + ], + pub(crate) COM_OUT_CTL [ + BAT_DISABLE_0 OFFSET(0) NUMBITS(1) [], + INTERRUPT_0 OFFSET(1) NUMBITS(1) [], + EC_RST_0 OFFSET(2) NUMBITS(1) [], + RST_REQ_0 OFFSET(3) NUMBITS(1) [], + ], + pub(crate) COMBO_INTR_STATUS [ + COMBO0_H2L OFFSET(0) NUMBITS(1) [], + COMBO1_H2L OFFSET(1) NUMBITS(1) [], + COMBO2_H2L OFFSET(2) NUMBITS(1) [], + COMBO3_H2L OFFSET(3) NUMBITS(1) [], + ], + pub(crate) KEY_INTR_STATUS [ + PWRB_H2L OFFSET(0) NUMBITS(1) [], + KEY0_IN_H2L OFFSET(1) NUMBITS(1) [], + KEY1_IN_H2L OFFSET(2) NUMBITS(1) [], + KEY2_IN_H2L OFFSET(3) NUMBITS(1) [], + AC_PRESENT_H2L OFFSET(4) NUMBITS(1) [], + EC_RST_L_H2L OFFSET(5) NUMBITS(1) [], + FLASH_WP_L_H2L OFFSET(6) NUMBITS(1) [], + PWRB_L2H OFFSET(7) NUMBITS(1) [], + KEY0_IN_L2H OFFSET(8) NUMBITS(1) [], + KEY1_IN_L2H OFFSET(9) NUMBITS(1) [], + KEY2_IN_L2H OFFSET(10) NUMBITS(1) [], + AC_PRESENT_L2H OFFSET(11) NUMBITS(1) [], + EC_RST_L_L2H OFFSET(12) NUMBITS(1) [], + FLASH_WP_L_L2H OFFSET(13) NUMBITS(1) [], + ], +]; + +// End generated register constants for sysrst_ctrl diff --git a/chips/lowrisc/src/registers/uart_regs.rs b/chips/lowrisc/src/registers/uart_regs.rs new file mode 100644 index 0000000000..14dc3e982d --- /dev/null +++ b/chips/lowrisc/src/registers/uart_regs.rs @@ -0,0 +1,136 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for uart. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/uart/data/uart.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of alerts +pub const UART_PARAM_NUM_ALERTS: u32 = 1; +/// Register width +pub const UART_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub UartRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// UART control register + (0x0010 => pub(crate) ctrl: ReadWrite), + /// UART live status register + (0x0014 => pub(crate) status: ReadWrite), + /// UART read data + (0x0018 => pub(crate) rdata: ReadWrite), + /// UART write data + (0x001c => pub(crate) wdata: ReadWrite), + /// UART FIFO control register + (0x0020 => pub(crate) fifo_ctrl: ReadWrite), + /// UART FIFO status register + (0x0024 => pub(crate) fifo_status: ReadWrite), + /// TX pin override control. Gives direct SW control over TX pin state + (0x0028 => pub(crate) ovrd: ReadWrite), + /// UART oversampled values + (0x002c => pub(crate) val: ReadWrite), + /// UART RX timeout control + (0x0030 => pub(crate) timeout_ctrl: ReadWrite), + (0x0034 => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + TX_WATERMARK OFFSET(0) NUMBITS(1) [], + RX_WATERMARK OFFSET(1) NUMBITS(1) [], + TX_EMPTY OFFSET(2) NUMBITS(1) [], + RX_OVERFLOW OFFSET(3) NUMBITS(1) [], + RX_FRAME_ERR OFFSET(4) NUMBITS(1) [], + RX_BREAK_ERR OFFSET(5) NUMBITS(1) [], + RX_TIMEOUT OFFSET(6) NUMBITS(1) [], + RX_PARITY_ERR OFFSET(7) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + FATAL_FAULT OFFSET(0) NUMBITS(1) [], + ], + pub(crate) CTRL [ + TX OFFSET(0) NUMBITS(1) [], + RX OFFSET(1) NUMBITS(1) [], + NF OFFSET(2) NUMBITS(1) [], + SLPBK OFFSET(4) NUMBITS(1) [], + LLPBK OFFSET(5) NUMBITS(1) [], + PARITY_EN OFFSET(6) NUMBITS(1) [], + PARITY_ODD OFFSET(7) NUMBITS(1) [], + RXBLVL OFFSET(8) NUMBITS(2) [ + BREAK2 = 0, + BREAK4 = 1, + BREAK8 = 2, + BREAK16 = 3, + ], + NCO OFFSET(16) NUMBITS(16) [], + ], + pub(crate) STATUS [ + TXFULL OFFSET(0) NUMBITS(1) [], + RXFULL OFFSET(1) NUMBITS(1) [], + TXEMPTY OFFSET(2) NUMBITS(1) [], + TXIDLE OFFSET(3) NUMBITS(1) [], + RXIDLE OFFSET(4) NUMBITS(1) [], + RXEMPTY OFFSET(5) NUMBITS(1) [], + ], + pub(crate) RDATA [ + RDATA OFFSET(0) NUMBITS(8) [], + ], + pub(crate) WDATA [ + WDATA OFFSET(0) NUMBITS(8) [], + ], + pub(crate) FIFO_CTRL [ + RXRST OFFSET(0) NUMBITS(1) [], + TXRST OFFSET(1) NUMBITS(1) [], + RXILVL OFFSET(2) NUMBITS(3) [ + RXLVL1 = 0, + RXLVL2 = 1, + RXLVL4 = 2, + RXLVL8 = 3, + RXLVL16 = 4, + RXLVL32 = 5, + RXLVL64 = 6, + RXLVL126 = 7, + ], + TXILVL OFFSET(5) NUMBITS(3) [ + TXLVL1 = 0, + TXLVL2 = 1, + TXLVL4 = 2, + TXLVL8 = 3, + TXLVL16 = 4, + TXLVL32 = 5, + TXLVL64 = 6, + ], + ], + pub(crate) FIFO_STATUS [ + TXLVL OFFSET(0) NUMBITS(8) [], + RXLVL OFFSET(16) NUMBITS(8) [], + ], + pub(crate) OVRD [ + TXEN OFFSET(0) NUMBITS(1) [], + TXVAL OFFSET(1) NUMBITS(1) [], + ], + pub(crate) VAL [ + RX OFFSET(0) NUMBITS(16) [], + ], + pub(crate) TIMEOUT_CTRL [ + VAL OFFSET(0) NUMBITS(24) [], + EN OFFSET(31) NUMBITS(1) [], + ], +]; + +// End generated register constants for uart diff --git a/chips/lowrisc/src/registers/usbdev_regs.rs b/chips/lowrisc/src/registers/usbdev_regs.rs new file mode 100644 index 0000000000..e343b37d5a --- /dev/null +++ b/chips/lowrisc/src/registers/usbdev_regs.rs @@ -0,0 +1,337 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright lowRISC contributors 2023. + +// Generated register constants for usbdev. +// Built for Earlgrey-M2.5.1-RC1-493-gedf5e35f5d +// https://github.com/lowRISC/opentitan/tree/edf5e35f5d50a5377641c90a315109a351de7635 +// Tree status: clean +// Build date: 2023-10-18T10:11:37 + +// Original reference file: hw/ip/usbdev/data/usbdev.hjson +use kernel::utilities::registers::ReadWrite; +use kernel::utilities::registers::{register_bitfields, register_structs}; +/// Number of endpoints +pub const USBDEV_PARAM_N_ENDPOINTS: u32 = 12; +/// Number of alerts +pub const USBDEV_PARAM_NUM_ALERTS: u32 = 1; +/// Register width +pub const USBDEV_PARAM_REG_WIDTH: u32 = 32; + +register_structs! { + pub UsbdevRegisters { + /// Interrupt State Register + (0x0000 => pub(crate) intr_state: ReadWrite), + /// Interrupt Enable Register + (0x0004 => pub(crate) intr_enable: ReadWrite), + /// Interrupt Test Register + (0x0008 => pub(crate) intr_test: ReadWrite), + /// Alert Test Register + (0x000c => pub(crate) alert_test: ReadWrite), + /// USB Control + (0x0010 => pub(crate) usbctrl: ReadWrite), + /// Enable an endpoint to respond to transactions in the downstream direction. + (0x0014 => pub(crate) ep_out_enable: [ReadWrite; 1]), + /// Enable an endpoint to respond to transactions in the upstream direction. + (0x0018 => pub(crate) ep_in_enable: [ReadWrite; 1]), + /// USB Status + (0x001c => pub(crate) usbstat: ReadWrite), + /// Available Buffer FIFO + (0x0020 => pub(crate) avbuffer: ReadWrite), + /// Received Buffer FIFO + (0x0024 => pub(crate) rxfifo: ReadWrite), + /// Receive SETUP transaction enable + (0x0028 => pub(crate) rxenable_setup: [ReadWrite; 1]), + /// Receive OUT transaction enable + (0x002c => pub(crate) rxenable_out: [ReadWrite; 1]), + /// Set NAK after OUT transactions + (0x0030 => pub(crate) set_nak_out: [ReadWrite; 1]), + /// IN Transaction Sent + (0x0034 => pub(crate) in_sent: [ReadWrite; 1]), + /// OUT Endpoint STALL control + (0x0038 => pub(crate) out_stall: [ReadWrite; 1]), + /// IN Endpoint STALL control + (0x003c => pub(crate) in_stall: [ReadWrite; 1]), + /// Configure IN Transaction + (0x0040 => pub(crate) configin: [ReadWrite; 12]), + /// OUT Endpoint isochronous setting + (0x0070 => pub(crate) out_iso: [ReadWrite; 1]), + /// IN Endpoint isochronous setting + (0x0074 => pub(crate) in_iso: [ReadWrite; 1]), + /// Clear the data toggle flag + (0x0078 => pub(crate) data_toggle_clear: [ReadWrite; 1]), + /// USB PHY pins sense. + (0x007c => pub(crate) phy_pins_sense: ReadWrite), + /// USB PHY pins drive. + (0x0080 => pub(crate) phy_pins_drive: ReadWrite), + /// USB PHY Configuration + (0x0084 => pub(crate) phy_config: ReadWrite), + /// USB wake module control for suspend / resume + (0x0088 => pub(crate) wake_control: ReadWrite), + /// USB wake module events and debug + (0x008c => pub(crate) wake_events: ReadWrite), + (0x0090 => _reserved1), + /// Memory area: 2 kB packet buffer. Divided into 32 64-byte buffers. + (0x0800 => pub(crate) buffer: [ReadWrite; 512]), + (0x1000 => @END), + } +} + +register_bitfields![u32, + /// Common Interrupt Offsets + pub(crate) INTR [ + PKT_RECEIVED OFFSET(0) NUMBITS(1) [], + PKT_SENT OFFSET(1) NUMBITS(1) [], + DISCONNECTED OFFSET(2) NUMBITS(1) [], + HOST_LOST OFFSET(3) NUMBITS(1) [], + LINK_RESET OFFSET(4) NUMBITS(1) [], + LINK_SUSPEND OFFSET(5) NUMBITS(1) [], + LINK_RESUME OFFSET(6) NUMBITS(1) [], + AV_EMPTY OFFSET(7) NUMBITS(1) [], + RX_FULL OFFSET(8) NUMBITS(1) [], + AV_OVERFLOW OFFSET(9) NUMBITS(1) [], + LINK_IN_ERR OFFSET(10) NUMBITS(1) [], + RX_CRC_ERR OFFSET(11) NUMBITS(1) [], + RX_PID_ERR OFFSET(12) NUMBITS(1) [], + RX_BITSTUFF_ERR OFFSET(13) NUMBITS(1) [], + FRAME OFFSET(14) NUMBITS(1) [], + POWERED OFFSET(15) NUMBITS(1) [], + LINK_OUT_ERR OFFSET(16) NUMBITS(1) [], + ], + pub(crate) ALERT_TEST [ + FATAL_FAULT OFFSET(0) NUMBITS(1) [], + ], + pub(crate) USBCTRL [ + ENABLE OFFSET(0) NUMBITS(1) [], + RESUME_LINK_ACTIVE OFFSET(1) NUMBITS(1) [], + DEVICE_ADDRESS OFFSET(16) NUMBITS(7) [], + ], + pub(crate) EP_OUT_ENABLE [ + ENABLE_0 OFFSET(0) NUMBITS(1) [], + ENABLE_1 OFFSET(1) NUMBITS(1) [], + ENABLE_2 OFFSET(2) NUMBITS(1) [], + ENABLE_3 OFFSET(3) NUMBITS(1) [], + ENABLE_4 OFFSET(4) NUMBITS(1) [], + ENABLE_5 OFFSET(5) NUMBITS(1) [], + ENABLE_6 OFFSET(6) NUMBITS(1) [], + ENABLE_7 OFFSET(7) NUMBITS(1) [], + ENABLE_8 OFFSET(8) NUMBITS(1) [], + ENABLE_9 OFFSET(9) NUMBITS(1) [], + ENABLE_10 OFFSET(10) NUMBITS(1) [], + ENABLE_11 OFFSET(11) NUMBITS(1) [], + ], + pub(crate) EP_IN_ENABLE [ + ENABLE_0 OFFSET(0) NUMBITS(1) [], + ENABLE_1 OFFSET(1) NUMBITS(1) [], + ENABLE_2 OFFSET(2) NUMBITS(1) [], + ENABLE_3 OFFSET(3) NUMBITS(1) [], + ENABLE_4 OFFSET(4) NUMBITS(1) [], + ENABLE_5 OFFSET(5) NUMBITS(1) [], + ENABLE_6 OFFSET(6) NUMBITS(1) [], + ENABLE_7 OFFSET(7) NUMBITS(1) [], + ENABLE_8 OFFSET(8) NUMBITS(1) [], + ENABLE_9 OFFSET(9) NUMBITS(1) [], + ENABLE_10 OFFSET(10) NUMBITS(1) [], + ENABLE_11 OFFSET(11) NUMBITS(1) [], + ], + pub(crate) USBSTAT [ + FRAME OFFSET(0) NUMBITS(11) [], + HOST_LOST OFFSET(11) NUMBITS(1) [], + LINK_STATE OFFSET(12) NUMBITS(3) [ + DISCONNECTED = 0, + POWERED = 1, + POWERED_SUSPENDED = 2, + ACTIVE = 3, + SUSPENDED = 4, + ACTIVE_NOSOF = 5, + RESUMING = 6, + ], + SENSE OFFSET(15) NUMBITS(1) [], + AV_DEPTH OFFSET(16) NUMBITS(4) [], + AV_FULL OFFSET(23) NUMBITS(1) [], + RX_DEPTH OFFSET(24) NUMBITS(4) [], + RX_EMPTY OFFSET(31) NUMBITS(1) [], + ], + pub(crate) AVBUFFER [ + BUFFER OFFSET(0) NUMBITS(5) [], + ], + pub(crate) RXFIFO [ + BUFFER OFFSET(0) NUMBITS(5) [], + SIZE OFFSET(8) NUMBITS(7) [], + SETUP OFFSET(19) NUMBITS(1) [], + EP OFFSET(20) NUMBITS(4) [], + ], + pub(crate) RXENABLE_SETUP [ + SETUP_0 OFFSET(0) NUMBITS(1) [], + SETUP_1 OFFSET(1) NUMBITS(1) [], + SETUP_2 OFFSET(2) NUMBITS(1) [], + SETUP_3 OFFSET(3) NUMBITS(1) [], + SETUP_4 OFFSET(4) NUMBITS(1) [], + SETUP_5 OFFSET(5) NUMBITS(1) [], + SETUP_6 OFFSET(6) NUMBITS(1) [], + SETUP_7 OFFSET(7) NUMBITS(1) [], + SETUP_8 OFFSET(8) NUMBITS(1) [], + SETUP_9 OFFSET(9) NUMBITS(1) [], + SETUP_10 OFFSET(10) NUMBITS(1) [], + SETUP_11 OFFSET(11) NUMBITS(1) [], + ], + pub(crate) RXENABLE_OUT [ + OUT_0 OFFSET(0) NUMBITS(1) [], + OUT_1 OFFSET(1) NUMBITS(1) [], + OUT_2 OFFSET(2) NUMBITS(1) [], + OUT_3 OFFSET(3) NUMBITS(1) [], + OUT_4 OFFSET(4) NUMBITS(1) [], + OUT_5 OFFSET(5) NUMBITS(1) [], + OUT_6 OFFSET(6) NUMBITS(1) [], + OUT_7 OFFSET(7) NUMBITS(1) [], + OUT_8 OFFSET(8) NUMBITS(1) [], + OUT_9 OFFSET(9) NUMBITS(1) [], + OUT_10 OFFSET(10) NUMBITS(1) [], + OUT_11 OFFSET(11) NUMBITS(1) [], + ], + pub(crate) SET_NAK_OUT [ + ENABLE_0 OFFSET(0) NUMBITS(1) [], + ENABLE_1 OFFSET(1) NUMBITS(1) [], + ENABLE_2 OFFSET(2) NUMBITS(1) [], + ENABLE_3 OFFSET(3) NUMBITS(1) [], + ENABLE_4 OFFSET(4) NUMBITS(1) [], + ENABLE_5 OFFSET(5) NUMBITS(1) [], + ENABLE_6 OFFSET(6) NUMBITS(1) [], + ENABLE_7 OFFSET(7) NUMBITS(1) [], + ENABLE_8 OFFSET(8) NUMBITS(1) [], + ENABLE_9 OFFSET(9) NUMBITS(1) [], + ENABLE_10 OFFSET(10) NUMBITS(1) [], + ENABLE_11 OFFSET(11) NUMBITS(1) [], + ], + pub(crate) IN_SENT [ + SENT_0 OFFSET(0) NUMBITS(1) [], + SENT_1 OFFSET(1) NUMBITS(1) [], + SENT_2 OFFSET(2) NUMBITS(1) [], + SENT_3 OFFSET(3) NUMBITS(1) [], + SENT_4 OFFSET(4) NUMBITS(1) [], + SENT_5 OFFSET(5) NUMBITS(1) [], + SENT_6 OFFSET(6) NUMBITS(1) [], + SENT_7 OFFSET(7) NUMBITS(1) [], + SENT_8 OFFSET(8) NUMBITS(1) [], + SENT_9 OFFSET(9) NUMBITS(1) [], + SENT_10 OFFSET(10) NUMBITS(1) [], + SENT_11 OFFSET(11) NUMBITS(1) [], + ], + pub(crate) OUT_STALL [ + ENDPOINT_0 OFFSET(0) NUMBITS(1) [], + ENDPOINT_1 OFFSET(1) NUMBITS(1) [], + ENDPOINT_2 OFFSET(2) NUMBITS(1) [], + ENDPOINT_3 OFFSET(3) NUMBITS(1) [], + ENDPOINT_4 OFFSET(4) NUMBITS(1) [], + ENDPOINT_5 OFFSET(5) NUMBITS(1) [], + ENDPOINT_6 OFFSET(6) NUMBITS(1) [], + ENDPOINT_7 OFFSET(7) NUMBITS(1) [], + ENDPOINT_8 OFFSET(8) NUMBITS(1) [], + ENDPOINT_9 OFFSET(9) NUMBITS(1) [], + ENDPOINT_10 OFFSET(10) NUMBITS(1) [], + ENDPOINT_11 OFFSET(11) NUMBITS(1) [], + ], + pub(crate) IN_STALL [ + ENDPOINT_0 OFFSET(0) NUMBITS(1) [], + ENDPOINT_1 OFFSET(1) NUMBITS(1) [], + ENDPOINT_2 OFFSET(2) NUMBITS(1) [], + ENDPOINT_3 OFFSET(3) NUMBITS(1) [], + ENDPOINT_4 OFFSET(4) NUMBITS(1) [], + ENDPOINT_5 OFFSET(5) NUMBITS(1) [], + ENDPOINT_6 OFFSET(6) NUMBITS(1) [], + ENDPOINT_7 OFFSET(7) NUMBITS(1) [], + ENDPOINT_8 OFFSET(8) NUMBITS(1) [], + ENDPOINT_9 OFFSET(9) NUMBITS(1) [], + ENDPOINT_10 OFFSET(10) NUMBITS(1) [], + ENDPOINT_11 OFFSET(11) NUMBITS(1) [], + ], + pub(crate) CONFIGIN [ + BUFFER_0 OFFSET(0) NUMBITS(5) [], + SIZE_0 OFFSET(8) NUMBITS(7) [], + PEND_0 OFFSET(30) NUMBITS(1) [], + RDY_0 OFFSET(31) NUMBITS(1) [], + ], + pub(crate) OUT_ISO [ + ISO_0 OFFSET(0) NUMBITS(1) [], + ISO_1 OFFSET(1) NUMBITS(1) [], + ISO_2 OFFSET(2) NUMBITS(1) [], + ISO_3 OFFSET(3) NUMBITS(1) [], + ISO_4 OFFSET(4) NUMBITS(1) [], + ISO_5 OFFSET(5) NUMBITS(1) [], + ISO_6 OFFSET(6) NUMBITS(1) [], + ISO_7 OFFSET(7) NUMBITS(1) [], + ISO_8 OFFSET(8) NUMBITS(1) [], + ISO_9 OFFSET(9) NUMBITS(1) [], + ISO_10 OFFSET(10) NUMBITS(1) [], + ISO_11 OFFSET(11) NUMBITS(1) [], + ], + pub(crate) IN_ISO [ + ISO_0 OFFSET(0) NUMBITS(1) [], + ISO_1 OFFSET(1) NUMBITS(1) [], + ISO_2 OFFSET(2) NUMBITS(1) [], + ISO_3 OFFSET(3) NUMBITS(1) [], + ISO_4 OFFSET(4) NUMBITS(1) [], + ISO_5 OFFSET(5) NUMBITS(1) [], + ISO_6 OFFSET(6) NUMBITS(1) [], + ISO_7 OFFSET(7) NUMBITS(1) [], + ISO_8 OFFSET(8) NUMBITS(1) [], + ISO_9 OFFSET(9) NUMBITS(1) [], + ISO_10 OFFSET(10) NUMBITS(1) [], + ISO_11 OFFSET(11) NUMBITS(1) [], + ], + pub(crate) DATA_TOGGLE_CLEAR [ + CLEAR_0 OFFSET(0) NUMBITS(1) [], + CLEAR_1 OFFSET(1) NUMBITS(1) [], + CLEAR_2 OFFSET(2) NUMBITS(1) [], + CLEAR_3 OFFSET(3) NUMBITS(1) [], + CLEAR_4 OFFSET(4) NUMBITS(1) [], + CLEAR_5 OFFSET(5) NUMBITS(1) [], + CLEAR_6 OFFSET(6) NUMBITS(1) [], + CLEAR_7 OFFSET(7) NUMBITS(1) [], + CLEAR_8 OFFSET(8) NUMBITS(1) [], + CLEAR_9 OFFSET(9) NUMBITS(1) [], + CLEAR_10 OFFSET(10) NUMBITS(1) [], + CLEAR_11 OFFSET(11) NUMBITS(1) [], + ], + pub(crate) PHY_PINS_SENSE [ + RX_DP_I OFFSET(0) NUMBITS(1) [], + RX_DN_I OFFSET(1) NUMBITS(1) [], + RX_D_I OFFSET(2) NUMBITS(1) [], + TX_DP_O OFFSET(8) NUMBITS(1) [], + TX_DN_O OFFSET(9) NUMBITS(1) [], + TX_D_O OFFSET(10) NUMBITS(1) [], + TX_SE0_O OFFSET(11) NUMBITS(1) [], + TX_OE_O OFFSET(12) NUMBITS(1) [], + PWR_SENSE OFFSET(16) NUMBITS(1) [], + ], + pub(crate) PHY_PINS_DRIVE [ + DP_O OFFSET(0) NUMBITS(1) [], + DN_O OFFSET(1) NUMBITS(1) [], + D_O OFFSET(2) NUMBITS(1) [], + SE0_O OFFSET(3) NUMBITS(1) [], + OE_O OFFSET(4) NUMBITS(1) [], + RX_ENABLE_O OFFSET(5) NUMBITS(1) [], + DP_PULLUP_EN_O OFFSET(6) NUMBITS(1) [], + DN_PULLUP_EN_O OFFSET(7) NUMBITS(1) [], + EN OFFSET(16) NUMBITS(1) [], + ], + pub(crate) PHY_CONFIG [ + USE_DIFF_RCVR OFFSET(0) NUMBITS(1) [], + TX_USE_D_SE0 OFFSET(1) NUMBITS(1) [], + EOP_SINGLE_BIT OFFSET(2) NUMBITS(1) [], + PINFLIP OFFSET(5) NUMBITS(1) [], + USB_REF_DISABLE OFFSET(6) NUMBITS(1) [], + TX_OSC_TEST_MODE OFFSET(7) NUMBITS(1) [], + ], + pub(crate) WAKE_CONTROL [ + SUSPEND_REQ OFFSET(0) NUMBITS(1) [], + WAKE_ACK OFFSET(1) NUMBITS(1) [], + ], + pub(crate) WAKE_EVENTS [ + MODULE_ACTIVE OFFSET(0) NUMBITS(1) [], + DISCONNECTED OFFSET(8) NUMBITS(1) [], + BUS_RESET OFFSET(9) NUMBITS(1) [], + ], +]; + +// End generated register constants for usbdev diff --git a/chips/lowrisc/src/rsa.rs b/chips/lowrisc/src/rsa.rs new file mode 100644 index 0000000000..68ace6ace1 --- /dev/null +++ b/chips/lowrisc/src/rsa.rs @@ -0,0 +1,251 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! RSA Implemented on top of the OTBN + +use crate::virtual_otbn::VirtualMuxAccel; +use kernel::hil::public_key_crypto::rsa_math::{Client, ClientMut, RsaCryptoBase}; +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::cells::TakeCell; +use kernel::utilities::mut_imut_buffer::MutImutBuffer; +use kernel::ErrorCode; + +pub struct AppAddresses { + pub imem_start: usize, + pub imem_size: usize, + pub dmem_start: usize, + pub dmem_size: usize, +} + +pub struct OtbnRsa<'a> { + otbn: &'a VirtualMuxAccel<'a>, + client: OptionalCell<&'a dyn Client<'a>>, + client_mut: OptionalCell<&'a dyn ClientMut<'a>>, + + internal: TakeCell<'static, [u8]>, + + message: TakeCell<'static, [u8]>, + modulus: OptionalCell>, + exponent: OptionalCell>, + + rsa: AppAddresses, +} + +impl<'a> OtbnRsa<'a> { + pub fn new( + otbn: &'a VirtualMuxAccel<'a>, + rsa: AppAddresses, + internal_buffer: &'static mut [u8], + ) -> Self { + OtbnRsa { + otbn, + client: OptionalCell::empty(), + client_mut: OptionalCell::empty(), + internal: TakeCell::new(internal_buffer), + message: TakeCell::empty(), + modulus: OptionalCell::empty(), + exponent: OptionalCell::empty(), + rsa, + } + } + + fn report_error(&self, error: ErrorCode, result: &'static mut [u8]) { + match self.exponent.take().unwrap() { + MutImutBuffer::Mutable(exponent) => { + self.client_mut.map(|client| { + match self.modulus.take().unwrap() { + MutImutBuffer::Mutable(modulus) => { + client.mod_exponent_done( + Err(error), + self.message.take().unwrap(), + modulus, + exponent, + result, + ); + } + MutImutBuffer::Immutable(_) => unreachable!(), + }; + }); + } + MutImutBuffer::Immutable(exponent) => { + match self.modulus.take().unwrap() { + MutImutBuffer::Immutable(modulus) => { + self.client.map(|client| { + client.mod_exponent_done( + Err(error), + self.message.take().unwrap(), + modulus, + exponent, + result, + ); + }); + } + MutImutBuffer::Mutable(_) => unreachable!(), + }; + } + } + } +} + +impl<'a> crate::otbn::Client<'a> for OtbnRsa<'a> { + fn op_done(&'a self, result: Result<(), ErrorCode>, output: &'static mut [u8]) { + if let Err(e) = result { + self.report_error(e, output); + return; + } + + // We want to return BE data + output.reverse(); + + match self.exponent.take().unwrap() { + MutImutBuffer::Mutable(exponent) => { + self.client_mut.map(|client| { + match self.modulus.take().unwrap() { + MutImutBuffer::Mutable(modulus) => { + client.mod_exponent_done( + Ok(true), + self.message.take().unwrap(), + modulus, + exponent, + output, + ); + } + MutImutBuffer::Immutable(_) => unreachable!(), + }; + }); + } + MutImutBuffer::Immutable(exponent) => { + match self.modulus.take().unwrap() { + MutImutBuffer::Immutable(modulus) => { + self.client.map(|client| { + client.mod_exponent_done( + Ok(true), + self.message.take().unwrap(), + modulus, + exponent, + output, + ); + }); + } + MutImutBuffer::Mutable(_) => unreachable!(), + }; + } + } + } +} + +impl<'a> RsaCryptoBase<'a> for OtbnRsa<'a> { + fn set_client(&'a self, client: &'a dyn Client<'a>) { + self.client.set(client); + } + + fn clear_data(&self) { + self.otbn.clear_data(); + } + + fn mod_exponent( + &self, + message: &'static mut [u8], + modulus: &'static [u8], + exponent: &'static [u8], + result: &'static mut [u8], + ) -> Result< + (), + ( + ErrorCode, + &'static mut [u8], + &'static [u8], + &'static [u8], + &'static mut [u8], + ), + > { + // Check that the lengths match our expectations + let op_len = modulus.len(); + + if result.len() < op_len { + return Err((ErrorCode::SIZE, message, modulus, exponent, result)); + } + + let slice = unsafe { + core::slice::from_raw_parts(self.rsa.imem_start as *mut u8, self.rsa.imem_size) + }; + if let Err(e) = self.otbn.load_binary(slice) { + return Err((e, message, modulus, exponent, result)); + } + + let slice = unsafe { + core::slice::from_raw_parts(self.rsa.dmem_start as *mut u8, self.rsa.dmem_size) + }; + if let Err(e) = self.otbn.load_data(0, slice) { + return Err((e, message, modulus, exponent, result)); + } + + // Set the mode to decryption + if let Some(data) = self.internal.take() { + data[0] = 2; + data[1] = 0; + data[2] = 0; + data[3] = 0; + // Set the RSA mode + // The address is the offset of `mode` in the RSA elf + if let Err(e) = self.otbn.load_data(0, &data[0..4]) { + return Err((e, message, modulus, exponent, result)); + } + + data[0] = (op_len / 32) as u8; + data[1] = 0; + data[2] = 0; + data[3] = 0; + // Set the RSA length + // The address is the offset of `n_limbs` in the RSA elf + if let Err(e) = self.otbn.load_data(4, &data[0..4]) { + return Err((e, message, modulus, exponent, result)); + } + + data[0..op_len].copy_from_slice(modulus); + // We were passed BE data and the OTBN expects LE + // so reverse the order. + data[0..op_len].reverse(); + // Set the RSA modulus + // The address is the offset of `modulus` in the RSA elf + if let Err(e) = self.otbn.load_data(0x20, &data[0..op_len]) { + return Err((e, message, modulus, exponent, result)); + } + + let len = exponent.len().min(op_len); + data[0..len].copy_from_slice(exponent); + // We were passed BE data and the OTBN expects LE + // so reverse the order. + data[0..len].reverse(); + + // Set the RSA exponent + // The address is the offset of `exp` in the RSA elf + if let Err(e) = self.otbn.load_data(0x220, &data[0..len]) { + return Err((e, message, modulus, exponent, result)); + } + + self.internal.replace(data); + } else { + return Err((ErrorCode::NOMEM, message, modulus, exponent, result)); + } + + // Set the data in + // The address is the offset of `inout` in the RSA elf + if let Err(e) = self.otbn.load_data(0x420, message) { + return Err((e, message, modulus, exponent, result)); + } + + self.message.replace(message); + self.modulus.replace(MutImutBuffer::Immutable(modulus)); + self.exponent.replace(MutImutBuffer::Immutable(exponent)); + + // Get the data out + // The address is the offset of `inout` in the RSA elf + if let Err(e) = self.otbn.run(0x420, result) { + return Err((e.0, self.message.take().unwrap(), modulus, exponent, e.1)); + } + + Ok(()) + } +} diff --git a/chips/lowrisc/src/spi_host.rs b/chips/lowrisc/src/spi_host.rs new file mode 100644 index 0000000000..7e013eef36 --- /dev/null +++ b/chips/lowrisc/src/spi_host.rs @@ -0,0 +1,736 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Serial Peripheral Interface (SPI) Host Driver +use core::cell::Cell; +use core::cmp; +use kernel::hil; +use kernel::hil::spi::SpiMaster; +use kernel::hil::spi::{ClockPhase, ClockPolarity}; +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::cells::TakeCell; +use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; +use kernel::utilities::registers::{ + register_bitfields, register_structs, ReadOnly, ReadWrite, WriteOnly, +}; +use kernel::utilities::StaticRef; +use kernel::ErrorCode; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum SpiHostStatus { + SpiTransferCmplt, + SpiTransferInprog, +} + +register_structs! { + pub SpiHostRegisters { + //SPI: Interrupt State Register, type rw1c + (0x000 => intr_state: ReadWrite), + //SPI: Interrupt Enable Register + (0x004 => intr_enable: ReadWrite), + //SPI: Interrupt Test Register + (0x008 => intr_test: WriteOnly), + //SPI: Alert Test Register + (0x00c => alert_test: WriteOnly), + //SPI: Control register + (0x010 => ctrl: ReadWrite), + //SPI: Status register + (0x014 => status: ReadOnly), + //SPI: Configuration options register. + (0x018 => config_opts: ReadWrite), + //SPI: Chip-Select ID + (0x01c => csid: ReadWrite), + //SPI: Command Register + (0x020 => command: WriteOnly), + //SPI: Received Data + (0x024 => rx_data: ReadWrite), + //SPI: Transmit Data + (0x028 => tx_data: WriteOnly), + //SPI: Controls which classes of errors raise an interrupt. + (0x02c => err_en: ReadWrite), + //SPI: Indicates that any errors that have occurred, type rw1c + (0x030 => err_status: ReadWrite), + //SPI: Controls which classes of SPI events raise an interrupt + (0x034 => event_en: ReadWrite), + (0x38 => @END), + } +} + +register_bitfields![u32, + intr [ + ERROR OFFSET(0) NUMBITS(1) [], + SPI_EVENT OFFSET(1) NUMBITS(1) [], + ], + alert_test [ + FETAL_FAULT OFFSET(0) NUMBITS(1) [], + ], + ctrl [ + RX_WATERMARK OFFSET(0) NUMBITS(8) [], + TX_WATERMARK OFFSET(8) NUMBITS(8) [], + //28:16 RESERVED + OUTPUT_EN OFFSET(29) NUMBITS(1) [], + SW_RST OFFSET(30) NUMBITS(1) [], + SPIEN OFFSET(31) NUMBITS(1) [] + ], + status [ + TXQD OFFSET(0) NUMBITS(8) [], + RXQD OFFSET(15) NUMBITS(8) [], + CMDQD OFFSET(16) NUMBITS(1) [], + RXWM OFFSET(20) NUMBITS(1) [], + BYTEORDER OFFSET(22) NUMBITS(1) [], + RXSTALL OFFSET(23) NUMBITS(1) [], + RXEMPTY OFFSET(24) NUMBITS(1) [], + RXFULL OFFSET(25) NUMBITS(1) [], + TXWM OFFSET(26) NUMBITS(1) [], + TXSTALL OFFSET(27) NUMBITS(1) [], + TXEMPTY OFFSET(28) NUMBITS(1) [], + TXFULL OFFSET(29) NUMBITS(1) [], + ACTIVE OFFSET(30) NUMBITS(1) [], + READY OFFSET(31) NUMBITS(1) [], + ], + conf_opts [ + CLKDIV_0 OFFSET(0) NUMBITS(16) [], + CSNIDLE_0 OFFSET(16) NUMBITS(3) [], + CSNTRAIL_0 OFFSET(20) NUMBITS(3) [], + CSNLEAD_0 OFFSET(24) NUMBITS(3) [], + //28 Reserved + FULLCYC_0 OFFSET(29) NUMBITS(1) [], + CPHA_0 OFFSET(30) NUMBITS(1) [], + CPOL_0 OFFSET(31) NUMBITS(1) [], + ], + csid_ctrl [ + CSID OFFSET(0) NUMBITS(32) [], + ], + command [ + LEN OFFSET(0) NUMBITS(8) [], + CSAAT OFFSET(9) NUMBITS(1) [], + SPEED OFFSET(10) NUMBITS(2) [], + DIRECTION OFFSET(12) NUMBITS(2) [], + ], + rx_data [ + DATA OFFSET(0) NUMBITS(32) [], + ], + tx_data [ + DATA OFFSET(0) NUMBITS(32) [], + ], + err_en [ + CMDBUSY OFFSET(0) NUMBITS(1) [], + OVERFLOW OFFSET(1) NUMBITS(1) [], + UNDERFLOW OFFSET(2) NUMBITS(1) [], + CMDINVAL OFFSET(3) NUMBITS(1) [], + CSIDINVAL OFFSET(4) NUMBITS(1) [], + ], + err_status [ + CMDBUSY OFFSET(0) NUMBITS(1) [], + OVERFLOW OFFSET(1) NUMBITS(1) [], + UNDERFLOW OFFSET(2) NUMBITS(1) [], + CMDINVAL OFFSET(3) NUMBITS(1) [], + CSIDINVAL OFFSET(4) NUMBITS(1) [], + ACCESSINVAL OFFSET(5) NUMBITS(1) [], + ], + event_en [ + RXFULL OFFSET(0) NUMBITS(1) [], + TXEMPTY OFFSET(1) NUMBITS(1) [], + RXWM OFFSET(2) NUMBITS(1) [], + TXWM OFFSET(3) NUMBITS(1) [], + READY OFFSET(4) NUMBITS(1) [], + IDLE OFFSET(5) NUMBITS(1) [], + ], +]; + +pub struct SpiHost<'a> { + registers: StaticRef, + client: OptionalCell<&'a dyn hil::spi::SpiMasterClient>, + busy: Cell, + chip_select: Cell, + cpu_clk: u32, + tsclk: Cell, + tx_buf: TakeCell<'static, [u8]>, + rx_buf: TakeCell<'static, [u8]>, + tx_len: Cell, + rx_len: Cell, + tx_offset: Cell, + rx_offset: Cell, +} +// SPI Host Command Direction: Bidirectional +const SPI_HOST_CMD_BIDIRECTIONAL: u32 = 3; +// SPI Host Command Speed: Standard SPI +const SPI_HOST_CMD_STANDARD_SPI: u32 = 0; + +impl<'a> SpiHost<'a> { + pub fn new(base: StaticRef, cpu_clk: u32) -> Self { + SpiHost { + registers: base, + client: OptionalCell::empty(), + busy: Cell::new(false), + chip_select: Cell::new(0), + cpu_clk, + tsclk: Cell::new(0), + tx_buf: TakeCell::empty(), + rx_buf: TakeCell::empty(), + tx_len: Cell::new(0), + rx_len: Cell::new(0), + tx_offset: Cell::new(0), + rx_offset: Cell::new(0), + } + } + + pub fn handle_interrupt(&self) { + let regs = self.registers; + let irq = regs.intr_state.extract(); + self.disable_interrupts(); + + if irq.is_set(intr::ERROR) { + //Clear all pending errors. + self.clear_err_interrupt(); + //Something went wrong, reset IP and clear buffers + self.reset_spi_ip(); + self.reset_internal_state(); + //r/w_done() may call r/w_bytes() to re-attempt transfer + self.client.map(|client| match self.tx_buf.take() { + None => (), + Some(tx_buf) => client.read_write_done( + tx_buf, + self.rx_buf.take(), + self.tx_offset.get(), + Err(ErrorCode::FAIL), + ), + }); + return; + } + + if irq.is_set(intr::SPI_EVENT) { + let status = regs.status.extract(); + self.clear_event_interrupt(); + + //This could be set at init, so only follow through + //once a transfer has started (is_busy()) + if status.is_set(status::TXEMPTY) && self.is_busy() { + match self.continue_transfer() { + Ok(SpiHostStatus::SpiTransferCmplt) => { + // Transfer success + self.client.map(|client| match self.tx_buf.take() { + None => (), + Some(tx_buf) => client.read_write_done( + tx_buf, + self.rx_buf.take(), + self.tx_len.get(), + Ok(()), + ), + }); + + self.disable_tx_interrupt(); + self.reset_internal_state(); + } + Ok(SpiHostStatus::SpiTransferInprog) => {} + Err(err) => { + //Transfer failed, lets clean up + //Clear all pending interrupts. + self.clear_err_interrupt(); + //Something went wrong, reset IP and clear buffers + self.reset_spi_ip(); + self.reset_internal_state(); + self.client.map(|client| match self.tx_buf.take() { + None => (), + Some(tx_buf) => client.read_write_done( + tx_buf, + self.rx_buf.take(), + self.tx_offset.get(), + Err(err), + ), + }); + } + } + } else { + self.enable_interrupts(); + } + } + } + + //Determine if transfer complete or if we need to keep + //writing from an offset. + fn continue_transfer(&self) -> Result { + let rc = self + .rx_buf + .take() + .map(|rx_buf| -> Result { + let regs = self.registers; + let mut val32: u32; + let mut val8: u8; + let mut shift_mask; + let rx_len = self.tx_offset.get() - self.rx_offset.get(); + let read_cycles = self.div_up(rx_len, 4); + + //Receive rx_data (Only 4byte reads are supported) + for _n in 0..read_cycles { + val32 = regs.rx_data.read(rx_data::DATA); + shift_mask = 0xFF; + for i in 0..4 { + if self.rx_offset.get() >= self.rx_len.get() { + break; + } + val8 = ((val32 & shift_mask) >> (i * 8)) as u8; + if let Some(ptr) = rx_buf.get_mut(self.rx_offset.get()) { + *ptr = val8; + } else { + // We have run out of rx buffer size + break; + } + self.rx_offset.set(self.rx_offset.get() + 1); + shift_mask <<= 8; + } + } + //Save buffer! + self.rx_buf.replace(rx_buf); + //Transfer was complete */ + if self.tx_offset.get() == self.tx_len.get() { + Ok(SpiHostStatus::SpiTransferCmplt) + } else { + //Theres more to transfer, continue writing from the offset + self.spi_transfer_progress() + } + }) + .map_or_else(|| Err(ErrorCode::FAIL), |rc| rc); + + rc + } + + /// Continue SPI transfer from offset point + fn spi_transfer_progress(&self) -> Result { + let mut transfer_complete = false; + if self + .tx_buf + .take() + .map(|tx_buf| -> Result<(), ErrorCode> { + let regs = self.registers; + let mut t_byte: u32; + let mut tx_slice: [u8; 4]; + + if regs.status.read(status::TXQD) != 0 || regs.status.read(status::ACTIVE) != 0 { + self.tx_buf.replace(tx_buf); + return Err(ErrorCode::BUSY); + } + + while !regs.status.is_set(status::TXFULL) && regs.status.read(status::TXQD) < 64 { + tx_slice = [0, 0, 0, 0]; + for elem in tx_slice.iter_mut() { + if self.tx_offset.get() >= self.tx_len.get() { + break; + } + if let Some(val) = tx_buf.get(self.tx_offset.get()) { + *elem = *val; + self.tx_offset.set(self.tx_offset.get() + 1); + } else { + //Unexpectedly ran out of tx buffer + break; + } + } + t_byte = u32::from_le_bytes(tx_slice); + regs.tx_data.write(tx_data::DATA.val(t_byte)); + + //Transfer Complete in one-shot + if self.tx_offset.get() >= self.tx_len.get() { + transfer_complete = true; + break; + } + } + + //Hold tx_buf for offset transfer continue + self.tx_buf.replace(tx_buf); + + //Set command register to init transfer + self.start_transceive(); + Ok(()) + }) + .transpose() + .is_err() + { + return Err(ErrorCode::BUSY); + } + + if transfer_complete { + Ok(SpiHostStatus::SpiTransferCmplt) + } else { + Ok(SpiHostStatus::SpiTransferInprog) + } + } + + /// Issue a command to start SPI transaction + /// Currently only Bi-Directional transactions are supported + fn start_transceive(&self) { + let regs = self.registers; + //TXQD holds number of 32bit words + let txfifo_num_bytes = regs.status.read(status::TXQD) * 4; + + //8-bits that describe command transfer len (cannot exceed 255) + let num_transfer_bytes: u32 = if txfifo_num_bytes > u8::MAX as u32 { + u8::MAX as u32 + } else { + txfifo_num_bytes + }; + + self.enable_interrupts(); + self.enable_tx_interrupt(); + + //Flush all data in TXFIFO and assert CSAAT for all + // but the last transfer segment. + if self.tx_offset.get() >= self.tx_len.get() { + regs.command.write( + command::LEN.val(num_transfer_bytes) + + command::DIRECTION.val(SPI_HOST_CMD_BIDIRECTIONAL) + + command::CSAAT::CLEAR + + command::SPEED.val(SPI_HOST_CMD_STANDARD_SPI), + ); + } else { + regs.command.write( + command::LEN.val(num_transfer_bytes) + + command::DIRECTION.val(SPI_HOST_CMD_BIDIRECTIONAL) + + command::CSAAT::SET + + command::SPEED.val(SPI_HOST_CMD_STANDARD_SPI), + ); + } + } + + /// Reset the soft internal state, should be called once + /// a spi transaction has been completed. + fn reset_internal_state(&self) { + self.clear_spi_busy(); + self.tx_len.set(0); + self.rx_len.set(0); + self.tx_offset.set(0); + self.rx_offset.set(0); + + debug_assert!(self.tx_buf.is_none()); + debug_assert!(self.rx_buf.is_none()); + } + + /// Enable SPI_HOST IP + /// `dead_code` to silence warnings when not building for mainline qemu + #[allow(dead_code)] + fn enable_spi_host(&self) { + let regs = self.registers; + //Enables the SPI host + regs.ctrl.modify(ctrl::SPIEN::SET + ctrl::OUTPUT_EN::SET); + } + + /// Reset SPI Host + fn reset_spi_ip(&self) { + let regs = self.registers; + //IP to reset state + regs.ctrl.modify(ctrl::SW_RST::SET); + + //Wait for status ready to be set before continuing + while regs.status.is_set(status::ACTIVE) {} + //Wait for both FIFOs to completely drain + while regs.status.read(status::TXQD) != 0 && regs.status.read(status::RXQD) != 0 {} + //Clear Reset + regs.ctrl.modify(ctrl::SW_RST::CLEAR); + } + + /// Enable both event/err IRQ + fn enable_interrupts(&self) { + self.registers + .intr_state + .write(intr::ERROR::SET + intr::SPI_EVENT::SET); + self.registers + .intr_enable + .modify(intr::ERROR::SET + intr::SPI_EVENT::SET); + } + + /// Disable both event/err IRQ + fn disable_interrupts(&self) { + let regs = self.registers; + regs.intr_enable + .write(intr::ERROR::CLEAR + intr::SPI_EVENT::CLEAR); + } + + /// Clear the error IRQ + fn clear_err_interrupt(&self) { + let regs = self.registers; + //Clear Error Masks (rw1c) + regs.err_status.modify(err_status::CMDBUSY::SET); + regs.err_status.modify(err_status::OVERFLOW::SET); + regs.err_status.modify(err_status::UNDERFLOW::SET); + regs.err_status.modify(err_status::CMDINVAL::SET); + regs.err_status.modify(err_status::CSIDINVAL::SET); + regs.err_status.modify(err_status::ACCESSINVAL::SET); + //Clear Error IRQ + regs.intr_state.modify(intr::ERROR::SET); + } + + /// Clear the event IRQ + fn clear_event_interrupt(&self) { + let regs = self.registers; + regs.intr_state.modify(intr::SPI_EVENT::SET); + } + /// Will generate a `test` interrupt on the error irq + /// Note: Left to allow debug accessibility + #[allow(dead_code)] + fn test_error_interrupt(&self) { + let regs = self.registers; + regs.intr_test.write(intr::ERROR::SET); + } + /// Clear test interrupts + /// Note: Left to allow debug accessibility + #[allow(dead_code)] + fn clear_tests(&self) { + let regs = self.registers; + regs.intr_test + .write(intr::ERROR::CLEAR + intr::SPI_EVENT::CLEAR); + } + + /// Will generate a `test` interrupt on the event irq + /// Note: Left to allow debug accessibility + #[allow(dead_code)] + fn test_event_interrupt(&self) { + let regs = self.registers; + regs.intr_test.write(intr::SPI_EVENT::SET); + } + + /// Enable required `event interrupts` + /// `dead_code` to silence warnings when not building for mainline qemu + #[allow(dead_code)] + fn event_enable(&self) { + let regs = self.registers; + regs.event_en.write(event_en::TXEMPTY::SET); + } + + fn disable_tx_interrupt(&self) { + let regs = self.registers; + regs.event_en.modify(event_en::TXEMPTY::CLEAR); + } + + fn enable_tx_interrupt(&self) { + let regs = self.registers; + regs.event_en.modify(event_en::TXEMPTY::SET); + } + + /// Enable required error interrupts + /// `dead_code` to silence warnings when not building for mainline qemu + #[allow(dead_code)] + fn err_enable(&self) { + let regs = self.registers; + regs.err_en.modify( + err_en::CMDBUSY::SET + + err_en::CMDINVAL::SET + + err_en::CSIDINVAL::SET + + err_en::OVERFLOW::SET + + err_en::UNDERFLOW::SET, + ); + } + + fn set_spi_busy(&self) { + self.busy.set(true); + } + + fn clear_spi_busy(&self) { + self.busy.set(false); + } + + /// Divide a/b and return a value always rounded + /// up to the nearest integer + fn div_up(&self, a: usize, b: usize) -> usize { + (a + (b - 1)) / b + } + + /// Calculate the scaler based on a specified tsclk rate + /// This scaler will pre-scale the cpu_clk and must be <= cpu_clk/2 + fn calculate_tsck_scaler(&self, rate: u32) -> Result { + if rate > self.cpu_clk / 2 { + return Err(ErrorCode::NOSUPPORT); + } + //Divide and truncate + let mut scaler: u32 = (self.cpu_clk / (2 * rate)) - 1; + + //Increase scaler if the division was not exact, ensuring that it does not overflow + //or exceed divider specification where tsck is at most <= Tclk/2 + if self.cpu_clk % (2 * rate) != 0 && scaler != 0xFF { + scaler += 1; + } + Ok(scaler as u16) + } +} + +impl<'a> hil::spi::SpiMaster<'a> for SpiHost<'a> { + type ChipSelect = u32; + + fn init(&self) -> Result<(), ErrorCode> { + let regs = self.registers; + self.event_enable(); + self.err_enable(); + + self.enable_interrupts(); + + self.enable_spi_host(); + + //TODO: I think this is bug in OT, where the `first` word written + // (while TXEMPTY) to TX_DATA is dropped/ignored and not added to TX_FIFO (TXQD = 0). + // The following write (0x00), works around this `bug`. + // Could be Verilator specific + regs.tx_data.write(tx_data::DATA.val(0x00)); + assert_eq!(regs.status.read(status::TXQD), 0); + Ok(()) + } + + fn set_client(&self, client: &'a dyn hil::spi::SpiMasterClient) { + self.client.set(client); + } + + fn is_busy(&self) -> bool { + self.busy.get() + } + + fn read_write_bytes( + &self, + tx_buf: &'static mut [u8], + rx_buf: Option<&'static mut [u8]>, + len: usize, + ) -> Result<(), (ErrorCode, &'static mut [u8], Option<&'static mut [u8]>)> { + debug_assert!(!self.busy.get()); + debug_assert!(self.tx_buf.is_none()); + debug_assert!(self.rx_buf.is_none()); + let regs = self.registers; + + if self.is_busy() || regs.status.is_set(status::TXFULL) { + return Err((ErrorCode::BUSY, tx_buf, rx_buf)); + } + + if rx_buf.is_none() { + return Err((ErrorCode::NOMEM, tx_buf, rx_buf)); + } + + self.tx_len.set(cmp::min(len, tx_buf.len())); + + let mut t_byte: u32; + let mut tx_slice: [u8; 4]; + //We are committing to the transfer now + self.set_spi_busy(); + + while !regs.status.is_set(status::TXFULL) && regs.status.read(status::TXQD) < 64 { + tx_slice = [0, 0, 0, 0]; + for elem in tx_slice.iter_mut() { + if self.tx_offset.get() >= self.tx_len.get() { + break; + } + *elem = tx_buf[self.tx_offset.get()]; + self.tx_offset.set(self.tx_offset.get() + 1); + } + t_byte = u32::from_le_bytes(tx_slice); + regs.tx_data.write(tx_data::DATA.val(t_byte)); + + //Transfer Complete in one-shot + if self.tx_offset.get() >= self.tx_len.get() { + break; + } + } + + //Hold tx_buf for offset transfer continue + self.tx_buf.replace(tx_buf); + + //Hold rx_buf for later + + rx_buf.map(|rx_buf_t| { + self.rx_len.set(cmp::min(self.tx_len.get(), rx_buf_t.len())); + self.rx_buf.replace(rx_buf_t); + }); + + //Set command register to init transfer + self.start_transceive(); + + Ok(()) + } + + fn write_byte(&self, _val: u8) -> Result<(), ErrorCode> { + //Use `read_write_bytes()` instead. + Err(ErrorCode::FAIL) + } + + fn read_byte(&self) -> Result { + //Use `read_write_bytes()` instead. + Err(ErrorCode::FAIL) + } + + fn read_write_byte(&self, _val: u8) -> Result { + //Use `read_write_bytes()` instead. + Err(ErrorCode::FAIL) + } + + fn specify_chip_select(&self, cs: Self::ChipSelect) -> Result<(), ErrorCode> { + let regs = self.registers; + + //CSID will index the CONFIGOPTS multi-register + regs.csid.write(csid_ctrl::CSID.val(cs)); + self.chip_select.set(cs); + + Ok(()) + } + + fn set_rate(&self, rate: u32) -> Result { + let regs = self.registers; + + match self.calculate_tsck_scaler(rate) { + Ok(scaler) => { + regs.config_opts + .modify(conf_opts::CLKDIV_0.val(scaler as u32)); + self.tsclk.set(rate); + Ok(rate) + } + Err(e) => Err(e), + } + } + + fn get_rate(&self) -> u32 { + self.tsclk.get() + } + + fn set_polarity(&self, polarity: ClockPolarity) -> Result<(), ErrorCode> { + let regs = self.registers; + match polarity { + ClockPolarity::IdleLow => regs.config_opts.modify(conf_opts::CPOL_0::CLEAR), + ClockPolarity::IdleHigh => regs.config_opts.modify(conf_opts::CPOL_0::SET), + }; + Ok(()) + } + + fn get_polarity(&self) -> ClockPolarity { + let regs = self.registers; + + match regs.config_opts.read(conf_opts::CPOL_0) { + 0 => ClockPolarity::IdleLow, + 1 => ClockPolarity::IdleHigh, + _ => unreachable!(), + } + } + + fn set_phase(&self, phase: ClockPhase) -> Result<(), ErrorCode> { + let regs = self.registers; + match phase { + ClockPhase::SampleLeading => regs.config_opts.modify(conf_opts::CPHA_0::CLEAR), + ClockPhase::SampleTrailing => regs.config_opts.modify(conf_opts::CPHA_0::SET), + }; + Ok(()) + } + + fn get_phase(&self) -> ClockPhase { + let regs = self.registers; + + match regs.config_opts.read(conf_opts::CPHA_0) { + 1 => ClockPhase::SampleTrailing, + 0 => ClockPhase::SampleLeading, + _ => unreachable!(), + } + } + + /// hold_low is controlled by IP based on command segments issued + /// force holds are not supported + fn hold_low(&self) { + unimplemented!("spi_host: does not support hold low"); + } + + /// release_low is controlled by IP based on command segments issued + /// force releases are not supported + fn release_low(&self) { + unimplemented!("spi_host: does not support release low"); + } +} diff --git a/chips/lowrisc/src/uart.rs b/chips/lowrisc/src/uart.rs index b18fa5c2e2..4639cc1a57 100644 --- a/chips/lowrisc/src/uart.rs +++ b/chips/lowrisc/src/uart.rs @@ -1,110 +1,29 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! UART driver. use core::cell::Cell; use kernel::ErrorCode; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; use kernel::hil; use kernel::hil::uart; use kernel::utilities::cells::OptionalCell; use kernel::utilities::cells::TakeCell; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; -use kernel::utilities::registers::{ - register_bitfields, register_structs, ReadOnly, ReadWrite, WriteOnly, -}; use kernel::utilities::StaticRef; -register_structs! { - pub UartRegisters { - (0x00 => intr_state: ReadWrite), - (0x04 => intr_enable: ReadWrite), - (0x08 => intr_test: ReadWrite), - (0x0C => alert_test: ReadWrite), - /// UART control register - (0x10 => ctrl: ReadWrite), - /// UART live status register - (0x14 => status: ReadOnly), - /// UART read data) - (0x18 => rdata: ReadOnly), - /// UART write data - (0x1C => wdata: WriteOnly), - /// UART FIFO control register") - (0x20 => fifo_ctrl: ReadWrite), - /// UART FIFO status register - (0x24 => fifo_status: ReadWrite), - /// TX pin override control. Gives direct SW control over TX pin state - (0x28 => ovrd: ReadWrite), - /// UART oversampled values - (0x2C => val: ReadWrite), - /// UART RX timeout control - (0x30 => timeout_ctrl: ReadWrite), - (0x34 => @END), - } -} - -register_bitfields![u32, - intr [ - tx_watermark OFFSET(0) NUMBITS(1) [], - rx_watermark OFFSET(1) NUMBITS(1) [], - tx_empty OFFSET(2) NUMBITS(1) [], - rx_overflow OFFSET(3) NUMBITS(1) [], - rx_frame_err OFFSET(4) NUMBITS(1) [], - rx_break_err OFFSET(5) NUMBITS(1) [], - rx_timeout OFFSET(6) NUMBITS(1) [], - rx_parity_err OFFSET(7) NUMBITS(1) [] - ], - ctrl [ - tx OFFSET(0) NUMBITS(1) [], - rx OFFSET(1) NUMBITS(1) [], - nf OFFSET(2) NUMBITS(1) [], - slpbk OFFSET(4) NUMBITS(1) [], - llpbk OFFSET(5) NUMBITS(1) [], - parity_en OFFSET(6) NUMBITS(1) [], - parity_odd OFFSET(7) NUMBITS(1) [], - rxblvl OFFSET(8) NUMBITS(2) [], - nco OFFSET(16) NUMBITS(16) [] - ], - status [ - txfull OFFSET(0) NUMBITS(1) [], - rxfull OFFSET(1) NUMBITS(1) [], - txempty OFFSET(2) NUMBITS(1) [], - txidle OFFSET(3) NUMBITS(1) [], - rxidle OFFSET(4) NUMBITS(1) [], - rxempty OFFSET(5) NUMBITS(1) [] - ], - rdata [ - data OFFSET(0) NUMBITS(8) [] - ], - wdata [ - data OFFSET(0) NUMBITS(8) [] - ], - fifo_ctrl [ - rxrst OFFSET(0) NUMBITS(1) [], - txrst OFFSET(1) NUMBITS(1) [], - rxilvl OFFSET(2) NUMBITS(2) [], - txilvl OFFSET(5) NUMBITS(2) [] - ], - fifo_status [ - txlvl OFFSET(0) NUMBITS(5) [], - rxlvl OFFSET(16) NUMBITS(5) [] - ], - ovrd [ - txen OFFSET(0) NUMBITS(1) [], - txval OFFSET(1) NUMBITS(1) [] - ], - val [ - rx OFFSET(0) NUMBITS(16) [] - ], - timeout_ctrl [ - val OFFSET(0) NUMBITS(23) [], - en OFFSET(31) NUMBITS(1) [] - ] -]; +use crate::registers::uart_regs::UartRegisters; +use crate::registers::uart_regs::{CTRL, FIFO_CTRL, INTR, STATUS, TIMEOUT_CTRL, WDATA}; pub struct Uart<'a> { registers: StaticRef, clock_frequency: u32, tx_client: OptionalCell<&'a dyn hil::uart::TransmitClient>, rx_client: OptionalCell<&'a dyn hil::uart::ReceiveClient>, + rx_deferred_call: DeferredCall, tx_buffer: TakeCell<'static, [u8]>, tx_len: Cell, @@ -112,70 +31,129 @@ pub struct Uart<'a> { rx_buffer: TakeCell<'static, [u8]>, rx_len: Cell, + rx_index: Cell, + rx_timeout: Cell, } #[derive(Copy, Clone)] pub struct UartParams { pub baud_rate: u32, + pub parity: uart::Parity, +} + +/// Compute a / b, rounding such that the result is within 2.5% error of the floating point result. +fn div_round_bounded(a: u64, b: u64) -> Result { + let q = a / b; + + if 39 * a <= 40 * b * q { + Ok(q) + } else if 40 * b * (q + 1) <= 41 * a { + Ok(q + 1) + } else { + Err(ErrorCode::INVAL) + } } impl<'a> Uart<'a> { - pub const fn new(base: StaticRef, clock_frequency: u32) -> Uart<'a> { + pub fn new(base: StaticRef, clock_frequency: u32) -> Uart<'a> { Uart { registers: base, - clock_frequency: clock_frequency, + clock_frequency, tx_client: OptionalCell::empty(), rx_client: OptionalCell::empty(), + rx_deferred_call: DeferredCall::new(), tx_buffer: TakeCell::empty(), tx_len: Cell::new(0), tx_index: Cell::new(0), rx_buffer: TakeCell::empty(), rx_len: Cell::new(0), + rx_index: Cell::new(0), + rx_timeout: Cell::new(0), } } - fn set_baud_rate(&self, baud_rate: u32) { + fn set_baud_rate(&self, baud_rate: u32) -> Result<(), ErrorCode> { + const NCO_BITS: u32 = u32::count_ones(CTRL::NCO.mask); + let regs = self.registers; - let uart_ctrl_nco = ((baud_rate as u64) << 20) / self.clock_frequency as u64; + let baud_adj = (baud_rate as u64) << (NCO_BITS + 4); + let freq_clk = self.clock_frequency as u64; + let uart_ctrl_nco = div_round_bounded(baud_adj, freq_clk)?; regs.ctrl - .write(ctrl::nco.val((uart_ctrl_nco & 0xffff) as u32)); - regs.ctrl.modify(ctrl::tx::SET + ctrl::rx::SET); + .write(CTRL::NCO.val((uart_ctrl_nco & 0xffff) as u32)); + regs.ctrl.modify(CTRL::TX::SET + CTRL::RX::SET); regs.fifo_ctrl - .write(fifo_ctrl::rxrst::SET + fifo_ctrl::txrst::SET); + .write(FIFO_CTRL::RXRST::SET + FIFO_CTRL::TXRST::SET); + + Ok(()) } fn enable_tx_interrupt(&self) { let regs = self.registers; - regs.intr_enable.modify(intr::tx_empty::SET); + regs.intr_enable.modify(INTR::TX_EMPTY::SET); } fn disable_tx_interrupt(&self) { let regs = self.registers; - regs.intr_enable.modify(intr::tx_empty::CLEAR); + regs.intr_enable.modify(INTR::TX_EMPTY::CLEAR); // Clear the interrupt bit (by writing 1), if it happens to be set - regs.intr_state.write(intr::tx_empty::SET); + regs.intr_state.write(INTR::TX_EMPTY::SET); } fn enable_rx_interrupt(&self) { let regs = self.registers; // Generate an interrupt if we get any value in the RX buffer - regs.intr_enable.modify(intr::rx_watermark::SET); - regs.fifo_ctrl.write(fifo_ctrl::rxilvl.val(0 as u32)); + regs.intr_enable.modify(INTR::RX_WATERMARK::SET); + regs.fifo_ctrl.write(FIFO_CTRL::RXILVL.val(0_u32)); + + // In cases where the RX FIFO isn't empty the edge-triggered watermark will never trigger. + // If there is already data pending, set a deferred call to read the data instead. + if !regs.status.is_set(STATUS::RXEMPTY) { + self.rx_deferred_call.set(); + self.disable_rx_interrupt(); + } } fn disable_rx_interrupt(&self) { let regs = self.registers; // Generate an interrupt if we get any value in the RX buffer - regs.intr_enable.modify(intr::rx_watermark::CLEAR); + regs.intr_enable.modify(INTR::RX_WATERMARK::CLEAR); + + // Clear the interrupt bit (by writing 1), if it happens to be set + regs.intr_state.write(INTR::RX_WATERMARK::SET); + } + + fn enable_rx_timeout(&self, interbyte_timeout: u8) { + let regs = self.registers; + + // Program the timeout value + regs.timeout_ctrl + .write(TIMEOUT_CTRL::VAL.val(interbyte_timeout as u32)); + + // Enable RX timeout feature + regs.timeout_ctrl.write(TIMEOUT_CTRL::EN::SET); + + // Enable RX timeout interrupt + regs.intr_enable.write(INTR::RX_TIMEOUT::SET); + } + + fn disable_rx_timeout(&self) { + let regs = self.registers; + + // Disable RX timeout feature + regs.timeout_ctrl.modify(TIMEOUT_CTRL::EN::CLEAR); + + // Disable RX timeout interrupt + regs.intr_enable.modify(INTR::RX_TIMEOUT::CLEAR); // Clear the interrupt bit (by writing 1), if it happens to be set - regs.intr_state.write(intr::rx_watermark::SET); + regs.intr_state.write(INTR::RX_TIMEOUT::SET); } fn tx_progress(&self) { @@ -196,22 +174,57 @@ impl<'a> Uart<'a> { let tx_len = len - idx; for i in 0..tx_len { - if regs.status.is_set(status::txfull) { + if regs.status.is_set(STATUS::TXFULL) { break; } let tx_idx = idx + i; - regs.wdata.write(wdata::data.val(tx_buf[tx_idx] as u32)); + regs.wdata.write(WDATA::WDATA.val(tx_buf[tx_idx] as u32)); self.tx_index.set(tx_idx + 1) } }); } } + fn consume_rx(&self) { + let regs = self.registers; + + self.rx_client.map(|client| { + self.rx_buffer.take().map(|rx_buf| { + let mut len = 0; + let mut return_code = Ok(()); + + for i in self.rx_index.get()..self.rx_len.get() { + if regs.status.is_set(STATUS::RXEMPTY) { + /* RX is empty */ + + // If this was kicked off by `receive_automatic()` then we can reenable + // interupts and wait for either the rest of the data or for the timeout. + let rx_timeout = self.rx_timeout.get(); + if rx_timeout > 0 { + self.rx_index.set(i); + self.enable_rx_timeout(rx_timeout); + self.enable_rx_interrupt(); + return; + } else { + return_code = Err(ErrorCode::SIZE); + break; + } + } + + rx_buf[i] = regs.rdata.get() as u8; + len = i + 1; + } + + client.received_buffer(rx_buf, len, return_code, uart::Error::None); + }); + }); + } + pub fn handle_interrupt(&self) { let regs = self.registers; let intrs = regs.intr_state.extract(); - if intrs.is_set(intr::tx_empty) { + if intrs.is_set(INTR::TX_EMPTY) { self.disable_tx_interrupt(); if self.tx_index.get() == self.tx_len.get() { @@ -226,26 +239,72 @@ impl<'a> Uart<'a> { // We have more to transmit, so continue in tx_progress(). self.tx_progress(); } - } else if intrs.is_set(intr::rx_watermark) { + } else if intrs.is_set(INTR::RX_WATERMARK) { + self.disable_rx_interrupt(); + self.consume_rx(); + } else if intrs.is_set(INTR::RX_TIMEOUT) { self.disable_rx_interrupt(); + self.disable_rx_timeout(); + // On timeout return whatever is in the buffer to the client. self.rx_client.map(|client| { self.rx_buffer.take().map(|rx_buf| { - let mut len = 0; - let mut return_code = Ok(()); - - for i in 0..self.rx_len.get() { - rx_buf[i] = regs.rdata.get() as u8; - len = i + 1; - - if regs.status.is_set(status::rxempty) { - /* RX is empty */ - return_code = Err(ErrorCode::SIZE); - break; - } - } - - client.received_buffer(rx_buf, len, return_code, uart::Error::None); + client.received_buffer( + rx_buf, + self.rx_index.get() + 1, + Err(kernel::ErrorCode::SIZE), + uart::Error::None, + ); + }) + }); + } else if intrs.is_set(INTR::TX_WATERMARK) { + // TODO: Additional logic or notification related to the watermark. + } else if intrs.is_set(INTR::RX_OVERFLOW) { + self.disable_rx_interrupt(); + self.rx_client.map(|client| { + self.rx_buffer.take().map(|rx_buf| { + client.received_buffer( + rx_buf, + self.rx_index.get(), + Err(kernel::ErrorCode::FAIL), + uart::Error::OverrunError, + ); + }); + }); + } else if intrs.is_set(INTR::RX_FRAME_ERR) { + self.disable_rx_interrupt(); + self.rx_client.map(|client| { + self.rx_buffer.take().map(|rx_buf| { + client.received_buffer( + rx_buf, + self.rx_index.get(), + Err(kernel::ErrorCode::FAIL), + uart::Error::FramingError, + ); + }); + }); + } else if intrs.is_set(INTR::RX_BREAK_ERR) { + self.disable_rx_interrupt(); + self.rx_client.map(|client| { + self.rx_buffer.take().map(|rx_buf| { + client.received_buffer( + rx_buf, + self.rx_index.get(), + Err(kernel::ErrorCode::FAIL), + uart::Error::BreakError, + ); + }); + }); + } else if intrs.is_set(INTR::RX_PARITY_ERR) { + self.disable_rx_interrupt(); + self.rx_client.map(|client| { + self.rx_buffer.take().map(|rx_buf| { + client.received_buffer( + rx_buf, + self.rx_index.get(), + Err(kernel::ErrorCode::FAIL), + uart::Error::ParityError, + ); }); }); } @@ -254,8 +313,8 @@ impl<'a> Uart<'a> { pub fn transmit_sync(&self, bytes: &[u8]) { let regs = self.registers; for b in bytes.iter() { - while regs.status.is_set(status::txfull) {} - regs.wdata.write(wdata::data.val(*b as u32)); + while regs.status.is_set(STATUS::TXFULL) {} + regs.wdata.write(WDATA::WDATA.val(*b as u32)); } } } @@ -264,13 +323,25 @@ impl hil::uart::Configure for Uart<'_> { fn configure(&self, params: hil::uart::Parameters) -> Result<(), ErrorCode> { let regs = self.registers; // We can set the baud rate. - self.set_baud_rate(params.baud_rate); + self.set_baud_rate(params.baud_rate)?; + + match params.parity { + uart::Parity::Even => regs + .ctrl + .modify(CTRL::PARITY_EN::SET + CTRL::PARITY_ODD::CLEAR), + uart::Parity::Odd => regs + .ctrl + .modify(CTRL::PARITY_EN::SET + CTRL::PARITY_ODD::SET), + uart::Parity::None => regs + .ctrl + .modify(CTRL::PARITY_EN::CLEAR + CTRL::PARITY_ODD::CLEAR), + } regs.fifo_ctrl - .write(fifo_ctrl::rxrst::SET + fifo_ctrl::txrst::SET); + .write(FIFO_CTRL::RXRST::SET + FIFO_CTRL::TXRST::SET); // Disable all interrupts for now - regs.intr_enable.set(0 as u32); + regs.intr_enable.set(0_u32); Ok(()) } @@ -329,6 +400,8 @@ impl<'a> hil::uart::Receive<'a> for Uart<'a> { self.rx_buffer.replace(rx_buffer); self.rx_len.set(rx_len); + self.rx_timeout.set(0); + self.rx_index.set(0); Ok(()) } @@ -341,3 +414,58 @@ impl<'a> hil::uart::Receive<'a> for Uart<'a> { Err(ErrorCode::FAIL) } } + +impl<'a> hil::uart::ReceiveAdvanced<'a> for Uart<'a> { + fn receive_automatic( + &self, + rx_buffer: &'static mut [u8], + rx_len: usize, + interbyte_timeout: u8, + ) -> Result<(), (ErrorCode, &'static mut [u8])> { + if rx_len == 0 || rx_len > rx_buffer.len() { + return Err((ErrorCode::SIZE, rx_buffer)); + } + + self.rx_buffer.replace(rx_buffer); + self.rx_len.set(rx_len); + self.rx_timeout.set(interbyte_timeout); + self.rx_index.set(0); + + Ok(()) + } +} + +impl<'a> DeferredCallClient for Uart<'a> { + fn handle_deferred_call(&self) { + self.consume_rx(); + } + + fn register(&'static self) { + self.rx_deferred_call.register(self); + } +} + +#[cfg(test)] +mod tests { + use super::div_round_bounded; + use kernel::ErrorCode; + + #[test] + fn test_bounded_division() { + const TEST_VECTORS: [(u64, u64, Result); 10] = [ + (100, 4, Ok(25)), + (41, 40, Ok(1)), + (83, 40, Err(ErrorCode::INVAL)), + (105, 40, Err(ErrorCode::INVAL)), + (120, 40, Ok(3)), + (121, 40, Ok(3)), + (158, 40, Ok(4)), + (159, 40, Ok(4)), + (10, 3, Err(ErrorCode::INVAL)), + (120_795_955_200, 6_000_000, Ok(20132)), + ]; + for (a, b, expected) in &TEST_VECTORS { + assert_eq!(div_round_bounded(*a, *b), *expected); + } + } +} diff --git a/chips/lowrisc/src/usbdev.rs b/chips/lowrisc/src/usbdev.rs index 9d8caca571..14b51325ed 100644 --- a/chips/lowrisc/src/usbdev.rs +++ b/chips/lowrisc/src/usbdev.rs @@ -1,4 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! USB Client driver. +// [!! Untested !!]: This driver is out of date (functionality wise) with hw and +// likely does not work. It's is currently not used with +// the OpenTitan board. use core::cell::Cell; use kernel::hil; @@ -18,19 +25,29 @@ register_structs! { (0x000 => intr_state: ReadWrite), (0x004 => intr_enable: ReadWrite), (0x008 => intr_test: WriteOnly), - (0x00c => usbctrl: ReadWrite), - (0x010 => usbstat: ReadOnly), - (0x014 => avbuffer: WriteOnly), - (0x018 => rxfifo: ReadOnly), - (0x01c => rxenable_setup: ReadWrite), - (0x020 => rxenable_out: ReadWrite), - (0x024 => in_sent: ReadWrite), - (0x028 => stall: ReadWrite), - (0x02c => configin: [ReadWrite; N_ENDPOINTS]), - (0x05c => iso: ReadWrite), - (0x060 => data_toggle_clear: WriteOnly), - (0x064 => phy_config: ReadWrite), - (0x068 => _reserved0), + (0x00C => alert_test: WriteOnly), + (0x010 => usbctrl: ReadWrite), + (0x014 => ep_out_enable: ReadWrite), + (0x018 => ep_in_enable: ReadWrite), + (0x01C => usbstat: ReadOnly), + (0x020 => avbuffer: WriteOnly), + (0x024 => rxfifo: ReadOnly), + (0x028 => rxenable_setup: ReadWrite), + (0x02C => rxenable_out: ReadWrite), + (0x030 => set_nak_out: ReadWrite), + (0x034 => in_sent: ReadWrite), + (0x038 => out_stall: ReadWrite), + (0x03C => in_stall: ReadWrite), + (0x040 => configin: [ReadWrite; N_ENDPOINTS]), + (0x070 => out_iso: ReadWrite), + (0x074 => in_iso: ReadWrite), + (0x078 => data_toggle_clear: WriteOnly), + (0x07C => phy_pins_sens: ReadOnly), + (0x080 => phy_pins_drive: ReadOnly), + (0x084 => phy_config: ReadWrite), + (0x088 => wake_ctrl: WriteOnly), + (0x08C => wake_events: ReadOnly), + (0x090 => _reserved0), (0x800 => buffer: [ReadWrite; N_BUFFERS * 8]), (0x1000 => @END), } @@ -57,16 +74,17 @@ register_bitfields![u32, ], USBCTRL [ ENABLE OFFSET(0) NUMBITS(1) [], + RESUME_LINK_ACTIVE OFFSET(1) NUMBITS(1) [], DEVICE_ADDRESS OFFSET(16) NUMBITS(7) [] ], USBSTAT [ FRAME OFFSET(0) NUMBITS(10) [], HOST_LOST OFFSET(11) NUMBITS(1) [], - LINK_STATE OFFSET(12) NUMBITS(2) [], + LINK_STATE OFFSET(12) NUMBITS(3) [], SENSE OFFSET(15) NUMBITS(1) [], - AV_DEPTH OFFSET(16) NUMBITS(2) [], + AV_DEPTH OFFSET(16) NUMBITS(3) [], AV_FULL OFFSET(23) NUMBITS(1) [], - RX_DEPTH OFFSET(24) NUMBITS(2) [], + RX_DEPTH OFFSET(24) NUMBITS(3) [], RX_EMPTY OFFSET(31) NUMBITS(1) [] ], AVBUFFER [ @@ -74,9 +92,9 @@ register_bitfields![u32, ], RXFIFO [ BUFFER OFFSET(0) NUMBITS(4) [], - SIZE OFFSET(8) NUMBITS(6) [], + SIZE OFFSET(8) NUMBITS(7) [], SETUP OFFSET(19) NUMBITS(1) [], - EP OFFSET(20) NUMBITS(3) [] + EP OFFSET(20) NUMBITS(4) [] ], RXENABLE_SETUP [ SETUP0 OFFSET(0) NUMBITS(1) [], @@ -137,7 +155,7 @@ register_bitfields![u32, CONFIGIN [ BUFFER OFFSET(0) NUMBITS(4) [], SIZE OFFSET(8) NUMBITS(7) [], - PEND OFFSET(30) NUMBITS(1) [], + PEND OFFSET(30) NUMBITS(1) [], //RW1C RDY OFFSET(31) NUMBITS(1) [] ], ISO [ @@ -172,10 +190,9 @@ register_bitfields![u32, RX_DIFFERENTIAL_MODE OFFSET(0) NUMBITS(1) [], TX_DIFFERENTIAL_MODE OFFSET(1) NUMBITS(1) [], EOP_SINGLE_BIT OFFSET(2) NUMBITS(1) [], - OVERRIDE_PWR_SENSE_EN OFFSET(3) NUMBITS(1) [], - OVERRIDE_PWR_SENSE_VAL OFFSET(4) NUMBITS(1) [], PINFLIP OFFSET(5) NUMBITS(1) [], - USB_REF_DISABLE OFFSET(6) NUMBITS(1) [] + USB_REF_DISABLE OFFSET(6) NUMBITS(1) [], + TX_OSC_TEST_MODE OFFSET(7) NUMBITS(1) [] ] ]; @@ -191,40 +208,6 @@ register_bitfields![u64, ] ]; -enum SetupRequest { - GetStatus = 0, - ClearFeature = 1, - SetFeature = 3, - SetAddress = 5, - GetDescriptor = 6, - SetDescriptor = 7, - GetConfiguration = 8, - SetConfiguration = 9, - GetInterface = 10, - SetInterface = 11, - SynchFrame = 12, - Unsupported = 100, -} - -impl From for SetupRequest { - fn from(num: u32) -> Self { - match num { - 0 => SetupRequest::GetStatus, - 1 => SetupRequest::ClearFeature, - 3 => SetupRequest::SetFeature, - 5 => SetupRequest::SetAddress, - 6 => SetupRequest::GetDescriptor, - 7 => SetupRequest::SetDescriptor, - 8 => SetupRequest::GetConfiguration, - 9 => SetupRequest::SetConfiguration, - 10 => SetupRequest::GetInterface, - 11 => SetupRequest::SetInterface, - 12 => SetupRequest::SynchFrame, - _ => SetupRequest::Unsupported, - } - } -} - /// State of the control endpoint (endpoint 0). #[derive(Copy, Clone, PartialEq, Debug)] pub enum CtrlState { @@ -493,10 +476,10 @@ impl<'a> Usb<'a> { self.bufs.set(bufs); } - fn stall(&self, endpoint: usize) { + fn in_stall(&self, endpoint: usize) { self.registers - .stall - .set(1 << endpoint | self.registers.stall.get()); + .in_stall + .set(1 << endpoint | self.registers.in_stall.get()); } fn copy_slice_out_to_hw(&self, ep: usize, buf_id: usize, size: usize) { @@ -654,7 +637,7 @@ impl<'a> Usb<'a> { } } _err => { - self.stall(ep); + self.in_stall(ep); self.free_buffer(buf_id); self.descriptors[ep] .state @@ -689,7 +672,7 @@ impl<'a> Usb<'a> { self.client.map(|client| { self.copy_from_hw(ep, buf_id, size as usize); - let result = client.packet_out(self.get_transfer_type(ep), ep as usize, size); + let result = client.packet_out(self.get_transfer_type(ep), ep, size); match self.descriptors[ep].state.get() { EndpointState::Disabled => unimplemented!(), EndpointState::Ctrl(_state) => unimplemented!(), @@ -759,27 +742,27 @@ impl<'a> Usb<'a> { EndpointState::Interrupt(packet_size, state) => match state { InterruptState::Init => {} InterruptState::In(send_size) => { - let buf = self.registers.configin[ep as usize].read(CONFIGIN::BUFFER); + let buf = self.registers.configin[ep].read(CONFIGIN::BUFFER); self.free_buffer(buf as usize); self.client.map(|client| { - match client.packet_in(TransferType::Interrupt, ep as usize) { + match client.packet_in(TransferType::Interrupt, ep) { hil::usb::InResult::Packet(size) => { if size == 0 { panic!("Empty ctrl packet?"); } - self.copy_slice_out_to_hw(ep as usize, buf as usize, size); + self.copy_slice_out_to_hw(ep, buf as usize, size); if send_size == size { - self.descriptors[ep as usize].state.set( + self.descriptors[ep].state.set( EndpointState::Interrupt( packet_size, InterruptState::Init, ), ); } else { - self.descriptors[ep as usize].state.set( + self.descriptors[ep].state.set( EndpointState::Interrupt( packet_size, InterruptState::In(send_size - size), @@ -805,7 +788,7 @@ impl<'a> Usb<'a> { // We are handling this case, clear it self.registers.in_sent.set(1 << ep); - in_sent = in_sent & !(1 << ep); + in_sent &= !(1 << ep); let buf = self.registers.configin[ep as usize].read(CONFIGIN::BUFFER); @@ -867,7 +850,7 @@ impl<'a> Usb<'a> { } if irqs.is_set(INTR::PKT_RECEIVED) { - while !self.registers.usbstat.is_set(USBSTAT::RX_EMPTY) { + if !self.registers.usbstat.is_set(USBSTAT::RX_EMPTY) { let rxinfo = self.registers.rxfifo.extract(); let buf = rxinfo.read(RXFIFO::BUFFER); let size = rxinfo.read(RXFIFO::SIZE); @@ -879,7 +862,6 @@ impl<'a> Usb<'a> { 0 => { self.control_ep_receive(ep as usize, buf as usize, size, setup); self.free_buffer(buf as usize); - break; } 1..=7 => { let receive_size = match self.descriptors[ep as usize].state.get() { @@ -891,7 +873,6 @@ impl<'a> Usb<'a> { }; self.ep_receive(ep as usize, buf as usize, receive_size, setup); self.free_buffer(buf as usize); - break; } 8 => unimplemented!("isochronous endpoint"), _ => unimplemented!(), @@ -931,12 +912,12 @@ impl<'a> Usb<'a> { } self.bufs.set(bufs); - match self.descriptors[ep as usize].state.get() { + match self.descriptors[ep].state.get() { EndpointState::Disabled => unreachable!(), EndpointState::Ctrl(_state) => unreachable!(), EndpointState::Bulk(_in_state, _out_state) => { - if buf_id.is_some() { - self.copy_slice_out_to_hw(ep, buf_id.unwrap(), size) + if let Some(buf) = buf_id { + self.copy_slice_out_to_hw(ep, buf, size) } else { panic!("No free bufs"); }; @@ -951,7 +932,7 @@ impl<'a> Usb<'a> { hil::usb::InResult::Delay => { // No packet to send now. Wait for a resume call from the client. - match self.descriptors[ep as usize].state.get() { + match self.descriptors[ep].state.get() { EndpointState::Disabled => unreachable!(), EndpointState::Ctrl(_state) => unreachable!(), EndpointState::Bulk(_in_state, _out_state) => { @@ -965,8 +946,8 @@ impl<'a> Usb<'a> { } hil::usb::InResult::Error => { - self.stall(ep); - match self.descriptors[ep as usize].state.get() { + self.in_stall(ep); + match self.descriptors[ep].state.get() { EndpointState::Disabled => unreachable!(), EndpointState::Ctrl(_state) => unreachable!(), EndpointState::Bulk(_in_state, _out_state) => { @@ -1020,7 +1001,7 @@ impl<'a> hil::usb::UsbController<'a> for Usb<'a> { ); self.set_state(State::Idle(Mode::Device { - speed: speed, + speed, config: DeviceConfig::default(), })) } @@ -1086,7 +1067,7 @@ impl<'a> hil::usb::UsbController<'a> for Usb<'a> { self.registers .rxenable_setup .set(1 << endpoint | self.registers.rxenable_setup.get()); - self.registers.iso.set(1 << endpoint); + self.registers.in_iso.set(1 << endpoint); self.descriptors[endpoint].state.set(EndpointState::Iso); } }; @@ -1131,7 +1112,7 @@ impl<'a> hil::usb::UsbController<'a> for Usb<'a> { self.registers .rxenable_out .set(1 << endpoint | self.registers.rxenable_out.get()); - self.registers.iso.set(1 << endpoint); + self.registers.out_iso.set(1 << endpoint); self.descriptors[endpoint].state.set(EndpointState::Iso); } }; diff --git a/chips/lowrisc/src/virtual_otbn.rs b/chips/lowrisc/src/virtual_otbn.rs index 2cea387b40..984e4d20f4 100644 --- a/chips/lowrisc/src/virtual_otbn.rs +++ b/chips/lowrisc/src/virtual_otbn.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Virtualise the Accel interface to enable multiple users of an underlying //! Accel hardware peripheral. @@ -29,7 +33,7 @@ impl<'a> VirtualMuxAccel<'a> { mux: mux_accel, next: ListLink::empty(), client: OptionalCell::empty(), - id: id, + id, } } @@ -39,7 +43,7 @@ impl<'a> VirtualMuxAccel<'a> { pub fn load_binary(&self, input: &[u8]) -> Result<(), ErrorCode> { // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running.get() == false { + if !self.mux.running.get() { self.mux.running.set(true); self.mux.running_id.set(self.id); self.mux.accel.load_binary(input) @@ -52,7 +56,7 @@ impl<'a> VirtualMuxAccel<'a> { pub fn load_data(&self, address: usize, data: &[u8]) -> Result<(), ErrorCode> { // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running.get() == false { + if !self.mux.running.get() { self.mux.running.set(true); self.mux.running_id.set(self.id); self.mux.accel.load_data(address, data) @@ -69,7 +73,7 @@ impl<'a> VirtualMuxAccel<'a> { output: &'static mut [u8], ) -> Result<(), (ErrorCode, &'static mut [u8])> { // Check if any mux is enabled. If it isn't we enable it for us. - if self.mux.running.get() == false { + if !self.mux.running.get() { self.mux.running.set(true); self.mux.running_id.set(self.id); self.mux.accel.run(address, output) diff --git a/chips/msp432/Cargo.toml b/chips/msp432/Cargo.toml index 69e0964a13..85d2016460 100644 --- a/chips/msp432/Cargo.toml +++ b/chips/msp432/Cargo.toml @@ -1,10 +1,17 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "msp432" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] cortexm4 = { path = "../../arch/cortex-m4" } enum_primitive = { path = "../../libraries/enum_primitive" } kernel = { path = "../../kernel" } + +[lints] +workspace = true diff --git a/chips/msp432/src/adc.rs b/chips/msp432/src/adc.rs index 9ddd03e098..9713d7ccd8 100644 --- a/chips/msp432/src/adc.rs +++ b/chips/msp432/src/adc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Analog-Digital Converter (ADC) use crate::{dma, ref_module, timer}; @@ -486,11 +490,6 @@ register_bitfields![u32, ] ]; -/// Create a trait of both client types to allow a single client reference to -/// act as both -pub trait EverythingClient: hil::adc::Client + hil::adc::HighSpeedClient {} -impl EverythingClient for C {} - pub struct Adc<'a> { registers: StaticRef, resolution: AdcResolution, @@ -503,11 +502,12 @@ pub struct Adc<'a> { dma_src: u8, buffer1: TakeCell<'static, [u16]>, buffer2: TakeCell<'static, [u16]>, - client: OptionalCell<&'static dyn EverythingClient>, + client: OptionalCell<&'a dyn hil::adc::Client>, + highspeed_client: OptionalCell<&'a dyn hil::adc::HighSpeedClient>, } impl Adc<'_> { - pub const fn new() -> Self { + pub fn new() -> Self { Self { registers: ADC_BASE, resolution: DEFAULT_ADC_RESOLUTION, @@ -521,6 +521,7 @@ impl Adc<'_> { buffer1: TakeCell::empty(), buffer2: TakeCell::empty(), client: OptionalCell::empty(), + highspeed_client: OptionalCell::empty(), } } } @@ -624,46 +625,44 @@ impl<'a> Adc<'a> { self.registers.ie0.set(0); // Clear all pending interrupts - self.registers.clrifg0.set(core::u32::MAX); - self.registers.clrifg1.set(core::u32::MAX); + self.registers.clrifg0.set(u32::MAX); + self.registers.clrifg1.set(u32::MAX); } fn setup(&self) { self.stop(); for i in 0..AVAILABLE_ADC_CHANNELS { + // Set the input for the channel + // Set Reference voltage to Internal AVCC for Vref+ and AVSS (GND) for Vref- + // Configure the channel for single-ended mode + // Disable comparator window self.registers.mctl[i].modify( - // Set the input for the channel MCTLx::INCHx.val(i as u32) - // Set Reference voltage to Internal AVCC for Vref+ and AVSS (GND) for Vref- - + MCTLx::VRSEL::AvccAvss - // Configure the channel for single-ended mode - + MCTLx::DIF::SingleEnded - // Disable comparator window - + MCTLx::WINC::CLEAR, + + MCTLx::VRSEL::AvccAvss + + MCTLx::DIF::SingleEnded + + MCTLx::WINC::CLEAR, ); } + // Set predivider of the ADC-clock to 1 + // Set divider of the ADC-clock to 1 + // Set ADC-clock source to HSMCLK + // Set the sample-and-hold time to 4 clock-cyles for channel 0-7 and 24-31 + // Set the sample-and-hold time to 4 clock-cyles for channel 8-23 self.registers.ctl0.modify( - // Set predivider of the ADC-clock to 1 CTL0::PDIV::PreDivideBy1 - // Set divider of the ADC-clock to 1 - + CTL0::DIVx::DivideBy1 - // Set ADC-clock source to HSMCLK - + CTL0::SSELx::HSMCLK - // Set the sample-and-hold time to 4 clock-cyles for channel 0-7 and 24-31 - + CTL0::SHTOx::Cycles4 - // Set the sample-and-hold time to 4 clock-cyles for channel 8-23 - + CTL0::SHT1x::Cycles4, + + CTL0::DIVx::DivideBy1 + + CTL0::SSELx::HSMCLK + + CTL0::SHTOx::Cycles4 + + CTL0::SHT1x::Cycles4, ); + // Enable the battery monitor on channel 23 (measures 1/2 * AVCC) + // Enable the internal temperature sensor on channel 22 + // Set the ADC resolution self.registers.ctl1.modify( - // Enable the battery monitor on channel 23 (measures 1/2 * AVCC) - CTL1::BATMAP::Selected - // Enable the internal temperature sensor on channel 22 - + CTL1::TCMAP::Selected - // Set the ADC resolution - + CTL1::RES.val(self.resolution as u32), + CTL1::BATMAP::Selected + CTL1::TCMAP::Selected + CTL1::RES.val(self.resolution as u32), ); let dma_conf = dma::DmaConfig { @@ -712,10 +711,6 @@ impl<'a> Adc<'a> { self.dma.set(dma); } - pub fn set_client(&self, client: &'static dyn EverythingClient) { - self.client.set(client); - } - pub fn handle_interrupt(&self) { let chan = self.active_channel.get(); let chan_nr = chan as usize; @@ -733,22 +728,19 @@ impl<'a> Adc<'a> { // Stop sampling self.registers.ctl0.modify(CTL0::ENC::CLEAR); - - // Throw callback - self.client - .map(move |client| client.sample_ready(self.get_sample(chan))); - } else if mode == AdcMode::Repeated { - // Throw callback - self.client - .map(move |client| client.sample_ready(self.get_sample(chan))); } + + // Throw callback + self.client.map(|client| { + client.sample_ready(self.get_sample(chan)); + }); } else { panic!("ADC: unhandled interrupt: channel {}", chan_nr); } } } -impl dma::DmaClient for Adc<'_> { +impl<'a> dma::DmaClient for Adc<'a> { fn transfer_done( &self, _tx_buf: Option<&'static mut [u8]>, @@ -766,12 +758,14 @@ impl dma::DmaClient for Adc<'_> { buf[i] <<= shift; } - self.client.map(move |cl| cl.samples_ready(buf, samples)); + self.highspeed_client.map(|client| { + client.samples_ready(buf, samples); + }); } } } -impl hil::adc::Adc for Adc<'_> { +impl<'a> hil::adc::Adc<'a> for Adc<'a> { type Channel = Channel; fn sample(&self, channel: &Self::Channel) -> Result<(), ErrorCode> { @@ -793,17 +787,17 @@ impl hil::adc::Adc for Adc<'_> { self.enable_interrupt(*channel); + // Set ADC to mode where a single channel gets sampled once + // Set the sample-and-hold source select to software-based + // Set the sampling-timer for generating the sample-period + // Enable conversation + // Start conversation self.registers.ctl0.modify( - // Set ADC to mode where a single channel gets sampled once CTL0::CONSEQx::SingleChannelSingleConversion - // Set the sample-and-hold source select to software-based - + CTL0::SHSx::SCBit - // Set the sampling-timer for generating the sample-period - + CTL0::SHP::SET - // Enable conversation - + CTL0::ENC::SET - // Start conversation - + CTL0::SC::SET, + + CTL0::SHSx::SCBit + + CTL0::SHP::SET + + CTL0::ENC::SET + + CTL0::SC::SET, ); Ok(()) @@ -838,17 +832,17 @@ impl hil::adc::Adc for Adc<'_> { self.enable_interrupt(*channel); + // Set ADC to mode where a single channel gets sampled continuously + // Set the sample-and-hold source select to timer-triggered + // Use TIMER_A3 to generate the SAMPCON signal + // Enable multiple sample and conversions + // Enable conversation self.registers.ctl0.modify( - // Set ADC to mode where a single channel gets sampled continuously CTL0::CONSEQx::RepeatSingleChannel - // Set the sample-and-hold source select to timer-triggered - + CTL0::SHSx::Source7 - // Use TIMER_A3 to generate the SAMPCON signal - + CTL0::SHP::CLEAR - // Enable multiple sample and conversions - + CTL0::MSC::SET - // Enable conversation - + CTL0::ENC::SET, + + CTL0::SHSx::Source7 + + CTL0::SHP::CLEAR + + CTL0::MSC::SET + + CTL0::ENC::SET, ); Ok(()) @@ -896,12 +890,12 @@ impl hil::adc::Adc for Adc<'_> { self.ref_module.map(|ref_mod| ref_mod.ref_voltage_mv()) } - fn set_client(&self, _client: &'static dyn hil::adc::Client) { - unimplemented!(); + fn set_client(&self, client: &'a dyn hil::adc::Client) { + self.client.set(client); } } -impl hil::adc::AdcHighSpeed for Adc<'_> { +impl<'a> hil::adc::AdcHighSpeed<'a> for Adc<'a> { fn sample_highspeed( &self, channel: &Self::Channel, @@ -932,20 +926,22 @@ impl hil::adc::AdcHighSpeed for Adc<'_> { .ctl1 .modify(CTL1::STARTADDx.val(*channel as u32)); + // Set ADC to mode where a single channel gets sampled continuously + // Set the sample-and-hold source select to timer-triggered + // Use TIMER_A3 to generate the SAMPCON signal + // Enable multiple sample and conversions + // Enable conversation self.registers.ctl0.modify( - // Set ADC to mode where a single channel gets sampled continuously CTL0::CONSEQx::RepeatSingleChannel - // Set the sample-and-hold source select to timer-triggered - + CTL0::SHSx::Source7 - // Use TIMER_A3 to generate the SAMPCON signal - + CTL0::SHP::CLEAR - // Enable multiple sample and conversions - + CTL0::MSC::SET - // Enable conversation - + CTL0::ENC::SET, + + CTL0::SHSx::Source7 + + CTL0::SHP::CLEAR + + CTL0::MSC::SET + + CTL0::ENC::SET, ); - let adc_reg = &self.registers.mem[*channel as usize] as *const ReadWrite as *const (); + let adc_reg = + (core::ptr::from_ref::>(&self.registers.mem[*channel as usize])) + .cast::<()>(); // Convert the [u16] into an [u8] since the DMA works only with [u8] let buf1 = unsafe { buf_u16_to_buf_u8(buffer1) }; @@ -997,4 +993,8 @@ impl hil::adc::AdcHighSpeed for Adc<'_> { Ok((self.buffer1.take(), self.buffer2.take())) } } + + fn set_highspeed_client(&self, client: &'a dyn hil::adc::HighSpeedClient) { + self.highspeed_client.set(client); + } } diff --git a/chips/msp432/src/chip.rs b/chips/msp432/src/chip.rs index 069f5882a6..80a6ca893e 100644 --- a/chips/msp432/src/chip.rs +++ b/chips/msp432/src/chip.rs @@ -1,12 +1,16 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; -use cortexm4; +use cortexm4::{CortexM4, CortexMVariant}; use kernel::platform::chip::Chip; use crate::nvic; use crate::wdt; use kernel::platform::chip::InterruptService; -pub struct Msp432<'a, I: InterruptService<()> + 'a> { +pub struct Msp432<'a, I: InterruptService + 'a> { mpu: cortexm4::mpu::MPU, userspace_kernel_boundary: cortexm4::syscall::SysCall, interrupt_service: &'a I, @@ -64,7 +68,7 @@ impl<'a> Msp432DefaultPeripherals<'a> { } } -impl<'a> kernel::platform::chip::InterruptService<()> for Msp432DefaultPeripherals<'a> { +impl<'a> kernel::platform::chip::InterruptService for Msp432DefaultPeripherals<'a> { unsafe fn service_interrupt(&self, interrupt: u32) -> bool { match interrupt { nvic::ADC => self.adc.handle_interrupt(), @@ -88,12 +92,9 @@ impl<'a> kernel::platform::chip::InterruptService<()> for Msp432DefaultPeriphera } true } - unsafe fn service_deferred_call(&self, _: ()) -> bool { - false - } } -impl<'a, I: InterruptService<()> + 'a> Msp432<'a, I> { +impl<'a, I: InterruptService + 'a> Msp432<'a, I> { pub unsafe fn new(interrupt_service: &'a I) -> Self { Self { mpu: cortexm4::mpu::MPU::new(), @@ -103,7 +104,7 @@ impl<'a, I: InterruptService<()> + 'a> Msp432<'a, I> { } } -impl<'a, I: InterruptService<()> + 'a> Chip for Msp432<'a, I> { +impl<'a, I: InterruptService + 'a> Chip for Msp432<'a, I> { type MPU = cortexm4::mpu::MPU; type UserspaceKernelBoundary = cortexm4::syscall::SysCall; @@ -151,6 +152,6 @@ impl<'a, I: InterruptService<()> + 'a> Chip for Msp432<'a, I> { } unsafe fn print_state(&self, write: &mut dyn Write) { - cortexm4::print_cortexm4_state(write); + CortexM4::print_cortexm_state(write); } } diff --git a/chips/msp432/src/cs.rs b/chips/msp432/src/cs.rs index c4db523b19..ea6b4c6b9a 100644 --- a/chips/msp432/src/cs.rs +++ b/chips/msp432/src/cs.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Clock System (CS) use kernel::platform::chip::NoClockControl; @@ -317,7 +321,7 @@ impl ClockSystem { } } -impl<'a> PeripheralManagement for ClockSystem { +impl PeripheralManagement for ClockSystem { type RegisterType = CsRegisters; fn get_registers(&self) -> &CsRegisters { @@ -325,7 +329,7 @@ impl<'a> PeripheralManagement for ClockSystem { } fn get_clock(&self) -> &NoClockControl { - unsafe { &kernel::platform::chip::NO_CLOCK_CONTROL } + &kernel::platform::chip::NO_CLOCK_CONTROL } fn before_peripheral_access(&self, _c: &NoClockControl, r: &Self::RegisterType) { diff --git a/chips/msp432/src/dma.rs b/chips/msp432/src/dma.rs index 8434dc1f84..e1f50a8e3b 100644 --- a/chips/msp432/src/dma.rs +++ b/chips/msp432/src/dma.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Direct Memory Access (DMA) use core::cell::Cell; @@ -654,10 +658,10 @@ impl DmaConfig { } impl<'a> DmaChannel<'a> { - pub const fn new(chan_nr: usize) -> DmaChannel<'a> { + pub fn new(chan_nr: usize) -> DmaChannel<'a> { DmaChannel { registers: DMA_BASE, - chan_nr: chan_nr, + chan_nr, in_use: Cell::new(false), config: Cell::new(DmaConfig::const_default()), transfer_type: Cell::new(DmaTransferType::None), @@ -683,7 +687,7 @@ impl<'a> DmaChannel<'a> { // Set the pointer to the configuration-memory // Since the config needs exactly 256 bytes, mask out the lower 256 bytes - let addr = (&DMA_CONFIG.0[0] as *const DmaChannelControl as u32) & (!0xFFu32); + let addr = (core::ptr::from_ref::(&DMA_CONFIG.0[0]) as u32) & (!0xFFu32); self.registers.ctlbase.set(addr); } @@ -788,13 +792,13 @@ impl<'a> DmaChannel<'a> { 0 }; + // Set the DMA mode since the DMA module sets it back to stop after every cycle + // Set the DMA cycles to the amount of remaining words + // Set the number of DMA-transfers after the DMA has to rearbitrate the bus DMA_CONFIG.0[chan_nr].ctrl.modify( - // Set the DMA mode since the DMA module sets it back to stop after every cycle DMA_CTRL::CYCLE_CTRL.val(conf.mode as u32) - // Set the DMA cycles to the amount of remaining words - + DMA_CTRL::N_MINUS_1.val(((rem_words - 1) % MAX_TRANSFERS_LEN) as u32) - // Set the number of DMA-transfers after the DMA has to rearbitrate the bus - + DMA_CTRL::R_POWER.val(r_power), + + DMA_CTRL::N_MINUS_1.val(((rem_words - 1) % MAX_TRANSFERS_LEN) as u32) + + DMA_CTRL::R_POWER.val(r_power), ); } @@ -812,13 +816,13 @@ impl<'a> DmaChannel<'a> { fn configure_channel(&self, bytes_to_transmit: usize, chan_nr: usize) { let conf = self.config.get(); let transfers = bytes_to_transmit >> (conf.width as usize); + // The DMA can only transmit 1024 words with 1 transfer + // Reset the bits in case they were set before to a different value + // Set the DMA mode since it the DMA module sets it back to to stop after every cycle DMA_CONFIG.0[chan_nr].ctrl.modify( - // The DMA can only transmit 1024 words with 1 transfer DMA_CTRL::N_MINUS_1.val(((transfers - 1) % MAX_TRANSFERS_LEN) as u32) - // Reset the bits in case they were set before to a different value - + DMA_CTRL::R_POWER.val(0) - // Set the DMA mode since it the DMA module sets it back to to stop after every cycle - + DMA_CTRL::CYCLE_CTRL.val(conf.mode as u32), + + DMA_CTRL::R_POWER.val(0) + + DMA_CTRL::CYCLE_CTRL.val(conf.mode as u32), ); } @@ -936,8 +940,8 @@ impl<'a> DmaChannel<'a> { // The pointers must point to the end of the buffer, for detailed calculation see // datasheet p. 646, section 11.2.4.4. - let src_end_ptr = (&src_buf[0] as *const u8 as u32) + ((len as u32) - 1); - let dst_end_ptr = (&dst_buf[0] as *const u8 as u32) + ((len as u32) - 1); + let src_end_ptr = (core::ptr::from_ref::(&src_buf[0]) as u32) + ((len as u32) - 1); + let dst_end_ptr = (core::ptr::from_ref::(&dst_buf[0]) as u32) + ((len as u32) - 1); // Setup the DMA configuration self.set_dma_mode(DmaMode::Basic); @@ -977,7 +981,7 @@ impl<'a> DmaChannel<'a> { // The pointers must point to the end of the buffer, for detailed calculation see // datasheet p. 646, section 11.2.4.4. let src_end_ptr = src_reg as u32; - let dst_end_ptr = (&buf[0] as *const u8 as u32) + ((len as u32) - 1); + let dst_end_ptr = (core::ptr::from_ref::(&buf[0]) as u32) + ((len as u32) - 1); // Setup the DMA configuration self.set_dma_mode(DmaMode::Basic); @@ -1000,7 +1004,7 @@ impl<'a> DmaChannel<'a> { pub fn transfer_mem_to_periph(&self, dst_reg: *const (), buf: &'static mut [u8], len: usize) { // The pointers must point to the end of the buffer, for detailed calculation see // datasheet p. 646, section 11.2.4.4. - let src_end_ptr = (&buf[0] as *const u8 as u32) + ((len as u32) - 1); + let src_end_ptr = (core::ptr::from_ref::(&buf[0]) as u32) + ((len as u32) - 1); let dst_end_ptr = dst_reg as u32; // Setup the DMA configuration @@ -1033,8 +1037,8 @@ impl<'a> DmaChannel<'a> { // datasheet p. 646, section 11.2.4.4. let src_end_ptr = src_reg as u32; - let dst_end_ptr1 = (&buf1[0] as *const u8 as u32) + ((len1 as u32) - 1); - let dst_end_ptr2 = (&buf2[0] as *const u8 as u32) + ((len2 as u32) - 1); + let dst_end_ptr1 = (core::ptr::from_ref::(&buf1[0]) as u32) + ((len1 as u32) - 1); + let dst_end_ptr2 = (core::ptr::from_ref::(&buf2[0]) as u32) + ((len2 as u32) - 1); // Setup the DMA configuration self.set_dma_mode(DmaMode::PingPong); @@ -1059,7 +1063,7 @@ impl<'a> DmaChannel<'a> { /// Provide a new buffer for a ping-pong transfer pub fn provide_new_buffer(&self, buf: &'static mut [u8], len: usize) { - let buf_end_ptr = (&buf[0] as *const u8 as u32) + ((len as u32) - 1); + let buf_end_ptr = (core::ptr::from_ref::(&buf[0]) as u32) + ((len as u32) - 1); if self.transfer_type.get() == DmaTransferType::PeripheralToMemoryPingPong { if self.active_buf.get() == ActiveBuffer::Primary { diff --git a/chips/msp432/src/flctl.rs b/chips/msp432/src/flctl.rs index 72fb3d85e1..b7263b8f8d 100644 --- a/chips/msp432/src/flctl.rs +++ b/chips/msp432/src/flctl.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Flash Controller (FLCTL) use kernel::utilities::registers::interfaces::ReadWriteable; diff --git a/chips/msp432/src/gpio.rs b/chips/msp432/src/gpio.rs index e46be292fc..baf4706fb8 100644 --- a/chips/msp432/src/gpio.rs +++ b/chips/msp432/src/gpio.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! General Purpose Input/Output (GPIO) use core::cell::Cell; diff --git a/chips/msp432/src/i2c.rs b/chips/msp432/src/i2c.rs index 3d49f16579..92a36cce5e 100644 --- a/chips/msp432/src/i2c.rs +++ b/chips/msp432/src/i2c.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use crate::usci::{self, UsciBRegisters}; use core::cell::Cell; use kernel::hil::i2c::{self, Error}; @@ -25,17 +29,17 @@ enum OperatingMode { pub struct I2c<'a> { registers: StaticRef, mode: Cell, - read_len: Cell, - write_len: Cell, - buf_idx: Cell, + read_len: Cell, + write_len: Cell, + buf_idx: Cell, buffer: TakeCell<'static, [u8]>, master_client: OptionalCell<&'a dyn i2c::I2CHwMasterClient>, } impl<'a> I2c<'a> { - pub const fn new(registers: StaticRef) -> Self { + pub fn new(registers: StaticRef) -> Self { Self { - registers: registers, + registers, mode: Cell::new(OperatingMode::Unconfigured), read_len: Cell::new(0), write_len: Cell::new(0), @@ -86,19 +90,18 @@ impl<'a> I2c<'a> { fn enable_interrupts(&self) { // Enable interrupts + // + // Enable NACK interrupt + // Enable RX interrupt + // Enable Stop condition interrupt + // Enable Start condition interrupt + // Enable 'arbitration lost' interrupt self.registers.ie.modify( - // Enable NACK interrupt usci::UCBxIE::UCNACKIE::SET - // Enable TX interrupt - // + usci::UCBxIE::UCTXIE0::SET - // Enable RX interrupt - + usci::UCBxIE::UCRXIE0::SET - // Enable Stop condition interrupt - + usci::UCBxIE::UCSTPIE::SET - // Enable Start condition interrupt - + usci::UCBxIE::UCSTTIE::SET - // Enable 'arbitration lost' interrupt - + usci::UCBxIE::UCALIE::SET, + + usci::UCBxIE::UCRXIE0::SET + + usci::UCBxIE::UCSTPIE::SET + + usci::UCBxIE::UCSTTIE::SET + + usci::UCBxIE::UCALIE::SET, ); } @@ -120,7 +123,7 @@ impl<'a> I2c<'a> { self.registers.ie.modify(usci::UCBxIE::UCTXIE0::CLEAR); } - fn set_byte_counter(&self, val: u8) { + fn set_byte_counter(&self, val: usize) { self.registers.tbcnt.set(val as u16); } @@ -138,30 +141,30 @@ impl<'a> I2c<'a> { fn setup(&self) { self.set_module_to_reset(); + // Use 7 bit addresses + // Setup to master mode + // Setup to single master environment + // Configure USCI module to I2C mode + // Enable Synchronous mode + // Set clock source to SMCLK (1.5MHz) self.registers.ctlw0.modify( - // Use 7 bit addresses usci::UCBxCTLW0::UCSLA10::AddressSlaveWith7BitAddress - // Setup to master mode - + usci::UCBxCTLW0::UCMST::MasterMode - // Setup to single master environment - + usci::UCBxCTLW0::UCMM::SingleMasterEnvironment - // Configure USCI module to I2C mode - + usci::UCBxCTLW0::UCMODE::I2CMode - // Enable Synchronous mode - + usci::UCBxCTLW0::UCSYNC::SynchronousMode - // Set clock source to SMCLK (1.5MHz) - + usci::UCBxCTLW0::UCSSEL::SMCLK, + + usci::UCBxCTLW0::UCMST::MasterMode + + usci::UCBxCTLW0::UCMM::SingleMasterEnvironment + + usci::UCBxCTLW0::UCMODE::I2CMode + + usci::UCBxCTLW0::UCSYNC::SynchronousMode + + usci::UCBxCTLW0::UCSSEL::SMCLK, ); + // Disable clock low timeout + // Send a NACK before a stop condition + // Generate the ACK bit by hardware + // Set glitch filtering to 50ns (according to I2C standard) self.registers.ctlw1.modify( - // Disable clock low timeout usci::UCBxCTLW1::UCCLTO::CLEAR - // Send a NACK before a stop condition - + usci::UCBxCTLW1::UCSTPNACK::NackBeforeStop - // Generate the ACK bit by hardware - + usci::UCBxCTLW1::UCSWACK::HardwareTriggered - // Set glitch filtering to 50ns (according to I2C standard) - + usci::UCBxCTLW1::UCGLIT::_50ns, + + usci::UCBxCTLW1::UCSTPNACK::NackBeforeStop + + usci::UCBxCTLW1::UCSWACK::HardwareTriggered + + usci::UCBxCTLW1::UCGLIT::_50ns, ); // Don't clear the module reset here since we set the state to Disabled @@ -196,7 +199,7 @@ impl<'a> I2c<'a> { if idx < self.write_len.get() { // Transmit another byte self.buffer - .map(|buf| self.registers.txbuf.set(buf[idx as usize] as u16)); + .map(|buf| self.registers.txbuf.set(buf[idx] as u16)); self.buf_idx.set(idx + 1); } else { self.disable_transmit_interrupt(); @@ -227,7 +230,7 @@ impl<'a> I2c<'a> { } // Store received byte in buffer self.buffer - .map(|buf| buf[idx as usize] = self.registers.rxbuf.get() as u8); + .map(|buf| buf[idx] = self.registers.rxbuf.get() as u8); self.buf_idx.set(idx + 1); } else if mode == OperatingMode::WriteReadRead { // For some reason generating a stop condition manually in receive mode doesn't @@ -239,7 +242,7 @@ impl<'a> I2c<'a> { // Start condition interrupt if mode == OperatingMode::Write || mode == OperatingMode::WriteReadWrite { self.buffer - .map(|buf| self.registers.txbuf.set(buf[idx as usize] as u16)); + .map(|buf| self.registers.txbuf.set(buf[idx] as u16)); self.buf_idx.set(idx + 1); } } else if (ifg & (1 << usci::UCBxIFG::UCSTPIFG.shift)) > 0 { @@ -267,8 +270,8 @@ impl<'a> I2c<'a> { } } -impl<'a> i2c::I2CMaster for I2c<'a> { - fn set_master_client(&self, master_client: &'static dyn i2c::I2CHwMasterClient) { +impl<'a> i2c::I2CMaster<'a> for I2c<'a> { + fn set_master_client(&self, master_client: &'a dyn i2c::I2CHwMasterClient) { self.master_client.replace(master_client); } @@ -290,7 +293,7 @@ impl<'a> i2c::I2CMaster for I2c<'a> { &self, addr: u8, data: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (Error, &'static mut [u8])> { if self.mode.get() != OperatingMode::Idle { // Module is busy or not activated @@ -328,7 +331,7 @@ impl<'a> i2c::I2CMaster for I2c<'a> { &self, addr: u8, buffer: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (Error, &'static mut [u8])> { if self.mode.get() != OperatingMode::Idle { // Module is busy or not activated @@ -363,8 +366,8 @@ impl<'a> i2c::I2CMaster for I2c<'a> { &self, addr: u8, data: &'static mut [u8], - write_len: u8, - read_len: u8, + write_len: usize, + read_len: usize, ) -> Result<(), (Error, &'static mut [u8])> { if self.mode.get() != OperatingMode::Idle { // Module is busy or not activated diff --git a/chips/msp432/src/lib.rs b/chips/msp432/src/lib.rs index 0f96b55394..7d57aa2a18 100644 --- a/chips/msp432/src/lib.rs +++ b/chips/msp432/src/lib.rs @@ -1,12 +1,12 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + #![crate_name = "msp432"] #![crate_type = "rlib"] -#![feature(asm, const_fn_trait_bound)] #![no_std] -use cortexm4::{ - generic_isr, hard_fault_handler, initialize_ram_jump_to_main, svc_handler, systick_handler, - unhandled_interrupt, -}; +use cortexm4::{initialize_ram_jump_to_main, unhandled_interrupt, CortexM4, CortexMVariant}; pub mod adc; pub mod chip; @@ -39,90 +39,90 @@ extern "C" { pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ _estack, initialize_ram_jump_to_main, - unhandled_interrupt, // NMI - hard_fault_handler, // Hard Fault - unhandled_interrupt, // MemManage - unhandled_interrupt, // BusFault - unhandled_interrupt, // UsageFault + unhandled_interrupt, // NMI + CortexM4::HARD_FAULT_HANDLER, // Hard Fault + unhandled_interrupt, // MemManage + unhandled_interrupt, // BusFault + unhandled_interrupt, // UsageFault unhandled_interrupt, unhandled_interrupt, unhandled_interrupt, unhandled_interrupt, - svc_handler, // SVC - unhandled_interrupt, // DebugMon + CortexM4::SVC_HANDLER, // SVC + unhandled_interrupt, // DebugMon unhandled_interrupt, - unhandled_interrupt, // PendSV - systick_handler, // SysTick + unhandled_interrupt, // PendSV + CortexM4::SYSTICK_HANDLER, // SysTick ]; #[cfg_attr(all(target_arch = "arm", target_os = "none"), link_section = ".irqs")] // used Ensures that the symbol is kept until the final binary #[cfg_attr(all(target_arch = "arm", target_os = "none"), used)] pub static IRQS: [unsafe extern "C" fn(); 64] = [ - generic_isr, // Power Supply System (PSS) (0) - generic_isr, // Clock System (CS) (1) - generic_isr, // Power Control Manager (PCM) (2) - generic_isr, // Watchdog Timer A (WDT_A) (3) - generic_isr, // FPU_INT, Combined interrupt from flags in FPSCR (4) - generic_isr, // FLash Controller (FLCTL) (5) - generic_isr, // Comparator E0 (6) - generic_isr, // Comparator E1 (7) - generic_isr, // Timer A0 TA0CCTL0.CCIFG (8) - generic_isr, // Timer A0 TA0CCTLx.CCIFG (x = 1 to 4), TA0CTL.TAIFG (9) - generic_isr, // Timer A1 TA1CCTL0.CCIFG (10) - generic_isr, // Timer A1 TA1CCTLx.CCIFG (x = 1 to 4), TA1CTL.TAIFG (11) - generic_isr, // Timer A2 TA2CCTL0.CCIFG (12) - generic_isr, // Timer A2 TA2CCTLx.CCIFG (x = 1 to 4), TA2CTL.TAIFG (13) - generic_isr, // Timer A3 TA3CCTL0.CCIFG (13) - generic_isr, // Timer A3 TA3CCTLx.CCIFG (x = 1 to 4), TA3CTL.TAIFG (15) - generic_isr, // eUSCI A0 (16) - generic_isr, // eUSCI A1 (17) - generic_isr, // eUSCI A2 (18) - generic_isr, // eUSCI A3 (19) - generic_isr, // eUSCI B0 (20) - generic_isr, // eUSCI B1 (21) - generic_isr, // eUSCI B2 (22) - generic_isr, // eUSCI B3 (23) - generic_isr, // Precision ADC (24) - generic_isr, // Timer32 INT1 (25) - generic_isr, // Timer32 INT2 (26) - generic_isr, // Timer32 combined interrupt (27) - generic_isr, // AES256 (28) - generic_isr, // RTC_C (29) - generic_isr, // DMA error (30) - generic_isr, // DMA INT3 (31) - generic_isr, // DMA INT2 (32) - generic_isr, // DMA INT1 (33) - generic_isr, // DMA INT0 (34) - generic_isr, // IO Port 1 (35) - generic_isr, // IO Port 2 (36) - generic_isr, // IO Port 3 (37) - generic_isr, // IO Port 4 (38) - generic_isr, // IO Port 5 (39) - generic_isr, // IO Port 6 (40) - unhandled_interrupt, // Reserved (41) - unhandled_interrupt, // Reserved (42) - unhandled_interrupt, // Reserved (43) - unhandled_interrupt, // Reserved (44) - unhandled_interrupt, // Reserved (45) - unhandled_interrupt, // Reserved (46) - unhandled_interrupt, // Reserved (47) - unhandled_interrupt, // Reserved (48) - unhandled_interrupt, // Reserved (49) - unhandled_interrupt, // Reserved (50) - unhandled_interrupt, // Reserved (51) - unhandled_interrupt, // Reserved (52) - unhandled_interrupt, // Reserved (53) - unhandled_interrupt, // Reserved (54) - unhandled_interrupt, // Reserved (55) - unhandled_interrupt, // Reserved (56) - unhandled_interrupt, // Reserved (57) - unhandled_interrupt, // Reserved (58) - unhandled_interrupt, // Reserved (59) - unhandled_interrupt, // Reserved (60) - unhandled_interrupt, // Reserved (61) - unhandled_interrupt, // Reserved (62) - unhandled_interrupt, // Reserved (63) + CortexM4::GENERIC_ISR, // Power Supply System (PSS) (0) + CortexM4::GENERIC_ISR, // Clock System (CS) (1) + CortexM4::GENERIC_ISR, // Power Control Manager (PCM) (2) + CortexM4::GENERIC_ISR, // Watchdog Timer A (WDT_A) (3) + CortexM4::GENERIC_ISR, // FPU_INT, Combined interrupt from flags in FPSCR (4) + CortexM4::GENERIC_ISR, // FLash Controller (FLCTL) (5) + CortexM4::GENERIC_ISR, // Comparator E0 (6) + CortexM4::GENERIC_ISR, // Comparator E1 (7) + CortexM4::GENERIC_ISR, // Timer A0 TA0CCTL0.CCIFG (8) + CortexM4::GENERIC_ISR, // Timer A0 TA0CCTLx.CCIFG (x = 1 to 4), TA0CTL.TAIFG (9) + CortexM4::GENERIC_ISR, // Timer A1 TA1CCTL0.CCIFG (10) + CortexM4::GENERIC_ISR, // Timer A1 TA1CCTLx.CCIFG (x = 1 to 4), TA1CTL.TAIFG (11) + CortexM4::GENERIC_ISR, // Timer A2 TA2CCTL0.CCIFG (12) + CortexM4::GENERIC_ISR, // Timer A2 TA2CCTLx.CCIFG (x = 1 to 4), TA2CTL.TAIFG (13) + CortexM4::GENERIC_ISR, // Timer A3 TA3CCTL0.CCIFG (13) + CortexM4::GENERIC_ISR, // Timer A3 TA3CCTLx.CCIFG (x = 1 to 4), TA3CTL.TAIFG (15) + CortexM4::GENERIC_ISR, // eUSCI A0 (16) + CortexM4::GENERIC_ISR, // eUSCI A1 (17) + CortexM4::GENERIC_ISR, // eUSCI A2 (18) + CortexM4::GENERIC_ISR, // eUSCI A3 (19) + CortexM4::GENERIC_ISR, // eUSCI B0 (20) + CortexM4::GENERIC_ISR, // eUSCI B1 (21) + CortexM4::GENERIC_ISR, // eUSCI B2 (22) + CortexM4::GENERIC_ISR, // eUSCI B3 (23) + CortexM4::GENERIC_ISR, // Precision ADC (24) + CortexM4::GENERIC_ISR, // Timer32 INT1 (25) + CortexM4::GENERIC_ISR, // Timer32 INT2 (26) + CortexM4::GENERIC_ISR, // Timer32 combined interrupt (27) + CortexM4::GENERIC_ISR, // AES256 (28) + CortexM4::GENERIC_ISR, // RTC_C (29) + CortexM4::GENERIC_ISR, // DMA error (30) + CortexM4::GENERIC_ISR, // DMA INT3 (31) + CortexM4::GENERIC_ISR, // DMA INT2 (32) + CortexM4::GENERIC_ISR, // DMA INT1 (33) + CortexM4::GENERIC_ISR, // DMA INT0 (34) + CortexM4::GENERIC_ISR, // IO Port 1 (35) + CortexM4::GENERIC_ISR, // IO Port 2 (36) + CortexM4::GENERIC_ISR, // IO Port 3 (37) + CortexM4::GENERIC_ISR, // IO Port 4 (38) + CortexM4::GENERIC_ISR, // IO Port 5 (39) + CortexM4::GENERIC_ISR, // IO Port 6 (40) + unhandled_interrupt, // Reserved (41) + unhandled_interrupt, // Reserved (42) + unhandled_interrupt, // Reserved (43) + unhandled_interrupt, // Reserved (44) + unhandled_interrupt, // Reserved (45) + unhandled_interrupt, // Reserved (46) + unhandled_interrupt, // Reserved (47) + unhandled_interrupt, // Reserved (48) + unhandled_interrupt, // Reserved (49) + unhandled_interrupt, // Reserved (50) + unhandled_interrupt, // Reserved (51) + unhandled_interrupt, // Reserved (52) + unhandled_interrupt, // Reserved (53) + unhandled_interrupt, // Reserved (54) + unhandled_interrupt, // Reserved (55) + unhandled_interrupt, // Reserved (56) + unhandled_interrupt, // Reserved (57) + unhandled_interrupt, // Reserved (58) + unhandled_interrupt, // Reserved (59) + unhandled_interrupt, // Reserved (60) + unhandled_interrupt, // Reserved (61) + unhandled_interrupt, // Reserved (62) + unhandled_interrupt, // Reserved (63) ]; pub unsafe fn init() { diff --git a/chips/msp432/src/nvic.rs b/chips/msp432/src/nvic.rs index ffa13ae6b0..b33ba67a55 100644 --- a/chips/msp432/src/nvic.rs +++ b/chips/msp432/src/nvic.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Nested Vector Interrupt Controller (NVIC) pub const PSS: u32 = 0; diff --git a/chips/msp432/src/pcm.rs b/chips/msp432/src/pcm.rs index 7d3ca1f46b..3bbe48cbd2 100644 --- a/chips/msp432/src/pcm.rs +++ b/chips/msp432/src/pcm.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Power Control Manager (PCM) use kernel::utilities::registers::interfaces::{Readable, Writeable}; diff --git a/chips/msp432/src/ref_module.rs b/chips/msp432/src/ref_module.rs index 6c6bf75547..b81f7e37cf 100644 --- a/chips/msp432/src/ref_module.rs +++ b/chips/msp432/src/ref_module.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Reference Module (REF) use core::cell::Cell; @@ -147,12 +151,11 @@ impl Ref { /// The default-setting is enabled. pub fn enable_temp_sensor(&self, enable: bool) { while self.registers.ctl0.is_set(CTL0::REFGENBUSY) {} - self.registers.ctl0.modify( - // Enable the temperature sensor - CTL0::REFTCOFF.val((!enable) as u16) - // Enable the reference module, otherwise the temperature sensor doesn't work - + CTL0::REFON::SET, - ); + // Enable the temperature sensor + // Enable the reference module, otherwise the temperature sensor doesn't work + self.registers + .ctl0 + .modify(CTL0::REFTCOFF.val((!enable) as u16) + CTL0::REFON::SET); } } diff --git a/chips/msp432/src/sysctl.rs b/chips/msp432/src/sysctl.rs index 8f4564eec0..14c7103f08 100644 --- a/chips/msp432/src/sysctl.rs +++ b/chips/msp432/src/sysctl.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! System Controller (SYSCTL) use kernel::utilities::registers::interfaces::ReadWriteable; diff --git a/chips/msp432/src/timer.rs b/chips/msp432/src/timer.rs index fa0da5c5b8..7a5524bed3 100644 --- a/chips/msp432/src/timer.rs +++ b/chips/msp432/src/timer.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Timer (TIMER_Ax) use core::cell::Cell; @@ -278,12 +282,17 @@ impl<'a> TimerA<'a> { // mode, divide the clock down to 2048Hz: // 16bit at 2048Hz: granulation about 0.5ms, maximum interval about 30s. + // Set ACLK as clock source + // Divide the clock source by 8 -> 4096Hz + // Setup for continuous mode + // Disable interrupts + // Clear any pending interrupts self.registers.ctl.modify( - TAxCTL::TASSEL::ACLK // Set ACLK as clock source - + TAxCTL::ID::DividedBy8 // Divide the clock source by 8 -> 4096Hz - + TAxCTL::MC::ContinuousMode // Setup for contiuous mode - + TAxCTL::TAIE::CLEAR // Disable interrupts - + TAxCTL::TAIFG::CLEAR, // Clear any pending interrupts + TAxCTL::TASSEL::ACLK + + TAxCTL::ID::DividedBy8 + + TAxCTL::MC::ContinuousMode + + TAxCTL::TAIE::CLEAR + + TAxCTL::TAIFG::CLEAR, ); // divide the 4096Hz by 2 to get 2048Hz @@ -402,7 +411,7 @@ impl<'a> Alarm<'a> for TimerA<'a> { } fn minimum_dt(&self) -> Self::Ticks { - Self::Ticks::from(1 as u16) + Self::Ticks::from(1_u16) } } @@ -430,11 +439,12 @@ impl<'a> InternalTimer for TimerA<'a> { crate::cs::SMCLK_HZ / frequency_hz }; + // Set SMCLK as clock source + // Setup for up-mode + // Disable interrupts + // Clear any pending interrupts self.registers.ctl.modify( - TAxCTL::TASSEL::SMCLK // Set SMCLK as clock source - + TAxCTL::MC::UpMode // Setup for up-mode - + TAxCTL::TAIE::CLEAR // Disable interrupts - + TAxCTL::TAIFG::CLEAR, // Clear any pending interrupts + TAxCTL::TASSEL::SMCLK + TAxCTL::MC::UpMode + TAxCTL::TAIE::CLEAR + TAxCTL::TAIFG::CLEAR, ); // Set timer value diff --git a/chips/msp432/src/uart.rs b/chips/msp432/src/uart.rs index c5ddf19d0f..713d3ec80a 100644 --- a/chips/msp432/src/uart.rs +++ b/chips/msp432/src/uart.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Universal Asynchronous Receiver/Transmitter (UART) use crate::dma; @@ -6,7 +10,6 @@ use core::cell::Cell; use kernel::hil; use kernel::utilities::cells::OptionalCell; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; -use kernel::utilities::registers::{ReadOnly, ReadWrite}; use kernel::utilities::StaticRef; use kernel::ErrorCode; @@ -19,7 +22,7 @@ struct BaudFraction { #[rustfmt::skip] // Table out of the datasheet to correct the baudrate -const BAUD_FRACTIONS: &'static [BaudFraction; 36] = &[ +const BAUD_FRACTIONS: &[BaudFraction; 36] = &[ BaudFraction { frac: 0.0000, reg_val: 0x00 }, BaudFraction { frac: 0.0529, reg_val: 0x01 }, BaudFraction { frac: 0.0715, reg_val: 0x02 }, @@ -89,14 +92,14 @@ impl<'a> Uart<'a> { tx_client: OptionalCell::empty(), tx_dma: OptionalCell::empty(), - tx_dma_chan: tx_dma_chan, - tx_dma_src: tx_dma_src, + tx_dma_chan, + tx_dma_src, tx_busy: Cell::new(false), rx_client: OptionalCell::empty(), rx_dma: OptionalCell::empty(), - rx_dma_chan: rx_dma_chan, - rx_dma_src: rx_dma_src, + rx_dma_chan, + rx_dma_src, rx_busy: Cell::new(false), } } @@ -121,23 +124,17 @@ impl<'a> dma::DmaClient for Uart<'a> { rx_buf: Option<&'static mut [u8]>, transmitted_bytes: usize, ) { - if rx_buf.is_some() { + if let Some(rxbuf) = rx_buf { // RX-transfer done self.rx_busy.set(false); self.rx_client.map(|client| { - client.received_buffer( - rx_buf.unwrap(), - transmitted_bytes, - Ok(()), - hil::uart::Error::None, - ) + client.received_buffer(rxbuf, transmitted_bytes, Ok(()), hil::uart::Error::None) }); - } else if tx_buf.is_some() { + } else if let Some(txbuf) = tx_buf { // TX-transfer done self.tx_busy.set(false); - self.tx_client.map(|client| { - client.transmitted_buffer(tx_buf.unwrap(), transmitted_bytes, Ok(())) - }); + self.tx_client + .map(|client| client.transmitted_buffer(txbuf, transmitted_bytes, Ok(()))); } } } @@ -253,7 +250,7 @@ impl<'a> hil::uart::Transmit<'a> for Uart<'a> { Err((ErrorCode::BUSY, tx_buffer)) } else { self.tx_busy.set(true); - let tx_reg = &self.registers.txbuf as *const ReadWrite as *const (); + let tx_reg = core::ptr::addr_of!(self.registers.txbuf).cast::<()>(); self.tx_dma .map(move |dma| dma.transfer_mem_to_periph(tx_reg, tx_buffer, tx_len)); Ok(()) @@ -273,8 +270,8 @@ impl<'a> hil::uart::Transmit<'a> for Uart<'a> { let (nr_bytes, tx1, _rx1, _tx2, _rx2) = dma.stop(); self.tx_client.map(move |cl| { - if tx1.is_some() { - cl.transmitted_buffer(tx1.unwrap(), nr_bytes, Err(ErrorCode::CANCEL)); + if let Some(tx1_buf) = tx1 { + cl.transmitted_buffer(tx1_buf, nr_bytes, Err(ErrorCode::CANCEL)); } }); }); @@ -301,7 +298,7 @@ impl<'a> hil::uart::Receive<'a> for Uart<'a> { Err((ErrorCode::BUSY, rx_buffer)) } else { self.rx_busy.set(true); - let rx_reg = &self.registers.rxbuf as *const ReadOnly as *const (); + let rx_reg = core::ptr::addr_of!(self.registers.rxbuf).cast::<()>(); self.rx_dma .map(move |dma| dma.transfer_periph_to_mem(rx_reg, rx_buffer, rx_len)); Ok(()) @@ -321,9 +318,9 @@ impl<'a> hil::uart::Receive<'a> for Uart<'a> { let (nr_bytes, _tx1, rx1, _tx2, _rx2) = dma.stop(); self.rx_client.map(move |cl| { - if rx1.is_some() { + if let Some(rx1_buf) = rx1 { cl.received_buffer( - rx1.unwrap(), + rx1_buf, nr_bytes, Err(ErrorCode::CANCEL), hil::uart::Error::Aborted, diff --git a/chips/msp432/src/usci.rs b/chips/msp432/src/usci.rs index 89784fdde4..9609d7137c 100644 --- a/chips/msp432/src/usci.rs +++ b/chips/msp432/src/usci.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! (enhanced) Universal Serial Communication Interface (USCI) use kernel::utilities::registers::{register_bitfields, register_structs, ReadOnly, ReadWrite}; diff --git a/chips/msp432/src/wdt.rs b/chips/msp432/src/wdt.rs index ffcd4847f0..fb08758c74 100644 --- a/chips/msp432/src/wdt.rs +++ b/chips/msp432/src/wdt.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Watchdog Timer (WDT) use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; @@ -115,12 +119,16 @@ impl kernel::platform::watchdog::WatchDog for Wdt { // write cycle where the config is applied in order to avoid unexpected interrupts and // resets. self.disable(); + // Set SMCLK as source -> 750kHz + // Enable Watchdog mode + // According to datasheet necessary + // Prescaler of 2^15 self.registers.ctl.modify( WDTCTL::WDTPW.val(PASSWORD) - + WDTCTL::WDTSSEL::SMCLK // Set SMCLK as source -> 750kHz - + WDTCTL::WDTTMSEL::WatchdogMode // Enable Watchdog mode - + WDTCTL::WDTCNTCL::SET // According to datasheet necessary - + WDTCTL::WDTIS::WatchdogClockSource2151SAt32768KHz, // Prescaler of 2^15 + + WDTCTL::WDTSSEL::SMCLK + + WDTCTL::WDTTMSEL::WatchdogMode + + WDTCTL::WDTCNTCL::SET + + WDTCTL::WDTIS::WatchdogClockSource2151SAt32768KHz, ); self.start(); diff --git a/chips/nrf52/Cargo.toml b/chips/nrf52/Cargo.toml index 24264d6c86..9c77199475 100644 --- a/chips/nrf52/Cargo.toml +++ b/chips/nrf52/Cargo.toml @@ -1,8 +1,12 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "nrf52" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] cortexm4 = { path = "../../arch/cortex-m4" } @@ -12,3 +16,6 @@ enum_primitive = { path = "../../libraries/enum_primitive" } [dependencies.nrf5x] path = "../nrf5x" features = ["nrf52"] + +[lints] +workspace = true diff --git a/chips/nrf52/src/acomp.rs b/chips/nrf52/src/acomp.rs index 76ef5e869c..13db2f2873 100644 --- a/chips/nrf52/src/acomp.rs +++ b/chips/nrf52/src/acomp.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Analog Comparator Peripheral Driver, for nrf52 //! //! Partially based on sam4l implementation of an analog comparator. @@ -231,7 +235,11 @@ impl<'a> Comparator<'a> { fn enable(&self) { // Checks if it's already enabled // Assumes no one else is writing to comp registers directly - if self.registers.enable.matches_any(Enable::ENABLE::Enabled) { + if self + .registers + .enable + .any_matching_bits_set(Enable::ENABLE::Enabled) + { return; } diff --git a/chips/nrf52/src/adc.rs b/chips/nrf52/src/adc.rs index f4bb014a91..43590e48c6 100644 --- a/chips/nrf52/src/adc.rs +++ b/chips/nrf52/src/adc.rs @@ -1,7 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! ADC driver for the nRF52. Uses the SAADC peripheral. +use core::cell::Cell; +use core::cmp; use kernel::hil; -use kernel::utilities::cells::{OptionalCell, VolatileCell}; +use kernel::utilities::cells::{OptionalCell, TakeCell, VolatileCell}; use kernel::utilities::registers::interfaces::{Readable, Writeable}; use kernel::utilities::registers::{register_bitfields, ReadOnly, ReadWrite, WriteOnly}; use kernel::utilities::StaticRef; @@ -320,20 +326,46 @@ impl AdcChannelSetup { } } -pub struct Adc { +#[derive(Clone, Copy)] +enum AdcMode { + Idle, + Calibrate, + Single, + HighSpeed, +} + +pub struct Adc<'a> { registers: StaticRef, - client: OptionalCell<&'static dyn hil::adc::Client>, + reference: Cell, + mode: Cell, + client: OptionalCell<&'a dyn hil::adc::Client>, + highspeed_client: OptionalCell<&'a dyn hil::adc::HighSpeedClient>, + + buffer: TakeCell<'static, [u16]>, + length: Cell, + next_buffer: TakeCell<'static, [u16]>, + next_length: Cell, } -impl Adc { - pub const fn new() -> Self { +impl<'a> Adc<'a> { + pub const fn new(voltage_reference_in_mv: usize) -> Self { Self { registers: SAADC_BASE, + reference: Cell::new(voltage_reference_in_mv), + mode: Cell::new(AdcMode::Idle), client: OptionalCell::empty(), + highspeed_client: OptionalCell::empty(), + buffer: TakeCell::empty(), + length: Cell::new(0), + next_buffer: TakeCell::empty(), + next_length: Cell::new(0), } } + // Calibrate and measure the actual VDD of the board. pub fn calibrate(&self) { + self.mode.set(AdcMode::Calibrate); + // Enable the ADC self.registers.enable.write(ENABLE::ENABLE::SET); self.registers.inten.write(INTEN::CALIBRATEDONE::SET); @@ -341,39 +373,163 @@ impl Adc { } pub fn handle_interrupt(&self) { - // Determine what event occurred. - if self.registers.events_calibratedone.is_set(EVENT::EVENT) { - self.registers - .events_calibratedone - .write(EVENT::EVENT::CLEAR); - self.registers.enable.write(ENABLE::ENABLE::CLEAR); - } else if self.registers.events_started.is_set(EVENT::EVENT) { - self.registers.events_started.write(EVENT::EVENT::CLEAR); - // ADC has started, now issue the sample. - self.registers.tasks_sample.write(TASK::TASK::SET); - } else if self.registers.events_end.is_set(EVENT::EVENT) { - self.registers.events_end.write(EVENT::EVENT::CLEAR); - // Reading finished. Turn off the ADC. - self.registers.tasks_stop.write(TASK::TASK::SET); - } else if self.registers.events_stopped.is_set(EVENT::EVENT) { - self.registers.events_stopped.write(EVENT::EVENT::CLEAR); - // ADC is stopped. Disable and return value. - self.registers.enable.write(ENABLE::ENABLE::CLEAR); - - let val = unsafe { SAMPLE[0] as i16 }; - self.client.map(|client| { - // shift left to meet the ADC HIL requirement - client.sample_ready(if val < 0 { 0 } else { val << 4 } as u16); - }); + match self.mode.get() { + AdcMode::Calibrate => { + if self.registers.events_calibratedone.is_set(EVENT::EVENT) { + self.registers + .events_calibratedone + .write(EVENT::EVENT::CLEAR); + + // After calibration, read VDD to set our voltage reference. + self.registers.ch[0].pselp.write(PSEL::PSEL::VDD); + self.registers.ch[0].pseln.write(PSEL::PSEL::NotConnected); + + // Configure the ADC for a single read. + self.registers.ch[0].config.write( + CONFIG::GAIN::Gain1_6 + + CONFIG::REFSEL::Internal + + CONFIG::TACQ::us10 + + CONFIG::RESP::Bypass + + CONFIG::RESN::Bypass + + CONFIG::MODE::SE, + ); + + self.setup_resolution(); + self.setup_sample_count(1); + + // Where to put the reading. + unsafe { + self.registers.result_ptr.set(SAMPLE.as_ptr()); + } + + // No automatic sampling, will trigger manually. + self.registers.samplerate.write(SAMPLERATE::MODE::Task); + + // Enable the ADC + self.registers.enable.write(ENABLE::ENABLE::SET); + + // Enable started, sample end, and stopped interrupts. + self.registers + .inten + .write(INTEN::STARTED::SET + INTEN::END::SET + INTEN::STOPPED::SET); + + self.registers.tasks_start.write(TASK::TASK::SET); + + // self.registers.enable.write(ENABLE::ENABLE::CLEAR); + } else if self.registers.events_started.is_set(EVENT::EVENT) { + self.registers.events_started.write(EVENT::EVENT::CLEAR); + // ADC has started, now issue the sample. + self.registers.tasks_sample.write(TASK::TASK::SET); + } else if self.registers.events_end.is_set(EVENT::EVENT) { + self.registers.events_end.write(EVENT::EVENT::CLEAR); + // Reading finished. Turn off the ADC. + self.registers.tasks_stop.write(TASK::TASK::SET); + } else if self.registers.events_stopped.is_set(EVENT::EVENT) { + self.registers.events_stopped.write(EVENT::EVENT::CLEAR); + // ADC is stopped. Disable and return value. + self.registers.enable.write(ENABLE::ENABLE::CLEAR); + + let reading = unsafe { SAMPLE[0] as i16 } as usize; + + // reading = val * (gain/ref) * 2^12 + // = val * ((1/6)/0.6 V) * 2^12 + // = val * 1/3600 mV * 2^12 + // val = (reading * 3600 mV) / 2^12 + let val = (reading * 3600) / (1 << 12); + + // If the reading looks like it exists in a reasonable range + // than save this as the reference. + if val > 1000 && val < 5100 { + self.reference.set(val); + } + } + } + + AdcMode::Single => { + // Determine what event occurred. + if self.registers.events_calibratedone.is_set(EVENT::EVENT) { + self.registers + .events_calibratedone + .write(EVENT::EVENT::CLEAR); + self.registers.enable.write(ENABLE::ENABLE::CLEAR); + } else if self.registers.events_started.is_set(EVENT::EVENT) { + self.registers.events_started.write(EVENT::EVENT::CLEAR); + // ADC has started, now issue the sample. + self.registers.tasks_sample.write(TASK::TASK::SET); + } else if self.registers.events_end.is_set(EVENT::EVENT) { + self.registers.events_end.write(EVENT::EVENT::CLEAR); + // Reading finished. Turn off the ADC. + self.registers.tasks_stop.write(TASK::TASK::SET); + } else if self.registers.events_stopped.is_set(EVENT::EVENT) { + self.registers.events_stopped.write(EVENT::EVENT::CLEAR); + // ADC is stopped. Disable and return value. + self.registers.enable.write(ENABLE::ENABLE::CLEAR); + + let val = unsafe { SAMPLE[0] as i16 }; + self.client.map(|client| { + // shift left to meet the ADC HIL requirement + client.sample_ready(if val < 0 { 0 } else { val << 4 } as u16); + }); + } + } + + AdcMode::HighSpeed => { + if self.registers.events_started.is_set(EVENT::EVENT) { + self.registers.events_started.write(EVENT::EVENT::CLEAR); + + // According to PS1.7 Section 6.23.4, we can set the new + // buffer address after we get the start event. + self.next_buffer.map(|buf| { + // First determine the buffer's length in samples. + let dma_len = cmp::min(buf.len(), self.next_length.get()); + if dma_len > 0 { + self.registers.result_ptr.set(buf.as_ptr()); + } + }); + + // Trigger sample task to start taking samples. + self.registers.tasks_sample.write(TASK::TASK::SET); + } else if self.registers.events_end.is_set(EVENT::EVENT) { + self.registers.events_end.write(EVENT::EVENT::CLEAR); + + let ret_buf = self.buffer.take().unwrap(); + + // Left shift all samples to the MSB. This handles + // differences in resolution between ADC chips and meets the + // ADC HIL requirement. + let length = self.length.get(); + for i in 0..length { + ret_buf[i] <<= 4; + } + + self.highspeed_client.map(|client| { + client.samples_ready(ret_buf, length); + }); + + // Optionally setup to continue reading. We already + // configured the address if valid. + let length2 = self.next_length.get(); + if length2 > 0 { + self.length.set(length2); + self.buffer.put(self.next_buffer.take()); + self.registers + .result_maxcnt + .write(RESULT_MAXCNT::MAXCNT.val(length2 as u32)); + kernel::debug!("len2 {}", length2); + + // self.registers.tasks_sample.write(TASK::TASK::SET); + self.registers.tasks_start.write(TASK::TASK::SET); + } + } else if self.registers.events_stopped.is_set(EVENT::EVENT) { + self.registers.events_stopped.write(EVENT::EVENT::CLEAR); + } + } + + AdcMode::Idle => {} } } -} -/// Implements an ADC capable reading ADC samples on any channel. -impl hil::adc::Adc for Adc { - type Channel = AdcChannelSetup; - - fn sample(&self, channel: &Self::Channel) -> Result<(), ErrorCode> { + fn setup_channel(&self, channel: &AdcChannelSetup) { // Positive goes to the channel passed in, negative not connected. self.registers.ch[0] .pselp @@ -389,9 +545,42 @@ impl hil::adc::Adc for Adc { + CONFIG::RESN.val(channel.resn as u32) + CONFIG::MODE::SE, ); + } + fn setup_resolution(&self) { // Set max resolution (with oversampling). self.registers.resolution.write(RESOLUTION::VAL::bit12); + } + + fn setup_sample_count(&self, count: usize) { + self.registers + .result_maxcnt + .write(RESULT_MAXCNT::MAXCNT.val(count as u32)); + } + + fn setup_frequency(&self, frequency: u32) { + let raw_cc = 16000000 / frequency; + let cc = if raw_cc > 2047 { + 2047 + } else if raw_cc < 80 { + 80 + } else { + raw_cc + }; + + self.registers + .samplerate + .write(SAMPLERATE::MODE::Timers + SAMPLERATE::CC.val(cc)); + } +} + +/// Implements an ADC capable reading ADC samples on any channel. +impl<'a> hil::adc::Adc<'a> for Adc<'a> { + type Channel = AdcChannelSetup; + + fn sample(&self, channel: &Self::Channel) -> Result<(), ErrorCode> { + self.setup_channel(channel); + self.setup_resolution(); // Do one measurement. self.registers @@ -413,6 +602,8 @@ impl hil::adc::Adc for Adc { .inten .write(INTEN::STARTED::SET + INTEN::END::SET + INTEN::STOPPED::SET); + self.mode.set(AdcMode::Single); + // Start the SAADC and wait for the started interrupt. self.registers.tasks_start.write(TASK::TASK::SET); @@ -428,7 +619,8 @@ impl hil::adc::Adc for Adc { } fn stop_sampling(&self) -> Result<(), ErrorCode> { - Err(ErrorCode::FAIL) + self.registers.tasks_stop.write(TASK::TASK::SET); + Ok(()) } fn get_resolution_bits(&self) -> usize { @@ -436,10 +628,89 @@ impl hil::adc::Adc for Adc { } fn get_voltage_reference_mv(&self) -> Option { - Some(3300) + Some(self.reference.get()) } - fn set_client(&self, client: &'static dyn hil::adc::Client) { + fn set_client(&self, client: &'a dyn hil::adc::Client) { self.client.set(client); } } + +impl<'a> hil::adc::AdcHighSpeed<'a> for Adc<'a> { + fn sample_highspeed( + &self, + channel: &Self::Channel, + frequency: u32, + buffer1: &'static mut [u16], + length1: usize, + buffer2: &'static mut [u16], + length2: usize, + ) -> Result<(), (ErrorCode, &'static mut [u16], &'static mut [u16])> { + if length1 == 0 { + // At least need to take one sample. + Err((ErrorCode::INVAL, buffer1, buffer2)) + } else { + // Store the second buffer for later use + self.next_buffer.replace(buffer2); + self.next_length.set(length2); + + self.setup_channel(channel); + self.setup_resolution(); + + // Use EasyDMA to save the samples to our buffer. + self.registers.result_ptr.set(buffer1.as_ptr()); + + // Also need to save these to return to the caller. + self.buffer.replace(buffer1); + self.length.set(length1); + + // Number of measurements. + self.setup_sample_count(length1); + + // Set the frequency best we can. + self.setup_frequency(frequency); + + // Enable the ADC + self.registers.enable.write(ENABLE::ENABLE::SET); + + // Enable started, sample end, and stopped interrupts. + self.registers + .inten + .write(INTEN::STARTED::SET + INTEN::END::SET + INTEN::STOPPED::SET); + + self.mode.set(AdcMode::HighSpeed); + + // Start the SAADC and wait for the started interrupt. + self.registers.tasks_start.write(TASK::TASK::SET); + + Ok(()) + } + } + + fn provide_buffer( + &self, + buf: &'static mut [u16], + length: usize, + ) -> Result<(), (ErrorCode, &'static mut [u16])> { + if self.next_buffer.is_some() { + // we've already got a second buffer, we don't need a third yet + Err((ErrorCode::BUSY, buf)) + } else { + // store the buffer for later use + self.next_buffer.replace(buf); + self.next_length.set(length); + + Ok(()) + } + } + + fn retrieve_buffers( + &self, + ) -> Result<(Option<&'static mut [u16]>, Option<&'static mut [u16]>), ErrorCode> { + Ok((self.buffer.take(), self.next_buffer.take())) + } + + fn set_highspeed_client(&self, client: &'a dyn hil::adc::HighSpeedClient) { + self.highspeed_client.set(client); + } +} diff --git a/chips/nrf52/src/approtect.rs b/chips/nrf52/src/approtect.rs new file mode 100644 index 0000000000..5900e10ecd --- /dev/null +++ b/chips/nrf52/src/approtect.rs @@ -0,0 +1,107 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Access port protection +//! +//! +//! +//! The logic around APPROTECT was changed in newer revisions of the nRF52 +//! series chips (Oct 2021) and later which requires more careful disabling of +//! the access port (JTAG), both in the UICR register and in a software written +//! register. This module enables the kernel to disable the protection on boot. +//! +//! Example code to disable the APPROTECT protection in software: +//! +//! ```rust,ignore +//! let approtect = nrf52::approtect::Approtect::new(); +//! approtect.sw_disable_approtect(); +//! ``` + +use crate::ficr; +use kernel::utilities::registers::interfaces::Writeable; +use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite}; +use kernel::utilities::StaticRef; + +const APPROTECT_BASE: StaticRef = + unsafe { StaticRef::new(0x40000000 as *const ApprotectRegisters) }; + +register_structs! { + ApprotectRegisters { + (0x000 => _reserved0), + (0x550 => forceprotect: ReadWrite), + (0x554 => _reserved1), + (0x558 => disable: ReadWrite), + (0x55c => @END), + } +} + +register_bitfields! [u32, + Forceprotect [ + FORCEPROTECT OFFSET(0) NUMBITS(8) [ + FORCE = 0 + ] + ], + /// Access port protection + Disable [ + DISABLE OFFSET(0) NUMBITS(8) [ + SWDISABLE = 0x5a + ] + ] +]; + +pub struct Approtect { + registers: StaticRef, +} + +impl Approtect { + pub const fn new() -> Approtect { + Approtect { + registers: APPROTECT_BASE, + } + } + + /// Software disable the Access Port Protection mechanism. + /// + /// On newer variants of the nRF52, to enable JTAG, APPROTECT must be + /// disabled both in the UICR register (hardware) and in this register + /// (software). For older variants this is just a no-op. + /// + /// - + /// - + pub fn sw_disable_approtect(&self) { + let factory_config = ficr::Ficr::new(); + match factory_config.variant() { + ficr::Variant::AAF0 | ficr::Variant::Unspecified => { + // Newer revisions of the chip require setting the APPROTECT + // software register to `SwDisable`. We assume that an unspecified + // version means that it is new and the FICR module hasn't been + // updated to recognize it. + self.registers.disable.write(Disable::DISABLE::SWDISABLE); + } + + // Exhaustively list variants here to produce compiler error on + // adding a new variant, which would otherwise not match the above + // condition. + ficr::Variant::AAA0 + | ficr::Variant::AAAA + | ficr::Variant::AAAB + | ficr::Variant::AAB0 + | ficr::Variant::AABA + | ficr::Variant::AABB + | ficr::Variant::AAC0 + | ficr::Variant::AACA + | ficr::Variant::AACB + | ficr::Variant::AAD0 + | ficr::Variant::AAD1 + | ficr::Variant::AADA + | ficr::Variant::AAE0 + | ficr::Variant::AAEA + | ficr::Variant::ABBA + | ficr::Variant::BAAA + | ficr::Variant::CAAA => { + // All other revisions don't need this. + } + } + } +} diff --git a/chips/nrf52/src/ble_radio.rs b/chips/nrf52/src/ble_radio.rs index c159b31463..8120d28c65 100644 --- a/chips/nrf52/src/ble_radio.rs +++ b/chips/nrf52/src/ble_radio.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Radio driver, Bluetooth Low Energy, NRF52 //! //! The generic radio configuration i.e., not specific to Bluetooth are functions and similar which @@ -34,7 +38,7 @@ //! * CRC - 3 bytes use core::cell::Cell; -use core::convert::TryFrom; +use core::ptr::addr_of_mut; use kernel::hil::ble_advertising; use kernel::hil::ble_advertising::RadioChannel; use kernel::utilities::cells::OptionalCell; @@ -641,7 +645,11 @@ impl<'a> Radio<'a> { // Length is: S0 (1 Byte) + Length (1 Byte) + S1 (0 Bytes) + Payload // And because the length field is directly read from the packet // We need to add 2 to length to get the total length - client.receive_event(&mut PAYLOAD, PAYLOAD[1] + 2, result) + client.receive_event( + &mut *addr_of_mut!(PAYLOAD), + PAYLOAD[1] + 2, + result, + ) }); } } @@ -818,7 +826,7 @@ impl ble_advertising::BleConfig for Radio<'_> { // Convert u8 to TxPower match nrf5x::constants::TxPower::try_from(tx_power) { // Invalid transmitting power, propogate error - Err(_) => Err(ErrorCode::NOSUPPORT), + Err(()) => Err(ErrorCode::NOSUPPORT), // Valid transmitting power, propogate success Ok(res) => { self.tx_power.set(res); diff --git a/chips/nrf52/src/chip.rs b/chips/nrf52/src/chip.rs index 7c31895600..ef3d098a3e 100644 --- a/chips/nrf52/src/chip.rs +++ b/chips/nrf52/src/chip.rs @@ -1,17 +1,18 @@ -use crate::deferred_call_tasks::DeferredCallTask; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt::Write; -use cortexm4::{self, nvic}; -use kernel::deferred_call; -use kernel::hil::time::Alarm; +use cortexm4::{nvic, CortexM4, CortexMVariant}; use kernel::platform::chip::InterruptService; -pub struct NRF52<'a, I: InterruptService + 'a> { +pub struct NRF52<'a, I: InterruptService + 'a> { mpu: cortexm4::mpu::MPU, userspace_kernel_boundary: cortexm4::syscall::SysCall, interrupt_service: &'a I, } -impl<'a, I: InterruptService + 'a> NRF52<'a, I> { +impl<'a, I: InterruptService + 'a> NRF52<'a, I> { pub unsafe fn new(interrupt_service: &'a I) -> Self { Self { mpu: cortexm4::mpu::MPU::new(), @@ -21,7 +22,8 @@ impl<'a, I: InterruptService + 'a> NRF52<'a, I> { } } -/// This struct, when initialized, instantiates all peripheral drivers for the apollo3. +/// This struct, when initialized, instantiates all peripheral drivers for the nrf52. +/// /// If a board wishes to use only a subset of these peripherals, this /// should not be used or imported, and a modified version should be /// constructed manually in main.rs. @@ -29,7 +31,6 @@ pub struct Nrf52DefaultPeripherals<'a> { pub acomp: crate::acomp::Comparator<'a>, pub ecb: crate::aes::AesECB<'a>, pub pwr_clk: crate::power::Power<'a>, - pub ieee802154_radio: crate::ieee802154_radio::Radio<'a>, pub ble_radio: crate::ble_radio::Radio<'a>, pub trng: crate::trng::Trng<'a>, pub rtc: crate::rtc::Rtc<'a>, @@ -38,12 +39,10 @@ pub struct Nrf52DefaultPeripherals<'a> { pub timer1: crate::timer::TimerAlarm<'a>, pub timer2: crate::timer::Timer, pub uarte0: crate::uart::Uarte<'a>, - pub spim0: crate::spi::SPIM, - pub twi0: crate::i2c::TWI, - pub spim1: crate::spi::SPIM, - pub twi1: crate::i2c::TWI, - pub spim2: crate::spi::SPIM, - pub adc: crate::adc::Adc, + pub spim0: crate::spi::SPIM<'a>, + pub twi1: crate::i2c::TWI<'a>, + pub spim2: crate::spi::SPIM<'a>, + pub adc: crate::adc::Adc<'a>, pub nvmc: crate::nvmc::Nvmc, pub clock: crate::clock::Clock, pub pwm0: crate::pwm::Pwm, @@ -55,7 +54,6 @@ impl<'a> Nrf52DefaultPeripherals<'a> { acomp: crate::acomp::Comparator::new(), ecb: crate::aes::AesECB::new(), pwr_clk: crate::power::Power::new(), - ieee802154_radio: crate::ieee802154_radio::Radio::new(), ble_radio: crate::ble_radio::Radio::new(), trng: crate::trng::Trng::new(), rtc: crate::rtc::Rtc::new(), @@ -63,45 +61,32 @@ impl<'a> Nrf52DefaultPeripherals<'a> { timer0: crate::timer::TimerAlarm::new(0), timer1: crate::timer::TimerAlarm::new(1), timer2: crate::timer::Timer::new(2), - uarte0: crate::uart::Uarte::new(), + uarte0: crate::uart::Uarte::new(crate::uart::UARTE0_BASE), spim0: crate::spi::SPIM::new(0), - twi0: crate::i2c::TWI::new_twi0(), - spim1: crate::spi::SPIM::new(1), twi1: crate::i2c::TWI::new_twi1(), spim2: crate::spi::SPIM::new(2), - adc: crate::adc::Adc::new(), + // Default to 3.3 V VDD reference. + adc: crate::adc::Adc::new(3300), nvmc: crate::nvmc::Nvmc::new(), clock: crate::clock::Clock::new(), pwm0: crate::pwm::Pwm::new(), } } // Necessary for setting up circular dependencies - pub fn init(&'a self) { - self.ieee802154_radio.set_timer_ref(&self.timer0); - self.timer0.set_alarm_client(&self.ieee802154_radio); + pub fn init(&'static self) { + kernel::deferred_call::DeferredCallClient::register(&self.nvmc); } } -impl<'a> kernel::platform::chip::InterruptService - for Nrf52DefaultPeripherals<'a> -{ +impl<'a> kernel::platform::chip::InterruptService for Nrf52DefaultPeripherals<'a> { unsafe fn service_interrupt(&self, interrupt: u32) -> bool { match interrupt { crate::peripheral_interrupts::COMP => self.acomp.handle_interrupt(), crate::peripheral_interrupts::ECB => self.ecb.handle_interrupt(), crate::peripheral_interrupts::POWER_CLOCK => self.pwr_clk.handle_interrupt(), - crate::peripheral_interrupts::RADIO => { - match ( - self.ieee802154_radio.is_enabled(), - self.ble_radio.is_enabled(), - ) { - (false, false) => (), - (true, false) => self.ieee802154_radio.handle_interrupt(), - (false, true) => self.ble_radio.handle_interrupt(), - (true, true) => kernel::debug!( - "nRF 802.15.4 and BLE radios cannot be simultaneously enabled!" - ), - } - } + crate::peripheral_interrupts::RADIO => match self.ble_radio.is_enabled() { + false => (), + true => self.ble_radio.handle_interrupt(), + }, crate::peripheral_interrupts::RNG => self.trng.handle_interrupt(), crate::peripheral_interrupts::RTC1 => self.rtc.handle_interrupt(), crate::peripheral_interrupts::TEMP => self.temp.handle_interrupt(), @@ -109,49 +94,17 @@ impl<'a> kernel::platform::chip::InterruptService crate::peripheral_interrupts::TIMER1 => self.timer1.handle_interrupt(), crate::peripheral_interrupts::TIMER2 => self.timer2.handle_interrupt(), crate::peripheral_interrupts::UART0 => self.uarte0.handle_interrupt(), - crate::peripheral_interrupts::SPI0_TWI0 => { - // SPI0 and TWI0 share interrupts. - // Dispatch the correct handler. - match (self.spim0.is_enabled(), self.twi0.is_enabled()) { - (false, false) => (), - (true, false) => self.spim0.handle_interrupt(), - (false, true) => self.twi0.handle_interrupt(), - (true, true) => debug_assert!( - false, - "SPIM0 and TWIM0 cannot be \ - enabled at the same time." - ), - } - } - crate::peripheral_interrupts::SPI1_TWI1 => { - // SPI1 and TWI1 share interrupts. - // Dispatch the correct handler. - match (self.spim1.is_enabled(), self.twi1.is_enabled()) { - (false, false) => (), - (true, false) => self.spim1.handle_interrupt(), - (false, true) => self.twi1.handle_interrupt(), - (true, true) => debug_assert!( - false, - "SPIM1 and TWIM1 cannot be \ - enabled at the same time." - ), - } - } + crate::peripheral_interrupts::SPI0_TWI0 => self.spim0.handle_interrupt(), + crate::peripheral_interrupts::SPI1_TWI1 => self.twi1.handle_interrupt(), crate::peripheral_interrupts::SPIM2_SPIS2_SPI2 => self.spim2.handle_interrupt(), crate::peripheral_interrupts::ADC => self.adc.handle_interrupt(), _ => return false, } true } - unsafe fn service_deferred_call(&self, task: DeferredCallTask) -> bool { - match task { - DeferredCallTask::Nvmc => self.nvmc.handle_interrupt(), - } - true - } } -impl<'a, I: InterruptService + 'a> kernel::platform::chip::Chip for NRF52<'a, I> { +impl<'a, I: InterruptService + 'a> kernel::platform::chip::Chip for NRF52<'a, I> { type MPU = cortexm4::mpu::MPU; type UserspaceKernelBoundary = cortexm4::syscall::SysCall; @@ -166,11 +119,7 @@ impl<'a, I: InterruptService + 'a> kernel::platform::chip::Chi fn service_pending_interrupts(&self) { unsafe { loop { - if let Some(task) = deferred_call::DeferredCall::next_pending() { - if !self.interrupt_service.service_deferred_call(task) { - panic!("unhandled deferred call task"); - } - } else if let Some(interrupt) = nvic::next_pending() { + if let Some(interrupt) = nvic::next_pending() { if !self.interrupt_service.service_interrupt(interrupt) { panic!("unhandled interrupt {}", interrupt); } @@ -185,7 +134,7 @@ impl<'a, I: InterruptService + 'a> kernel::platform::chip::Chi } fn has_pending_interrupts(&self) -> bool { - unsafe { nvic::has_pending() || deferred_call::has_tasks() } + unsafe { nvic::has_pending() } } fn sleep(&self) { @@ -202,6 +151,6 @@ impl<'a, I: InterruptService + 'a> kernel::platform::chip::Chi } unsafe fn print_state(&self, write: &mut dyn Write) { - cortexm4::print_cortexm4_state(write); + CortexM4::print_cortexm_state(write); } } diff --git a/chips/nrf52/src/clock.rs b/chips/nrf52/src/clock.rs index b5ca77f24e..ef3bc49f0b 100644 --- a/chips/nrf52/src/clock.rs +++ b/chips/nrf52/src/clock.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Clock peripheral driver, nRF52 //! //! Based on Phil Levis clock driver for nRF51 diff --git a/chips/nrf52/src/crt1.rs b/chips/nrf52/src/crt1.rs index edf77f49f5..38a8bd12dd 100644 --- a/chips/nrf52/src/crt1.rs +++ b/chips/nrf52/src/crt1.rs @@ -1,6 +1,9 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use cortexm4::{ - generic_isr, hard_fault_handler, initialize_ram_jump_to_main, nvic, scb, svc_handler, - systick_handler, unhandled_interrupt, + initialize_ram_jump_to_main, nvic, scb, unhandled_interrupt, CortexM4, CortexMVariant, }; /* @@ -33,7 +36,7 @@ pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ // NMI unhandled_interrupt, // Hard Fault - hard_fault_handler, + CortexM4::HARD_FAULT_HANDLER, // Memory Management Fault unhandled_interrupt, // Bus Fault @@ -49,7 +52,7 @@ pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ // Reserved unhandled_interrupt, // SVCall - svc_handler, + CortexM4::SVC_HANDLER, // Reserved for Debug unhandled_interrupt, // Reserved @@ -57,7 +60,7 @@ pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ // PendSv unhandled_interrupt, // SysTick - systick_handler, + CortexM4::SYSTICK_HANDLER, ]; #[cfg_attr( @@ -66,7 +69,7 @@ pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ )] // used Ensures that the symbol is kept until the final binary #[cfg_attr(all(target_arch = "arm", target_os = "none"), used)] -pub static IRQS: [unsafe extern "C" fn(); 80] = [generic_isr; 80]; +pub static IRQS: [unsafe extern "C" fn(); 80] = [CortexM4::GENERIC_ISR; 80]; #[no_mangle] pub unsafe extern "C" fn init() { @@ -143,7 +146,7 @@ pub unsafe extern "C" fn init() { // correctly for Tock. The bootloader _may_ set this for us, but it may not // so that any errors early in the Tock boot process trap back to the bootloader. // To be safe we unconditionally set the vector table. - scb::set_vector_table_offset(BASE_VECTORS.as_ptr() as *const ()); + scb::set_vector_table_offset(BASE_VECTORS.as_ptr().cast::<()>()); nvic::enable_all(); } diff --git a/chips/nrf52/src/deferred_call_tasks.rs b/chips/nrf52/src/deferred_call_tasks.rs deleted file mode 100644 index 97326b2563..0000000000 --- a/chips/nrf52/src/deferred_call_tasks.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Definition of Deferred Call tasks. -//! -//! Deferred calls also peripheral drivers to register pseudo interrupts. -//! These are the definitions of which deferred calls this chip needs. - -use core::convert::Into; -use core::convert::TryFrom; - -/// A type of task to defer a call for -#[derive(Copy, Clone)] -pub enum DeferredCallTask { - Nvmc = 0, -} - -impl TryFrom for DeferredCallTask { - type Error = (); - - fn try_from(value: usize) -> Result { - match value { - 0 => Ok(DeferredCallTask::Nvmc), - _ => Err(()), - } - } -} - -impl Into for DeferredCallTask { - fn into(self) -> usize { - self as usize - } -} diff --git a/chips/nrf52/src/ficr.rs b/chips/nrf52/src/ficr.rs index ddd6a4093f..3a84d4e1cc 100644 --- a/chips/nrf52/src/ficr.rs +++ b/chips/nrf52/src/ficr.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Factory Information Configuration Registers (FICR) //! //! Factory information configuration registers (FICR) are pre-programmed in @@ -167,8 +171,16 @@ register_bitfields! [u32, ABBA = 0x41424241, /// AAD0 AAD0 = 0x41414430, + /// AAD1 + AAD1 = 0x41414431, + /// AADA + AADA = 0x41414441, /// AAE0 AAE0 = 0x41414530, + /// AAEA + AAEA = 0x41414541, + /// AAF0 + AAF0 = 0x41414630, /// BAAA BAAA = 0x42414141, /// CAAA @@ -234,7 +246,7 @@ register_bitfields! [u32, /// Variant describes part variant, hardware version, and production configuration. #[derive(PartialEq, Debug)] #[repr(u32)] -enum Variant { +pub(crate) enum Variant { AAA0 = 0x41414130, AAAA = 0x41414141, AAAB = 0x41414142, @@ -244,9 +256,13 @@ enum Variant { AAC0 = 0x41414330, AACA = 0x41414341, AACB = 0x41414342, - ABBA = 0x41424241, AAD0 = 0x41414430, + AAD1 = 0x41414431, + AADA = 0x41414441, AAE0 = 0x41414530, + AAEA = 0x41414541, + AAF0 = 0x41414630, + ABBA = 0x41424241, BAAA = 0x42414141, CAAA = 0x43414141, Unspecified = 0xffffffff, @@ -306,7 +322,7 @@ pub struct Ficr { } impl Ficr { - const fn new() -> Ficr { + pub(crate) const fn new() -> Ficr { Ficr { registers: FICR_BASE, } @@ -321,7 +337,9 @@ impl Ficr { } } - fn variant(&self) -> Variant { + pub(crate) fn variant(&self) -> Variant { + // If you update this, make sure to update + // `has_updated_approtect_logic()` as well. match self.registers.info_variant.get() { 0x41414130 => Variant::AAA0, 0x41414141 => Variant::AAAA, @@ -334,13 +352,32 @@ impl Ficr { 0x41414342 => Variant::AACB, 0x41424241 => Variant::ABBA, 0x41414430 => Variant::AAD0, + 0x41414431 => Variant::AAD1, + 0x41414441 => Variant::AADA, 0x41414530 => Variant::AAE0, + 0x41414541 => Variant::AAEA, + 0x41414630 => Variant::AAF0, 0x42414141 => Variant::BAAA, 0x43414141 => Variant::CAAA, _ => Variant::Unspecified, } } + /// Returns if this variant of the nRF52 has the updated APPROTECT logic. + /// This changed occurred towards the end of 2021 with chips becoming widely + /// available/used in 2023. + /// + /// See . + /// for more information. + pub(crate) fn has_updated_approtect_logic(&self) -> bool { + // We assume that an unspecified version means that it is new and this + // module hasn't been updated to recognize it. + match self.variant() { + Variant::AAF0 | Variant::Unspecified => true, + _ => false, + } + } + fn package(&self) -> Package { match self.registers.info_package.get() { 0x2000 => Package::QF, @@ -374,6 +411,15 @@ impl Ficr { } } + pub fn id(&self) -> [u8; 8] { + let lo = self.registers.deviceid0.read(DeviceId0::DEVICEID); + let hi = self.registers.deviceid1.read(DeviceId1::DEVICEID); + let mut addr = [0; 8]; + addr[..4].copy_from_slice(&lo.to_le_bytes()); + addr[4..].copy_from_slice(&hi.to_le_bytes()); + addr + } + pub fn address(&self) -> [u8; 6] { let lo = self .registers @@ -412,31 +458,28 @@ impl Ficr { .deviceaddr1 .read(DeviceAddress1::DEVICEADDRESS); - let h: [u8; 16] = [ - '0' as u8, '1' as u8, '2' as u8, '3' as u8, '4' as u8, '5' as u8, '6' as u8, '7' as u8, - '8' as u8, '9' as u8, 'a' as u8, 'b' as u8, 'c' as u8, 'd' as u8, 'e' as u8, 'f' as u8, - ]; + let h: [u8; 16] = *b"0123456789abcdef"; buf[0] = h[((hi >> 12) & 0xf) as usize]; buf[1] = h[((hi >> 8) & 0xf) as usize]; - buf[2] = ':' as u8; + buf[2] = b':'; buf[3] = h[((hi >> 4) & 0xf) as usize]; buf[4] = h[((hi >> 0) & 0xf) as usize]; - buf[5] = ':' as u8; + buf[5] = b':'; buf[6] = h[((lo >> 28) & 0xf) as usize]; buf[7] = h[((lo >> 24) & 0xf) as usize]; - buf[8] = ':' as u8; + buf[8] = b':'; buf[9] = h[((lo >> 20) & 0xf) as usize]; buf[10] = h[((lo >> 16) & 0xf) as usize]; - buf[11] = ':' as u8; + buf[11] = b':'; buf[12] = h[((lo >> 12) & 0xf) as usize]; buf[13] = h[((lo >> 8) & 0xf) as usize]; - buf[14] = ':' as u8; + buf[14] = b':'; buf[15] = h[((lo >> 4) & 0xf) as usize]; buf[16] = h[((lo >> 0) & 0xf) as usize]; // Safe because we use only ascii characters in this buffer. - unsafe { &*(buf as *const [u8] as *const str) } + unsafe { &*(core::ptr::from_ref::<[u8]>(buf) as *const str) } } } diff --git a/chips/nrf52/src/i2c.rs b/chips/nrf52/src/i2c.rs index d284567827..b86fe61ce0 100644 --- a/chips/nrf52/src/i2c.rs +++ b/chips/nrf52/src/i2c.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of I2C for nRF52 using EasyDMA. //! //! This module supports nRF52's two I2C master (`TWI`) peripherals, @@ -24,10 +28,10 @@ const INSTANCES: [StaticRef; 2] = unsafe { /// /// A `TWI` instance wraps a `registers::TWI` together with /// additional data necessary to implement an asynchronous interface. -pub struct TWI { +pub struct TWI<'a> { registers: StaticRef, - client: OptionalCell<&'static dyn hil::i2c::I2CHwMasterClient>, - slave_client: OptionalCell<&'static dyn hil::i2c::I2CHwSlaveClient>, + client: OptionalCell<&'a dyn hil::i2c::I2CHwMasterClient>, + slave_client: OptionalCell<&'a dyn hil::i2c::I2CHwSlaveClient>, buf: TakeCell<'static, [u8]>, slave_read_buf: TakeCell<'static, [u8]>, } @@ -40,7 +44,7 @@ pub enum Speed { K400 = 0x06400000, } -impl TWI { +impl<'a> TWI<'a> { const fn new(registers: StaticRef) -> Self { Self { registers, @@ -71,18 +75,43 @@ impl TWI { self.registers.frequency.set(speed as u32); } + /// Clear all pending events + /// This is useful when switching between a master and slave mode to ensure + /// we start from a clean state. + pub fn clear_events(&self) { + self.registers.events_stopped.write(EVENT::EVENT::CLEAR); + self.registers.events_error.write(EVENT::EVENT::CLEAR); + self.registers.events_rxstarted.write(EVENT::EVENT::CLEAR); + self.registers.events_txstarted.write(EVENT::EVENT::CLEAR); + self.registers.events_write.write(EVENT::EVENT::CLEAR); + self.registers.events_read.write(EVENT::EVENT::CLEAR); + self.registers.events_suspended.write(EVENT::EVENT::CLEAR); + self.registers.events_lastrx.write(EVENT::EVENT::CLEAR); + self.registers.events_lasttx.write(EVENT::EVENT::CLEAR); + } + + pub fn disable_interrupts(&self) { + // Disable all interrupts + self.registers.inten.set(0x00); + self.registers.intenclr.set(0xFFFF_FFFF); + } + /// Enables hardware TWIM peripheral. fn enable_master(&self) { + self.clear_events(); self.registers.enable.write(ENABLE::ENABLE::EnableMaster); } /// Enables hardware TWIS peripheral. fn enable_slave(&self) { + self.clear_events(); self.registers.enable.write(ENABLE::ENABLE::EnableSlave); } /// Disables hardware TWIM/TWIS peripheral. fn disable(&self) { + self.clear_events(); + self.disable_interrupts(); self.registers.enable.write(ENABLE::ENABLE::Disable); } @@ -94,6 +123,7 @@ impl TWI { self.client.map(|client| match self.buf.take() { None => (), Some(buf) => { + self.clear_events(); client.command_complete(buf, Ok(())); } }); @@ -115,6 +145,7 @@ impl TWI { } else { Ok(()) }; + self.clear_events(); client.command_complete(buf, status); } }); @@ -122,25 +153,36 @@ impl TWI { } else { self.registers.events_stopped.write(EVENT::EVENT::CLEAR); - // If RX started and we don't have a buffer then report - // read_expected() + // If RX started (master started write) and we don't have a buffer then report + // write_expected() if self.registers.events_rxstarted.is_set(EVENT::EVENT) { self.registers.events_rxstarted.write(EVENT::EVENT::CLEAR); - self.slave_client - .map(|client| match self.slave_read_buf.take() { - None => { - client.read_expected(); - } - Some(_buf) => {} - }); + self.slave_client.map(|client| { + if self.buf.is_none() { + client.write_expected(); + } + }); } + // If TX started (master started read) and we don't have a buffer then report + // read_expected() + if self.registers.events_txstarted.is_set(EVENT::EVENT) { + self.registers.events_txstarted.write(EVENT::EVENT::CLEAR); + self.slave_client.map(|client| { + if self.slave_read_buf.is_none() { + client.read_expected(); + } + }); + } + + // Write command received if self.registers.events_write.is_set(EVENT::EVENT) { self.registers.events_write.write(EVENT::EVENT::CLEAR); - let length = self.registers.rxd_amount.read(AMOUNT::AMOUNT) as u8; + let length = self.registers.rxd_amount.read(AMOUNT::AMOUNT) as usize; self.slave_client.map(|client| match self.buf.take() { None => (), Some(buf) => { + self.clear_events(); client.command_complete( buf, length, @@ -152,11 +194,12 @@ impl TWI { if self.registers.events_read.is_set(EVENT::EVENT) { self.registers.events_read.write(EVENT::EVENT::CLEAR); - let length = self.registers.txd_amount.read(AMOUNT::AMOUNT) as u8; + let length = self.registers.txd_amount.read(AMOUNT::AMOUNT) as usize; self.slave_client .map(|client| match self.slave_read_buf.take() { None => (), Some(buf) => { + self.clear_events(); client.command_complete( buf, length, @@ -169,7 +212,6 @@ impl TWI { // We can blindly clear the following events since we're not using them. self.registers.events_suspended.write(EVENT::EVENT::CLEAR); - self.registers.events_rxstarted.write(EVENT::EVENT::CLEAR); self.registers.events_lastrx.write(EVENT::EVENT::CLEAR); self.registers.events_lasttx.write(EVENT::EVENT::CLEAR); } @@ -191,8 +233,8 @@ impl TWI { } } -impl hil::i2c::I2CMaster for TWI { - fn set_master_client(&self, client: &'static dyn hil::i2c::I2CHwMasterClient) { +impl<'a> hil::i2c::I2CMaster<'a> for TWI<'a> { + fn set_master_client(&self, client: &'a dyn hil::i2c::I2CHwMasterClient) { self.client.set(client); } @@ -208,8 +250,8 @@ impl hil::i2c::I2CMaster for TWI { &self, addr: u8, data: &'static mut [u8], - write_len: u8, - read_len: u8, + write_len: usize, + read_len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { self.registers .address_0 @@ -242,7 +284,7 @@ impl hil::i2c::I2CMaster for TWI { &self, addr: u8, data: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { self.registers .address_0 @@ -269,7 +311,7 @@ impl hil::i2c::I2CMaster for TWI { &self, addr: u8, buffer: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { self.registers .address_0 @@ -293,8 +335,8 @@ impl hil::i2c::I2CMaster for TWI { } } -impl hil::i2c::I2CSlave for TWI { - fn set_slave_client(&self, client: &'static dyn hil::i2c::I2CHwSlaveClient) { +impl<'a> hil::i2c::I2CSlave<'a> for TWI<'a> { + fn set_slave_client(&self, client: &'a dyn hil::i2c::I2CHwSlaveClient) { self.slave_client.set(client); } @@ -317,7 +359,7 @@ impl hil::i2c::I2CSlave for TWI { fn write_receive( &self, data: &'static mut [u8], - max_len: u8, + max_len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { self.registers.rxd_ptr.set(data.as_mut_ptr() as u32); self.registers @@ -338,7 +380,7 @@ impl hil::i2c::I2CSlave for TWI { fn read_send( &self, data: &'static mut [u8], - max_len: u8, + max_len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { self.registers.txd_ptr.set(data.as_mut_ptr() as u32); self.registers @@ -361,6 +403,8 @@ impl hil::i2c::I2CSlave for TWI { } } +impl<'a> hil::i2c::I2CMasterSlave<'a> for TWI<'a> {} + // The SPI0_TWI0 and SPI1_TWI1 interrupts are dispatched to the // correct handler by the service_pending_interrupts() routine in // chip.rs based on which peripheral is enabled. @@ -585,7 +629,7 @@ register_bitfields![u32, MAXCNT OFFSET(0) NUMBITS(16) ], AMOUNT [ - AMOUNT OFFSET(0) NUMBITS(7), + AMOUNT OFFSET(0) NUMBITS(16), ], ADDRESS [ /// Address used in the TWI transfer diff --git a/chips/nrf52/src/ieee802154_radio.rs b/chips/nrf52/src/ieee802154_radio.rs deleted file mode 100644 index 2091d6bc1c..0000000000 --- a/chips/nrf52/src/ieee802154_radio.rs +++ /dev/null @@ -1,1164 +0,0 @@ -//! IEEE 802.15.4 radio driver for nRF52 - -use core::cell::Cell; -use core::convert::TryFrom; -use kernel; -use kernel::hil::radio::{self, PowerClient}; -use kernel::hil::time::{Alarm, AlarmClient}; -use kernel::utilities::cells::{OptionalCell, TakeCell}; -use kernel::utilities::registers::interfaces::{Readable, Writeable}; -use kernel::utilities::registers::{register_bitfields, ReadOnly, ReadWrite, WriteOnly}; -use kernel::utilities::StaticRef; -use kernel::ErrorCode; - -use nrf5x; -use nrf5x::constants::TxPower; - -// This driver has some significant flaws -- no ACK support, power cycles -// the radio after every transmission or reception, -// doesn't always check hardware for errors and instead defaults to -// returning Ok(()). However as of 05/26/20 it does interoperate -// with other 15.4 implementations for Tock's basic 15.4 apps. - -const RADIO_BASE: StaticRef = - unsafe { StaticRef::new(0x40001000 as *const RadioRegisters) }; - -pub const IEEE802154_PAYLOAD_LENGTH: usize = 255; -pub const IEEE802154_BACKOFF_PERIOD: usize = 320; //microseconds = 20 symbols -pub const IEEE802154_ACK_TIME: usize = 512; //microseconds = 32 symbols -pub const IEEE802154_MAX_POLLING_ATTEMPTS: u8 = 4; -pub const IEEE802154_MIN_BE: u8 = 3; -pub const IEEE802154_MAX_BE: u8 = 5; -pub const RAM_LEN_BITS: usize = 8; -pub const RAM_S1_BITS: usize = 0; -pub const PREBUF_LEN_BYTES: usize = 2; - -// artifact of entanglement with rf233 implementation, mac layer -// places packet data starting PSDU_OFFSET=2 bytes after start of -// buffer to make room for 1 byte spi header required when communicating -// with rf233 over SPI. nrf radio does not need this header, so we -// have to pretend the frame buffer starts 1 byte later than the -// frame passed by the mac layer. We can't just drop the byte from -// the buffer because then it would be lost forever when we tried -// to return the frame buffer. -const MIMIC_PSDU_OFFSET: u32 = 1; - -// IEEEStd 802.15.4-2011 Section 8.1.2.2 -// Frequency is 2405 + 5 * (k - 11) MHz, where k = 11, 12, ... , 26. -#[derive(PartialEq, Debug, Copy, Clone)] -pub enum RadioChannel { - DataChannel11 = 5, - DataChannel12 = 10, - DataChannel13 = 15, - DataChannel14 = 20, - DataChannel15 = 25, - DataChannel16 = 30, - DataChannel17 = 35, - DataChannel18 = 40, - DataChannel19 = 45, - DataChannel20 = 50, - DataChannel21 = 55, - DataChannel22 = 60, - DataChannel23 = 65, - DataChannel24 = 70, - DataChannel25 = 75, - DataChannel26 = 80, -} - -impl RadioChannel { - pub fn get_channel_index(&self) -> u8 { - match *self { - RadioChannel::DataChannel11 => 11, - RadioChannel::DataChannel12 => 12, - RadioChannel::DataChannel13 => 13, - RadioChannel::DataChannel14 => 14, - RadioChannel::DataChannel15 => 15, - RadioChannel::DataChannel16 => 16, - RadioChannel::DataChannel17 => 17, - RadioChannel::DataChannel18 => 18, - RadioChannel::DataChannel19 => 19, - RadioChannel::DataChannel20 => 20, - RadioChannel::DataChannel21 => 21, - RadioChannel::DataChannel22 => 22, - RadioChannel::DataChannel23 => 23, - RadioChannel::DataChannel24 => 24, - RadioChannel::DataChannel25 => 25, - RadioChannel::DataChannel26 => 26, - } - } -} - -impl TryFrom for RadioChannel { - type Error = (); - - fn try_from(val: u8) -> Result { - match val { - 11 => Ok(RadioChannel::DataChannel11), - 12 => Ok(RadioChannel::DataChannel12), - 13 => Ok(RadioChannel::DataChannel13), - 14 => Ok(RadioChannel::DataChannel14), - 15 => Ok(RadioChannel::DataChannel15), - 16 => Ok(RadioChannel::DataChannel16), - 17 => Ok(RadioChannel::DataChannel17), - 18 => Ok(RadioChannel::DataChannel18), - 19 => Ok(RadioChannel::DataChannel19), - 20 => Ok(RadioChannel::DataChannel20), - 21 => Ok(RadioChannel::DataChannel21), - 22 => Ok(RadioChannel::DataChannel22), - 23 => Ok(RadioChannel::DataChannel23), - 24 => Ok(RadioChannel::DataChannel24), - 25 => Ok(RadioChannel::DataChannel25), - 26 => Ok(RadioChannel::DataChannel26), - _ => Err(()), - } - } -} - -#[repr(C)] -struct RadioRegisters { - /// Enable Radio in TX mode - /// - Address: 0x000 - 0x004 - task_txen: WriteOnly, - /// Enable Radio in RX mode - /// - Address: 0x004 - 0x008 - task_rxen: WriteOnly, - /// Start Radio - /// - Address: 0x008 - 0x00c - task_start: WriteOnly, - /// Stop Radio - /// - Address: 0x00c - 0x010 - task_stop: WriteOnly, - /// Disable Radio - /// - Address: 0x010 - 0x014 - task_disable: WriteOnly, - /// Start the RSSI and take one single sample of the receive signal strength - /// - Address: 0x014- 0x018 - task_rssistart: WriteOnly, - /// Stop the RSSI measurement - /// - Address: 0x018 - 0x01c - task_rssistop: WriteOnly, - /// Start the bit counter - /// - Address: 0x01c - 0x020 - task_bcstart: WriteOnly, - /// Stop the bit counter - /// - Address: 0x020 - 0x024 - task_bcstop: WriteOnly, - /// Reserved - _reserved1: [u32; 2], - /// Stop the bit counter - /// - Address: 0x02c - 0x030 - task_ccastart: WriteOnly, - /// Stop the bit counter - /// - Address: 0x030 - 0x034 - task_ccastop: WriteOnly, - /// Reserved - _reserved2: [u32; 51], - /// Radio has ramped up and is ready to be started - /// - Address: 0x100 - 0x104 - event_ready: ReadWrite, - /// Address sent or received - /// - Address: 0x104 - 0x108 - event_address: ReadWrite, - /// Packet payload sent or received - /// - Address: 0x108 - 0x10c - event_payload: ReadWrite, - /// Packet sent or received - /// - Address: 0x10c - 0x110 - event_end: ReadWrite, - /// Radio has been disabled - /// - Address: 0x110 - 0x114 - event_disabled: ReadWrite, - /// A device address match occurred on the last received packet - /// - Address: 0x114 - 0x118 - event_devmatch: ReadWrite, - /// No device address match occurred on the last received packet - /// - Address: 0x118 - 0x11c - event_devmiss: ReadWrite, - /// Sampling of receive signal strength complete - /// - Address: 0x11c - 0x120 - event_rssiend: ReadWrite, - /// Reserved - _reserved3: [u32; 2], - /// Bit counter reached bit count value - /// - Address: 0x128 - 0x12c - event_bcmatch: ReadWrite, - /// Reserved - _reserved4: [u32; 1], - /// Packet received with CRC ok - /// - Address: 0x130 - 0x134 - event_crcok: ReadWrite, - /// Packet received with CRC error - /// - Address: 0x134 - 0x138 - crcerror: ReadWrite, - /// IEEE 802.15.4 length field received - /// - Address: 0x138 - 0x13c - event_framestart: ReadWrite, - /// Reserved - _reserved5: [u32; 2], - /// Wireless medium in idle - clear to send - /// - Address: 0x144-0x148 - event_ccaidle: ReadWrite, - /// Wireless medium busy - do not send - /// - Address: 0x148-0x14c - event_ccabusy: ReadWrite, - /// Reserved - _reserved6: [u32; 45], - /// Shortcut register - /// - Address: 0x200 - 0x204 - shorts: ReadWrite, - /// Reserved - _reserved7: [u32; 64], - /// Enable interrupt - /// - Address: 0x304 - 0x308 - intenset: ReadWrite, - /// Disable interrupt - /// - Address: 0x308 - 0x30c - intenclr: ReadWrite, - /// Reserved - _reserved8: [u32; 61], - /// CRC status - /// - Address: 0x400 - 0x404 - crcstatus: ReadOnly, - /// Reserved - _reserved9: [u32; 1], - /// Received address - /// - Address: 0x408 - 0x40c - rxmatch: ReadOnly, - /// CRC field of previously received packet - /// - Address: 0x40c - 0x410 - rxcrc: ReadOnly, - /// Device address match index - /// - Address: 0x410 - 0x414 - dai: ReadOnly, - /// Reserved - _reserved10: [u32; 60], - /// Packet pointer - /// - Address: 0x504 - 0x508 - packetptr: ReadWrite, - /// Frequency - /// - Address: 0x508 - 0x50c - frequency: ReadWrite, - /// Output power - /// - Address: 0x50c - 0x510 - txpower: ReadWrite, - /// Data rate and modulation - /// - Address: 0x510 - 0x514 - mode: ReadWrite, - /// Packet configuration register 0 - /// - Address 0x514 - 0x518 - pcnf0: ReadWrite, - /// Packet configuration register 1 - /// - Address: 0x518 - 0x51c - pcnf1: ReadWrite, - /// Base address 0 - /// - Address: 0x51c - 0x520 - base0: ReadWrite, - /// Base address 1 - /// - Address: 0x520 - 0x524 - base1: ReadWrite, - /// Prefix bytes for logical addresses 0-3 - /// - Address: 0x524 - 0x528 - prefix0: ReadWrite, - /// Prefix bytes for logical addresses 4-7 - /// - Address: 0x528 - 0x52c - prefix1: ReadWrite, - /// Transmit address select - /// - Address: 0x52c - 0x530 - txaddress: ReadWrite, - /// Receive address select - /// - Address: 0x530 - 0x534 - rxaddresses: ReadWrite, - /// CRC configuration - /// - Address: 0x534 - 0x538 - crccnf: ReadWrite, - /// CRC polynomial - /// - Address: 0x538 - 0x53c - crcpoly: ReadWrite, - /// CRC initial value - /// - Address: 0x53c - 0x540 - crcinit: ReadWrite, - /// Reserved - _reserved11: [u32; 1], - /// Interframe spacing in microseconds - /// - Address: 0x544 - 0x548 - tifs: ReadWrite, - /// RSSI sample - /// - Address: 0x548 - 0x54c - rssisample: ReadWrite, - /// Reserved - _reserved12: [u32; 1], - /// Current radio state - /// - Address: 0x550 - 0x554 - state: ReadOnly, - /// Data whitening initial value - /// - Address: 0x554 - 0x558 - datawhiteiv: ReadWrite, - /// Reserved - _reserved13: [u32; 2], - /// Bit counter compare - /// - Address: 0x560 - 0x564 - bcc: ReadWrite, - /// Reserved - _reserved14: [u32; 39], - /// Device address base segments - /// - Address: 0x600 - 0x620 - dab: [ReadWrite; 8], - /// Device address prefix - /// - Address: 0x620 - 0x640 - dap: [ReadWrite; 8], - /// Device address match configuration - /// - Address: 0x640 - 0x644 - dacnf: ReadWrite, - /// MAC header Search Pattern Configuration - /// - Address: 0x644 - 0x648 - mhrmatchconf: ReadWrite, - /// MAC Header Search Pattern Mask - /// - Address: 0x648 - 0x64C - mhrmatchmas: ReadWrite, - /// Reserved - _reserved15: [u32; 1], - /// Radio mode configuration register - /// - Address: 0x650 - 0x654 - modecnf0: ReadWrite, - /// Reserved - _reserved16: [u32; 6], - /// Clear Channel Assesment (CCA) control register - /// - Address: 0x66C - 0x670 - ccactrl: ReadWrite, - /// Reserved - _reserved17: [u32; 611], - /// Peripheral power control - /// - Address: 0xFFC - 0x1000 - power: ReadWrite, -} - -register_bitfields! [u32, - /// Task register - Task [ - /// Enable task - ENABLE OFFSET(0) NUMBITS(1) - ], - /// Event register - Event [ - /// Ready event - READY OFFSET(0) NUMBITS(1) - ], - /// Shortcut register - Shortcut [ - /// Shortcut between READY event and START task - READY_START OFFSET(0) NUMBITS(1), - /// Shortcut between END event and DISABLE task - END_DISABLE OFFSET(1) NUMBITS(1), - /// Shortcut between DISABLED event and TXEN task - DISABLED_TXEN OFFSET(2) NUMBITS(1), - /// Shortcut between DISABLED event and RXEN task - DISABLED_RXEN OFFSET(3) NUMBITS(1), - /// Shortcut between ADDRESS event and RSSISTART task - ADDRESS_RSSISTART OFFSET(4) NUMBITS(1), - /// Shortcut between END event and START task - END_START OFFSET(5) NUMBITS(1), - /// Shortcut between ADDRESS event and BCSTART task - ADDRESS_BCSTART OFFSET(6) NUMBITS(1), - /// Shortcut between DISABLED event and RSSISTOP task - DISABLED_RSSISTOP OFFSET(8) NUMBITS(1) - ], - /// Interrupt register - Interrupt [ - /// READY event - READY OFFSET(0) NUMBITS(1), - /// ADDRESS event - ADDRESS OFFSET(1) NUMBITS(1), - /// PAYLOAD event - PAYLOAD OFFSET(2) NUMBITS(1), - /// END event - END OFFSET(3) NUMBITS(1), - /// DISABLED event - DISABLED OFFSET(4) NUMBITS(1), - /// DEVMATCH event - DEVMATCH OFFSET(5) NUMBITS(1), - /// DEVMISS event - DEVMISS OFFSET(6) NUMBITS(1), - /// RSSIEND event - RSSIEND OFFSET(7) NUMBITS(1), - /// BCMATCH event - BCMATCH OFFSET(10) NUMBITS(1), - /// CRCOK event - CRCOK OFFSET(12) NUMBITS(1), - /// CRCERROR event - CRCERROR OFFSET(13) NUMBITS(1), - /// CCAIDLE event - FRAMESTART OFFSET(14) NUMBITS(1), - /// CCAIDLE event - CCAIDLE OFFSET(17) NUMBITS(1), - /// CCABUSY event - CCABUSY OFFSET(18) NUMBITS(1) - ], - /// Receive match register - ReceiveMatch [ - /// Logical address of which previous packet was received - MATCH OFFSET(0) NUMBITS(3) - ], - /// Received CRC register - ReceiveCrc [ - /// CRC field of previously received packet - CRC OFFSET(0) NUMBITS(24) - ], - /// Device address match index register - DeviceAddressIndex [ - /// Device address match index - /// Index (n) of device address, see DAB\[n\] and DAP\[n\], that got an - /// address match - INDEX OFFSET(0) NUMBITS(3) - ], - /// Packet pointer register - PacketPointer [ - /// Packet address to be used for the next transmission or reception. When transmitting, the packet pointed to by this - /// address will be transmitted and when receiving, the received packet will be written to this address. This address is a byte - /// aligned ram address. - POINTER OFFSET(0) NUMBITS(32) - ], - /// Frequency register - Frequency [ - /// Radio channel frequency - /// Frequency = 2400 + FREQUENCY (MHz) - FREQUENCY OFFSET(0) NUMBITS(7) [], - /// Channel map selection. - /// Channel map between 2400 MHZ .. 2500 MHZ - MAP OFFSET(8) NUMBITS(1) [ - DEFAULT = 0, - LOW = 1 - ] - ], - /// Transmitting power register - TransmitPower [ - /// Radio output power - POWER OFFSET(0) NUMBITS(8) [ - POS4DBM = 4, - POS3DBM = 3, - ODBM = 0, - NEG4DBM = 0xfc, - NEG8DBM = 0xf8, - NEG12DBM = 0xf4, - NEG16DBM = 0xf0, - NEG20DBM = 0xec, - NEG40DBM = 0xd8 - ] - ], - /// Data rate and modulation register - Mode [ - /// Radio data rate and modulation setting. - /// The radio supports Frequency-shift Keying (FSK) modulation - MODE OFFSET(0) NUMBITS(4) [ - NRF_1MBIT = 0, - NRF_2MBIT = 1, - NRF_250KBIT = 2, - BLE_1MBIT = 3, - BLE_2MBIT = 4, - BLE_LR125KBIT = 5, - BLE_LR500KBIT = 6, - IEEE802154_250KBIT = 15 - ] - ], - /// Packet configuration register 0 - PacketConfiguration0 [ - /// Length on air of LENGTH field in number of bits - LFLEN OFFSET(0) NUMBITS(4) [], - /// Length on air of S0 field in number of bytes - S0LEN OFFSET(8) NUMBITS(1) [], - /// Length on air of S1 field in number of bits. - S1LEN OFFSET(16) NUMBITS(4) [], - /// Include or exclude S1 field in RAM - S1INCL OFFSET(20) NUMBITS(1) [ - AUTOMATIC = 0, - INCLUDE = 1 - ], - /// Length of preamble on air. Decision point: TASKS_START task - PLEN OFFSET(24) NUMBITS(2) [ - EIGHT = 0, - SIXTEEN = 1, - THIRTYTWOZEROS = 2, - LONGRANGE = 3 - ], - CRCINC OFFSET(26) NUMBITS(1) [ - EXCLUDE = 0, - INCLUDE = 1 - ] - ], - /// Packet configuration register 1 - PacketConfiguration1 [ - /// Maximum length of packet payload - MAXLEN OFFSET(0) NUMBITS(8) [], - /// Static length in number of bytes - STATLEN OFFSET(8) NUMBITS(8) [], - /// Base address length in number of bytes - BALEN OFFSET(16) NUMBITS(3) [], - /// On air endianness - ENDIAN OFFSET(24) NUMBITS(1) [ - LITTLE = 0, - BIG = 1 - ], - /// Enable or disable packet whitening - WHITEEN OFFSET(25) NUMBITS(1) [ - DISABLED = 0, - ENABLED = 1 - ] - ], - /// Radio base address register - BaseAddress [ - /// BASE0 or BASE1 - BASE OFFSET(0) NUMBITS(32) - ], - /// Radio prefix0 registers - Prefix0 [ - /// Address prefix 0 - AP0 OFFSET(0) NUMBITS(8), - /// Address prefix 1 - AP1 OFFSET(8) NUMBITS(8), - /// Address prefix 2 - AP2 OFFSET(16) NUMBITS(8), - /// Address prefix 3 - AP3 OFFSET(24) NUMBITS(8) - ], - /// Radio prefix0 registers - Prefix1 [ - /// Address prefix 4 - AP4 OFFSET(0) NUMBITS(8), - /// Address prefix 5 - AP5 OFFSET(8) NUMBITS(8), - /// Address prefix 6 - AP6 OFFSET(16) NUMBITS(8), - /// Address prefix 7 - AP7 OFFSET(24) NUMBITS(8) - ], - /// Transmit address register - TransmitAddress [ - /// Logical address to be used when transmitting a packet - ADDRESS OFFSET(0) NUMBITS(3) - ], - /// Receive addresses register - ReceiveAddresses [ - /// Enable or disable reception on logical address 0-7 - ADDRESS OFFSET(0) NUMBITS(8) - ], - /// CRC configuration register - CrcConfiguration [ - /// CRC length in bytes - LEN OFFSET(0) NUMBITS(2) [ - DISABLED = 0, - ONE = 1, - TWO = 2, - THREE = 3 - ], - /// Include or exclude packet field from CRC calculation - SKIPADDR OFFSET(8) NUMBITS(2) [ - INCLUDE = 0, - EXCLUDE = 1, - IEEE802154 = 2 - ] - ], - /// CRC polynomial register - CrcPolynomial [ - /// CRC polynomial - CRCPOLY OFFSET(0) NUMBITS(24) - ], - /// CRC initial value register - CrcInitialValue [ - /// Initial value for CRC calculation - CRCINIT OFFSET(0) NUMBITS(24) - ], - /// Inter Frame Spacing in us register - InterFrameSpacing [ - /// Inter Frame Spacing in us - /// Inter frame space is the time interval between two consecutive packets. It is defined as the time, in micro seconds, from the - /// end of the last bit of the previous packet to the start of the first bit of the subsequent packet - TIFS OFFSET(0) NUMBITS(8) - ], - /// RSSI sample register - RssiSample [ - /// RSSI sample result - RSSISAMPLE OFFSET(0) NUMBITS(7) - ], - /// Radio state register - State [ - /// Current radio state - STATE OFFSET(0) NUMBITS(4) [ - DISABLED = 0, - RXRU = 1, - RXIDLE = 2, - RX = 3, - RXDISABLED = 4, - TXRU = 9, - TXIDLE = 10, - TX = 11, - TXDISABLED = 12 - ] - ], - /// Data whitening initial value register - DataWhiteIv [ - /// Data whitening initial value. Bit 6 is hard-wired to '1', writing '0' - /// to it has no effect, and it will always be read back and used by the device as '1' - DATEWHITEIV OFFSET(0) NUMBITS(7) - ], - /// Bit counter compare register - BitCounterCompare [ - /// Bit counter compare - BCC OFFSET(0) NUMBITS(32) - ], - /// Device address base register - DeviceAddressBase [ - /// Device address base 0-7 - DAB OFFSET(0) NUMBITS(32) - ], - /// Device address prefix register - DeviceAddressPrefix [ - /// Device address prefix 0-7 - DAP OFFSET(0) NUMBITS(32) - ], - /// Device address match configuration register - DeviceAddressMatch [ - /// Enable or disable device address matching on 0-7 - ENA OFFSET(0) NUMBITS(8), - /// TxAdd for device address 0-7 - TXADD OFFSET(8) NUMBITS(8) - ], - MACHeaderSearch [ - CONFIG OFFSET(0) NUMBITS(32) - ], - MACHeaderMask [ - PATTERN OFFSET(0) NUMBITS(32) - ], - CCAControl [ - CCAMODE OFFSET(0) NUMBITS(3) [ - ED_MODE = 0, - CARRIER_MODE = 1, - CARRIER_AND_ED_MODE = 2, - CARRIER_OR_ED_MODE = 3, - ED_MODE_TEST_1 = 4 - ], - CCAEDTHRESH OFFSET(8) NUMBITS(8) [], - CCACORRTHRESH OFFSET(16) NUMBITS(8) [], - CCACORRCNT OFFSET(24) NUMBITS(8) [] - ], - /// Radio mode configuration register - RadioModeConfig [ - /// Radio ramp-up time - RU OFFSET(0) NUMBITS(1) [ - DEFAULT = 0, - FAST = 1 - ], - /// Default TX value - /// Specifies what the RADIO will transmit when it is not started, i.e. between: - /// RADIO.EVENTS_READY and RADIO.TASKS_START - /// RADIO.EVENTS_END and RADIO.TASKS_START - DTX OFFSET(8) NUMBITS(2) [ - B1 = 0, - B0 = 1, - CENTER = 2 - ] - ] -]; - -pub struct Radio<'p> { - registers: StaticRef, - tx_power: Cell, - rx_client: OptionalCell<&'static dyn radio::RxClient>, - tx_client: OptionalCell<&'static dyn radio::TxClient>, - tx_buf: TakeCell<'static, [u8]>, - rx_buf: TakeCell<'static, [u8]>, - addr: Cell, - addr_long: Cell<[u8; 8]>, - pan: Cell, - cca_count: Cell, - cca_be: Cell, - random_nonce: Cell, - channel: Cell, - transmitting: Cell, - timer0: OptionalCell<&'p crate::timer::TimerAlarm<'p>>, -} - -impl<'a> AlarmClient for Radio<'a> { - fn alarm(&self) { - self.rx(); - } -} - -impl<'p> Radio<'p> { - pub const fn new() -> Self { - Self { - registers: RADIO_BASE, - tx_power: Cell::new(TxPower::ZerodBm), - rx_client: OptionalCell::empty(), - tx_client: OptionalCell::empty(), - tx_buf: TakeCell::empty(), - rx_buf: TakeCell::empty(), - addr: Cell::new(0), - addr_long: Cell::new([0x00; 8]), - pan: Cell::new(0), - cca_count: Cell::new(0), - cca_be: Cell::new(0), - random_nonce: Cell::new(0xDEADBEEF), - channel: Cell::new(RadioChannel::DataChannel26), - transmitting: Cell::new(false), - timer0: OptionalCell::empty(), - } - } - - pub fn set_timer_ref(&self, timer: &'p crate::timer::TimerAlarm<'p>) { - self.timer0.set(timer); - } - - pub fn is_enabled(&self) -> bool { - self.registers - .mode - .matches_all(Mode::MODE::IEEE802154_250KBIT) - } - - fn rx(&self) { - self.registers.event_ready.write(Event::READY::CLEAR); - - if self.transmitting.get() { - let tbuf = self.tx_buf.take().unwrap(); // Unwrap fail = Radio TX Buffer produced an invalid result when setting the DMA pointer. - - self.tx_buf.replace(self.set_dma_ptr(tbuf)); - } else { - let rbuf = self.rx_buf.take().unwrap(); // Unwrap fail = Radio RX Buffer produced an invalid result when setting the DMA pointer. - self.rx_buf.replace(self.set_dma_ptr(rbuf)); - } - - self.registers.task_rxen.write(Task::ENABLE::SET); - - self.enable_interrupts(); - } - - fn set_rx_address(&self) { - self.registers - .rxaddresses - .write(ReceiveAddresses::ADDRESS.val(1)); - } - - fn set_tx_address(&self) { - self.registers - .txaddress - .write(TransmitAddress::ADDRESS.val(0)); - } - - fn radio_on(&self) { - // reset and enable power - self.registers.power.write(Task::ENABLE::CLEAR); - self.registers.power.write(Task::ENABLE::SET); - } - - fn radio_off(&self) { - self.registers.power.write(Task::ENABLE::CLEAR); - } - - fn set_tx_power(&self) { - self.registers.txpower.set(self.tx_power.get() as u32); - } - - fn set_dma_ptr(&self, buffer: &'static mut [u8]) -> &'static mut [u8] { - self.registers - .packetptr - .set(buffer.as_ptr() as u32 + MIMIC_PSDU_OFFSET); - buffer - } - - // TODO: Theres an additional step for 802154 rx/tx handling - #[inline(never)] - pub fn handle_interrupt(&self) { - self.disable_all_interrupts(); - - if self.registers.event_ready.is_set(Event::READY) { - self.registers.event_ready.write(Event::READY::CLEAR); - self.registers.event_end.write(Event::READY::CLEAR); - if self.transmitting.get() - && self.registers.state.get() == nrf5x::constants::RADIO_STATE_RXIDLE - { - self.registers.task_ccastart.write(Task::ENABLE::SET); - } else { - self.registers.task_start.write(Task::ENABLE::SET); - } - } - - if self.registers.event_framestart.is_set(Event::READY) { - self.registers.event_framestart.write(Event::READY::CLEAR); - } - - // IF we receive the go ahead (channel is clear) - // THEN start the transmit part of the radio - if self.registers.event_ccaidle.is_set(Event::READY) { - self.registers.event_ccaidle.write(Event::READY::CLEAR); - self.registers.task_txen.write(Task::ENABLE::SET) - } - - if self.registers.event_ccabusy.is_set(Event::READY) { - self.registers.event_ccabusy.write(Event::READY::CLEAR); - self.registers.event_ready.write(Event::READY::CLEAR); - self.registers.task_disable.write(Task::ENABLE::SET); - while self.registers.event_disabled.get() == 0 {} - self.registers.event_disabled.write(Event::READY::CLEAR); - //need to back off for a period of time outlined - //in the IEEE 802.15.4 standard (see Figure 69 in - //section 7.5.1.4 The CSMA-CA algorithm of the - //standard). - if self.cca_count.get() < IEEE802154_MAX_POLLING_ATTEMPTS { - self.cca_count.set(self.cca_count.get() + 1); - self.cca_be.set(self.cca_be.get() + 1); - let backoff_periods = self.random_nonce() & ((1 << self.cca_be.get()) - 1); - self.timer0 - .unwrap_or_panic() // Unwrap fail = Missing timer reference for CSMA - .set_alarm( - kernel::hil::time::Ticks32::from(0), - kernel::hil::time::Ticks32::from( - backoff_periods * (IEEE802154_BACKOFF_PERIOD as u32), - ), - ); - } else { - self.transmitting.set(false); - //if we are transmitting, the CRCstatus check is always going to be an error - let result = Err(ErrorCode::BUSY); - //TODO: Acked is flagged as false until I get around to fixing it. - self.tx_client.map(|client| { - let tbuf = self.tx_buf.take().unwrap(); // Unwrap fail = TX Buffer produced error when sending it back to the requestor after the channel was busy. - client.send_done(tbuf, false, result) - }); - } - - self.enable_interrupts(); - } - - // tx or rx finished! - if self.registers.event_end.is_set(Event::READY) { - self.registers.event_end.write(Event::READY::CLEAR); - - let result = if self.registers.crcstatus.is_set(Event::READY) { - Ok(()) - } else { - Err(ErrorCode::FAIL) - }; - - match self.registers.state.get() { - nrf5x::constants::RADIO_STATE_TXRU - | nrf5x::constants::RADIO_STATE_TXIDLE - | nrf5x::constants::RADIO_STATE_TXDISABLE - | nrf5x::constants::RADIO_STATE_TX => { - self.transmitting.set(false); - //if we are transmitting, the CRCstatus check is always going to be an error - let result = Ok(()); - //TODO: Acked is flagged as false until I get around to fixing it. - self.tx_client.map(|client| { - let tbuf = self.tx_buf.take().unwrap(); // Unwrap fail = TX Buffer produced error when sending it back to the requestor after successful transmission. - - client.send_done(tbuf, false, result) - }); - } - nrf5x::constants::RADIO_STATE_RXRU - | nrf5x::constants::RADIO_STATE_RXIDLE - | nrf5x::constants::RADIO_STATE_RXDISABLE - | nrf5x::constants::RADIO_STATE_RX => { - self.rx_client.map(|client| { - let rbuf = self.rx_buf.take().unwrap(); // Unwrap fail = RX Buffer produced error when sending received packet to requestor - - let frame_len = rbuf[MIMIC_PSDU_OFFSET as usize] as usize - radio::MFR_SIZE; - // Length is: S0 (0 Byte) + Length (1 Byte) + S1 (0 Bytes) + Payload - // And because the length field is directly read from the packet - // We need to add 2 to length to get the total length - - client.receive(rbuf, frame_len, self.registers.crcstatus.get() == 1, result) - }); - } - // Radio state - Disabled - _ => (), - } - self.radio_off(); - self.radio_initialize(); - self.rx(); - } - self.enable_interrupts(); - } - - pub fn enable_interrupts(&self) { - self.registers.intenset.write( - Interrupt::READY::SET - + Interrupt::CCAIDLE::SET - + Interrupt::CCABUSY::SET - + Interrupt::END::SET - + Interrupt::FRAMESTART::SET, - ); - } - - pub fn enable_interrupt(&self, intr: u32) { - self.registers.intenset.set(intr); - } - - pub fn clear_interrupt(&self, intr: u32) { - self.registers.intenclr.set(intr); - } - - pub fn disable_all_interrupts(&self) { - // disable all possible interrupts - self.registers.intenclr.set(0xffffffff); - } - - fn radio_initialize(&self) { - self.radio_on(); - - // Radio disable - self.registers.event_disabled.set(0); - self.registers.task_disable.write(Task::ENABLE::SET); - while self.registers.event_disabled.get() == 0 {} - // end radio disable - - self.ieee802154_set_channel_rate(); - - self.ieee802154_set_packet_config(); - - self.ieee802154_set_crc_config(); - - self.ieee802154_set_rampup_mode(); - - self.ieee802154_set_cca_config(); - - self.ieee802154_set_tx_power(); - - self.ieee802154_set_channel_freq(self.channel.get()); - - self.set_tx_address(); - self.set_rx_address(); - - // First step in transmitting or receiving is entering rx mode - self.rx(); - } - - // IEEE802.15.4 SPECIFICATION Section 6.20.12.5 of the NRF52840 Datasheet - fn ieee802154_set_crc_config(&self) { - self.registers - .crccnf - .write(CrcConfiguration::LEN::TWO + CrcConfiguration::SKIPADDR::IEEE802154); - self.registers - .crcinit - .set(nrf5x::constants::RADIO_CRCINIT_IEEE802154); - self.registers - .crcpoly - .set(nrf5x::constants::RADIO_CRCPOLY_IEEE802154); - } - - fn ieee802154_set_rampup_mode(&self) { - self.registers - .modecnf0 - .write(RadioModeConfig::RU::FAST + RadioModeConfig::DTX::CENTER); - } - - fn ieee802154_set_cca_config(&self) { - self.registers.ccactrl.write( - CCAControl::CCAMODE.val(nrf5x::constants::IEEE802154_CCA_MODE) - + CCAControl::CCAEDTHRESH.val(nrf5x::constants::IEEE802154_CCA_ED_THRESH) - + CCAControl::CCACORRTHRESH.val(nrf5x::constants::IEEE802154_CCA_CORR_THRESH) - + CCAControl::CCACORRCNT.val(nrf5x::constants::IEEE802154_CCA_CORR_CNT), - ); - } - - // Packet configuration - // Settings taken from RiotOS nrf52840 15.4 driver - fn ieee802154_set_packet_config(&self) { - self.registers.pcnf0.write( - PacketConfiguration0::LFLEN.val(8) - + PacketConfiguration0::PLEN::THIRTYTWOZEROS - + PacketConfiguration0::CRCINC::INCLUDE, - ); - - self.registers - .pcnf1 - .write(PacketConfiguration1::MAXLEN.val(nrf5x::constants::RADIO_PAYLOAD_LENGTH as u32)); - } - - fn ieee802154_set_channel_rate(&self) { - self.registers.mode.write(Mode::MODE::IEEE802154_250KBIT); - } - - fn ieee802154_set_channel_freq(&self, channel: RadioChannel) { - self.registers - .frequency - .write(Frequency::FREQUENCY.val(channel as u32)); - } - - fn ieee802154_set_tx_power(&self) { - self.set_tx_power(); - } - - pub fn startup(&self) -> Result<(), ErrorCode> { - self.radio_initialize(); - Ok(()) - } - - // Returns a new pseudo-random number and updates the randomness state. - // - // Uses the [Xorshift](https://en.wikipedia.org/wiki/Xorshift) algorithm to - // produce pseudo-random numbers. Uses the `random_nonce` field to keep - // state. - fn random_nonce(&self) -> u32 { - let mut next_nonce = ::core::num::Wrapping(self.random_nonce.get()); - next_nonce ^= next_nonce << 13; - next_nonce ^= next_nonce >> 17; - next_nonce ^= next_nonce << 5; - self.random_nonce.set(next_nonce.0); - self.random_nonce.get() - } -} - -impl<'p> kernel::hil::radio::RadioConfig for Radio<'p> { - fn initialize( - &self, - _spi_buf: &'static mut [u8], - _reg_write: &'static mut [u8], - _reg_read: &'static mut [u8], - ) -> Result<(), ErrorCode> { - self.radio_initialize(); - Ok(()) - } - - fn set_power_client(&self, _client: &'static dyn PowerClient) { - // - } - - fn reset(&self) -> Result<(), ErrorCode> { - self.radio_on(); - Ok(()) - } - fn start(&self) -> Result<(), ErrorCode> { - let _ = self.reset(); - Ok(()) - } - fn stop(&self) -> Result<(), ErrorCode> { - self.radio_off(); - Ok(()) - } - fn is_on(&self) -> bool { - true - } - fn busy(&self) -> bool { - false - } - - //################################################# - ///These methods are holdovers from when the radio HIL was mostly to an external - ///module over an interface - //################################################# - - //fn set_power_client(&self, client: &'static radio::PowerClient){ - - //} - /// Commit the config calls to hardware, changing the address, - /// PAN ID, TX power, and channel to the specified values, issues - /// a callback to the config client when done. - fn config_commit(&self) { - self.radio_off(); - self.radio_initialize(); - } - - fn set_config_client(&self, _client: &'static dyn radio::ConfigClient) {} - - //################################################# - /// Accessors - //################################################# - - fn get_address(&self) -> u16 { - self.addr.get() - } - - fn get_address_long(&self) -> [u8; 8] { - self.addr_long.get() - } - - /// The 16-bit PAN ID - fn get_pan(&self) -> u16 { - self.pan.get() - } - /// The transmit power, in dBm - fn get_tx_power(&self) -> i8 { - self.tx_power.get() as i8 - } - /// The 802.15.4 channel - fn get_channel(&self) -> u8 { - self.channel.get().get_channel_index() - } - - //################################################# - /// Mutators - //################################################# - - fn set_address(&self, addr: u16) { - self.addr.set(addr); - } - - fn set_address_long(&self, addr: [u8; 8]) { - self.addr_long.set(addr); - } - - fn set_pan(&self, id: u16) { - self.pan.set(id); - } - - fn set_channel(&self, chan: u8) -> Result<(), ErrorCode> { - match RadioChannel::try_from(chan) { - Err(_) => Err(ErrorCode::NOSUPPORT), - Ok(res) => { - self.channel.set(res); - Ok(()) - } - } - } - - fn set_tx_power(&self, tx_power: i8) -> Result<(), ErrorCode> { - // Convert u8 to TxPower - match nrf5x::constants::TxPower::try_from(tx_power as u8) { - // Invalid transmitting power, propogate error - Err(_) => Err(ErrorCode::NOSUPPORT), - // Valid transmitting power, propogate success - Ok(res) => { - self.tx_power.set(res); - Ok(()) - } - } - } -} - -impl<'p> kernel::hil::radio::RadioData for Radio<'p> { - fn set_receive_client(&self, client: &'static dyn radio::RxClient, buffer: &'static mut [u8]) { - self.rx_client.set(client); - self.rx_buf.replace(buffer); - } - - fn set_receive_buffer(&self, buffer: &'static mut [u8]) { - self.rx_buf.replace(buffer); - } - - fn set_transmit_client(&self, client: &'static dyn radio::TxClient) { - self.tx_client.set(client); - } - - fn transmit( - &self, - buf: &'static mut [u8], - frame_len: usize, - ) -> Result<(), (ErrorCode, &'static mut [u8])> { - if self.tx_buf.is_some() || self.transmitting.get() { - return Err((ErrorCode::BUSY, buf)); - } else if radio::PSDU_OFFSET + frame_len >= buf.len() { - // Not enough room for CRC - return Err((ErrorCode::SIZE, buf)); - } - - buf[MIMIC_PSDU_OFFSET as usize] = (frame_len + radio::MFR_SIZE) as u8; - self.tx_buf.replace(buf); - - self.transmitting.set(true); - - self.cca_count.set(0); - self.cca_be.set(IEEE802154_MIN_BE); - - self.radio_off(); - self.radio_initialize(); - Ok(()) - } -} diff --git a/chips/nrf52/src/lib.rs b/chips/nrf52/src/lib.rs index c981a8ddb6..ee860982b1 100644 --- a/chips/nrf52/src/lib.rs +++ b/chips/nrf52/src/lib.rs @@ -1,18 +1,20 @@ -#![feature(const_fn_trait_bound)] +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + #![no_std] #![crate_name = "nrf52"] #![crate_type = "rlib"] pub mod acomp; pub mod adc; +pub mod approtect; pub mod ble_radio; pub mod chip; pub mod clock; pub mod crt1; -pub mod deferred_call_tasks; pub mod ficr; pub mod i2c; -pub mod ieee802154_radio; pub mod nvmc; pub mod power; pub mod ppi; diff --git a/chips/nrf52/src/nvmc.rs b/chips/nrf52/src/nvmc.rs index 09d819c889..dc8b09ae16 100644 --- a/chips/nrf52/src/nvmc.rs +++ b/chips/nrf52/src/nvmc.rs @@ -1,10 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Non-Volatile Memory Controller //! //! Used in order read and write to internal flash. use core::cell::Cell; use core::ops::{Index, IndexMut}; -use kernel::deferred_call::DeferredCall; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; use kernel::hil; use kernel::utilities::cells::OptionalCell; use kernel::utilities::cells::TakeCell; @@ -14,8 +18,6 @@ use kernel::utilities::registers::{register_bitfields, ReadOnly, ReadWrite}; use kernel::utilities::StaticRef; use kernel::ErrorCode; -use crate::deferred_call_tasks::DeferredCallTask; - const NVMC_BASE: StaticRef = unsafe { StaticRef::new(0x4001E400 as *const NvmcRegisters) }; @@ -24,8 +26,12 @@ struct NvmcRegisters { /// Ready flag /// Address 0x400 - 0x404 pub ready: ReadOnly, + _reserved0: [u32; 4], + /// Ready flag + /// Address 0x408 - 0x40C + pub ready_next: ReadOnly, /// Reserved - _reserved1: [u32; 64], + _reserved1: [u32; 59], /// Configuration register /// Address: 0x504 - 0x508 pub config: ReadWrite, @@ -137,11 +143,6 @@ register_bitfields! [u32, ] ]; -/// This mechanism allows us to schedule "interrupts" even if the hardware -/// does not support them. -static DEFERRED_CALL: DeferredCall = - unsafe { DeferredCall::new(DeferredCallTask::Nvmc) }; - const PAGE_SIZE: usize = 4096; /// This is a wrapper around a u8 array that is sized to a single page for the @@ -157,13 +158,11 @@ const PAGE_SIZE: usize = 4096; /// /// let pagebuffer = unsafe { static_init!(NrfPage, NrfPage::default()) }; /// ``` -pub struct NrfPage(pub [u8; PAGE_SIZE as usize]); +pub struct NrfPage(pub [u8; PAGE_SIZE]); impl Default for NrfPage { fn default() -> Self { - Self { - 0: [0; PAGE_SIZE as usize], - } + Self([0; PAGE_SIZE]) } } impl NrfPage { @@ -206,15 +205,17 @@ pub struct Nvmc { client: OptionalCell<&'static dyn hil::flash::Client>, buffer: TakeCell<'static, NrfPage>, state: Cell, + deferred_call: DeferredCall, } impl Nvmc { - pub const fn new() -> Nvmc { - Nvmc { + pub fn new() -> Self { + Self { registers: NVMC_BASE, client: OptionalCell::empty(), buffer: TakeCell::empty(), state: Cell::new(FlashState::Ready), + deferred_call: DeferredCall::new(), } } @@ -249,20 +250,20 @@ impl Nvmc { FlashState::Read => { self.client.map(|client| { self.buffer.take().map(|buffer| { - client.read_complete(buffer, hil::flash::Error::CommandComplete); + client.read_complete(buffer, Ok(())); }); }); } FlashState::Write => { self.client.map(|client| { self.buffer.take().map(|buffer| { - client.write_complete(buffer, hil::flash::Error::CommandComplete); + client.write_complete(buffer, Ok(())); }); }); } FlashState::Erase => { self.client.map(|client| { - client.erase_complete(hil::flash::Error::CommandComplete); + client.erase_complete(Ok(())); }); } _ => {} @@ -304,7 +305,7 @@ impl Nvmc { // Mark the need for an interrupt so we can call the read done // callback. self.state.set(FlashState::Read); - DEFERRED_CALL.set(); + self.deferred_call.set(); Ok(()) } @@ -329,6 +330,7 @@ impl Nvmc { let address = ((page_number * PAGE_SIZE) + i) as u32; let location = unsafe { &*(address as *const VolatileCell) }; location.set(word); + while !self.registers.ready.is_set(Ready::READY) {} } // Make sure that the NVMC is done. The CPU should be blocked while the @@ -341,7 +343,7 @@ impl Nvmc { // Mark the need for an interrupt so we can call the write done // callback. self.state.set(FlashState::Write); - DEFERRED_CALL.set(); + self.deferred_call.set(); Ok(()) } @@ -353,7 +355,7 @@ impl Nvmc { // Mark that we want to trigger a pseudo interrupt so that we can issue // the callback even though the NVMC is completely blocking. self.state.set(FlashState::Erase); - DEFERRED_CALL.set(); + self.deferred_call.set(); Ok(()) } @@ -388,3 +390,13 @@ impl hil::flash::Flash for Nvmc { self.erase_page(page_number) } } + +impl DeferredCallClient for Nvmc { + fn handle_deferred_call(&self) { + self.handle_interrupt(); + } + + fn register(&'static self) { + self.deferred_call.register(self); + } +} diff --git a/chips/nrf52/src/power.rs b/chips/nrf52/src/power.rs index 92abd9074c..50e268bcb1 100644 --- a/chips/nrf52/src/power.rs +++ b/chips/nrf52/src/power.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Power management use kernel::utilities::cells::OptionalCell; diff --git a/chips/nrf52/src/ppi.rs b/chips/nrf52/src/ppi.rs index 89f4c87834..970f9ce393 100644 --- a/chips/nrf52/src/ppi.rs +++ b/chips/nrf52/src/ppi.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Programmable peripheral interconnect, nRF52 //! //! Chapter 20 of the nRF52832 Objective Product Specification v0.6.3: diff --git a/chips/nrf52/src/pwm.rs b/chips/nrf52/src/pwm.rs index 77f7393e40..afb619bb49 100644 --- a/chips/nrf52/src/pwm.rs +++ b/chips/nrf52/src/pwm.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! PWM driver for nRF52. use kernel::hil; @@ -6,7 +10,6 @@ use kernel::utilities::registers::interfaces::Writeable; use kernel::utilities::registers::{register_bitfields, ReadWrite, WriteOnly}; use kernel::utilities::StaticRef; use kernel::ErrorCode; -use nrf5x; #[repr(C)] struct PwmRegisters { @@ -229,7 +232,10 @@ impl Pwm { // Setup the duty cycles unsafe { DUTY_CYCLES[0] = dc_out as u16; - self.registers.seq0.seq_ptr.set(&DUTY_CYCLES as *const u16); + self.registers + .seq0 + .seq_ptr + .set(core::ptr::addr_of!(DUTY_CYCLES) as *const u16); } self.registers.seq0.seq_cnt.write(SEQ_CNT::CNT.val(1)); self.registers diff --git a/chips/nrf52/src/spi.rs b/chips/nrf52/src/spi.rs index 427a746ef9..a83ea17640 100644 --- a/chips/nrf52/src/spi.rs +++ b/chips/nrf52/src/spi.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of SPI for NRF52 using EasyDMA. //! //! This file only implements support for the three SPI master (`SPIM`) @@ -233,24 +237,22 @@ impl Frequency { /// /// A `SPIM` instance wraps a `registers::spim::SPIM` together with /// addition data necessary to implement an asynchronous interface. -pub struct SPIM { +pub struct SPIM<'a> { registers: StaticRef, - client: OptionalCell<&'static dyn hil::spi::SpiMasterClient>, - chip_select: OptionalCell<&'static dyn hil::gpio::Pin>, - initialized: Cell, + client: OptionalCell<&'a dyn hil::spi::SpiMasterClient>, + chip_select: OptionalCell<&'a dyn hil::gpio::Pin>, busy: Cell, tx_buf: TakeCell<'static, [u8]>, rx_buf: TakeCell<'static, [u8]>, transfer_len: Cell, } -impl SPIM { - pub const fn new(instance: usize) -> SPIM { +impl<'a> SPIM<'a> { + pub const fn new(instance: usize) -> SPIM<'a> { SPIM { registers: INSTANCES[instance], client: OptionalCell::empty(), chip_select: OptionalCell::empty(), - initialized: Cell::new(false), busy: Cell::new(false), tx_buf: TakeCell::empty(), rx_buf: TakeCell::empty(), @@ -271,6 +273,9 @@ impl SPIM { self.chip_select.map(|cs| cs.set()); self.registers.events_end.write(EVENT::EVENT::CLEAR); + // When we are no longer active or busy we can disable the + // peripheral. + self.disable(); self.busy.set(false); self.client.map(|client| match self.tx_buf.take() { @@ -314,7 +319,6 @@ impl SPIM { self.registers.psel_mosi.set(mosi); self.registers.psel_miso.set(miso); self.registers.psel_sck.set(sck); - self.enable(); } /// Enables `SPIM` peripheral. @@ -332,16 +336,14 @@ impl SPIM { } } -impl hil::spi::SpiMaster for SPIM { - type ChipSelect = &'static dyn hil::gpio::Pin; +impl<'a> hil::spi::SpiMaster<'a> for SPIM<'a> { + type ChipSelect = &'a dyn hil::gpio::Pin; - fn set_client(&self, client: &'static dyn hil::spi::SpiMasterClient) { + fn set_client(&self, client: &'a dyn hil::spi::SpiMasterClient) { self.client.set(client); } fn init(&self) -> Result<(), ErrorCode> { - self.registers.intenset.write(INTE::END::Enable); - self.initialized.set(true); Ok(()) } @@ -355,7 +357,6 @@ impl hil::spi::SpiMaster for SPIM { rx_buf: Option<&'static mut [u8]>, len: usize, ) -> Result<(), (ErrorCode, &'static mut [u8], Option<&'static mut [u8]>)> { - debug_assert!(self.initialized.get()); debug_assert!(!self.busy.get()); debug_assert!(self.tx_buf.is_none()); debug_assert!(self.rx_buf.is_none()); @@ -391,22 +392,25 @@ impl hil::spi::SpiMaster for SPIM { // Start the transfer self.busy.set(true); + + // Start and enable the SPIM peripheral. The SPIM peripheral is only + // enabled when the busy flag is set. + self.registers.intenset.write(INTE::END::Enable); + self.enable(); + self.registers.tasks_start.write(TASK::TASK::SET); Ok(()) } fn write_byte(&self, _val: u8) -> Result<(), ErrorCode> { - debug_assert!(self.initialized.get()); unimplemented!("SPI: Use `read_write_bytes()` instead."); } fn read_byte(&self) -> Result { - debug_assert!(self.initialized.get()); unimplemented!("SPI: Use `read_write_bytes()` instead."); } fn read_write_byte(&self, _val: u8) -> Result { - debug_assert!(self.initialized.get()); unimplemented!("SPI: Use `read_write_bytes()` instead."); } @@ -422,15 +426,12 @@ impl hil::spi::SpiMaster for SPIM { // Returns the actual rate set fn set_rate(&self, rate: u32) -> Result { - debug_assert!(self.initialized.get()); let f = Frequency::from_spi_rate(rate); self.registers.frequency.set(f as u32); Ok(f.into_spi_rate()) } fn get_rate(&self) -> u32 { - debug_assert!(self.initialized.get()); - // Reset value is a valid frequency (250kbps), so .expect // should be safe here let f = Frequency::from_register(self.registers.frequency.get()).unwrap(); // Unwrap fail = nrf52 unknown spi rate @@ -438,8 +439,6 @@ impl hil::spi::SpiMaster for SPIM { } fn set_polarity(&self, polarity: hil::spi::ClockPolarity) -> Result<(), ErrorCode> { - debug_assert!(self.initialized.get()); - debug_assert!(self.initialized.get()); let new_polarity = match polarity { hil::spi::ClockPolarity::IdleLow => CONFIG::CPOL::ActiveHigh, hil::spi::ClockPolarity::IdleHigh => CONFIG::CPOL::ActiveLow, @@ -449,7 +448,6 @@ impl hil::spi::SpiMaster for SPIM { } fn get_polarity(&self) -> hil::spi::ClockPolarity { - debug_assert!(self.initialized.get()); match self.registers.config.read(CONFIG::CPOL) { 0 => hil::spi::ClockPolarity::IdleLow, 1 => hil::spi::ClockPolarity::IdleHigh, @@ -458,7 +456,6 @@ impl hil::spi::SpiMaster for SPIM { } fn set_phase(&self, phase: hil::spi::ClockPhase) -> Result<(), ErrorCode> { - debug_assert!(self.initialized.get()); let new_phase = match phase { hil::spi::ClockPhase::SampleLeading => CONFIG::CPHA::SampleOnLeadingEdge, hil::spi::ClockPhase::SampleTrailing => CONFIG::CPHA::SampleOnTrailingEdge, @@ -468,7 +465,6 @@ impl hil::spi::SpiMaster for SPIM { } fn get_phase(&self) -> hil::spi::ClockPhase { - debug_assert!(self.initialized.get()); match self.registers.config.read(CONFIG::CPHA) { 0 => hil::spi::ClockPhase::SampleLeading, 1 => hil::spi::ClockPhase::SampleTrailing, diff --git a/chips/nrf52/src/uart.rs b/chips/nrf52/src/uart.rs index aa7efe5b07..a159d86153 100644 --- a/chips/nrf52/src/uart.rs +++ b/chips/nrf52/src/uart.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Universal asynchronous receiver/transmitter with EasyDMA (UARTE) //! //! Author @@ -6,7 +10,6 @@ //! * Author: Niklas Adolfsson //! * Date: March 10 2018 -use core; use core::cell::Cell; use core::cmp::min; use kernel::hil::uart; @@ -21,11 +24,11 @@ const UARTE_MAX_BUFFER_SIZE: u32 = 0xff; static mut BYTE: u8 = 0; -const UARTE_BASE: StaticRef = +pub const UARTE0_BASE: StaticRef = unsafe { StaticRef::new(0x40002000 as *const UarteRegisters) }; #[repr(C)] -struct UarteRegisters { +pub struct UarteRegisters { task_startrx: WriteOnly, task_stoprx: WriteOnly, task_starttx: WriteOnly, @@ -180,9 +183,9 @@ pub struct UARTParams { impl<'a> Uarte<'a> { /// Constructor // This should only be constructed once - pub const fn new() -> Uarte<'a> { + pub const fn new(regs: StaticRef) -> Uarte<'a> { Uarte { - registers: UARTE_BASE, + registers: regs, tx_client: OptionalCell::empty(), tx_buffer: kernel::utilities::cells::TakeCell::empty(), tx_len: Cell::new(0), @@ -388,7 +391,7 @@ impl<'a> Uarte<'a> { self.registers.event_endtx.write(Event::READY::CLEAR); // precaution: copy value into variable with static lifetime BYTE = byte; - self.registers.txd_ptr.set((&BYTE as *const u8) as u32); + self.registers.txd_ptr.set(core::ptr::addr_of!(BYTE) as u32); self.registers.txd_maxcnt.write(Counter::COUNTER.val(1)); self.registers.task_starttx.write(Task::ENABLE::SET); } @@ -475,7 +478,7 @@ impl<'a> uart::Configure for Uarte<'a> { if params.parity != uart::Parity::None { return Err(ErrorCode::NOSUPPORT); } - if params.hw_flow_control != false { + if params.hw_flow_control { return Err(ErrorCode::NOSUPPORT); } diff --git a/chips/nrf52/src/uicr.rs b/chips/nrf52/src/uicr.rs index 3b51717d08..121c4f20f5 100644 --- a/chips/nrf52/src/uicr.rs +++ b/chips/nrf52/src/uicr.rs @@ -1,8 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! User information configuration registers //! //! Minimal implementation to support activation of the reset button on //! nRF52-DK. +use crate::ficr; use enum_primitive::cast::FromPrimitive; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; use kernel::utilities::registers::{register_bitfields, ReadWrite}; @@ -55,6 +60,8 @@ register_bitfields! [u32, PALL OFFSET(0) NUMBITS(8) [ /// Enable ENABLED = 0x00, + /// Disable for later nRF52 variants + HWDISABLE = 0x5a, /// Disable DISABLED = 0xff ] @@ -174,14 +181,39 @@ impl Uicr { } pub fn is_ap_protect_enabled(&self) -> bool { - // Here we compare to DISABLED value because any other value should enable the protection. - !self - .registers - .approtect - .matches_all(ApProtect::PALL::DISABLED) + // We need to understand the variant of this nRF52 chip to correctly + // implement this function. Newer versions use a different value to + // indicate disabled. + let factory_config = ficr::Ficr::new(); + let disabled_val = if factory_config.has_updated_approtect_logic() { + ApProtect::PALL::HWDISABLE + } else { + ApProtect::PALL::DISABLED + }; + + // Here we compare to the correct DISABLED value because any other value + // should enable the protection. + !self.registers.approtect.matches_all(disabled_val) } pub fn set_ap_protect(&self) { self.registers.approtect.write(ApProtect::PALL::ENABLED); } + + /// Disable the access port protection in the UICR register. This is stored + /// in flash and is persistent. This behavior can also be accomplished + /// outside of tock by running `nrfjprog --recover`. + pub fn disable_ap_protect(&self) { + // We need to understand the variant of this nRF52 chip to correctly + // implement this function. + let factory_config = ficr::Ficr::new(); + if factory_config.has_updated_approtect_logic() { + // Newer revisions of the chip require setting the APPROTECT + // register to `HwDisable`. + self.registers.approtect.write(ApProtect::PALL::HWDISABLE); + } else { + // All other revisions just use normal disable. + self.registers.approtect.write(ApProtect::PALL::DISABLED); + } + } } diff --git a/chips/nrf52/src/usbd.rs b/chips/nrf52/src/usbd.rs index e6d9fe239f..419662506c 100644 --- a/chips/nrf52/src/usbd.rs +++ b/chips/nrf52/src/usbd.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Universal Serial Bus Device with EasyDMA (USBD) use core::cell::Cell; @@ -19,31 +23,31 @@ use crate::power; // replaced with better error handling. macro_rules! debug_events { [ $( $arg:expr ),+ ] => { - {} // debug!($( $arg ),+) + {} // kernel::debug!($( $arg ),+) }; } macro_rules! debug_tasks { [ $( $arg:expr ),+ ] => { - {} // debug!($( $arg ),+) + {} // kernel::debug!($( $arg ),+) }; } macro_rules! debug_packets { [ $( $arg:expr ),+ ] => { - {} // debug!($( $arg ),+) + {} // kernel::debug!($( $arg ),+) }; } macro_rules! debug_info { [ $( $arg:expr ),+ ] => { - {} // debug!($( $arg ),+) + {} // kernel::debug!($( $arg ),+) }; } macro_rules! internal_warn { [ $( $arg:expr ),+ ] => { - {} // debug!($( $arg ),+) + {} // kernel::debug!($( $arg ),+) }; } @@ -322,7 +326,7 @@ mod detail { impl<'a> EndpointRegisters<'a> { pub fn set_buffer(&self, slice: &'a [VolatileCell]) { - self.ptr.set(slice.as_ptr() as *const u8); + self.ptr.set(slice.as_ptr().cast::()); self.maxcnt.write(Count::MAXCNT.val(slice.len() as u32)); } } @@ -603,7 +607,9 @@ register_bitfields! [u32, REVA = 0, REVB = 1, REVC = 2, - REVD = 3 + REVD = 3, + REVE = 4, + REVF = 5, ] ] ]; @@ -739,6 +745,15 @@ impl<'a> Usbd<'a> { self.power.set(power); } + // ERRATA + // + // There are known issues with nRF52840 USB hardware, and we check if + // specific errata apply given different versions of the chip. + // + // Reference + // https://github.com/NordicSemiconductor/nrfx/blob/master/mdk/nrf52_erratas.h + // for how the different errata apply. + fn has_errata_166(&self) -> bool { true } @@ -754,7 +769,9 @@ impl<'a> Usbd<'a> { && match CHIPINFO_BASE.chip_revision.read_as_enum(ChipRevision::REV) { Some(ChipRevision::REV::Value::REVB) | Some(ChipRevision::REV::Value::REVC) - | Some(ChipRevision::REV::Value::REVD) => true, + | Some(ChipRevision::REV::Value::REVD) + | Some(ChipRevision::REV::Value::REVE) + | Some(ChipRevision::REV::Value::REVF) => true, Some(ChipRevision::REV::Value::REVA) | None => false, } } @@ -912,7 +929,10 @@ impl<'a> Usbd<'a> { chip_revision.get() ); } - Some(ChipRevision::REV::Value::REVC) | Some(ChipRevision::REV::Value::REVD) => { + Some(ChipRevision::REV::Value::REVC) + | Some(ChipRevision::REV::Value::REVD) + | Some(ChipRevision::REV::Value::REVE) + | Some(ChipRevision::REV::Value::REVF) => { debug_info!( "Your chip is NRF52840 revision {}. The USB stack was tested on your chip :)", chip_revision.get() diff --git a/chips/nrf52832/Cargo.toml b/chips/nrf52832/Cargo.toml index 84efe4de00..1c1bb483dd 100644 --- a/chips/nrf52832/Cargo.toml +++ b/chips/nrf52832/Cargo.toml @@ -1,10 +1,17 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "nrf52832" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] cortexm4 = { path = "../../arch/cortex-m4" } kernel = { path = "../../kernel" } nrf52 = { path = "../nrf52" } + +[lints] +workspace = true diff --git a/chips/nrf52832/src/gpio.rs b/chips/nrf52832/src/gpio.rs index 0070ff6ef8..ffe6bc9b7c 100644 --- a/chips/nrf52832/src/gpio.rs +++ b/chips/nrf52832/src/gpio.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + pub use nrf52::gpio::{GPIOPin, Pin, Port}; pub const NUM_PINS: usize = 32; diff --git a/chips/nrf52832/src/interrupt_service.rs b/chips/nrf52832/src/interrupt_service.rs index d90e772eb3..a3b4156e83 100644 --- a/chips/nrf52832/src/interrupt_service.rs +++ b/chips/nrf52832/src/interrupt_service.rs @@ -1,4 +1,7 @@ -use crate::deferred_call_tasks::DeferredCallTask; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use nrf52::chip::Nrf52DefaultPeripherals; /// This struct, when initialized, instantiates all peripheral drivers for the nrf52840. @@ -17,13 +20,11 @@ impl<'a> Nrf52832DefaultPeripherals<'a> { } } // Necessary for setting up circular dependencies - pub fn init(&'a self) { + pub fn init(&'static self) { self.nrf52.init(); } } -impl<'a> kernel::platform::chip::InterruptService - for Nrf52832DefaultPeripherals<'a> -{ +impl<'a> kernel::platform::chip::InterruptService for Nrf52832DefaultPeripherals<'a> { unsafe fn service_interrupt(&self, interrupt: u32) -> bool { match interrupt { nrf52::peripheral_interrupts::GPIOTE => self.gpio_port.handle_interrupt(), @@ -31,7 +32,4 @@ impl<'a> kernel::platform::chip::InterruptService } true } - unsafe fn service_deferred_call(&self, task: DeferredCallTask) -> bool { - self.nrf52.service_deferred_call(task) - } } diff --git a/chips/nrf52832/src/lib.rs b/chips/nrf52832/src/lib.rs index 19bf4fdb2b..864ea00b29 100644 --- a/chips/nrf52832/src/lib.rs +++ b/chips/nrf52832/src/lib.rs @@ -1,9 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + #![no_std] pub use nrf52::{ - acomp, adc, aes, ble_radio, chip, clock, constants, crt1, deferred_call_tasks, ficr, i2c, - ieee802154_radio, init, nvmc, peripheral_interrupts as base_interrupts, pinmux, power, ppi, - pwm, rtc, spi, temperature, timer, trng, uart, uicr, + acomp, adc, aes, ble_radio, chip, clock, constants, crt1, ficr, i2c, init, nvmc, + peripheral_interrupts as base_interrupts, pinmux, power, ppi, pwm, rtc, spi, temperature, + timer, trng, uart, uicr, }; pub mod gpio; pub mod interrupt_service; diff --git a/chips/nrf52833/Cargo.toml b/chips/nrf52833/Cargo.toml index 792cbd930b..b5260de8c6 100644 --- a/chips/nrf52833/Cargo.toml +++ b/chips/nrf52833/Cargo.toml @@ -1,10 +1,17 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "nrf52833" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] cortexm4 = { path = "../../arch/cortex-m4" } kernel = { path = "../../kernel" } nrf52 = { path = "../nrf52" } + +[lints] +workspace = true diff --git a/chips/nrf52833/src/gpio.rs b/chips/nrf52833/src/gpio.rs index f62fe4e169..980bd187d4 100644 --- a/chips/nrf52833/src/gpio.rs +++ b/chips/nrf52833/src/gpio.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + pub use nrf52::gpio::{GPIOPin, Pin, Port}; pub const NUM_PINS: usize = 48; diff --git a/chips/nrf52833/src/interrupt_service.rs b/chips/nrf52833/src/interrupt_service.rs index 3b92c295b7..71cbc41c63 100644 --- a/chips/nrf52833/src/interrupt_service.rs +++ b/chips/nrf52833/src/interrupt_service.rs @@ -1,4 +1,7 @@ -use crate::deferred_call_tasks::DeferredCallTask; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use nrf52::chip::Nrf52DefaultPeripherals; /// This struct, when initialized, instantiates all peripheral drivers for the nrf52840. @@ -17,13 +20,11 @@ impl<'a> Nrf52833DefaultPeripherals<'a> { } } // Necessary for setting up circular dependencies - pub fn init(&'a self) { + pub fn init(&'static self) { self.nrf52.init(); } } -impl<'a> kernel::platform::chip::InterruptService - for Nrf52833DefaultPeripherals<'a> -{ +impl<'a> kernel::platform::chip::InterruptService for Nrf52833DefaultPeripherals<'a> { unsafe fn service_interrupt(&self, interrupt: u32) -> bool { match interrupt { nrf52::peripheral_interrupts::GPIOTE => self.gpio_port.handle_interrupt(), @@ -31,7 +32,4 @@ impl<'a> kernel::platform::chip::InterruptService } true } - unsafe fn service_deferred_call(&self, task: DeferredCallTask) -> bool { - self.nrf52.service_deferred_call(task) - } } diff --git a/chips/nrf52833/src/lib.rs b/chips/nrf52833/src/lib.rs index 19bf4fdb2b..20d9d8a706 100644 --- a/chips/nrf52833/src/lib.rs +++ b/chips/nrf52833/src/lib.rs @@ -1,9 +1,15 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + #![no_std] +// FIXME: Move ieee802154_radio to an nrf528xx crate so this can access it. + pub use nrf52::{ - acomp, adc, aes, ble_radio, chip, clock, constants, crt1, deferred_call_tasks, ficr, i2c, - ieee802154_radio, init, nvmc, peripheral_interrupts as base_interrupts, pinmux, power, ppi, - pwm, rtc, spi, temperature, timer, trng, uart, uicr, + acomp, adc, aes, ble_radio, chip, clock, constants, crt1, ficr, i2c, init, nvmc, + peripheral_interrupts as base_interrupts, pinmux, power, ppi, pwm, rtc, spi, temperature, + timer, trng, uart, uicr, }; pub mod gpio; pub mod interrupt_service; diff --git a/chips/nrf52840/Cargo.toml b/chips/nrf52840/Cargo.toml index 4e725afaea..84e4f8cde3 100644 --- a/chips/nrf52840/Cargo.toml +++ b/chips/nrf52840/Cargo.toml @@ -1,10 +1,17 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "nrf52840" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] cortexm4 = { path = "../../arch/cortex-m4" } kernel = { path = "../../kernel" } nrf52 = { path = "../nrf52" } + +[lints] +workspace = true diff --git a/chips/nrf52840/src/gpio.rs b/chips/nrf52840/src/gpio.rs index 8b7acecd28..d8b9b70409 100644 --- a/chips/nrf52840/src/gpio.rs +++ b/chips/nrf52840/src/gpio.rs @@ -1,8 +1,12 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + pub use nrf52::gpio::{GPIOPin, Pin, Port}; pub const NUM_PINS: usize = 48; -pub fn nrf52840_gpio_create<'a>() -> Port<'a, NUM_PINS> { +pub const fn nrf52840_gpio_create<'a>() -> Port<'a, NUM_PINS> { Port::new([ GPIOPin::new(Pin::P0_00), GPIOPin::new(Pin::P0_01), diff --git a/chips/nrf52840/src/ieee802154_radio.rs b/chips/nrf52840/src/ieee802154_radio.rs new file mode 100644 index 0000000000..2b2c6d4e04 --- /dev/null +++ b/chips/nrf52840/src/ieee802154_radio.rs @@ -0,0 +1,1418 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! IEEE 802.15.4 radio driver for nRF52 +//! +//! This driver implements a subset of 802.15.4 sending and receiving for the +//! nRF52840 chip per the nRF52840_PS_v1.0 spec. Upon calling the initialization +//! function, the chip is powered on and configured to the fields of the Radio +//! struct. This driver maintains a state machine between receiving, +//! transmitting, and sending acknowledgements. Because the nRF52840 15.4 radio +//! chip does not possess hardware support for ACK, this driver implements +//! software support for sending ACK when a received packet requests to be +//! acknowledged. The driver currently lacks support to listen for requested ACK +//! on packets the radio has sent. As of 8/14/23, the driver is able to send and +//! receive 15.4 packets as used in the basic 15.4 libtock-c apps. +//! +//! ## Driver State Machine +//! +//! To aid in future implementations, this describes a simplified and concise +//! version of the nrf52840 radio state machine specification and the state +//! machine this driver separately maintains. +//! +//! To interact with the radio, tasks are issued to the radio which in turn +//! trigger interrupt events. To receive, the radio must first "ramp up". The +//! RXRU state is entered by issuing a RXEN task. Once the radio has ramped up +//! successfully, it is now in the RXIDLE state and triggers a READY interrupt +//! event. To optimize the radio's operation, this driver enables hardware +//! shortcuts such that upon receiving the READY event, the radio chip +//! immediately triggers a START task. The START task notifies the radio to begin +//! officially "listening for packets" (RX state). Upon completing receiving the +//! packet, the radio issues an END event. The driver then determines if the +//! received packet has requested to be acknowledged (bit flag) and sends an ACK +//! accordingly. Finally, the received packet buffer and accompanying fields are +//! passed to the registered radio client. This marks the end of a receive cycle +//! and a new READY event is issued to once again begin listening for packets. +//! +//! When a registered radio client wishes to send a packet. The transmit(...) +//! method is called. To transmit a packet, the radio must first ramp up for +//! receiving and then perform a clear channel assessment by listening for a +//! specified period of time to determine if there is "traffic". If traffic is +//! detected, the radio sets an alarm and waits to perform another CCA after this +//! backoff. If the channel is determined to be clear, the radio then begins a TX +//! ramp up, enters a TX state and then sends the packet. To progress through +//! these states, hardware shortcuts are once again enabled in this driver. The +//! driver first issues a DISABLE task. A hardware shortcut is enabled so that +//! upon receipt of the disable task, the radio automatically issues a RXEN task +//! to enter the RXRU state. Additionally, a shortcut is enabled such that when +//! the RXREADY event is received, the radio automatically issues a CCA_START +//! task. Finally, a shortcut is also enabled such that upon receiving a CCAIDLE +//! event the radio automatically issues a TXEN event to ramp up the radio. The +//! driver then handles receiving the READY interrupt event and triggers the +//! START task to begin sending the packet. Upon completing the sending of the +//! packet, the radio issues an END event, to which the driver then returns the +//! radio to a receiving mode as described above. (For a more complete +//! explanation of the radio's operation, refer to nRF52840_PS_v1.0) +//! +//! This radio state machine provides nine possible states the radio can exist +//! in. For ease of implementation and clarity, this driver also maintains a +//! simplified state machine. These states consist of the radio being off (OFF), +//! receiving (RX), transmitting (TX), or acknowledging (ACK). + +// Author: Tyler Potyondy +// 8/21/23 + +use crate::timer::TimerAlarm; +use core::cell::Cell; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; +use kernel::hil::radio::{self, PowerClient, RadioChannel, RadioConfig, RadioData}; +use kernel::hil::time::{Alarm, AlarmClient, Time}; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::utilities::registers::interfaces::{Readable, Writeable}; +use kernel::utilities::registers::{register_bitfields, ReadOnly, ReadWrite, WriteOnly}; +use kernel::utilities::StaticRef; +use kernel::ErrorCode; + +use nrf52::constants::TxPower; + +const RADIO_BASE: StaticRef = + unsafe { StaticRef::new(0x40001000 as *const RadioRegisters) }; + +const ACK_FLAG: u8 = 0b00100000; + +pub const IEEE802154_PAYLOAD_LENGTH: usize = 255; +pub const IEEE802154_BACKOFF_PERIOD: usize = 320; //microseconds = 20 symbols +pub const IEEE802154_ACK_TIME: usize = 512; //microseconds = 32 symbols +pub const IEEE802154_MAX_POLLING_ATTEMPTS: u8 = 4; +pub const IEEE802154_MIN_BE: u8 = 3; +pub const IEEE802154_MAX_BE: u8 = 5; + +// ACK Requires MHR and MFR fields. More explicitly this is composed of: +// | Frame Control (2 bytes) | Sequence Number (1 byte) | MFR (2 bytes) |. +// In total the ACK frame is 5 bytes long + 2 PSDU bytes (7 bytes total). +const SEQ_NUM_LEN: usize = 1; +pub const ACK_BUF_SIZE: usize = + radio::SPI_HEADER_SIZE + radio::PHR_SIZE + radio::MHR_FC_SIZE + SEQ_NUM_LEN + radio::MFR_SIZE; + +/// Where the 15.4 packet from the radio is stored in the buffer. The HIL +/// reserves one byte at the beginning of the buffer for use by the +/// capsule/hardware. We have no use for this, but the upper layers expect it so +/// we skip over it. +// We can't just drop the byte from the buffer because then it would be lost +// forever when we tried to return the frame buffer. +const BUF_PREFIX_SIZE: u32 = 1; + +#[repr(C)] +struct RadioRegisters { + /// Enable Radio in TX mode + /// - Address: 0x000 - 0x004 + task_txen: WriteOnly, + /// Enable Radio in RX mode + /// - Address: 0x004 - 0x008 + task_rxen: WriteOnly, + /// Start Radio + /// - Address: 0x008 - 0x00c + task_start: WriteOnly, + /// Stop Radio + /// - Address: 0x00c - 0x010 + task_stop: WriteOnly, + /// Disable Radio + /// - Address: 0x010 - 0x014 + task_disable: WriteOnly, + /// Start the RSSI and take one single sample of the receive signal strength + /// - Address: 0x014- 0x018 + task_rssistart: WriteOnly, + /// Stop the RSSI measurement + /// - Address: 0x018 - 0x01c + task_rssistop: WriteOnly, + /// Start the bit counter + /// - Address: 0x01c - 0x020 + task_bcstart: WriteOnly, + /// Stop the bit counter + /// - Address: 0x020 - 0x024 + task_bcstop: WriteOnly, + /// Reserved + _reserved1: [u32; 2], + /// Stop the bit counter + /// - Address: 0x02c - 0x030 + task_ccastart: WriteOnly, + /// Stop the bit counter + /// - Address: 0x030 - 0x034 + task_ccastop: WriteOnly, + /// Reserved + _reserved2: [u32; 51], + /// Radio has ramped up and is ready to be started + /// - Address: 0x100 - 0x104 + event_ready: ReadWrite, + /// Address sent or received + /// - Address: 0x104 - 0x108 + event_address: ReadWrite, + /// Packet payload sent or received + /// - Address: 0x108 - 0x10c + event_payload: ReadWrite, + /// Packet sent or received + /// - Address: 0x10c - 0x110 + event_end: ReadWrite, + /// Radio has been disabled + /// - Address: 0x110 - 0x114 + event_disabled: ReadWrite, + /// A device address match occurred on the last received packet + /// - Address: 0x114 - 0x118 + event_devmatch: ReadWrite, + /// No device address match occurred on the last received packet + /// - Address: 0x118 - 0x11c + event_devmiss: ReadWrite, + /// Sampling of receive signal strength complete + /// - Address: 0x11c - 0x120 + event_rssiend: ReadWrite, + /// Reserved + _reserved3: [u32; 2], + /// Bit counter reached bit count value + /// - Address: 0x128 - 0x12c + event_bcmatch: ReadWrite, + /// Reserved + _reserved4: [u32; 1], + /// Packet received with CRC ok + /// - Address: 0x130 - 0x134 + event_crcok: ReadWrite, + /// Packet received with CRC error + /// - Address: 0x134 - 0x138 + crcerror: ReadWrite, + /// IEEE 802.15.4 length field received + /// - Address: 0x138 - 0x13c + event_framestart: ReadWrite, + /// Reserved + _reserved5: [u32; 2], + /// Wireless medium in idle - clear to send + /// - Address: 0x144-0x148 + event_ccaidle: ReadWrite, + /// Wireless medium busy - do not send + /// - Address: 0x148-0x14c + event_ccabusy: ReadWrite, + /// Reserved + _reserved6: [u32; 45], + /// Shortcut register + /// - Address: 0x200 - 0x204 + shorts: ReadWrite, + /// Reserved + _reserved7: [u32; 64], + /// Enable interrupt + /// - Address: 0x304 - 0x308 + intenset: ReadWrite, + /// Disable interrupt + /// - Address: 0x308 - 0x30c + intenclr: ReadWrite, + /// Reserved + _reserved8: [u32; 61], + /// CRC status + /// - Address: 0x400 - 0x404 + crcstatus: ReadOnly, + /// Reserved + _reserved9: [u32; 1], + /// Received address + /// - Address: 0x408 - 0x40c + rxmatch: ReadOnly, + /// CRC field of previously received packet + /// - Address: 0x40c - 0x410 + rxcrc: ReadOnly, + /// Device address match index + /// - Address: 0x410 - 0x414 + dai: ReadOnly, + /// Reserved + _reserved10: [u32; 60], + /// Packet pointer + /// - Address: 0x504 - 0x508 + packetptr: ReadWrite, + /// Frequency + /// - Address: 0x508 - 0x50c + frequency: ReadWrite, + /// Output power + /// - Address: 0x50c - 0x510 + txpower: ReadWrite, + /// Data rate and modulation + /// - Address: 0x510 - 0x514 + mode: ReadWrite, + /// Packet configuration register 0 + /// - Address 0x514 - 0x518 + pcnf0: ReadWrite, + /// Packet configuration register 1 + /// - Address: 0x518 - 0x51c + pcnf1: ReadWrite, + /// Base address 0 + /// - Address: 0x51c - 0x520 + base0: ReadWrite, + /// Base address 1 + /// - Address: 0x520 - 0x524 + base1: ReadWrite, + /// Prefix bytes for logical addresses 0-3 + /// - Address: 0x524 - 0x528 + prefix0: ReadWrite, + /// Prefix bytes for logical addresses 4-7 + /// - Address: 0x528 - 0x52c + prefix1: ReadWrite, + /// Transmit address select + /// - Address: 0x52c - 0x530 + txaddress: ReadWrite, + /// Receive address select + /// - Address: 0x530 - 0x534 + rxaddresses: ReadWrite, + /// CRC configuration + /// - Address: 0x534 - 0x538 + crccnf: ReadWrite, + /// CRC polynomial + /// - Address: 0x538 - 0x53c + crcpoly: ReadWrite, + /// CRC initial value + /// - Address: 0x53c - 0x540 + crcinit: ReadWrite, + /// Reserved + _reserved11: [u32; 1], + /// Interframe spacing in microseconds + /// - Address: 0x544 - 0x548 + tifs: ReadWrite, + /// RSSI sample + /// - Address: 0x548 - 0x54c + rssisample: ReadWrite, + /// Reserved + _reserved12: [u32; 1], + /// Current radio state + /// - Address: 0x550 - 0x554 + state: ReadOnly, + /// Data whitening initial value + /// - Address: 0x554 - 0x558 + datawhiteiv: ReadWrite, + /// Reserved + _reserved13: [u32; 2], + /// Bit counter compare + /// - Address: 0x560 - 0x564 + bcc: ReadWrite, + /// Reserved + _reserved14: [u32; 39], + /// Device address base segments + /// - Address: 0x600 - 0x620 + dab: [ReadWrite; 8], + /// Device address prefix + /// - Address: 0x620 - 0x640 + dap: [ReadWrite; 8], + /// Device address match configuration + /// - Address: 0x640 - 0x644 + dacnf: ReadWrite, + /// MAC header Search Pattern Configuration + /// - Address: 0x644 - 0x648 + mhrmatchconf: ReadWrite, + /// MAC Header Search Pattern Mask + /// - Address: 0x648 - 0x64C + mhrmatchmas: ReadWrite, + /// Reserved + _reserved15: [u32; 1], + /// Radio mode configuration register + /// - Address: 0x650 - 0x654 + modecnf0: ReadWrite, + /// Reserved + _reserved16: [u32; 6], + /// Clear Channel Assesment (CCA) control register + /// - Address: 0x66C - 0x670 + ccactrl: ReadWrite, + /// Reserved + _reserved17: [u32; 611], + /// Peripheral power control + /// - Address: 0xFFC - 0x1000 + power: ReadWrite, +} + +register_bitfields! [u32, + /// Task register + Task [ + /// Enable task + ENABLE OFFSET(0) NUMBITS(1) + ], + /// Event register + Event [ + /// Ready event + READY OFFSET(0) NUMBITS(1) + ], + /// Shortcut register + Shortcut [ + /// Shortcut between READY event and START task + READY_START OFFSET(0) NUMBITS(1), + /// Shortcut between END event and DISABLE task + END_DISABLE OFFSET(1) NUMBITS(1), + /// Shortcut between DISABLED event and TXEN task + DISABLED_TXEN OFFSET(2) NUMBITS(1), + /// Shortcut between DISABLED event and RXEN task + DISABLED_RXEN OFFSET(3) NUMBITS(1), + /// Shortcut between ADDRESS event and RSSISTART task + ADDRESS_RSSISTART OFFSET(4) NUMBITS(1), + /// Shortcut between END event and START task + END_START OFFSET(5) NUMBITS(1), + /// Shortcut between ADDRESS event and BCSTART task + ADDRESS_BCSTART OFFSET(6) NUMBITS(1), + /// Shortcut between DISABLED event and RSSISTOP task + DISABLED_RSSISTOP OFFSET(8) NUMBITS(1), + /// Shortcut between CCAIDLE_TXEN + CCAIDLE_TXEN OFFSET(12) NUMBITS(1), + /// Shortcut between RXREADY_CCASTART + RXREADY_CCASTART OFFSET(11) NUMBITS(1), + /// Shortcut between TXREADY event and START task + TXREADY_START OFFSET(19) NUMBITS(1), + + ], + /// Interrupt register + Interrupt [ + /// READY event + READY OFFSET(0) NUMBITS(1), + /// ADDRESS event + ADDRESS OFFSET(1) NUMBITS(1), + /// PAYLOAD event + PAYLOAD OFFSET(2) NUMBITS(1), + /// END event + END OFFSET(3) NUMBITS(1), + /// DISABLED event + DISABLED OFFSET(4) NUMBITS(1), + /// DEVMATCH event + DEVMATCH OFFSET(5) NUMBITS(1), + /// DEVMISS event + DEVMISS OFFSET(6) NUMBITS(1), + /// RSSIEND event + RSSIEND OFFSET(7) NUMBITS(1), + /// BCMATCH event + BCMATCH OFFSET(10) NUMBITS(1), + /// CRCOK event + CRCOK OFFSET(12) NUMBITS(1), + /// CRCERROR event + CRCERROR OFFSET(13) NUMBITS(1), + /// CCAIDLE event + FRAMESTART OFFSET(14) NUMBITS(1), + /// CCAIDLE event + CCAIDLE OFFSET(17) NUMBITS(1), + /// CCABUSY event + CCABUSY OFFSET(18) NUMBITS(1), + /// TXREADY event + TXREADY OFFSET(21) NUMBITS(1), + /// RXREADY event + RXREADY OFFSET(22) NUMBITS(1), + ], + /// Receive match register + ReceiveMatch [ + /// Logical address of which previous packet was received + MATCH OFFSET(0) NUMBITS(3) + ], + /// Received CRC register + ReceiveCrc [ + /// CRC field of previously received packet + CRC OFFSET(0) NUMBITS(24) + ], + /// Device address match index register + DeviceAddressIndex [ + /// Device address match index + /// Index (n) of device address, see DAB\[n\] and DAP\[n\], that got an + /// address match + INDEX OFFSET(0) NUMBITS(3) + ], + /// Packet pointer register + PacketPointer [ + /// Packet address to be used for the next transmission or reception. When transmitting, the packet pointed to by this + /// address will be transmitted and when receiving, the received packet will be written to this address. This address is a byte + /// aligned ram address. + POINTER OFFSET(0) NUMBITS(32) + ], + /// Frequency register + Frequency [ + /// Radio channel frequency + /// Frequency = 2400 + FREQUENCY (MHz) + FREQUENCY OFFSET(0) NUMBITS(7) [], + /// Channel map selection. + /// Channel map between 2400 MHZ .. 2500 MHZ + MAP OFFSET(8) NUMBITS(1) [ + DEFAULT = 0, + LOW = 1 + ] + ], + /// Transmitting power register + TransmitPower [ + /// Radio output power + POWER OFFSET(0) NUMBITS(8) [ + POS4DBM = 4, + POS3DBM = 3, + ODBM = 0, + NEG4DBM = 0xfc, + NEG8DBM = 0xf8, + NEG12DBM = 0xf4, + NEG16DBM = 0xf0, + NEG20DBM = 0xec, + NEG40DBM = 0xd8 + ] + ], + /// Data rate and modulation register + Mode [ + /// Radio data rate and modulation setting. + /// The radio supports Frequency-shift Keying (FSK) modulation + MODE OFFSET(0) NUMBITS(4) [ + NRF_1MBIT = 0, + NRF_2MBIT = 1, + NRF_250KBIT = 2, + BLE_1MBIT = 3, + BLE_2MBIT = 4, + BLE_LR125KBIT = 5, + BLE_LR500KBIT = 6, + IEEE802154_250KBIT = 15 + ] + ], + /// Packet configuration register 0 + PacketConfiguration0 [ + /// Length on air of LENGTH field in number of bits + LFLEN OFFSET(0) NUMBITS(4) [], + /// Length on air of S0 field in number of bytes + S0LEN OFFSET(8) NUMBITS(1) [], + /// Length on air of S1 field in number of bits. + S1LEN OFFSET(16) NUMBITS(4) [], + /// Include or exclude S1 field in RAM + S1INCL OFFSET(20) NUMBITS(1) [ + AUTOMATIC = 0, + INCLUDE = 1 + ], + /// Length of preamble on air. Decision point: TASKS_START task + PLEN OFFSET(24) NUMBITS(2) [ + EIGHT = 0, + SIXTEEN = 1, + THIRTYTWOZEROS = 2, + LONGRANGE = 3 + ], + CRCINC OFFSET(26) NUMBITS(1) [ + EXCLUDE = 0, + INCLUDE = 1 + ] + ], + /// Packet configuration register 1 + PacketConfiguration1 [ + /// Maximum length of packet payload + MAXLEN OFFSET(0) NUMBITS(8) [], + /// Static length in number of bytes + STATLEN OFFSET(8) NUMBITS(8) [], + /// Base address length in number of bytes + BALEN OFFSET(16) NUMBITS(3) [], + /// On air endianness + ENDIAN OFFSET(24) NUMBITS(1) [ + LITTLE = 0, + BIG = 1 + ], + /// Enable or disable packet whitening + WHITEEN OFFSET(25) NUMBITS(1) [ + DISABLED = 0, + ENABLED = 1 + ] + ], + /// Radio base address register + BaseAddress [ + /// BASE0 or BASE1 + BASE OFFSET(0) NUMBITS(32) + ], + /// Radio prefix0 registers + Prefix0 [ + /// Address prefix 0 + AP0 OFFSET(0) NUMBITS(8), + /// Address prefix 1 + AP1 OFFSET(8) NUMBITS(8), + /// Address prefix 2 + AP2 OFFSET(16) NUMBITS(8), + /// Address prefix 3 + AP3 OFFSET(24) NUMBITS(8) + ], + /// Radio prefix0 registers + Prefix1 [ + /// Address prefix 4 + AP4 OFFSET(0) NUMBITS(8), + /// Address prefix 5 + AP5 OFFSET(8) NUMBITS(8), + /// Address prefix 6 + AP6 OFFSET(16) NUMBITS(8), + /// Address prefix 7 + AP7 OFFSET(24) NUMBITS(8) + ], + /// Transmit address register + TransmitAddress [ + /// Logical address to be used when transmitting a packet + ADDRESS OFFSET(0) NUMBITS(3) + ], + /// Receive addresses register + ReceiveAddresses [ + /// Enable or disable reception on logical address 0-7 + ADDRESS OFFSET(0) NUMBITS(8) + ], + /// CRC configuration register + CrcConfiguration [ + /// CRC length in bytes + LEN OFFSET(0) NUMBITS(2) [ + DISABLED = 0, + ONE = 1, + TWO = 2, + THREE = 3 + ], + /// Include or exclude packet field from CRC calculation + SKIPADDR OFFSET(8) NUMBITS(2) [ + INCLUDE = 0, + EXCLUDE = 1, + IEEE802154 = 2 + ] + ], + /// CRC polynomial register + CrcPolynomial [ + /// CRC polynomial + CRCPOLY OFFSET(0) NUMBITS(24) + ], + /// CRC initial value register + CrcInitialValue [ + /// Initial value for CRC calculation + CRCINIT OFFSET(0) NUMBITS(24) + ], + /// Inter Frame Spacing in us register + InterFrameSpacing [ + /// Inter Frame Spacing in us + /// Inter frame space is the time interval between two consecutive packets. It is defined as the time, in micro seconds, from the + /// end of the last bit of the previous packet to the start of the first bit of the subsequent packet + TIFS OFFSET(0) NUMBITS(8) + ], + /// RSSI sample register + RssiSample [ + /// RSSI sample result + RSSISAMPLE OFFSET(0) NUMBITS(7) + ], + /// Radio state register + State [ + /// Current radio state + STATE OFFSET(0) NUMBITS(4) [ + DISABLED = 0, + RXRU = 1, + RXIDLE = 2, + RX = 3, + RXDISABLED = 4, + TXRU = 9, + TXIDLE = 10, + TX = 11, + TXDISABLED = 12 + ] + ], + /// Data whitening initial value register + DataWhiteIv [ + /// Data whitening initial value. Bit 6 is hard-wired to '1', writing '0' + /// to it has no effect, and it will always be read back and used by the device as '1' + DATEWHITEIV OFFSET(0) NUMBITS(7) + ], + /// Bit counter compare register + BitCounterCompare [ + /// Bit counter compare + BCC OFFSET(0) NUMBITS(32) + ], + /// Device address base register + DeviceAddressBase [ + /// Device address base 0-7 + DAB OFFSET(0) NUMBITS(32) + ], + /// Device address prefix register + DeviceAddressPrefix [ + /// Device address prefix 0-7 + DAP OFFSET(0) NUMBITS(32) + ], + /// Device address match configuration register + DeviceAddressMatch [ + /// Enable or disable device address matching on 0-7 + ENA OFFSET(0) NUMBITS(8), + /// TxAdd for device address 0-7 + TXADD OFFSET(8) NUMBITS(8) + ], + MACHeaderSearch [ + CONFIG OFFSET(0) NUMBITS(32) + ], + MACHeaderMask [ + PATTERN OFFSET(0) NUMBITS(32) + ], + CCAControl [ + CCAMODE OFFSET(0) NUMBITS(3) [ + ED_MODE = 0, + CARRIER_MODE = 1, + CARRIER_AND_ED_MODE = 2, + CARRIER_OR_ED_MODE = 3, + ED_MODE_TEST_1 = 4 + ], + CCAEDTHRESH OFFSET(8) NUMBITS(8) [], + CCACORRTHRESH OFFSET(16) NUMBITS(8) [], + CCACORRCNT OFFSET(24) NUMBITS(8) [] + ], + /// Radio mode configuration register + RadioModeConfig [ + /// Radio ramp-up time + RU OFFSET(0) NUMBITS(1) [ + DEFAULT = 0, + FAST = 1 + ], + /// Default TX value + /// Specifies what the RADIO will transmit when it is not started, i.e. between: + /// RADIO.EVENTS_READY and RADIO.TASKS_START + /// RADIO.EVENTS_END and RADIO.TASKS_START + DTX OFFSET(8) NUMBITS(2) [ + B1 = 0, + B0 = 1, + CENTER = 2 + ] + ] +]; + +/// Operating mode of the radio. +#[derive(Debug, Clone, Copy, PartialEq)] +enum RadioState { + /// Radio peripheral is off. + OFF, + /// Currently transmitting a packet. + TX, + /// Default state when radio is on. Radio is configured to be in RX mode + /// when the radio is turned on but not transmitting. + RX, + /// Transmitting an acknowledgement packet. + ACK, +} + +/// We use a single deferred call for two operations: triggering config clients +/// and power change clients. This allows us to track which operation we need to +/// perform when we get the deferred call callback. +#[derive(Debug, Clone, Copy)] +enum DeferredOperation { + /// Waiting to notify that the configuration operation is complete. + ConfigClientCallback, + /// Waiting to notify that the power state of the radio changed (ie it + /// turned on or off). + PowerClientCallback, +} + +pub struct Radio<'a> { + registers: StaticRef, + rx_client: OptionalCell<&'a dyn radio::RxClient>, + tx_client: OptionalCell<&'a dyn radio::TxClient>, + config_client: OptionalCell<&'a dyn radio::ConfigClient>, + power_client: OptionalCell<&'a dyn radio::PowerClient>, + tx_power: Cell, + tx_buf: TakeCell<'static, [u8]>, + rx_buf: TakeCell<'static, [u8]>, + ack_buf: TakeCell<'static, [u8]>, + addr: Cell, + addr_long: Cell<[u8; 8]>, + pan: Cell, + cca_count: Cell, + cca_be: Cell, + random_nonce: Cell, + channel: Cell, + timer0: OptionalCell<&'a TimerAlarm<'a>>, + state: Cell, + deferred_call: DeferredCall, + deferred_call_operation: OptionalCell, +} + +impl<'a> AlarmClient for Radio<'a> { + fn alarm(&self) { + // This alarm function is the callback for when the CCA backoff alarm completes + // Attempt a new CCA period by issuing CCASTART task + self.registers.task_ccastart.write(Task::ENABLE::SET); + } +} + +impl<'a> Radio<'a> { + pub fn new(ack_buf: &'static mut [u8; ACK_BUF_SIZE]) -> Self { + Self { + registers: RADIO_BASE, + rx_client: OptionalCell::empty(), + tx_client: OptionalCell::empty(), + config_client: OptionalCell::empty(), + power_client: OptionalCell::empty(), + tx_power: Cell::new(TxPower::ZerodBm), + tx_buf: TakeCell::empty(), + rx_buf: TakeCell::empty(), + ack_buf: TakeCell::new(ack_buf), + addr: Cell::new(0), + addr_long: Cell::new([0x00; 8]), + pan: Cell::new(0), + cca_count: Cell::new(0), + cca_be: Cell::new(0), + random_nonce: Cell::new(0xDEADBEEF), + channel: Cell::new(RadioChannel::Channel26), + timer0: OptionalCell::empty(), + state: Cell::new(RadioState::OFF), + deferred_call: DeferredCall::new(), + deferred_call_operation: OptionalCell::empty(), + } + } + + pub fn set_timer_ref(&self, timer: &'a crate::timer::TimerAlarm<'a>) { + self.timer0.set(timer); + } + + pub fn is_enabled(&self) -> bool { + self.registers + .mode + .matches_all(Mode::MODE::IEEE802154_250KBIT) + } + + fn rx(&self) { + self.state.set(RadioState::RX); + + // Unwrap fail = Radio RX Buffer is missing (may be due to receive client not replacing in receive(...) method, + // or some instance in driver taking buffer without properly replacing). + let rbuf = self.rx_buf.take().unwrap(); + self.rx_buf.replace(self.set_dma_ptr(rbuf)); + + // Instruct radio hardware to automatically progress from RXIDLE to RX + // state upon receipt of internal `READY` signal after radio ramp-up completes. + self.registers.shorts.write(Shortcut::READY_START::SET); + + self.registers.task_rxen.write(Task::ENABLE::SET); + } + + fn radio_on(&self) { + // reset and enable power + self.registers.power.write(Task::ENABLE::CLEAR); + self.registers.power.write(Task::ENABLE::SET); + } + + fn radio_off(&self) { + self.state.set(RadioState::OFF); + + self.registers.power.write(Task::ENABLE::CLEAR); + } + + fn radio_is_on(&self) -> bool { + self.registers.power.is_set(Task::ENABLE) + } + + fn set_dma_ptr(&self, buffer: &'static mut [u8]) -> &'static mut [u8] { + self.registers + .packetptr + .set(buffer.as_ptr() as u32 + BUF_PREFIX_SIZE); + buffer + } + + fn crc_check(&self) -> Result<(), ErrorCode> { + if self.registers.crcstatus.is_set(Event::READY) { + Ok(()) + } else { + Err(ErrorCode::FAIL) + } + } + + // TODO: RECEIVING ACK FOR A SENT TX IS NOT IMPLEMENTED + // + // As a general note for the interrupt handler, event registers must still + // be cleared even when hardware shortcuts are enabled. + #[inline(never)] + pub fn handle_interrupt(&self) { + self.disable_all_interrupts(); + + let mut start_task = false; + let mut rx_init = false; + + match self.state.get() { + // It should not be possible to receive an interrupt while the + // tracked radio state is OFF. + RadioState::OFF => { + kernel::debug!("[ERROR]--15.4 state machine"); + kernel::debug!("Received interrupt while off"); + } + RadioState::RX => { + //////////////////////////////////////////////////////////////// + // NOTE: This state machine assumes that the READY_START + // shortcut is enabled at this point in time. If the READY_START + // shortcut is not enabled, the state machine/driver will likely + // exhibit undefined behavior. + //////////////////////////////////////////////////////////////// + + // Since READY_START shortcut enabled, always clear READY event + self.registers.event_ready.write(Event::READY::CLEAR); + + // Completed receiving a packet, now determine if we need to send ACK + if self.registers.event_end.is_set(Event::READY) { + self.registers.event_end.write(Event::READY::CLEAR); + let crc = self.crc_check(); + + // Unwrap fail = Radio RX Buffer is missing (may be due to + // receive client not replacing in receive(...) method, or + // some instance in driver taking buffer without properly + // replacing). + let rbuf = self.rx_buf.take().unwrap(); + + // Data buffer format: | PREFIX | PHR | PSDU | LQI | + // + // Retrieve the length of the PSDU (actual frame). The frame + // length is only 7 bits, but of course the field is a byte. + // The nRF52840 product specification says this about the + // PHR byte (Version 1.8, section 6.20.12.1): + // + // > The most significant bit is reserved and is set to zero + // > for frames that are standard compliant. The radio + // > module will report all eight bits and it can + // > potentially be used to carry some information. + // + // We are not using that for any information so we just + // force it to zero. This ensures that `data_len` will not + // be longer than our buffer. + let data_len = (rbuf[radio::PHR_OFFSET] & 0x7F) as usize; + + // LQI is found just after the data received. + let lqi = rbuf[data_len]; + + // We drop the CRC bytes (the MFR) from our frame. + let frame_len = data_len - radio::MFR_SIZE; + + // 6th bit in the first byte of the MAC header determines if + // sender requested ACK. If so send ACK first before handing + // packet reception. This optimizes the time taken to send + // an ACK. If we call the receive function here, there is a + // non deterministic time required to complete the function + // as it may be passed up the entirety of the networking + // stack (leading to ACK timeout being exceeded). + if rbuf[radio::PSDU_OFFSET] & ACK_FLAG != 0 && crc.is_ok() { + self.ack_buf + .take() + .map_or(Err(ErrorCode::NOMEM), |ack_buf| { + // Entered ACK state // + self.state.set(RadioState::ACK); + + // 4th byte of received packet is the 15.4 + // sequence number. + let sequence_number = rbuf[radio::PSDU_OFFSET + radio::MHR_FC_SIZE]; + + // The frame control field is hardcoded for now; + // this is the only possible type of ACK + // currently supported so it is reasonable to + // hardcode this. + ack_buf[radio::PSDU_OFFSET] = 2; + ack_buf[radio::PSDU_OFFSET + 1] = 0; + ack_buf[radio::PSDU_OFFSET + radio::MHR_FC_SIZE] = sequence_number; + + // Ensure we replace our RX buffer for the time + // being. + self.rx_buf.replace(rbuf); + + // If the transmit function fails, replace the + // buffer and return an error. + if let Err((_, ret_buf)) = self.transmit(ack_buf, 3) { + self.ack_buf.replace(ret_buf); + Err(ErrorCode::FAIL) + } else { + Ok(()) + } + }) + .unwrap_or_else(|err| { + // The ACK was not sent; we do not need to drop + // the packet, but print msg for debugging + // purposes, notify receive client of packet, + // and reset radio to receiving. + self.rx_client.map(|client| { + start_task = true; + client.receive( + self.rx_buf.take().unwrap(), + frame_len, + lqi, + crc.is_ok(), + Err(err), + ); + }); + + kernel::debug!( + "[ACKFail] Failed sending ACK in response to received packet." + ); + }); + } else { + // Packet received that does not require an ACK. Pass + // received packet to client and return radio to general + // receiving state to listen for new packets. + self.rx_client.map(|client| { + start_task = true; + client.receive(rbuf, frame_len, lqi, crc.is_ok(), Ok(())); + }); + } + } + } + RadioState::TX => { + //////////////////////////////////////////////////////////////// + // NOTE: This state machine assumes that the DISABLED_RXEN, + // CCAIDLE_TXEN, RXREADY_CCASTART shortcuts are enabled at this + // point in time. If the READY_START shortcut is not enabled, + // the state machine/driver will likely exhibit undefined + // behavior. + //////////////////////////////////////////////////////////////// + + // Handle Event_ready interrupt. The TX path performs both a TX + // ramp up and an RX ramp up. This means that there are two + // potential cases we must handle. The ready event due to the Rx + // Ramp up shortcuts to the CCASTART while the ready event due + // to the Tx ramp up requires we issue a start task in response + // to progress the state machine. + if self.registers.event_ready.is_set(Event::READY) { + // In both cases, we must clear event + self.registers.event_ready.write(Event::READY::CLEAR); + + // Ready event from Tx ramp up will be in radio internal + // TXIDLE state + if self.registers.state.get() == nrf52::constants::RADIO_STATE_TXIDLE { + start_task = true; + } + } + + // Handle CCA related interrupts. + if self.registers.event_ccabusy.is_set(Event::READY) { + self.registers.event_ccabusy.write(Event::READY::CLEAR); + + // Need to back off for a period of time outlined in the + // IEEE 802.15.4 standard (see Figure 69 in section 7.5.1.4 + // The CSMA-CA algorithm of the standard). + if self.cca_count.get() < IEEE802154_MAX_POLLING_ATTEMPTS { + self.cca_count.set(self.cca_count.get() + 1); + self.cca_be.set(self.cca_be.get() + 1); + let backoff_periods = self.random_nonce() & ((1 << self.cca_be.get()) - 1); + let current_time = self.timer0.unwrap_or_panic().now(); + self.timer0 + .unwrap_or_panic() // Unwrap fail = Missing timer reference for CSMA + .set_alarm( + current_time, + kernel::hil::time::Ticks32::from( + backoff_periods * (IEEE802154_BACKOFF_PERIOD as u32), + ), + ); + } else { + // We have exceeded the IEEE802154_MAX_POLLING_ATTEMPTS + // and should fail the transmission/return buffer to + // sending client. + + let result = Err(ErrorCode::BUSY); + self.tx_client.map(|client| { + // Unwrap fail = TX Buffer is missing and was + // mistakenly not replaced after completion of + // set_dma_ptr(...) + let tbuf = self.tx_buf.take().unwrap(); + client.send_done(tbuf, false, result); + }); + rx_init = true; + } + } + + // End event received; The TX is now finished and we need to + // notify the sending client. + if self.registers.event_end.is_set(Event::READY) { + self.registers.event_end.write(Event::READY::CLEAR); + let result = Ok(()); + + // TODO: Acked is hardcoded to always return false; add + // support to receive tx ACK. + self.tx_client.map(|client| { + // Unwrap fail = TX Buffer is missing and was mistakenly + // not replaced after completion of set_dma_ptr(...) + let tbuf = self.tx_buf.take().unwrap(); + client.send_done(tbuf, false, result); + }); + rx_init = true; + } + } + RadioState::ACK => { + //////////////////////////////////////////////////////////////// + // NOTE: This state machine assumes that the READY_START + // shortcut is enabled at this point in time. If the READY_START + // shortcut is not enabled, the state machine/driver will likely + // exhibit undefined behavior. + //////////////////////////////////////////////////////////////// + + // Since READY_START shortcut enabled, always clear READY event + self.registers.event_ready.write(Event::READY::CLEAR); + + // Completed sending ACK + if self.registers.event_end.is_set(Event::READY) { + self.registers.event_end.write(Event::READY::CLEAR); + + // Unwrap fail = TX Buffer is missing and was mistakenly not + // replaced after completion of set_dma_ptr(...) + let tbuf = self.tx_buf.take().unwrap(); + + // We must replace the ACK buffer that was passed to tx_buf + self.ack_buf.replace(tbuf); + + // Reset radio to proper receiving state + rx_init = true; + + // Notify receive client of packet that triggered the ACK. + self.rx_client.map(|client| { + // Unwrap fail = Radio RX Buffer is missing (may be due + // to receive client not replacing in receive(...) + // method, or some instance in driver taking buffer + // without properly replacing). + let rbuf = self.rx_buf.take().unwrap(); + + // Data buffer format: | PREFIX | PHR | PSDU | LQI | + // + // See the RX case above for how these values are set. + let data_len = (rbuf[radio::PHR_OFFSET] & 0x7F) as usize; + let lqi = rbuf[data_len]; + let frame_len = data_len - radio::MFR_SIZE; + + // We know the CRC passed because otherwise we would not + // have transmitted an ACK. + client.receive(rbuf, frame_len, lqi, true, Ok(())); + }); + } + } + } + + // Enabling hardware shortcuts allows for a much faster operation. + // However, this can also lead to race conditions and strange edge + // cases. Namely, if a task_start or rx_en is set while interrupts are + // disabled, the event_end interrupt can be "missed" and the interrupt + // handler will not be called. If the event is missed, the state machine + // is unable to progress and the driver enters a deadlock. + self.enable_interrupts(); + if rx_init { + self.rx(); + } + if start_task { + self.registers.task_start.write(Task::ENABLE::SET); + } + } + + pub fn enable_interrupts(&self) { + self.registers + .intenset + .write(Interrupt::READY::SET + Interrupt::CCABUSY::SET + Interrupt::END::SET); + } + + pub fn enable_interrupt(&self, intr: u32) { + self.registers.intenset.set(intr); + } + + pub fn clear_interrupt(&self, intr: u32) { + self.registers.intenclr.set(intr); + } + + pub fn disable_all_interrupts(&self) { + // disable all possible interrupts + self.registers.intenclr.set(0xffffffff); + } + + pub fn set_ack_buffer(&self, buffer: &'static mut [u8]) { + self.ack_buf.replace(buffer); + } + + fn radio_initialize(&self) { + self.radio_on(); + + // CONFIGURE RADIO // + self.ieee802154_set_channel_rate(); + + self.ieee802154_set_packet_config(); + + self.ieee802154_set_crc_config(); + + self.ieee802154_set_rampup_mode(); + + self.ieee802154_set_cca_config(); + + self.ieee802154_set_tx_power(); + + self.ieee802154_set_channel_freq(); + + // Begin receiving procedure + self.enable_interrupts(); + self.rx(); + } + + // IEEE802.15.4 SPECIFICATION Section 6.20.12.5 of the NRF52840 Datasheet + fn ieee802154_set_crc_config(&self) { + self.registers + .crccnf + .write(CrcConfiguration::LEN::TWO + CrcConfiguration::SKIPADDR::IEEE802154); + self.registers + .crcinit + .set(nrf52::constants::RADIO_CRCINIT_IEEE802154); + self.registers + .crcpoly + .set(nrf52::constants::RADIO_CRCPOLY_IEEE802154); + } + + fn ieee802154_set_rampup_mode(&self) { + self.registers + .modecnf0 + .write(RadioModeConfig::RU::FAST + RadioModeConfig::DTX::CENTER); + } + + fn ieee802154_set_cca_config(&self) { + self.registers.ccactrl.write( + CCAControl::CCAMODE.val(nrf52::constants::IEEE802154_CCA_MODE) + + CCAControl::CCAEDTHRESH.val(nrf52::constants::IEEE802154_CCA_ED_THRESH) + + CCAControl::CCACORRTHRESH.val(nrf52::constants::IEEE802154_CCA_CORR_THRESH) + + CCAControl::CCACORRCNT.val(nrf52::constants::IEEE802154_CCA_CORR_CNT), + ); + } + + // Packet configuration + // Settings taken from RiotOS nrf52840 15.4 driver + fn ieee802154_set_packet_config(&self) { + self.registers.pcnf0.write( + PacketConfiguration0::LFLEN.val(8) + + PacketConfiguration0::PLEN::THIRTYTWOZEROS + + PacketConfiguration0::CRCINC::INCLUDE, + ); + + self.registers + .pcnf1 + .write(PacketConfiguration1::MAXLEN.val(nrf52::constants::RADIO_PAYLOAD_LENGTH as u32)); + } + + fn ieee802154_set_channel_rate(&self) { + self.registers.mode.write(Mode::MODE::IEEE802154_250KBIT); + } + + fn ieee802154_set_channel_freq(&self) { + let channel = self.channel.get(); + self.registers + .frequency + .write(Frequency::FREQUENCY.val(channel as u32)); + } + + fn ieee802154_set_tx_power(&self) { + self.registers.txpower.set(self.tx_power.get() as u32); + } + + pub fn startup(&self) -> Result<(), ErrorCode> { + self.radio_initialize(); + Ok(()) + } + + // Returns a new pseudo-random number and updates the randomness state. + // + // Uses the [Xorshift](https://en.wikipedia.org/wiki/Xorshift) algorithm to + // produce pseudo-random numbers. Uses the `random_nonce` field to keep + // state. + fn random_nonce(&self) -> u32 { + let mut next_nonce = ::core::num::Wrapping(self.random_nonce.get()); + next_nonce ^= next_nonce << 13; + next_nonce ^= next_nonce >> 17; + next_nonce ^= next_nonce << 5; + self.random_nonce.set(next_nonce.0); + self.random_nonce.get() + } +} + +impl<'a> kernel::hil::radio::RadioConfig<'a> for Radio<'a> { + fn initialize(&self) -> Result<(), ErrorCode> { + Ok(()) + } + + fn set_power_client(&self, client: &'a dyn PowerClient) { + self.power_client.set(client); + } + + fn reset(&self) -> Result<(), ErrorCode> { + self.radio_initialize(); + Ok(()) + } + + fn start(&self) -> Result<(), ErrorCode> { + self.radio_initialize(); + + // Configure deferred call to trigger callback. + self.deferred_call_operation + .set(DeferredOperation::PowerClientCallback); + self.deferred_call.set(); + + Ok(()) + } + + fn stop(&self) -> Result<(), ErrorCode> { + self.radio_off(); + + // Configure deferred call to trigger callback. + self.deferred_call_operation + .set(DeferredOperation::PowerClientCallback); + self.deferred_call.set(); + + Ok(()) + } + + fn is_on(&self) -> bool { + self.radio_is_on() + } + + fn busy(&self) -> bool { + // `tx_buf` is only occupied when a transmission is underway. + self.tx_buf.is_some() + } + + fn set_config_client(&self, client: &'a dyn radio::ConfigClient) { + self.config_client.set(client); + } + + /// Commit the config calls to hardware, changing (in theory): + /// + /// - the RX address + /// - PAN ID + /// - TX power + /// - channel + /// + /// to the specified values. **However**, the nRF52840 IEEE 802.15.4 radio + /// does not support hardware-level address filtering (see + /// [here](https://devzone.nordicsemi.com/f/nordic-q-a/19320/using-nrf52840-for-802-15-4)). + /// So setting the addresses and PAN ID have no meaning for this chip and + /// any filtering must be done in higher layers in software. + /// + /// Issues a callback to the config client when done. + fn config_commit(&self) { + // All we can configure is TX power and channel frequency. + self.ieee802154_set_tx_power(); + self.ieee802154_set_channel_freq(); + + // Enable deferred call so we can generate a `ConfigClient` callback. + self.deferred_call_operation + .set(DeferredOperation::ConfigClientCallback); + self.deferred_call.set(); + } + + //################################################# + /// Accessors + //################################################# + + fn get_address(&self) -> u16 { + self.addr.get() + } + + fn get_address_long(&self) -> [u8; 8] { + self.addr_long.get() + } + + /// The 16-bit PAN ID + fn get_pan(&self) -> u16 { + self.pan.get() + } + + /// The transmit power, in dBm + fn get_tx_power(&self) -> i8 { + self.tx_power.get() as i8 + } + + /// The 802.15.4 channel + fn get_channel(&self) -> u8 { + self.channel.get().get_channel_number() + } + + //################################################# + /// Mutators + //################################################# + + fn set_address(&self, addr: u16) { + self.addr.set(addr); + } + + fn set_address_long(&self, addr: [u8; 8]) { + self.addr_long.set(addr); + } + + fn set_pan(&self, id: u16) { + self.pan.set(id); + } + + fn set_channel(&self, chan: RadioChannel) { + self.channel.set(chan); + } + + fn set_tx_power(&self, tx_power: i8) -> Result<(), ErrorCode> { + // Convert u8 to TxPower + match nrf52::constants::TxPower::try_from(tx_power as u8) { + // Invalid transmitting power, propagate error + Err(()) => Err(ErrorCode::NOSUPPORT), + // Valid transmitting power, propagate success + Ok(res) => { + self.tx_power.set(res); + Ok(()) + } + } + } +} + +impl<'a> kernel::hil::radio::RadioData<'a> for Radio<'a> { + fn set_receive_client(&self, client: &'a dyn radio::RxClient) { + self.rx_client.set(client); + } + + fn set_receive_buffer(&self, buffer: &'static mut [u8]) { + self.rx_buf.replace(buffer); + } + + fn set_transmit_client(&self, client: &'a dyn radio::TxClient) { + self.tx_client.set(client); + } + + fn transmit( + &self, + buf: &'static mut [u8], + frame_len: usize, + ) -> Result<(), (ErrorCode, &'static mut [u8])> { + if self.state.get() == RadioState::OFF { + return Err((ErrorCode::OFF, buf)); + } else if self.busy() { + return Err((ErrorCode::BUSY, buf)); + } else if buf.len() < radio::PSDU_OFFSET + frame_len + radio::MFR_SIZE { + // Not enough room for CRC or PHR or reserved byte + return Err((ErrorCode::SIZE, buf)); + } + + // Insert the PHR which is the PDSU length. + buf[radio::PHR_OFFSET] = (frame_len + radio::MFR_SIZE) as u8; + + // The tx_buf does not possess static memory. This buffer only + // temporarily holds a reference to another buffer passed as a function + // argument. The tx_buf holds ownership of this buffer until it is + // returned through the send_done(...) function. + self.tx_buf.replace(self.set_dma_ptr(buf)); + + // The transmit function handles sending both ACK and standard packets + if let RadioState::ACK = self.state.get() { + self.registers.task_txen.write(Task::ENABLE::SET); + } else { + // Configure radio for standard packet TX + self.state.set(RadioState::TX); + + // Instruct radio hardware to automatically progress from: + // - RXDISABLE to RXRU state upon receipt of internal disabled event + // - RXIDLE to RX state upon receipt of ready event and radio ramp + // up completed, begin CCA backoff + // - RX to TXRU state upon internal receipt CCA completion event + // (clear to begin transmitting) + self.registers.shorts.write( + Shortcut::DISABLED_RXEN::SET + + Shortcut::RXREADY_CCASTART::SET + + Shortcut::CCAIDLE_TXEN::SET, + ); + + // Radio is in proper shortcut state, disable and begin TX sequence + self.registers.task_disable.write(Task::ENABLE::SET); + } + + Ok(()) + } +} + +impl DeferredCallClient for Radio<'_> { + fn handle_deferred_call(&self) { + // On deferred call we trigger the config or power callbacks. The + // `.take()` ensures we clear what is pending. + self.deferred_call_operation.take().map(|op| match op { + DeferredOperation::ConfigClientCallback => { + self.config_client.map(|client| { + client.config_done(Ok(())); + }); + } + DeferredOperation::PowerClientCallback => { + self.power_client.map(|client| { + client.changed(self.radio_is_on()); + }); + } + }); + } + + fn register(&'static self) { + self.deferred_call.register(self); + } +} diff --git a/chips/nrf52840/src/interrupt_service.rs b/chips/nrf52840/src/interrupt_service.rs index 4eb4721e83..21c717adb3 100644 --- a/chips/nrf52840/src/interrupt_service.rs +++ b/chips/nrf52840/src/interrupt_service.rs @@ -1,4 +1,8 @@ -use crate::deferred_call_tasks::DeferredCallTask; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use kernel::hil::time::Alarm; use nrf52::chip::Nrf52DefaultPeripherals; /// This struct, when initialized, instantiates all peripheral drivers for the nrf52840. @@ -8,37 +12,52 @@ use nrf52::chip::Nrf52DefaultPeripherals; //create all base nrf52 peripherals pub struct Nrf52840DefaultPeripherals<'a> { pub nrf52: Nrf52DefaultPeripherals<'a>, + pub ieee802154_radio: crate::ieee802154_radio::Radio<'a>, pub usbd: crate::usbd::Usbd<'a>, pub gpio_port: crate::gpio::Port<'a, { crate::gpio::NUM_PINS }>, } impl<'a> Nrf52840DefaultPeripherals<'a> { - pub unsafe fn new() -> Self { + pub unsafe fn new( + ieee802154_radio_ack_buf: &'static mut [u8; crate::ieee802154_radio::ACK_BUF_SIZE], + ) -> Self { Self { nrf52: Nrf52DefaultPeripherals::new(), + ieee802154_radio: crate::ieee802154_radio::Radio::new(ieee802154_radio_ack_buf), usbd: crate::usbd::Usbd::new(), gpio_port: crate::gpio::nrf52840_gpio_create(), } } // Necessary for setting up circular dependencies - pub fn init(&'a self) { + pub fn init(&'static self) { + self.ieee802154_radio.set_timer_ref(&self.nrf52.timer0); + self.nrf52.timer0.set_alarm_client(&self.ieee802154_radio); self.nrf52.pwr_clk.set_usb_client(&self.usbd); self.usbd.set_power_ref(&self.nrf52.pwr_clk); + kernel::deferred_call::DeferredCallClient::register(&self.ieee802154_radio); self.nrf52.init(); } } -impl<'a> kernel::platform::chip::InterruptService - for Nrf52840DefaultPeripherals<'a> -{ +impl<'a> kernel::platform::chip::InterruptService for Nrf52840DefaultPeripherals<'a> { unsafe fn service_interrupt(&self, interrupt: u32) -> bool { match interrupt { crate::peripheral_interrupts::USBD => self.usbd.handle_interrupt(), nrf52::peripheral_interrupts::GPIOTE => self.gpio_port.handle_interrupt(), + nrf52::peripheral_interrupts::RADIO => { + match ( + self.ieee802154_radio.is_enabled(), + self.nrf52.ble_radio.is_enabled(), + ) { + (false, false) => (), + (true, false) => self.ieee802154_radio.handle_interrupt(), + (false, true) => self.nrf52.ble_radio.handle_interrupt(), + (true, true) => kernel::debug!( + "nRF 802.15.4 and BLE radios cannot be simultaneously enabled!" + ), + } + } _ => return self.nrf52.service_interrupt(interrupt), } true } - unsafe fn service_deferred_call(&self, task: DeferredCallTask) -> bool { - self.nrf52.service_deferred_call(task) - } } diff --git a/chips/nrf52840/src/lib.rs b/chips/nrf52840/src/lib.rs index b9bb5440bd..0f5eba67db 100644 --- a/chips/nrf52840/src/lib.rs +++ b/chips/nrf52840/src/lib.rs @@ -1,10 +1,19 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + #![no_std] pub use nrf52::{ - acomp, adc, aes, ble_radio, chip, clock, constants, crt1, deferred_call_tasks, ficr, i2c, - ieee802154_radio, init, nvmc, peripheral_interrupts as base_interrupts, pinmux, power, ppi, - pwm, rtc, spi, temperature, timer, trng, uart, uicr, usbd, + acomp, adc, aes, ble_radio, chip, clock, constants, crt1, ficr, i2c, init, nvmc, + peripheral_interrupts as base_interrupts, pinmux, power, ppi, pwm, rtc, spi, temperature, + timer, trng, uart, uicr, usbd, }; pub mod gpio; pub mod interrupt_service; +// FIXME: We need a nrf528xx crate as well. The nrf52832 does NOT support 15.4, +// but the nrf52833 and nrf52840 do support it. That's a more substantial +// restructuring than belongs in the ACK-fix PR, however. +pub mod ieee802154_radio; + pub mod peripheral_interrupts; diff --git a/chips/nrf52840/src/peripheral_interrupts.rs b/chips/nrf52840/src/peripheral_interrupts.rs index 04c0f3f5ff..a74df9d548 100644 --- a/chips/nrf52840/src/peripheral_interrupts.rs +++ b/chips/nrf52840/src/peripheral_interrupts.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + pub const USBD: u32 = 39; #[allow(dead_code)] pub const UART1: u32 = 40; diff --git a/chips/nrf5x/Cargo.toml b/chips/nrf5x/Cargo.toml index 92a71d7d54..f39481b1bd 100644 --- a/chips/nrf5x/Cargo.toml +++ b/chips/nrf5x/Cargo.toml @@ -1,8 +1,12 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "nrf5x" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] kernel = { path = "../../kernel" } @@ -13,3 +17,6 @@ default = [] nrf51 = [] nrf52 = [] + +[lints] +workspace = true diff --git a/chips/nrf5x/src/aes.rs b/chips/nrf5x/src/aes.rs index cacca62609..9228f9a779 100644 --- a/chips/nrf5x/src/aes.rs +++ b/chips/nrf5x/src/aes.rs @@ -1,9 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! AES128 driver, nRF5X-family //! -//! Provides a simple driverto encrypt and decrypt -//! messages using aes128-ctr mode on top of aes128-ecb. +//! Provides a simple driver to encrypt and decrypt messages using aes128-ctr +//! mode on top of aes128-ecb, as well as encrypt with aes128-ecb and +//! aes128-cbc. //! -//! Roughly, the module three buffers with the following content: +//! Roughly, the module uses three buffers with the following content: //! //! * Key //! * Initial counter @@ -55,7 +60,6 @@ const PLAINTEXT_END: usize = 32; const CIPHERTEXT_START: usize = 33; #[allow(dead_code)] const CIPHERTEXT_END: usize = 47; -const MAX_LENGTH: usize = 128; const AESECB_BASE: StaticRef = unsafe { StaticRef::new(0x4000E000 as *const AesEcbRegisters) }; @@ -120,14 +124,20 @@ register_bitfields! [u32, ] ]; +#[derive(Copy, Clone, Debug)] +enum AESMode { + ECB, + CTR, + CBC, +} + pub struct AesECB<'a> { registers: StaticRef, + mode: Cell, client: OptionalCell<&'a dyn kernel::hil::symmetric_encryption::Client<'a>>, /// Input either plaintext or ciphertext to be encrypted or decrypted. input: TakeCell<'static, [u8]>, output: TakeCell<'static, [u8]>, - /// Keystream to be XOR'ed with the input. - keystream: Cell<[u8; MAX_LENGTH]>, current_idx: Cell, start_idx: Cell, end_idx: Cell, @@ -137,10 +147,10 @@ impl<'a> AesECB<'a> { pub const fn new() -> AesECB<'a> { AesECB { registers: AESECB_BASE, + mode: Cell::new(AESMode::CTR), client: OptionalCell::empty(), input: TakeCell::empty(), output: TakeCell::empty(), - keystream: Cell::new([0; MAX_LENGTH]), current_idx: Cell::new(0), start_idx: Cell::new(0), end_idx: Cell::new(0), @@ -153,6 +163,45 @@ impl<'a> AesECB<'a> { } } + /// Verify that the provided start and stop indices work with the given + /// buffers. + fn try_set_indices(&self, start_index: usize, stop_index: usize) -> bool { + stop_index.checked_sub(start_index).map_or(false, |sublen| { + sublen % symmetric_encryption::AES128_BLOCK_SIZE == 0 && { + self.input.map_or_else( + || { + // The destination buffer is also the input + if self.output.map_or(false, |dest| stop_index <= dest.len()) { + self.current_idx.set(0); + self.start_idx.set(start_index); + self.end_idx.set(stop_index); + true + } else { + false + } + }, + |source| { + if sublen == source.len() + && self.output.map_or(false, |dest| stop_index <= dest.len()) + { + // We will start writing to the AES from the + // beginning of `source`, and end at its end + self.current_idx.set(0); + + // We will start reading from the AES into `dest` at + // `start_index`, and continue until `stop_index` + self.start_idx.set(start_index); + self.end_idx.set(stop_index); + true + } else { + false + } + }, + ) + } + }) + } + // FIXME: should this be performed in constant time i.e. skip the break part // and always loop 16 times? fn update_ctr(&self) { @@ -166,7 +215,104 @@ impl<'a> AesECB<'a> { } } + /// Get the relevant positions of our input data whether we are using a + /// source buffer or overwriting the destination buffer. + fn get_start_end_take(&self) -> (usize, usize, usize) { + let current_idx = self.current_idx.get(); + + // Location in the appropriate source buffer we are currently working + // on. + let start = current_idx + self.input.map_or(self.start_idx.get(), |_| 0); + // Last index in the appropriate source buffer we need to work on. + let end = self.end_idx.get() - self.input.map_or(0, |_| self.start_idx.get()); + + // Get the number of bytes that were used in the keystream/block. + let take = match end.checked_sub(start) { + Some(v) if v > symmetric_encryption::AES128_BLOCK_SIZE => { + symmetric_encryption::AES128_BLOCK_SIZE + } + Some(v) => v, + None => 0, + }; + + (start, end, take) + } + + fn copy_plaintext(&self) { + let (start, _end, take) = self.get_start_end_take(); + + // Copy the plaintext either from the source if it exists or from the + // destination buffer. + if take > 0 { + match self.mode.get() { + AESMode::ECB => { + self.input.map_or_else( + || { + self.output.map(|output| { + for i in 0..take { + // Copy into static mut DMA buffer + unsafe { + ECB_DATA[i + PLAINTEXT_START] = output[i + start]; + } + } + }); + }, + |input| { + for i in 0..take { + // Copy into static mut DMA buffer + unsafe { + ECB_DATA[i + PLAINTEXT_START] = input[i + start]; + } + } + }, + ); + } + + AESMode::CBC => { + self.input.map_or_else( + || { + self.output.map(|output| { + for i in 0..take { + let ecb_idx = i + PLAINTEXT_START; + + // Copy into static mut DMA buffer + unsafe { + ECB_DATA[ecb_idx] ^= output[i + start]; + } + } + }); + }, + |input| { + for i in 0..take { + let ecb_idx = i + PLAINTEXT_START; + // Copy into static mut DMA buffer + unsafe { + ECB_DATA[ecb_idx] ^= input[i + start]; + } + } + }, + ); + } + + AESMode::CTR => { + // no copying plaintext in ctr mode + } + } + } + } + fn crypt(&self) { + match self.mode.get() { + AESMode::CTR => {} + AESMode::ECB => { + // Need to copy the plaintext to the ECB buffer. + self.copy_plaintext(); + } + AESMode::CBC => { + self.copy_plaintext(); + } + } + self.registers.event_endecb.write(Event::READY::CLEAR); self.registers.task_startecb.set(1); @@ -179,57 +325,97 @@ impl<'a> AesECB<'a> { self.disable_interrupts(); if self.registers.event_endecb.get() == 1 { + let (start, end, take) = self.get_start_end_take(); + let start_idx = self.start_idx.get(); let current_idx = self.current_idx.get(); - let end_idx = self.end_idx.get(); - // Get the number of bytes to be used in the keystream/block - let take = match end_idx.checked_sub(current_idx) { - Some(v) if v > symmetric_encryption::AES128_BLOCK_SIZE => { - symmetric_encryption::AES128_BLOCK_SIZE + match self.mode.get() { + AESMode::CTR => { + // Fill in the ciphertext in the output buffer. + if take > 0 { + self.input.map_or_else( + || { + // No input buffer, so source data comes from + // output buffer. + self.output.map(|output| { + for i in 0..take { + let in_byte = output[start + i]; + let keystream_byte = unsafe { ECB_DATA[i + PLAINTEXT_END] }; + + output[start + i] = keystream_byte ^ in_byte; + } + }); + }, + |input| { + self.output.map(|output| { + let start_idx = self.start_idx.get(); + + for i in 0..take { + let in_byte = input[start + i]; + let keystream_byte = unsafe { ECB_DATA[i + PLAINTEXT_END] }; + + output[start_idx + current_idx + i] = + keystream_byte ^ in_byte; + } + }); + }, + ); + + self.update_ctr(); + } } - Some(v) => v, - None => 0, - }; - - let mut ks = self.keystream.get(); - // Append keystream to the KEYSTREAM array - if take > 0 { - for i in current_idx..current_idx + take { - ks[i] = unsafe { ECB_DATA[i - current_idx + PLAINTEXT_END] } + AESMode::ECB => { + // Copy ciphertext to output. + if take > 0 { + self.output.map(|output| { + for i in 0..take { + // We write to the buffer starting at the + // originally provided start index, plus our + // offset at current_idx. + let dest_idx = start_idx + current_idx + i; + // Copy out of static mut DMA buffer + unsafe { + output[dest_idx] = ECB_DATA[i + PLAINTEXT_END]; + } + } + }); + } + } + AESMode::CBC => { + // Copy ciphertext to both output AND the ECB payload to use + // on the next iteration. + if take > 0 { + self.output.map(|output| { + for i in 0..take { + // We write to the buffer starting at the + // originally provided start index, plus our + // offset at current_idx. + let dest_idx = start_idx + current_idx + i; + // Copy out of static mut DMA buffer + unsafe { + output[dest_idx] = ECB_DATA[i + PLAINTEXT_END]; + ECB_DATA[i + PLAINTEXT_START] = ECB_DATA[i + PLAINTEXT_END]; + } + } + }); + } } - self.current_idx.set(current_idx + take); - self.update_ctr(); } - // More bytes to encrypt!!! - if self.current_idx.get() < self.end_idx.get() { - self.crypt(); - } - // Entire keystream generated we are done! - // XOR keystream the input - else if self.input.is_some() && self.output.is_some() { - self.input.take().map(|slice| { - self.output.take().map(|buf| { - let start = self.start_idx.get(); - let end = self.end_idx.get(); - let len = end - start; - - for ((i, out), inp) in buf.as_mut()[start..end] - .iter_mut() - .enumerate() - .zip(slice.as_ref()[0..len].iter()) - { - *out = ks[i] ^ *inp; - } + // Advance through the buffer. + self.current_idx.set(current_idx + take); - self.client - .map(move |client| client.crypt_done(Some(slice), buf)); - }); + // Check if we are done or if we need to crypt another block. + if start + take < end { + // More to do. + self.crypt(); + } else { + self.output.take().map(|output| { + self.client + .map(move |client| client.crypt_done(self.input.take(), output)); }); } - - self.keystream.set(ks); } } @@ -287,12 +473,8 @@ impl<'a> kernel::hil::symmetric_encryption::AES128<'a> for AesECB<'a> { } // not needed by NRF5x - fn start_message(&self) { - () - } + fn start_message(&self) {} - // start_index and stop_index not used!!! - // assuming that fn crypt( &self, source: Option<&'static mut [u8]>, @@ -304,49 +486,52 @@ impl<'a> kernel::hil::symmetric_encryption::AES128<'a> for AesECB<'a> { Option<&'static mut [u8]>, &'static mut [u8], )> { - match source { - None => Some((Err(ErrorCode::INVAL), source, dest)), - Some(src) => { - if stop_index - start_index <= MAX_LENGTH { - // replace buffers - self.input.replace(src); - self.output.replace(dest); - - // configure buffer offsets - self.current_idx.set(0); - self.start_idx.set(start_index); - self.end_idx.set(stop_index); - - // start crypt - self.crypt(); - None - } else { - Some((Err(ErrorCode::SIZE), Some(src), dest)) - } - } + self.input.put(source); + self.output.replace(dest); + if self.try_set_indices(start_index, stop_index) { + self.crypt(); + None + } else { + Some(( + Err(ErrorCode::INVAL), + self.input.take(), + self.output.take().unwrap(), + )) } } } impl kernel::hil::symmetric_encryption::AES128ECB for AesECB<'_> { // not needed by NRF5x (the configuration is the same for encryption and decryption) - fn set_mode_aes128ecb(&self, _encrypting: bool) -> Result<(), ErrorCode> { - Ok(()) + fn set_mode_aes128ecb(&self, encrypting: bool) -> Result<(), ErrorCode> { + if encrypting { + self.mode.set(AESMode::ECB); + Ok(()) + } else { + Err(ErrorCode::INVAL) + } } } impl kernel::hil::symmetric_encryption::AES128Ctr for AesECB<'_> { // not needed by NRF5x (the configuration is the same for encryption and decryption) fn set_mode_aes128ctr(&self, _encrypting: bool) -> Result<(), ErrorCode> { + self.mode.set(AESMode::CTR); Ok(()) } } impl kernel::hil::symmetric_encryption::AES128CBC for AesECB<'_> { - fn set_mode_aes128cbc(&self, _encrypting: bool) -> Result<(), ErrorCode> { - Ok(()) + fn set_mode_aes128cbc(&self, encrypting: bool) -> Result<(), ErrorCode> { + if encrypting { + self.mode.set(AESMode::CBC); + Ok(()) + } else { + Err(ErrorCode::INVAL) + } } } + //TODO: replace this placeholder with a proper implementation of the AES system impl<'a> kernel::hil::symmetric_encryption::AES128CCM<'a> for AesECB<'a> { /// Set the client instance which will receive `crypt_done()` callbacks diff --git a/chips/nrf5x/src/constants.rs b/chips/nrf5x/src/constants.rs index 4e415adbb6..d16846f11e 100644 --- a/chips/nrf5x/src/constants.rs +++ b/chips/nrf5x/src/constants.rs @@ -1,4 +1,6 @@ -use core::convert::TryFrom; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. // PCNF0 pub const RADIO_PCNF0_LFLEN_POS: u32 = 0; diff --git a/chips/nrf5x/src/gpio.rs b/chips/nrf5x/src/gpio.rs index ed390d6378..ec4be0e62c 100644 --- a/chips/nrf5x/src/gpio.rs +++ b/chips/nrf5x/src/gpio.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! GPIO and GPIOTE (task and events), nRF5x-family //! //! ### Author @@ -20,7 +24,7 @@ const NUM_GPIOTE: usize = 4; const NUM_GPIOTE: usize = 8; // Dummy value for testing on Travis-CI. #[cfg(all( - not(any(target_arch = "arm", target_os = "none")), + not(all(target_arch = "arm", target_os = "none")), not(feature = "nrf51"), not(feature = "nrf52"), ))] @@ -360,6 +364,7 @@ pub struct GPIOPin<'a> { client: OptionalCell<&'a dyn hil::gpio::Client>, gpiote_registers: StaticRef, gpio_registers: StaticRef, + allocated_channel: OptionalCell, } impl<'a> GPIOPin<'a> { @@ -375,6 +380,7 @@ impl<'a> GPIOPin<'a> { ) }, gpiote_registers: GPIOTE_BASE, + allocated_channel: OptionalCell::empty(), } } @@ -385,6 +391,16 @@ impl<'a> GPIOPin<'a> { PinConfig::DRIVE::S0S1 }); } + + // This sets the specified pin cfg as per the TRM for i2c pin usage. + pub fn set_i2c_pin_cfg(&self) { + self.gpio_registers.pin_cnf[self.pin as usize].modify( + PinConfig::DIR::Input + + PinConfig::INPUT::Disconnect + + PinConfig::DRIVE::S0D1 + + PinConfig::SENSE::Disabled, + ); + } } impl hil::gpio::Configure for GPIOPin<'_> { @@ -481,35 +497,50 @@ impl<'a> hil::gpio::Interrupt<'a> for GPIOPin<'a> { } fn is_pending(&self) -> bool { - if let Ok(channel) = self.find_channel(self.pin) { + if let Some(channel) = self.allocated_channel.get() { let ev = &self.gpiote_registers.event_in[channel]; - ev.matches_any(EventsIn::EVENT::Ready) + ev.any_matching_bits_set(EventsIn::EVENT::Ready) } else { false } } fn enable_interrupts(&self, mode: hil::gpio::InterruptEdge) { - if let Ok(channel) = self.allocate_channel() { - let polarity = match mode { - hil::gpio::InterruptEdge::EitherEdge => Config::POLARITY::Toggle, - hil::gpio::InterruptEdge::RisingEdge => Config::POLARITY::LoToHi, - hil::gpio::InterruptEdge::FallingEdge => Config::POLARITY::HiToLo, - }; - let pin: u32 = (GPIO_PER_PORT as u32 * self.port as u32) + self.pin as u32; - self.gpiote_registers.config[channel] - .write(Config::MODE::Event + Config::PSEL.val(pin) + polarity); - self.gpiote_registers.intenset.set(1 << channel); + let channel = if let Some(chan) = self.allocated_channel.get() { + // We only support one interrupt mode per pin, despite the + // hardware supporting multiple. This is to comply with + // expectations in other Tock components, such as the button + // driver which re-registers interrupts for a restarted app, + // assuming the old ones to be overwritten. + chan + } else if let Ok(chan) = self.allocate_channel() { + // Don't have a channel yet, got a new one: + chan } else { debug!("No available GPIOTE interrupt channels"); - } + return; + }; + + // Remember that we have allocated this channel for this pin: + self.allocated_channel.set(channel); + + let polarity = match mode { + hil::gpio::InterruptEdge::EitherEdge => Config::POLARITY::Toggle, + hil::gpio::InterruptEdge::RisingEdge => Config::POLARITY::LoToHi, + hil::gpio::InterruptEdge::FallingEdge => Config::POLARITY::HiToLo, + }; + let pin: u32 = (GPIO_PER_PORT as u32 * self.port as u32) + self.pin as u32; + self.gpiote_registers.config[channel] + .write(Config::MODE::Event + Config::PSEL.val(pin) + polarity); + self.gpiote_registers.intenset.set(1 << channel); } fn disable_interrupts(&self) { - if let Ok(channel) = self.find_channel(self.pin) { + if let Some(channel) = self.allocated_channel.get() { self.gpiote_registers.config[channel] .write(Config::MODE::CLEAR + Config::PSEL::CLEAR + Config::POLARITY::CLEAR); self.gpiote_registers.intenclr.set(1 << channel); + self.allocated_channel.clear(); } } } @@ -526,18 +557,6 @@ impl GPIOPin<'_> { Err(()) } - /// Return which channel is allocated to a pin, - /// If the channel is not found return an error instead - fn find_channel(&self, pin: u8) -> Result { - for (i, ch) in self.gpiote_registers.config.iter().enumerate() { - let encoded_pin = (GPIO_PER_PORT as u32 * self.port as u32) + pin as u32; - if ch.matches_all(Config::PSEL.val(encoded_pin)) { - return Ok(i); - } - } - Err(()) - } - fn handle_interrupt(&self) { self.client.map(|client| { client.fired(); @@ -564,7 +583,7 @@ impl<'a, const N: usize> IndexMut for Port<'a, N> { } impl<'a, const N: usize> Port<'a, N> { - pub fn new(pins: [GPIOPin<'a>; N]) -> Self { + pub const fn new(pins: [GPIOPin<'a>; N]) -> Self { Self { pins } } @@ -576,7 +595,7 @@ impl<'a, const N: usize> Port<'a, N> { let pin_registers = self.pins[0].gpiote_registers; for (i, ev) in pin_registers.event_in.iter().enumerate() { - if ev.matches_any(EventsIn::EVENT::Ready) { + if ev.any_matching_bits_set(EventsIn::EVENT::Ready) { ev.write(EventsIn::EVENT::NotReady); // Get pin number for the event and `trigger` an interrupt manually on that pin let pin = pin_registers.config[i].read(Config::PSEL) as usize; diff --git a/chips/nrf5x/src/lib.rs b/chips/nrf5x/src/lib.rs index b97075e360..99dcd101d5 100644 --- a/chips/nrf5x/src/lib.rs +++ b/chips/nrf5x/src/lib.rs @@ -1,4 +1,7 @@ -#![feature(const_fn_trait_bound)] +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + #![no_std] pub mod aes; diff --git a/chips/nrf5x/src/peripheral_interrupts.rs b/chips/nrf5x/src/peripheral_interrupts.rs index ffde991114..cf2b8c952a 100644 --- a/chips/nrf5x/src/peripheral_interrupts.rs +++ b/chips/nrf5x/src/peripheral_interrupts.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + pub const POWER_CLOCK: u32 = 0; pub const RADIO: u32 = 1; pub const UART0: u32 = 2; diff --git a/chips/nrf5x/src/pinmux.rs b/chips/nrf5x/src/pinmux.rs index 25bb033b5b..9fb72c4e33 100644 --- a/chips/nrf5x/src/pinmux.rs +++ b/chips/nrf5x/src/pinmux.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! An abstraction over the pin multiplexer, nRF5X-family //! //! Controller drivers should use the `Pinmux` type (instead of a `u32`) for @@ -41,8 +45,8 @@ impl Pinmux { } } -impl Into for Pinmux { - fn into(self) -> u32 { - self.0 +impl From for u32 { + fn from(val: Pinmux) -> Self { + val.0 } } diff --git a/chips/nrf5x/src/rtc.rs b/chips/nrf5x/src/rtc.rs index 70791a7eae..c21c1c09a6 100644 --- a/chips/nrf5x/src/rtc.rs +++ b/chips/nrf5x/src/rtc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! RTC driver, nRF5X-family use core::cell::Cell; diff --git a/chips/nrf5x/src/temperature.rs b/chips/nrf5x/src/temperature.rs index 1a7eb21dbd..b8f454d96c 100644 --- a/chips/nrf5x/src/temperature.rs +++ b/chips/nrf5x/src/temperature.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Temperature sensor driver, nRF5X-family //! //! Generates a simple temperature measurement without sampling @@ -124,8 +128,8 @@ impl<'a> Temp<'a> { self.disable_interrupts(); // get temperature - // Result of temperature measurement in °C, 2's complement format, 0.25 °C - let temp = (self.registers.temp.get() / 4) * 100; + // Result of temperature measurement in °C, 2's complement format, 0.25 °C steps + let temp = (self.registers.temp.get() as i32 * 100) / 4; // stop measurement self.registers.task_stop.write(Task::ENABLE::SET); @@ -134,7 +138,7 @@ impl<'a> Temp<'a> { self.disable_interrupts(); // trigger callback with temperature - self.client.map(|client| client.callback(temp as usize)); + self.client.map(|client| client.callback(Ok(temp))); } fn enable_interrupts(&self) { diff --git a/chips/nrf5x/src/timer.rs b/chips/nrf5x/src/timer.rs index 6183e2c551..0b2cd1eb75 100644 --- a/chips/nrf5x/src/timer.rs +++ b/chips/nrf5x/src/timer.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Timer driver, nRF5X-family //! //! The nRF51822 timer system operates off of the high frequency clock @@ -238,7 +242,7 @@ impl Timer { // clear it and store its bit in val to pass in callback. for i in 0..4 { if self.registers.events_compare[i].is_set(Event::READY) { - val = val | 1 << i; + val |= 1 << i; self.registers.events_compare[i].write(Event::READY::CLEAR); // Disable corresponding interrupt let interrupt_bit = match i { diff --git a/chips/nrf5x/src/trng.rs b/chips/nrf5x/src/trng.rs index e34160e059..01fcd7e46b 100644 --- a/chips/nrf5x/src/trng.rs +++ b/chips/nrf5x/src/trng.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! TRNG driver, nRF5X-family //! //! The TRNG generates 1 byte randomness at the time value in the interval @@ -40,23 +44,23 @@ pub struct RngRegisters { /// Address: 0x004 - 0x008 pub task_stop: WriteOnly, /// Reserved - pub _reserved1: [u32; 62], + _reserved1: [u32; 62], /// Event being generated for every new random number written to the VALUE register /// Address: 0x100 - 0x104 pub event_valrdy: ReadWrite, /// Reserved - pub _reserved2: [u32; 63], + _reserved2: [u32; 63], /// Shortcut register /// Address: 0x200 - 0x204 pub shorts: ReadWrite, - pub _reserved3: [u32; 64], + _reserved3: [u32; 64], /// Enable interrupt /// Address: 0x304 - 0x308 pub intenset: ReadWrite, /// Disable interrupt /// Address: 0x308 - 0x30c pub intenclr: ReadWrite, - pub _reserved4: [u32; 126], + _reserved4: [u32; 126], /// Configuration register /// Address: 0x504 - 0x508 pub config: ReadWrite, @@ -137,7 +141,7 @@ impl<'a> Trng<'a> { // e = 1 -> byte 2 // e = 2 -> byte 3 // e = 3 -> byte 4 MSB - rn |= r << 8 * e; + rn |= r << (8 * e); self.randomness.set(rn); self.index.set(e + 1); diff --git a/chips/qemu_rv32_virt_chip/Cargo.toml b/chips/qemu_rv32_virt_chip/Cargo.toml new file mode 100644 index 0000000000..b7b3a6a87e --- /dev/null +++ b/chips/qemu_rv32_virt_chip/Cargo.toml @@ -0,0 +1,18 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[package] +name = "qemu_rv32_virt_chip" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[dependencies] +sifive = { path = "../sifive" } +virtio = { path = "../virtio" } +rv32i = { path = "../../arch/rv32i" } +kernel = { path = "../../kernel" } + +[lints] +workspace = true diff --git a/chips/qemu_rv32_virt_chip/README.md b/chips/qemu_rv32_virt_chip/README.md new file mode 100644 index 0000000000..51bb9c5948 --- /dev/null +++ b/chips/qemu_rv32_virt_chip/README.md @@ -0,0 +1,3 @@ +qemu-system-riscv32 `virt` machine chip crate +============================================= + diff --git a/chips/qemu_rv32_virt_chip/src/chip.rs b/chips/qemu_rv32_virt_chip/src/chip.rs new file mode 100644 index 0000000000..75565031ef --- /dev/null +++ b/chips/qemu_rv32_virt_chip/src/chip.rs @@ -0,0 +1,283 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! High-level setup and interrupt mapping for the chip. + +use core::fmt::Write; +use core::ptr::addr_of; + +use kernel::debug; +use kernel::hil::time::Freq10MHz; +use kernel::platform::chip::{Chip, InterruptService}; + +use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; + +use rv32i::csr::{mcause, mie::mie, mip::mip, CSR}; + +use crate::plic::PLIC; +use sifive::plic::Plic; + +use crate::interrupts; + +use virtio::transports::mmio::VirtIOMMIODevice; + +type QemuRv32VirtPMP = rv32i::pmp::PMPUserMPU< + 5, + rv32i::pmp::kernel_protection_mml_epmp::KernelProtectionMMLEPMP<16, 5>, +>; + +pub type QemuRv32VirtClint<'a> = sifive::clint::Clint<'a, Freq10MHz>; + +pub struct QemuRv32VirtChip<'a, I: InterruptService + 'a> { + userspace_kernel_boundary: rv32i::syscall::SysCall, + pmp: QemuRv32VirtPMP, + plic: &'a Plic, + timer: &'a QemuRv32VirtClint<'a>, + plic_interrupt_service: &'a I, +} + +pub struct QemuRv32VirtDefaultPeripherals<'a> { + pub uart0: crate::uart::Uart16550<'a>, + pub virtio_mmio: [VirtIOMMIODevice; 8], +} + +impl<'a> QemuRv32VirtDefaultPeripherals<'a> { + pub fn new() -> Self { + Self { + uart0: crate::uart::Uart16550::new(crate::uart::UART0_BASE), + virtio_mmio: [ + VirtIOMMIODevice::new(crate::virtio_mmio::VIRTIO_MMIO_0_BASE), + VirtIOMMIODevice::new(crate::virtio_mmio::VIRTIO_MMIO_1_BASE), + VirtIOMMIODevice::new(crate::virtio_mmio::VIRTIO_MMIO_2_BASE), + VirtIOMMIODevice::new(crate::virtio_mmio::VIRTIO_MMIO_3_BASE), + VirtIOMMIODevice::new(crate::virtio_mmio::VIRTIO_MMIO_4_BASE), + VirtIOMMIODevice::new(crate::virtio_mmio::VIRTIO_MMIO_5_BASE), + VirtIOMMIODevice::new(crate::virtio_mmio::VIRTIO_MMIO_6_BASE), + VirtIOMMIODevice::new(crate::virtio_mmio::VIRTIO_MMIO_7_BASE), + ], + } + } +} + +impl<'a> InterruptService for QemuRv32VirtDefaultPeripherals<'a> { + unsafe fn service_interrupt(&self, interrupt: u32) -> bool { + match interrupt { + interrupts::UART0 => self.uart0.handle_interrupt(), + interrupts::VIRTIO_MMIO_0 => self.virtio_mmio[0].handle_interrupt(), + interrupts::VIRTIO_MMIO_1 => self.virtio_mmio[1].handle_interrupt(), + interrupts::VIRTIO_MMIO_2 => self.virtio_mmio[2].handle_interrupt(), + interrupts::VIRTIO_MMIO_3 => self.virtio_mmio[3].handle_interrupt(), + interrupts::VIRTIO_MMIO_4 => self.virtio_mmio[4].handle_interrupt(), + interrupts::VIRTIO_MMIO_5 => self.virtio_mmio[5].handle_interrupt(), + interrupts::VIRTIO_MMIO_6 => self.virtio_mmio[6].handle_interrupt(), + interrupts::VIRTIO_MMIO_7 => self.virtio_mmio[7].handle_interrupt(), + _ => return false, + } + true + } +} + +impl<'a, I: InterruptService + 'a> QemuRv32VirtChip<'a, I> { + pub unsafe fn new( + plic_interrupt_service: &'a I, + timer: &'a QemuRv32VirtClint<'a>, + pmp: rv32i::pmp::kernel_protection_mml_epmp::KernelProtectionMMLEPMP<16, 5>, + ) -> Self { + Self { + userspace_kernel_boundary: rv32i::syscall::SysCall::new(), + pmp: rv32i::pmp::PMPUserMPU::new(pmp), + plic: &*addr_of!(PLIC), + timer, + plic_interrupt_service, + } + } + + pub unsafe fn enable_plic_interrupts(&self) { + self.plic.disable_all(); + self.plic.clear_all_pending(); + self.plic.enable_all(); + } + + unsafe fn handle_plic_interrupts(&self) { + while let Some(interrupt) = self.plic.get_saved_interrupts() { + if !self.plic_interrupt_service.service_interrupt(interrupt) { + debug!("Pidx {}", interrupt); + } + self.atomic(|| { + self.plic.complete(interrupt); + }); + } + } +} + +impl<'a, I: InterruptService + 'a> Chip for QemuRv32VirtChip<'a, I> { + type MPU = QemuRv32VirtPMP; + type UserspaceKernelBoundary = rv32i::syscall::SysCall; + + fn mpu(&self) -> &Self::MPU { + &self.pmp + } + + fn userspace_kernel_boundary(&self) -> &rv32i::syscall::SysCall { + &self.userspace_kernel_boundary + } + + fn service_pending_interrupts(&self) { + loop { + let mip = CSR.mip.extract(); + + if mip.is_set(mip::mtimer) { + self.timer.handle_interrupt(); + } + if self.plic.get_saved_interrupts().is_some() { + unsafe { + self.handle_plic_interrupts(); + } + } + + if !mip.any_matching_bits_set(mip::mtimer::SET) + && self.plic.get_saved_interrupts().is_none() + { + break; + } + } + + // Re-enable all MIE interrupts that we care about. Since we looped + // until we handled them all, we can re-enable all of them. + CSR.mie.modify(mie::mext::SET + mie::mtimer::SET); + } + + fn has_pending_interrupts(&self) -> bool { + // First check if the global machine timer interrupt is set. + // We would also need to check for additional global interrupt bits + // if there were to be used for anything in the future. + if CSR.mip.is_set(mip::mtimer) { + return true; + } + + // Then we can check the PLIC. + self.plic.get_saved_interrupts().is_some() + } + + fn sleep(&self) { + unsafe { + rv32i::support::wfi(); + } + } + + unsafe fn atomic(&self, f: F) -> R + where + F: FnOnce() -> R, + { + rv32i::support::atomic(f) + } + + unsafe fn print_state(&self, writer: &mut dyn Write) { + rv32i::print_riscv_state(writer); + let _ = writer.write_fmt(format_args!("{}", self.pmp.pmp)); + } +} + +fn handle_exception(exception: mcause::Exception) { + match exception { + mcause::Exception::UserEnvCall | mcause::Exception::SupervisorEnvCall => (), + + mcause::Exception::InstructionMisaligned + | mcause::Exception::InstructionFault + | mcause::Exception::IllegalInstruction + | mcause::Exception::Breakpoint + | mcause::Exception::LoadMisaligned + | mcause::Exception::LoadFault + | mcause::Exception::StoreMisaligned + | mcause::Exception::StoreFault + | mcause::Exception::MachineEnvCall + | mcause::Exception::InstructionPageFault + | mcause::Exception::LoadPageFault + | mcause::Exception::StorePageFault + | mcause::Exception::Unknown => { + panic!("fatal exception"); + } + } +} + +unsafe fn handle_interrupt(intr: mcause::Interrupt) { + match intr { + mcause::Interrupt::UserSoft + | mcause::Interrupt::UserTimer + | mcause::Interrupt::UserExternal => { + panic!("unexpected user-mode interrupt"); + } + mcause::Interrupt::SupervisorExternal + | mcause::Interrupt::SupervisorTimer + | mcause::Interrupt::SupervisorSoft => { + panic!("unexpected supervisor-mode interrupt"); + } + + mcause::Interrupt::MachineSoft => { + CSR.mie.modify(mie::msoft::CLEAR); + } + mcause::Interrupt::MachineTimer => { + CSR.mie.modify(mie::mtimer::CLEAR); + } + mcause::Interrupt::MachineExternal => { + // We received an interrupt, disable interrupts while we handle them + CSR.mie.modify(mie::mext::CLEAR); + + // Claim the interrupt, unwrap() as we know an interrupt exists + // Once claimed this interrupt won't fire until it's completed + // NOTE: The interrupt is no longer pending in the PLIC + loop { + let interrupt = PLIC.next_pending(); + + match interrupt { + Some(irq) => { + // Safe as interrupts are disabled + PLIC.save_interrupt(irq); + } + None => { + // Enable generic interrupts + CSR.mie.modify(mie::mext::SET); + + break; + } + } + } + } + + mcause::Interrupt::Unknown => { + panic!("interrupt of unknown cause"); + } + } +} + +/// Trap handler for board/chip specific code. +/// +/// For the qemu-system-riscv32 virt machine this gets called when an +/// interrupt occurs while the chip is in kernel mode. +#[export_name = "_start_trap_rust_from_kernel"] +pub unsafe extern "C" fn start_trap_rust() { + match mcause::Trap::from(CSR.mcause.extract()) { + mcause::Trap::Interrupt(interrupt) => { + handle_interrupt(interrupt); + } + mcause::Trap::Exception(exception) => { + handle_exception(exception); + } + } +} + +/// Function that gets called if an interrupt occurs while an app was running. +/// mcause is passed in, and this function should correctly handle disabling the +/// interrupt that fired so that it does not trigger again. +#[export_name = "_disable_interrupt_trap_rust_from_app"] +pub unsafe extern "C" fn disable_interrupt_trap_handler(mcause_val: u32) { + match mcause::Trap::from(mcause_val as usize) { + mcause::Trap::Interrupt(interrupt) => { + handle_interrupt(interrupt); + } + _ => { + panic!("unexpected non-interrupt\n"); + } + } +} diff --git a/chips/qemu_rv32_virt_chip/src/clint.rs b/chips/qemu_rv32_virt_chip/src/clint.rs new file mode 100644 index 0000000000..f4a08112e9 --- /dev/null +++ b/chips/qemu_rv32_virt_chip/src/clint.rs @@ -0,0 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Machine Timer instantiation. + +use kernel::utilities::StaticRef; +use sifive::clint::ClintRegisters; + +pub const CLINT_BASE: StaticRef = + unsafe { StaticRef::new(0x0200_0000 as *const ClintRegisters) }; diff --git a/chips/qemu_rv32_virt_chip/src/interrupts.rs b/chips/qemu_rv32_virt_chip/src/interrupts.rs new file mode 100644 index 0000000000..6249b941c0 --- /dev/null +++ b/chips/qemu_rv32_virt_chip/src/interrupts.rs @@ -0,0 +1,19 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Named interrupts for the qemu-system-riscv32 virt machine. + +#![allow(dead_code)] + +pub const VIRTIO_MMIO_0: u32 = 1; +pub const VIRTIO_MMIO_1: u32 = 2; +pub const VIRTIO_MMIO_2: u32 = 3; +pub const VIRTIO_MMIO_3: u32 = 4; +pub const VIRTIO_MMIO_4: u32 = 5; +pub const VIRTIO_MMIO_5: u32 = 6; +pub const VIRTIO_MMIO_6: u32 = 7; +pub const VIRTIO_MMIO_7: u32 = 8; + +pub const UART0: u32 = 10; +pub const RTC: u32 = 11; diff --git a/chips/qemu_rv32_virt_chip/src/lib.rs b/chips/qemu_rv32_virt_chip/src/lib.rs new file mode 100644 index 0000000000..111252db67 --- /dev/null +++ b/chips/qemu_rv32_virt_chip/src/lib.rs @@ -0,0 +1,19 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Chip support for the qemu-system-riscv32 virt machine + +#![no_std] +#![crate_name = "qemu_rv32_virt_chip"] +#![crate_type = "rlib"] + +pub use virtio; + +mod interrupts; +pub mod virtio_mmio; + +pub mod chip; +pub mod clint; +pub mod plic; +pub mod uart; diff --git a/chips/qemu_rv32_virt_chip/src/plic.rs b/chips/qemu_rv32_virt_chip/src/plic.rs new file mode 100644 index 0000000000..e903e9d99d --- /dev/null +++ b/chips/qemu_rv32_virt_chip/src/plic.rs @@ -0,0 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Instantiation of the sifive Platform Level Interrupt Controller + +use kernel::utilities::StaticRef; +use sifive::plic::{Plic, PlicRegisters}; + +pub const PLIC_BASE: StaticRef = + unsafe { StaticRef::new(0x0c00_0000 as *const PlicRegisters) }; + +pub static mut PLIC: Plic = Plic::new(PLIC_BASE); diff --git a/chips/qemu_rv32_virt_chip/src/uart.rs b/chips/qemu_rv32_virt_chip/src/uart.rs new file mode 100644 index 0000000000..fc9315497e --- /dev/null +++ b/chips/qemu_rv32_virt_chip/src/uart.rs @@ -0,0 +1,512 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! QEMU's memory mapped 16550 UART + +use core::cell::Cell; + +use kernel::hil; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; +use kernel::utilities::registers::{ + register_bitfields, Aliased, Field, InMemoryRegister, ReadOnly, ReadWrite, +}; +use kernel::utilities::StaticRef; +use kernel::ErrorCode; + +pub const UART0_BASE: StaticRef = + unsafe { StaticRef::new(0x1000_0000 as *const Uart16550Registers) }; + +pub const UART_16550_BAUD_BASE: usize = 399193; + +type Uart16550RegshiftInt = u8; + +#[repr(C)] +pub struct Uart16550Registers { + /// 0x00: + /// - DLAB = 0 + /// - Read: receiver buffer (RBR) + /// - Write: transmitter holding (THR) + /// - DLAB = 1: divisor latch LSB (DLL) + rbr_thr: Aliased, + + /// 0x01: + /// - DLAB = 0: interrupt enable (IER) + /// - DLAB = 1: divisor latch MSB (DLM) + ier: ReadWrite, + + /// 0x02: + /// - Read: interrupt identification (IIR) + /// - Write: FIFO control (FCR) + iir_fcr: Aliased, + + /// 0x03: line control (LCR) + lcr: ReadWrite, + + /// 0x04: modem control (MCR) + mcr: ReadWrite, + + /// 0x05: line status (LSR) + lsr: ReadOnly, + + /// 0x06: modem status (MSR) + msr: ReadOnly, +} + +impl Uart16550Registers { + /// Access the DLL and DLM (divisor latch LSB and MSB) registers + /// + /// Setting the 7th bit of the line control register (LCR) changes + /// the RBR/THR and IER to be the DLL and DLM register + /// respectively. + /// + /// This function takes care of latching the registers and calling + /// a closure in which this virtual register can be + /// accessed. Prior to calling the closure, the register is + /// latched back and the closure is instead provided with an + /// in-memory copy of the register, which is then written back to + /// DLL and DLM. + /// + /// This provides a safe way to access the DLL and DLM + /// registers. + fn divisor_latch_reg(&self, f: F) -> R + where + F: FnOnce(&InMemoryRegister) -> R, + { + let dlab_field = Field::::new(1, 7); + + // Set DLAB = 1 + self.lcr.modify(dlab_field.val(1)); + + // Read the old values of rbr_thr and ier and combine them + // into a u16 + let old_val = u16::from_be_bytes([self.ier.get(), self.rbr_thr.get()]); + + // Set DLAB = 0 prior to handing back to the caller + self.lcr.modify(dlab_field.val(0)); + + let dlr = InMemoryRegister::::new(old_val); + let ret = f(&dlr); + + // Get the bytes from the modified value + let [new_ier, new_rbr_thr] = u16::to_be_bytes(dlr.get()); + + // Set DLAB = 1 + self.lcr.modify(dlab_field.val(1)); + + // Write the modified value back to the registers + self.ier.set(new_ier as Uart16550RegshiftInt); + self.rbr_thr.set(new_rbr_thr as Uart16550RegshiftInt); + + // Set DLAB = 0 + self.lcr.modify(dlab_field.val(0)); + + ret + } +} + +register_bitfields![u8, + RBR [ + Data OFFSET(0) NUMBITS(8) [], + ], + THR [ + Data OFFSET(0) NUMBITS(8) [], + ], + IER [ + ModemStatusRegisterChange OFFSET(3) NUMBITS(1) [], + ReceiverLineStatusRegisterChange OFFSET(2) NUMBITS(1) [], + TransmitterHoldingRegisterEmpty OFFSET(1) NUMBITS(1) [], + ReceivedDataAvailable OFFSET(0) NUMBITS(1) [], + ], + IIR [ + FIFO OFFSET(6) NUMBITS(2) [ + NoFIFO = 0, + UnusableFIFO = 2, + FIFOEnabled = 3, + ], + Identification OFFSET(1) NUMBITS(3) [ + ModemStatusChange = 0, + TransmitterHoldingRegisterEmpty = 1, + ReceiveDataAvailable = 2, + LineStatusChange = 3, + CharacterTimeout = 6, + ], + Pending OFFSET(0) NUMBITS(1) [ + Pending = 0, + NotPending = 1, + ], + ], + FCR [ + ReceiveFIFOInterruptTriggerLevel OFFSET(6) NUMBITS(2) [ + Bytes1 = 0, + Bytes4 = 1, + Bytes8 = 2, + Bytes14 = 3, + ], + DMAMode OFFSET(3) NUMBITS(1) [ + Mode0 = 0, + Mode1 = 1, + ], + ClearTransmitFIFO OFFSET(2) NUMBITS(1) [], + ClearReceiveFIFO OFFSET(1) NUMBITS(1) [], + Enable OFFSET(0) NUMBITS(1) [], + ], + LCR [ + BreakSignal OFFSET(6) NUMBITS(1) [], + ParityMode OFFSET(4) NUMBITS(2) [ + Odd = 0, + Even = 1, + High = 2, + Low = 3, + ], + Parity OFFSET(3) NUMBITS(1) [], + StopBits OFFSET(2) NUMBITS(1) [ + One = 0, + OneHalfTwo = 1, + ], + DataWordLength OFFSET(0) NUMBITS(2) [ + Bits5 = 0, + Bits6 = 1, + Bits7 = 2, + Bits8 = 3, + ], + ], + MCR [ + LoopbackMode OFFSET(4) NUMBITS(1) [], + AuxiliaryOutput2 OFFSET(3) NUMBITS(1) [], + AuxiliaryOutput1 OFFSET(2) NUMBITS(1) [], + RequestToSend OFFSET(1) NUMBITS(1) [], + DataTerminalReady OFFSET(0) NUMBITS(1) [], + ], + LSR [ + ErronousDataInFIFO OFFSET(7) NUMBITS(1) [], + THREmptyLineIdle OFFSET(6) NUMBITS(1) [], + THREmpty OFFSET(5) NUMBITS(1) [], + BreakSignalReceived OFFSET(4) NUMBITS(1) [], + FramingError OFFSET(3) NUMBITS(1) [], + ParityError OFFSET(2) NUMBITS(1) [], + OverrunError OFFSET(1) NUMBITS(1) [], + DataAvailable OFFSET(0) NUMBITS(1) [], + ], + MSR [ + CarrierDetect OFFSET(7) NUMBITS(1) [], + RingIndicator OFFSET(6) NUMBITS(1) [], + DataSetReady OFFSET(5) NUMBITS(1) [], + ClearToSend OFFSET(4) NUMBITS(1) [], + ChangeInCarrierDetect OFFSET(3) NUMBITS(1) [], + TrailingEdgeRingIndicator OFFSET(2) NUMBITS(1) [], + ChangeInDataSetReady OFFSET(1) NUMBITS(1) [], + ChangeInClearToSend OFFSET(0) NUMBITS(1) [], + ], +]; + +register_bitfields![u16, + DLR [ + Divisor OFFSET(0) NUMBITS(16) [], + ], +]; + +pub struct Uart16550<'a> { + regs: StaticRef, + tx_client: OptionalCell<&'a dyn hil::uart::TransmitClient>, + rx_client: OptionalCell<&'a dyn hil::uart::ReceiveClient>, + tx_buffer: TakeCell<'static, [u8]>, + tx_len: Cell, + tx_index: Cell, + rx_buffer: TakeCell<'static, [u8]>, + rx_len: Cell, + rx_index: Cell, +} + +impl<'a> Uart16550<'a> { + pub fn new(regs: StaticRef) -> Uart16550<'a> { + // Disable all interrupts when constructing the UART + regs.ier.set(0xF); + + regs.iir_fcr.write(FCR::Enable::CLEAR); + + Uart16550 { + regs, + tx_client: OptionalCell::empty(), + rx_client: OptionalCell::empty(), + tx_buffer: TakeCell::empty(), + tx_len: Cell::new(0), + tx_index: Cell::new(0), + rx_buffer: TakeCell::empty(), + rx_len: Cell::new(0), + rx_index: Cell::new(0), + } + } +} + +impl<'a> Uart16550<'a> { + pub fn handle_interrupt(&self) { + // Currently we can only receive a tx interrupt, however we + // need to check the interrupt cause nonetheless as this will + // clear the TX interrupt bit + let iir = self.regs.iir_fcr.extract(); + + // Check if the register contained a valid interrupt at all + if !iir.matches_all(IIR::Pending::Pending) { + panic!("UART 16550: interrupt without interrupt"); + } + + // Check whether there is space for new data + if iir.matches_all(IIR::Identification::TransmitterHoldingRegisterEmpty) { + // The respective interrupt has already been cleared by + // the extraction of IIR + + // We also check whether the tx_buffer is set, as we + // could've generated the interrupt by transmit_sync + if self.tx_buffer.is_some() { + self.transmit_continue(); + } + } + // Check whether we've received some new data. + else if iir.matches_all(IIR::Identification::ReceiveDataAvailable) { + self.receive(); + } + // We don't care about MSC interrupts, but have to ack the + // interrupt by reading MSR + else if iir.matches_all(IIR::Identification::ModemStatusChange) { + let _ = self.regs.msr.get(); + } + // We don't care about LSC interrupts, but have to ack the + // interrupt by reading LSR + else if iir.matches_all(IIR::Identification::LineStatusChange) { + let _ = self.regs.lsr.get(); + } + // We con't care about character timeout interrupts, but CAN'T + // SUPPRESS THEM and have to ack by reading RBR + else if iir.matches_all(IIR::Identification::CharacterTimeout) { + let _ = self.regs.rbr_thr.get(); + } + // All other interrupt sources are unknown, panic if we see + // them + else { + panic!("UART 16550: unknown interrupt"); + } + } + + /// Blocking transmit + /// + /// This function will transmit the passed slice in a blocking + /// fashing, returning when finished. + /// + /// The current device configuration is used, and the device must + /// be enabled. Otherwise, this function may block indefinitely. + pub fn transmit_sync(&self, bytes: &[u8]) { + // We don't want to cause excessive interrupts here, so + // disable transmit interrupts temporarily + let prev_ier = self.regs.ier.extract(); + if prev_ier.is_set(IER::TransmitterHoldingRegisterEmpty) { + self.regs + .ier + .modify_no_read(prev_ier, IER::TransmitterHoldingRegisterEmpty::CLEAR); + } + + for byte in bytes.iter() { + while !self.regs.lsr.is_set(LSR::THREmpty) {} + self.regs.rbr_thr.write(THR::Data.val(*byte)); + } + + // Restore the IER register to its original state + self.regs.ier.set(prev_ier.get()); + } + + fn transmit_continue(&self) { + // This should only be called as a result of a transmit + // interrupt + + // Get the current transmission information + let mut index = self.tx_index.get(); + let tx_data = self.tx_buffer.take().expect("UART 16550: no tx buffer"); + + if index < self.tx_len.get() { + // Still data to send + while index < self.tx_len.get() && self.regs.lsr.is_set(LSR::THREmpty) { + self.regs.rbr_thr.write(THR::Data.val(tx_data[index])); + index += 1; + } + + // Put the updated index and buffer back, wait for an + // interrupt + self.tx_index.set(index); + self.tx_buffer.replace(tx_data); + } else { + // We are finished, disable tx interrupts + self.regs + .ier + .modify(IER::TransmitterHoldingRegisterEmpty::CLEAR); + + // Callback to the client + self.tx_client + .map(move |client| client.transmitted_buffer(tx_data, self.tx_len.get(), Ok(()))); + } + } + + fn receive(&self) { + // Receive interrupts must only be enabled when we're currently holding + // a buffer to receive data into: + let rx_buffer = self.rx_buffer.take().expect("UART 16550: no rx buffer"); + let len = self.rx_len.get(); + let mut index = self.rx_index.get(); + + // Read in a while loop, until no more data in the FIFO + while self.regs.lsr.is_set(LSR::DataAvailable) && index < len { + rx_buffer[index] = self.regs.rbr_thr.get(); + index += 1; + } + + // Check whether we've read sufficient data: + if index == len { + // We're done, disable interrupts and return to the client: + self.regs.ier.modify(IER::ReceivedDataAvailable::CLEAR); + + self.rx_client.map(move |client| { + client.received_buffer(rx_buffer, len, Ok(()), hil::uart::Error::None) + }); + } else { + // Store the new index and place the buffer back: + self.rx_index.set(index); + self.rx_buffer.replace(rx_buffer); + } + } +} + +impl hil::uart::Configure for Uart16550<'_> { + fn configure(&self, params: hil::uart::Parameters) -> Result<(), ErrorCode> { + use hil::uart::{Parity, StopBits, Width}; + + // 16550 operates at a default frequency of 115200. Dividing + // this by the target frequency gives the divisor register + // contents. + let divisor: u16 = (115_200 / params.baud_rate) as u16; + self.regs.divisor_latch_reg(|dlr| { + dlr.set(divisor); + }); + + let mut lcr = self.regs.lcr.extract(); + + match params.width { + Width::Six => lcr.modify(LCR::DataWordLength::Bits6), + Width::Seven => lcr.modify(LCR::DataWordLength::Bits7), + Width::Eight => lcr.modify(LCR::DataWordLength::Bits8), + }; + + match params.stop_bits { + StopBits::One => LCR::StopBits::One, + // 1.5 stop bits for 5bit works, 2 stop bits for 6-8 bit + // words. We only support 6-8 bit words, so this + // configures 2 stop bits + StopBits::Two => LCR::StopBits::OneHalfTwo, + }; + + match params.parity { + Parity::None => lcr.modify(LCR::Parity.val(0b000)), + Parity::Odd => lcr.modify(LCR::Parity.val(0b001)), + Parity::Even => lcr.modify(LCR::Parity.val(0b011)), + }; + + match params.hw_flow_control { + true => lcr.modify(LCR::BreakSignal::SET), + false => lcr.modify(LCR::BreakSignal::CLEAR), + }; + + self.regs.lcr.set(lcr.get()); + + Ok(()) + } +} + +impl<'a> hil::uart::Transmit<'a> for Uart16550<'a> { + fn set_transmit_client(&self, client: &'a dyn hil::uart::TransmitClient) { + self.tx_client.set(client); + } + + fn transmit_buffer( + &self, + tx_data: &'static mut [u8], + tx_len: usize, + ) -> Result<(), (ErrorCode, &'static mut [u8])> { + if tx_len > tx_data.len() { + return Err((ErrorCode::INVAL, tx_data)); + } + + if self.tx_buffer.is_some() { + return Err((ErrorCode::BUSY, tx_data)); + } + + // Enable interrupts for the transmitter holding register + // being empty such that we can callback to the client + self.regs + .ier + .modify(IER::TransmitterHoldingRegisterEmpty::SET); + + // Start transmitting the first data word(s) already + let mut index = 0; + while index < tx_len && self.regs.lsr.is_set(LSR::THREmpty) { + self.regs.rbr_thr.write(THR::Data.val(tx_data[index])); + index += 1; + } + + // Store the required buffer and information for the interrupt + // handler + self.tx_buffer.replace(tx_data); + self.tx_len.set(tx_len); + self.tx_index.set(index); + + Ok(()) + } + + fn transmit_abort(&self) -> Result<(), ErrorCode> { + Err(ErrorCode::FAIL) + } + + fn transmit_word(&self, _word: u32) -> Result<(), ErrorCode> { + Err(ErrorCode::FAIL) + } +} + +impl<'a> hil::uart::Receive<'a> for Uart16550<'a> { + fn set_receive_client(&self, client: &'a dyn hil::uart::ReceiveClient) { + self.rx_client.set(client); + } + + fn receive_buffer( + &self, + rx_buffer: &'static mut [u8], + rx_len: usize, + ) -> Result<(), (ErrorCode, &'static mut [u8])> { + // Ensure the provided buffer holds at least `rx_len` bytes, and + // `rx_len` is strictly positive (otherwise we'd need to use deferred + // calls): + if rx_buffer.len() < rx_len && rx_len > 0 { + return Err((ErrorCode::SIZE, rx_buffer)); + } + + // Store the receive buffer and byte count. We cannot call into the + // generic receive routine here, as the client callback needs to be + // called from another call stack. Hence simply enable interrupts here. + self.rx_buffer.replace(rx_buffer); + self.rx_len.set(rx_len); + self.rx_index.set(0); + + // Enable receive interrupts: + self.regs.ier.modify(IER::ReceivedDataAvailable::SET); + + Ok(()) + } + + fn receive_abort(&self) -> Result<(), ErrorCode> { + // Currently unsupported as we'd like to avoid using deferred + // calls. Needs to be migrated to the new UART HIL anyways. + Err(ErrorCode::FAIL) + } + + fn receive_word(&self) -> Result<(), ErrorCode> { + // Currently unsupported. + Err(ErrorCode::FAIL) + } +} diff --git a/chips/qemu_rv32_virt_chip/src/virtio_mmio.rs b/chips/qemu_rv32_virt_chip/src/virtio_mmio.rs new file mode 100644 index 0000000000..fb185b6190 --- /dev/null +++ b/chips/qemu_rv32_virt_chip/src/virtio_mmio.rs @@ -0,0 +1,25 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! QEMU VirtIO MMIO instantiation + +use kernel::utilities::StaticRef; +use virtio::transports::mmio::VirtIOMMIODeviceRegisters; + +pub const VIRTIO_MMIO_0_BASE: StaticRef = + unsafe { StaticRef::new(0x1000_1000 as *const VirtIOMMIODeviceRegisters) }; +pub const VIRTIO_MMIO_1_BASE: StaticRef = + unsafe { StaticRef::new(0x1000_2000 as *const VirtIOMMIODeviceRegisters) }; +pub const VIRTIO_MMIO_2_BASE: StaticRef = + unsafe { StaticRef::new(0x1000_3000 as *const VirtIOMMIODeviceRegisters) }; +pub const VIRTIO_MMIO_3_BASE: StaticRef = + unsafe { StaticRef::new(0x1000_4000 as *const VirtIOMMIODeviceRegisters) }; +pub const VIRTIO_MMIO_4_BASE: StaticRef = + unsafe { StaticRef::new(0x1000_5000 as *const VirtIOMMIODeviceRegisters) }; +pub const VIRTIO_MMIO_5_BASE: StaticRef = + unsafe { StaticRef::new(0x1000_6000 as *const VirtIOMMIODeviceRegisters) }; +pub const VIRTIO_MMIO_6_BASE: StaticRef = + unsafe { StaticRef::new(0x1000_7000 as *const VirtIOMMIODeviceRegisters) }; +pub const VIRTIO_MMIO_7_BASE: StaticRef = + unsafe { StaticRef::new(0x1000_8000 as *const VirtIOMMIODeviceRegisters) }; diff --git a/chips/rp2040/Cargo.toml b/chips/rp2040/Cargo.toml index 935ef297b2..adaa745795 100644 --- a/chips/rp2040/Cargo.toml +++ b/chips/rp2040/Cargo.toml @@ -1,12 +1,17 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "rp2040" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] cortexm0p = { path="../../arch/cortex-m0p" } kernel = { path = "../../kernel" } enum_primitive = { path = "../../libraries/enum_primitive" } - +[lints] +workspace = true diff --git a/chips/rp2040/src/adc.rs b/chips/rp2040/src/adc.rs index 17b351006b..d79072b324 100644 --- a/chips/rp2040/src/adc.rs +++ b/chips/rp2040/src/adc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::cell::Cell; use kernel::hil; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; @@ -137,14 +141,14 @@ enum ADCStatus { OneSample, } -pub struct Adc { +pub struct Adc<'a> { registers: StaticRef, status: Cell, channel: Cell, - client: OptionalCell<&'static dyn hil::adc::Client>, + client: OptionalCell<&'a dyn hil::adc::Client>, } -impl Adc { +impl<'a> Adc<'a> { pub const fn new() -> Self { Self { registers: ADC_BASE, @@ -182,13 +186,13 @@ impl Adc { } self.client.map(|client| { self.disable_interrupt(); - client.sample_ready(self.registers.fifo.read(FIFO::VAL) as u16) + client.sample_ready((self.registers.fifo.read(FIFO::VAL) << 4) as u16) }); } } } -impl hil::adc::Adc for Adc { +impl<'a> hil::adc::Adc<'a> for Adc<'a> { type Channel = Channel; fn sample(&self, channel: &Self::Channel) -> Result<(), ErrorCode> { @@ -201,7 +205,7 @@ impl hil::adc::Adc for Adc { self.registers.cs.modify(CS::AINSEL.val(*channel as u32)); self.registers .fcs - .modify(FCS::THRESH.val(1 as u32) + FCS::EN::SET); + .modify(FCS::THRESH.val(1_u32) + FCS::EN::SET); self.enable_interrupt(); self.registers.cs.modify(CS::START_ONCE::SET); Ok(()) @@ -230,7 +234,7 @@ impl hil::adc::Adc for Adc { Some(3300) } - fn set_client(&self, client: &'static dyn hil::adc::Client) { + fn set_client(&self, client: &'a dyn hil::adc::Client) { self.client.set(client); } } diff --git a/chips/rp2040/src/chip.rs b/chips/rp2040/src/chip.rs index 5c69efb883..86233e274a 100644 --- a/chips/rp2040/src/chip.rs +++ b/chips/rp2040/src/chip.rs @@ -1,23 +1,29 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Chip trait setup. use core::fmt::Write; -use kernel::deferred_call; use kernel::platform::chip::Chip; use kernel::platform::chip::InterruptService; use crate::adc; use crate::clocks::Clocks; -use crate::gpio::{RPPins, SIO}; +use crate::gpio::{RPGpio, RPPins, SIO}; use crate::i2c; use crate::interrupts; +use crate::pwm; use crate::resets::Resets; +use crate::rtc; use crate::spi; use crate::sysinfo; use crate::timer::RPTimer; use crate::uart::Uart; +use crate::usb; use crate::watchdog::Watchdog; use crate::xosc::Xosc; -use cortexm0p::interrupt_mask; +use cortexm0p::{interrupt_mask, CortexM0P, CortexMVariant}; #[repr(u8)] pub enum Processor { @@ -25,7 +31,7 @@ pub enum Processor { Processor1 = 1, } -pub struct Rp2040<'a, I: InterruptService<()> + 'a> { +pub struct Rp2040<'a, I: InterruptService + 'a> { mpu: cortexm0p::mpu::MPU, userspace_kernel_boundary: cortexm0p::syscall::SysCall, interrupt_service: &'a I, @@ -34,20 +40,20 @@ pub struct Rp2040<'a, I: InterruptService<()> + 'a> { processor1_interrupt_mask: (u128, u128), } -impl<'a, I: InterruptService<()>> Rp2040<'a, I> { +impl<'a, I: InterruptService> Rp2040<'a, I> { pub unsafe fn new(interrupt_service: &'a I, sio: &'a SIO) -> Self { Self { mpu: cortexm0p::mpu::MPU::new(), userspace_kernel_boundary: cortexm0p::syscall::SysCall::new(), interrupt_service, - sio: sio, + sio, processor0_interrupt_mask: interrupt_mask!(interrupts::SIO_IRQ_PROC1), processor1_interrupt_mask: interrupt_mask!(interrupts::SIO_IRQ_PROC0), } } } -impl<'a, I: InterruptService<()>> Chip for Rp2040<'a, I> { +impl<'a, I: InterruptService> Chip for Rp2040<'a, I> { type MPU = cortexm0p::mpu::MPU; type UserspaceKernelBoundary = cortexm0p::syscall::SysCall; @@ -83,7 +89,7 @@ impl<'a, I: InterruptService<()>> Chip for Rp2040<'a, I> { Processor::Processor0 => self.processor0_interrupt_mask, Processor::Processor1 => self.processor1_interrupt_mask, }; - unsafe { cortexm0p::nvic::has_pending_with_mask(mask) || deferred_call::has_tasks() } + unsafe { cortexm0p::nvic::has_pending_with_mask(mask) } } fn mpu(&self) -> &Self::MPU { @@ -108,51 +114,66 @@ impl<'a, I: InterruptService<()>> Chip for Rp2040<'a, I> { } unsafe fn print_state(&self, writer: &mut dyn Write) { - cortexm0p::print_cortexm0_state(writer); + CortexM0P::print_cortexm_state(writer); } } pub struct Rp2040DefaultPeripherals<'a> { - pub resets: Resets, - pub sio: SIO, + pub adc: adc::Adc<'a>, pub clocks: Clocks, - pub xosc: Xosc, - pub timer: RPTimer<'a>, - pub watchdog: Watchdog, + pub i2c0: i2c::I2c<'a, 'a>, pub pins: RPPins<'a>, - pub uart0: Uart<'a>, - pub adc: adc::Adc, + pub pwm: pwm::Pwm<'a>, + pub resets: Resets, + pub sio: SIO, pub spi0: spi::Spi<'a>, pub sysinfo: sysinfo::SysInfo, - pub i2c0: i2c::I2c<'a>, + pub timer: RPTimer<'a>, + pub uart0: Uart<'a>, + pub uart1: Uart<'a>, + pub usb: usb::UsbCtrl<'a>, + pub watchdog: Watchdog<'a>, + pub xosc: Xosc, + pub rtc: rtc::Rtc<'a>, } impl<'a> Rp2040DefaultPeripherals<'a> { - pub const fn new() -> Self { + pub fn new() -> Self { Self { - resets: Resets::new(), - sio: SIO::new(), + adc: adc::Adc::new(), clocks: Clocks::new(), - xosc: Xosc::new(), - timer: RPTimer::new(), - watchdog: Watchdog::new(), + i2c0: i2c::I2c::new_i2c0(), pins: RPPins::new(), - uart0: Uart::new_uart0(), - adc: adc::Adc::new(), + pwm: pwm::Pwm::new(), + resets: Resets::new(), + sio: SIO::new(), spi0: spi::Spi::new_spi0(), sysinfo: sysinfo::SysInfo::new(), - i2c0: i2c::I2c::new_i2c0(), + timer: RPTimer::new(), + uart0: Uart::new_uart0(), + uart1: Uart::new_uart1(), + usb: usb::UsbCtrl::new(), + watchdog: Watchdog::new(), + xosc: Xosc::new(), + rtc: rtc::Rtc::new(), } } - pub fn resolve_dependencies(&'a self) { + pub fn resolve_dependencies(&'static self) { + self.pwm.set_clocks(&self.clocks); + self.watchdog.resolve_dependencies(&self.resets); self.spi0.set_clocks(&self.clocks); self.uart0.set_clocks(&self.clocks); + kernel::deferred_call::DeferredCallClient::register(&self.uart0); + kernel::deferred_call::DeferredCallClient::register(&self.uart1); + kernel::deferred_call::DeferredCallClient::register(&self.rtc); self.i2c0.resolve_dependencies(&self.clocks, &self.resets); + self.usb.set_gpio(self.pins.get_pin(RPGpio::GPIO15)); + self.rtc.set_clocks(&self.clocks); } } -impl InterruptService<()> for Rp2040DefaultPeripherals<'_> { +impl InterruptService for Rp2040DefaultPeripherals<'_> { unsafe fn service_interrupt(&self, interrupt: u32) -> bool { match interrupt { interrupts::TIMER_IRQ_0 => { @@ -179,19 +200,27 @@ impl InterruptService<()> for Rp2040DefaultPeripherals<'_> { self.adc.handle_interrupt(); true } + interrupts::USBCTRL_IRQ => { + self.usb.handle_interrupt(); + true + } interrupts::IO_IRQ_BANK0 => { self.pins.handle_interrupt(); true } + interrupts::I2C0_IRQ => { self.i2c0.handle_interrupt(); true } + interrupts::PWM_IRQ_WRAP => { + // As the PWM HIL doesn't provide any support for interrupts, they are + // simply ignored. + // + // Note that PWM interrupts are raised only during unit tests. + true + } _ => false, } } - - unsafe fn service_deferred_call(&self, _task: ()) -> bool { - false - } } diff --git a/chips/rp2040/src/clocks.rs b/chips/rp2040/src/clocks.rs index 75b928e82d..6967be34ef 100644 --- a/chips/rp2040/src/clocks.rs +++ b/chips/rp2040/src/clocks.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::cell::Cell; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; use kernel::utilities::registers::{register_bitfields, register_structs, ReadOnly, ReadWrite}; @@ -1038,12 +1042,13 @@ impl Clocks { (((source_freq as u64) << 8) / freq as u64) as u32 } + #[cfg(all(target_arch = "arm", target_os = "none"))] #[inline] fn loop_3_cycles(&self, clock: Clock) { if self.get_frequency(clock) > 0 { let _delay_cyc: u32 = self.get_frequency(Clock::System) / self.get_frequency(clock) + 1; - #[cfg(target_arch = "arm")] unsafe { + use core::arch::asm; asm! ( "1:", "subs {0}, #1", @@ -1054,6 +1059,11 @@ impl Clocks { } } + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + fn loop_3_cycles(&self, _clock: Clock) { + unimplemented!() + } + pub fn configure_gpio_out( &self, clock: Clock, diff --git a/chips/stm32f4xx/src/deferred_calls.rs b/chips/rp2040/src/deferred_calls.rs similarity index 54% rename from chips/stm32f4xx/src/deferred_calls.rs rename to chips/rp2040/src/deferred_calls.rs index 8a934f39bc..b8317127cb 100644 --- a/chips/stm32f4xx/src/deferred_calls.rs +++ b/chips/rp2040/src/deferred_calls.rs @@ -1,15 +1,17 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Definition of Deferred Call tasks. //! //! Deferred calls also peripheral drivers to register pseudo interrupts. //! These are the definitions of which deferred calls this chip needs. -use core::convert::Into; -use core::convert::TryFrom; - /// A type of task to defer a call for #[derive(Copy, Clone)] pub enum DeferredCallTask { - Fsmc = 0, + DateTimeGet = 0, + DateTimeSet = 1, } impl TryFrom for DeferredCallTask { @@ -17,14 +19,15 @@ impl TryFrom for DeferredCallTask { fn try_from(value: usize) -> Result { match value { - 0 => Ok(DeferredCallTask::Fsmc), + 0 => Ok(DeferredCallTask::DateTimeGet), + 1 => Ok(DeferredCallTask::DateTimeSet), _ => Err(()), } } } -impl Into for DeferredCallTask { - fn into(self) -> usize { - self as usize +impl From for usize { + fn from(val: DeferredCallTask) -> Self { + val as usize } } diff --git a/chips/rp2040/src/gpio.rs b/chips/rp2040/src/gpio.rs index 47b42e5b74..4eecfb9360 100644 --- a/chips/rp2040/src/gpio.rs +++ b/chips/rp2040/src/gpio.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! GPIO, RP2040 //! //! ### Author @@ -343,13 +347,13 @@ impl<'a> RPPins<'a> { let l_low_reg_no = pin * 4; if (current_val & enabled_val & (1 << l_low_reg_no)) != 0 { self.pins[pin + bank_no * 8].handle_interrupt(); - } else if (current_val & enabled_val & (1 << l_low_reg_no + 1)) != 0 { + } else if (current_val & enabled_val & (1 << (l_low_reg_no + 1))) != 0 { self.pins[pin + bank_no * 8].handle_interrupt(); - } else if (current_val & enabled_val & (1 << l_low_reg_no + 2)) != 0 { - self.gpio_registers.intr[bank_no].set(current_val & (1 << l_low_reg_no + 2)); + } else if (current_val & enabled_val & (1 << (l_low_reg_no + 2))) != 0 { + self.gpio_registers.intr[bank_no].set(current_val & (1 << (l_low_reg_no + 2))); self.pins[pin + bank_no * 8].handle_interrupt(); - } else if (current_val & enabled_val & (1 << l_low_reg_no + 3)) != 0 { - self.gpio_registers.intr[bank_no].set(current_val & (1 << l_low_reg_no + 3)); + } else if (current_val & enabled_val & (1 << (l_low_reg_no + 3))) != 0 { + self.gpio_registers.intr[bank_no].set(current_val & (1 << (l_low_reg_no + 3))); self.pins[pin + bank_no * 8].handle_interrupt(); } } @@ -422,11 +426,7 @@ impl<'a> RPGpioPin<'a> { fn read_pin(&self) -> bool { //TODO - read alternate function let value = self.sio_registers.gpio_out.read(GPIO_OUT::OUT) & (1 << self.pin); - if value == 0 { - false - } else { - true - } + value != 0 } pub fn set_function(&self, f: GpioFunction) { @@ -460,6 +460,31 @@ impl<'a> RPGpioPin<'a> { pub fn handle_interrupt(&self) { self.client.map(|client| client.fired()); } + + // needed for usb errata https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#RP2040-E5 + + pub fn start_usb_errata(&self) -> (u32, u32) { + let prev_ctrl = self.gpio_registers.pin[self.pin].ctrl.get(); + let prev_pad = self.gpio_pad_registers.gpio_pad[self.pin].get(); + + self.gpio_pad_registers.gpio_pad[self.pin].modify(GPIO_PAD::PUE::SET + GPIO_PAD::PDE::SET); + self.gpio_registers.pin[self.pin] + .ctrl + .modify(GPIOx_CTRL::OEOVER::Disable); + + self.set_function(GpioFunction::GPCK); + + self.gpio_registers.pin[self.pin] + .ctrl + .modify(GPIOx_CTRL::INOVER::DriveHigh); + + (prev_ctrl, prev_pad) + } + + pub fn finish_usb_errata(&self, prev_ctrl: u32, prev_pad: u32) { + self.gpio_registers.pin[self.pin].ctrl.set(prev_ctrl); + self.gpio_pad_registers.gpio_pad[self.pin].set(prev_pad); + } } impl<'a> hil::gpio::Interrupt<'a> for RPGpioPin<'a> { @@ -471,17 +496,12 @@ impl<'a> hil::gpio::Interrupt<'a> for RPGpioPin<'a> { let interrupt_bank_no = self.pin / 8; let l_low_reg_no = (self.pin * 4) % 32; let current_val = self.gpio_registers.interrupt_proc[0].status[interrupt_bank_no].get(); - if (current_val + (current_val & (1 << l_low_reg_no) - & (1 << l_low_reg_no + 1) - & (1 << l_low_reg_no + 2) - & (1 << l_low_reg_no + 3)) - == 0 - { - false - } else { - true - } + & (1 << (l_low_reg_no + 1)) + & (1 << (l_low_reg_no + 2)) + & (1 << (l_low_reg_no + 3))) + != 0 } fn enable_interrupts(&self, mode: hil::gpio::InterruptEdge) { @@ -624,11 +644,7 @@ impl hil::gpio::Output for RPGpioPin<'_> { impl hil::gpio::Input for RPGpioPin<'_> { fn read(&self) -> bool { let value = self.sio_registers.gpio_in.read(GPIO_IN::IN) & (1 << self.pin); - if value == 0 { - false - } else { - true - } + value != 0 } } diff --git a/chips/rp2040/src/i2c.rs b/chips/rp2040/src/i2c.rs index bab9725633..a24af0339b 100644 --- a/chips/rp2040/src/i2c.rs +++ b/chips/rp2040/src/i2c.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use crate::clocks; use crate::resets; use core::cell::Cell; @@ -237,13 +241,13 @@ enum State { WaitingForStop, } -pub struct I2c<'a> { +pub struct I2c<'a, 'c> { instance_num: u8, registers: StaticRef, clocks: OptionalCell<&'a clocks::Clocks>, resets: OptionalCell<&'a resets::Resets>, - client: OptionalCell<&'static dyn hil::i2c::I2CHwMasterClient>, + client: OptionalCell<&'c dyn hil::i2c::I2CHwMasterClient>, buf: TakeCell<'static, [u8]>, state: Cell, @@ -255,8 +259,8 @@ pub struct I2c<'a> { abort_reason: OptionalCell>, } -impl<'a> I2c<'a> { - const fn new(instance_num: u8) -> Self { +impl<'a, 'c> I2c<'a, 'c> { + fn new(instance_num: u8) -> Self { Self { instance_num, registers: INSTANCES[instance_num as usize], @@ -276,11 +280,11 @@ impl<'a> I2c<'a> { } } - pub const fn new_i2c0() -> Self { + pub fn new_i2c0() -> Self { I2c::new(0) } - pub const fn new_i2c1() -> Self { + pub fn new_i2c1() -> Self { I2c::new(1) } @@ -339,18 +343,17 @@ impl<'a> I2c<'a> { // internally provide a hold time of at least 300ns for the SDA signal to // bridge the undefined region of the falling edge of SCL. A smaller hold // time of 120ns is used for fast mode plus. - let sda_tx_hold_count; - if baudrate < 1000000 { + let sda_tx_hold_count = if baudrate < 1000000 { // sda_tx_hold_count = freq_in [cycles/s] * 300ns * (1s / 1e9ns) // Reduce 300/1e9 to 3/1e7 to avoid numbers that don't fit in uint. // Add 1 to avoid division truncation. - sda_tx_hold_count = ((freq_in * 3) / 10000000) + 1; + ((freq_in * 3) / 10000000) + 1 } else { // sda_tx_hold_count = freq_in [cycles/s] * 120ns * (1s / 1e9ns) // Reduce 120/1e9 to 3/25e6 to avoid numbers that don't fit in uint. // Add 1 to avoid division truncation. - sda_tx_hold_count = ((freq_in * 3) / 25000000) + 1; - } + ((freq_in * 3) / 25000000) + 1 + }; assert!(sda_tx_hold_count <= lcnt - 2); self.registers.ic_enable.modify(IC_ENABLE::ENABLE::CLEAR); @@ -411,8 +414,8 @@ impl<'a> I2c<'a> { fn write_then_read( &self, addr: u8, - write_len: u8, - read_len: u8, + write_len: usize, + read_len: usize, ) -> Result<(), hil::i2c::Error> { let state = self.state.get(); assert!(state != State::Uninitialized); @@ -527,7 +530,7 @@ impl<'a> I2c<'a> { self.rw_index.set(idx + 1); } - fn read(&self, addr: u8, len: u8) -> Result<(), hil::i2c::Error> { + fn read(&self, addr: u8, len: usize) -> Result<(), hil::i2c::Error> { let state = self.state.get(); assert!(state != State::Uninitialized); if state != State::Idle { @@ -685,8 +688,8 @@ impl<'a> I2c<'a> { } } -impl<'a> hil::i2c::I2CMaster for I2c<'a> { - fn set_master_client(&self, client: &'static dyn hil::i2c::I2CHwMasterClient) { +impl<'a, 'c> hil::i2c::I2CMaster<'c> for I2c<'a, 'c> { + fn set_master_client(&self, client: &'c dyn hil::i2c::I2CHwMasterClient) { self.client.set(client); } @@ -703,8 +706,8 @@ impl<'a> hil::i2c::I2CMaster for I2c<'a> { &self, addr: u8, data: &'static mut [u8], - write_len: u8, - read_len: u8, + write_len: usize, + read_len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { self.buf.put(Some(data)); @@ -720,7 +723,7 @@ impl<'a> hil::i2c::I2CMaster for I2c<'a> { &self, addr: u8, data: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { // Setting read_len to 0 will result in having just a write self.write_read(addr, data, len, 0) @@ -730,7 +733,7 @@ impl<'a> hil::i2c::I2CMaster for I2c<'a> { &self, addr: u8, buffer: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { self.buf.put(Some(buffer)); diff --git a/chips/rp2040/src/interrupts.rs b/chips/rp2040/src/interrupts.rs index b11df688d7..d97f63978d 100644 --- a/chips/rp2040/src/interrupts.rs +++ b/chips/rp2040/src/interrupts.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + pub const TIMER_IRQ_0: u32 = 0; pub const TIMER_IRQ_1: u32 = 1; pub const TIMER_IRQ_2: u32 = 2; diff --git a/chips/rp2040/src/lib.rs b/chips/rp2040/src/lib.rs index c093273b03..7ed5a0924f 100644 --- a/chips/rp2040/src/lib.rs +++ b/chips/rp2040/src/lib.rs @@ -1,24 +1,29 @@ -#![feature(const_fn_trait_bound, asm)] +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + #![no_std] pub mod adc; pub mod chip; pub mod clocks; +mod deferred_calls; pub mod gpio; pub mod i2c; pub mod interrupts; +pub mod pwm; pub mod resets; +pub mod rtc; pub mod spi; pub mod sysinfo; +pub mod test; pub mod timer; pub mod uart; +pub mod usb; pub mod watchdog; pub mod xosc; -use cortexm0p::{ - self, generic_isr, hard_fault_handler, initialize_ram_jump_to_main, svc_handler, - systick_handler, unhandled_interrupt, -}; +use cortexm0p::{initialize_ram_jump_to_main, unhandled_interrupt, CortexM0P, CortexMVariant}; extern "C" { // _estack is not really a function, but it makes the types work @@ -35,20 +40,20 @@ extern "C" { pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ _estack, initialize_ram_jump_to_main, - unhandled_interrupt, // NMI - hard_fault_handler, // Hard Fault - unhandled_interrupt, // MemManage - unhandled_interrupt, // BusFault - unhandled_interrupt, // UsageFault + unhandled_interrupt, // NMI + CortexM0P::HARD_FAULT_HANDLER, // Hard Fault + unhandled_interrupt, // MemManage + unhandled_interrupt, // BusFault + unhandled_interrupt, // UsageFault unhandled_interrupt, unhandled_interrupt, unhandled_interrupt, unhandled_interrupt, - svc_handler, // SVC - unhandled_interrupt, // DebugMon + CortexM0P::SVC_HANDLER, // SVC + unhandled_interrupt, // DebugMon unhandled_interrupt, - unhandled_interrupt, // PendSV - systick_handler, // SysTick + unhandled_interrupt, // PendSV + CortexM0P::SYSTICK_HANDLER, // SysTick ]; // RP2040 has total of 26 interrupts, but the SDK declares 32 as 26 - 32 might be manually handled @@ -56,38 +61,38 @@ pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ // used Ensures that the symbol is kept until the final binary #[cfg_attr(all(target_arch = "arm", target_os = "none"), used)] pub static IRQS: [unsafe extern "C" fn(); 32] = [ - generic_isr, // TIMER0 (0) - generic_isr, // TIMER1 (1) - generic_isr, // TIMER2 (2) - generic_isr, // TIMER3 (3) - generic_isr, // PWM WRAP (4) - generic_isr, // USB (5) - generic_isr, // XIP (6) - generic_isr, // PIO0 INT0 (7) - generic_isr, // PIO0 INT1 (8) - generic_isr, // PIO1 INT0 (9) - generic_isr, // PIO1 INT1 (10) - generic_isr, // DMA0 (11) - generic_isr, // DMA1 (12) - generic_isr, // IO BANK 0 (13) - generic_isr, // IO QSPI (14) - generic_isr, // SIO PROC 0 (15) - generic_isr, // SIO PROC 1 (16) - generic_isr, // CLOCKS (17) - generic_isr, // SPI 0 (18) - generic_isr, // SPI 1 (19) - generic_isr, // UART 0 (20) - generic_isr, // UART 1 (21) - generic_isr, // ADC FIFO (22) - generic_isr, // I2C 0 (23) - generic_isr, // I2C 1 (24) - generic_isr, // RTC (25) - unhandled_interrupt, // (26) - unhandled_interrupt, // (27) - unhandled_interrupt, // (28) - unhandled_interrupt, // (29) - unhandled_interrupt, // (30) - unhandled_interrupt, // (31) + CortexM0P::GENERIC_ISR, // TIMER0 (0) + CortexM0P::GENERIC_ISR, // TIMER1 (1) + CortexM0P::GENERIC_ISR, // TIMER2 (2) + CortexM0P::GENERIC_ISR, // TIMER3 (3) + CortexM0P::GENERIC_ISR, // PWM WRAP (4) + CortexM0P::GENERIC_ISR, // USB (5) + CortexM0P::GENERIC_ISR, // XIP (6) + CortexM0P::GENERIC_ISR, // PIO0 INT0 (7) + CortexM0P::GENERIC_ISR, // PIO0 INT1 (8) + CortexM0P::GENERIC_ISR, // PIO1 INT0 (9) + CortexM0P::GENERIC_ISR, // PIO1 INT1 (10) + CortexM0P::GENERIC_ISR, // DMA0 (11) + CortexM0P::GENERIC_ISR, // DMA1 (12) + CortexM0P::GENERIC_ISR, // IO BANK 0 (13) + CortexM0P::GENERIC_ISR, // IO QSPI (14) + CortexM0P::GENERIC_ISR, // SIO PROC 0 (15) + CortexM0P::GENERIC_ISR, // SIO PROC 1 (16) + CortexM0P::GENERIC_ISR, // CLOCKS (17) + CortexM0P::GENERIC_ISR, // SPI 0 (18) + CortexM0P::GENERIC_ISR, // SPI 1 (19) + CortexM0P::GENERIC_ISR, // UART 0 (20) + CortexM0P::GENERIC_ISR, // UART 1 (21) + CortexM0P::GENERIC_ISR, // ADC FIFO (22) + CortexM0P::GENERIC_ISR, // I2C 0 (23) + CortexM0P::GENERIC_ISR, // I2C 1 (24) + CortexM0P::GENERIC_ISR, // RTC (25) + unhandled_interrupt, // (26) + unhandled_interrupt, // (27) + unhandled_interrupt, // (28) + unhandled_interrupt, // (29) + unhandled_interrupt, // (30) + unhandled_interrupt, // (31) ]; extern "C" { diff --git a/chips/rp2040/src/mod.rs b/chips/rp2040/src/mod.rs new file mode 100644 index 0000000000..de47e607d0 --- /dev/null +++ b/chips/rp2040/src/mod.rs @@ -0,0 +1,5 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +pub mod test; diff --git a/chips/rp2040/src/pwm.rs b/chips/rp2040/src/pwm.rs new file mode 100644 index 0000000000..86bcfcfdef --- /dev/null +++ b/chips/rp2040/src/pwm.rs @@ -0,0 +1,1286 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Pulse wave modulation (PWM) driver for RP2040. +//! +//! # Hardware Interface Layer (HIL) +//! +//! The driver implements both Pwm and PwmPin HILs. The following features are available when using +//! the driver through HIL: +//! +//! + Configurable top and compare values +//! + Independent configuration for each channel and for each output/input pin +//! + Duty cycle from 0% to 100% **inclusive** +//! +//! # Examples +//! +//! The integration tests for Raspberry Pi Pico provide some examples using the driver. +//! See boards/raspberry_pi_pico/src/test/pwm.rs + +use kernel::debug; +use kernel::hil; +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; +use kernel::utilities::registers::{ + register_bitfields, register_structs, ReadOnly, ReadWrite, WriteOnly, +}; +use kernel::utilities::StaticRef; +use kernel::ErrorCode; + +use crate::clocks; +use crate::gpio::RPGpio; + +register_bitfields![u32, + CSR [ + // Enable PWM channel + EN OFFSET(0) NUMBITS(1) [], + // Enable phase-correct modulation + PH_CORRECT OFFSET(1) NUMBITS(1) [], + // Invert output A + A_INV OFFSET(2) NUMBITS(1) [], + // Invert output B + B_INV OFFSET(3) NUMBITS(1) [], + // PWM slice event selection for fractional clock divider + // Default value = FREE_RUNNING (always on) + // If the event is different from FREE_RUNNING, then pin B becomes + // an input pin + DIVMOD OFFSET(4) NUMBITS(2) [ + // Free-running counting at rate dictated by fractional divider + FREE_RUNNING = 0, + // Fractional divider operation is gated by the PWM B pin + B_HIGH = 1, + // Counter advances with each rising edge of the PWM B pin + B_RISING = 2, + // Counter advances with each falling edge of the PWM B pin + B_FALLING = 3 + ], + // Retard the phase of the counter by 1 count, while it is running + // Self-clearing. Write a 1, and poll until low. Counter must be running. + PH_RET OFFSET(6) NUMBITS(1) [], + // Advance the phase of the counter by 1 count, while it is running + // Self clearing. Write a 1, and poll until low. Counter must be running. + PH_ADV OFFSET(7) NUMBITS(1) [] + ], + + // DIV register + // INT and FRAC form a fixed-point fractional number. + // Counting rate is system clock frequency divided by this number. + // Fractional division uses simple 1st-order sigma-delta. + DIV [ + FRAC OFFSET(0) NUMBITS(4) [], + INT OFFSET(4) NUMBITS(8) [] + ], + + // Direct access to the PWM counter + CTR [ + CTR OFFSET(0) NUMBITS(16) [] + ], + + // Counter compare values + CC [ + A OFFSET(0) NUMBITS(16) [], + B OFFSET(16) NUMBITS(16) [] + ], + + // Counter top value + // When the value of the counter reaches the top value, depending on the + // ph_correct value, the counter will either: + // + wrap to 0 if ph_correct == 0 + // + it starts counting downward until it reaches 0 again if ph_correct == 0 + TOP [ + TOP OFFSET(0) NUMBITS(16) [] + ], + + // Control multiple channels at once. + // Each bit controls one channel. + CH [ + CH OFFSET(0) NUMBITS(8) [ + CH0 = 1, + CH1 = 2, + CH2 = 4, + CH3 = 8, + CH4 = 16, + CH5 = 32, + CH6 = 64, + CH7 = 128 + ] + ] +]; + +const NUMBER_CHANNELS: usize = 8; + +#[repr(C)] +struct Channel { + // Control and status register + csr: ReadWrite, + // Division register + div: ReadWrite, + // Direct access to the PWM counter register + ctr: ReadWrite, + // Counter compare values register + cc: ReadWrite, + // Counter wrap value register + top: ReadWrite, +} + +register_structs! { + PwmRegisters { + // Channel registers + (0x0000 => ch: [Channel; NUMBER_CHANNELS]), + // Enable register + // This register aliases the CSR_EN bits for all channels. + // Writing to this register allows multiple channels to be enabled or disabled + // or disables simultaneously, so they can run in perfect sync. + (0x00A0 => en: ReadWrite), + // Raw interrupts register + (0x00A4 => intr: WriteOnly), + // Interrupt enable register + (0x00A8 => inte: ReadWrite), + // Interrupt force register + (0x00AC => intf: ReadWrite), + // Interrupt status after masking & forcing + (0x00B0 => ints: ReadOnly), + (0x00B4 => @END), + } +} + +#[derive(Clone, Copy)] +/// Fractional clock divider running mode +/// +/// Each channel can be configured to run in four different ways: +/// +/// + Free running: The fractional clock divider is always enabled. In this mode, +/// pins A and B are configured as output pins. In other modes, pin B becomes +/// an input pin. +/// + High: The fractional clock divider is enabled when pin B is high. +/// + Rising: The fractional clock divider is enabled when a rising-edge is +/// detected on pin B. +/// + Falling: The fractional clock divider is enabled when a falling-edge +/// is detected on pin B. +pub enum DivMode { + FreeRunning, + High, + Rising, + Falling, +} + +/// Channel identifier +/// +/// There are a total of 8 eight PWM channels. +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum ChannelNumber { + Ch0, + Ch1, + Ch2, + Ch3, + Ch4, + Ch5, + Ch6, + Ch7, +} + +const CHANNEL_NUMBERS: [ChannelNumber; NUMBER_CHANNELS] = [ + ChannelNumber::Ch0, + ChannelNumber::Ch1, + ChannelNumber::Ch2, + ChannelNumber::Ch3, + ChannelNumber::Ch4, + ChannelNumber::Ch5, + ChannelNumber::Ch6, + ChannelNumber::Ch7, +]; + +/// Each GPIO pin can be configured as a PWM pin. +/// The following table shows the mapping between GPIO pins and PWM pins: +/// +/// | GPIO | PWM | +/// | ----- | --- | +/// | 0 | 0A | +/// | 1 | 0B | +/// | 2 | 1A | +/// | 3 | 1B | +/// | 4 | 2A | +/// | 5 | 2B | +/// | 6 | 3A | +/// | 7 | 3B | +/// | 8 | 4A | +/// | 9 | 4B | +/// | 10 | 5A | +/// | 11 | 5B | +/// | 12 | 6A | +/// | 13 | 6B | +/// | 14 | 7A | +/// | 15 | 7B | +/// | 16 | 0A | +/// | 17 | 0B | +/// | 18 | 1A | +/// | 19 | 1B | +/// | 20 | 2A | +/// | 21 | 2B | +/// | 22 | 3A | +/// | 23 | 3B | +/// | 24 | 4A | +/// | 25 | 4B | +/// | 26 | 5A | +/// | 27 | 5B | +/// | 28 | 6A | +/// | 29 | 6B | +/// +/// **Note**: +/// +/// + The same PWM output can be selected on two GPIO pins. The same signal will appear on each +/// GPIO. +/// + If a PWM B pin is used as an input, and is selected on multiple GPIO pins, then the PWM +/// channel will see the logical OR of those two GPIO inputs +impl From for ChannelNumber { + fn from(gpio: RPGpio) -> Self { + match gpio as u8 >> 1 & 0b111 { + // Because of the bitwise AND, there are only eight possible values + 0 => ChannelNumber::Ch0, + 1 => ChannelNumber::Ch1, + 2 => ChannelNumber::Ch2, + 3 => ChannelNumber::Ch3, + 4 => ChannelNumber::Ch4, + 5 => ChannelNumber::Ch5, + 6 => ChannelNumber::Ch6, + _ => ChannelNumber::Ch7, + } + } +} + +/// Identifier for a channel pin +/// +/// Each PWM channel has two pins: A and B. +/// Pin A is always configured as an output pin. +/// Pin B is configured as an output pin when running in free running mode. Otherwise, it is +/// configured as an input pin. +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum ChannelPin { + A, + B, +} + +/// Check ChannelNumber implementation for more details +impl From for ChannelPin { + fn from(gpio: RPGpio) -> Self { + match gpio as u8 & 0b0000_0001 { + // Because of the bitwise AND, there are only two possible values + 0 => ChannelPin::A, + _ => ChannelPin::B, + } + } +} + +// PWM channel configuration structure +// +// This helper struct allows multiple channels to share the same configuration. +struct PwmChannelConfiguration { + en: bool, + ph_correct: bool, + a_inv: bool, + b_inv: bool, + divmode: DivMode, + int: u8, + frac: u8, + cc_a: u16, + cc_b: u16, + top: u16, +} + +impl Default for PwmChannelConfiguration { + // Create a set of default values to use for configuring a PWM channel: + // + the channel is disabled + // + trailing-edge modulation configured + // + no pin A and B polarity inversion + // + free running mode for the fractional clock divider + // + integral part of the divider is 1 and the fractional part is 0 + // + compare values for both pins are set 0 (0% duty cycle) + // + top value is set to its maximum value + fn default() -> Self { + PwmChannelConfiguration { + en: false, + ph_correct: false, + a_inv: false, + b_inv: false, + divmode: DivMode::FreeRunning, + int: 1, + frac: 0, + cc_a: 0, + cc_b: 0, + top: u16::MAX, + } + } +} + +const PWM_BASE: StaticRef = + unsafe { StaticRef::new(0x40050000 as *const PwmRegisters) }; + +/// Main struct for controlling PWM peripheral +pub struct Pwm<'a> { + registers: StaticRef, + clocks: OptionalCell<&'a clocks::Clocks>, +} + +impl<'a> Pwm<'a> { + /// Create a new Pwm struct + /// + /// **Note**: + /// + This method must be called only once when setting up the kernel peripherals. + /// + This peripheral depends on the chip's clocks. + /// + Also, if interrupts are required, then an interrupt handler must be set. Otherwise, all + /// the interrupts will be ignored. + pub fn new() -> Self { + let pwm = Self { + registers: PWM_BASE, + clocks: OptionalCell::empty(), + }; + pwm.init(); + pwm + } + + // Enable or disable the given PWM channel + // + // enable == false ==> disable channel + // enable == true ==> enable channel + fn set_enabled(&self, channel_number: ChannelNumber, enable: bool) { + self.registers.ch[channel_number as usize] + .csr + .modify(match enable { + true => CSR::EN::SET, + false => CSR::EN::CLEAR, + }); + } + + // Set phase correct (dual slope) modulation for the givem PWM channel + // + // ph_correct == false ==> trailing-edge modulation + // ph_correct == true ==> phase-correct modulation + fn set_ph_correct(&self, channel_number: ChannelNumber, ph_correct: bool) { + self.registers.ch[channel_number as usize] + .csr + .modify(match ph_correct { + true => CSR::PH_CORRECT::SET, + false => CSR::PH_CORRECT::CLEAR, + }); + } + + // Invert polarity for pin A + // a_inv == true ==> invert polarity for pin A + fn set_invert_polarity_a(&self, channel_number: ChannelNumber, inv: bool) { + self.registers.ch[channel_number as usize] + .csr + .modify(match inv { + true => CSR::A_INV::SET, + false => CSR::A_INV::CLEAR, + }); + } + + // Invert polarity for pin B + // b_inv == true ==> invert polarity for pin B + fn set_invert_polarity_b(&self, channel_number: ChannelNumber, inv: bool) { + self.registers.ch[channel_number as usize] + .csr + .modify(match inv { + true => CSR::B_INV::SET, + false => CSR::B_INV::CLEAR, + }); + } + + // Invert polarity for both pins + fn set_invert_polarity(&self, channel_number: ChannelNumber, a_inv: bool, b_inv: bool) { + self.set_invert_polarity_a(channel_number, a_inv); + self.set_invert_polarity_b(channel_number, b_inv); + } + + // Set running mode for the givel channel + // + // divmode == FreeRunning ==> always enable clock divider + // divmode == High ==> enable clock divider when pin B is high + // divmode == Rising ==> enable clock divider when pin B is rising + // divmode == Falling ==> enable clock divider when pin B is falling + fn set_div_mode(&self, channel_number: ChannelNumber, div_mode: DivMode) { + self.registers.ch[channel_number as usize] + .csr + .modify(match div_mode { + DivMode::FreeRunning => CSR::DIVMOD::FREE_RUNNING, + DivMode::High => CSR::DIVMOD::B_HIGH, + DivMode::Rising => CSR::DIVMOD::B_RISING, + DivMode::Falling => CSR::DIVMOD::B_FALLING, + }); + } + + // Set integral and fractional part of the clock divider + // RP 2040 uses a 8.4 fractional clock divider. + // The minimum value of the divider is 1 (int) + 0 / 16 (frac). + // The maximum value of the divider is 255 (int) + 15 / 16 (frac). + // + // **Note**: this method will do nothing if int == 0 || frac > 15. + fn set_divider_int_frac(&self, channel_number: ChannelNumber, int: u8, frac: u8) { + if int == 0 || frac > 15 { + return; + } + self.registers.ch[channel_number as usize] + .div + .modify(DIV::INT.val(int as u32)); + self.registers.ch[channel_number as usize] + .div + .modify(DIV::FRAC.val(frac as u32)); + } + + // Set output pin A compare value + // If counter value < compare value A ==> pin A high + fn set_compare_value_a(&self, channel_number: ChannelNumber, cc_a: u16) { + self.registers.ch[channel_number as usize] + .cc + .modify(CC::A.val(cc_a as u32)); + } + + // Set output pin B compare value + // If counter value < compare value B ==> pin B high (if divmode == FreeRunning) + fn set_compare_value_b(&self, channel_number: ChannelNumber, cc_b: u16) { + self.registers.ch[channel_number as usize] + .cc + .modify(CC::B.val(cc_b as u32)); + } + + // Set compare values for both pins + fn set_compare_values_a_and_b(&self, channel_number: ChannelNumber, cc_a: u16, cc_b: u16) { + self.set_compare_value_a(channel_number, cc_a); + self.set_compare_value_b(channel_number, cc_b); + } + + // Set counter top value + fn set_top(&self, channel_number: ChannelNumber, top: u16) { + self.registers.ch[channel_number as usize] + .top + .modify(TOP::TOP.val(top as u32)); + } + + // Get the current value of the counter + fn get_counter(&self, channel_number: ChannelNumber) -> u16 { + self.registers.ch[channel_number as usize] + .ctr + .read(CTR::CTR) as u16 + } + + // Set the value of the counter + fn set_counter(&self, channel_number: ChannelNumber, value: u16) { + self.registers.ch[channel_number as usize] + .ctr + .modify(CTR::CTR.val(value as u32)); + } + + fn wait_for(count: usize, f: impl Fn() -> bool) -> bool { + for _ in 0..count { + if f() { + return true; + } + } + + false + } + + // Increments the value of the counter + // + // The counter must be running at less than full speed. The method will return + // once the increment is complete. + fn advance_count(&self, channel_number: ChannelNumber) -> bool { + self.registers.ch[channel_number as usize] + .csr + .modify(CSR::PH_ADV::SET); + Self::wait_for(100, || { + self.registers.ch[channel_number as usize] + .csr + .read(CSR::PH_ADV) + == 0 + }) + } + + // Retards the phase of the counter by 1 count + // + // The counter must be running. The method will return once the retardation + // is complete. + fn retard_count(&self, channel_number: ChannelNumber) -> bool { + self.registers.ch[channel_number as usize] + .csr + .modify(CSR::PH_RET::SET); + Self::wait_for(100, || { + self.registers.ch[channel_number as usize] + .csr + .read(CSR::PH_RET) + == 0 + }) + } + + // Enable interrupt on the given PWM channel + fn enable_interrupt(&self, channel_number: ChannelNumber) { + // What about adding a new method to the register interface which performs + // a bitwise OR and another one for AND? + let mask = self.registers.inte.read(CH::CH); + self.registers + .inte + .modify(CH::CH.val(mask | 1 << channel_number as u32)); + } + + // Disable interrupt on the given PWM channel + fn disable_interrupt(&self, channel_number: ChannelNumber) { + let mask = self.registers.inte.read(CH::CH); + self.registers + .inte + .modify(CH::CH.val(mask & !(1 << channel_number as u32))); + } + + // Enable multiple channel interrupts at once. + // + // Bits 0 to 7 ==> enable channel 0-7 interrupts. + fn enable_mask_interrupt(&self, mask: u8) { + let old_mask = self.registers.inte.read(CH::CH); + self.registers + .inte + .modify(CH::CH.val(old_mask | mask as u32)); + } + + // Disable multiple channel interrupts at once. + // + // Bits 0 to 7 ==> disable channel 0-7 interrupts. + fn disable_mask_interrupt(&self, mask: u8) { + let old_mask = self.registers.inte.read(CH::CH); + self.registers + .inte + .modify(CH::CH.val(old_mask & !mask as u32)); + } + + // Clear interrupt flag + fn clear_interrupt(&self, channel_number: ChannelNumber) { + self.registers + .intr + .write(CH::CH.val(1 << channel_number as u32)); + } + + // Force interrupt on the given channel + fn force_interrupt(&self, channel_number: ChannelNumber) { + let mask = self.registers.intf.read(CH::CH); + self.registers + .intf + .modify(CH::CH.val(mask | 1 << channel_number as u32)); + } + + // Unforce interrupt + fn unforce_interrupt(&self, channel_number: ChannelNumber) { + let mask = self.registers.intf.read(CH::CH); + self.registers + .intf + .modify(CH::CH.val(mask & !(1 << channel_number as u32))); + } + + // Get interrupt status + fn get_interrupt_status(&self, channel_number: ChannelNumber) -> bool { + (self.registers.ints.read(CH::CH) & 1 << channel_number as u32) != 0 + } + + // Configure the given channel using the given configuration + fn configure_channel(&self, channel_number: ChannelNumber, config: &PwmChannelConfiguration) { + self.set_ph_correct(channel_number, config.ph_correct); + self.set_invert_polarity(channel_number, config.a_inv, config.b_inv); + self.set_div_mode(channel_number, config.divmode); + self.set_divider_int_frac(channel_number, config.int, config.frac); + self.set_compare_value_a(channel_number, config.cc_a); + self.set_compare_value_b(channel_number, config.cc_b); + self.set_top(channel_number, config.top); + self.set_enabled(channel_number, config.en); + } + + // Initialize the struct + fn init(&self) { + let default_config: PwmChannelConfiguration = PwmChannelConfiguration::default(); + for channel_number in CHANNEL_NUMBERS { + self.configure_channel(channel_number, &default_config); + self.set_counter(channel_number, 0); + self.disable_interrupt(channel_number); + } + self.registers.intr.write(CH::CH.val(0)); + } + + // This method should be called when resolving dependencies for the + // default peripherals. See [crate::chip::Rp2040DefaultPeripherals::resolve_dependencies] + pub(crate) fn set_clocks(&self, clocks: &'a clocks::Clocks) { + self.clocks.set(clocks); + } + + // Given a channel number and a channel pin, return a struct that allows controlling it + fn new_pwm_pin(&'a self, channel_number: ChannelNumber, channel_pin: ChannelPin) -> PwmPin<'a> { + PwmPin { + pwm_struct: self, + channel_number, + channel_pin, + } + } + + // Map the given GPIO to a PWM channel and a PWM pin + fn gpio_to_pwm(&self, gpio: RPGpio) -> (ChannelNumber, ChannelPin) { + (ChannelNumber::from(gpio), ChannelPin::from(gpio)) + } + + /// Map the GPIO to a PwmPin struct + /// + /// The returned structure can be used to control the PWM pin. + /// + /// See [PwmPin] + pub fn gpio_to_pwm_pin(&'a self, gpio: RPGpio) -> PwmPin { + let (channel_number, channel_pin) = self.gpio_to_pwm(gpio); + self.new_pwm_pin(channel_number, channel_pin) + } + + // Helper function to compute top, int and frac values + // selected_freq_hz ==> user's desired frequency + // + // Return value: Ok(top, int, frac) in case of no error, otherwise Err(()) + fn compute_top_int_frac(&self, selected_freq_hz: usize) -> Result<(u16, u8, u8), ()> { + let max_freq_hz = hil::pwm::Pwm::get_maximum_frequency_hz(self); + let threshold_freq_hz = max_freq_hz / hil::pwm::Pwm::get_maximum_duty_cycle(self); + // If the desired frequency doesn't make sense, return directly an error + if selected_freq_hz > max_freq_hz || selected_freq_hz == 0 { + return Err(()); + } + + // If the selected frequency is high enough, then there is no need for a divider + if selected_freq_hz > threshold_freq_hz { + return Ok(((max_freq_hz / selected_freq_hz - 1) as u16, 1, 0)); + } + // If the selected frequency is below the threshold frequency, then a divider is necessary + + // Set top to max + let top = u16::MAX; + // Get the corresponding integral part of the divider + let int = threshold_freq_hz / selected_freq_hz; + // If the desired frequency is too low, then it can't be achieved using the divider. + // In this case, notify the caller with an error. + if int >= 256 { + return Err(()); + } + // Now that the integral part is valid, the fractional part can be computed as well. + // The fractional part is on 4 bits. + let frac = ((threshold_freq_hz << 4) / selected_freq_hz - (int << 4)) as u8; + + // Return the final result + // Since int < 256, the cast will not truncate the value. + Ok((top, int as u8, frac)) + } + + // Starts a PWM pin with the given frequency and duty cycle. + // + // Note: the actual values may vary due to rounding errors. + fn start_pwm_pin( + &self, + channel_number: ChannelNumber, + channel_pin: ChannelPin, + frequency_hz: usize, + duty_cycle: usize, + ) -> Result<(), ErrorCode> { + let (top, int, frac) = match self.compute_top_int_frac(frequency_hz) { + Ok(result) => result, + Err(()) => return Result::from(ErrorCode::INVAL), + }; + + let max_duty_cycle = hil::pwm::Pwm::get_maximum_duty_cycle(self); + // Return an error if the selected duty cycle is higher than the maximum value + if duty_cycle > max_duty_cycle { + return Err(ErrorCode::INVAL); + } + // If top value is equal to u16::MAX, then it is impossible to + // have a 100% duty cycle, so an error will be returned. + let compare_value = if duty_cycle == max_duty_cycle { + if top == u16::MAX { + return Result::from(ErrorCode::INVAL); + } else { + // counter compare value for 100% glitch-free duty cycle + top + 1 + } + } else { + // Normally, no overflow should occur if duty_cycle is less than or + // equal to get_maximum_duty_cycle(). It is in user's responsability to + // ensure the value is valid. + ((top as usize + 1) * duty_cycle / max_duty_cycle) as u16 + }; + + // Configure the channel accordingly + self.set_top(channel_number, top); + self.set_divider_int_frac(channel_number, int, frac); + // Configure the pin accordingly + if channel_pin == ChannelPin::A { + self.set_compare_value_a(channel_number, compare_value); + } else { + self.set_compare_value_b(channel_number, compare_value); + }; + // Finally, enable the channel + self.set_enabled(channel_number, true); + Ok(()) + } + + // Stop a PWM channel. + // + // This method does nothing if the PWM channel was already disabled. + // + // Note that disabling a PWM channel may result in disabling multiple PWM pins. + fn stop_pwm_channel(&self, channel_number: ChannelNumber) -> Result<(), ErrorCode> { + self.set_enabled(channel_number, false); + Ok(()) + } +} + +/// Implementation of the Hardware Interface Layer (HIL) +impl hil::pwm::Pwm for Pwm<'_> { + type Pin = RPGpio; + + /// Start a PWM pin + /// + /// Start the given PWM pin with the given frequency and the given duty cycle. + /// The actual values may vary due to rounding errors. For high precision duty cycles, + /// the frequency should be set less than: + /// + /// ```rust,ignore + /// let threshold_freq = pwm_struct.get_maximum_frequency_hz() / pwm_struct.get_maximum_duty_cycle() + /// ``` + /// + /// ## Errors + /// + /// This method may fail in one of the following situations: + /// + /// + selected frequency and duty cycle higher than the maximum possible values + /// + 100% duty cycle demand for low frequencies (close to or below threshold_freq) + /// + very low frequencies + /// + /// ## Safety + /// + /// It is safe to call multiples times this method with different values while the pin is + /// running. + /// + /// **Note**: the pin must be set as a PWM pin prior to calling this method. + fn start( + &self, + pin: &Self::Pin, + frequency_hz: usize, + duty_cycle: usize, + ) -> Result<(), ErrorCode> { + let (channel_number, channel_pin) = self.gpio_to_pwm(*pin); + self.start_pwm_pin(channel_number, channel_pin, frequency_hz, duty_cycle) + } + + /// Stop the given pin + /// + /// ## Errors + /// + /// This method may never fail. + /// + /// ## Safety + /// + /// It is safe to call this method multiple times on the same pin. If the pin is already + /// stopped, then it does nothing. + fn stop(&self, pin: &Self::Pin) -> Result<(), ErrorCode> { + let (channel_number, _) = self.gpio_to_pwm(*pin); + self.stop_pwm_channel(channel_number) + } + + /// Return the maximum value of the frequency in Hz + /// + /// ## Panics + /// + /// This method will panic if the dependencies are not resolved. + fn get_maximum_frequency_hz(&self) -> usize { + self.clocks + .unwrap_or_panic() + .get_frequency(clocks::Clock::System) as usize + } + + /// Return an opaque value representing 100% duty cycle + fn get_maximum_duty_cycle(&self) -> usize { + u16::MAX as usize + 1 + } +} + +/// Helper structure to control a PWM pin +pub struct PwmPin<'a> { + pwm_struct: &'a Pwm<'a>, + channel_number: ChannelNumber, + channel_pin: ChannelPin, +} + +impl PwmPin<'_> { + /// Returns the PWM channel the pin belongs to + pub fn get_channel_number(&self) -> ChannelNumber { + self.channel_number + } + + /// Returns the PWM pin the pin belongs to + pub fn get_channel_pin(&self) -> ChannelPin { + self.channel_pin + } + + // See [Pwm::set_invert_polarity_a] and [Pwm::set_invert_polarity_b] + fn set_invert_polarity(&self, inv: bool) { + if self.channel_pin == ChannelPin::A { + self.pwm_struct + .set_invert_polarity_a(self.channel_number, inv); + } else { + self.pwm_struct + .set_invert_polarity_b(self.channel_number, inv); + } + } + + // See [Pwm::set_compare_value_a] and [Pwm::set_compare_value_b] + fn set_compare_value(&self, compare_value: u16) { + if self.channel_pin == ChannelPin::A { + self.pwm_struct + .set_compare_value_a(self.channel_number, compare_value); + } else { + self.pwm_struct + .set_compare_value_b(self.channel_number, compare_value); + } + } +} + +impl hil::pwm::PwmPin for PwmPin<'_> { + /// Same as Pwm::start + fn start(&self, frequency_hz: usize, duty_cycle: usize) -> Result<(), ErrorCode> { + self.pwm_struct.start_pwm_pin( + self.channel_number, + self.channel_pin, + frequency_hz, + duty_cycle, + ) + } + + /// Same as Pwm::stop + fn stop(&self) -> Result<(), ErrorCode> { + self.pwm_struct.stop_pwm_channel(self.channel_number) + } + + /// Same as Pwm::get_maximum_frequency_hz + fn get_maximum_frequency_hz(&self) -> usize { + hil::pwm::Pwm::get_maximum_frequency_hz(self.pwm_struct) + } + + /// Same as Pwm::get_maximum_duty_cycle + fn get_maximum_duty_cycle(&self) -> usize { + hil::pwm::Pwm::get_maximum_duty_cycle(self.pwm_struct) + } +} + +/// Unit tests +/// +/// This module provides unit tests for the PWM driver. +/// +/// To run the tests, add the following line before loading processes: +/// +/// ```rust,ignore +/// rp2040::pwm::unit_tests::run::(&peripherals.pwm); +/// ``` +/// +/// Compile and flash the kernel on the board. Then, connect to UART on GPIOs 1 and 2. +/// If everything goes right, the following output should be displayed: +/// +/// ```txt +/// Testing ChannelNumber enum... +/// ChannelNumber enum OK +/// Testing ChannelPin enum... +/// ChannelPin enum OK +/// Testing PWM struct... +/// Starting testing channel 1... +/// Channel 1 works! +/// Starting testing channel 2... +/// Channel 2 works! +/// Starting testing channel 3... +/// Channel 3 works! +/// Starting testing channel 4... +/// Channel 4 works! +/// Starting testing channel 5... +/// Channel 5 works! +/// Starting testing channel 6... +/// Channel 6 works! +/// Starting testing channel 7... +/// Channel 7 works! +/// PWM struct OK +/// Testing PwmPinStruct... +/// PwmPin struct OK +/// Testing PWM HIL trait... +/// PWM HIL trait OK +/// ``` + +pub mod unit_tests { + use super::*; + + fn test_channel_number() { + debug!("Testing ChannelNumber enum..."); + assert_eq!(ChannelNumber::from(RPGpio::GPIO0), ChannelNumber::Ch0); + assert_eq!(ChannelNumber::from(RPGpio::GPIO3), ChannelNumber::Ch1); + assert_eq!(ChannelNumber::from(RPGpio::GPIO14), ChannelNumber::Ch7); + assert_eq!(ChannelNumber::from(RPGpio::GPIO28), ChannelNumber::Ch6); + debug!("ChannelNumber enum OK"); + } + + fn test_channel_pin() { + debug!("Testing ChannelPin enum..."); + assert_eq!(ChannelPin::from(RPGpio::GPIO4), ChannelPin::A); + assert_eq!(ChannelPin::from(RPGpio::GPIO5), ChannelPin::B); + debug!("ChannelPin enum OK"); + } + + fn test_channel(pwm: &Pwm, channel_number: ChannelNumber) { + debug!("Starting testing channel {}...", channel_number as usize); + + // Testing set_enabled() + pwm.set_enabled(channel_number, true); + assert_eq!( + pwm.registers.ch[channel_number as usize].csr.read(CSR::EN), + 1 + ); + pwm.set_enabled(channel_number, false); + assert_eq!( + pwm.registers.ch[channel_number as usize].csr.read(CSR::EN), + 0 + ); + + // Testing set_ph_correct() + pwm.set_ph_correct(channel_number, true); + assert_eq!( + pwm.registers.ch[channel_number as usize] + .csr + .read(CSR::PH_CORRECT), + 1 + ); + pwm.set_ph_correct(channel_number, false); + assert_eq!( + pwm.registers.ch[channel_number as usize] + .csr + .read(CSR::PH_CORRECT), + 0 + ); + + // Testing set_invert_polarity() + pwm.set_invert_polarity(channel_number, true, true); + assert_eq!( + pwm.registers.ch[channel_number as usize] + .csr + .read(CSR::A_INV), + 1 + ); + assert_eq!( + pwm.registers.ch[channel_number as usize] + .csr + .read(CSR::B_INV), + 1 + ); + pwm.set_invert_polarity(channel_number, true, false); + assert_eq!( + pwm.registers.ch[channel_number as usize] + .csr + .read(CSR::A_INV), + 1 + ); + assert_eq!( + pwm.registers.ch[channel_number as usize] + .csr + .read(CSR::B_INV), + 0 + ); + pwm.set_invert_polarity(channel_number, false, true); + assert_eq!( + pwm.registers.ch[channel_number as usize] + .csr + .read(CSR::A_INV), + 0 + ); + assert_eq!( + pwm.registers.ch[channel_number as usize] + .csr + .read(CSR::B_INV), + 1 + ); + pwm.set_invert_polarity(channel_number, false, false); + assert_eq!( + pwm.registers.ch[channel_number as usize] + .csr + .read(CSR::A_INV), + 0 + ); + assert_eq!( + pwm.registers.ch[channel_number as usize] + .csr + .read(CSR::B_INV), + 0 + ); + + // Testing set_div_mode() + pwm.set_div_mode(channel_number, DivMode::FreeRunning); + assert_eq!( + pwm.registers.ch[channel_number as usize] + .csr + .read(CSR::DIVMOD), + DivMode::FreeRunning as u32 + ); + pwm.set_div_mode(channel_number, DivMode::High); + assert_eq!( + pwm.registers.ch[channel_number as usize] + .csr + .read(CSR::DIVMOD), + DivMode::High as u32 + ); + pwm.set_div_mode(channel_number, DivMode::Rising); + assert_eq!( + pwm.registers.ch[channel_number as usize] + .csr + .read(CSR::DIVMOD), + DivMode::Rising as u32 + ); + pwm.set_div_mode(channel_number, DivMode::Falling); + assert_eq!( + pwm.registers.ch[channel_number as usize] + .csr + .read(CSR::DIVMOD), + DivMode::Falling as u32 + ); + + // Testing set_divider_int_frac() + pwm.set_divider_int_frac(channel_number, 123, 4); + assert_eq!( + pwm.registers.ch[channel_number as usize].div.read(DIV::INT), + 123 + ); + assert_eq!( + pwm.registers.ch[channel_number as usize] + .div + .read(DIV::FRAC), + 4 + ); + + // Testing set_compare_value() methods + pwm.set_compare_value_a(channel_number, 2022); + assert_eq!( + pwm.registers.ch[channel_number as usize].cc.read(CC::A), + 2022 + ); + pwm.set_compare_value_b(channel_number, 12); + assert_eq!(pwm.registers.ch[channel_number as usize].cc.read(CC::B), 12); + pwm.set_compare_values_a_and_b(channel_number, 2023, 1); + assert_eq!( + pwm.registers.ch[channel_number as usize].cc.read(CC::A), + 2023 + ); + assert_eq!(pwm.registers.ch[channel_number as usize].cc.read(CC::B), 1); + + // Testing set_top() + pwm.set_top(channel_number, 12345); + assert_eq!( + pwm.registers.ch[channel_number as usize].top.read(TOP::TOP), + 12345 + ); + + // Testing get_counter() and set_counter() + pwm.set_counter(channel_number, 1); + assert_eq!( + pwm.registers.ch[channel_number as usize].ctr.read(CTR::CTR), + 1 + ); + assert_eq!(pwm.get_counter(channel_number), 1); + + // Testing advance_count and retard_count() + // The counter must be running to pass retard_count() + // The counter must run at less than full speed (div_int + div_frac / 16 > 1) to pass + // advance_count() + pwm.set_div_mode(channel_number, DivMode::FreeRunning); + assert!(pwm.advance_count(channel_number)); + assert_eq!(pwm.get_counter(channel_number), 2); + pwm.set_enabled(channel_number, true); + // No assert for retard count since it is impossible to predict how much the counter + // will advance while running. However, the fact that the function returns true is a + // good indicator that it does its job. + assert!(pwm.retard_count(channel_number)); + // Disabling PWM to prevent it from generating interrupts signals for next tests + pwm.set_enabled(channel_number, false); + + // Testing enable_interrupt() and disable_interrupt() + pwm.enable_interrupt(channel_number); + assert_eq!( + pwm.registers.inte.read(CH::CH), + 1 << (channel_number as u32) + ); + pwm.disable_interrupt(channel_number); + assert_eq!(pwm.registers.inte.read(CH::CH), 0); + + // Testing get_interrupt_status() + pwm.enable_interrupt(channel_number); + pwm.set_counter(channel_number, 12345); + pwm.advance_count(channel_number); + assert!(pwm.get_interrupt_status(channel_number)); + pwm.disable_interrupt(channel_number); + + // Testing clear_interrupt() + pwm.clear_interrupt(channel_number); + assert!(!pwm.get_interrupt_status(channel_number)); + + // Testing force_interrupt(), unforce_interrupt() + pwm.force_interrupt(channel_number); + assert_eq!( + pwm.registers.intf.read(CH::CH), + 1 << (channel_number as u32) + ); + assert!(pwm.get_interrupt_status(channel_number)); + pwm.unforce_interrupt(channel_number); + assert_eq!(pwm.registers.intf.read(CH::CH), 0); + assert!(!pwm.get_interrupt_status(channel_number)); + + debug!("Channel {} works!", channel_number as usize); + } + + fn test_pwm_struct(pwm: &Pwm) { + debug!("Testing PWM struct..."); + let channel_number_list = [ + // Pins 0 and 1 are kept available for UART + ChannelNumber::Ch1, + ChannelNumber::Ch2, + ChannelNumber::Ch3, + ChannelNumber::Ch4, + ChannelNumber::Ch5, + ChannelNumber::Ch6, + ChannelNumber::Ch7, + ]; + + // Testing enable_mask_interrupt() and disable_mask_interrupt() + pwm.enable_mask_interrupt(u8::MAX); + assert_eq!(pwm.registers.inte.read(CH::CH), u8::MAX as u32); + pwm.disable_mask_interrupt(u8::MAX); + assert_eq!(pwm.registers.inte.read(CH::CH), 0); + + for channel_number in channel_number_list { + test_channel(pwm, channel_number); + } + debug!("PWM struct OK"); + } + + fn test_pwm_pin_struct<'a>(pwm: &'a Pwm<'a>) { + debug!("Testing PwmPin struct..."); + let pwm_pin = pwm.gpio_to_pwm_pin(RPGpio::GPIO13); + assert_eq!(pwm_pin.get_channel_number(), ChannelNumber::Ch6); + assert_eq!(pwm_pin.get_channel_pin(), ChannelPin::B); + + pwm_pin.set_invert_polarity(true); + assert_eq!( + pwm.registers.ch[pwm_pin.get_channel_number() as usize] + .csr + .read(CSR::B_INV), + 1 + ); + pwm_pin.set_invert_polarity(false); + assert_eq!( + pwm.registers.ch[pwm_pin.get_channel_number() as usize] + .csr + .read(CSR::B_INV), + 0 + ); + + pwm_pin.set_compare_value(987); + assert_eq!( + pwm.registers.ch[pwm_pin.get_channel_number() as usize] + .cc + .read(CC::B), + 987 + ); + debug!("PwmPin struct OK"); + } + + fn test_pwm_trait(pwm: &Pwm) { + debug!("Testing PWM HIL trait..."); + let max_freq_hz = hil::pwm::Pwm::get_maximum_frequency_hz(pwm); + let max_duty_cycle = hil::pwm::Pwm::get_maximum_duty_cycle(pwm); + + let (top, int, frac) = pwm.compute_top_int_frac(max_freq_hz).unwrap(); + assert_eq!(top, 0); + assert_eq!(int, 1); + assert_eq!(frac, 0); + + let (top, int, frac) = pwm.compute_top_int_frac(max_freq_hz / 4).unwrap(); + assert_eq!(top, 3); + assert_eq!(int, 1); + assert_eq!(frac, 0); + + let (top, int, frac) = pwm + .compute_top_int_frac(max_freq_hz / max_duty_cycle) + .unwrap(); + assert_eq!(top, u16::MAX); + assert_eq!(int, 1); + assert_eq!(frac, 0); + + let (top, int, frac) = pwm + .compute_top_int_frac(max_freq_hz / max_duty_cycle / 2) + .unwrap(); + assert_eq!(top, u16::MAX); + assert_eq!(int, 2); + assert_eq!(frac, 0); + + let freq = ((max_freq_hz / max_duty_cycle) as f32 / 2.5) as usize; + let (top, int, frac) = pwm.compute_top_int_frac(freq).unwrap(); + assert_eq!(top, u16::MAX); + assert_eq!(int, 2); + assert_eq!(frac, 8); + + let freq = ((max_freq_hz / max_duty_cycle) as f32 / 3.15) as usize; + let (top, int, frac) = pwm.compute_top_int_frac(freq).unwrap(); + assert_eq!(top, u16::MAX); + assert_eq!(int, 3); + assert_eq!(frac, 2); + + assert!(pwm + .compute_top_int_frac(max_freq_hz / max_duty_cycle / 256) + .is_err()); + assert!(pwm.compute_top_int_frac(max_freq_hz + 1).is_err()); + + let (channel_number, channel_pin) = pwm.gpio_to_pwm(RPGpio::GPIO24); + assert!(pwm + .start_pwm_pin(channel_number, channel_pin, max_freq_hz / 4, 0) + .is_ok()); + assert_eq!(pwm.registers.ch[channel_number as usize].cc.read(CC::A), 0); + + assert!(pwm + .start_pwm_pin( + channel_number, + channel_pin, + max_freq_hz / 4, + max_duty_cycle / 4 * 3 + ) + .is_ok()); + assert_eq!(pwm.registers.ch[channel_number as usize].cc.read(CC::A), 3); + + assert!(pwm + .start_pwm_pin(channel_number, channel_pin, max_freq_hz / 4, max_duty_cycle) + .is_ok()); + assert_eq!(pwm.registers.ch[channel_number as usize].cc.read(CC::A), 4); + + assert!(pwm + .start_pwm_pin( + channel_number, + channel_pin, + max_freq_hz / max_duty_cycle, + max_duty_cycle + ) + .is_err()); + assert!(pwm + .start_pwm_pin(channel_number, channel_pin, max_freq_hz + 1, max_duty_cycle) + .is_err()); + assert!(pwm + .start_pwm_pin(channel_number, channel_pin, max_freq_hz, max_duty_cycle + 1) + .is_err()); + debug!("PWM HIL trait OK") + } + + /// Run all unit tests + /// + /// pwm must be initialized and its dependencies resolved. + pub fn run<'a>(pwm: &'a Pwm<'a>) { + test_channel_number(); + test_channel_pin(); + test_pwm_struct(pwm); + test_pwm_pin_struct(pwm); + test_pwm_trait(pwm); + } +} diff --git a/chips/rp2040/src/resets.rs b/chips/rp2040/src/resets.rs index e5f4c2e7ef..c2139cc41c 100644 --- a/chips/rp2040/src/resets.rs +++ b/chips/rp2040/src/resets.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; use kernel::utilities::registers::{register_bitfields, register_structs, FieldValue, ReadWrite}; use kernel::utilities::StaticRef; @@ -309,7 +313,7 @@ impl Resets { if peripherals.len() > 0 { let mut value: FieldValue = peripherals[0].get_reset_field_set(); for peripheral in peripherals { - value = value + peripheral.get_reset_field_set(); + value += peripheral.get_reset_field_set(); } self.registers.reset.modify(value); } @@ -320,7 +324,7 @@ impl Resets { let mut value: FieldValue = peripherals[0].get_reset_field_clear(); for peripheral in peripherals { - value = value + peripheral.get_reset_field_clear(); + value += peripheral.get_reset_field_clear(); } self.registers.reset.modify(value); @@ -328,7 +332,7 @@ impl Resets { let mut value_done: FieldValue = peripherals[0].get_reset_done_field_set(); for peripheral in peripherals { - value_done = value_done + peripheral.get_reset_done_field_set(); + value_done += peripheral.get_reset_done_field_set(); } while !self.registers.reset_done.matches_all(value_done) {} } @@ -356,4 +360,12 @@ impl Resets { while (self.registers.reset_done.get() & value) != value {} } } + + pub fn watchdog_reset_all_except(&self, peripherals: &'static [Peripheral]) { + let mut value = 0xFFFFFF; + for peripheral in peripherals { + value ^= peripheral.get_reset_field_set().value; + } + self.registers.wdsel.set(value); + } } diff --git a/chips/rp2040/src/rtc.rs b/chips/rp2040/src/rtc.rs new file mode 100644 index 0000000000..d51249c4d0 --- /dev/null +++ b/chips/rp2040/src/rtc.rs @@ -0,0 +1,454 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Real Time Clock (RTC) driver for RP2040. +//! +//! Author: Irina Bradu +//! Remus Rughinis +//! +//! # Hardware Interface Layer (HIL) +//! +//! The driver implements Date_Time HIL. The following features are available when using +//! the driver through HIL: +//! +//! + Set time from which real time clock should start counting +//! + Read current time from the RTC registers +//! + +use crate::clocks; +use core::cell::Cell; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; +use kernel::hil::date_time; +use kernel::hil::date_time::{DateTimeClient, DateTimeValues, DayOfWeek, Month}; +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; +use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite}; +use kernel::utilities::StaticRef; +use kernel::ErrorCode; + +register_structs! { + /// Register block to control RTC + RtcRegisters { + /// Divider minus 1 for the 1 second counter. Safe to change the value when RTC is n + (0x000 => clkdiv_m1: ReadWrite), + /// RTC setup register 0 + (0x004 => setup_0: ReadWrite), + /// RTC setup register 1 + (0x008 => setup_1: ReadWrite), + /// RTC Control and status + (0x00C => ctrl: ReadWrite), + /// Interrupt setup register 0 + (0x010 => irq_setup_0: ReadWrite), + /// Interrupt setup register 1 + (0x014 => irq_setup_1: ReadWrite), + /// RTC register 1. + (0x018 => rtc_1: ReadWrite), + /// RTC register 0\n + /// Read this before RTC 1! + (0x01C => rtc_0: ReadWrite), + /// Raw Interrupts + (0x020 => intr: ReadWrite), + /// Interrupt Enable + (0x024 => inte: ReadWrite), + /// Interrupt Force + (0x028 => intf: ReadWrite), + /// Interrupt status after masking & forcing + (0x02C => ints: ReadWrite), + (0x030 => @END), + } +} +register_bitfields![u32, +CLKDIV_M1 [ + CLKDIV_M OFFSET(0) NUMBITS(16) [] +], +SETUP_0 [ + /// Year + YEAR OFFSET(12) NUMBITS(12) [], + /// Month (1..12) + MONTH OFFSET(8) NUMBITS(4) [], + /// Day of the month (1..31) + DAY OFFSET(0) NUMBITS(5) [] +], +SETUP_1 [ + /// Day of the week: 1-Monday...0-Sunday ISO 8601 mod 7 + DOTW OFFSET(24) NUMBITS(3) [], + /// Hours + HOUR OFFSET(16) NUMBITS(5) [], + /// Minutes + MIN OFFSET(8) NUMBITS(6) [], + /// Seconds + SEC OFFSET(0) NUMBITS(6) [] +], +CTRL [ + /// If set, leapyear is forced off.\n + /// Useful for years divisible by 100 but not by 400 + FORCE_NOTLEAPYEAR OFFSET(8) NUMBITS(1) [], + /// Load RTC + LOAD OFFSET(4) NUMBITS(1) [], + /// RTC enabled (running) + RTC_ACTIVE OFFSET(1) NUMBITS(1) [], + /// Enable RTC + RTC_ENABLE OFFSET(0) NUMBITS(1) [] +], +IRQ_SETUP_0 [ + MATCH_ACTIVE OFFSET(29) NUMBITS(1) [], + /// Global match enable. Don't change any other value while this one is enabled + MATCH_ENA OFFSET(28) NUMBITS(1) [], + /// Enable year matching + YEAR_ENA OFFSET(26) NUMBITS(1) [], + /// Enable month matching + MONTH_ENA OFFSET(25) NUMBITS(1) [], + /// Enable day matching + DAY_ENA OFFSET(24) NUMBITS(1) [], + /// Year + YEAR OFFSET(12) NUMBITS(12) [], + /// Month (1..12) + MONTH OFFSET(8) NUMBITS(4) [], + /// Day of the month (1..31) + DAY OFFSET(0) NUMBITS(5) [] +], +IRQ_SETUP_1 [ + /// Enable day of the week matching + DOTW_ENA OFFSET(31) NUMBITS(1) [], + /// Enable hour matching + HOUR_ENA OFFSET(30) NUMBITS(1) [], + /// Enable minute matching + MIN_ENA OFFSET(29) NUMBITS(1) [], + /// Enable second matching + SEC_ENA OFFSET(28) NUMBITS(1) [], + /// Day of the week + DOTW OFFSET(24) NUMBITS(3) [], + /// Hours + HOUR OFFSET(16) NUMBITS(5) [], + /// Minutes + MIN OFFSET(8) NUMBITS(6) [], + /// Seconds + SEC OFFSET(0) NUMBITS(6) [] +], +RTC_1 [ + /// Year + YEAR OFFSET(12) NUMBITS(12) [], + /// Month (1..12) + MONTH OFFSET(8) NUMBITS(4) [], + /// Day of the month (1..31) + DAY OFFSET(0) NUMBITS(5) [] +], +RTC_0 [ + /// Day of the week + DOTW OFFSET(24) NUMBITS(3) [], + /// Hours + HOUR OFFSET(16) NUMBITS(5) [], + /// Minutes + MIN OFFSET(8) NUMBITS(6) [], + /// Seconds + SEC OFFSET(0) NUMBITS(6) [] +], +INTR [ + + RTC OFFSET(0) NUMBITS(1) [] +], +INTE [ + + RTC OFFSET(0) NUMBITS(1) [] +], +INTF [ + + RTC OFFSET(0) NUMBITS(1) [] +], +INTS [ + + RTC OFFSET(0) NUMBITS(1) [] +] +]; + +const RTC_BASE: StaticRef = + unsafe { StaticRef::new(0x4005C000 as *const RtcRegisters) }; + +pub struct Rtc<'a> { + registers: StaticRef, + client: OptionalCell<&'a dyn date_time::DateTimeClient>, + clocks: OptionalCell<&'a clocks::Clocks>, + time: Cell, + + deferred_call: DeferredCall, + deferred_call_task: OptionalCell, +} + +#[derive(Clone, Copy)] +enum DeferredCallTask { + Get, + Set, +} + +impl<'a> DeferredCallClient for Rtc<'a> { + fn handle_deferred_call(&self) { + self.deferred_call_task.take().map(|value| match value { + DeferredCallTask::Get => self + .client + .map(|client| client.get_date_time_done(Ok(self.time.get()))), + DeferredCallTask::Set => self.client.map(|client| client.set_date_time_done(Ok(()))), + }); + } + fn register(&'static self) { + self.deferred_call.register(self); + } +} + +impl<'a> Rtc<'a> { + pub fn new() -> Rtc<'a> { + Rtc { + registers: RTC_BASE, + client: OptionalCell::empty(), + clocks: OptionalCell::empty(), + time: Cell::new(DateTimeValues { + year: 0, + month: Month::January, + day: 1, + day_of_week: DayOfWeek::Sunday, + hour: 0, + minute: 0, + seconds: 0, + }), + + deferred_call: DeferredCall::new(), + deferred_call_task: OptionalCell::empty(), + } + } + + fn dotw_try_from_u32(&self, dotw: u32) -> Result { + match dotw { + 0 => Ok(DayOfWeek::Sunday), + 1 => Ok(DayOfWeek::Monday), + 2 => Ok(DayOfWeek::Tuesday), + 3 => Ok(DayOfWeek::Wednesday), + 4 => Ok(DayOfWeek::Thursday), + 5 => Ok(DayOfWeek::Friday), + 6 => Ok(DayOfWeek::Saturday), + _ => Err(ErrorCode::INVAL), + } + } + + fn dotw_into_u32(&self, dotw: DayOfWeek) -> u32 { + match dotw { + DayOfWeek::Sunday => 0, + DayOfWeek::Monday => 1, + DayOfWeek::Tuesday => 2, + DayOfWeek::Wednesday => 3, + DayOfWeek::Thursday => 4, + DayOfWeek::Friday => 5, + DayOfWeek::Saturday => 6, + } + } + + fn month_try_from_u32(&self, month_num: u32) -> Result { + match month_num { + 1 => Ok(Month::January), + 2 => Ok(Month::February), + 3 => Ok(Month::March), + 4 => Ok(Month::April), + 5 => Ok(Month::May), + 6 => Ok(Month::June), + 7 => Ok(Month::July), + 8 => Ok(Month::August), + 9 => Ok(Month::September), + 10 => Ok(Month::October), + 11 => Ok(Month::November), + 12 => Ok(Month::December), + _ => Err(ErrorCode::INVAL), + } + } + + fn month_into_u32(&self, month: Month) -> u32 { + match month { + Month::January => 1, + Month::February => 2, + Month::March => 3, + Month::April => 4, + Month::May => 5, + Month::June => 6, + Month::July => 7, + Month::August => 8, + Month::September => 9, + Month::October => 10, + Month::November => 11, + Month::December => 12, + } + } + + pub fn handle_set_interrupt(&self) { + self.client.map(|client| client.set_date_time_done(Ok(()))); + } + + pub fn handle_get_interrupt(&self) { + self.client + .map(|client| client.get_date_time_done(Ok(self.time.get()))); + } + + pub fn set_clocks(&self, clocks: &'a clocks::Clocks) { + self.clocks.replace(clocks); + } + + fn date_time_setup(&self, datetime: date_time::DateTimeValues) -> Result<(), ErrorCode> { + let month_val: u32 = self.month_into_u32(datetime.month); + let day_val: u32 = self.dotw_into_u32(datetime.day_of_week); + + if !(datetime.year <= 4095) { + return Err(ErrorCode::INVAL); + } + + if !(datetime.day >= 1 && datetime.day <= 31) { + return Err(ErrorCode::INVAL); + } + + if !(datetime.hour <= 23) { + return Err(ErrorCode::INVAL); + } + if !(datetime.minute <= 59) { + return Err(ErrorCode::INVAL); + } + if !(datetime.seconds <= 59) { + return Err(ErrorCode::INVAL); + } + + self.registers + .setup_0 + .modify(SETUP_0::YEAR.val(datetime.year as u32)); + self.registers.setup_0.modify(SETUP_0::MONTH.val(month_val)); + self.registers + .setup_0 + .modify(SETUP_0::DAY.val(datetime.day as u32)); + + self.registers.setup_1.modify(SETUP_1::DOTW.val(day_val)); + self.registers + .setup_1 + .modify(SETUP_1::HOUR.val(datetime.hour as u32)); + self.registers + .setup_1 + .modify(SETUP_1::MIN.val(datetime.minute as u32)); + self.registers + .setup_1 + .modify(SETUP_1::SEC.val(datetime.seconds as u32)); + + self.registers.ctrl.modify(CTRL::LOAD::SET); + + Ok(()) + } + + fn set_initial(&self) -> Result<(), ErrorCode> { + let mut hw_ctrl: u32; + + self.registers.ctrl.modify(CTRL::RTC_ENABLE.val(0)); + hw_ctrl = self.registers.ctrl.read(CTRL::RTC_ENABLE); + + while hw_ctrl & self.registers.ctrl.read(CTRL::RTC_ACTIVE) > 0 {} + + let datetime = date_time::DateTimeValues { + year: 1970, + month: Month::January, + day: 1, + day_of_week: DayOfWeek::Sunday, + hour: 0, + minute: 0, + seconds: 0, + }; + + self.date_time_setup(datetime)?; + self.registers.ctrl.modify(CTRL::LOAD::SET); + self.registers.ctrl.modify(CTRL::RTC_ENABLE.val(1)); + hw_ctrl = self.registers.ctrl.read(CTRL::RTC_ENABLE); + + while !((hw_ctrl & self.registers.ctrl.read(CTRL::RTC_ACTIVE)) > 0) { + // wait until rtc starts + } + + Ok(()) + } + + pub fn rtc_init(&self) -> Result<(), ErrorCode> { + let mut rtc_freq = self + .clocks + .map_or(46875, |clocks| clocks.get_frequency(clocks::Clock::Rtc)); + + rtc_freq -= rtc_freq; + + self.registers + .clkdiv_m1 + .modify(CLKDIV_M1::CLKDIV_M.val(rtc_freq)); + + self.set_initial() + } +} + +impl<'a> date_time::DateTime<'a> for Rtc<'a> { + fn get_date_time(&self) -> Result<(), ErrorCode> { + match self.deferred_call_task.take() { + Some(DeferredCallTask::Set) => { + self.deferred_call_task.insert(Some(DeferredCallTask::Set)); + return Err(ErrorCode::BUSY); + } + Some(DeferredCallTask::Get) => { + self.deferred_call_task.insert(Some(DeferredCallTask::Get)); + return Err(ErrorCode::ALREADY); + } + _ => (), + } + + let month_num: u32 = self.registers.setup_0.read(SETUP_0::MONTH); + let month_name: Month = match self.month_try_from_u32(month_num) { + Result::Ok(t) => t, + Result::Err(e) => { + return Err(e); + } + }; + let dotw_num = self.registers.setup_1.read(SETUP_1::DOTW); + let dotw = match self.dotw_try_from_u32(dotw_num) { + Result::Ok(t) => t, + Result::Err(e) => { + return Err(e); + } + }; + + let datetime = date_time::DateTimeValues { + hour: self.registers.rtc_0.read(RTC_0::HOUR) as u8, + minute: self.registers.rtc_0.read(RTC_0::MIN) as u8, + seconds: self.registers.rtc_0.read(RTC_0::SEC) as u8, + + year: self.registers.rtc_1.read(RTC_1::YEAR) as u16, + month: month_name, + day: self.registers.rtc_1.read(RTC_1::DAY) as u8, + day_of_week: dotw, + }; + + self.time.replace(datetime); + + self.deferred_call_task.insert(Some(DeferredCallTask::Get)); + self.deferred_call.set(); + + Ok(()) + } + + fn set_date_time(&self, date_time: date_time::DateTimeValues) -> Result<(), ErrorCode> { + match self.deferred_call_task.take() { + Some(DeferredCallTask::Set) => { + self.deferred_call_task.insert(Some(DeferredCallTask::Set)); + return Err(ErrorCode::ALREADY); + } + Some(DeferredCallTask::Get) => { + self.deferred_call_task.insert(Some(DeferredCallTask::Get)); + return Err(ErrorCode::BUSY); + } + _ => (), + } + + self.date_time_setup(date_time)?; + + self.deferred_call_task.insert(Some(DeferredCallTask::Set)); + self.deferred_call.set(); + Ok(()) + } + + fn set_client(&self, client: &'a dyn DateTimeClient) { + self.client.set(client); + } +} diff --git a/chips/rp2040/src/spi.rs b/chips/rp2040/src/spi.rs index b036de44cd..0660b0ebb3 100644 --- a/chips/rp2040/src/spi.rs +++ b/chips/rp2040/src/spi.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use crate::clocks; use core::cell::Cell; use core::cmp; @@ -248,7 +252,7 @@ pub struct Spi<'a> { } impl<'a> Spi<'a> { - pub const fn new_spi0() -> Self { + pub fn new_spi0() -> Self { Self { registers: SPI0_BASE, clocks: OptionalCell::empty(), @@ -268,7 +272,7 @@ impl<'a> Spi<'a> { } } - pub const fn new_spi1() -> Self { + pub fn new_spi1() -> Self { Self { registers: SPI1_BASE, clocks: OptionalCell::empty(), @@ -288,7 +292,7 @@ impl<'a> Spi<'a> { } } - pub fn set_clocks(&self, clocks: &'a clocks::Clocks) { + pub(crate) fn set_clocks(&self, clocks: &'a clocks::Clocks) { self.clocks.set(clocks); } @@ -481,10 +485,10 @@ impl<'a> Spi<'a> { } } -impl<'a> SpiMaster for Spi<'a> { +impl<'a> SpiMaster<'a> for Spi<'a> { type ChipSelect = &'a crate::gpio::RPGpioPin<'a>; - fn set_client(&self, client: &'static dyn SpiMasterClient) { + fn set_client(&self, client: &'a dyn SpiMasterClient) { self.master_client.set(client); } @@ -507,7 +511,8 @@ impl<'a> SpiMaster for Spi<'a> { } fn is_busy(&self) -> bool { - self.registers.sspsr.is_set(SSPSR::BSY) + // self.registers.sspsr.is_set(SSPSR::BSY) + self.transfers.get() != SPI_IDLE } fn read_write_bytes( @@ -547,9 +552,7 @@ impl<'a> SpiMaster for Spi<'a> { fn read_write_byte(&self, val: u8) -> Result { if !self.is_busy() { - if let Err(error) = self.write_byte(val) { - return Err(error); - } + self.write_byte(val)?; while !self.registers.sspsr.is_set(SSPSR::RNE) {} diff --git a/chips/rp2040/src/sysinfo.rs b/chips/rp2040/src/sysinfo.rs index 96a6202ec7..9ef0d96742 100644 --- a/chips/rp2040/src/sysinfo.rs +++ b/chips/rp2040/src/sysinfo.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::utilities::registers::interfaces::Readable; use kernel::utilities::StaticRef; @@ -58,16 +62,16 @@ impl SysInfo { } } - pub fn get_revision(&self) -> u32 { - self.registers.chip_id.read(CHIP_ID::REVISION) + pub fn get_revision(&self) -> u8 { + self.registers.chip_id.read(CHIP_ID::REVISION) as u8 } - pub fn get_part(&self) -> u32 { - self.registers.chip_id.read(CHIP_ID::PART) + pub fn get_part(&self) -> u16 { + self.registers.chip_id.read(CHIP_ID::PART) as u16 } - pub fn get_manufacturer_rp2040(&self) -> u32 { - self.registers.chip_id.read(CHIP_ID::MANUFACTURER) + pub fn get_manufacturer_rp2040(&self) -> u16 { + self.registers.chip_id.read(CHIP_ID::MANUFACTURER) as u16 } pub fn get_asic(&self) -> u32 { diff --git a/chips/rp2040/src/test/mod.rs b/chips/rp2040/src/test/mod.rs new file mode 100644 index 0000000000..7585b5faf1 --- /dev/null +++ b/chips/rp2040/src/test/mod.rs @@ -0,0 +1,5 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +pub mod pwm; diff --git a/chips/rp2040/src/test/pwm.rs b/chips/rp2040/src/test/pwm.rs new file mode 100644 index 0000000000..98af47480c --- /dev/null +++ b/chips/rp2040/src/test/pwm.rs @@ -0,0 +1,76 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Integration tests for PWM peripheral +//! +//! This module provides four integration tests: +//! +//! ## hello_pwm +//! +//! This test sets up GPIOs 14 and 15 as PWM pins. GPIO 15 should be much brighter than 14. +//! +//! ## Running the test +//! +//! First step is including the test module: +//! +//! ```rust,ignore +//! #[allow(dead_code)] +//! use rp2040::test; +//! ``` +//! +//! Then create a test instance: +//! +//! ```rust,ignore +//! let pwm_test = test::pwm::new(peripherals); +//! ``` +//! +//! Then run the test: +//! +//! ```rust,ignore +//! pwm_test.hello_pwm(); +//! ``` + +use kernel::debug; +use kernel::hil::pwm::Pwm; +use kernel::hil::pwm::PwmPin; + +use crate::chip::Rp2040DefaultPeripherals; +use crate::gpio::{GpioFunction, RPGpio}; + +/// Struct used to run integration tests +pub struct PwmTest { + peripherals: &'static Rp2040DefaultPeripherals<'static>, +} + +/// Create a PwmTest to run tests +pub fn new(peripherals: &'static Rp2040DefaultPeripherals<'static>) -> PwmTest { + PwmTest { peripherals } +} + +impl PwmTest { + /// Run hello_pwm test + pub fn hello_pwm(&self) { + self.peripherals + .pins + .get_pin(RPGpio::GPIO14) + .set_function(GpioFunction::PWM); + self.peripherals + .pins + .get_pin(RPGpio::GPIO15) + .set_function(GpioFunction::PWM); + let pwm_pin_14 = self.peripherals.pwm.gpio_to_pwm_pin(RPGpio::GPIO14); + let max_freq = pwm_pin_14.get_maximum_frequency_hz(); + let max_duty_cycle = pwm_pin_14.get_maximum_duty_cycle(); + assert_eq!(pwm_pin_14.start(max_freq / 8, max_duty_cycle / 2), Ok(())); + let pwm = &self.peripherals.pwm; + debug!("PWM pin 14 started"); + let max_freq = pwm.get_maximum_frequency_hz(); + let max_duty_cycle = pwm.get_maximum_duty_cycle(); + assert_eq!( + pwm.start(&RPGpio::GPIO15, max_freq / 8, max_duty_cycle / 8 * 7), + Ok(()) + ); + debug!("PWM pin 15 started"); + } +} diff --git a/chips/rp2040/src/timer.rs b/chips/rp2040/src/timer.rs index 73aad5ba01..ff4f44aeaf 100644 --- a/chips/rp2040/src/timer.rs +++ b/chips/rp2040/src/timer.rs @@ -1,4 +1,7 @@ -use cortexm0p; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use cortexm0p::support::atomic; use kernel::hil; use kernel::hil::time::{Alarm, Ticks, Ticks32, Time}; diff --git a/chips/rp2040/src/uart.rs b/chips/rp2040/src/uart.rs index fb7e8a6594..eef54cea2f 100644 --- a/chips/rp2040/src/uart.rs +++ b/chips/rp2040/src/uart.rs @@ -1,4 +1,9 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::cell::Cell; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; use kernel::hil; use kernel::hil::uart::ReceiveClient; use kernel::hil::uart::{ @@ -389,10 +394,12 @@ pub struct Uart<'a> { rx_position: Cell, rx_len: Cell, rx_status: Cell, + + deferred_call: DeferredCall, } impl<'a> Uart<'a> { - pub const fn new_uart0() -> Self { + pub fn new_uart0() -> Self { Self { registers: UART0_BASE, clocks: OptionalCell::empty(), @@ -409,9 +416,11 @@ impl<'a> Uart<'a> { rx_position: Cell::new(0), rx_len: Cell::new(0), rx_status: Cell::new(UARTStateRX::Idle), + + deferred_call: DeferredCall::new(), } } - pub const fn new_uart1() -> Self { + pub fn new_uart1() -> Self { Self { registers: UART1_BASE, clocks: OptionalCell::empty(), @@ -427,10 +436,12 @@ impl<'a> Uart<'a> { rx_position: Cell::new(0), rx_len: Cell::new(0), rx_status: Cell::new(UARTStateRX::Idle), + + deferred_call: DeferredCall::new(), } } - pub fn set_clocks(&self, clocks: &'a clocks::Clocks) { + pub(crate) fn set_clocks(&self, clocks: &'a clocks::Clocks) { self.clocks.set(clocks); } @@ -489,17 +500,6 @@ impl<'a> Uart<'a> { }); }); } - } else if self.tx_status.get() == UARTStateTX::AbortRequested { - self.tx_status.replace(UARTStateTX::Idle); - self.tx_client.map(|client| { - if let Some(buf) = self.tx_buffer.take() { - client.transmitted_buffer( - buf, - self.tx_position.get(), - Err(ErrorCode::CANCEL), - ); - } - }); } } } @@ -536,18 +536,6 @@ impl<'a> Uart<'a> { }); } } - } else if self.rx_status.get() == UARTStateRX::AbortRequested { - self.rx_status.replace(UARTStateRX::Idle); - self.rx_client.map(|client| { - if let Some(buf) = self.rx_buffer.take() { - client.received_buffer( - buf, - self.rx_position.get(), - Err(ErrorCode::CANCEL), - hil::uart::Error::Aborted, - ); - } - }); } } } @@ -564,13 +552,41 @@ impl<'a> Uart<'a> { } pub fn is_configured(&self) -> bool { - if self.registers.uartcr.is_set(UARTCR::UARTEN) + self.registers.uartcr.is_set(UARTCR::UARTEN) && (self.registers.uartcr.is_set(UARTCR::RXE) || self.registers.uartcr.is_set(UARTCR::TXE)) - { - true - } else { - false + } +} + +impl DeferredCallClient for Uart<'_> { + fn register(&'static self) { + self.deferred_call.register(self) + } + + fn handle_deferred_call(&self) { + if self.tx_status.get() == UARTStateTX::AbortRequested { + // alert client + self.tx_client.map(|client| { + self.tx_buffer.take().map(|buf| { + client.transmitted_buffer(buf, self.tx_position.get(), Err(ErrorCode::CANCEL)); + }); + }); + self.tx_status.set(UARTStateTX::Idle); + } + + if self.rx_status.get() == UARTStateRX::AbortRequested { + // alert client + self.rx_client.map(|client| { + self.rx_buffer.take().map(|buf| { + client.received_buffer( + buf, + self.rx_position.get(), + Err(ErrorCode::CANCEL), + hil::uart::Error::Aborted, + ); + }); + }); + self.rx_status.set(UARTStateRX::Idle); } } } @@ -693,7 +709,16 @@ impl<'a> Transmit<'a> for Uart<'a> { } fn transmit_abort(&self) -> Result<(), ErrorCode> { - Err(ErrorCode::FAIL) + if self.tx_status.get() != UARTStateTX::Idle { + self.disable_transmit_interrupt(); + self.tx_status.set(UARTStateTX::AbortRequested); + + self.deferred_call.set(); + + Err(ErrorCode::BUSY) + } else { + Ok(()) + } } } @@ -729,7 +754,11 @@ impl<'a> Receive<'a> for Uart<'a> { fn receive_abort(&self) -> Result<(), ErrorCode> { if self.rx_status.get() != UARTStateRX::Idle { + self.disable_receive_interrupt(); self.rx_status.set(UARTStateRX::AbortRequested); + + self.deferred_call.set(); + Err(ErrorCode::BUSY) } else { Ok(()) diff --git a/chips/rp2040/src/usb.rs b/chips/rp2040/src/usb.rs new file mode 100644 index 0000000000..3d740d87d3 --- /dev/null +++ b/chips/rp2040/src/usb.rs @@ -0,0 +1,2485 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Universal Serial Bus Device for Raspberry Pi Pico + +//! Authors: Cosmin Radu +//! Teodora Miu + +use crate::gpio::RPGpioPin; +use core::cell::Cell; +use kernel::hil; +use kernel::hil::usb::TransferType; +use kernel::utilities::cells::{OptionalCell, VolatileCell}; +use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; +use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite}; +use kernel::utilities::StaticRef; + +macro_rules! internal_err { + [ $( $arg:expr ),+ ] => { + panic!($( $arg ),+) + }; +} + +register_structs! { + Ep_ctrl { + (0x00 => ep_in_ctrl: ReadWrite), + (0x04 => ep_out_ctrl: ReadWrite), + (0x08 => @END), + } +} + +register_structs! { + Ep_buf_ctrl { + (0x00 => ep_in_buf_ctrl: ReadWrite), + (0x04 => ep_out_buf_ctrl: ReadWrite), + (0x08 => @END), + } +} + +register_structs! { + /// USB FS/LS controller device registers + Usbctrl_DPSRAM { + /// Device address and endpoint control + (0x00 => setup_h: ReadWrite), + (0x04 => setup_l: ReadWrite), + (0x08 => ep_ctrl: [Ep_ctrl; 15]), + (0x80 => ep_buf_ctrl: [Ep_buf_ctrl; 16]), + (0x100 => ep0_buffer0: [VolatileCell; 0x40]), + (0x140 => optional_ep0_buffer0: [VolatileCell; 0x40]), + (0x180 => buffers: [VolatileCell; 4096-0x180]), + (0x1000 => @END), + } +} + +register_structs! { + /// USB FS/LS controller device registers + Usbctrl_RegsRegisters { + /// Device address and endpoint control + (0x000 => addr_endp: ReadWrite), + /// Interrupt endpoint 1. Only valid for HOST mode. + (0x004 => addr_endp1: ReadWrite), + /// Interrupt endpoint 2. Only valid for HOST mode. + (0x008 => addr_endp2: ReadWrite), + /// Interrupt endpoint 3. Only valid for HOST mode. + (0x00C => addr_endp3: ReadWrite), + /// Interrupt endpoint 4. Only valid for HOST mode. + (0x010 => addr_endp4: ReadWrite), + /// Interrupt endpoint 5. Only valid for HOST mode. + (0x014 => addr_endp5: ReadWrite), + /// Interrupt endpoint 6. Only valid for HOST mode. + (0x018 => addr_endp6: ReadWrite), + /// Interrupt endpoint 7. Only valid for HOST mode. + (0x01C => addr_endp7: ReadWrite), + /// Interrupt endpoint 8. Only valid for HOST mode. + (0x020 => addr_endp8: ReadWrite), + /// Interrupt endpoint 9. Only valid for HOST mode. + (0x024 => addr_endp9: ReadWrite), + /// Interrupt endpoint 10. Only valid for HOST mode. + (0x028 => addr_endp10: ReadWrite), + /// Interrupt endpoint 11. Only valid for HOST mode. + (0x02C => addr_endp11: ReadWrite), + /// Interrupt endpoint 12. Only valid for HOST mode. + (0x030 => addr_endp12: ReadWrite), + /// Interrupt endpoint 13. Only valid for HOST mode. + (0x034 => addr_endp13: ReadWrite), + /// Interrupt endpoint 14. Only valid for HOST mode. + (0x038 => addr_endp14: ReadWrite), + /// Interrupt endpoint 15. Only valid for HOST mode. + (0x03C => addr_endp15: ReadWrite), + /// Main control register + (0x040 => main_ctrl: ReadWrite), + /// Set the SOF (Start of Frame) frame number in the host controller. + /// The SOF packet is sent every 1ms and the host will increment the + /// frame number by 1 each time. + (0x044 => sof_wr: ReadWrite), + /// Read the last SOF (Start of Frame) frame number seen. In device + /// mode the last SOF received from the host. In host mode the last + /// SOF sent by the host. + (0x048 => sof_rd: ReadWrite), + /// SIE control register + (0x04C => sie_ctrl: ReadWrite), + /// SIE status register + (0x050 => sie_status: ReadWrite), + /// interrupt endpoint control register + (0x054 => int_ep_ctrl: ReadWrite), + /// Buffer status register. A bit set here indicates that a buffer has + /// completed on the endpoint (if the buffer interrupt is enabled). It + /// is possible for 2 buffers to be completed, so clearing the buffer + /// status bit may instantly re set it on the next clock cycle. + (0x058 => buff_status: ReadWrite), + /// Which of the double buffers should be handled. Only valid if + /// using an interrupt per buffer (i.e. not per 2 buffers). Not valid for + /// host interrupt endpoint polling because they are only single + /// buffered. + (0x05C => buff_cpu_should_handle: ReadWrite), + /// Device only: Can be set to ignore the buffer control register for + /// this endpoint in case you would like to revoke a buffer. A NAK + /// will be sent for every access to the endpoint until this bit is + /// cleared. A corresponding bit in EP_ABORT_DONE is set when it is safe + /// to modify the buffer control register. + (0x060 => ep_abort: ReadWrite), + /// Device only: Used in conjunction with EP_ABORT. Set once an + /// endpoint is idle so the programmer knows it is safe to modify the + /// buffer control register. + (0x064 => ep_abort_done: ReadWrite), + /// Device: this bit must be set in conjunction with the STALL bit in the + /// buffer control register to send a STALL on EP0. The device + /// controller clears these bits when a SETUP packet is received + /// because the USB spec requires that a STALL condition is cleared + /// when a SETUP packet is received. + (0x068 => ep_stall_arm: ReadWrite), + /// Used by the host controller. Sets the wait time in microseconds + /// before trying again if the device replies with a NAK. + (0x06C => nak_poll: ReadWrite), + /// Device: bits are set when the IRQ_ON_NAK or IRQ_ON_STALL bits are + /// set. For EP0 this comes from SIE_CTRL. For all other endpoints it + /// comes from the endpoint control register. + (0x070 => ep_status_stall_nak: ReadWrite), + /// Where to connect the USB controller. Should be to_phy by default. + (0x074 => usb_muxing: ReadWrite), + /// Overrides for the power signals in the event that the VBUS + /// signals are not hooked up to GPIO. Set the value of the override + /// and then the override enable to switch over to the override value. + (0x078 => usb_pwr: ReadWrite), + /// This register allows for direct control of the USB phy. Use in + /// conjunction with usbphy_direct_override register to enable each + /// override bit. + (0x07C => usbphy_direct: ReadWrite), + /// Override enable for each control in usbphy_direct + (0x080 => usbphy_direct_override: ReadWrite), + /// Used to adjust trim values of USB phy pull down resistors. + (0x084 => usbphy_trim: ReadWrite), + (0x088 => _reserved0), + /// Raw Interrupts + (0x08C => intr: ReadWrite), + /// Interrupt Enable + (0x090 => inte: ReadWrite), + /// Interrupt Force + (0x094 => intf: ReadWrite), + /// Interrupt status after masking & forcing + (0x098 => ints: ReadWrite), + (0x09C => @END), + } +} +register_bitfields![u32, +ADDR_ENDP [ + /// Device endpoint to send data to. Only valid for HOST mode. + ENDPOINT OFFSET(16) NUMBITS(4) [], + /// In device mode, the address that the device should + /// respond to. Set in response to a SET_ADDR setup packet + /// from the host. In host mode set to the address of the + /// device to communicate with. + ADDRESS OFFSET(0) NUMBITS(7) [] +], +ADDR_ENDP1 [ + /// Interrupt EP requires preamble (is a low speed device on a full speed hub) + INTEP_PREAMBLE OFFSET(26) NUMBITS(1) [], + /// Direction of the interrupt endpoint. In=0, Out=1 + INTEP_DIR OFFSET(25) NUMBITS(1) [], + /// Endpoint number of the interrupt endpoint + ENDPOINT OFFSET(16) NUMBITS(4) [], + /// Device address + ADDRESS OFFSET(0) NUMBITS(7) [] +], +ADDR_ENDP2 [ + /// Interrupt EP requires preamble (is a low speed device on a full speed hub) + INTEP_PREAMBLE OFFSET(26) NUMBITS(1) [], + /// Direction of the interrupt endpoint. In=0, Out=1 + INTEP_DIR OFFSET(25) NUMBITS(1) [], + /// Endpoint number of the interrupt endpoint + ENDPOINT OFFSET(16) NUMBITS(4) [], + /// Device address + ADDRESS OFFSET(0) NUMBITS(7) [] +], +ADDR_ENDP3 [ + /// Interrupt EP requires preamble (is a low speed device on a full speed hub) + INTEP_PREAMBLE OFFSET(26) NUMBITS(1) [], + /// Direction of the interrupt endpoint. In=0, Out=1 + INTEP_DIR OFFSET(25) NUMBITS(1) [], + /// Endpoint number of the interrupt endpoint + ENDPOINT OFFSET(16) NUMBITS(4) [], + /// Device address + ADDRESS OFFSET(0) NUMBITS(7) [] +], +ADDR_ENDP4 [ + /// Interrupt EP requires preamble (is a low speed device on a full speed hub) + INTEP_PREAMBLE OFFSET(26) NUMBITS(1) [], + /// Direction of the interrupt endpoint. In=0, Out=1 + INTEP_DIR OFFSET(25) NUMBITS(1) [], + /// Endpoint number of the interrupt endpoint + ENDPOINT OFFSET(16) NUMBITS(4) [], + /// Device address + ADDRESS OFFSET(0) NUMBITS(7) [] +], +ADDR_ENDP5 [ + /// Interrupt EP requires preamble (is a low speed device on a full speed hub) + INTEP_PREAMBLE OFFSET(26) NUMBITS(1) [], + /// Direction of the interrupt endpoint. In=0, Out=1 + INTEP_DIR OFFSET(25) NUMBITS(1) [], + /// Endpoint number of the interrupt endpoint + ENDPOINT OFFSET(16) NUMBITS(4) [], + /// Device address + ADDRESS OFFSET(0) NUMBITS(7) [] +], +ADDR_ENDP6 [ + /// Interrupt EP requires preamble (is a low speed device on a full speed hub) + INTEP_PREAMBLE OFFSET(26) NUMBITS(1) [], + /// Direction of the interrupt endpoint. In=0, Out=1 + INTEP_DIR OFFSET(25) NUMBITS(1) [], + /// Endpoint number of the interrupt endpoint + ENDPOINT OFFSET(16) NUMBITS(4) [], + /// Device address + ADDRESS OFFSET(0) NUMBITS(7) [] +], +ADDR_ENDP7 [ + /// Interrupt EP requires preamble (is a low speed device on a full speed hub) + INTEP_PREAMBLE OFFSET(26) NUMBITS(1) [], + /// Direction of the interrupt endpoint. In=0, Out=1 + INTEP_DIR OFFSET(25) NUMBITS(1) [], + /// Endpoint number of the interrupt endpoint + ENDPOINT OFFSET(16) NUMBITS(4) [], + /// Device address + ADDRESS OFFSET(0) NUMBITS(7) [] +], +ADDR_ENDP8 [ + /// Interrupt EP requires preamble (is a low speed device on a full speed hub) + INTEP_PREAMBLE OFFSET(26) NUMBITS(1) [], + /// Direction of the interrupt endpoint. In=0, Out=1 + INTEP_DIR OFFSET(25) NUMBITS(1) [], + /// Endpoint number of the interrupt endpoint + ENDPOINT OFFSET(16) NUMBITS(4) [], + /// Device address + ADDRESS OFFSET(0) NUMBITS(7) [] +], +ADDR_ENDP9 [ + /// Interrupt EP requires preamble (is a low speed device on a full speed hub) + INTEP_PREAMBLE OFFSET(26) NUMBITS(1) [], + /// Direction of the interrupt endpoint. In=0, Out=1 + INTEP_DIR OFFSET(25) NUMBITS(1) [], + /// Endpoint number of the interrupt endpoint + ENDPOINT OFFSET(16) NUMBITS(4) [], + /// Device address + ADDRESS OFFSET(0) NUMBITS(7) [] +], +ADDR_ENDP10 [ + /// Interrupt EP requires preamble (is a low speed device on a full speed hub) + INTEP_PREAMBLE OFFSET(26) NUMBITS(1) [], + /// Direction of the interrupt endpoint. In=0, Out=1 + INTEP_DIR OFFSET(25) NUMBITS(1) [], + /// Endpoint number of the interrupt endpoint + ENDPOINT OFFSET(16) NUMBITS(4) [], + /// Device address + ADDRESS OFFSET(0) NUMBITS(7) [] +], +ADDR_ENDP11 [ + /// Interrupt EP requires preamble (is a low speed device on a full speed hub) + INTEP_PREAMBLE OFFSET(26) NUMBITS(1) [], + /// Direction of the interrupt endpoint. In=0, Out=1 + INTEP_DIR OFFSET(25) NUMBITS(1) [], + /// Endpoint number of the interrupt endpoint + ENDPOINT OFFSET(16) NUMBITS(4) [], + /// Device address + ADDRESS OFFSET(0) NUMBITS(7) [] +], +ADDR_ENDP12 [ + /// Interrupt EP requires preamble (is a low speed device on a full speed hub) + INTEP_PREAMBLE OFFSET(26) NUMBITS(1) [], + /// Direction of the interrupt endpoint. In=0, Out=1 + INTEP_DIR OFFSET(25) NUMBITS(1) [], + /// Endpoint number of the interrupt endpoint + ENDPOINT OFFSET(16) NUMBITS(4) [], + /// Device address + ADDRESS OFFSET(0) NUMBITS(7) [] +], +ADDR_ENDP13 [ + /// Interrupt EP requires preamble (is a low speed device on a full speed hub) + INTEP_PREAMBLE OFFSET(26) NUMBITS(1) [], + /// Direction of the interrupt endpoint. In=0, Out=1 + INTEP_DIR OFFSET(25) NUMBITS(1) [], + /// Endpoint number of the interrupt endpoint + ENDPOINT OFFSET(16) NUMBITS(4) [], + /// Device address + ADDRESS OFFSET(0) NUMBITS(7) [] +], +ADDR_ENDP14 [ + /// Interrupt EP requires preamble (is a low speed device on a full speed hub) + INTEP_PREAMBLE OFFSET(26) NUMBITS(1) [], + /// Direction of the interrupt endpoint. In=0, Out=1 + INTEP_DIR OFFSET(25) NUMBITS(1) [], + /// Endpoint number of the interrupt endpoint + ENDPOINT OFFSET(16) NUMBITS(4) [], + /// Device address + ADDRESS OFFSET(0) NUMBITS(7) [] +], +ADDR_ENDP15 [ + /// Interrupt EP requires preamble (is a low speed device on a full speed hub) + INTEP_PREAMBLE OFFSET(26) NUMBITS(1) [], + /// Direction of the interrupt endpoint. In=0, Out=1 + INTEP_DIR OFFSET(25) NUMBITS(1) [], + /// Endpoint number of the interrupt endpoint + ENDPOINT OFFSET(16) NUMBITS(4) [], + /// Device address + ADDRESS OFFSET(0) NUMBITS(7) [] +], +MAIN_CTRL [ + /// Reduced timings for simulation + SIM_TIMING OFFSET(31) NUMBITS(1) [], + /// Device mode = 0, Host mode = 1 + HOST_NDEVICE OFFSET(1) NUMBITS(1) [], + /// Enable controller + CONTROLLER_EN OFFSET(0) NUMBITS(1) [] +], +SOF_WR [ + + COUNT OFFSET(0) NUMBITS(11) [] +], +SOF_RD [ + + COUNT OFFSET(0) NUMBITS(11) [] +], +SIE_CTRL [ + /// Device: Set bit in EP_STATUS_STALL_NAK when EP0 sends a STALL + EP0_INT_STALL OFFSET(31) NUMBITS(1) [], + /// Device: EP0 single buffered = 0, double buffered = 1 + EP0_DOUBLE_BUF OFFSET(30) NUMBITS(1) [], + /// Device: Set bit in BUFF_STATUS for every buffer completed on EP0 + EP0_INT_1BUF OFFSET(29) NUMBITS(1) [], + /// Device: Set bit in BUFF_STATUS for every 2 buffers completed on EP0 + EP0_INT_2BUF OFFSET(28) NUMBITS(1) [], + /// Device: Set bit in EP_STATUS_STALL_NAK when EP0 sends a NAK + EP0_INT_NAK OFFSET(27) NUMBITS(1) [], + /// Direct bus drive enable + DIRECT_EN OFFSET(26) NUMBITS(1) [], + /// Direct control of DP + DIRECT_DP OFFSET(25) NUMBITS(1) [], + /// Direct control of DM + DIRECT_DM OFFSET(24) NUMBITS(1) [], + /// Power down bus transceiver + TRANSCEIVER_PD OFFSET(18) NUMBITS(1) [], + /// Device: Pull-up strength (0=1K2, 1=2k3) + RPU_OPT OFFSET(17) NUMBITS(1) [], + /// Device: Enable pull up resistor + PULLUP_EN OFFSET(16) NUMBITS(1) [], + /// Host: Enable pull down resistors + PULLDOWN_EN OFFSET(15) NUMBITS(1) [], + /// Host: Reset bus + RESET_BUS OFFSET(13) NUMBITS(1) [], + /// Device: Remote wakeup. Device can initiate its own resume after suspend. + RESUME OFFSET(12) NUMBITS(1) [], + /// Host: Enable VBUS + VBUS_EN OFFSET(11) NUMBITS(1) [], + /// Host: Enable keep alive packet (for low speed bus) + KEEP_ALIVE_EN OFFSET(10) NUMBITS(1) [], + /// Host: Enable SOF generation (for full speed bus) + SOF_EN OFFSET(9) NUMBITS(1) [], + /// Host: Delay packet(s) until after SOF + SOF_SYNC OFFSET(8) NUMBITS(1) [], + /// Host: Preable enable for LS device on FS hub + PREAMBLE_EN OFFSET(6) NUMBITS(1) [], + /// Host: Stop transaction + STOP_TRANS OFFSET(4) NUMBITS(1) [], + /// Host: Receive transaction (IN to host) + RECEIVE_DATA OFFSET(3) NUMBITS(1) [], + /// Host: Send transaction (OUT from host) + SEND_DATA OFFSET(2) NUMBITS(1) [], + /// Host: Send Setup packet + SEND_SETUP OFFSET(1) NUMBITS(1) [], + /// Host: Start transaction + START_TRANS OFFSET(0) NUMBITS(1) [] +], +SIE_STATUS [ + /// Data Sequence Error. + /// The device can raise a sequence error in the following conditions: + /// * A SETUP packet is received followed by a DATA1 packet (data phase + /// should always be DATA0) * An OUT packet is received from the host but + /// doesn't match the data pid in the buffer control register read from DPSRAM + /// The host can raise a data sequence error in the following conditions: + /// * An IN packet from the device has the wrong data PID + DATA_SEQ_ERROR OFFSET(31) NUMBITS(1) [], + /// ACK received. Raised by both host and device. + ACK_REC OFFSET(30) NUMBITS(1) [], + /// Host: STALL received + STALL_REC OFFSET(29) NUMBITS(1) [], + /// Host: NAK received + NAK_REC OFFSET(28) NUMBITS(1) [], + /// RX timeout is raised by both the host and device if an ACK + /// is not received in the maximum time specified by the USB + /// spec. + RX_TIMEOUT OFFSET(27) NUMBITS(1) [], + /// RX overflow is raised by the Serial RX engine if the incoming data is too fast. + RX_OVERFLOW OFFSET(26) NUMBITS(1) [], + /// Bit Stuff Error. Raised by the Serial RX engine. + BIT_STUFF_ERROR OFFSET(25) NUMBITS(1) [], + /// CRC Error. Raised by the Serial RX engine. + CRC_ERROR OFFSET(24) NUMBITS(1) [], + /// Device: bus reset received + BUS_RESET OFFSET(19) NUMBITS(1) [], + /// Transaction complete. + /// Raised by device if: + /// * An IN or OUT packet is sent with the LAST_BUFF bit set in + /// the buffer control register + /// Raised by host if: + /// * A setup packet is sent when no data in or data out + /// transaction follows * An IN packet is received and the + /// LAST_BUFF bit is set in the buffer control register * An IN + /// packet is received with zero length * An OUT packet is + /// sent and the LAST_BUFF bit is set + TRANS_COMPLETE OFFSET(18) NUMBITS(1) [], + /// Device: Setup packet received + SETUP_REC OFFSET(17) NUMBITS(1) [], + /// Device: connected + CONNECTED OFFSET(16) NUMBITS(1) [], + /// Host: Device has initiated a remote resume. Device: host has initiated a resume. + RESUME OFFSET(11) NUMBITS(1) [], + /// VBUS over current detected + VBUS_OVER_CURR OFFSET(10) NUMBITS(1) [], + /// Host: device speed. Disconnected = 00, LS = 01, FS = 10 + SPEED OFFSET(8) NUMBITS(2) [], + /// Bus in suspended state. Valid for device and host. Host + /// and device will go into suspend if neither Keep Alive / SOF + /// frames are enabled. + SUSPENDED OFFSET(4) NUMBITS(1) [], + /// USB bus line state + LINE_STATE OFFSET(2) NUMBITS(2) [], + /// Device: VBUS Detected + VBUS_DETECTED OFFSET(0) NUMBITS(1) [] +], +INT_EP_CTRL [ + /// Host: Enable interrupt endpoint 1 -> 15 + INT_EP_ACTIVE OFFSET(1) NUMBITS(15) [] +], +BUFF_STATUS [ + + EP15_OUT OFFSET(31) NUMBITS(1) [], + + EP15_IN OFFSET(30) NUMBITS(1) [], + + EP14_OUT OFFSET(29) NUMBITS(1) [], + + EP14_IN OFFSET(28) NUMBITS(1) [], + + EP13_OUT OFFSET(27) NUMBITS(1) [], + + EP13_IN OFFSET(26) NUMBITS(1) [], + + EP12_OUT OFFSET(25) NUMBITS(1) [], + + EP12_IN OFFSET(24) NUMBITS(1) [], + + EP11_OUT OFFSET(23) NUMBITS(1) [], + + EP11_IN OFFSET(22) NUMBITS(1) [], + + EP10_OUT OFFSET(21) NUMBITS(1) [], + + EP10_IN OFFSET(20) NUMBITS(1) [], + + EP9_OUT OFFSET(19) NUMBITS(1) [], + + EP9_IN OFFSET(18) NUMBITS(1) [], + + EP8_OUT OFFSET(17) NUMBITS(1) [], + + EP8_IN OFFSET(16) NUMBITS(1) [], + + EP7_OUT OFFSET(15) NUMBITS(1) [], + + EP7_IN OFFSET(14) NUMBITS(1) [], + + EP6_OUT OFFSET(13) NUMBITS(1) [], + + EP6_IN OFFSET(12) NUMBITS(1) [], + + EP5_OUT OFFSET(11) NUMBITS(1) [], + + EP5_IN OFFSET(10) NUMBITS(1) [], + + EP4_OUT OFFSET(9) NUMBITS(1) [], + + EP4_IN OFFSET(8) NUMBITS(1) [], + + EP3_OUT OFFSET(7) NUMBITS(1) [], + + EP3_IN OFFSET(6) NUMBITS(1) [], + + EP2_OUT OFFSET(5) NUMBITS(1) [], + + EP2_IN OFFSET(4) NUMBITS(1) [], + + EP1_OUT OFFSET(3) NUMBITS(1) [], + + EP1_IN OFFSET(2) NUMBITS(1) [], + + EP0_OUT OFFSET(1) NUMBITS(1) [], + + EP0_IN OFFSET(0) NUMBITS(1) [] +], +BUFF_CPU_SHOULD_HANDLE [ + + EP15_OUT OFFSET(31) NUMBITS(1) [], + + EP15_IN OFFSET(30) NUMBITS(1) [], + + EP14_OUT OFFSET(29) NUMBITS(1) [], + + EP14_IN OFFSET(28) NUMBITS(1) [], + + EP13_OUT OFFSET(27) NUMBITS(1) [], + + EP13_IN OFFSET(26) NUMBITS(1) [], + + EP12_OUT OFFSET(25) NUMBITS(1) [], + + EP12_IN OFFSET(24) NUMBITS(1) [], + + EP11_OUT OFFSET(23) NUMBITS(1) [], + + EP11_IN OFFSET(22) NUMBITS(1) [], + + EP10_OUT OFFSET(21) NUMBITS(1) [], + + EP10_IN OFFSET(20) NUMBITS(1) [], + + EP9_OUT OFFSET(19) NUMBITS(1) [], + + EP9_IN OFFSET(18) NUMBITS(1) [], + + EP8_OUT OFFSET(17) NUMBITS(1) [], + + EP8_IN OFFSET(16) NUMBITS(1) [], + + EP7_OUT OFFSET(15) NUMBITS(1) [], + + EP7_IN OFFSET(14) NUMBITS(1) [], + + EP6_OUT OFFSET(13) NUMBITS(1) [], + + EP6_IN OFFSET(12) NUMBITS(1) [], + + EP5_OUT OFFSET(11) NUMBITS(1) [], + + EP5_IN OFFSET(10) NUMBITS(1) [], + + EP4_OUT OFFSET(9) NUMBITS(1) [], + + EP4_IN OFFSET(8) NUMBITS(1) [], + + EP3_OUT OFFSET(7) NUMBITS(1) [], + + EP3_IN OFFSET(6) NUMBITS(1) [], + + EP2_OUT OFFSET(5) NUMBITS(1) [], + + EP2_IN OFFSET(4) NUMBITS(1) [], + + EP1_OUT OFFSET(3) NUMBITS(1) [], + + EP1_IN OFFSET(2) NUMBITS(1) [], + + EP0_OUT OFFSET(1) NUMBITS(1) [], + + EP0_IN OFFSET(0) NUMBITS(1) [] +], +EP_ABORT [ + + EP15_OUT OFFSET(31) NUMBITS(1) [], + + EP15_IN OFFSET(30) NUMBITS(1) [], + + EP14_OUT OFFSET(29) NUMBITS(1) [], + + EP14_IN OFFSET(28) NUMBITS(1) [], + + EP13_OUT OFFSET(27) NUMBITS(1) [], + + EP13_IN OFFSET(26) NUMBITS(1) [], + + EP12_OUT OFFSET(25) NUMBITS(1) [], + + EP12_IN OFFSET(24) NUMBITS(1) [], + + EP11_OUT OFFSET(23) NUMBITS(1) [], + + EP11_IN OFFSET(22) NUMBITS(1) [], + + EP10_OUT OFFSET(21) NUMBITS(1) [], + + EP10_IN OFFSET(20) NUMBITS(1) [], + + EP9_OUT OFFSET(19) NUMBITS(1) [], + + EP9_IN OFFSET(18) NUMBITS(1) [], + + EP8_OUT OFFSET(17) NUMBITS(1) [], + + EP8_IN OFFSET(16) NUMBITS(1) [], + + EP7_OUT OFFSET(15) NUMBITS(1) [], + + EP7_IN OFFSET(14) NUMBITS(1) [], + + EP6_OUT OFFSET(13) NUMBITS(1) [], + + EP6_IN OFFSET(12) NUMBITS(1) [], + + EP5_OUT OFFSET(11) NUMBITS(1) [], + + EP5_IN OFFSET(10) NUMBITS(1) [], + + EP4_OUT OFFSET(9) NUMBITS(1) [], + + EP4_IN OFFSET(8) NUMBITS(1) [], + + EP3_OUT OFFSET(7) NUMBITS(1) [], + + EP3_IN OFFSET(6) NUMBITS(1) [], + + EP2_OUT OFFSET(5) NUMBITS(1) [], + + EP2_IN OFFSET(4) NUMBITS(1) [], + + EP1_OUT OFFSET(3) NUMBITS(1) [], + + EP1_IN OFFSET(2) NUMBITS(1) [], + + EP0_OUT OFFSET(1) NUMBITS(1) [], + + EP0_IN OFFSET(0) NUMBITS(1) [] +], +EP_ABORT_DONE [ + + EP15_OUT OFFSET(31) NUMBITS(1) [], + + EP15_IN OFFSET(30) NUMBITS(1) [], + + EP14_OUT OFFSET(29) NUMBITS(1) [], + + EP14_IN OFFSET(28) NUMBITS(1) [], + + EP13_OUT OFFSET(27) NUMBITS(1) [], + + EP13_IN OFFSET(26) NUMBITS(1) [], + + EP12_OUT OFFSET(25) NUMBITS(1) [], + + EP12_IN OFFSET(24) NUMBITS(1) [], + + EP11_OUT OFFSET(23) NUMBITS(1) [], + + EP11_IN OFFSET(22) NUMBITS(1) [], + + EP10_OUT OFFSET(21) NUMBITS(1) [], + + EP10_IN OFFSET(20) NUMBITS(1) [], + + EP9_OUT OFFSET(19) NUMBITS(1) [], + + EP9_IN OFFSET(18) NUMBITS(1) [], + + EP8_OUT OFFSET(17) NUMBITS(1) [], + + EP8_IN OFFSET(16) NUMBITS(1) [], + + EP7_OUT OFFSET(15) NUMBITS(1) [], + + EP7_IN OFFSET(14) NUMBITS(1) [], + + EP6_OUT OFFSET(13) NUMBITS(1) [], + + EP6_IN OFFSET(12) NUMBITS(1) [], + + EP5_OUT OFFSET(11) NUMBITS(1) [], + + EP5_IN OFFSET(10) NUMBITS(1) [], + + EP4_OUT OFFSET(9) NUMBITS(1) [], + + EP4_IN OFFSET(8) NUMBITS(1) [], + + EP3_OUT OFFSET(7) NUMBITS(1) [], + + EP3_IN OFFSET(6) NUMBITS(1) [], + + EP2_OUT OFFSET(5) NUMBITS(1) [], + + EP2_IN OFFSET(4) NUMBITS(1) [], + + EP1_OUT OFFSET(3) NUMBITS(1) [], + + EP1_IN OFFSET(2) NUMBITS(1) [], + + EP0_OUT OFFSET(1) NUMBITS(1) [], + + EP0_IN OFFSET(0) NUMBITS(1) [] +], +EP_STALL_ARM [ + + EP0_OUT OFFSET(1) NUMBITS(1) [], + + EP0_IN OFFSET(0) NUMBITS(1) [] +], +NAK_POLL [ + /// NAK polling interval for a full speed device + DELAY_FS OFFSET(16) NUMBITS(10) [], + /// NAK polling interval for a low speed device + DELAY_LS OFFSET(0) NUMBITS(10) [] +], +EP_STATUS_STALL_NAK [ + + EP15_OUT OFFSET(31) NUMBITS(1) [], + + EP15_IN OFFSET(30) NUMBITS(1) [], + + EP14_OUT OFFSET(29) NUMBITS(1) [], + + EP14_IN OFFSET(28) NUMBITS(1) [], + + EP13_OUT OFFSET(27) NUMBITS(1) [], + + EP13_IN OFFSET(26) NUMBITS(1) [], + + EP12_OUT OFFSET(25) NUMBITS(1) [], + + EP12_IN OFFSET(24) NUMBITS(1) [], + + EP11_OUT OFFSET(23) NUMBITS(1) [], + + EP11_IN OFFSET(22) NUMBITS(1) [], + + EP10_OUT OFFSET(21) NUMBITS(1) [], + + EP10_IN OFFSET(20) NUMBITS(1) [], + + EP9_OUT OFFSET(19) NUMBITS(1) [], + + EP9_IN OFFSET(18) NUMBITS(1) [], + + EP8_OUT OFFSET(17) NUMBITS(1) [], + + EP8_IN OFFSET(16) NUMBITS(1) [], + + EP7_OUT OFFSET(15) NUMBITS(1) [], + + EP7_IN OFFSET(14) NUMBITS(1) [], + + EP6_OUT OFFSET(13) NUMBITS(1) [], + + EP6_IN OFFSET(12) NUMBITS(1) [], + + EP5_OUT OFFSET(11) NUMBITS(1) [], + + EP5_IN OFFSET(10) NUMBITS(1) [], + + EP4_OUT OFFSET(9) NUMBITS(1) [], + + EP4_IN OFFSET(8) NUMBITS(1) [], + + EP3_OUT OFFSET(7) NUMBITS(1) [], + + EP3_IN OFFSET(6) NUMBITS(1) [], + + EP2_OUT OFFSET(5) NUMBITS(1) [], + + EP2_IN OFFSET(4) NUMBITS(1) [], + + EP1_OUT OFFSET(3) NUMBITS(1) [], + + EP1_IN OFFSET(2) NUMBITS(1) [], + + EP0_OUT OFFSET(1) NUMBITS(1) [], + + EP0_IN OFFSET(0) NUMBITS(1) [] +], +USB_MUXING [ + + SOFTCON OFFSET(3) NUMBITS(1) [], + + TO_DIGITAL_PAD OFFSET(2) NUMBITS(1) [], + + TO_EXTPHY OFFSET(1) NUMBITS(1) [], + + TO_PHY OFFSET(0) NUMBITS(1) [] +], +USB_PWR [ + + OVERCURR_DETECT_EN OFFSET(5) NUMBITS(1) [], + + OVERCURR_DETECT OFFSET(4) NUMBITS(1) [], + + VBUS_DETECT_OVERRIDE_EN OFFSET(3) NUMBITS(1) [], + + VBUS_DETECT OFFSET(2) NUMBITS(1) [], + + VBUS_EN_OVERRIDE_EN OFFSET(1) NUMBITS(1) [], + + VBUS_EN OFFSET(0) NUMBITS(1) [] +], +USBPHY_DIRECT [ + /// DM over voltage + DM_OVV OFFSET(22) NUMBITS(1) [], + /// DP over voltage + DP_OVV OFFSET(21) NUMBITS(1) [], + /// DM overcurrent + DM_OVCN OFFSET(20) NUMBITS(1) [], + /// DP overcurrent + DP_OVCN OFFSET(19) NUMBITS(1) [], + /// DPM pin state + RX_DM OFFSET(18) NUMBITS(1) [], + /// DPP pin state + RX_DP OFFSET(17) NUMBITS(1) [], + /// Differential RX + RX_DD OFFSET(16) NUMBITS(1) [], + /// TX_DIFFMODE=0: Single ended mode + /// TX_DIFFMODE=1: Differential drive mode (TX_DM, TX_DM_OE ignored) + TX_DIFFMODE OFFSET(15) NUMBITS(1) [], + /// TX_FSSLEW=0: Low speed slew rate + /// TX_FSSLEW=1: Full speed slew rate + TX_FSSLEW OFFSET(14) NUMBITS(1) [], + /// TX power down override (if override enable is set). 1 = powered down. + TX_PD OFFSET(13) NUMBITS(1) [], + /// RX power down override (if override enable is set). 1 = powered down. + RX_PD OFFSET(12) NUMBITS(1) [], + /// Output data. TX_DIFFMODE=1, Ignored + /// TX_DIFFMODE=0, Drives DPM only. TX_DM_OE=1 to + /// enable drive. DPM=TX_DM + TX_DM OFFSET(11) NUMBITS(1) [], + /// Output data. If TX_DIFFMODE=1, Drives DPP/DPM diff + /// pair. TX_DP_OE=1 to enable drive. DPP=TX_DP, + /// DPM=~TX_DP + /// If TX_DIFFMODE=0, Drives DPP only. TX_DP_OE=1 to + /// enable drive. DPP=TX_DP + TX_DP OFFSET(10) NUMBITS(1) [], + /// Output enable. If TX_DIFFMODE=1, Ignored. + /// If TX_DIFFMODE=0, OE for DPM only. 0 - DPM in Hi-Z + /// state; 1 - DPM driving + TX_DM_OE OFFSET(9) NUMBITS(1) [], + /// Output enable. If TX_DIFFMODE=1, Ignored. + /// If TX_DIFFMODE=0, OE for DPM only. 0 - DPM in Hi-Z + /// state; 1 - DPM driving + TX_DP_OE OFFSET(8) NUMBITS(1) [], + /// DM pull down enable + DM_PULLDN_EN OFFSET(6) NUMBITS(1) [], + /// DM pull up enable + DM_PULLUP_EN OFFSET(5) NUMBITS(1) [], + /// Enable the second DM pull up resistor. 0 - Pull = Rpu2; 1 - Pull = Rpu1 + Rpu2 + DM_PULLUP_HISEL OFFSET(4) NUMBITS(1) [], + /// DP pull down enable + DP_PULLDN_EN OFFSET(2) NUMBITS(1) [], + /// DP pull up enable + DP_PULLUP_EN OFFSET(1) NUMBITS(1) [], + /// Enable the second DP pull up resistor. 0 - Pull = Rpu2; 1 - Pull = Rpu1 + Rpu2 + DP_PULLUP_HISEL OFFSET(0) NUMBITS(1) [] +], +USBPHY_DIRECT_OVERRIDE [ + + TX_DIFFMODE_OVERRIDE_EN OFFSET(15) NUMBITS(1) [], + + DM_PULLUP_OVERRIDE_EN OFFSET(12) NUMBITS(1) [], + + TX_FSSLEW_OVERRIDE_EN OFFSET(11) NUMBITS(1) [], + + TX_PD_OVERRIDE_EN OFFSET(10) NUMBITS(1) [], + + RX_PD_OVERRIDE_EN OFFSET(9) NUMBITS(1) [], + + TX_DM_OVERRIDE_EN OFFSET(8) NUMBITS(1) [], + + TX_DP_OVERRIDE_EN OFFSET(7) NUMBITS(1) [], + + TX_DM_OE_OVERRIDE_EN OFFSET(6) NUMBITS(1) [], + + TX_DP_OE_OVERRIDE_EN OFFSET(5) NUMBITS(1) [], + + DM_PULLDN_EN_OVERRIDE_EN OFFSET(4) NUMBITS(1) [], + + DP_PULLDN_EN_OVERRIDE_EN OFFSET(3) NUMBITS(1) [], + + DP_PULLUP_EN_OVERRIDE_EN OFFSET(2) NUMBITS(1) [], + + DM_PULLUP_HISEL_OVERRIDE_EN OFFSET(1) NUMBITS(1) [], + + DP_PULLUP_HISEL_OVERRIDE_EN OFFSET(0) NUMBITS(1) [] +], +USBPHY_TRIM [ + /// Value to drive to USB PHY + /// DM pulldown resistor trim control + // Experimental data suggests that the reset value will work, + // but this register allows adjustment if required + DM_PULLDN_TRIM OFFSET(8) NUMBITS(5) [], + /// Value to drive to USB PHY + /// DP pulldown resistor trim control + /// Experimental data suggests that the reset value will work, + /// but this register allows adjustment if required + DP_PULLDN_TRIM OFFSET(0) NUMBITS(5) [] +], +INTR [ + /// Raised when any bit in EP_STATUS_STALL_NAK is set. + /// Clear by clearing all bits in EP_STATUS_STALL_NAK. + EP_STALL_NAK OFFSET(19) NUMBITS(1) [], + /// Raised when any bit in ABORT_DONE is set. Clear by + /// clearing all bits in ABORT_DONE. + ABORT_DONE OFFSET(18) NUMBITS(1) [], + /// Set every time the device receives a SOF (Start of Frame) + /// packet. Cleared by reading SOF_RD + DEV_SOF OFFSET(17) NUMBITS(1) [], + /// Device. Source: SIE_STATUS.SETUP_REC + SETUP_REQ OFFSET(16) NUMBITS(1) [], + /// Set when the device receives a resume from the host. + /// Cleared by writing to SIE_STATUS.RESUME + DEV_RESUME_FROM_HOST OFFSET(15) NUMBITS(1) [], + /// Set when the device suspend state changes. Cleared by + /// writing to SIE_STATUS.SUSPENDED + DEV_SUSPEND OFFSET(14) NUMBITS(1) [], + /// Set when the device connection state changes. Cleared by + /// writing to SIE_STATUS.CONNECTED + DEV_CONN_DIS OFFSET(13) NUMBITS(1) [], + /// Source: SIE_STATUS.BUS_RESET + BUS_RESET OFFSET(12) NUMBITS(1) [], + /// Source: SIE_STATUS.VBUS_DETECT + VBUS_DETECT OFFSET(11) NUMBITS(1) [], + /// Source: SIE_STATUS.STALL_REC + STALL OFFSET(10) NUMBITS(1) [], + /// Source: SIE_STATUS.CRC_ERROR + ERROR_CRC OFFSET(9) NUMBITS(1) [], + /// Source: SIE_STATUS.BIT_STUFF_ERROR + ERROR_BIT_STUFF OFFSET(8) NUMBITS(1) [], + /// Source: SIE_STATUS.RX_OVERFLOW + ERROR_RX_OVERFLOW OFFSET(7) NUMBITS(1) [], + /// Source: SIE_STATUS.RX_TIMEOUT + ERROR_RX_TIMEOUT OFFSET(6) NUMBITS(1) [], + /// Source: SIE_STATUS.DATA_SEQ_ERROR + ERROR_DATA_SEQ OFFSET(5) NUMBITS(1) [], + /// Raised when any bit in BUFF_STATUS is set. Clear by + /// clearing all bits in BUFF_STATUS. + BUFF_STATUS OFFSET(4) NUMBITS(1) [], + /// Raised every time SIE_STATUS.TRANS_COMPLETE is set. + /// Clear by writing to this bit. + TRANS_COMPLETE OFFSET(3) NUMBITS(1) [], + /// Host: raised every time the host sends a SOF (Start of + /// Frame). Cleared by reading SOF_RD + HOST_SOF OFFSET(2) NUMBITS(1) [], + /// Host: raised when a device wakes up the host. Cleared by + /// writing to SIE_STATUS.RESUME + HOST_RESUME OFFSET(1) NUMBITS(1) [], + /// Host: raised when a device is connected or disconnected + /// (i.e. when SIE_STATUS.SPEED changes). Cleared by + /// writing to SIE_STATUS.SPEED + HOST_CONN_DIS OFFSET(0) NUMBITS(1) [] +], +INTE [ + /// Raised when any bit in EP_STATUS_STALL_NAK is set. + /// Clear by clearing all bits in EP_STATUS_STALL_NAK. + EP_STALL_NAK OFFSET(19) NUMBITS(1) [], + /// Raised when any bit in ABORT_DONE is set. Clear by + /// clearing all bits in ABORT_DONE. + ABORT_DONE OFFSET(18) NUMBITS(1) [], + /// Set every time the device receives a SOF (Start of Frame) + /// packet. Cleared by reading SOF_RD + DEV_SOF OFFSET(17) NUMBITS(1) [], + /// Device. Source: SIE_STATUS.SETUP_REC + SETUP_REQ OFFSET(16) NUMBITS(1) [], + /// Set when the device receives a resume from the host. + /// Cleared by writing to SIE_STATUS.RESUME + DEV_RESUME_FROM_HOST OFFSET(15) NUMBITS(1) [], + /// Set when the device suspend state changes. Cleared by + /// writing to SIE_STATUS.SUSPENDED + DEV_SUSPEND OFFSET(14) NUMBITS(1) [], + /// Set when the device connection state changes. Cleared by + /// writing to SIE_STATUS.CONNECTED + DEV_CONN_DIS OFFSET(13) NUMBITS(1) [], + /// Source: SIE_STATUS.BUS_RESET + BUS_RESET OFFSET(12) NUMBITS(1) [], + /// Source: SIE_STATUS.VBUS_DETECT + VBUS_DETECT OFFSET(11) NUMBITS(1) [], + /// Source: SIE_STATUS.STALL_REC + STALL OFFSET(10) NUMBITS(1) [], + /// Source: SIE_STATUS.CRC_ERROR + ERROR_CRC OFFSET(9) NUMBITS(1) [], + /// Source: SIE_STATUS.BIT_STUFF_ERROR + ERROR_BIT_STUFF OFFSET(8) NUMBITS(1) [], + /// Source: SIE_STATUS.RX_OVERFLOW + ERROR_RX_OVERFLOW OFFSET(7) NUMBITS(1) [], + /// Source: SIE_STATUS.RX_TIMEOUT + ERROR_RX_TIMEOUT OFFSET(6) NUMBITS(1) [], + /// Source: SIE_STATUS.DATA_SEQ_ERROR + ERROR_DATA_SEQ OFFSET(5) NUMBITS(1) [], + /// Raised when any bit in BUFF_STATUS is set. Clear by + /// clearing all bits in BUFF_STATUS. + BUFF_STATUS OFFSET(4) NUMBITS(1) [], + /// Raised every time SIE_STATUS.TRANS_COMPLETE is set. + /// Clear by writing to this bit. + TRANS_COMPLETE OFFSET(3) NUMBITS(1) [], + /// Host: raised every time the host sends a SOF (Start of + /// Frame). Cleared by reading SOF_RD + HOST_SOF OFFSET(2) NUMBITS(1) [], + /// Host: raised when a device wakes up the host. Cleared by + /// writing to SIE_STATUS.RESUME + HOST_RESUME OFFSET(1) NUMBITS(1) [], + /// Host: raised when a device is connected or disconnected + /// (i.e. when SIE_STATUS.SPEED changes). Cleared by + /// writing to SIE_STATUS.SPEED + HOST_CONN_DIS OFFSET(0) NUMBITS(1) [] +], +INTF [ + /// Raised when any bit in EP_STATUS_STALL_NAK is set. + /// Clear by clearing all bits in EP_STATUS_STALL_NAK. + EP_STALL_NAK OFFSET(19) NUMBITS(1) [], + /// Raised when any bit in ABORT_DONE is set. Clear by + /// clearing all bits in ABORT_DONE. + ABORT_DONE OFFSET(18) NUMBITS(1) [], + /// Set every time the device receives a SOF (Start of Frame) + /// packet. Cleared by reading SOF_RD + DEV_SOF OFFSET(17) NUMBITS(1) [], + /// Device. Source: SIE_STATUS.SETUP_REC + SETUP_REQ OFFSET(16) NUMBITS(1) [], + /// Set when the device receives a resume from the host. + /// Cleared by writing to SIE_STATUS.RESUME + DEV_RESUME_FROM_HOST OFFSET(15) NUMBITS(1) [], + /// Set when the device suspend state changes. Cleared by + /// writing to SIE_STATUS.SUSPENDED + DEV_SUSPEND OFFSET(14) NUMBITS(1) [], + /// Set when the device connection state changes. Cleared by + /// writing to SIE_STATUS.CONNECTED + DEV_CONN_DIS OFFSET(13) NUMBITS(1) [], + /// Source: SIE_STATUS.BUS_RESET + BUS_RESET OFFSET(12) NUMBITS(1) [], + /// Source: SIE_STATUS.VBUS_DETECT + VBUS_DETECT OFFSET(11) NUMBITS(1) [], + /// Source: SIE_STATUS.STALL_REC + STALL OFFSET(10) NUMBITS(1) [], + /// Source: SIE_STATUS.CRC_ERROR + ERROR_CRC OFFSET(9) NUMBITS(1) [], + /// Source: SIE_STATUS.BIT_STUFF_ERROR + ERROR_BIT_STUFF OFFSET(8) NUMBITS(1) [], + /// Source: SIE_STATUS.RX_OVERFLOW + ERROR_RX_OVERFLOW OFFSET(7) NUMBITS(1) [], + /// Source: SIE_STATUS.RX_TIMEOUT + ERROR_RX_TIMEOUT OFFSET(6) NUMBITS(1) [], + /// Source: SIE_STATUS.DATA_SEQ_ERROR + ERROR_DATA_SEQ OFFSET(5) NUMBITS(1) [], + /// Raised when any bit in BUFF_STATUS is set. Clear by + /// clearing all bits in BUFF_STATUS. + BUFF_STATUS OFFSET(4) NUMBITS(1) [], + /// Raised every time SIE_STATUS.TRANS_COMPLETE is set. + /// Clear by writing to this bit. + TRANS_COMPLETE OFFSET(3) NUMBITS(1) [], + /// Host: raised every time the host sends a SOF (Start of + /// Frame). Cleared by reading SOF_RD + HOST_SOF OFFSET(2) NUMBITS(1) [], + /// Host: raised when a device wakes up the host. Cleared by + /// writing to SIE_STATUS.RESUME + HOST_RESUME OFFSET(1) NUMBITS(1) [], + /// Host: raised when a device is connected or disconnected + /// (i.e. when SIE_STATUS.SPEED changes). Cleared by + /// writing to SIE_STATUS.SPEED + HOST_CONN_DIS OFFSET(0) NUMBITS(1) [] +], +INTS [ + /// Raised when any bit in EP_STATUS_STALL_NAK is set. + /// Clear by clearing all bits in EP_STATUS_STALL_NAK. + EP_STALL_NAK OFFSET(19) NUMBITS(1) [], + /// Raised when any bit in ABORT_DONE is set. Clear by + /// clearing all bits in ABORT_DONE. + ABORT_DONE OFFSET(18) NUMBITS(1) [], + /// Set every time the device receives a SOF (Start of Frame) + /// packet. Cleared by reading SOF_RD + DEV_SOF OFFSET(17) NUMBITS(1) [], + /// Device. Source: SIE_STATUS.SETUP_REC + SETUP_REQ OFFSET(16) NUMBITS(1) [], + /// Set when the device receives a resume from the host. + /// Cleared by writing to SIE_STATUS.RESUME + DEV_RESUME_FROM_HOST OFFSET(15) NUMBITS(1) [], + /// Set when the device suspend state changes. Cleared by + /// writing to SIE_STATUS.SUSPENDED + DEV_SUSPEND OFFSET(14) NUMBITS(1) [], + /// Set when the device connection state changes. Cleared by + /// writing to SIE_STATUS.CONNECTED + DEV_CONN_DIS OFFSET(13) NUMBITS(1) [], + /// Source: SIE_STATUS.BUS_RESET + BUS_RESET OFFSET(12) NUMBITS(1) [], + /// Source: SIE_STATUS.VBUS_DETECT + VBUS_DETECT OFFSET(11) NUMBITS(1) [], + /// Source: SIE_STATUS.STALL_REC + STALL OFFSET(10) NUMBITS(1) [], + /// Source: SIE_STATUS.CRC_ERROR + ERROR_CRC OFFSET(9) NUMBITS(1) [], + /// Source: SIE_STATUS.BIT_STUFF_ERROR + ERROR_BIT_STUFF OFFSET(8) NUMBITS(1) [], + /// Source: SIE_STATUS.RX_OVERFLOW + ERROR_RX_OVERFLOW OFFSET(7) NUMBITS(1) [], + /// Source: SIE_STATUS.RX_TIMEOUT + ERROR_RX_TIMEOUT OFFSET(6) NUMBITS(1) [], + /// Source: SIE_STATUS.DATA_SEQ_ERROR + ERROR_DATA_SEQ OFFSET(5) NUMBITS(1) [], + /// Raised when any bit in BUFF_STATUS is set. Clear by + /// clearing all bits in BUFF_STATUS. + BUFF_STATUS OFFSET(4) NUMBITS(1) [], + /// Raised every time SIE_STATUS.TRANS_COMPLETE is set. + /// Clear by writing to this bit. + TRANS_COMPLETE OFFSET(3) NUMBITS(1) [], + /// Host: raised every time the host sends a SOF (Start of + /// Frame). Cleared by reading SOF_RD + HOST_SOF OFFSET(2) NUMBITS(1) [], + /// Host: raised when a device wakes up the host. Cleared by + /// writing to SIE_STATUS.RESUME + HOST_RESUME OFFSET(1) NUMBITS(1) [], + /// Host: raised when a device is connected or disconnected + /// (i.e. when SIE_STATUS.SPEED changes). Cleared by + /// writing to SIE_STATUS.SPEED + HOST_CONN_DIS OFFSET(0) NUMBITS(1) [] +] +]; + +register_bitfields![u32, + RequestType [ + RECIPIENT OFFSET(0) NUMBITS(5) [ + Device = 0, + Interface = 1, + Endpoint = 2, + Other = 3 + ], + TYPE OFFSET(5) NUMBITS(2) [ + Standard = 0, + Class = 1, + Vendor = 2 + ], + DIRECTION OFFSET(7) NUMBITS(1) [ + HostToDevice = 0, + DeviceToHost = 1 + ] + ], + SETUP_H [ + BM_REQUEST_TYPE OFFSET(0) NUMBITS(8) [], + B_REQUEST OFFSET(8) NUMBITS(8) [ + GET_ADDRESS = 0x05, + GET_DESCRIPTOR = 0x07, + GET_CONFIGURATION = 0x09, + ], + W_VALUE_L OFFSET(16) NUMBITS(8) [], + W_VALUE_H OFFSET(24) NUMBITS(8) [], + ], + SETUP_L [ + W_INDEX_L OFFSET(0) NUMBITS(8) [], + W_INDEX_H OFFSET(8) NUMBITS(8) [], + W_LENGTH_L OFFSET(16) NUMBITS(8) [], + W_LENGTH_H OFFSET(24) NUMBITS(8) [], + ], + EP_CONTROL [ + ENDPOINT_ENABLE OFFSET(31) NUMBITS(1) [], + DOUBLE_BUFFERED OFFSET(30) NUMBITS(1) [], + INTERRUPT_SINGLE_BIT OFFSET(29) NUMBITS(1) [], + INTERRUPT_DOUBLE_BIT OFFSET(28) NUMBITS(1) [], + ENDPOINT_TYPE OFFSET(26) NUMBITS(2) [ + CONTROL = 0, + ISO = 1, + BULK = 2, + INT = 3 + ], + INT_STALL OFFSET(17) NUMBITS(1) [], + INT_NAK OFFSET(16) NUMBITS(1) [], + ADDR_BASE OFFSET(0) NUMBITS(16) [], + ], + EP_BUFFER_CONTROL [ + BUFFER1_FULL OFFSET(31) NUMBITS(1) [], + LAST_BUFFER1 OFFSET(30) NUMBITS(1) [], + DATA_PID1 OFFSET(29) NUMBITS(1) [], + DOUBLE_BUFFERED_OFFSET_ISO OFFSET(27) NUMBITS(2) [ + OFFSET_128 = 0, + OFFSET_256 = 1, + OFFSET_512 = 2, + OFFSET_1024 = 3, + ], + AVAILABLE1 OFFSET(26) NUMBITS(1) [], + TRANSFER_LENGTH1 OFFSET(16) NUMBITS(10) [], + BUFFER0_FULL OFFSET(15) NUMBITS(1) [], + LAST_BUFFER0 OFFSET(14) NUMBITS(1) [], + DATA_PID0 OFFSET(13) NUMBITS(1) [], + RESET_BUFFER OFFSET(12) NUMBITS(1) [], + STALL OFFSET(11) NUMBITS(1) [], + AVAILABLE0 OFFSET(10) NUMBITS(1) [], + TRANSFER_LENGTH0 OFFSET(0) NUMBITS(10) [], + ] +]; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum UsbState { + Disabled, + Started, + Initialized, + PoweredOn, + Attached, + Configured, +} + +#[derive(Copy, Clone, Debug)] +pub enum EndpointState { + Disabled, + Ctrl(CtrlState), + Bulk(TransferType, Option, Option), +} + +impl EndpointState { + fn ctrl_state(self) -> CtrlState { + match self { + EndpointState::Ctrl(state) => state, + _ => panic!("Expected EndpointState::Ctrl"), + } + } + + fn bulk_state(self) -> (TransferType, Option, Option) { + match self { + EndpointState::Bulk(transfer_type, in_state, out_state) => { + (transfer_type, in_state, out_state) + } + _ => panic!("Expected EndpointState::Bulk"), + } + } +} + +/// State of the control endpoint (endpoint 0). +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum CtrlState { + /// Control endpoint is idle, and waiting for a command from the host. + Init, + /// Control endpoint has started an IN transfer. + ReadIn, + /// Control endpoint has moved to the status phase. + ReadStatus, + /// Control endpoint is handling a control write (OUT) transfer. + WriteOut, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum BulkInState { + // The endpoint is ready to perform transactions. + Init, + // There is a pending IN packet transfer on this endpoint. + InData, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum BulkOutState { + // The endpoint is ready to perform transactions. + Init, + // There is a pending OUT packet in this endpoint's buffer, to be read by + // the client application. + OutDelay, + // There is a pending EPDATA to reply to. Store the size right after the + // EPDATA event. + OutData { size: u32 }, +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum EndpointType { + NONE, + IN, + OUT, +} + +pub struct Endpoint<'a> { + slice_in: OptionalCell<&'a [VolatileCell]>, + slice_out: OptionalCell<&'a [VolatileCell]>, + state: Cell, + // Whether a transfer is requested on this IN endpoint. + request_transmit_in: Cell, + // Whether a transfer is requested on this OUT endpoint. + request_transmit_out: Cell, + direction: Cell, +} + +impl Endpoint<'_> { + const fn new() -> Self { + Endpoint { + slice_in: OptionalCell::empty(), + slice_out: OptionalCell::empty(), + state: Cell::new(EndpointState::Disabled), + request_transmit_in: Cell::new(false), + request_transmit_out: Cell::new(false), + direction: Cell::new(EndpointType::NONE), + } + } +} + +const USBCTRL_DPSRAM: StaticRef = + unsafe { StaticRef::new(0x50100000 as *const Usbctrl_DPSRAM) }; + +const USBCTRL_REGS_BASE: StaticRef = + unsafe { StaticRef::new(0x50110000 as *const Usbctrl_RegsRegisters) }; + +pub const N_ENDPOINTS: usize = 16; + +pub struct UsbCtrl<'a> { + dpsram: StaticRef, + registers: StaticRef, + state: OptionalCell, + client: OptionalCell<&'a dyn hil::usb::Client<'a>>, + descriptors: [Endpoint<'a>; N_ENDPOINTS], + should_set_address: VolatileCell, + address: VolatileCell, + next_pid_in: [VolatileCell; 16], + next_pid_out: [VolatileCell; 16], + errata_pin: OptionalCell<&'a RPGpioPin<'a>>, + counter: VolatileCell, +} + +impl<'a> UsbCtrl<'a> { + pub const fn new() -> Self { + Self { + dpsram: USBCTRL_DPSRAM, + registers: USBCTRL_REGS_BASE, + client: OptionalCell::empty(), + state: OptionalCell::new(UsbState::Disabled), + descriptors: [ + Endpoint::new(), + Endpoint::new(), + Endpoint::new(), + Endpoint::new(), + Endpoint::new(), + Endpoint::new(), + Endpoint::new(), + Endpoint::new(), + Endpoint::new(), + Endpoint::new(), + Endpoint::new(), + Endpoint::new(), + Endpoint::new(), + Endpoint::new(), + Endpoint::new(), + Endpoint::new(), + ], + should_set_address: VolatileCell::new(false), + address: VolatileCell::new(0), + next_pid_in: [ + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + ], + next_pid_out: [ + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + VolatileCell::new(0), + ], + errata_pin: OptionalCell::empty(), + counter: VolatileCell::new(0), + } + } + + fn nop_wait(&self) { + for _i in 0..100 { + cortexm0p::support::nop() + } + } + + // This is errata RP2040-E5 https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf#RP2040-E5 + pub fn set_gpio(&self, gpio_pin: &'a RPGpioPin<'a>) { + self.errata_pin.set(gpio_pin); + } + + pub fn enable(&self) { + self.registers + .usb_muxing + .modify(USB_MUXING::TO_PHY::SET + USB_MUXING::SOFTCON::SET); + self.registers + .usb_pwr + .modify(USB_PWR::VBUS_DETECT::SET + USB_PWR::VBUS_DETECT_OVERRIDE_EN::SET); + self.registers.main_ctrl.modify( + MAIN_CTRL::CONTROLLER_EN::SET + + MAIN_CTRL::HOST_NDEVICE::CLEAR + + MAIN_CTRL::SIM_TIMING::CLEAR, + ); + + self.apply_errata_e5(); + self.state.set(UsbState::Started); + } + + pub fn get_state(&self) -> UsbState { + self.state.unwrap_or_panic() + } + + // Allows the peripheral to be enumerated by the USB master + fn start(&self) { + if self.get_state() == UsbState::Disabled { + self.enable(); + self.registers + .inte + .modify(INTE::SETUP_REQ::SET + INTE::BUFF_STATUS::SET + INTE::BUS_RESET::SET); + self.registers + .sie_ctrl + .modify(SIE_CTRL::EP0_DOUBLE_BUF::CLEAR + SIE_CTRL::EP0_INT_1BUF::SET); + } + } + + pub fn enable_pullup(&self) { + if self.get_state() == UsbState::Started { + self.registers.sie_ctrl.modify(SIE_CTRL::PULLUP_EN::SET); + } + self.state.set(UsbState::Attached); + } + + pub fn disable_pullup(&self) { + self.state.set(UsbState::Started); + self.registers.sie_ctrl.modify(SIE_CTRL::PULLUP_EN::CLEAR); + } + + fn enable_in_endpoint_(&self, transfer_type: TransferType, endpoint: usize) { + self.descriptors[endpoint].state.set(match endpoint { + 0 => { + self.dpsram.ep_buf_ctrl[endpoint].ep_in_buf_ctrl.set(0); + self.dpsram.ep_buf_ctrl[endpoint] + .ep_in_buf_ctrl + .modify(EP_BUFFER_CONTROL::DATA_PID0::SET + EP_BUFFER_CONTROL::AVAILABLE0::SET); + EndpointState::Ctrl(CtrlState::Init) + } + 1..=N_ENDPOINTS => { + self.dpsram.ep_ctrl[endpoint - 1].ep_in_ctrl.write( + EP_CONTROL::ENDPOINT_ENABLE::SET + + EP_CONTROL::DOUBLE_BUFFERED::CLEAR + + EP_CONTROL::ENDPOINT_TYPE::BULK + + EP_CONTROL::INTERRUPT_SINGLE_BIT::SET + + EP_CONTROL::INTERRUPT_DOUBLE_BIT::CLEAR + + EP_CONTROL::ADDR_BASE.val((0x180 + 64 * (endpoint - 1)) as u32), + ); + self.dpsram.ep_buf_ctrl[endpoint].ep_in_buf_ctrl.set(0); + self.descriptors[endpoint].direction.set(EndpointType::IN); + EndpointState::Bulk(transfer_type, Some(BulkInState::Init), None) + } + _ => unreachable!("unexisting endpoint"), + }); + } + + fn enable_out_endpoint_(&self, transfer_type: TransferType, endpoint: usize) { + self.descriptors[endpoint].state.set(match endpoint { + 0 => { + self.dpsram.ep_buf_ctrl[endpoint].ep_out_buf_ctrl.set(0); + self.dpsram.ep_buf_ctrl[endpoint] + .ep_out_buf_ctrl + .modify(EP_BUFFER_CONTROL::DATA_PID0::SET); + EndpointState::Ctrl(CtrlState::Init) + } + 1..=N_ENDPOINTS => { + self.dpsram.ep_ctrl[endpoint].ep_out_ctrl.set(0); + self.dpsram.ep_ctrl[endpoint - 1].ep_out_ctrl.modify( + EP_CONTROL::ENDPOINT_ENABLE::SET + + EP_CONTROL::DOUBLE_BUFFERED::CLEAR + + EP_CONTROL::ENDPOINT_TYPE::BULK + + EP_CONTROL::INTERRUPT_SINGLE_BIT::SET + + EP_CONTROL::INTERRUPT_DOUBLE_BIT::CLEAR + + EP_CONTROL::ADDR_BASE.val((0x180 + 64 * (endpoint - 1)) as u32), + ); + self.dpsram.ep_buf_ctrl[endpoint].ep_out_buf_ctrl.set(0); + self.descriptors[endpoint].direction.set(EndpointType::OUT); + EndpointState::Bulk(transfer_type, None, Some(BulkOutState::Init)) + } + _ => unreachable!("unexisting endpoint"), + }); + } + + fn apply_errata_e5(&self) { + self.errata_pin.map(|p| { + let (prev_ctrl, prev_pad) = p.start_usb_errata(); + self.registers.usb_muxing.set(0); + self.registers + .usb_muxing + .modify(USB_MUXING::TO_DIGITAL_PAD::SET + USB_MUXING::SOFTCON::SET); + for _i in 0..106400 { + cortexm0p::support::nop() + } + self.registers.usb_muxing.set(0); + self.registers + .usb_muxing + .modify(USB_MUXING::TO_PHY::SET + USB_MUXING::SOFTCON::SET); + p.finish_usb_errata(prev_ctrl, prev_pad); + }); + } + + fn handle_bus_reset(&self) { + for (ep, desc) in self.descriptors.iter().enumerate() { + match desc.state.get() { + EndpointState::Disabled => {} + EndpointState::Ctrl(_) => desc.state.set(EndpointState::Ctrl(CtrlState::Init)), + EndpointState::Bulk(transfer_type, in_state, out_state) => { + desc.state.set(EndpointState::Bulk( + transfer_type, + in_state.map(|_| BulkInState::Init), + out_state.map(|_| BulkOutState::Init), + )); + if out_state.is_some() { + self.dpsram.ep_buf_ctrl[ep].ep_out_buf_ctrl.set(0); + self.dpsram.ep_buf_ctrl[ep].ep_out_buf_ctrl.modify( + EP_BUFFER_CONTROL::AVAILABLE0::SET + + EP_BUFFER_CONTROL::TRANSFER_LENGTH0.val(64_u32), + ); + } + } + } + self.next_pid_in[ep].set(0); + self.next_pid_out[ep].set(0); + desc.request_transmit_in.set(false); + desc.request_transmit_out.set(false); + } + self.dpsram.ep_buf_ctrl[0] + .ep_out_buf_ctrl + .modify(EP_BUFFER_CONTROL::DATA_PID0::SET); + + self.dpsram.ep_buf_ctrl[0].ep_in_buf_ctrl.modify( + EP_BUFFER_CONTROL::AVAILABLE0::SET + + EP_BUFFER_CONTROL::TRANSFER_LENGTH0.val(64) + + EP_BUFFER_CONTROL::DATA_PID0::CLEAR, + ); + self.registers.buff_status.set(0); + self.registers.addr_endp.modify(ADDR_ENDP::ADDRESS.val(0)); + + self.address.set(0); + self.client.map(|client| { + client.bus_reset(); + }); + self.registers.sie_status.modify(SIE_STATUS::BUS_RESET::SET); + } + + pub fn handle_interrupt(&self) { + if self.registers.ints.is_set(INTS::BUS_RESET) { + self.handle_bus_reset(); + } + + if self.registers.buff_status.get() != 0 { + self.handle_buff_status(); + } + + self.process_requests(); + + if self.registers.ints.is_set(INTS::SETUP_REQ) { + self.registers.sie_status.modify(SIE_STATUS::SETUP_REC::SET); + self.usb_handle_setup_packet(); + } + } + + fn handle_buff_status(&self) { + // Endpoint 0 + if self.registers.buff_status.is_set(BUFF_STATUS::EP0_IN) { + self.handle_ep0datadone(); + } + if self.registers.buff_status.is_set(BUFF_STATUS::EP0_OUT) { + self.handle_endepout(0); + } + // Endpoint 1 + if self.registers.buff_status.is_set(BUFF_STATUS::EP1_IN) { + self.handle_endepin(1); + } + if self.registers.buff_status.is_set(BUFF_STATUS::EP1_OUT) { + self.handle_epdata_out(1); + } + // Endpoint 2 + if self.registers.buff_status.is_set(BUFF_STATUS::EP2_IN) { + self.handle_epdata_in(2); + } + if self.registers.buff_status.is_set(BUFF_STATUS::EP2_OUT) { + self.handle_epdata_out(2); + } + // Endpoint 3 + if self.registers.buff_status.is_set(BUFF_STATUS::EP3_IN) { + self.handle_endepin(3); + } + if self.registers.buff_status.is_set(BUFF_STATUS::EP3_OUT) { + self.handle_epdata_out(3); + } + // Endpoint 4 + if self.registers.buff_status.is_set(BUFF_STATUS::EP4_IN) { + self.handle_epdata_in(4); + } + if self.registers.buff_status.is_set(BUFF_STATUS::EP4_OUT) { + self.handle_epdata_out(4); + } + // Endpoint 5 + if self.registers.buff_status.is_set(BUFF_STATUS::EP5_IN) { + self.handle_epdata_in(5); + } + if self.registers.buff_status.is_set(BUFF_STATUS::EP5_OUT) { + self.registers + .buff_status + .modify(BUFF_STATUS::EP5_OUT::CLEAR); + self.handle_epdata_out(5); + } + // Endpoint 6 + if self.registers.buff_status.is_set(BUFF_STATUS::EP6_IN) { + self.registers + .buff_status + .modify(BUFF_STATUS::EP6_IN::CLEAR); + self.handle_epdata_in(6); + } + if self.registers.buff_status.is_set(BUFF_STATUS::EP6_OUT) { + self.registers + .buff_status + .modify(BUFF_STATUS::EP6_OUT::CLEAR); + self.handle_epdata_out(6); + } + // Endpoint 7 + if self.registers.buff_status.is_set(BUFF_STATUS::EP7_IN) { + self.registers + .buff_status + .modify(BUFF_STATUS::EP7_IN::CLEAR); + self.handle_epdata_in(7); + } + if self.registers.buff_status.is_set(BUFF_STATUS::EP7_OUT) { + self.registers + .buff_status + .modify(BUFF_STATUS::EP7_OUT::CLEAR); + self.handle_epdata_out(7); + } + // Endpoint 8 + if self.registers.buff_status.is_set(BUFF_STATUS::EP8_IN) { + self.registers + .buff_status + .modify(BUFF_STATUS::EP8_IN::CLEAR); + self.handle_epdata_in(8); + } + if self.registers.buff_status.is_set(BUFF_STATUS::EP8_OUT) { + self.registers + .buff_status + .modify(BUFF_STATUS::EP8_OUT::CLEAR); + self.handle_epdata_out(8); + } + // Endpoint 9 + if self.registers.buff_status.is_set(BUFF_STATUS::EP9_IN) { + self.registers + .buff_status + .modify(BUFF_STATUS::EP9_IN::CLEAR); + self.handle_epdata_in(9); + } + if self.registers.buff_status.is_set(BUFF_STATUS::EP9_OUT) { + self.registers + .buff_status + .modify(BUFF_STATUS::EP9_OUT::CLEAR); + self.handle_epdata_out(9); + } + // Endpoint 10 + if self.registers.buff_status.is_set(BUFF_STATUS::EP10_IN) { + self.registers + .buff_status + .modify(BUFF_STATUS::EP10_IN::CLEAR); + self.handle_epdata_in(10); + } + if self.registers.buff_status.is_set(BUFF_STATUS::EP10_OUT) { + self.registers + .buff_status + .modify(BUFF_STATUS::EP10_OUT::CLEAR); + self.handle_epdata_out(10); + } + // Endpoint 11 + if self.registers.buff_status.is_set(BUFF_STATUS::EP11_IN) { + self.registers + .buff_status + .modify(BUFF_STATUS::EP11_IN::CLEAR); + self.handle_epdata_in(11); + } + if self.registers.buff_status.is_set(BUFF_STATUS::EP11_OUT) { + self.registers + .buff_status + .modify(BUFF_STATUS::EP11_OUT::CLEAR); + self.handle_epdata_out(11); + } + // Endpoint 12 + if self.registers.buff_status.is_set(BUFF_STATUS::EP12_IN) { + self.registers + .buff_status + .modify(BUFF_STATUS::EP12_IN::CLEAR); + self.handle_epdata_in(12); + } + if self.registers.buff_status.is_set(BUFF_STATUS::EP12_OUT) { + self.registers + .buff_status + .modify(BUFF_STATUS::EP12_OUT::CLEAR); + self.handle_epdata_out(12); + } + // Endpoint 13 + if self.registers.buff_status.is_set(BUFF_STATUS::EP13_IN) { + self.registers + .buff_status + .modify(BUFF_STATUS::EP13_IN::CLEAR); + self.handle_epdata_in(13); + } + if self.registers.buff_status.is_set(BUFF_STATUS::EP13_OUT) { + self.registers + .buff_status + .modify(BUFF_STATUS::EP13_OUT::CLEAR); + self.handle_epdata_out(13); + } + // Endpoint 14 + if self.registers.buff_status.is_set(BUFF_STATUS::EP14_IN) { + self.handle_epdata_in(14); + self.registers + .buff_status + .modify(BUFF_STATUS::EP14_IN::CLEAR); + } + if self.registers.buff_status.is_set(BUFF_STATUS::EP14_OUT) { + self.handle_epdata_out(14); + self.registers + .buff_status + .modify(BUFF_STATUS::EP14_OUT::CLEAR); + } + // Endpoint 15 + if self.registers.buff_status.is_set(BUFF_STATUS::EP15_IN) { + self.handle_epdata_in(15); + } + self.registers + .buff_status + .modify(BUFF_STATUS::EP15_IN::CLEAR); + + if self.registers.buff_status.is_set(BUFF_STATUS::EP15_OUT) { + self.handle_epdata_out(15); + } + self.registers + .buff_status + .modify(BUFF_STATUS::EP15_OUT::CLEAR); + + self.registers.buff_status.set(0); + } + + fn process_requests(&self) { + for (endpoint, desc) in self.descriptors.iter().enumerate() { + if desc.request_transmit_in.take() { + if endpoint == 0 { + self.transmit_in_ep0(); + } else { + self.transmit_in(endpoint); + } + } + if desc.request_transmit_out.take() { + if endpoint == 0 { + self.transmit_out_ep0(); + } else { + self.transmit_out(endpoint); + } + } + } + } + + fn handle_epdata_out(&self, ep: usize) { + let (transfer_type, in_state, out_state) = self.descriptors[ep].state.get().bulk_state(); + assert!(out_state.is_some()); + + // We need to read the size at this point in the process. At this point + // the USB hardware has received the data, but we need to + // copy the data to memory. Later on the EPOUT.SIZE register can + // be overwritten, particularly if the host is sending OUT + // transactions quickly. + let ep_size = self.dpsram.ep_buf_ctrl[ep] + .ep_out_buf_ctrl + .read(EP_BUFFER_CONTROL::TRANSFER_LENGTH0); + + match out_state.unwrap() { + BulkOutState::Init => { + // The endpoint is ready to receive data. Request a transmit_out. + self.descriptors[ep].request_transmit_out.set(true); + } + BulkOutState::OutDelay => { + // The endpoint will be resumed later by the client application with transmit_out(). + } + BulkOutState::OutData { size: _ } => { + self.descriptors[ep].request_transmit_out.set(true); + } + } + // Indicate that the endpoint now has data available. + self.descriptors[ep].state.set(EndpointState::Bulk( + transfer_type, + in_state, + Some(BulkOutState::OutData { size: ep_size }), + )); + } + + fn handle_epdata_in(&self, endpoint: usize) { + let (transfer_type, in_state, out_state) = + self.descriptors[endpoint].state.get().bulk_state(); + assert!(in_state.is_some()); + match in_state.unwrap() { + BulkInState::InData => { + // Totally expected state. Nothing to do. + self.client + .map(|client| client.packet_transmitted(endpoint)); + self.descriptors[endpoint].state.set(EndpointState::Bulk( + transfer_type, + Some(BulkInState::Init), + out_state, + )); + } + BulkInState::Init => {} + } + } + + fn usb_handle_setup_packet(&self) { + let endpoint = 0; + + // We are idle, and ready for any control transfer. + let state = self.descriptors[endpoint].state.get().ctrl_state(); + match state { + CtrlState::Init => { + let ep_buf = &self.descriptors[endpoint].slice_out; + let ep_buf = ep_buf.unwrap_or_panic(); + if ep_buf.len() < 8 { + panic!("EP0 DMA buffer length < 8"); + } + + // Re-construct the SETUP packet from various registers. The + // client's ctrl_setup() will parse it as a SetupData + // descriptor. + ep_buf[0].set(self.dpsram.setup_h.read(SETUP_H::BM_REQUEST_TYPE) as u8); + ep_buf[1].set(self.dpsram.setup_h.read(SETUP_H::B_REQUEST) as u8); + ep_buf[2].set(self.dpsram.setup_h.read(SETUP_H::W_VALUE_L) as u8); + ep_buf[3].set(self.dpsram.setup_h.read(SETUP_H::W_VALUE_H) as u8); + ep_buf[4].set(self.dpsram.setup_l.read(SETUP_L::W_INDEX_L) as u8); + ep_buf[5].set(self.dpsram.setup_l.read(SETUP_L::W_INDEX_H) as u8); + ep_buf[6].set(self.dpsram.setup_l.read(SETUP_L::W_LENGTH_L) as u8); + ep_buf[7].set(self.dpsram.setup_l.read(SETUP_L::W_LENGTH_H) as u8); + + let size = self.dpsram.setup_l.read(SETUP_L::W_LENGTH_L) + + (self.dpsram.setup_l.read(SETUP_L::W_LENGTH_H) << 8); + self.client.map(|client| { + // Notify the client that the ctrl setup event has occurred. + // Allow it to configure any data we need to send back. + match client.ctrl_setup(endpoint) { + hil::usb::CtrlSetupResult::OkSetAddress => { + self.should_set_address.set(true); + self.send_empty_in(endpoint); + self.descriptors[0] + .state + .set(EndpointState::Ctrl(CtrlState::ReadStatus)); + } + hil::usb::CtrlSetupResult::Ok => { + // Setup request is successful. + if size == 0 { + // Directly handle a 0 length setup request. + self.send_empty_in(endpoint); + } else { + match self.dpsram.setup_h.read(SETUP_H::BM_REQUEST_TYPE) >> 7 { + 0 => { + self.send_empty_in(endpoint); + + self.transmit_out_ep0(); + } + 1 => { + self.descriptors[endpoint] + .state + .set(EndpointState::Ctrl(CtrlState::ReadIn)); + // Transmit first packet. + self.next_pid_in[endpoint].set(1); + self.transmit_in_ep0(); + } + _ => { + unreachable!() + } + } + } + } + _err => { + // An error occurred, we stall the endpoint. + self.registers + .ep_stall_arm + .modify(EP_STALL_ARM::EP0_IN::SET); + self.dpsram.ep_buf_ctrl[0] + .ep_in_buf_ctrl + .modify(EP_BUFFER_CONTROL::STALL::SET); + } + } + }); + } + + CtrlState::ReadIn | CtrlState::ReadStatus | CtrlState::WriteOut => { + // Unexpected state to receive a SETUP packet. Let's STALL the endpoint. + self.registers.sie_ctrl.write(SIE_CTRL::EP0_INT_STALL::SET); + } + } + } + + fn handle_ep0datadone(&self) { + let endpoint = 0; + let state = self.descriptors[endpoint].state.get().ctrl_state(); + + match state { + CtrlState::ReadIn => { + self.transmit_in_ep0(); + } + + CtrlState::ReadStatus => { + self.complete_ctrl_status(); + } + + CtrlState::WriteOut => { + // We just completed the Setup stage for a CTRL WRITE transfer. + self.transmit_out_ep0(); + } + + CtrlState::Init => { + self.send_empty_in(0); + self.complete_ctrl_status(); + } + } + + self.nop_wait(); + + self.dpsram.ep_buf_ctrl[0] + .ep_in_buf_ctrl + .modify(EP_BUFFER_CONTROL::AVAILABLE0::SET); + } + + fn handle_endepin(&self, endpoint: usize) { + match endpoint { + 0 => {} + 1..=N_ENDPOINTS => { + let (transfer_type, _in_state, out_state) = + self.descriptors[endpoint].state.get().bulk_state(); + self.descriptors[endpoint].state.set(EndpointState::Bulk( + transfer_type, + Some(BulkInState::InData), + out_state, + )); + } + _ => panic!("unexisting endpoint"), + } + + // Nothing else to do. Wait for the EPDATA event. + } + + fn handle_endepout(&self, endpoint: usize) { + match endpoint { + 0 => { + // We got data on the control endpoint during a CTRL WRITE + // transfer. Let the client handle the data, and then finish up + // the control write by moving to the status stage. + + // Now we can handle it and pass it to the client to see + // what the client returns. + + if self.dpsram.ep0_buffer0[0].get() == 128 + && self.dpsram.ep0_buffer0[1].get() == 37 + && self.dpsram.ep0_buffer0[2].get() == 0 + { + self.dpsram.ep0_buffer0[0].set(0); + self.dpsram.ep0_buffer0[1].set(194); + self.dpsram.ep0_buffer0[2].set(1); + } + + self.transmit_out_ep0(); + self.client.map(|client| { + match client.ctrl_out( + endpoint, + self.dpsram.ep_buf_ctrl[endpoint] + .ep_out_buf_ctrl + .read(EP_BUFFER_CONTROL::TRANSFER_LENGTH0), + ) { + hil::usb::CtrlOutResult::Ok => { + // We only handle the simple case where we have + // received all of the data we need to. + self.complete_ctrl_status(); + } + hil::usb::CtrlOutResult::Delay => {} + _ => { + // Respond with STALL to any following transactions + // in this request + self.registers + .ep_stall_arm + .modify(EP_STALL_ARM::EP0_OUT::SET); + self.dpsram.ep_buf_ctrl[0] + .ep_in_buf_ctrl + .modify(EP_BUFFER_CONTROL::STALL::SET); + } + }; + }); + } + 1..=N_ENDPOINTS => { + // Notify the client about the new packet. + let (transfer_type, in_state, out_state) = + self.descriptors[endpoint].state.get().bulk_state(); + + let packet_bytes = if let Some(BulkOutState::OutData { size }) = out_state { + size + } else { + 0 + }; + + self.client.map(|client| { + let result = client.packet_out(transfer_type, endpoint, packet_bytes); + let new_out_state = match result { + hil::usb::OutResult::Ok => { + if self.dpsram.ep_buf_ctrl[endpoint] + .ep_out_buf_ctrl + .read(EP_BUFFER_CONTROL::DATA_PID0) + == 0 + { + self.dpsram.ep_buf_ctrl[endpoint].ep_out_buf_ctrl.modify( + EP_BUFFER_CONTROL::TRANSFER_LENGTH0.val(packet_bytes) + + EP_BUFFER_CONTROL::DATA_PID0::SET + + EP_BUFFER_CONTROL::BUFFER0_FULL::CLEAR, + ); + self.next_pid_out[endpoint].set(0); + } else { + self.dpsram.ep_buf_ctrl[endpoint].ep_out_buf_ctrl.modify( + EP_BUFFER_CONTROL::TRANSFER_LENGTH0.val(packet_bytes) + + EP_BUFFER_CONTROL::DATA_PID0::CLEAR + + EP_BUFFER_CONTROL::BUFFER0_FULL::CLEAR, + ); + self.next_pid_out[endpoint].set(1); + } + self.nop_wait(); + self.dpsram.ep_buf_ctrl[endpoint] + .ep_out_buf_ctrl + .modify(EP_BUFFER_CONTROL::AVAILABLE0::SET); + BulkOutState::Init + } + + hil::usb::OutResult::Delay => { + // We can't send the packet now. Wait for a resume_out call from the client. + BulkOutState::OutDelay + } + + hil::usb::OutResult::Error => { + self.registers + .ep_stall_arm + .modify(EP_STALL_ARM::EP0_OUT::SET); + self.dpsram.ep_buf_ctrl[endpoint] + .ep_out_buf_ctrl + .modify(EP_BUFFER_CONTROL::STALL::SET); + BulkOutState::Init + } + }; + self.descriptors[endpoint].state.set(EndpointState::Bulk( + transfer_type, + in_state, + Some(new_out_state), + )); + }); + } + _ => unreachable!("unexisting endpoint"), + } + } + + fn transmit_in_ep0(&self) { + let endpoint = 0; + self.client.map(|client| { + match client.ctrl_in(endpoint) { + hil::usb::CtrlInResult::Packet(size, last) => { + if size == 0 { + internal_err!("Empty ctrl packet?"); + } + let slice = self.descriptors[endpoint].slice_in.unwrap_or_panic(); + + for idx in 0..size { + self.dpsram.ep0_buffer0[idx].set(slice[idx].get()); + } + + if self.next_pid_in[endpoint].get() == 1 { + self.dpsram.ep_buf_ctrl[endpoint].ep_in_buf_ctrl.modify( + EP_BUFFER_CONTROL::TRANSFER_LENGTH0.val(size as u32) + + EP_BUFFER_CONTROL::BUFFER0_FULL::SET + + EP_BUFFER_CONTROL::DATA_PID0::SET, + ); + self.next_pid_in[endpoint].set(0); + } else { + self.dpsram.ep_buf_ctrl[endpoint].ep_in_buf_ctrl.modify( + EP_BUFFER_CONTROL::TRANSFER_LENGTH0.val(size as u32) + + EP_BUFFER_CONTROL::BUFFER0_FULL::SET + + EP_BUFFER_CONTROL::DATA_PID0::CLEAR, + ); + self.next_pid_in[endpoint].set(1); + } + self.nop_wait(); + self.dpsram.ep_buf_ctrl[endpoint] + .ep_in_buf_ctrl + .modify(EP_BUFFER_CONTROL::AVAILABLE0::SET); + if last { + self.transmit_out_ep0(); + self.complete_ctrl_status(); + } + } + + hil::usb::CtrlInResult::Delay => { + self.registers.sie_ctrl.write(SIE_CTRL::EP0_INT_NAK::SET); + } + + hil::usb::CtrlInResult::Error => { + // An error occurred, we STALL + self.registers + .ep_stall_arm + .modify(EP_STALL_ARM::EP0_IN::SET); + self.registers.sie_ctrl.write(SIE_CTRL::EP0_INT_STALL::SET); + self.descriptors[endpoint] + .state + .set(EndpointState::Ctrl(CtrlState::Init)); + } + }; + }); + } + + fn transmit_out_ep0(&self) { + let endpoint = 0; + + let slice = self.descriptors[endpoint].slice_out.unwrap_or_panic(); + + for idx in 0..self.dpsram.ep_buf_ctrl[endpoint] + .ep_out_buf_ctrl + .read(EP_BUFFER_CONTROL::TRANSFER_LENGTH0) as usize + { + slice[idx].set(self.dpsram.ep0_buffer0[idx].get()); + } + + if self.dpsram.ep_buf_ctrl[endpoint] + .ep_out_buf_ctrl + .read(EP_BUFFER_CONTROL::DATA_PID0) + == 0 + { + self.dpsram.ep_buf_ctrl[endpoint].ep_out_buf_ctrl.modify( + EP_BUFFER_CONTROL::TRANSFER_LENGTH0.val(slice.len() as u32) + + EP_BUFFER_CONTROL::DATA_PID0::SET + + EP_BUFFER_CONTROL::BUFFER0_FULL::CLEAR, + ); + self.next_pid_out[endpoint].set(0); + self.nop_wait(); + self.dpsram.ep_buf_ctrl[endpoint] + .ep_out_buf_ctrl + .modify(EP_BUFFER_CONTROL::AVAILABLE0::SET); + } else { + self.dpsram.ep_buf_ctrl[endpoint].ep_out_buf_ctrl.modify( + EP_BUFFER_CONTROL::TRANSFER_LENGTH0.val(slice.len() as u32) + + EP_BUFFER_CONTROL::DATA_PID0::CLEAR + + EP_BUFFER_CONTROL::BUFFER0_FULL::CLEAR, + ); + self.next_pid_out[endpoint].set(1); + self.nop_wait(); + self.dpsram.ep_buf_ctrl[endpoint] + .ep_out_buf_ctrl + .modify(EP_BUFFER_CONTROL::AVAILABLE0::SET); + } + } + + fn complete_ctrl_status(&self) { + let endpoint = 0; + self.client.map(|client| { + client.ctrl_status(endpoint); + if self.should_set_address.get() { + self.should_set_address.set(false); + } + client.ctrl_status_complete(endpoint); + self.descriptors[endpoint] + .state + .set(EndpointState::Ctrl(CtrlState::Init)); + }); + } + + fn transmit_in(&self, endpoint: usize) { + self.client.map(|client| { + let (transfer_type, in_state, out_state) = + self.descriptors[endpoint].state.get().bulk_state(); + assert_eq!(in_state, Some(BulkInState::Init)); + + let result = client.packet_in(transfer_type, endpoint); + + let new_in_state = match result { + hil::usb::InResult::Packet(size) => { + let slice = self.descriptors[endpoint].slice_in.unwrap_or_panic(); + + self.counter.set(self.counter.get() + 1); + for idx in 0..size { + self.dpsram.buffers[(64 * (endpoint - 1)) + idx].set(slice[idx].get()); + } + if self.next_pid_in[endpoint].get() == 1 { + self.dpsram.ep_buf_ctrl[endpoint].ep_in_buf_ctrl.modify( + EP_BUFFER_CONTROL::TRANSFER_LENGTH0.val(size as u32) + + EP_BUFFER_CONTROL::BUFFER0_FULL::SET + + EP_BUFFER_CONTROL::DATA_PID0::SET, + ); + self.next_pid_in[endpoint].set(0); + } else { + self.dpsram.ep_buf_ctrl[endpoint].ep_in_buf_ctrl.modify( + EP_BUFFER_CONTROL::TRANSFER_LENGTH0.val(size as u32) + + EP_BUFFER_CONTROL::BUFFER0_FULL::SET + + EP_BUFFER_CONTROL::DATA_PID0::CLEAR, + ); + self.next_pid_in[endpoint].set(1); + } + self.nop_wait(); + self.dpsram.ep_buf_ctrl[endpoint] + .ep_in_buf_ctrl + .modify(EP_BUFFER_CONTROL::AVAILABLE0::SET); + self.descriptors[endpoint].request_transmit_in.set(false); + BulkInState::InData + } + + hil::usb::InResult::Delay => { + // No packet to send now. Wait for a resume call from the client. + BulkInState::Init + } + + hil::usb::InResult::Error => { + self.dpsram.ep_buf_ctrl[endpoint] + .ep_in_buf_ctrl + .modify(EP_BUFFER_CONTROL::STALL::SET); + BulkInState::Init + } + }; + self.descriptors[endpoint].state.set(EndpointState::Bulk( + transfer_type, + Some(new_in_state), + out_state, + )); + }); + } + + fn send_empty_in(&self, endpoint: usize) { + match endpoint { + 0 => { + self.dpsram.ep_buf_ctrl[0].ep_in_buf_ctrl.modify( + EP_BUFFER_CONTROL::TRANSFER_LENGTH0.val(0) + + EP_BUFFER_CONTROL::BUFFER0_FULL::SET + + EP_BUFFER_CONTROL::DATA_PID0::SET, + ); + self.next_pid_in[endpoint].set(1); + self.nop_wait(); + self.dpsram.ep_buf_ctrl[0] + .ep_in_buf_ctrl + .modify(EP_BUFFER_CONTROL::AVAILABLE0::SET); + } + 1..=N_ENDPOINTS => { + if self.dpsram.ep_buf_ctrl[endpoint] + .ep_in_buf_ctrl + .read(EP_BUFFER_CONTROL::DATA_PID0) + == 0 + { + self.dpsram.ep_buf_ctrl[endpoint].ep_in_buf_ctrl.modify( + EP_BUFFER_CONTROL::TRANSFER_LENGTH0.val(0) + + EP_BUFFER_CONTROL::BUFFER0_FULL::SET + + EP_BUFFER_CONTROL::DATA_PID0::SET, + ); + } else { + self.dpsram.ep_buf_ctrl[endpoint].ep_in_buf_ctrl.modify( + EP_BUFFER_CONTROL::TRANSFER_LENGTH0.val(0) + + EP_BUFFER_CONTROL::BUFFER0_FULL::SET + + EP_BUFFER_CONTROL::DATA_PID0::SET, + ); + } + self.nop_wait(); + self.dpsram.ep_buf_ctrl[endpoint] + .ep_in_buf_ctrl + .modify(EP_BUFFER_CONTROL::AVAILABLE0::SET); + } + _ => unreachable!("unexisting endpoint"), + } + } + + fn transmit_out(&self, endpoint: usize) { + let size = self.dpsram.ep_buf_ctrl[endpoint] + .ep_out_buf_ctrl + .read(EP_BUFFER_CONTROL::TRANSFER_LENGTH0); + + let slice = self.descriptors[endpoint].slice_out.unwrap_or_panic(); + + for idx in 0..size as usize { + slice[idx].set(self.dpsram.buffers[(64 * (endpoint - 1)) + idx].get()); + } + let (transfer_type, in_state, out_state) = + self.descriptors[endpoint].state.get().bulk_state(); + // Starting the receiving can only happen in the OutData state, i.e. after an EPDATA event. + assert!(matches!(out_state, Some(BulkOutState::OutData { .. }))); + self.descriptors[endpoint].request_transmit_out.set(false); + let size = if let Some(BulkOutState::OutData { size }) = out_state { + size + } else { + 0 + }; + + self.descriptors[endpoint].state.set(EndpointState::Bulk( + transfer_type, + in_state, + Some(BulkOutState::OutData { size }), + )); + + self.handle_endepout(endpoint); + } +} + +impl<'a> hil::usb::UsbController<'a> for UsbCtrl<'a> { + fn set_client(&self, client: &'a dyn hil::usb::Client<'a>) { + self.client.set(client); + } + + fn endpoint_set_ctrl_buffer(&self, buf: &'a [VolatileCell]) { + if buf.len() < 8 { + panic!("Endpoint buffer must be at least 8 bytes"); + } + if !buf.len().is_power_of_two() { + panic!("Buffer size must be a power of 2"); + } + self.descriptors[0].slice_in.set(buf); + self.descriptors[0].slice_out.set(buf); + } + + fn endpoint_set_in_buffer(&self, endpoint: usize, buf: &'a [VolatileCell]) { + if buf.len() < 8 { + panic!("Endpoint buffer must be at least 8 bytes"); + } + if !buf.len().is_power_of_two() { + panic!("Buffer size must be a power of 2"); + } + if endpoint == 0 || endpoint >= N_ENDPOINTS { + panic!("Endpoint number is invalid"); + } + self.descriptors[endpoint].slice_in.set(buf); + } + + fn endpoint_set_out_buffer(&self, endpoint: usize, buf: &'a [VolatileCell]) { + if buf.len() < 8 { + panic!("Endpoint buffer must be at least 8 bytes"); + } + if !buf.len().is_power_of_two() { + panic!("Buffer size must be a power of 2"); + } + if endpoint == 0 || endpoint >= N_ENDPOINTS { + panic!("Endpoint number is invalid"); + } + self.descriptors[endpoint].slice_out.set(buf); + } + + fn enable_as_device(&self, speed: hil::usb::DeviceSpeed) { + match speed { + hil::usb::DeviceSpeed::Low => internal_err!("Low speed is not supported"), + hil::usb::DeviceSpeed::Full => {} + } + self.start(); + } + + fn attach(&self) { + self.enable_pullup(); + } + + fn detach(&self) { + self.disable_pullup(); + } + + fn set_address(&self, addr: u16) { + self.address.set(addr as u32); + } + + fn enable_address(&self) { + self.registers + .addr_endp + .modify(ADDR_ENDP::ADDRESS.val(self.address.get())); + } + + fn endpoint_in_enable(&self, transfer_type: TransferType, endpoint: usize) { + match transfer_type { + TransferType::Control => { + panic!("There is no IN control endpoint"); + } + TransferType::Bulk | TransferType::Interrupt => { + if endpoint == 0 || endpoint >= N_ENDPOINTS { + panic!("Bulk/Interrupt endpoints are endpoints 1 to 7"); + } + self.enable_in_endpoint_(transfer_type, endpoint); + } + TransferType::Isochronous => unimplemented!("isochronous endpoint"), + } + } + + fn endpoint_out_enable(&self, transfer_type: TransferType, endpoint: usize) { + match transfer_type { + TransferType::Control => { + if endpoint != 0 { + panic!("Only endpoint 0 can be a control endpoint"); + } + self.enable_out_endpoint_(transfer_type, endpoint); + } + TransferType::Bulk | TransferType::Interrupt => { + if endpoint == 0 || endpoint >= N_ENDPOINTS { + panic!("Bulk/Interrupt endpoints are endpoints 1 to 7"); + } + self.enable_out_endpoint_(transfer_type, endpoint); + } + TransferType::Isochronous => unimplemented!("isochronous endpoint"), + } + } + + fn endpoint_in_out_enable(&self, transfer_type: TransferType, endpoint: usize) { + match transfer_type { + TransferType::Control => { + panic!("There is no IN control endpoint"); + } + TransferType::Bulk | TransferType::Interrupt => { + if endpoint == 0 || endpoint >= N_ENDPOINTS { + panic!("Bulk/Interrupt endpoints are endpoints 1 to 7"); + } + } + TransferType::Isochronous => unimplemented!("isochronous endpoint"), + } + } + + fn endpoint_resume_in(&self, endpoint: usize) { + // Get the state of the endpoint that the upper layer requested to start + // an IN transfer with for our state machine. + let (_, in_state, _) = self.descriptors[endpoint].state.get().bulk_state(); + // If the state is `None`, this endpoint is not configured and should + // not have been used to call `endpoint_resume_in()`. + assert!(in_state.is_some()); + + // If there is an active request, or we are waiting on finishing up + // a previous IN transfer, we queue this request and it will be serviced + // after those complete. + if in_state == Some(BulkInState::Init) { + // If we aren't waiting on anything, trigger the transaction now. + self.transmit_in(endpoint); + } else { + self.descriptors[endpoint].request_transmit_in.set(true); + } + } + + fn endpoint_resume_out(&self, endpoint: usize) { + let (transfer_type, in_state, out_state) = + self.descriptors[endpoint].state.get().bulk_state(); + assert!(out_state.is_some()); + + match out_state.unwrap() { + BulkOutState::OutDelay => { + // The endpoint has now finished processing the last ENDEPOUT. No EPDATA event + // happened in the meantime, so the state is now back to Init. + self.descriptors[endpoint].state.set(EndpointState::Bulk( + transfer_type, + in_state, + Some(BulkOutState::Init), + )); + } + BulkOutState::OutData { size: _ } => { + // Although the client reported a delay before, an EPDATA event has + // happened in the meantime. This pending transaction will now + // continue in transmit_out(). + self.transmit_out(endpoint); + } + BulkOutState::Init => { + internal_err!("Unexpected state: {:?}", out_state); + } + } + } +} diff --git a/chips/rp2040/src/watchdog.rs b/chips/rp2040/src/watchdog.rs index 3dc2b575e2..ab0f9df314 100644 --- a/chips/rp2040/src/watchdog.rs +++ b/chips/rp2040/src/watchdog.rs @@ -1,7 +1,14 @@ -use kernel::utilities::registers::interfaces::ReadWriteable; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::registers::interfaces::{ReadWriteable, Writeable}; use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite}; use kernel::utilities::StaticRef; +use crate::resets; + register_structs! { WatchdogRegisters { @@ -97,20 +104,32 @@ register_bitfields![u32, const WATCHDOG_BASE: StaticRef = unsafe { StaticRef::new(0x40058000 as *const WatchdogRegisters) }; -pub struct Watchdog { +pub struct Watchdog<'a> { registers: StaticRef, + resets: OptionalCell<&'a resets::Resets>, } -impl Watchdog { - pub const fn new() -> Watchdog { +impl<'a> Watchdog<'a> { + pub const fn new() -> Watchdog<'a> { Watchdog { registers: WATCHDOG_BASE, + resets: OptionalCell::empty(), } } + pub fn resolve_dependencies(&self, resets: &'a resets::Resets) { + self.resets.set(resets); + } + pub fn start_tick(&self, cycles_in_mhz: u32) { self.registers .tick .modify(TICK::CYCLES.val(cycles_in_mhz) + TICK::ENABLE::SET); } + + pub fn reboot(&self) { + self.resets + .map(|resets| resets.watchdog_reset_all_except(&[])); + self.registers.ctrl.write(CTRL::TRIGGER::SET); + } } diff --git a/chips/rp2040/src/xosc.rs b/chips/rp2040/src/xosc.rs index 5fdc6b7fbc..b597024675 100644 --- a/chips/rp2040/src/xosc.rs +++ b/chips/rp2040/src/xosc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite}; use kernel::utilities::StaticRef; diff --git a/chips/sam4l/Cargo.toml b/chips/sam4l/Cargo.toml index cba9ec7263..c009f7a9e3 100644 --- a/chips/sam4l/Cargo.toml +++ b/chips/sam4l/Cargo.toml @@ -1,9 +1,16 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "sam4l" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] cortexm4 = { path = "../../arch/cortex-m4" } kernel = { path = "../../kernel" } + +[lints] +workspace = true diff --git a/chips/sam4l/src/acifc.rs b/chips/sam4l/src/acifc.rs index c2d87474a6..f0e2eda72a 100644 --- a/chips/sam4l/src/acifc.rs +++ b/chips/sam4l/src/acifc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the SAM4L ACIFC controller. //! //! See datasheet section "37. Analog Comparator Interface (ACIFC)". @@ -314,7 +318,7 @@ impl<'a> Acifc<'a> { // Make sure enabling was succesful let result = regs.ctrl.is_set(Control::EN); - if result == false { + if !result { debug!("Failed enabling analog comparator, are you sure the clock is enabled?"); } } diff --git a/chips/sam4l/src/adc.rs b/chips/sam4l/src/adc.rs index 535a50301d..74c6092a4f 100644 --- a/chips/sam4l/src/adc.rs +++ b/chips/sam4l/src/adc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the SAM4L ADCIFE. //! //! This is an implementation of the SAM4L analog to digital converter. It is @@ -76,13 +80,8 @@ impl AdcChannel { } } -/// Create a trait of both client types to allow a single client reference to -/// act as both -pub trait EverythingClient: hil::adc::Client + hil::adc::HighSpeedClient {} -impl EverythingClient for C {} - /// ADC driver code for the SAM4L. -pub struct Adc { +pub struct Adc<'a> { registers: StaticRef, // state tracking for the ADC @@ -106,7 +105,8 @@ pub struct Adc { stopped_buffer: TakeCell<'static, [u16]>, // ADC client to send sample complete notifications to - client: OptionalCell<&'static dyn EverythingClient>, + client: OptionalCell<&'a dyn hil::adc::Client>, + highspeed_client: OptionalCell<&'a dyn hil::adc::HighSpeedClient>, pm: &'static pm::PowerManager, } @@ -322,11 +322,11 @@ const BASE_ADDRESS: StaticRef = unsafe { StaticRef::new(0x40038000 as *const AdcRegisters) }; /// Functions for initializing the ADC. -impl Adc { +impl<'a> Adc<'a> { /// Create a new ADC driver. /// /// - `rx_dma_peripheral`: type used for DMA transactions - pub const fn new(rx_dma_peripheral: dma::DMAPeripheral, pm: &'static pm::PowerManager) -> Adc { + pub fn new(rx_dma_peripheral: dma::DMAPeripheral, pm: &'static pm::PowerManager) -> Adc { Adc { // pointer to memory mapped I/O registers registers: BASE_ADDRESS, @@ -345,7 +345,7 @@ impl Adc { // DMA status and stuff rx_dma: OptionalCell::empty(), - rx_dma_peripheral: rx_dma_peripheral, + rx_dma_peripheral, rx_length: Cell::new(0), next_dma_buffer: TakeCell::empty(), next_dma_length: Cell::new(0), @@ -353,17 +353,11 @@ impl Adc { // higher layer to send responses to client: OptionalCell::empty(), + highspeed_client: OptionalCell::empty(), pm, } } - /// Sets the client for this driver. - /// - /// - `client`: reference to capsule which handles responses - pub fn set_client(&self, client: &'static C) { - self.client.set(client); - } - /// Sets the DMA channel for this driver. /// /// - `rx_dma`: reference to the DMA channel the ADC should use @@ -478,22 +472,21 @@ impl Adc { if frequency <= 113600 / 32 { // RC oscillator self.cpu_clock.set(false); - let max_freq: u32; - if frequency <= 32000 / 32 { + let max_freq: u32 = if frequency <= 32000 / 32 { // frequency of the RC32K is 32KHz. scif::generic_clock_enable( scif::GenericClock::GCLK10, scif::ClockSource::RC32K, ); - max_freq = 32000 / 32; + 32000 / 32 } else { // frequency of the RCSYS is 115KHz. scif::generic_clock_enable( scif::GenericClock::GCLK10, scif::ClockSource::RCSYS, ); - max_freq = 113600 / 32; - } + 113600 / 32 + }; let divisor = (frequency + max_freq - 1) / frequency; // ceiling of division let divisor_pow2 = math::closest_power_of_two(divisor); let clock_divisor = cmp::min(math::log_base_two(divisor_pow2), 7); @@ -513,10 +506,7 @@ impl Adc { let cpu_frequency = self.pm.get_system_frequency(); let divisor = (cpu_frequency + (1500000 - 1)) / 1500000; // ceiling of division let divisor_pow2 = math::closest_power_of_two(divisor); - let clock_divisor = cmp::min( - math::log_base_two(divisor_pow2).checked_sub(2).unwrap_or(0), - 7, - ); + let clock_divisor = cmp::min(math::log_base_two(divisor_pow2).saturating_sub(2), 7); self.adc_clk_freq .set(cpu_frequency / (1 << (clock_divisor + 2))); cfg_val += Configuration::PRESCAL.val(clock_divisor); @@ -602,7 +592,7 @@ impl Adc { } /// Implements an ADC capable reading ADC samples on any channel. -impl hil::adc::Adc for Adc { +impl<'a> hil::adc::Adc<'a> for Adc<'a> { type Channel = AdcChannel; /// Capture a single analog sample, calling the client when complete. @@ -626,8 +616,9 @@ impl hil::adc::Adc for Adc { self.timer_repeats.set(0); self.timer_counts.set(0); - let cfg = SequencerConfig::MUXNEG.val(0x7) + // ground pad - SequencerConfig::MUXPOS.val(channel.chan_num) + // MUXNEG.val(0x7) -> ground pad + let cfg = SequencerConfig::MUXNEG.val(0x7) + + SequencerConfig::MUXPOS.val(channel.chan_num) + SequencerConfig::INTERNAL.val(0x2 | channel.internal) + SequencerConfig::RES::Bits12 + SequencerConfig::TRGSEL::Software @@ -676,8 +667,9 @@ impl hil::adc::Adc for Adc { self.continuous.set(true); // adc sequencer configuration - let mut cfg = SequencerConfig::MUXNEG.val(0x7) + // ground pad - SequencerConfig::MUXPOS.val(channel.chan_num) + // MUXNEG.val(0x7) -> ground pad + let mut cfg = SequencerConfig::MUXNEG.val(0x7) + + SequencerConfig::MUXPOS.val(channel.chan_num) + SequencerConfig::INTERNAL.val(0x2 | channel.internal) + SequencerConfig::RES::Bits12 + SequencerConfig::GCOMP::Disable @@ -704,8 +696,7 @@ impl hil::adc::Adc for Adc { // counter in addition and only actually perform a callback every N // timer fires. This is important to enable low-jitter sampling in // the 1-22 Hz range. - let timer_frequency; - if frequency < 23 { + let timer_frequency = if frequency < 23 { // set a number of timer repeats before the callback is // performed. 60 here is an arbitrary number which limits the // actual itimer frequency to between 42 and 60 in the desired @@ -714,13 +705,13 @@ impl hil::adc::Adc for Adc { let counts = 60 / frequency; self.timer_repeats.set(counts as u8); self.timer_counts.set(0); - timer_frequency = frequency * counts; + frequency * counts } else { // we can sample at this frequency directly with the timer self.timer_repeats.set(0); self.timer_counts.set(0); - timer_frequency = frequency; - } + frequency + }; // set timer, limit to bounds // f(timer) = f(adc) / (counter + 1) @@ -815,13 +806,13 @@ impl hil::adc::Adc for Adc { /// Sets the client for this driver. /// /// - `client`: reference to capsule which handles responses - fn set_client(&self, _client: &'static dyn hil::adc::Client) { - unimplemented!(); + fn set_client(&self, client: &'a dyn hil::adc::Client) { + self.client.set(client); } } /// Implements an ADC capable of continuous sampling -impl hil::adc::AdcHighSpeed for Adc { +impl<'a> hil::adc::AdcHighSpeed<'a> for Adc<'a> { /// Capture buffered samples from the ADC continuously at a given /// frequency, calling the client whenever a buffer fills up. The client is /// then expected to either stop sampling or provide an additional buffer @@ -870,8 +861,9 @@ impl hil::adc::AdcHighSpeed for Adc { self.next_dma_length.set(length2); // adc sequencer configuration - let mut cfg = SequencerConfig::MUXNEG.val(0x7) + // ground pad - SequencerConfig::MUXPOS.val(channel.chan_num) + // MUXNEG.val(0x7) -> ground pad + let mut cfg = SequencerConfig::MUXNEG.val(0x7) + + SequencerConfig::MUXPOS.val(channel.chan_num) + SequencerConfig::INTERNAL.val(0x2 | channel.internal) + SequencerConfig::RES::Bits12 + SequencerConfig::GCOMP::Disable @@ -974,10 +966,14 @@ impl hil::adc::AdcHighSpeed for Adc { Ok((self.next_dma_buffer.take(), self.stopped_buffer.take())) } } + + fn set_highspeed_client(&self, client: &'a dyn hil::adc::HighSpeedClient) { + self.highspeed_client.set(client); + } } /// Implements a client of a DMA. -impl dma::DMAClient for Adc { +impl<'a> dma::DMAClient for Adc<'a> { /// Handler for DMA transfer completion. /// /// - `pid`: the DMA peripheral that is complete @@ -1036,7 +1032,7 @@ impl dma::DMAClient for Adc { }); // alert client - self.client.map(|client| { + self.highspeed_client.map(|client| { dma_buffer.map(|dma_buf| { // change buffer back into a [u16] // the buffer was originally a [u16] so this should be okay diff --git a/chips/sam4l/src/aes.rs b/chips/sam4l/src/aes.rs index 207def6f85..e4f2825947 100644 --- a/chips/sam4l/src/aes.rs +++ b/chips/sam4l/src/aes.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the AESA peripheral on the SAM4L. //! //! Authors: @@ -162,7 +166,7 @@ pub struct Aes<'a> { } impl<'a> Aes<'a> { - pub const fn new() -> Aes<'a> { + pub fn new() -> Aes<'a> { Aes { registers: AES_BASE, client: OptionalCell::empty(), @@ -214,7 +218,7 @@ impl<'a> Aes<'a> { } fn set_mode(&self, encrypting: bool, mode: ConfidentialityMode) { - let encrypt = if encrypting { 1 } else { 0 }; + let encrypt = u32::from(encrypting); let dma = 0; self.registers.mode.write( Mode::ENCRYPT.val(encrypt) diff --git a/chips/sam4l/src/ast.rs b/chips/sam4l/src/ast.rs index ef8147c297..5e4dc0424a 100644 --- a/chips/sam4l/src/ast.rs +++ b/chips/sam4l/src/ast.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of a single hardware timer. //! //! - Author: Amit Levy @@ -257,7 +261,7 @@ impl<'a> Ast<'a> { } fn is_enabled(&self) -> bool { - let regs: &AstRegisters = &*self.registers; + let regs: &AstRegisters = &self.registers; while self.busy() {} regs.cr.is_set(Control::EN) } @@ -294,7 +298,7 @@ impl<'a> Ast<'a> { } fn set_counter(&self, val: u32) { - let regs: &AstRegisters = &*self.registers; + let regs: &AstRegisters = &self.registers; while self.busy() {} regs.cv.set(val); } @@ -350,7 +354,7 @@ impl<'a> time::Alarm<'a> for Ast<'a> { if !now.within_range(reference, expire) { // We have already passed when: just fire ASAP // Note this will also trigger the increment below - expire = Self::Ticks::from(now); + expire = now; } // Firing is too close in the future, delay it a bit diff --git a/chips/sam4l/src/bpm.rs b/chips/sam4l/src/bpm.rs index f944a46c33..7473a43e08 100644 --- a/chips/sam4l/src/bpm.rs +++ b/chips/sam4l/src/bpm.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the BPM peripheral. use kernel::utilities::registers::interfaces::{Readable, Writeable}; diff --git a/chips/sam4l/src/bscif.rs b/chips/sam4l/src/bscif.rs index 9eab63f9a7..c4e83845b8 100644 --- a/chips/sam4l/src/bscif.rs +++ b/chips/sam4l/src/bscif.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the Backup System Control Interface (BSCIF) peripheral. use kernel::utilities::registers::interfaces::{Readable, Writeable}; diff --git a/chips/sam4l/src/chip.rs b/chips/sam4l/src/chip.rs index 57470884bb..b650b2a381 100644 --- a/chips/sam4l/src/chip.rs +++ b/chips/sam4l/src/chip.rs @@ -1,21 +1,23 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interrupt mapping and DMA channel setup. -use crate::deferred_call_tasks::Task; use crate::pm; use core::fmt::Write; -use cortexm4; -use kernel::deferred_call; +use cortexm4::{CortexM4, CortexMVariant}; use kernel::platform::chip::{Chip, InterruptService}; -pub struct Sam4l + 'static> { +pub struct Sam4l { mpu: cortexm4::mpu::MPU, userspace_kernel_boundary: cortexm4::syscall::SysCall, pub pm: &'static crate::pm::PowerManager, interrupt_service: &'static I, } -impl + 'static> Sam4l { +impl Sam4l { pub unsafe fn new(pm: &'static crate::pm::PowerManager, interrupt_service: &'static I) -> Self { Self { mpu: cortexm4::mpu::MPU::new(), @@ -32,7 +34,7 @@ impl + 'static> Sam4l { /// constructed manually in main.rs. pub struct Sam4lDefaultPeripherals { pub acifc: crate::acifc::Acifc<'static>, - pub adc: crate::adc::Adc, + pub adc: crate::adc::Adc<'static>, pub aes: crate::aes::Aes<'static>, pub ast: crate::ast::Ast<'static>, pub crccu: crate::crccu::Crccu<'static>, @@ -44,11 +46,11 @@ pub struct Sam4lDefaultPeripherals { pub pa: crate::gpio::Port<'static>, pub pb: crate::gpio::Port<'static>, pub pc: crate::gpio::Port<'static>, - pub i2c0: crate::i2c::I2CHw, - pub i2c1: crate::i2c::I2CHw, - pub i2c2: crate::i2c::I2CHw, - pub i2c3: crate::i2c::I2CHw, - pub spi: crate::spi::SpiHw, + pub i2c0: crate::i2c::I2CHw<'static>, + pub i2c1: crate::i2c::I2CHw<'static>, + pub i2c2: crate::i2c::I2CHw<'static>, + pub i2c3: crate::i2c::I2CHw<'static>, + pub spi: crate::spi::SpiHw<'static>, pub trng: crate::trng::Trng<'static>, pub usart0: crate::usart::USART<'static>, pub usart1: crate::usart::USART<'static>, @@ -109,10 +111,7 @@ impl Sam4lDefaultPeripherals { } } - // Sam4l was the only chip that partially initialized some drivers in new, I - // have moved that initialization to this helper function. - // TODO: Delete explanation - pub fn setup_dma(&'static self) { + pub fn setup_circular_deps(&'static self) { use crate::dma; self.usart0 .set_dma(&self.dma_channels[0], &self.dma_channels[1]); @@ -150,9 +149,17 @@ impl Sam4lDefaultPeripherals { self.adc.set_dma(&self.dma_channels[13]); self.dma_channels[13].initialize(&self.adc, dma::DMAWidth::Width16Bit); + + // REGISTER ALL PERIPHERALS WITH DEFERRED CALLS + kernel::deferred_call::DeferredCallClient::register(&self.crccu); + kernel::deferred_call::DeferredCallClient::register(&self.flash_controller); + kernel::deferred_call::DeferredCallClient::register(&self.usart0); + kernel::deferred_call::DeferredCallClient::register(&self.usart1); + kernel::deferred_call::DeferredCallClient::register(&self.usart2); + kernel::deferred_call::DeferredCallClient::register(&self.usart3); } } -impl InterruptService for Sam4lDefaultPeripherals { +impl InterruptService for Sam4lDefaultPeripherals { unsafe fn service_interrupt(&self, interrupt: u32) -> bool { use crate::nvic; match interrupt { @@ -225,28 +232,16 @@ impl InterruptService for Sam4lDefaultPeripherals { } true } - unsafe fn service_deferred_call(&self, task: Task) -> bool { - match task { - crate::deferred_call_tasks::Task::Flashcalw => self.flash_controller.handle_interrupt(), - crate::deferred_call_tasks::Task::CRCCU => self.crccu.handle_deferred_call(), - } - true - } } -impl + 'static> Chip for Sam4l { +impl Chip for Sam4l { type MPU = cortexm4::mpu::MPU; type UserspaceKernelBoundary = cortexm4::syscall::SysCall; fn service_pending_interrupts(&self) { unsafe { loop { - if let Some(task) = deferred_call::DeferredCall::next_pending() { - match self.interrupt_service.service_deferred_call(task) { - true => {} - false => panic!("unhandled deferred call task"), - } - } else if let Some(interrupt) = cortexm4::nvic::next_pending() { + if let Some(interrupt) = cortexm4::nvic::next_pending() { match self.interrupt_service.service_interrupt(interrupt) { true => {} false => panic!("unhandled interrupt"), @@ -262,7 +257,7 @@ impl + 'static> Chip for Sam4l { } fn has_pending_interrupts(&self) -> bool { - unsafe { cortexm4::nvic::has_pending() || deferred_call::has_tasks() } + unsafe { cortexm4::nvic::has_pending() } } fn mpu(&self) -> &cortexm4::mpu::MPU { @@ -297,6 +292,6 @@ impl + 'static> Chip for Sam4l { } unsafe fn print_state(&self, writer: &mut dyn Write) { - cortexm4::print_cortexm4_state(writer); + CortexM4::print_cortexm_state(writer); } } diff --git a/chips/sam4l/src/crccu.rs b/chips/sam4l/src/crccu.rs index 10d9f9b161..0271de9993 100644 --- a/chips/sam4l/src/crccu.rs +++ b/chips/sam4l/src/crccu.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the SAM4L CRCCU. //! //! See datasheet section "41. Cyclic Redundancy Check Calculation Unit (CRCCU)". @@ -48,13 +52,12 @@ // // - Support continuous-mode CRC -use crate::deferred_call_tasks::Task; use crate::pm::{disable_clock, enable_clock, Clock, HSBClock, PBBClock}; use core::cell::Cell; -use kernel::deferred_call::DeferredCall; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; use kernel::hil::crc::{Client, Crc, CrcAlgorithm, CrcOutput}; use kernel::utilities::cells::OptionalCell; -use kernel::utilities::leasable_buffer::LeasableBuffer; +use kernel::utilities::leasable_buffer::SubSliceMut; use kernel::utilities::registers::interfaces::{Readable, Writeable}; use kernel::utilities::registers::{ register_bitfields, FieldValue, InMemoryRegister, ReadOnly, ReadWrite, WriteOnly, @@ -66,8 +69,6 @@ use kernel::ErrorCode; pub const BASE_ADDRESS: StaticRef = unsafe { StaticRef::new(0x400A4000 as *const CrccuRegisters) }; -static DEFERRED_CALL: DeferredCall = unsafe { DeferredCall::new(Task::CRCCU) }; - #[repr(C)] pub struct CrccuRegisters { // From page 1005 of SAM4L manual @@ -265,18 +266,21 @@ pub struct Crccu<'a> { // Must be aligned to a 512-byte boundary, which is guaranteed by // the struct definition. descriptor: Descriptor, + + deferred_call: DeferredCall, } impl Crccu<'_> { pub fn new(base_addr: StaticRef) -> Self { - Crccu { + Self { registers: base_addr, client: OptionalCell::empty(), state: Cell::new(State::Invalid), algorithm: OptionalCell::empty(), - current_full_buffer: Cell::new((0 as *mut u8, 0)), + current_full_buffer: Cell::new((core::ptr::null_mut::(), 0)), compute_requested: Cell::new(false), descriptor: Descriptor::new(), + deferred_call: DeferredCall::new(), } } @@ -330,7 +334,7 @@ impl Crccu<'_> { // Disable the unit self.registers.mr.write(Mode::ENABLE::Disabled); - // Recover the window into the LeasableBuffer + // Recover the window into the SubSliceMut let window_addr = self.descriptor.addr.get(); let window_len = TCR(self.descriptor.ctrl.get()).get_btsize() as usize; @@ -348,7 +352,7 @@ impl Crccu<'_> { // Reconstruct the leasable buffer from stored // information and slice into the proper window let (full_buffer_addr, full_buffer_len) = self.current_full_buffer.get(); - let mut data = LeasableBuffer::<'static, u8>::new(unsafe { + let mut data = SubSliceMut::<'static, u8>::new(unsafe { core::slice::from_raw_parts_mut(full_buffer_addr, full_buffer_len) }); @@ -364,8 +368,9 @@ impl Crccu<'_> { } } } - - pub fn handle_deferred_call(&self) { +} +impl DeferredCallClient for Crccu<'_> { + fn handle_deferred_call(&self) { // A deferred call is currently only issued on a call to // compute, in which case we need to provide the CRC to the // client @@ -384,6 +389,10 @@ impl Crccu<'_> { client.crc_done(Ok(result)); }); } + + fn register(&'static self) { + self.deferred_call.register(self); + } } // Implement the generic CRC interface with the CRCCU @@ -426,9 +435,9 @@ impl<'a> Crc<'a> for Crccu<'a> { fn input( &self, - mut data: LeasableBuffer<'static, u8>, - ) -> Result<(), (ErrorCode, LeasableBuffer<'static, u8>)> { - let algorithm = if let Some(algorithm) = self.algorithm.extract() { + mut data: SubSliceMut<'static, u8>, + ) -> Result<(), (ErrorCode, SubSliceMut<'static, u8>)> { + let algorithm = if let Some(algorithm) = self.algorithm.get() { algorithm } else { return Err((ErrorCode::RESERVE, data)); @@ -475,13 +484,13 @@ impl<'a> Crc<'a> for Crccu<'a> { // Configure the data transfer descriptor // // The data length is guaranteed to be <= u16::MAX by the - // above LeasableBuffer resizing mechanism + // above SubSliceMut resizing mechanism self.descriptor.addr.set(data.as_ptr() as u32); self.descriptor.ctrl.set(ctrl.0); self.descriptor.crc.set(0); // this is the CRC compare field, not used // Prior to starting the DMA operation, drop the - // LeasableBuffer slice. Otherwise we violate Rust's mutable + // SubSlice slice. Otherwise we violate Rust's mutable // aliasing rules. let full_slice = data.take(); let full_slice_ptr_len = (full_slice.as_mut_ptr(), full_slice.len()); @@ -502,7 +511,7 @@ impl<'a> Crc<'a> for Crccu<'a> { // Set the descriptor memory address accordingly self.registers .dscr - .set(&self.descriptor as *const Descriptor as u32); + .set(core::ptr::addr_of!(self.descriptor) as u32); // Configure the unit to compute a checksum self.registers.mr.write( @@ -533,7 +542,7 @@ impl<'a> Crc<'a> for Crccu<'a> { // Request a deferred call such that we can provide the result // back to the client - DEFERRED_CALL.set(); + self.deferred_call.set(); Ok(()) } diff --git a/chips/sam4l/src/dac.rs b/chips/sam4l/src/dac.rs index 44fcff463b..c0a6372875 100644 --- a/chips/sam4l/src/dac.rs +++ b/chips/sam4l/src/dac.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the SAM4L DACC. //! //! Ensure that the `ADVREFP` pin is tied to `ADDANA`. @@ -130,11 +134,6 @@ impl Dac { } } - // Not currently using interrupt. - pub fn handle_interrupt(&self) {} -} - -impl hil::dac::DacChannel for Dac { fn initialize(&self) -> Result<(), ErrorCode> { if !self.enabled.get() { self.enabled.set(true); @@ -161,20 +160,25 @@ impl hil::dac::DacChannel for Dac { Ok(()) } + // Not currently using interrupt. + pub fn handle_interrupt(&self) {} +} + +impl hil::dac::DacChannel for Dac { fn set_value(&self, value: usize) -> Result<(), ErrorCode> { if !self.enabled.get() { - Err(ErrorCode::OFF) - } else { - // Check if ready to write to CDR - if !self.registers.isr.is_set(InterruptStatus::TXRDY) { - return Err(ErrorCode::BUSY); - } - - // Write to CDR - self.registers - .cdr - .write(ConversionData::DATA.val(value as u32)); - Ok(()) + self.initialize()?; } + + // Check if ready to write to CDR + if !self.registers.isr.is_set(InterruptStatus::TXRDY) { + return Err(ErrorCode::BUSY); + } + + // Write to CDR + self.registers + .cdr + .write(ConversionData::DATA.val(value as u32)); + Ok(()) } } diff --git a/chips/sam4l/src/deferred_call_tasks.rs b/chips/sam4l/src/deferred_call_tasks.rs deleted file mode 100644 index b9bc8e6d69..0000000000 --- a/chips/sam4l/src/deferred_call_tasks.rs +++ /dev/null @@ -1,32 +0,0 @@ -//! Definition of Deferred Call tasks. -//! -//! Deferred calls allow peripheral drivers to register pseudo interrupts. -//! These are the definitions of which deferred calls this chip needs. - -use core::convert::Into; -use core::convert::TryFrom; - -/// A type of task to defer a call for -#[derive(Copy, Clone)] -pub enum Task { - Flashcalw = 0, - CRCCU = 1, -} - -impl TryFrom for Task { - type Error = (); - - fn try_from(value: usize) -> Result { - match value { - 0 => Ok(Task::Flashcalw), - 1 => Ok(Task::CRCCU), - _ => Err(()), - } - } -} - -impl Into for Task { - fn into(self) -> usize { - self as usize - } -} diff --git a/chips/sam4l/src/dma.rs b/chips/sam4l/src/dma.rs index 8a94465867..5a7ca05950 100644 --- a/chips/sam4l/src/dma.rs +++ b/chips/sam4l/src/dma.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the PDCA DMA peripheral. use crate::pm; @@ -170,8 +174,11 @@ pub enum DMAPeripheral { #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] pub enum DMAWidth { + /// DMA is acting on bytes Width8Bit = 0, + /// DMA is acting on halfwords Width16Bit = 1, + /// DMA is acting on words Width32Bit = 2, } @@ -188,7 +195,7 @@ pub trait DMAClient { } impl DMAChannel { - pub const fn new(channel: DMAChannelNum) -> DMAChannel { + pub fn new(channel: DMAChannelNum) -> DMAChannel { DMAChannel { registers: unsafe { StaticRef::new( @@ -263,9 +270,9 @@ impl DMAChannel { let maxlen = buf.len() / match self.width.get() { - DMAWidth::Width8Bit /* DMA is acting on bytes */ => 1, - DMAWidth::Width16Bit /* DMA is acting on halfwords */ => 2, - DMAWidth::Width32Bit /* DMA is acting on words */ => 4, + DMAWidth::Width8Bit => 1, + DMAWidth::Width16Bit => 2, + DMAWidth::Width32Bit => 4, }; len = cmp::min(len, maxlen); self.registers @@ -275,7 +282,7 @@ impl DMAChannel { self.registers.psr.set(pid); self.registers .marr - .write(MemoryAddressReload::MARV.val(&buf[0] as *const u8 as u32)); + .write(MemoryAddressReload::MARV.val(core::ptr::from_ref::(&buf[0]) as u32)); self.registers .tcrr .write(TransferCounter::TCV.val(len as u32)); diff --git a/chips/sam4l/src/eic.rs b/chips/sam4l/src/eic.rs index b2690d4827..98ebf7edb2 100644 --- a/chips/sam4l/src/eic.rs +++ b/chips/sam4l/src/eic.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the SAM4L External Interrupt Controller (EIC). //! //! Datasheet section "21. External Interrupt Controller (EIC)". @@ -64,7 +68,7 @@ pub struct EicRegisters { imr: ReadOnly, /// A bit is set when an interrupt triggers isr: ReadOnly, - /// Clears ISR + /// Clears ISR icr: WriteOnly, /// Sets interrupt mode mode: ReadWrite, @@ -134,7 +138,7 @@ impl PeripheralManagement for Eic<'_> { type RegisterType = EicRegisters; fn get_registers(&self) -> &EicRegisters { - &*EIC_BASE + &EIC_BASE } fn get_clock(&self) -> &pm::Clock { diff --git a/chips/sam4l/src/flashcalw.rs b/chips/sam4l/src/flashcalw.rs index 1b7a862f5a..b51767e364 100644 --- a/chips/sam4l/src/flashcalw.rs +++ b/chips/sam4l/src/flashcalw.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the SAM4L flash controller. //! //! This implementation of the flash controller for the SAM4L uses interrupts to @@ -20,11 +24,10 @@ //! - Author: Kevin Baichoo //! - Date: July 27, 2016 -use crate::deferred_call_tasks::Task; use crate::pm; use core::cell::Cell; use core::ops::{Index, IndexMut}; -use kernel::deferred_call::DeferredCall; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; use kernel::hil; use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; @@ -318,8 +321,6 @@ enum RegKey { GPFRLO, } -static DEFERRED_CALL: DeferredCall = unsafe { DeferredCall::new(Task::Flashcalw) }; - /// There are 18 recognized commands for the flash. These are "bare-bones" /// commands and values that are written to the Flash's command register to /// inform the flash what to do. Table 14-5. @@ -376,9 +377,7 @@ pub struct Sam4lPage(pub [u8; PAGE_SIZE as usize]); impl Default for Sam4lPage { fn default() -> Self { - Self { - 0: [0; PAGE_SIZE as usize], - } + Self([0; PAGE_SIZE as usize]) } } @@ -417,29 +416,16 @@ pub struct FLASHCALW { client: OptionalCell<&'static dyn hil::flash::Client>, current_state: Cell, buffer: TakeCell<'static, Sam4lPage>, + deferred_call: DeferredCall, } // Few constants relating to module configuration. const PAGE_SIZE: u32 = 512; -#[cfg(CONFIG_FLASH_READ_MODE_HIGH_SPEED_DISABLE)] -const FREQ_PS1_FWS_1_FWU_MAX_FREQ: u32 = 12000000; -#[cfg(CONFIG_FLASH_READ_MODE_HIGH_SPEED_DISABLE)] -const FREQ_PS0_FWS_0_MAX_FREQ: u32 = 18000000; -#[cfg(CONFIG_FLASH_READ_MODE_HIGH_SPEED_DISABLE)] -const FREQ_PS0_FWS_1_MAX_FREQ: u32 = 36000000; -#[cfg(CONFIG_FLASH_READ_MODE_HIGH_SPEED_DISABLE)] -const FREQ_PS1_FWS_0_MAX_FREQ: u32 = 8000000; - -#[cfg(not(CONFIG_FLASH_READ_MODE_HIGH_SPEED_DISABLE))] const FREQ_PS2_FWS_0_MAX_FREQ: u32 = 24000000; impl FLASHCALW { - pub const fn new( - ahb_clk: pm::HSBClock, - hramc1_clk: pm::HSBClock, - pb_clk: pm::PBBClock, - ) -> FLASHCALW { + pub fn new(ahb_clk: pm::HSBClock, hramc1_clk: pm::HSBClock, pb_clk: pm::PBBClock) -> FLASHCALW { FLASHCALW { registers: FLASHCALW_ADDRESS, ahb_clock: pm::Clock::HSB(ahb_clk), @@ -448,6 +434,7 @@ impl FLASHCALW { client: OptionalCell::empty(), current_state: Cell::new(FlashState::Unconfigured), buffer: TakeCell::empty(), + deferred_call: DeferredCall::new(), } } @@ -500,18 +487,18 @@ impl FLASHCALW { self.client.map(|client| match attempted_operation { FlashState::Read => { self.buffer.take().map(|buffer| { - client.read_complete(buffer, hil::flash::Error::FlashError); + client.read_complete(buffer, Err(hil::flash::Error::FlashError)); }); } FlashState::WriteUnlocking { .. } | FlashState::WriteErasing { .. } | FlashState::WriteWriting => { self.buffer.take().map(|buffer| { - client.write_complete(buffer, hil::flash::Error::FlashError); + client.write_complete(buffer, Err(hil::flash::Error::FlashError)); }); } FlashState::EraseUnlocking { .. } | FlashState::EraseErasing => { - client.erase_complete(hil::flash::Error::FlashError); + client.erase_complete(Err(hil::flash::Error::FlashError)); } _ => {} }); @@ -524,13 +511,12 @@ impl FLASHCALW { self.client.map(|client| { self.buffer.take().map(|buffer| { - client.read_complete(buffer, hil::flash::Error::CommandComplete); + client.read_complete(buffer, Ok(())); }); }); } FlashState::WriteUnlocking { page } => { - self.current_state - .set(FlashState::WriteErasing { page: page }); + self.current_state.set(FlashState::WriteErasing { page }); self.flashcalw_erase_page(page); } FlashState::WriteErasing { page } => { @@ -552,7 +538,7 @@ impl FLASHCALW { self.client.map(|client| { self.buffer.take().map(|buffer| { - client.write_complete(buffer, hil::flash::Error::CommandComplete); + client.write_complete(buffer, Ok(())); }); }); } @@ -567,7 +553,7 @@ impl FLASHCALW { self.current_state.set(FlashState::Ready); self.client.map(|client| { - client.erase_complete(hil::flash::Error::CommandComplete); + client.erase_complete(Ok(())); }); } _ => { @@ -600,7 +586,6 @@ impl FLASHCALW { // By default, we are going with High Speed Enable (based on our device running // in PS2). - #[cfg(not(CONFIG_FLASH_READ_MODE_HIGH_SPEED_DISABLE))] fn set_flash_waitstate_and_readmode(&self, cpu_freq: u32, _ps_val: u32, _is_fwu_enabled: bool) { // ps_val and is_fwu_enabled not used in this implementation. if cpu_freq > FREQ_PS2_FWS_0_MAX_FREQ { @@ -612,41 +597,6 @@ impl FLASHCALW { self.issue_command(FlashCMD::HSEN, -1); } - #[cfg(CONFIG_FLASH_READ_MODE_HIGH_SPEED_DISABLE)] - fn set_flash_waitstate_and_readmode( - &mut self, - cpu_freq: u32, - ps_val: u32, - is_fwu_enabled: bool, - ) { - if ps_val == 0 { - if cpu_freq > FREQ_PS0_FWS_0_MAX_FREQ { - self.set_wait_state(1); - if cpu_freq <= FREQ_PS0_FWS_1_MAX_FREQ { - self.issue_command(FlashCMD::HSDIS, -1); - } else { - self.issue_command(FlashCMD::HSEN, -1); - } - } else { - if is_fwu_enabled && cpu_freq <= FREQ_PS1_FWS_1_FWU_MAX_FREQ { - self.set_wait_state(1); - self.issue_command(FlashCMD::HSDIS, -1); - } else { - self.set_wait_state(0); - self.issue_command(FlashCMD::HSDIS, -1); - } - } - } else { - // ps_val == 1 - if cpu_freq > FREQ_PS1_FWS_0_MAX_FREQ { - self.set_wait_state(1); - } else { - self.set_wait_state(0); - } - self.issue_command(FlashCMD::HSDIS, -1); - } - } - /// Configure high-speed flash mode. This is taken from the ASF code pub fn enable_high_speed_flash(&self) { // Since we are running at a fast speed we have to set a clock delay @@ -833,12 +783,10 @@ impl FLASHCALW { pm::enable_clock(self.ahb_clock); // Check that address makes sense and buffer has room. - if address > (self.get_flash_size() as usize) - || address + size > (self.get_flash_size() as usize) - || address + size < size - || buffer.len() < size - { - // invalid flash address + let Some(end_address) = address.checked_add(size) else { + return Err((ErrorCode::INVAL, buffer)); + }; + if end_address > (self.get_flash_size() as usize) || buffer.len() < size { return Err((ErrorCode::INVAL, buffer)); } @@ -858,7 +806,7 @@ impl FLASHCALW { // This is kind of strange, but because read() in this case is // synchronous, we still need to schedule as if we had an interrupt so // we can allow this function to return and then call the callback. - DEFERRED_CALL.set(); + self.deferred_call.set(); Ok(()) } @@ -905,6 +853,16 @@ impl FLASHCALW { } } +impl DeferredCallClient for FLASHCALW { + fn handle_deferred_call(&self) { + self.handle_interrupt(); + } + + fn register(&'static self) { + self.deferred_call.register(self); + } +} + impl> hil::flash::HasClient<'static, C> for FLASHCALW { fn set_client(&self, client: &'static C) { self.client.set(client); diff --git a/chips/sam4l/src/gloc.rs b/chips/sam4l/src/gloc.rs index e54077ce71..e2fda93bd1 100644 --- a/chips/sam4l/src/gloc.rs +++ b/chips/sam4l/src/gloc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the SAM4L glue logic controller (GLOC). //! //! GLOC input and output pins must be selected appropriately from table 3-1 in @@ -85,7 +89,7 @@ impl Gloc { /// Gets the memory-mapped registers associated with a LUT. fn lut_registers(&self, lut: Lut) -> &GlocRegisters { - &*self.lut_regs[lut as usize] + &self.lut_regs[lut as usize] } /// Set the truth table values. diff --git a/chips/sam4l/src/gpio.rs b/chips/sam4l/src/gpio.rs index 7f9b1ea722..f24123e000 100644 --- a/chips/sam4l/src/gpio.rs +++ b/chips/sam4l/src/gpio.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the GPIO controller for the SAM4L. use core::ops::{Index, IndexMut}; @@ -274,7 +278,7 @@ impl<'a> Port<'a> { } pub fn handle_interrupt(&self) { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; // Interrupt Flag Register (IFR) bits are only valid if the same bits // are enabled in Interrupt Enabled Register (IER). @@ -318,7 +322,7 @@ impl<'a> GPIOPin<'a> { pub fn select_peripheral(&self, function: PeripheralFunction) { let f = function as u32; let (bit0, bit1, bit2) = (f & 0b1, (f & 0b10) >> 1, (f & 0b100) >> 2); - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; // clear GPIO enable for pin port.gper.clear.set(self.pin_mask); @@ -342,47 +346,47 @@ impl<'a> GPIOPin<'a> { } pub fn enable(&self) { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; port.gper.set.set(self.pin_mask); } pub fn disable(&self) { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; port.gper.clear.set(self.pin_mask); } pub fn is_pending(&self) -> bool { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; (port.ifr.val.get() & self.pin_mask) != 0 } pub fn enable_output(&self) { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; port.oder.set.set(self.pin_mask); } pub fn disable_output(&self) { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; port.oder.clear.set(self.pin_mask); } pub fn enable_pull_down(&self) { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; port.pder.set.set(self.pin_mask); } pub fn disable_pull_down(&self) { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; port.pder.clear.set(self.pin_mask); } pub fn enable_pull_up(&self) { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; port.puer.set.set(self.pin_mask); } pub fn disable_pull_up(&self) { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; port.puer.clear.set(self.pin_mask); } @@ -399,7 +403,7 @@ impl<'a> GPIOPin<'a> { /// | 0b10 | Falling edge | /// pub fn set_interrupt_mode(&self, mode: u8) { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; if mode & 0b01 != 0 { port.imr0.set.set(self.pin_mask); } else { @@ -414,7 +418,7 @@ impl<'a> GPIOPin<'a> { } pub fn enable_interrupt(&self) { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; if port.ier.val.get() & self.pin_mask == 0 { INTERRUPT_COUNT.fetch_add(1, Ordering::Relaxed); port.ier.set.set(self.pin_mask); @@ -422,7 +426,7 @@ impl<'a> GPIOPin<'a> { } pub fn disable_interrupt(&self) { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; if port.ier.val.get() & self.pin_mask != 0 { INTERRUPT_COUNT.fetch_sub(1, Ordering::Relaxed); port.ier.clear.set(self.pin_mask); @@ -436,33 +440,33 @@ impl<'a> GPIOPin<'a> { } pub fn disable_schmidtt_trigger(&self) { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; port.ster.clear.set(self.pin_mask); } pub fn enable_schmidtt_trigger(&self) { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; port.ster.set.set(self.pin_mask); } pub fn read(&self) -> bool { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; (port.pvr.get() & self.pin_mask) > 0 } pub fn toggle(&self) -> bool { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; port.ovr.toggle.set(self.pin_mask); self.read() } pub fn set(&self) { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; port.ovr.set.set(self.pin_mask); } pub fn clear(&self) { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; port.ovr.clear.set(self.pin_mask); } } @@ -515,7 +519,7 @@ impl<'a> gpio::Configure for GPIOPin<'a> { } fn disable_output(&self) -> gpio::Configuration { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; port.oder.clear.set(self.pin_mask); self.configuration() } @@ -525,17 +529,17 @@ impl<'a> gpio::Configure for GPIOPin<'a> { } fn is_input(&self) -> bool { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; port.gper.val.get() & self.pin_mask != 0 } fn is_output(&self) -> bool { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; port.oder.val.get() & self.pin_mask != 0 } fn floating_state(&self) -> gpio::FloatingState { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; let down = (port.pder.val.get() & self.pin_mask) != 0; let up = (port.puer.val.get() & self.pin_mask) != 0; if down { @@ -548,7 +552,7 @@ impl<'a> gpio::Configure for GPIOPin<'a> { } fn configuration(&self) -> gpio::Configuration { - let port: &GpioRegisters = &*self.port; + let port: &GpioRegisters = &self.port; let input = self.is_input(); let output = self.is_output(); let gpio = (port.gper.val.get() & self.pin_mask) == 1; diff --git a/chips/sam4l/src/i2c.rs b/chips/sam4l/src/i2c.rs index 9b3b27fc6c..89b5daadc0 100644 --- a/chips/sam4l/src/i2c.rs +++ b/chips/sam4l/src/i2c.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the SAM4L TWIMS peripheral. //! //! The implementation, especially of repeated starts, is quite sensitive to the @@ -544,33 +548,33 @@ impl ClockInterface for TWISClock { } /// Abstraction of the I2C hardware -pub struct I2CHw { +pub struct I2CHw<'a> { master_mmio_address: StaticRef, slave_mmio_address: Option>, master_clock: TWIMClock, slave_clock: TWISClock, dma: OptionalCell<&'static DMAChannel>, dma_pids: (DMAPeripheral, DMAPeripheral), - master_client: Cell>, - slave_client: Cell>, + master_client: Cell>, + slave_client: Cell>, on_deck: Cell>, slave_enabled: Cell, my_slave_address: Cell, slave_read_buffer: TakeCell<'static, [u8]>, - slave_read_buffer_len: Cell, - slave_read_buffer_index: Cell, + slave_read_buffer_len: Cell, + slave_read_buffer_index: Cell, slave_write_buffer: TakeCell<'static, [u8]>, - slave_write_buffer_len: Cell, - slave_write_buffer_index: Cell, + slave_write_buffer_len: Cell, + slave_write_buffer_index: Cell, pm: &'static pm::PowerManager, } -impl PeripheralManagement for I2CHw { +impl<'a> PeripheralManagement for I2CHw<'a> { type RegisterType = TWIMRegisters; fn get_registers(&self) -> &TWIMRegisters { - &*self.master_mmio_address + &self.master_mmio_address } fn get_clock(&self) -> &TWIMClock { @@ -578,7 +582,7 @@ impl PeripheralManagement for I2CHw { } fn before_peripheral_access(&self, clock: &TWIMClock, _: &TWIMRegisters) { - if clock.is_enabled() == false { + if !clock.is_enabled() { clock.enable(); } } @@ -591,13 +595,13 @@ impl PeripheralManagement for I2CHw { } } } -type TWIMRegisterManager<'a> = PeripheralManager<'a, I2CHw, TWIMClock>; +type TWIMRegisterManager<'a, 'm> = PeripheralManager<'m, I2CHw<'a>, TWIMClock>; -impl PeripheralManagement for I2CHw { +impl<'a> PeripheralManagement for I2CHw<'a> { type RegisterType = TWISRegisters; - fn get_registers<'a>(&'a self) -> &'a TWISRegisters { - &*self.slave_mmio_address.as_ref().unwrap() // Unwrap fail = Access of non-existent slave + fn get_registers(&self) -> &TWISRegisters { + self.slave_mmio_address.as_ref().unwrap() // Unwrap fail = Access of non-existent slave } fn get_clock(&self) -> &TWISClock { @@ -605,7 +609,7 @@ impl PeripheralManagement for I2CHw { } fn before_peripheral_access(&self, clock: &TWISClock, _: &TWISRegisters) { - if clock.is_enabled() == false { + if !clock.is_enabled() { clock.enable(); } } @@ -618,7 +622,7 @@ impl PeripheralManagement for I2CHw { } } } -type TWISRegisterManager<'a> = PeripheralManager<'a, I2CHw, TWISClock>; +type TWISRegisterManager<'a, 'm> = PeripheralManager<'m, I2CHw<'a>, TWISClock>; const fn create_twims_clocks( master: pm::Clock, @@ -629,15 +633,15 @@ const fn create_twims_clocks( // Need to implement the `new` function on the I2C device as a constructor. // This gets called from the device tree. -impl I2CHw { - const fn new( +impl<'a> I2CHw<'a> { + fn new( base_addr: StaticRef, slave_base_addr: Option>, clocks: (TWIMClock, TWISClock), dma_rx: DMAPeripheral, dma_tx: DMAPeripheral, pm: &'static pm::PowerManager, - ) -> I2CHw { + ) -> I2CHw<'a> { I2CHw { master_mmio_address: base_addr, slave_mmio_address: slave_base_addr, @@ -661,7 +665,7 @@ impl I2CHw { } } - pub const fn new_i2c0(pm: &'static pm::PowerManager) -> Self { + pub fn new_i2c0(pm: &'static pm::PowerManager) -> Self { I2CHw::new( I2C_BASE_ADDRS[0], Some(I2C_SLAVE_BASE_ADDRS[0]), @@ -675,7 +679,7 @@ impl I2CHw { ) } - pub const fn new_i2c1(pm: &'static pm::PowerManager) -> Self { + pub fn new_i2c1(pm: &'static pm::PowerManager) -> Self { I2CHw::new( I2C_BASE_ADDRS[1], Some(I2C_SLAVE_BASE_ADDRS[1]), @@ -689,7 +693,7 @@ impl I2CHw { ) } - pub const fn new_i2c2(pm: &'static pm::PowerManager) -> Self { + pub fn new_i2c2(pm: &'static pm::PowerManager) -> Self { I2CHw::new( I2C_BASE_ADDRS[2], None, @@ -700,7 +704,7 @@ impl I2CHw { ) } - pub const fn new_i2c3(pm: &'static pm::PowerManager) -> Self { + pub fn new_i2c3(pm: &'static pm::PowerManager) -> Self { I2CHw::new( I2C_BASE_ADDRS[3], None, @@ -749,7 +753,7 @@ impl I2CHw { pub fn handle_interrupt(&self) { let old_status = { - let twim = &TWIMRegisterManager::new(&self); + let twim = &TWIMRegisterManager::new(self); let old_status = twim.registers.sr.extract(); @@ -785,7 +789,7 @@ impl I2CHw { match on_deck { None => { { - let twim = &TWIMRegisterManager::new(&self); + let twim = &TWIMRegisterManager::new(self); twim.registers.cmdr.set(0); twim.registers.ncmdr.set(0); @@ -822,7 +826,7 @@ impl I2CHw { // and call this I2C command complete. if (len == 1) && old_status.is_set(Status::TXRDY) { let the_byte = { - let twim = &TWIMRegisterManager::new(&self); + let twim = &TWIMRegisterManager::new(self); twim.registers.cmdr.set(0); twim.registers.ncmdr.set(0); @@ -854,7 +858,7 @@ impl I2CHw { }); } else { { - let twim = &TWIMRegisterManager::new(&self); + let twim = &TWIMRegisterManager::new(self); // Enable transaction error interrupts twim.registers.ier.write( Interrupt::CCOMP::SET @@ -879,7 +883,7 @@ impl I2CHw { chip: u8, flags: FieldValue, direction: FieldValue, - len: u8, + len: usize, ) { // disable before configuring twim.registers.cr.write(Control::MDIS::SET); @@ -909,7 +913,7 @@ impl I2CHw { chip: u8, flags: FieldValue, direction: FieldValue, - len: u8, + len: usize, ) { // disable before configuring twim.registers.cr.write(Control::MDIS::SET); @@ -936,13 +940,13 @@ impl I2CHw { chip: u8, flags: FieldValue, data: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { - let twim = &TWIMRegisterManager::new(&self); + let twim = &TWIMRegisterManager::new(self); if self.dma.is_some() { self.dma.map(move |dma| { dma.enable(); - dma.prepare_transfer(self.dma_pids.1, data, len as usize); + dma.prepare_transfer(self.dma_pids.1, data, len); self.setup_transfer(twim, chip, flags, Command::READ::Transmit, len); self.master_enable(twim); dma.start_transfer(); @@ -958,13 +962,13 @@ impl I2CHw { chip: u8, flags: FieldValue, data: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { - let twim = &TWIMRegisterManager::new(&self); + let twim = &TWIMRegisterManager::new(self); if self.dma.is_some() { self.dma.map(move |dma| { dma.enable(); - dma.prepare_transfer(self.dma_pids.0, data, len as usize); + dma.prepare_transfer(self.dma_pids.0, data, len); self.setup_transfer(twim, chip, flags, Command::READ::Receive, len); self.master_enable(twim); dma.start_transfer(); @@ -979,14 +983,14 @@ impl I2CHw { &self, chip: u8, data: &'static mut [u8], - split: u8, - read_len: u8, + split: usize, + read_len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { - let twim = &TWIMRegisterManager::new(&self); + let twim = &TWIMRegisterManager::new(self); if self.dma.is_some() { self.dma.map(move |dma| { dma.enable(); - dma.prepare_transfer(self.dma_pids.1, data, split as usize); + dma.prepare_transfer(self.dma_pids.1, data, split); self.setup_transfer( twim, chip, @@ -1001,7 +1005,7 @@ impl I2CHw { Command::READ::Receive, read_len, ); - self.on_deck.set(Some((self.dma_pids.0, read_len as usize))); + self.on_deck.set(Some((self.dma_pids.0, read_len))); dma.start_transfer(); }); Ok(()) @@ -1017,7 +1021,7 @@ impl I2CHw { /// Handle possible interrupt for TWIS module. pub fn handle_slave_interrupt(&self) { if self.slave_mmio_address.is_some() { - let twis = &TWISRegisterManager::new(&self); + let twis = &TWISRegisterManager::new(self); // Get current status from the hardware. let status = twis.registers.sr.extract(); @@ -1027,7 +1031,7 @@ impl I2CHw { let interrupts = status.bitand(imr.get()); // Check for errors. - if interrupts.matches_any( + if interrupts.any_matching_bits_set( StatusSlave::BUSERR::SET + StatusSlave::SMBPECERR::SET + StatusSlave::SMBTOUT::SET @@ -1135,7 +1139,7 @@ impl I2CHw { self.slave_read_buffer.take().map(|buffer| { client.command_complete( buffer, - nbytes as u8, + nbytes as usize, hil::i2c::SlaveTransmissionType::Read, ); }); @@ -1148,8 +1152,7 @@ impl I2CHw { if len > idx { self.slave_write_buffer.map(|buffer| { - buffer[idx as usize] = - twis.registers.rhr.read(ReceiveHolding::RXDATA) as u8; + buffer[idx] = twis.registers.rhr.read(ReceiveHolding::RXDATA) as u8; }); self.slave_write_buffer_index.set(idx + 1); } else { @@ -1161,7 +1164,7 @@ impl I2CHw { self.slave_write_buffer.take().map(|buffer| { client.command_complete( buffer, - nbytes as u8, + nbytes as usize, hil::i2c::SlaveTransmissionType::Write, ); }); @@ -1178,9 +1181,9 @@ impl I2CHw { if len > idx { self.slave_read_buffer.map(|buffer| { - twis.registers.thr.write( - TransmitHolding::TXDATA.val(buffer[idx as usize] as u32), - ); + twis.registers + .thr + .write(TransmitHolding::TXDATA.val(buffer[idx] as u32)); }); self.slave_read_buffer_index.set(idx + 1); } else { @@ -1214,7 +1217,7 @@ impl I2CHw { if len > idx { self.slave_write_buffer.map(|buffer| { - buffer[idx as usize] = + buffer[idx] = twis.registers.rhr.read(ReceiveHolding::RXDATA) as u8; }); self.slave_write_buffer_index.set(idx + 1); @@ -1241,13 +1244,13 @@ impl I2CHw { fn slave_write_receive( &self, buffer: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { if self.slave_enabled.get() { if self.slave_mmio_address.is_some() { self.slave_write_buffer.replace(buffer); self.slave_write_buffer_len.set(len); - let twis = &TWISRegisterManager::new(&self); + let twis = &TWISRegisterManager::new(self); let status = twis.registers.sr.extract(); let imr = twis.registers.imr.extract(); @@ -1271,14 +1274,14 @@ impl I2CHw { fn slave_read_send( &self, buffer: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { if self.slave_enabled.get() { if self.slave_mmio_address.is_some() { self.slave_read_buffer.replace(buffer); self.slave_read_buffer_len.set(len); self.slave_read_buffer_index.set(0); - let twis = &TWISRegisterManager::new(&self); + let twis = &TWISRegisterManager::new(self); // Check to see if we should send the first byte. let status = twis.registers.sr.extract(); @@ -1327,7 +1330,7 @@ impl I2CHw { fn slave_listen(&self) { if self.slave_mmio_address.is_some() { - let twis = &TWISRegisterManager::new(&self); + let twis = &TWISRegisterManager::new(self); // Enable and configure let control = ControlSlave::ADR.val((self.my_slave_address.get() as u32) & 0x7F) @@ -1343,12 +1346,12 @@ impl I2CHw { } } -impl DMAClient for I2CHw { +impl<'a> DMAClient for I2CHw<'a> { fn transfer_done(&self, _pid: DMAPeripheral) {} } -impl hil::i2c::I2CMaster for I2CHw { - fn set_master_client(&self, client: &'static dyn hil::i2c::I2CHwMasterClient) { +impl<'a> hil::i2c::I2CMaster<'a> for I2CHw<'a> { + fn set_master_client(&self, client: &'a dyn hil::i2c::I2CHwMasterClient) { self.master_client.set(Some(client)); } /// This enables the entire I2C peripheral @@ -1356,7 +1359,7 @@ impl hil::i2c::I2CMaster for I2CHw { //disable the i2c slave peripheral hil::i2c::I2CSlave::disable(self); - let twim = &TWIMRegisterManager::new(&self); + let twim = &TWIMRegisterManager::new(self); // enable, reset, disable twim.registers.cr.write(Control::MEN::SET); @@ -1379,7 +1382,7 @@ impl hil::i2c::I2CMaster for I2CHw { /// This disables the entire I2C peripheral fn disable(&self) { - let twim = &TWIMRegisterManager::new(&self); + let twim = &TWIMRegisterManager::new(self); twim.registers.cr.write(Control::MDIS::SET); self.disable_interrupts(twim); } @@ -1388,7 +1391,7 @@ impl hil::i2c::I2CMaster for I2CHw { &self, addr: u8, data: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { I2CHw::write( self, @@ -1403,7 +1406,7 @@ impl hil::i2c::I2CMaster for I2CHw { &self, addr: u8, data: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { I2CHw::read( self, @@ -1418,20 +1421,20 @@ impl hil::i2c::I2CMaster for I2CHw { &self, addr: u8, data: &'static mut [u8], - write_len: u8, - read_len: u8, + write_len: usize, + read_len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { I2CHw::write_read(self, addr, data, write_len, read_len) } } -impl hil::i2c::I2CSlave for I2CHw { - fn set_slave_client(&self, client: &'static dyn hil::i2c::I2CHwSlaveClient) { +impl<'a> hil::i2c::I2CSlave<'a> for I2CHw<'a> { + fn set_slave_client(&self, client: &'a dyn hil::i2c::I2CHwSlaveClient) { self.slave_client.set(Some(client)); } fn enable(&self) { if self.slave_mmio_address.is_some() { - let twis = &TWISRegisterManager::new(&self); + let twis = &TWISRegisterManager::new(self); // enable, reset, disable twis.registers.cr.write(ControlSlave::SEN::SET); @@ -1469,7 +1472,7 @@ impl hil::i2c::I2CSlave for I2CHw { self.slave_enabled.set(false); if self.slave_mmio_address.is_some() { - let twis = &TWISRegisterManager::new(&self); + let twis = &TWISRegisterManager::new(self); twis.registers.cr.set(0); self.slave_disable_interrupts(twis); } @@ -1483,7 +1486,7 @@ impl hil::i2c::I2CSlave for I2CHw { fn write_receive( &self, data: &'static mut [u8], - max_len: u8, + max_len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { self.slave_write_receive(data, max_len) } @@ -1491,7 +1494,7 @@ impl hil::i2c::I2CSlave for I2CHw { fn read_send( &self, data: &'static mut [u8], - max_len: u8, + max_len: usize, ) -> Result<(), (hil::i2c::Error, &'static mut [u8])> { self.slave_read_send(data, max_len) } diff --git a/chips/sam4l/src/lib.rs b/chips/sam4l/src/lib.rs index 232a3bc101..f6c33c5196 100644 --- a/chips/sam4l/src/lib.rs +++ b/chips/sam4l/src/lib.rs @@ -1,14 +1,15 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Peripheral implementations for the SAM4L MCU. //! //! #![crate_name = "sam4l"] #![crate_type = "rlib"] -#![feature(const_fn_trait_bound)] #![no_std] -pub mod deferred_call_tasks; - pub mod acifc; pub mod adc; pub mod aes; @@ -34,10 +35,7 @@ pub mod usart; pub mod usbc; pub mod wdt; -use cortexm4::{ - generic_isr, hard_fault_handler, initialize_ram_jump_to_main, svc_handler, systick_handler, - unhandled_interrupt, -}; +use cortexm4::{initialize_ram_jump_to_main, unhandled_interrupt, CortexM4, CortexMVariant}; extern "C" { // _estack is not really a function, but it makes the types work @@ -54,20 +52,20 @@ extern "C" { pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ _estack, initialize_ram_jump_to_main, - unhandled_interrupt, // NMI - hard_fault_handler, // Hard Fault - unhandled_interrupt, // MemManage - unhandled_interrupt, // BusFault - unhandled_interrupt, // UsageFault + unhandled_interrupt, // NMI + CortexM4::HARD_FAULT_HANDLER, // Hard Fault + unhandled_interrupt, // MemManage + unhandled_interrupt, // BusFault + unhandled_interrupt, // UsageFault unhandled_interrupt, unhandled_interrupt, unhandled_interrupt, unhandled_interrupt, - svc_handler, // SVC - unhandled_interrupt, // DebugMon + CortexM4::SVC_HANDLER, // SVC + unhandled_interrupt, // DebugMon unhandled_interrupt, - unhandled_interrupt, // PendSV - systick_handler, // SysTick + unhandled_interrupt, // PendSV + CortexM4::SYSTICK_HANDLER, // SysTick ]; #[cfg_attr( @@ -76,7 +74,7 @@ pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ )] // used Ensures that the symbol is kept until the final binary #[cfg_attr(all(target_arch = "arm", target_os = "none"), used)] -pub static IRQS: [unsafe extern "C" fn(); 80] = [generic_isr; 80]; +pub static IRQS: [unsafe extern "C" fn(); 80] = [CortexM4::GENERIC_ISR; 80]; pub unsafe fn init() { cortexm4::nvic::disable_all(); diff --git a/chips/sam4l/src/nvic.rs b/chips/sam4l/src/nvic.rs index 80a8c4079e..cdfb24f310 100644 --- a/chips/sam4l/src/nvic.rs +++ b/chips/sam4l/src/nvic.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Named constants for NVIC ids pub const HFLASHC: u32 = 0; diff --git a/chips/sam4l/src/pm.rs b/chips/sam4l/src/pm.rs index 82136addf8..4cce785f2c 100644 --- a/chips/sam4l/src/pm.rs +++ b/chips/sam4l/src/pm.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the power manager (PM) peripheral. use crate::bpm; @@ -572,7 +576,7 @@ pub struct PowerManager { impl PowerManager { pub const fn new() -> Self { Self { - /// Set to the RCSYS by default. + // Set to the RCSYS by default. system_clock_source: Cell::new(SystemClockSource::RcsysAt115kHz), system_on_clocks: Cell::new(ClockMask::RCSYS as u32), @@ -655,13 +659,13 @@ impl PowerManager { // If the 80MHz RC is used as the main clock source, it must be divided by // at least 2 before being used as CPU's clock source - let cpusel = (*PM_REGS).cpusel.extract(); + let cpusel = PM_REGS.cpusel.extract(); unlock(0x00000004); - (*PM_REGS).cpusel.modify_no_read( + PM_REGS.cpusel.modify_no_read( cpusel, CpuClockSelect::CPUDIV::SET + CpuClockSelect::CPUSEL::CLEAR, ); - while (*PM_REGS).sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} + while PM_REGS.sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} // Set Flash wait state to 1 for > 24MHz in PS2 flash_controller.set_wait_state(1); @@ -742,50 +746,50 @@ impl PowerManager { scif::disable_rc_80mhz(); // Stop dividing the main clock - let cpusel = (*PM_REGS).cpusel.extract(); + let cpusel = PM_REGS.cpusel.extract(); unlock(0x00000004); - (*PM_REGS).cpusel.modify_no_read( + PM_REGS.cpusel.modify_no_read( cpusel, CpuClockSelect::CPUDIV::CLEAR + CpuClockSelect::CPUSEL::CLEAR, ); - while (*PM_REGS).sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} + while PM_REGS.sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} // Stop dividing peripheral clocks - let pbasel = (*PM_REGS).pbasel.extract(); + let pbasel = PM_REGS.pbasel.extract(); unlock(0x0000000C); - (*PM_REGS).pbasel.modify_no_read( + PM_REGS.pbasel.modify_no_read( pbasel, PeripheralBusXClockSelect::PBDIV::CLEAR + PeripheralBusXClockSelect::PBSEL::CLEAR, ); - while (*PM_REGS).sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} + while PM_REGS.sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} - let pbbsel = (*PM_REGS).pbbsel.extract(); + let pbbsel = PM_REGS.pbbsel.extract(); unlock(0x00000010); - (*PM_REGS).pbbsel.modify_no_read( + PM_REGS.pbbsel.modify_no_read( pbbsel, PeripheralBusXClockSelect::PBDIV::CLEAR + PeripheralBusXClockSelect::PBSEL::CLEAR, ); - while (*PM_REGS).sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} + while PM_REGS.sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} - let pbcsel = (*PM_REGS).pbcsel.extract(); + let pbcsel = PM_REGS.pbcsel.extract(); unlock(0x00000014); - (*PM_REGS).pbcsel.modify_no_read( + PM_REGS.pbcsel.modify_no_read( pbcsel, PeripheralBusXClockSelect::PBDIV::CLEAR + PeripheralBusXClockSelect::PBSEL::CLEAR, ); - while (*PM_REGS).sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} + while PM_REGS.sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} - let pbdsel = (*PM_REGS).pbdsel.extract(); + let pbdsel = PM_REGS.pbdsel.extract(); unlock(0x00000018); - (*PM_REGS).pbdsel.modify_no_read( + PM_REGS.pbdsel.modify_no_read( pbdsel, PeripheralBusXClockSelect::PBDIV::CLEAR + PeripheralBusXClockSelect::PBSEL::CLEAR, ); - while (*PM_REGS).sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} + while PM_REGS.sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} let clock_mask = self.system_on_clocks.get(); self.system_on_clocks @@ -906,37 +910,37 @@ impl PowerManager { scif::setup_rc_80mhz(); // Divide peripheral clocks so that fCPU >= fAPBx - let pbasel = (*PM_REGS).pbasel.extract(); + let pbasel = PM_REGS.pbasel.extract(); unlock(0x0000000C); - (*PM_REGS).pbasel.modify_no_read( + PM_REGS.pbasel.modify_no_read( pbasel, PeripheralBusXClockSelect::PBDIV::SET + PeripheralBusXClockSelect::PBSEL::CLEAR, ); - while (*PM_REGS).sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} + while PM_REGS.sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} - let pbbsel = (*PM_REGS).pbbsel.extract(); + let pbbsel = PM_REGS.pbbsel.extract(); unlock(0x00000010); - (*PM_REGS).pbbsel.modify_no_read( + PM_REGS.pbbsel.modify_no_read( pbbsel, PeripheralBusXClockSelect::PBDIV::SET + PeripheralBusXClockSelect::PBSEL::CLEAR, ); - while (*PM_REGS).sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} + while PM_REGS.sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} - let pbcsel = (*PM_REGS).pbcsel.extract(); + let pbcsel = PM_REGS.pbcsel.extract(); unlock(0x00000014); - (*PM_REGS).pbcsel.modify_no_read( + PM_REGS.pbcsel.modify_no_read( pbcsel, PeripheralBusXClockSelect::PBDIV::SET + PeripheralBusXClockSelect::PBSEL::CLEAR, ); - while (*PM_REGS).sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} + while PM_REGS.sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} - let pbdsel = (*PM_REGS).pbdsel.extract(); + let pbdsel = PM_REGS.pbdsel.extract(); unlock(0x00000018); - (*PM_REGS).pbdsel.modify_no_read( + PM_REGS.pbdsel.modify_no_read( pbdsel, PeripheralBusXClockSelect::PBDIV::SET + PeripheralBusXClockSelect::PBSEL::CLEAR, ); - while (*PM_REGS).sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} + while PM_REGS.sr.matches_all(InterruptOrStatus::CKRDY::CLEAR) {} let clock_mask = self.system_on_clocks.get(); self.system_on_clocks @@ -1001,11 +1005,15 @@ fn select_main_clock(clock: MainClock) { /// /// It takes one of two forms: /// +/// ```rust,ignore /// mask_clock!(CLOCK_MASK_OFFSET_MASK_OFFSET: pm_register | value) +/// ``` /// /// which performs a logical-or on the existing register value, or /// +/// ```rust,ignore /// mask_clock!(CLOCK_MASK_OFFSET_MASK_OFFSET: pm_register & value) +/// ``` /// /// which performs a logical-and. /// @@ -1064,25 +1072,33 @@ macro_rules! get_clock { /// through the INTERRUPT_COUNT variable. pub fn deep_sleep_ready() -> bool { // HSB clocks that can be enabled and the core is permitted to enter deep sleep. - let deep_sleep_hsbmask: FieldValue = - /* added by us */ ClockMaskHsb::PDCA::SET + - /* default */ ClockMaskHsb::FLASHCALW::SET + - /* added by us */ ClockMaskHsb::FLASHCALW_PICOCACHE::SET + - /* default */ ClockMaskHsb::APBA_BRIDGE::SET + - /* default */ ClockMaskHsb::APBB_BRIDGE::SET + - /* default */ ClockMaskHsb::APBC_BRIDGE::SET + - /* default */ ClockMaskHsb::APBD_BRIDGE::SET; + // added by us: ClockMaskHsb::PDCA::SET + // default: ClockMaskHsb::FLASHCALW::SET + // added by us: ClockMaskHsb::FLASHCALW_PICOCACHE::SET + // default: ClockMaskHsb::APBA_BRIDGE::SET + // default: ClockMaskHsb::APBB_BRIDGE::SET + // default: ClockMaskHsb::APBC_BRIDGE::SET + // default: ClockMaskHsb::APBD_BRIDGE::SET + let deep_sleep_hsbmask: FieldValue = ClockMaskHsb::PDCA::SET + + ClockMaskHsb::FLASHCALW::SET + + ClockMaskHsb::FLASHCALW_PICOCACHE::SET + + ClockMaskHsb::APBA_BRIDGE::SET + + ClockMaskHsb::APBB_BRIDGE::SET + + ClockMaskHsb::APBC_BRIDGE::SET + + ClockMaskHsb::APBD_BRIDGE::SET; // PBA clocks that can be enabled and the core is permitted to enter deep sleep. + // added by us: ClockMaskPba::TWIS0::SET + // added by us: ClockMaskPba::TWIS1::SET let deep_sleep_pbamask: FieldValue = - /* added by us */ ClockMaskPba::TWIS0::SET + - /* added by us */ ClockMaskPba::TWIS1::SET; + ClockMaskPba::TWIS0::SET + ClockMaskPba::TWIS1::SET; // PBB clocks that can be enabled and the core is permitted to enter deep sleep. + // default: ClockMaskPbb::FLASHCALW::SET + // added by us: ClockMaskPbb::HRAMC1::SET + // added by us: ClockMaskPbb::PDCA::SET let deep_sleep_pbbmask: FieldValue = - /* default */ ClockMaskPbb::FLASHCALW::SET + - /* added by us */ ClockMaskPbb::HRAMC1::SET + - /* added by us */ ClockMaskPbb::PDCA::SET; + ClockMaskPbb::FLASHCALW::SET + ClockMaskPbb::HRAMC1::SET + ClockMaskPbb::PDCA::SET; let hsb = PM_REGS.hsbmask.get() & !deep_sleep_hsbmask.mask() == 0; let pba = PM_REGS.pbamask.get() & !deep_sleep_pbamask.mask() == 0; @@ -1093,32 +1109,32 @@ pub fn deep_sleep_ready() -> bool { impl ClockInterface for Clock { fn is_enabled(&self) -> bool { - match self { - &Clock::HSB(v) => get_clock!(HSB_MASK_OFFSET: hsbmask & (1 << (v as u32))), - &Clock::PBA(v) => get_clock!(PBA_MASK_OFFSET: pbamask & (1 << (v as u32))), - &Clock::PBB(v) => get_clock!(PBB_MASK_OFFSET: pbbmask & (1 << (v as u32))), - &Clock::PBC(v) => get_clock!(PBC_MASK_OFFSET: pbcmask & (1 << (v as u32))), - &Clock::PBD(v) => get_clock!(PBD_MASK_OFFSET: pbdmask & (1 << (v as u32))), + match *self { + Clock::HSB(v) => get_clock!(HSB_MASK_OFFSET: hsbmask & (1 << (v as u32))), + Clock::PBA(v) => get_clock!(PBA_MASK_OFFSET: pbamask & (1 << (v as u32))), + Clock::PBB(v) => get_clock!(PBB_MASK_OFFSET: pbbmask & (1 << (v as u32))), + Clock::PBC(v) => get_clock!(PBC_MASK_OFFSET: pbcmask & (1 << (v as u32))), + Clock::PBD(v) => get_clock!(PBD_MASK_OFFSET: pbdmask & (1 << (v as u32))), } } fn enable(&self) { - match self { - &Clock::HSB(v) => mask_clock!(HSB_MASK_OFFSET: hsbmask | 1 << (v as u32)), - &Clock::PBA(v) => mask_clock!(PBA_MASK_OFFSET: pbamask | 1 << (v as u32)), - &Clock::PBB(v) => mask_clock!(PBB_MASK_OFFSET: pbbmask | 1 << (v as u32)), - &Clock::PBC(v) => mask_clock!(PBC_MASK_OFFSET: pbcmask | 1 << (v as u32)), - &Clock::PBD(v) => mask_clock!(PBD_MASK_OFFSET: pbdmask | 1 << (v as u32)), + match *self { + Clock::HSB(v) => mask_clock!(HSB_MASK_OFFSET: hsbmask | 1 << (v as u32)), + Clock::PBA(v) => mask_clock!(PBA_MASK_OFFSET: pbamask | 1 << (v as u32)), + Clock::PBB(v) => mask_clock!(PBB_MASK_OFFSET: pbbmask | 1 << (v as u32)), + Clock::PBC(v) => mask_clock!(PBC_MASK_OFFSET: pbcmask | 1 << (v as u32)), + Clock::PBD(v) => mask_clock!(PBD_MASK_OFFSET: pbdmask | 1 << (v as u32)), } } fn disable(&self) { - match self { - &Clock::HSB(v) => mask_clock!(HSB_MASK_OFFSET: hsbmask & !(1 << (v as u32))), - &Clock::PBA(v) => mask_clock!(PBA_MASK_OFFSET: pbamask & !(1 << (v as u32))), - &Clock::PBB(v) => mask_clock!(PBB_MASK_OFFSET: pbbmask & !(1 << (v as u32))), - &Clock::PBC(v) => mask_clock!(PBC_MASK_OFFSET: pbcmask & !(1 << (v as u32))), - &Clock::PBD(v) => mask_clock!(PBD_MASK_OFFSET: pbdmask & !(1 << (v as u32))), + match *self { + Clock::HSB(v) => mask_clock!(HSB_MASK_OFFSET: hsbmask & !(1 << (v as u32))), + Clock::PBA(v) => mask_clock!(PBA_MASK_OFFSET: pbamask & !(1 << (v as u32))), + Clock::PBB(v) => mask_clock!(PBB_MASK_OFFSET: pbbmask & !(1 << (v as u32))), + Clock::PBC(v) => mask_clock!(PBC_MASK_OFFSET: pbcmask & !(1 << (v as u32))), + Clock::PBD(v) => mask_clock!(PBD_MASK_OFFSET: pbdmask & !(1 << (v as u32))), } } } diff --git a/chips/sam4l/src/scif.rs b/chips/sam4l/src/scif.rs index b879081423..032fec1935 100644 --- a/chips/sam4l/src/scif.rs +++ b/chips/sam4l/src/scif.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the system control interface for the SAM4L. //! //! This file includes support for the SCIF (Chapter 13 of SAML manual), which diff --git a/chips/sam4l/src/serial_num.rs b/chips/sam4l/src/serial_num.rs index 27d75aaac1..5dde87e824 100644 --- a/chips/sam4l/src/serial_num.rs +++ b/chips/sam4l/src/serial_num.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Provides a struct that enables access to the unique 120 bit serial number stored in read-only //! flash on the sam4l. @@ -44,6 +48,6 @@ impl SerialNum { .rev() .take(8) .enumerate() - .fold(0u64, |sum, (i, &val)| sum + ((val as u64) << i * 8)) + .fold(0u64, |sum, (i, &val)| sum + ((val as u64) << (i * 8))) } } diff --git a/chips/sam4l/src/spi.rs b/chips/sam4l/src/spi.rs index 87b59f4d70..a0dd44d949 100644 --- a/chips/sam4l/src/spi.rs +++ b/chips/sam4l/src/spi.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of DMA-based SPI master and slave communication for the //! SAM4L. //! @@ -182,8 +186,8 @@ pub enum SpiRole { } /// Abstraction of the SPI Hardware -pub struct SpiHw { - client: OptionalCell<&'static dyn SpiMasterClient>, +pub struct SpiHw<'a> { + client: OptionalCell<&'a dyn SpiMasterClient>, dma_read: OptionalCell<&'static DMAChannel>, dma_write: OptionalCell<&'static DMAChannel>, // keep track of which how many DMA transfers are pending to correctly @@ -192,19 +196,19 @@ pub struct SpiHw { dma_length: Cell, // Slave client is distinct from master client - slave_client: OptionalCell<&'static dyn SpiSlaveClient>, + slave_client: OptionalCell<&'a dyn SpiSlaveClient>, role: Cell, - pm: &'static pm::PowerManager, + pm: &'a pm::PowerManager, } const SPI_BASE: StaticRef = unsafe { StaticRef::new(0x40008000 as *const SpiRegisters) }; -impl PeripheralManagement for SpiHw { +impl PeripheralManagement for SpiHw<'_> { type RegisterType = SpiRegisters; fn get_registers(&self) -> &SpiRegisters { - &*SPI_BASE + &SPI_BASE } fn get_clock(&self) -> &pm::Clock { @@ -222,11 +226,11 @@ impl PeripheralManagement for SpiHw { } } -type SpiRegisterManager<'a> = PeripheralManager<'a, SpiHw, pm::Clock>; +type SpiRegisterManager<'a, 'm> = PeripheralManager<'m, SpiHw<'a>, pm::Clock>; -impl SpiHw { +impl<'a> SpiHw<'a> { /// Creates a new SPI object, with peripheral 0 selected - pub const fn new(pm: &'static pm::PowerManager) -> SpiHw { + pub const fn new(pm: &'a pm::PowerManager) -> SpiHw<'a> { SpiHw { client: OptionalCell::empty(), dma_read: OptionalCell::empty(), @@ -240,7 +244,7 @@ impl SpiHw { } } - fn init_as_role(&self, spi: &SpiRegisterManager, role: SpiRole) { + fn init_as_role(&self, spi: &SpiRegisterManager<'a, '_>, role: SpiRole) { self.role.set(role); if role == SpiRole::SpiMaster { @@ -263,7 +267,7 @@ impl SpiHw { } fn enable(&self) { - let spi = &SpiRegisterManager::new(&self); + let spi = &SpiRegisterManager::new(self); spi.registers.cr.write(Control::SPIEN::SET); @@ -273,7 +277,7 @@ impl SpiHw { } fn disable(&self) { - let spi = &SpiRegisterManager::new(&self); + let spi = &SpiRegisterManager::new(self); // TODO(alevy): we actually probably want to do this asynchrounously but // because we're using DMA, a transfer may have completed with a byte @@ -320,21 +324,21 @@ impl SpiHw { if clock % real_rate != 0 && scbr != 0xFF { scbr += 1; } - let spi = &SpiRegisterManager::new(&self); + let spi = &SpiRegisterManager::new(self); let csr = self.get_active_csr(spi); csr.modify(ChipSelectParams::SCBR.val(scbr)); clock / scbr } fn get_baud_rate(&self) -> u32 { - let spi = &SpiRegisterManager::new(&self); + let spi = &SpiRegisterManager::new(self); let clock = 48000000; let scbr = self.get_active_csr(spi).read(ChipSelectParams::SCBR); clock / scbr } fn set_polarity(&self, polarity: ClockPolarity) { - let spi = &SpiRegisterManager::new(&self); + let spi = &SpiRegisterManager::new(self); let csr = self.get_active_csr(spi); match polarity { ClockPolarity::IdleHigh => csr.modify(ChipSelectParams::CPOL::InactiveHigh), @@ -343,7 +347,7 @@ impl SpiHw { } fn get_polarity(&self) -> ClockPolarity { - let spi = &SpiRegisterManager::new(&self); + let spi = &SpiRegisterManager::new(self); let csr = self.get_active_csr(spi); if csr.matches_all(ChipSelectParams::CPOL::InactiveLow) { ClockPolarity::IdleLow @@ -353,7 +357,7 @@ impl SpiHw { } fn set_phase(&self, phase: ClockPhase) { - let spi = &SpiRegisterManager::new(&self); + let spi = &SpiRegisterManager::new(self); let csr = self.get_active_csr(spi); match phase { ClockPhase::SampleLeading => csr.modify(ChipSelectParams::NCPHA::CaptureLeading), @@ -362,7 +366,7 @@ impl SpiHw { } fn get_phase(&self) -> ClockPhase { - let spi = &SpiRegisterManager::new(&self); + let spi = &SpiRegisterManager::new(self); let csr = self.get_active_csr(spi); if csr.matches_all(ChipSelectParams::NCPHA::CaptureTrailing) { ClockPhase::SampleTrailing @@ -374,7 +378,7 @@ impl SpiHw { pub fn set_active_peripheral(&self, peripheral: Peripheral) { // Slave cannot set active peripheral if self.role.get() == SpiRole::SpiMaster { - let spi = &SpiRegisterManager::new(&self); + let spi = &SpiRegisterManager::new(self); let mr = match peripheral { Peripheral::Peripheral0 => Mode::PCS::PCS0, Peripheral::Peripheral1 => Mode::PCS::PCS1, @@ -386,7 +390,7 @@ impl SpiHw { } /// Returns the currently active peripheral - fn get_active_peripheral(&self, spi: &SpiRegisterManager) -> Peripheral { + fn get_active_peripheral(&self, spi: &SpiRegisterManager<'a, '_>) -> Peripheral { if self.role.get() == SpiRole::SpiMaster { if spi.registers.mr.matches_all(Mode::PCS::PCS3) { Peripheral::Peripheral3 @@ -406,10 +410,10 @@ impl SpiHw { /// Returns the value of CSR0, CSR1, CSR2, or CSR3, /// whichever corresponds to the active peripheral - fn get_active_csr<'a>( - &self, - spi: &'a SpiRegisterManager, - ) -> &'a registers::ReadWrite { + fn get_active_csr<'s>( + &'s self, + spi: &SpiRegisterManager<'a, 's>, + ) -> &'s registers::ReadWrite { match self.get_active_peripheral(spi) { Peripheral::Peripheral0 => &spi.registers.csr[0], Peripheral::Peripheral1 => &spi.registers.csr[1], @@ -425,7 +429,7 @@ impl SpiHw { } pub fn handle_interrupt(&self) { - let spi = &SpiRegisterManager::new(&self); + let spi = &SpiRegisterManager::new(self); self.slave_client.map(|client| { if spi.registers.sr.is_set(Status::NSSR) { @@ -515,17 +519,17 @@ impl SpiHw { } } -impl spi::SpiMaster for SpiHw { +impl<'a> spi::SpiMaster<'a> for SpiHw<'a> { type ChipSelect = u8; - fn set_client(&self, client: &'static dyn SpiMasterClient) { + fn set_client(&self, client: &'a dyn SpiMasterClient) { self.client.set(client); } /// By default, initialize SPI to operate at 40KHz, clock is /// idle on low, and sample on the leading edge. fn init(&self) -> Result<(), ErrorCode> { - let spi = &SpiRegisterManager::new(&self); + let spi = &SpiRegisterManager::new(self); self.init_as_role(spi, SpiRole::SpiMaster); Ok(()) } @@ -537,7 +541,7 @@ impl spi::SpiMaster for SpiHw { /// Write a byte to the SPI and discard the read; if an /// asynchronous operation is outstanding, do nothing. fn write_byte(&self, out_byte: u8) -> Result<(), ErrorCode> { - let spi = &SpiRegisterManager::new(&self); + let spi = &SpiRegisterManager::new(self); let tdr = (out_byte as u32) & spi_consts::tdr::TD; // Wait for data to leave TDR and enter serializer, so TDR is free @@ -556,7 +560,7 @@ impl spi::SpiMaster for SpiHw { /// Write a byte to the SPI and return the read; if an /// asynchronous operation is outstanding, do nothing. fn read_write_byte(&self, val: u8) -> Result { - let spi = &SpiRegisterManager::new(&self); + let spi = &SpiRegisterManager::new(self); self.write_byte(val)?; while !spi.registers.sr.is_set(Status::RDRF) {} @@ -620,13 +624,13 @@ impl spi::SpiMaster for SpiHw { } fn hold_low(&self) { - let spi = &SpiRegisterManager::new(&self); + let spi = &SpiRegisterManager::new(self); let csr = self.get_active_csr(spi); csr.modify(ChipSelectParams::CSAAT::ActiveAfterTransfer); } fn release_low(&self) { - let spi = &SpiRegisterManager::new(&self); + let spi = &SpiRegisterManager::new(self); let csr = self.get_active_csr(spi); csr.modify(ChipSelectParams::CSAAT::InactiveAfterTransfer); } @@ -648,9 +652,9 @@ impl spi::SpiMaster for SpiHw { } } -impl spi::SpiSlave for SpiHw { +impl<'a> spi::SpiSlave<'a> for SpiHw<'a> { // Set to None to disable the whole thing - fn set_client(&self, client: Option<&'static dyn SpiSlaveClient>) { + fn set_client(&self, client: Option<&'a dyn SpiSlaveClient>) { self.slave_client.insert(client); } @@ -659,7 +663,7 @@ impl spi::SpiSlave for SpiHw { } fn init(&self) -> Result<(), ErrorCode> { - let spi = &SpiRegisterManager::new(&self); + let spi = &SpiRegisterManager::new(self); self.init_as_role(spi, SpiRole::SpiSlave); Ok(()) } @@ -667,7 +671,7 @@ impl spi::SpiSlave for SpiHw { /// This sets the value in the TDR register, to be sent as soon as the /// chip select pin is low. fn set_write_byte(&self, write_byte: u8) { - let spi = &SpiRegisterManager::new(&self); + let spi = SpiRegisterManager::new(self); spi.registers.tdr.set(write_byte as u32); } @@ -711,7 +715,7 @@ impl spi::SpiSlave for SpiHw { } } -impl DMAClient for SpiHw { +impl DMAClient for SpiHw<'_> { fn transfer_done(&self, _pid: DMAPeripheral) { // Only callback that the transfer is done if either: // 1) The transfer was TX only and TX finished diff --git a/chips/sam4l/src/trng.rs b/chips/sam4l/src/trng.rs index cf844be005..ead7d7e3af 100644 --- a/chips/sam4l/src/trng.rs +++ b/chips/sam4l/src/trng.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the SAM4L TRNG. It provides an implementation of //! the Entropy32 trait. diff --git a/chips/sam4l/src/usart.rs b/chips/sam4l/src/usart.rs index a0c67b6d90..7a33d26542 100644 --- a/chips/sam4l/src/usart.rs +++ b/chips/sam4l/src/usart.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the SAM4L USART peripheral. //! //! Supports UART and SPI master modes. @@ -5,6 +9,7 @@ use core::cell::Cell; use core::cmp; use core::sync::atomic::{AtomicBool, Ordering}; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; use kernel::hil; use kernel::hil::spi; use kernel::hil::uart; @@ -294,10 +299,10 @@ static IS_PANICING: AtomicBool = AtomicBool::new(false); impl<'a> USARTRegManager<'a> { fn real_new(usart: &'a USART) -> USARTRegManager<'a> { - if pm::is_clock_enabled(usart.clock) == false { + if !pm::is_clock_enabled(usart.clock) { pm::enable_clock(usart.clock); } - let regs: &UsartRegisters = &*usart.registers; + let regs: &UsartRegisters = &usart.registers; USARTRegManager { registers: regs, clock: usart.clock, @@ -319,7 +324,7 @@ impl<'a> USARTRegManager<'a> { impl Drop for USARTRegManager<'_> { fn drop(&mut self) { // Anything listening for RX or TX interrupts? - let ints_active = self.registers.imr.matches_any( + let ints_active = self.registers.imr.any_matching_bits_set( Interrupt::RXBUFF::SET + Interrupt::TXEMPTY::SET + Interrupt::TIMEOUT::SET @@ -375,6 +380,14 @@ enum UsartClient<'a> { SpiMaster(&'a dyn spi::SpiMasterClient), } +// State that needs to be stored for deferred calls +struct DeferredCallState { + abort_rx_buf: Option<&'static mut [u8]>, + abort_rx_len: usize, + abort_rx_rcode: Result<(), ErrorCode>, + abort_rx_error: uart::Error, +} + pub struct USART<'a> { registers: StaticRef, clock: pm::Clock, @@ -395,17 +408,19 @@ pub struct USART<'a> { spi_chip_select: OptionalCell<&'a dyn hil::gpio::Pin>, pm: &'a pm::PowerManager, + dc_state: OptionalCell, + deferred_call: DeferredCall, } impl<'a> USART<'a> { - const fn new( + fn new( base_addr: StaticRef, clock: pm::PBAClock, rx_dma_peripheral: dma::DMAPeripheral, tx_dma_peripheral: dma::DMAPeripheral, pm: &'a pm::PowerManager, - ) -> USART<'a> { - USART { + ) -> Self { + Self { registers: base_addr, clock: pm::Clock::PBA(clock), @@ -416,10 +431,10 @@ impl<'a> USART<'a> { // these get defined later by `chip.rs` rx_dma: Cell::new(None), - rx_dma_peripheral: rx_dma_peripheral, + rx_dma_peripheral, rx_len: Cell::new(0), tx_dma: Cell::new(None), - tx_dma_peripheral: tx_dma_peripheral, + tx_dma_peripheral, tx_len: Cell::new(0), // this gets defined later by `main.rs` @@ -428,10 +443,12 @@ impl<'a> USART<'a> { // This is only used if the USART is in SPI mode. spi_chip_select: OptionalCell::empty(), pm, + dc_state: OptionalCell::empty(), + deferred_call: DeferredCall::new(), } } - pub const fn new_usart0(pm: &'a pm::PowerManager) -> Self { + pub fn new_usart0(pm: &'a pm::PowerManager) -> Self { USART::new( USART_BASE_ADDRS[0], pm::PBAClock::USART0, @@ -441,7 +458,7 @@ impl<'a> USART<'a> { ) } - pub const fn new_usart1(pm: &'a pm::PowerManager) -> Self { + pub fn new_usart1(pm: &'a pm::PowerManager) -> Self { USART::new( USART_BASE_ADDRS[1], pm::PBAClock::USART1, @@ -451,7 +468,7 @@ impl<'a> USART<'a> { ) } - pub const fn new_usart2(pm: &'a pm::PowerManager) -> Self { + pub fn new_usart2(pm: &'a pm::PowerManager) -> Self { USART::new( USART_BASE_ADDRS[2], pm::PBAClock::USART2, @@ -461,7 +478,7 @@ impl<'a> USART<'a> { ) } - pub const fn new_usart3(pm: &'a pm::PowerManager) -> Self { + pub fn new_usart3(pm: &'a pm::PowerManager) -> Self { USART::new( USART_BASE_ADDRS[3], pm::PBAClock::USART3, @@ -488,7 +505,7 @@ impl<'a> USART<'a> { self.usart_mode.set(mode); - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); // disable interrupts self.disable_interrupts(usart); @@ -530,15 +547,17 @@ impl<'a> USART<'a> { buf }); self.rx_len.set(0); - - // alert client - self.client.map(|usartclient| { - if let UsartClient::Uart(Some(rx), _tx) = usartclient { - buffer - .take() - .map(|buf| rx.received_buffer(buf, length, rcode, error)); - } - }); + // Save state for deferred call + let dc_state = DeferredCallState { + abort_rx_buf: buffer.take(), + abort_rx_len: length, + abort_rx_rcode: rcode, + abort_rx_error: error, + }; + self.dc_state.set(dc_state); + + // schedule a deferred call to alert the client of this particular UART + self.deferred_call.set(); } } @@ -618,7 +637,7 @@ impl<'a> USART<'a> { } pub fn handle_interrupt(&self) { - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); let status = usart.registers.csr.extract(); let mask = usart.registers.imr.extract(); @@ -769,9 +788,32 @@ impl<'a> USART<'a> { } } +impl DeferredCallClient for USART<'_> { + fn handle_deferred_call(&self) { + self.dc_state.take().map(|mut dc_state| { + self.client.map(|usartclient| { + if let UsartClient::Uart(Some(rx), _tx) = usartclient { + dc_state.abort_rx_buf.take().map(|buf| { + rx.received_buffer( + buf, + dc_state.abort_rx_len, + dc_state.abort_rx_rcode, + dc_state.abort_rx_error, + ) + }); + } + }); + }); + } + + fn register(&'static self) { + self.deferred_call.register(self); + } +} + impl dma::DMAClient for USART<'_> { fn transfer_done(&self, pid: dma::DMAPeripheral) { - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); match self.usart_mode.get() { UsartMode::Uart => { // determine if it was an RX or TX transfer @@ -855,7 +897,7 @@ impl<'a> uart::Receive<'a> for USART<'a> { if rx_len > rx_buffer.len() { return Err((ErrorCode::SIZE, rx_buffer)); } - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); // enable RX self.enable_rx(usart); @@ -873,7 +915,7 @@ impl<'a> uart::Receive<'a> for USART<'a> { } fn receive_abort(&self) -> Result<(), ErrorCode> { - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); self.disable_rx_timeout(usart); self.abort_rx(usart, Err(ErrorCode::CANCEL), uart::Error::Aborted); Err(ErrorCode::BUSY) @@ -896,7 +938,7 @@ impl<'a> uart::Transmit<'a> for USART<'a> { if tx_len > tx_buffer.len() { return Err((ErrorCode::SIZE, tx_buffer)); } - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); // enable TX self.enable_tx(usart); self.usart_tx_state.set(USARTStateTX::DMA_Transmitting); @@ -917,7 +959,7 @@ impl<'a> uart::Transmit<'a> for USART<'a> { fn transmit_abort(&self) -> Result<(), ErrorCode> { if self.usart_tx_state.get() != USARTStateTX::Idle { - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); self.abort_tx(usart, Err(ErrorCode::CANCEL)); Err(ErrorCode::BUSY) } else { @@ -944,7 +986,7 @@ impl uart::Configure for USART<'_> { return Err(ErrorCode::OFF); } - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); // set USART mode register let mut mode = Mode::OVER::SET; // OVER: oversample at 8x @@ -985,7 +1027,7 @@ impl<'a> uart::ReceiveAdvanced<'a> for USART<'a> { if self.usart_rx_state.get() != USARTStateRX::Idle { Err((ErrorCode::BUSY, rx_buffer)) } else { - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); let length = cmp::min(len, rx_buffer.len()); // enable RX @@ -1008,11 +1050,11 @@ impl<'a> uart::ReceiveAdvanced<'a> for USART<'a> { } /// SPI -impl spi::SpiMaster for USART<'_> { +impl<'a> spi::SpiMaster<'a> for USART<'a> { type ChipSelect = Option<&'static dyn hil::gpio::Pin>; fn init(&self) -> Result<(), ErrorCode> { - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); self.usart_mode.set(UsartMode::Spi); @@ -1033,7 +1075,7 @@ impl spi::SpiMaster for USART<'_> { Ok(()) } - fn set_client(&self, client: &'static dyn spi::SpiMasterClient) { + fn set_client(&self, client: &'a dyn spi::SpiMasterClient) { let c = UsartClient::SpiMaster(client); self.client.set(c); } @@ -1048,7 +1090,7 @@ impl spi::SpiMaster for USART<'_> { read_buffer: Option<&'static mut [u8]>, len: usize, ) -> Result<(), (ErrorCode, &'static mut [u8], Option<&'static mut [u8]>)> { - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); self.enable_tx(usart); self.enable_rx(usart); @@ -1109,7 +1151,7 @@ impl spi::SpiMaster for USART<'_> { } fn write_byte(&self, val: u8) -> Result<(), ErrorCode> { - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); usart .registers .cr @@ -1122,12 +1164,12 @@ impl spi::SpiMaster for USART<'_> { } fn read_byte(&self) -> Result { - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); Ok(usart.registers.rhr.read(ReceiverHold::RXCHR) as u8) } fn read_write_byte(&self, val: u8) -> Result { - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); usart .registers .cr @@ -1149,7 +1191,7 @@ impl spi::SpiMaster for USART<'_> { /// Returns the actual rate set fn set_rate(&self, rate: u32) -> Result { - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); self.set_baud_rate(usart, rate); // Calculate what rate will actually be @@ -1159,14 +1201,14 @@ impl spi::SpiMaster for USART<'_> { } fn get_rate(&self) -> u32 { - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); let system_frequency = self.pm.get_system_frequency(); let cd = usart.registers.brgr.read(BaudRate::CD); system_frequency / cd } fn set_polarity(&self, polarity: spi::ClockPolarity) -> Result<(), ErrorCode> { - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); // Note that in SPI mode MSBF bit is clock polarity (CPOL) match polarity { spi::ClockPolarity::IdleLow => { @@ -1180,7 +1222,7 @@ impl spi::SpiMaster for USART<'_> { } fn get_polarity(&self) -> spi::ClockPolarity { - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); // Note that in SPI mode MSBF bit is clock polarity (CPOL) let idle = usart.registers.mr.read(Mode::MSBF); @@ -1191,7 +1233,7 @@ impl spi::SpiMaster for USART<'_> { } fn set_phase(&self, phase: spi::ClockPhase) -> Result<(), ErrorCode> { - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); // Note that in SPI mode SYNC bit is clock phase match phase { @@ -1206,7 +1248,7 @@ impl spi::SpiMaster for USART<'_> { } fn get_phase(&self) -> spi::ClockPhase { - let usart = &USARTRegManager::new(&self); + let usart = &USARTRegManager::new(self); let phase = usart.registers.mr.read(Mode::SYNC); // Note that in SPI mode SYNC bit is clock phase diff --git a/chips/sam4l/src/usbc/debug.rs b/chips/sam4l/src/usbc/debug.rs index 716e7aadb0..101c393122 100644 --- a/chips/sam4l/src/usbc/debug.rs +++ b/chips/sam4l/src/usbc/debug.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::fmt; pub struct HexBuf<'a>(pub &'a [u8]); @@ -6,10 +10,8 @@ impl fmt::Debug for HexBuf<'_> { #[allow(unused_must_use)] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "["); - let mut i: usize = 0; - for b in self.0 { + for (i, b) in self.0.iter().enumerate() { write!(f, "{}{:.02x}", if i > 0 { " " } else { "" }, b); - i += 1; } write!(f, "]") } diff --git a/chips/sam4l/src/usbc/mod.rs b/chips/sam4l/src/usbc/mod.rs index 5a66abcb6f..8e8b563fef 100644 --- a/chips/sam4l/src/usbc/mod.rs +++ b/chips/sam4l/src/usbc/mod.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! SAM4L USB controller pub mod debug; @@ -227,7 +231,7 @@ const USBC_BASE: StaticRef = #[inline] fn usbc_regs() -> &'static UsbcRegisters { - &*USBC_BASE + &USBC_BASE } // Datastructures for tracking USB controller state @@ -298,8 +302,9 @@ pub struct DeviceState { pub endpoint_states: [EndpointState; N_ENDPOINTS], } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Default)] pub enum EndpointState { + #[default] Disabled, Ctrl(CtrlState), BulkIn(BulkInState), @@ -329,12 +334,6 @@ pub enum BulkOutState { Delay, } -impl Default for EndpointState { - fn default() -> Self { - EndpointState::Disabled - } -} - #[derive(Copy, Clone, PartialEq, Debug)] pub enum Speed { Full, @@ -521,7 +520,9 @@ impl<'a> Usbc<'a> { usbc_regs().usbcon.modify(Control::USBE::SET); // Set the pointer to the endpoint descriptors - usbc_regs().udesc.set(&self.descriptors as *const _ as u32); + usbc_regs() + .udesc + .set(core::ptr::addr_of!(self.descriptors) as u32); // Clear pending device global interrupts usbc_regs().udintclr.write( @@ -1397,7 +1398,7 @@ impl<'a> Usbc<'a> { \n {:?}\ \n {:?}\ \n {:?}", - bi, // (&b.addr as *const _), b.addr.get(), + bi, // core::ptr::addr_of!(b.addr), b.addr.get(), b.packet_size.get(), b.control_status.get(), _buf.map(HexBuf) @@ -1478,7 +1479,7 @@ impl<'a> hil::usb::UsbController<'a> for Usbc<'a> { match self.get_state() { State::Reset => self._enable(Mode::Device { - speed: speed, + speed, config: DeviceConfig::default(), state: DeviceState::default(), }), diff --git a/chips/sam4l/src/wdt.rs b/chips/sam4l/src/wdt.rs index 8880c3aefa..50d88665cd 100644 --- a/chips/sam4l/src/wdt.rs +++ b/chips/sam4l/src/wdt.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the SAM4L hardware watchdog timer. use core::cell::Cell; @@ -102,8 +106,7 @@ register_bitfields![u32, // Page 59 of SAM4L data sheet const WDT_BASE: *mut WdtRegisters = 0x400F0C00 as *mut WdtRegisters; -const WDT_REGS: StaticRef = - unsafe { StaticRef::new(WDT_BASE as *const WdtRegisters) }; +const WDT_REGS: StaticRef = unsafe { StaticRef::new(WDT_BASE.cast_const()) }; pub struct Wdt { enabled: Cell, diff --git a/chips/sifive/Cargo.toml b/chips/sifive/Cargo.toml index 8d9ee4b999..30fe76b66f 100644 --- a/chips/sifive/Cargo.toml +++ b/chips/sifive/Cargo.toml @@ -1,10 +1,17 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "sifive" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] rv32i = { path = "../../arch/rv32i" } kernel = { path = "../../kernel" } + +[lints] +workspace = true diff --git a/chips/sifive/src/clint.rs b/chips/sifive/src/clint.rs index ebc41919cc..5b28ba39bb 100644 --- a/chips/sifive/src/clint.rs +++ b/chips/sifive/src/clint.rs @@ -1,6 +1,12 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Create a timer using the Machine Timer registers. -use kernel::hil::time::{self, Alarm, ConvertTicks, Freq32KHz, Frequency, Ticks, Ticks64, Time}; +use core::marker::PhantomData; + +use kernel::hil::time::{self, Alarm, ConvertTicks, Frequency, Ticks, Ticks64, Time}; use kernel::utilities::cells::OptionalCell; use kernel::utilities::registers::interfaces::Writeable; use kernel::utilities::registers::{register_structs, ReadWrite}; @@ -21,13 +27,14 @@ register_structs! { } } -pub struct Clint<'a> { +pub struct Clint<'a, F: Frequency> { registers: StaticRef, client: OptionalCell<&'a dyn time::AlarmClient>, mtimer: MachineTimer<'a>, + _freq: PhantomData, } -impl<'a> Clint<'a> { +impl<'a, F: Frequency> Clint<'a, F> { pub fn new(base: &'a StaticRef) -> Self { Self { registers: *base, @@ -38,6 +45,7 @@ impl<'a> Clint<'a> { &base.value_low, &base.value_high, ), + _freq: PhantomData, } } @@ -55,8 +63,8 @@ impl<'a> Clint<'a> { } } -impl Time for Clint<'_> { - type Frequency = Freq32KHz; +impl Time for Clint<'_, F> { + type Frequency = F; type Ticks = Ticks64; fn now(&self) -> Ticks64 { @@ -64,7 +72,7 @@ impl Time for Clint<'_> { } } -impl<'a> time::Alarm<'a> for Clint<'a> { +impl<'a, F: Frequency> time::Alarm<'a> for Clint<'a, F> { fn set_alarm_client(&self, client: &'a dyn time::AlarmClient) { self.client.set(client); } @@ -94,7 +102,7 @@ impl<'a> time::Alarm<'a> for Clint<'a> { /// used by a chip if that chip has multiple hardware timer peripherals such that a different /// hardware timer can be used to provide alarms to capsules and userspace. This /// implementation will not work alongside other uses of the machine timer. -impl kernel::platform::scheduler_timer::SchedulerTimer for Clint<'_> { +impl kernel::platform::scheduler_timer::SchedulerTimer for Clint<'_, F> { fn start(&self, us: u32) { let now = self.now(); let tics = self.ticks_from_us(us); diff --git a/chips/sifive/src/gpio.rs b/chips/sifive/src/gpio.rs index ad6d1b7efb..e535dbf932 100644 --- a/chips/sifive/src/gpio.rs +++ b/chips/sifive/src/gpio.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! General Purpose Input/Output driver. use kernel::hil; @@ -98,9 +102,9 @@ impl<'a> GpioPin<'a> { ) -> GpioPin<'a> { GpioPin { registers: base, - pin: pin, - set: set, - clear: clear, + pin, + set, + clear, client: OptionalCell::empty(), } } diff --git a/chips/sifive/src/lib.rs b/chips/sifive/src/lib.rs index b731216ee0..419c1d5a9f 100644 --- a/chips/sifive/src/lib.rs +++ b/chips/sifive/src/lib.rs @@ -1,12 +1,16 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementations for generic SiFive MCU peripherals. -#![feature(const_fn_trait_bound)] #![no_std] #![crate_name = "sifive"] #![crate_type = "rlib"] pub mod clint; pub mod gpio; +pub mod plic; pub mod prci; pub mod pwm; pub mod rtc; diff --git a/chips/sifive/src/plic.rs b/chips/sifive/src/plic.rs new file mode 100644 index 0000000000..62a33e444f --- /dev/null +++ b/chips/sifive/src/plic.rs @@ -0,0 +1,159 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Platform Level Interrupt Control peripheral driver. + +use kernel::utilities::cells::VolatileCell; +use kernel::utilities::registers::interfaces::{Readable, Writeable}; +use kernel::utilities::registers::LocalRegisterCopy; +use kernel::utilities::registers::{register_bitfields, ReadOnly, ReadWrite}; +use kernel::utilities::StaticRef; + +#[repr(C)] +pub struct PlicRegisters { + /// Interrupt Priority Register + _reserved0: u32, + priority: [ReadWrite; 51], + _reserved1: [u8; 3888], + /// Interrupt Pending Register + pending: [ReadOnly; 2], + _reserved2: [u8; 4088], + /// Interrupt Enable Register + enable: [ReadWrite; 2], + _reserved3: [u8; 2088952], + /// Priority Threshold Register + threshold: ReadWrite, + /// Claim/Complete Register + claim: ReadWrite, +} + +register_bitfields![u32, + priority [ + Priority OFFSET(0) NUMBITS(3) [] + ] +]; + +pub struct Plic { + registers: StaticRef, + saved: [VolatileCell>; 2], +} + +impl Plic { + pub const fn new(base: StaticRef) -> Self { + Plic { + registers: base, + saved: [ + VolatileCell::new(LocalRegisterCopy::new(0)), + VolatileCell::new(LocalRegisterCopy::new(0)), + ], + } + } + + /// Clear all pending interrupts. The [`E31 core manual`] PLIC Chapter 9.8 + /// p 117: A successful claim also atomically clears the corresponding + /// pending bit on the interrupt source. + /// Note that this function requires you call `enable_all()` first! (As ch. + /// 9.4 p.114 writes.) + /// + /// [`E31 core manual`]: https://sifive.cdn.prismic.io/sifive/c29f9c69-5254-4f9a-9e18-24ea73f34e81_e31_core_complex_manual_21G2.pdf + pub fn clear_all_pending(&self) { + let regs = self.registers; + + loop { + let id = regs.claim.get(); + if id == 0 { + break; + } + regs.claim.set(id); + } + } + + /// Enable all interrupts. + pub fn enable_all(&self) { + for enable in self.registers.enable.iter() { + enable.set(0xFFFF_FFFF); + } + + // Set some default priority for each interrupt. This is not really used + // at this point. + for priority in self.registers.priority.iter() { + priority.write(priority::Priority.val(4)); + } + + // Accept all interrupts. + self.registers.threshold.write(priority::Priority.val(0)); + } + + /// Disable all interrupts. + pub fn disable_all(&self) { + for enable in self.registers.enable.iter() { + enable.set(0); + } + } + + /// Get the index (0-256) of the lowest number pending interrupt, or `None` if + /// none is pending. RISC-V PLIC has a "claim" register which makes it easy + /// to grab the highest priority pending interrupt. + pub fn next_pending(&self) -> Option { + let claim = self.registers.claim.get(); + if claim == 0 { + None + } else { + Some(claim) + } + } + + /// Save the current interrupt to be handled later + /// This will save the interrupt at index internally to be handled later. + /// Interrupts must be disabled before this is called. + /// Saved interrupts can be retrieved by calling `get_saved_interrupts()`. + /// Saved interrupts are cleared when `'complete()` is called. + pub unsafe fn save_interrupt(&self, index: u32) { + let offset = usize::from(index >= 32); + let irq = index % 32; + + // OR the current saved state with the new value + let new_saved = self.saved[offset].get().get() | 1 << irq; + + // Set the new state + self.saved[offset].set(LocalRegisterCopy::new(new_saved)); + } + + /// The `next_pending()` function will only return enabled interrupts. + /// This function will return a pending interrupt that has been disabled by + /// `save_interrupt()`. + pub fn get_saved_interrupts(&self) -> Option { + for (i, pending) in self.saved.iter().enumerate() { + let saved = pending.get().get(); + if saved != 0 { + return Some(saved.trailing_zeros() + (i as u32 * 32)); + } + } + + None + } + + /// Signal that an interrupt is finished being handled. In Tock, this should be + /// called from the normal main loop (not the interrupt handler). + /// Interrupts must be disabled before this is called. + pub unsafe fn complete(&self, index: u32) { + self.registers.claim.set(index); + + let offset = usize::from(index >= 32); + let irq = index % 32; + + // OR the current saved state with the new value + let new_saved = self.saved[offset].get().get() & !(1 << irq); + + // Set the new state + self.saved[offset].set(LocalRegisterCopy::new(new_saved)); + } + + /// This is a generic implementation. There may be board specific versions as + /// some platforms have added more bits to the `mtvec` register. + pub fn suppress_all(&self) { + // Accept all interrupts. + self.registers.threshold.write(priority::Priority.val(0)); + } +} diff --git a/chips/sifive/src/prci.rs b/chips/sifive/src/prci.rs index 2c162f4f39..057c9525b4 100644 --- a/chips/sifive/src/prci.rs +++ b/chips/sifive/src/prci.rs @@ -1,7 +1,15 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Power Reset Clock Interrupt controller driver. +use core::cell::Cell; +use kernel::utilities::registers::interfaces::ReadWriteable; +use kernel::utilities::registers::interfaces::Readable; use kernel::utilities::registers::{register_bitfields, ReadWrite}; use kernel::utilities::StaticRef; +use rv32i::csr; #[repr(C)] pub struct PrciRegisters { @@ -47,22 +55,111 @@ register_bitfields![u32, pub enum ClockFrequency { Freq16Mhz, + Freq344Mhz, } pub struct Prci { registers: StaticRef, + current_frequency: Cell, } impl Prci { pub const fn new(base: StaticRef) -> Prci { - Prci { registers: base } + Prci { + registers: base, + current_frequency: Cell::new(ClockFrequency::Freq16Mhz), + } + } + + pub fn switch_to_internal_clock(&self) { + let regs = self.registers; + // Enable internal high-frequency clock if it's not enabled + if regs.hfrosccfg.read(hfrosccfg::enable) == 0 { + regs.hfrosccfg.modify(hfrosccfg::enable::SET); + } + // ... Wait until the clock is ready + while regs.hfrosccfg.read(hfrosccfg::ready) == 0 {} + // ... and now actually switch + regs.pllcfg + .modify(pllcfg::sel::CLEAR + pllcfg::bypass::CLEAR); + } + + pub fn set_internal_clock_default(&self) { + let regs = self.registers; + // Set to defaults, which according to data sheet should set to 14.4MHz +- 50%, + regs.hfrosccfg + .modify(hfrosccfg::div.val(4) + hfrosccfg::trim.val(0x10)); + } + + pub fn enable_external_clock(&self) { + let regs = self.registers; + // Make sure external crystal oscillator is enabled + if regs.hfxosccfg.read(hfxosccfg::enable) == 0 { + regs.hfxosccfg.modify(hfxosccfg::enable::SET); + } + // ... Wait until the clock is ready + while regs.hfxosccfg.read(hfxosccfg::ready) == 0 {} } pub fn set_clock_frequency(&self, frequency: ClockFrequency) { - let _regs = self.registers; + let regs = self.registers; + // According to someone affiliated with SiFive in forum post: + // https://forums.sifive.com/t/is-it-possible-to-brick-the-hifive-board/751/6, + // it is safe to adjust the internal high frequency clock while it + // is in use, but not the PLL output. + // + // So first switch to internal clock before doing any other clock manipulation + + // Reset internal clock to defaults so we can estimate how long we're spinning for the PLL + // lock delay. At default, the frequency should be 14.4MHz +- 50%, so a maximum frequency + // of just under 22MHz + self.set_internal_clock_default(); + self.switch_to_internal_clock(); + + // Make sure external clock subsystem is enabled before adjusting the PLL + self.enable_external_clock(); match frequency { - ClockFrequency::Freq16Mhz => {} + ClockFrequency::Freq16Mhz => { + // Bypass enabled, feeds clock directly from external clock. For HiFive1 revB, this + // is a 16 MHz clock + regs.pllcfg + .modify(pllcfg::bypass::SET + pllcfg::refsel::SET); + // ... configure final PLL divider to divide by 1 + regs.plloutdiv + .modify(plloutdiv::divby1.val(1) + plloutdiv::div.val(0)); + // ... and finally enable the output + regs.pllcfg.modify(pllcfg::sel::SET); + } + ClockFrequency::Freq344Mhz => { + // Disable bypass, and set external clock as source for PLL + // divide 16 MHz input by 2 (pllr(1)), times 86 (pllf(42)), divide by 2 (pllq(1)) + // for a frequency of 344MHz + regs.pllcfg.modify( + pllcfg::bypass::CLEAR + + pllcfg::refsel::SET + + pllcfg::pllr.val(1) + + pllcfg::pllf.val(42) + + pllcfg::pllq.val(1), + ); + // Divide PLL output by 1 + regs.plloutdiv + .modify(plloutdiv::divby1.val(1) + plloutdiv::div.val(0)); + + // We need to wait for PLL to settle before checking if it's stable, which takes + // about 100 microseconds. Assuming internal clock is worst case of 22MHz (14.7 MHz + // +- 50%), that's about 2200 cycles. + let start = csr::CSR.mcycle.get(); + while csr::CSR.mcycle.get() - start < 2200 {} + // ... and now wait for the PLL lock + while regs.pllcfg.read(pllcfg::lock) == 0 {} + // ... and finally switch to the PLL output + regs.pllcfg.modify(pllcfg::sel::SET); + } }; + self.current_frequency.set(frequency); + + // Finally, disable internal clock as we've now switched to something else. + regs.hfrosccfg.modify(hfrosccfg::enable::CLEAR); } } diff --git a/chips/sifive/src/pwm.rs b/chips/sifive/src/pwm.rs index 4c044209cb..18c20d26e1 100644 --- a/chips/sifive/src/pwm.rs +++ b/chips/sifive/src/pwm.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Pulse Width Modulation (PWM) driver. use kernel::utilities::registers::interfaces::Writeable; diff --git a/chips/sifive/src/rtc.rs b/chips/sifive/src/rtc.rs index 586bcaecdb..aac5dfe7ab 100644 --- a/chips/sifive/src/rtc.rs +++ b/chips/sifive/src/rtc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Real Time Clock (RTC) driver. use kernel::utilities::registers::interfaces::Writeable; diff --git a/chips/sifive/src/uart.rs b/chips/sifive/src/uart.rs index 84de0e9287..f7249da494 100644 --- a/chips/sifive/src/uart.rs +++ b/chips/sifive/src/uart.rs @@ -1,6 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! UART driver. use core::cell::Cell; +use kernel::utilities::registers::FieldValue; use kernel::ErrorCode; use crate::gpio; @@ -11,6 +16,8 @@ use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeabl use kernel::utilities::registers::{register_bitfields, ReadOnly, ReadWrite}; use kernel::utilities::StaticRef; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; + #[repr(C)] pub struct UartRegisters { /// Transmit Data Register @@ -59,15 +66,39 @@ register_bitfields![u32, ] ]; +#[derive(Copy, Clone, PartialEq)] +enum UARTStateTX { + Idle, + Transmitting, + AbortRequested, +} + +#[derive(Copy, Clone, PartialEq)] +enum UARTStateRX { + Idle, + Receiving, + AbortRequested, +} + pub struct Uart<'a> { registers: StaticRef, clock_frequency: u32, + stop_bits: Cell, + tx_client: OptionalCell<&'a dyn hil::uart::TransmitClient>, rx_client: OptionalCell<&'a dyn hil::uart::ReceiveClient>, - stop_bits: Cell, - buffer: TakeCell<'static, [u8]>, - len: Cell, - index: Cell, + + tx_buffer: TakeCell<'static, [u8]>, + tx_len: Cell, + tx_position: Cell, + tx_status: Cell, + + rx_buffer: TakeCell<'static, [u8]>, + rx_len: Cell, + rx_position: Cell, + rx_status: Cell, + + deferred_call: DeferredCall, } #[derive(Copy, Clone)] @@ -76,16 +107,26 @@ pub struct UartParams { } impl<'a> Uart<'a> { - pub const fn new(base: StaticRef, clock_frequency: u32) -> Uart<'a> { + pub fn new(base: StaticRef, clock_frequency: u32) -> Uart<'a> { Uart { registers: base, - clock_frequency: clock_frequency, + clock_frequency, + stop_bits: Cell::new(hil::uart::StopBits::One), + tx_client: OptionalCell::empty(), rx_client: OptionalCell::empty(), - stop_bits: Cell::new(hil::uart::StopBits::One), - buffer: TakeCell::empty(), - len: Cell::new(0), - index: Cell::new(0), + + tx_buffer: TakeCell::empty(), + tx_len: Cell::new(0), + tx_position: Cell::new(0), + tx_status: Cell::new(UARTStateTX::Idle), + + rx_buffer: TakeCell::empty(), + rx_len: Cell::new(0), + rx_position: Cell::new(0), + rx_status: Cell::new(UARTStateRX::Idle), + + deferred_call: DeferredCall::new(), } } @@ -106,16 +147,72 @@ impl<'a> Uart<'a> { regs.div.write(div::div.val(divisor)); } + fn get_stop_bits(&self) -> FieldValue { + match self.stop_bits.get() { + hil::uart::StopBits::One => txctrl::nstop::OneStopBit, + hil::uart::StopBits::Two => txctrl::nstop::TwoStopBits, + } + } + + pub fn disable(&self) { + let regs = self.registers; + regs.txctrl.modify(txctrl::txen::CLEAR); + regs.rxctrl.modify(rxctrl::enable::CLEAR); + + self.disable_rx_interrupt(); + self.disable_tx_interrupt(); + } + fn enable_tx_interrupt(&self) { let regs = self.registers; regs.ie.modify(interrupt::txwm::SET); } + fn enable_rx_interrupt(&self) { + let regs = self.registers; + regs.ie.modify(interrupt::rxwm::SET); + } + + fn disable_rx_interrupt(&self) { + let regs = self.registers; + regs.ie.modify(interrupt::rxwm::CLEAR); + } + fn disable_tx_interrupt(&self) { let regs = self.registers; regs.ie.modify(interrupt::txwm::CLEAR); } + fn uart_is_writable(&self) -> bool { + !self.registers.txdata.is_set(txdata::full) + } + + fn tx_progress(&self) { + while self.uart_is_writable() && self.tx_position.get() < self.tx_len.get() { + self.tx_buffer.map(|buf| { + self.registers + .txdata + .set(buf[self.tx_position.get()].into()); + self.tx_position.replace(self.tx_position.get() + 1); + }); + } + } + + fn rx_progress(&self) { + while self.rx_position.get() < self.rx_len.get() { + let rxdata_copy = self.registers.rxdata.extract(); + + if rxdata_copy.read(rxdata::empty) == 1 { + break; + } + + self.rx_buffer.map(|buf| { + buf[self.rx_position.get()] = rxdata_copy.read(rxdata::data) as u8; + self.rx_position.replace(self.rx_position.get() + 1); + }); + } + } + pub fn handle_interrupt(&self) { let regs = self.registers; @@ -123,44 +220,67 @@ impl<'a> Uart<'a> { let pending_interrupts = regs.ip.extract(); // Determine why an interrupt occurred. - if pending_interrupts.is_set(interrupt::txwm) { + if self.tx_status.get() == UARTStateTX::Transmitting + && pending_interrupts.is_set(interrupt::txwm) + { // Got a TX interrupt which means the number of bytes in the FIFO // has fallen to zero. If there is more to send do that, otherwise // send a callback to the client. - if self.len.get() == self.index.get() { + + if self.tx_position.get() == self.tx_len.get() { // We are done. regs.txctrl.write(txctrl::txen::CLEAR); self.disable_tx_interrupt(); + self.tx_status.set(UARTStateTX::Idle); - // Signal client write done + // Signal client write is done self.tx_client.map(|client| { - self.buffer.take().map(|buffer| { - client.transmitted_buffer(buffer, self.len.get(), Ok(())); + self.tx_buffer.take().map(|buffer| { + client.transmitted_buffer(buffer, self.tx_len.get(), Ok(())); }); }); } else { - // More to send. Fill the buffer until it is full. - self.buffer.map(|buffer| { - for i in self.index.get()..self.len.get() { - // Write the byte from the array to the tx register. - regs.txdata.write(txdata::data.val(buffer[i] as u32)); - self.index.set(i + 1); - // Check if the buffer is full - if regs.txdata.is_set(txdata::full) { - // If it is, break and wait for the TX interrupt. - break; - } + self.tx_progress(); + } + } + + if self.rx_status.get() == UARTStateRX::Receiving + && pending_interrupts.is_set(interrupt::rxwm) + { + self.disable_rx_interrupt(); + // Got a RX interrupt which means the number of bytes in the FIFO + // is greater than zero. Read them. + self.rx_progress(); + + if self.rx_position.get() == self.rx_len.get() { + // reception done + regs.rxctrl.write(rxctrl::enable::CLEAR); + self.rx_status.replace(UARTStateRX::Idle); + + // Signal client read is done + self.rx_client.map(|client| { + if let Some(buf) = self.rx_buffer.take() { + client.received_buffer( + buf, + self.rx_len.get(), + Ok(()), + hil::uart::Error::None, + ); } }); + } else { + self.enable_rx_interrupt(); } } } pub fn transmit_sync(&self, bytes: &[u8]) { let regs = self.registers; + // Make sure the UART is enabled. regs.txctrl - .write(txctrl::txen::SET + txctrl::nstop::OneStopBit + txctrl::txcnt.val(1)); + .write(txctrl::txen::SET + self.get_stop_bits() + txctrl::txcnt.val(1)); + for b in bytes.iter() { while regs.txdata.is_set(txdata::full) {} regs.txdata.write(txdata::data.val(*b as u32)); @@ -168,13 +288,46 @@ impl<'a> Uart<'a> { } } +impl DeferredCallClient for Uart<'_> { + fn register(&'static self) { + self.deferred_call.register(self) + } + + fn handle_deferred_call(&self) { + if self.tx_status.get() == UARTStateTX::AbortRequested { + // alert client + self.tx_client.map(|client| { + self.tx_buffer.take().map(|buf| { + client.transmitted_buffer(buf, self.tx_position.get(), Err(ErrorCode::CANCEL)); + }); + }); + self.tx_status.set(UARTStateTX::Idle); + } + + if self.rx_status.get() == UARTStateRX::AbortRequested { + // alert client + self.rx_client.map(|client| { + self.rx_buffer.take().map(|buf| { + client.received_buffer( + buf, + self.rx_position.get(), + Err(ErrorCode::CANCEL), + hil::uart::Error::Aborted, + ); + }); + }); + self.rx_status.set(UARTStateRX::Idle); + } + } +} + impl hil::uart::Configure for Uart<'_> { fn configure(&self, params: hil::uart::Parameters) -> Result<(), ErrorCode> { // This chip does not support these features. if params.parity != hil::uart::Parity::None { return Err(ErrorCode::NOSUPPORT); } - if params.hw_flow_control != false { + if params.hw_flow_control { return Err(ErrorCode::NOSUPPORT); } @@ -195,48 +348,46 @@ impl<'a> hil::uart::Transmit<'a> for Uart<'a> { fn transmit_buffer( &self, - tx_data: &'static mut [u8], + tx_buffer: &'static mut [u8], tx_len: usize, ) -> Result<(), (ErrorCode, &'static mut [u8])> { - let regs = self.registers; - - if tx_len == 0 { - return Err((ErrorCode::SIZE, tx_data)); - } - - // Enable the interrupt so we know when we can keep writing. - self.enable_tx_interrupt(); - - // Fill the TX buffer until it reports full. - for i in 0..tx_len { - // Write the byte from the array to the tx register. - regs.txdata.write(txdata::data.val(tx_data[i] as u32)); - self.index.set(i + 1); - // Check if the buffer is full - if regs.txdata.is_set(txdata::full) { - // If it is, break and wait for the TX interrupt. - break; - } + if self.tx_status.get() != UARTStateTX::Idle { + Err((ErrorCode::BUSY, tx_buffer)) + } else if tx_len == 0 || tx_len > tx_buffer.len() { + Err((ErrorCode::SIZE, tx_buffer)) + } else { + self.tx_status.set(UARTStateTX::Transmitting); + + // Save the buffer so we can keep sending it. + self.tx_buffer.replace(tx_buffer); + self.tx_len.set(tx_len); + self.tx_position.set(0); + + // Enable transmissions and wait until the FIFO is empty before getting + // an interrupt. + self.registers + .txctrl + .write(txctrl::txen::SET + self.get_stop_bits() + txctrl::txcnt.val(1)); + + // Enable the interrupt so we know when we can keep writing. + self.enable_tx_interrupt(); + + Ok(()) } - - // Save the buffer so we can keep sending it. - self.buffer.replace(tx_data); - self.len.set(tx_len); - - // Enable transmissions, and wait until the FIFO is empty before getting - // an interrupt. - let stop_bits = match self.stop_bits.get() { - hil::uart::StopBits::One => txctrl::nstop::OneStopBit, - hil::uart::StopBits::Two => txctrl::nstop::TwoStopBits, - }; - regs.txctrl - .write(txctrl::txen::SET + stop_bits + txctrl::txcnt.val(1)); - - Ok(()) } fn transmit_abort(&self) -> Result<(), ErrorCode> { - Err(ErrorCode::FAIL) + if self.tx_status.get() != UARTStateTX::Idle { + self.registers.txctrl.write(txctrl::txen::CLEAR); + self.disable_tx_interrupt(); + self.tx_status.set(UARTStateTX::AbortRequested); + + self.deferred_call.set(); + + Err(ErrorCode::BUSY) + } else { + Ok(()) + } } fn transmit_word(&self, _word: u32) -> Result<(), ErrorCode> { @@ -252,13 +403,43 @@ impl<'a> hil::uart::Receive<'a> for Uart<'a> { fn receive_buffer( &self, rx_buffer: &'static mut [u8], - _rx_len: usize, + rx_len: usize, ) -> Result<(), (ErrorCode, &'static mut [u8])> { - Err((ErrorCode::FAIL, rx_buffer)) + if self.rx_status.get() != UARTStateRX::Idle { + Err((ErrorCode::BUSY, rx_buffer)) + } else if rx_len > rx_buffer.len() { + Err((ErrorCode::SIZE, rx_buffer)) + } else { + self.rx_status.set(UARTStateRX::Receiving); + + self.rx_buffer.put(Some(rx_buffer)); + self.rx_position.set(0); + self.rx_len.set(rx_len); + + // Enable receptions and wait until the FIFO has at least one byte + // before getting an interrupt. + self.registers + .rxctrl + .write(rxctrl::enable::SET + rxctrl::counter.val(0)); + + self.enable_rx_interrupt(); + + Ok(()) + } } fn receive_abort(&self) -> Result<(), ErrorCode> { - Err(ErrorCode::FAIL) + if self.rx_status.get() != UARTStateRX::Idle { + self.registers.rxctrl.write(rxctrl::enable::CLEAR); + self.disable_rx_interrupt(); + self.rx_status.set(UARTStateRX::AbortRequested); + + self.deferred_call.set(); + + Err(ErrorCode::BUSY) + } else { + Ok(()) + } } fn receive_word(&self) -> Result<(), ErrorCode> { diff --git a/chips/sifive/src/watchdog.rs b/chips/sifive/src/watchdog.rs index 90c90734ca..6c6f5bb2fd 100644 --- a/chips/sifive/src/watchdog.rs +++ b/chips/sifive/src/watchdog.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Watchdog driver. use kernel::utilities::registers::interfaces::Writeable; diff --git a/chips/stm32f303xc/Cargo.toml b/chips/stm32f303xc/Cargo.toml index 24bdc5fe67..3417265db4 100644 --- a/chips/stm32f303xc/Cargo.toml +++ b/chips/stm32f303xc/Cargo.toml @@ -1,10 +1,17 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "stm32f303xc" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] cortexm4 = { path = "../../arch/cortex-m4" } enum_primitive = { path = "../../libraries/enum_primitive" } kernel = { path = "../../kernel" } + +[lints] +workspace = true diff --git a/chips/stm32f303xc/src/adc.rs b/chips/stm32f303xc/src/adc.rs index 79caabb46e..c23ffe4a61 100644 --- a/chips/stm32f303xc/src/adc.rs +++ b/chips/stm32f303xc/src/adc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Analog to Digital Converter Peripheral use crate::rcc; @@ -10,9 +14,6 @@ use kernel::utilities::registers::{register_bitfields, ReadOnly, ReadWrite}; use kernel::utilities::StaticRef; use kernel::ErrorCode; -pub trait EverythingClient: hil::adc::Client + hil::adc::HighSpeedClient {} -impl EverythingClient for C {} - #[repr(C)] struct AdcRegisters { isr: ReadWrite, @@ -503,7 +504,7 @@ pub struct Adc<'a> { common_registers: StaticRef, clock: AdcClock<'a>, status: Cell, - client: OptionalCell<&'static dyn hil::adc::Client>, + client: OptionalCell<&'a dyn hil::adc::Client>, requested: Cell, requested_channel: Cell, sc_enabled: Cell, @@ -643,7 +644,7 @@ impl<'a> Adc<'a> { } fn sample_u32(&self, channel: u32) -> Result<(), ErrorCode> { - if self.sc_enabled.get() == false { + if !self.sc_enabled.get() { self.enable_special_channels(); } if self.status.get() == ADCStatus::Idle { @@ -679,7 +680,7 @@ impl ClockInterface for AdcClock<'_> { } } -impl hil::adc::Adc for Adc<'_> { +impl<'a> hil::adc::Adc<'a> for Adc<'a> { type Channel = Channel; fn sample(&self, channel: &Self::Channel) -> Result<(), ErrorCode> { @@ -722,13 +723,13 @@ impl hil::adc::Adc for Adc<'_> { Some(3300) } - fn set_client(&self, client: &'static dyn hil::adc::Client) { + fn set_client(&self, client: &'a dyn hil::adc::Client) { self.client.set(client); } } /// Not yet supported -impl hil::adc::AdcHighSpeed for Adc<'_> { +impl<'a> hil::adc::AdcHighSpeed<'a> for Adc<'a> { /// Capture buffered samples from the ADC continuously at a given /// frequency, calling the client whenever a buffer fills up. The client is /// then expected to either stop sampling or provide an additional buffer @@ -774,4 +775,6 @@ impl hil::adc::AdcHighSpeed for Adc<'_> { ) -> Result<(Option<&'static mut [u16]>, Option<&'static mut [u16]>), ErrorCode> { Err(ErrorCode::NOSUPPORT) } + + fn set_highspeed_client(&self, _client: &'a dyn hil::adc::HighSpeedClient) {} } diff --git a/chips/stm32f303xc/src/chip.rs b/chips/stm32f303xc/src/chip.rs index fa0b5cfbc3..e389a3fb82 100644 --- a/chips/stm32f303xc/src/chip.rs +++ b/chips/stm32f303xc/src/chip.rs @@ -1,15 +1,17 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Chip trait setup. use core::fmt::Write; -use cortexm4; -use kernel::deferred_call; +use cortexm4::{CortexM4, CortexMVariant}; use kernel::platform::chip::Chip; use kernel::platform::chip::InterruptService; -use crate::deferred_call_tasks::DeferredCallTask; use crate::nvic; -pub struct Stm32f3xx<'a, I: InterruptService + 'a> { +pub struct Stm32f3xx<'a, I: InterruptService + 'a> { mpu: cortexm4::mpu::MPU, userspace_kernel_boundary: cortexm4::syscall::SysCall, interrupt_service: &'a I, @@ -48,15 +50,23 @@ impl<'a> Stm32f3xxDefaultPeripherals<'a> { } } - pub fn setup_circular_deps(&'a self) { + // Setup any circular dependencies and register deferred calls + pub fn setup_circular_deps(&'static self) { self.gpio_ports.setup_circular_deps(); + + kernel::deferred_call::DeferredCallClient::register(&self.flash); + kernel::deferred_call::DeferredCallClient::register(&self.usart1); + kernel::deferred_call::DeferredCallClient::register(&self.usart2); + kernel::deferred_call::DeferredCallClient::register(&self.usart3); } } -impl<'a> InterruptService for Stm32f3xxDefaultPeripherals<'a> { +impl<'a> InterruptService for Stm32f3xxDefaultPeripherals<'a> { unsafe fn service_interrupt(&self, interrupt: u32) -> bool { match interrupt { nvic::USART1 => self.usart1.handle_interrupt(), + nvic::USART2 => self.usart2.handle_interrupt(), + nvic::USART3 => self.usart3.handle_interrupt(), nvic::TIM2 => self.tim2.handle_interrupt(), @@ -79,16 +89,9 @@ impl<'a> InterruptService for Stm32f3xxDefaultPeripherals<'a> } true } - - unsafe fn service_deferred_call(&self, task: DeferredCallTask) -> bool { - match task { - DeferredCallTask::Flash => self.flash.handle_interrupt(), - } - true - } } -impl<'a, I: InterruptService + 'a> Stm32f3xx<'a, I> { +impl<'a, I: InterruptService + 'a> Stm32f3xx<'a, I> { pub unsafe fn new(interrupt_service: &'a I) -> Self { Self { mpu: cortexm4::mpu::MPU::new(), @@ -98,18 +101,14 @@ impl<'a, I: InterruptService + 'a> Stm32f3xx<'a, I> { } } -impl<'a, I: InterruptService + 'a> Chip for Stm32f3xx<'a, I> { +impl<'a, I: InterruptService + 'a> Chip for Stm32f3xx<'a, I> { type MPU = cortexm4::mpu::MPU; type UserspaceKernelBoundary = cortexm4::syscall::SysCall; fn service_pending_interrupts(&self) { unsafe { loop { - if let Some(task) = deferred_call::DeferredCall::next_pending() { - if !self.interrupt_service.service_deferred_call(task) { - panic!("unhandled deferred call"); - } - } else if let Some(interrupt) = cortexm4::nvic::next_pending() { + if let Some(interrupt) = cortexm4::nvic::next_pending() { if !self.interrupt_service.service_interrupt(interrupt) { panic!("unhandled interrupt {}", interrupt); } @@ -124,7 +123,7 @@ impl<'a, I: InterruptService + 'a> Chip for Stm32f3xx<'a, I> { } fn has_pending_interrupts(&self) -> bool { - unsafe { cortexm4::nvic::has_pending() || deferred_call::has_tasks() } + unsafe { cortexm4::nvic::has_pending() } } fn mpu(&self) -> &cortexm4::mpu::MPU { @@ -150,6 +149,6 @@ impl<'a, I: InterruptService + 'a> Chip for Stm32f3xx<'a, I> { } unsafe fn print_state(&self, write: &mut dyn Write) { - cortexm4::print_cortexm4_state(write); + CortexM4::print_cortexm_state(write); } } diff --git a/chips/stm32f303xc/src/deferred_call_tasks.rs b/chips/stm32f303xc/src/deferred_call_tasks.rs index 76a1eeef21..04136e874b 100644 --- a/chips/stm32f303xc/src/deferred_call_tasks.rs +++ b/chips/stm32f303xc/src/deferred_call_tasks.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Definition of Deferred Call tasks. //! //! Deferred calls also peripheral drivers to register pseudo interrupts. @@ -10,6 +14,9 @@ use core::convert::TryFrom; #[derive(Copy, Clone)] pub enum DeferredCallTask { Flash = 0, + Usart1 = 1, + Usart2 = 2, + Usart3 = 3, } impl TryFrom for DeferredCallTask { @@ -18,6 +25,9 @@ impl TryFrom for DeferredCallTask { fn try_from(value: usize) -> Result { match value { 0 => Ok(DeferredCallTask::Flash), + 1 => Ok(DeferredCallTask::Usart1), + 2 => Ok(DeferredCallTask::Usart2), + 3 => Ok(DeferredCallTask::Usart3), _ => Err(()), } } diff --git a/chips/stm32f303xc/src/dma.rs b/chips/stm32f303xc/src/dma.rs index 85c76e4d46..e53fad7c1c 100644 --- a/chips/stm32f303xc/src/dma.rs +++ b/chips/stm32f303xc/src/dma.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::platform::chip::ClockInterface; use kernel::utilities::registers::{register_bitfields, ReadOnly, ReadWrite}; use kernel::utilities::StaticRef; diff --git a/chips/stm32f303xc/src/exti.rs b/chips/stm32f303xc/src/exti.rs index a2a27a0648..187171a8f7 100644 --- a/chips/stm32f303xc/src/exti.rs +++ b/chips/stm32f303xc/src/exti.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use cortexm4::support::atomic; use enum_primitive::cast::FromPrimitive; use enum_primitive::enum_from_primitive; diff --git a/chips/stm32f303xc/src/flash.rs b/chips/stm32f303xc/src/flash.rs index 59f308dec3..203eba2e7c 100644 --- a/chips/stm32f303xc/src/flash.rs +++ b/chips/stm32f303xc/src/flash.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Embedded Flash Memory Controller //! //! Used for reading, writing and erasing the flash and the option bytes. @@ -11,7 +15,7 @@ use core::cell::Cell; use core::ops::{Index, IndexMut}; -use kernel::deferred_call::DeferredCall; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; use kernel::hil; use kernel::utilities::cells::OptionalCell; use kernel::utilities::cells::TakeCell; @@ -22,8 +26,6 @@ use kernel::utilities::registers::{ReadOnly, ReadWrite, WriteOnly}; use kernel::utilities::StaticRef; use kernel::ErrorCode; -use crate::deferred_call_tasks::DeferredCallTask; - const FLASH_BASE: StaticRef = unsafe { StaticRef::new(0x40022000 as *const FlashRegisters) }; @@ -207,9 +209,6 @@ register_bitfields! [u32, ] ]; -static DEFERRED_CALL: DeferredCall = - unsafe { DeferredCall::new(DeferredCallTask::Flash) }; - const PAGE_SIZE: usize = 2048; /// Address of the first flash page. @@ -235,13 +234,11 @@ const KEY2: u32 = 0xCDEF89AB; /// /// let pagebuffer = unsafe { static_init!(StmF303Page, StmF303Page::default()) }; /// ``` -pub struct StmF303Page(pub [u8; PAGE_SIZE as usize]); +pub struct StmF303Page(pub [u8; PAGE_SIZE]); impl Default for StmF303Page { fn default() -> Self { - Self { - 0: [0; PAGE_SIZE as usize], - } + Self([0; PAGE_SIZE]) } } @@ -288,10 +285,11 @@ pub struct Flash { state: Cell, write_counter: Cell, page_number: Cell, + deferred_call: DeferredCall, } impl Flash { - pub const fn new() -> Flash { + pub fn new() -> Flash { Flash { registers: FLASH_BASE, client: OptionalCell::empty(), @@ -299,6 +297,7 @@ impl Flash { state: Cell::new(FlashState::Ready), write_counter: Cell::new(0), page_number: Cell::new(0), + deferred_call: DeferredCall::new(), } } @@ -351,7 +350,7 @@ impl Flash { self.client.map(|client| { self.buffer.take().map(|buffer| { - client.write_complete(buffer, hil::flash::Error::CommandComplete); + client.write_complete(buffer, Ok(())); }); }); } else { @@ -369,7 +368,7 @@ impl Flash { self.state.set(FlashState::Ready); self.client.map(|client| { - client.erase_complete(hil::flash::Error::CommandComplete); + client.erase_complete(Ok(())); }); } FlashState::WriteOption => { @@ -378,7 +377,7 @@ impl Flash { self.client.map(|client| { self.buffer.take().map(|buffer| { - client.write_complete(buffer, hil::flash::Error::CommandComplete); + client.write_complete(buffer, Ok(())); }); }); } @@ -387,7 +386,7 @@ impl Flash { self.state.set(FlashState::Ready); self.client.map(|client| { - client.erase_complete(hil::flash::Error::CommandComplete); + client.erase_complete(Ok(())); }); } _ => {} @@ -398,7 +397,7 @@ impl Flash { self.state.set(FlashState::Ready); self.client.map(|client| { self.buffer.take().map(|buffer| { - client.read_complete(buffer, hil::flash::Error::CommandComplete); + client.read_complete(buffer, Ok(())); }); }); } @@ -412,13 +411,13 @@ impl Flash { self.registers.cr.modify(Control::PG::CLEAR); self.client.map(|client| { self.buffer.take().map(|buffer| { - client.write_complete(buffer, hil::flash::Error::FlashError); + client.write_complete(buffer, Err(hil::flash::Error::FlashError)); }); }); } FlashState::Erase => { self.client.map(|client| { - client.erase_complete(hil::flash::Error::FlashError); + client.erase_complete(Err(hil::flash::Error::FlashError)); }); } _ => {} @@ -436,7 +435,7 @@ impl Flash { self.registers.cr.modify(Control::PG::CLEAR); self.client.map(|client| { self.buffer.take().map(|buffer| { - client.write_complete(buffer, hil::flash::Error::FlashError); + client.write_complete(buffer, Err(hil::flash::Error::FlashError)); }); }); } @@ -444,13 +443,13 @@ impl Flash { self.registers.cr.modify(Control::OPTPG::CLEAR); self.client.map(|client| { self.buffer.take().map(|buffer| { - client.write_complete(buffer, hil::flash::Error::FlashError); + client.write_complete(buffer, Err(hil::flash::Error::FlashError)); }); }); } FlashState::Erase => { self.client.map(|client| { - client.erase_complete(hil::flash::Error::FlashError); + client.erase_complete(Err(hil::flash::Error::FlashError)); }); } _ => {} @@ -556,7 +555,7 @@ impl Flash { self.buffer.replace(buffer); self.state.set(FlashState::Read); - DEFERRED_CALL.set(); + self.deferred_call.set(); Ok(()) } @@ -604,6 +603,16 @@ impl Flash { } } +impl DeferredCallClient for Flash { + fn register(&'static self) { + self.deferred_call.register(self); + } + + fn handle_deferred_call(&self) { + self.handle_interrupt(); + } +} + impl> hil::flash::HasClient<'static, C> for Flash { fn set_client(&self, client: &'static C) { self.client.set(client); diff --git a/chips/stm32f303xc/src/gpio.rs b/chips/stm32f303xc/src/gpio.rs index d5d43f9aae..0fd76d07ef 100644 --- a/chips/stm32f303xc/src/gpio.rs +++ b/chips/stm32f303xc/src/gpio.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + // use cortexm4; use cortexm4::support::atomic; use enum_primitive::cast::FromPrimitive; @@ -492,7 +496,7 @@ impl PinId { pub fn get_pin_number(&self) -> u8 { let mut pin_num = *self as u8; - pin_num = pin_num & 0b00001111; + pin_num &= 0b00001111; pin_num } @@ -827,9 +831,9 @@ impl<'a> Pin<'a> { } pub unsafe fn enable_interrupt(&'static self) { - let exti_line_id = LineId::from_u8(self.pinid.get_pin_number() as u8).unwrap(); + let exti_line_id = LineId::from_u8(self.pinid.get_pin_number()).unwrap(); - self.exti.associate_line_gpiopin(exti_line_id, &self); + self.exti.associate_line_gpiopin(exti_line_id, self); } pub fn set_exti_lineid(&self, lineid: exti::LineId) { @@ -1114,7 +1118,7 @@ impl<'a> hil::gpio::Interrupt<'a> for Pin<'a> { unsafe { atomic(|| { self.exti_lineid.map(|lineid| { - let l = lineid.clone(); + let l = lineid; // disable the interrupt self.exti.mask_interrupt(l); @@ -1145,7 +1149,7 @@ impl<'a> hil::gpio::Interrupt<'a> for Pin<'a> { unsafe { atomic(|| { self.exti_lineid.map(|lineid| { - let l = lineid.clone(); + let l = lineid; self.exti.mask_interrupt(l); self.exti.clear_pending(l); }); @@ -1159,6 +1163,6 @@ impl<'a> hil::gpio::Interrupt<'a> for Pin<'a> { fn is_pending(&self) -> bool { self.exti_lineid - .map_or(false, |&mut lineid| self.exti.is_pending(lineid)) + .map_or(false, |lineid| self.exti.is_pending(lineid)) } } diff --git a/chips/stm32f303xc/src/i2c.rs b/chips/stm32f303xc/src/i2c.rs index 6b4c246c67..dcaebf3883 100644 --- a/chips/stm32f303xc/src/i2c.rs +++ b/chips/stm32f303xc/src/i2c.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::cell::Cell; use kernel::hil; @@ -238,10 +242,10 @@ pub struct I2C<'a> { master_client: OptionalCell<&'a dyn hil::i2c::I2CHwMasterClient>, buffer: TakeCell<'static, [u8]>, - tx_position: Cell, - rx_position: Cell, - tx_len: Cell, - rx_len: Cell, + tx_position: Cell, + rx_position: Cell, + tx_len: Cell, + rx_len: Cell, slave_address: Cell, @@ -258,7 +262,7 @@ enum I2CStatus { } impl<'a> I2C<'a> { - const fn new(base_addr: StaticRef, clock: I2CClock<'a>) -> Self { + fn new(base_addr: StaticRef, clock: I2CClock<'a>) -> Self { Self { registers: base_addr, clock, @@ -278,7 +282,7 @@ impl<'a> I2C<'a> { } } - pub const fn new_i2c1(rcc: &'a rcc::Rcc) -> Self { + pub fn new_i2c1(rcc: &'a rcc::Rcc) -> Self { Self::new( I2C1_BASE, I2CClock(rcc::PeripheralClock::new( @@ -335,7 +339,7 @@ impl<'a> I2C<'a> { // send the next byte if self.buffer.is_some() && self.tx_position.get() < self.tx_len.get() { self.buffer.map(|buf| { - let byte = buf[self.tx_position.get() as usize]; + let byte = buf[self.tx_position.get()]; self.registers.txdr.write(TXDR::TXDATA.val(byte as u32)); self.tx_position.set(self.tx_position.get() + 1); }); @@ -349,7 +353,7 @@ impl<'a> I2C<'a> { let byte = self.registers.rxdr.read(RXDR::RXDATA) as u8; if self.buffer.is_some() && self.rx_position.get() < self.rx_len.get() { self.buffer.map(|buf| { - buf[self.rx_position.get() as usize] = byte; + buf[self.rx_position.get()] = byte; self.rx_position.set(self.rx_position.get() + 1); }); } @@ -471,8 +475,8 @@ impl<'a> I2C<'a> { } } -impl i2c::I2CMaster for I2C<'_> { - fn set_master_client(&self, master_client: &'static dyn I2CHwMasterClient) { +impl<'a> i2c::I2CMaster<'a> for I2C<'a> { + fn set_master_client(&self, master_client: &'a dyn I2CHwMasterClient) { self.master_client.replace(master_client); } fn enable(&self) { @@ -485,8 +489,8 @@ impl i2c::I2CMaster for I2C<'_> { &self, addr: u8, data: &'static mut [u8], - write_len: u8, - read_len: u8, + write_len: usize, + read_len: usize, ) -> Result<(), (Error, &'static mut [u8])> { if self.status.get() == I2CStatus::Idle { self.reset(); @@ -506,7 +510,7 @@ impl i2c::I2CMaster for I2C<'_> { &self, addr: u8, data: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (Error, &'static mut [u8])> { if self.status.get() == I2CStatus::Idle { self.reset(); @@ -525,7 +529,7 @@ impl i2c::I2CMaster for I2C<'_> { &self, addr: u8, buffer: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (Error, &'static mut [u8])> { if self.status.get() == I2CStatus::Idle { self.reset(); diff --git a/chips/stm32f303xc/src/lib.rs b/chips/stm32f303xc/src/lib.rs index 1a0aea0b68..511cca885f 100644 --- a/chips/stm32f303xc/src/lib.rs +++ b/chips/stm32f303xc/src/lib.rs @@ -1,14 +1,16 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Peripheral implementations for the STM32F3xx MCU. //! //! STM32F303: #![crate_name = "stm32f303xc"] #![crate_type = "rlib"] -#![feature(const_fn_trait_bound)] #![no_std] pub mod chip; -mod deferred_call_tasks; pub mod nvic; // Peripherals @@ -25,10 +27,7 @@ pub mod tim2; pub mod usart; pub mod wdt; -use cortexm4::{ - generic_isr, hard_fault_handler, initialize_ram_jump_to_main, svc_handler, systick_handler, - unhandled_interrupt, -}; +use cortexm4::{initialize_ram_jump_to_main, unhandled_interrupt, CortexM4, CortexMVariant}; extern "C" { // _estack is not really a function, but it makes the types work @@ -45,20 +44,20 @@ extern "C" { pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ _estack, initialize_ram_jump_to_main, - unhandled_interrupt, // NMI - hard_fault_handler, // Hard Fault - unhandled_interrupt, // MemManage - unhandled_interrupt, // BusFault - unhandled_interrupt, // UsageFault + unhandled_interrupt, // NMI + CortexM4::HARD_FAULT_HANDLER, // Hard Fault + unhandled_interrupt, // MemManage + unhandled_interrupt, // BusFault + unhandled_interrupt, // UsageFault unhandled_interrupt, unhandled_interrupt, unhandled_interrupt, unhandled_interrupt, - svc_handler, // SVC - unhandled_interrupt, // DebugMon + CortexM4::SVC_HANDLER, // SVC + unhandled_interrupt, // DebugMon unhandled_interrupt, - unhandled_interrupt, // PendSV - systick_handler, // SysTick + unhandled_interrupt, // PendSV + CortexM4::SYSTICK_HANDLER, // SysTick ]; // STM32F303VCT6 has total of 82 interrupts @@ -68,88 +67,88 @@ pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ // used Ensures that the symbol is kept until the final binary #[cfg_attr(all(target_arch = "arm", target_os = "none"), used)] pub static IRQS: [unsafe extern "C" fn(); 82] = [ - generic_isr, // WWDG (0) - generic_isr, // PVD (1) - generic_isr, // TAMP_STAMP (2) - generic_isr, // RTC_WKUP (3) - generic_isr, // FLASH (4) - generic_isr, // RCC (5) - generic_isr, // EXTI0 (6) - generic_isr, // EXTI1 (7) - generic_isr, // EXTI2 (8) - generic_isr, // EXTI3 (9) - generic_isr, // EXTI4 (10) - generic_isr, // DMA1_Stream0 (11) - generic_isr, // DMA1_Stream1 (12) - generic_isr, // DMA1_Stream2 (13) - generic_isr, // DMA1_Stream3 (14) - generic_isr, // DMA1_Stream4 (15) - generic_isr, // DMA1_Stream5 (16) - generic_isr, // DMA1_Stream6 (17) - generic_isr, // ADC1_2 (18) - generic_isr, // HP_USB or CAN1_TX (19) - generic_isr, // LP_USB or CAN1_RX0 (20) - generic_isr, // CAN1_RX1 (21) - generic_isr, // CAN1_SCE (22) - generic_isr, // EXTI9_5 (23) - generic_isr, // TIM1_BRK_TIM9 (24) - generic_isr, // TIM1_UP_TIM10 (25) - generic_isr, // TIM1_TRG_COM_TIM11 (26) - generic_isr, // TIM1_CC (27) - generic_isr, // TIM2 (28) - generic_isr, // TIM3 (29) - generic_isr, // TIM4 (30) - generic_isr, // I2C1_EV (31) - generic_isr, // I2C1_ER (32) - generic_isr, // I2C2_EV (33) - generic_isr, // I2C2_ER (34) - generic_isr, // SPI1 (35) - generic_isr, // SPI2 (36) - generic_isr, // USART1 (37) - generic_isr, // USART2 (38) - generic_isr, // USART3 (39) - generic_isr, // EXTI15_10 (40) - generic_isr, // RTC_Alarm (41) - generic_isr, // USB_WKUP (42) - generic_isr, // TIM8_BRK_TIM12 (43) - generic_isr, // TIM8_UP_TIM13 (44) - generic_isr, // TIM8_TRG_COM_TIM14 (45) - generic_isr, // TIM8_CC (46) - generic_isr, // ADC3 (47) - unhandled_interrupt, // (48) - unhandled_interrupt, // (49) - unhandled_interrupt, // (50) - generic_isr, // SPI3 (51) - generic_isr, // UART4 (52) - generic_isr, // UART5 (53) - generic_isr, // TIM6_DAC (54) - generic_isr, // TIM7 (55) - generic_isr, // DMA2_Stream0 (56) - generic_isr, // DMA2_Stream1 (57) - generic_isr, // DMA2_Stream2 (58) - generic_isr, // DMA2_Stream3 (59) - generic_isr, // DMA2_Stream4 (60) - generic_isr, // ADC4 (61) - unhandled_interrupt, // (62) - unhandled_interrupt, // (63) - generic_isr, // COMP1_2_3 (64) - generic_isr, // COMP4_5_6 (65) - generic_isr, // COMP7 (66) - unhandled_interrupt, //(67) - unhandled_interrupt, //(68) - unhandled_interrupt, //(69) - unhandled_interrupt, //(70) - unhandled_interrupt, //(71) - unhandled_interrupt, //(72) - unhandled_interrupt, //(73) - generic_isr, // USB_HP (74) - generic_isr, // USB_LP (75) - generic_isr, // USB_RMP_WKUP (76) - unhandled_interrupt, // (77) - unhandled_interrupt, // (78) - unhandled_interrupt, // (79) - unhandled_interrupt, // (80) - generic_isr, // FPU (81) + CortexM4::GENERIC_ISR, // WWDG (0) + CortexM4::GENERIC_ISR, // PVD (1) + CortexM4::GENERIC_ISR, // TAMP_STAMP (2) + CortexM4::GENERIC_ISR, // RTC_WKUP (3) + CortexM4::GENERIC_ISR, // FLASH (4) + CortexM4::GENERIC_ISR, // RCC (5) + CortexM4::GENERIC_ISR, // EXTI0 (6) + CortexM4::GENERIC_ISR, // EXTI1 (7) + CortexM4::GENERIC_ISR, // EXTI2 (8) + CortexM4::GENERIC_ISR, // EXTI3 (9) + CortexM4::GENERIC_ISR, // EXTI4 (10) + CortexM4::GENERIC_ISR, // DMA1_Stream0 (11) + CortexM4::GENERIC_ISR, // DMA1_Stream1 (12) + CortexM4::GENERIC_ISR, // DMA1_Stream2 (13) + CortexM4::GENERIC_ISR, // DMA1_Stream3 (14) + CortexM4::GENERIC_ISR, // DMA1_Stream4 (15) + CortexM4::GENERIC_ISR, // DMA1_Stream5 (16) + CortexM4::GENERIC_ISR, // DMA1_Stream6 (17) + CortexM4::GENERIC_ISR, // ADC1_2 (18) + CortexM4::GENERIC_ISR, // HP_USB or CAN1_TX (19) + CortexM4::GENERIC_ISR, // LP_USB or CAN1_RX0 (20) + CortexM4::GENERIC_ISR, // CAN1_RX1 (21) + CortexM4::GENERIC_ISR, // CAN1_SCE (22) + CortexM4::GENERIC_ISR, // EXTI9_5 (23) + CortexM4::GENERIC_ISR, // TIM1_BRK_TIM9 (24) + CortexM4::GENERIC_ISR, // TIM1_UP_TIM10 (25) + CortexM4::GENERIC_ISR, // TIM1_TRG_COM_TIM11 (26) + CortexM4::GENERIC_ISR, // TIM1_CC (27) + CortexM4::GENERIC_ISR, // TIM2 (28) + CortexM4::GENERIC_ISR, // TIM3 (29) + CortexM4::GENERIC_ISR, // TIM4 (30) + CortexM4::GENERIC_ISR, // I2C1_EV (31) + CortexM4::GENERIC_ISR, // I2C1_ER (32) + CortexM4::GENERIC_ISR, // I2C2_EV (33) + CortexM4::GENERIC_ISR, // I2C2_ER (34) + CortexM4::GENERIC_ISR, // SPI1 (35) + CortexM4::GENERIC_ISR, // SPI2 (36) + CortexM4::GENERIC_ISR, // USART1 (37) + CortexM4::GENERIC_ISR, // USART2 (38) + CortexM4::GENERIC_ISR, // USART3 (39) + CortexM4::GENERIC_ISR, // EXTI15_10 (40) + CortexM4::GENERIC_ISR, // RTC_Alarm (41) + CortexM4::GENERIC_ISR, // USB_WKUP (42) + CortexM4::GENERIC_ISR, // TIM8_BRK_TIM12 (43) + CortexM4::GENERIC_ISR, // TIM8_UP_TIM13 (44) + CortexM4::GENERIC_ISR, // TIM8_TRG_COM_TIM14 (45) + CortexM4::GENERIC_ISR, // TIM8_CC (46) + CortexM4::GENERIC_ISR, // ADC3 (47) + unhandled_interrupt, // (48) + unhandled_interrupt, // (49) + unhandled_interrupt, // (50) + CortexM4::GENERIC_ISR, // SPI3 (51) + CortexM4::GENERIC_ISR, // UART4 (52) + CortexM4::GENERIC_ISR, // UART5 (53) + CortexM4::GENERIC_ISR, // TIM6_DAC (54) + CortexM4::GENERIC_ISR, // TIM7 (55) + CortexM4::GENERIC_ISR, // DMA2_Stream0 (56) + CortexM4::GENERIC_ISR, // DMA2_Stream1 (57) + CortexM4::GENERIC_ISR, // DMA2_Stream2 (58) + CortexM4::GENERIC_ISR, // DMA2_Stream3 (59) + CortexM4::GENERIC_ISR, // DMA2_Stream4 (60) + CortexM4::GENERIC_ISR, // ADC4 (61) + unhandled_interrupt, // (62) + unhandled_interrupt, // (63) + CortexM4::GENERIC_ISR, // COMP1_2_3 (64) + CortexM4::GENERIC_ISR, // COMP4_5_6 (65) + CortexM4::GENERIC_ISR, // COMP7 (66) + unhandled_interrupt, //(67) + unhandled_interrupt, //(68) + unhandled_interrupt, //(69) + unhandled_interrupt, //(70) + unhandled_interrupt, //(71) + unhandled_interrupt, //(72) + unhandled_interrupt, //(73) + CortexM4::GENERIC_ISR, // USB_HP (74) + CortexM4::GENERIC_ISR, // USB_LP (75) + CortexM4::GENERIC_ISR, // USB_RMP_WKUP (76) + unhandled_interrupt, // (77) + unhandled_interrupt, // (78) + unhandled_interrupt, // (79) + unhandled_interrupt, // (80) + CortexM4::GENERIC_ISR, // FPU (81) ]; pub unsafe fn init() { diff --git a/chips/stm32f303xc/src/nvic.rs b/chips/stm32f303xc/src/nvic.rs index 3da4785c4e..82d362e857 100644 --- a/chips/stm32f303xc/src/nvic.rs +++ b/chips/stm32f303xc/src/nvic.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Named constants for NVIC ids #![allow(non_upper_case_globals)] diff --git a/chips/stm32f303xc/src/rcc.rs b/chips/stm32f303xc/src/rcc.rs index 23748bc113..51a17c70a6 100644 --- a/chips/stm32f303xc/src/rcc.rs +++ b/chips/stm32f303xc/src/rcc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::platform::chip::ClockInterface; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; use kernel::utilities::registers::{register_bitfields, ReadWrite}; diff --git a/chips/stm32f303xc/src/spi.rs b/chips/stm32f303xc/src/spi.rs index 082a561d0c..e3b95d58b7 100644 --- a/chips/stm32f303xc/src/spi.rs +++ b/chips/stm32f303xc/src/spi.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::cell::Cell; use core::cmp; use kernel::ErrorCode; @@ -203,7 +207,7 @@ pub struct Spi<'a> { } impl<'a> Spi<'a> { - const fn new(base_addr: StaticRef, clock: SpiClock<'a>) -> Self { + fn new(base_addr: StaticRef, clock: SpiClock<'a>) -> Self { Self { registers: base_addr, clock, @@ -225,7 +229,7 @@ impl<'a> Spi<'a> { } } - pub const fn new_spi1(rcc: &'a rcc::Rcc) -> Self { + pub fn new_spi1(rcc: &'a rcc::Rcc) -> Self { Self::new( SPI1_BASE, SpiClock(rcc::PeripheralClock::new( @@ -265,7 +269,7 @@ impl<'a> Spi<'a> { if self.registers.sr.is_set(SR::RXNE) { while self.registers.sr.read(SR::FRLVL) > 0 { - let byte = self.registers.dr.read(DR::DR) as u8; + let byte = self.registers.dr.read(DR::DR); if self.rx_buffer.is_some() && self.rx_position.get() < self.len.get() { self.rx_buffer.map(|buf| { buf[self.rx_position.get()] = byte; @@ -411,10 +415,10 @@ impl<'a> Spi<'a> { } } -impl<'a> spi::SpiMaster for Spi<'a> { +impl<'a> spi::SpiMaster<'a> for Spi<'a> { type ChipSelect = &'a crate::gpio::Pin<'a>; - fn set_client(&self, client: &'static dyn SpiMasterClient) { + fn set_client(&self, client: &'a dyn SpiMasterClient) { self.master_client.set(client); } @@ -422,23 +426,18 @@ impl<'a> spi::SpiMaster for Spi<'a> { // enable error interrupt (used only for debugging) // self.registers.cr2.modify(CR2::ERRIE::SET); - self.registers.cr2.modify( - // Set 8 bit mode - CR2::DS.val (0b0111)+ - // Set FIFO level at 1/4 - CR2::FRXTH::SET, - ); + // Set 8 bit mode + // Set FIFO level at 1/4 + self.registers + .cr2 + .modify(CR2::DS.val(0b0111) + CR2::FRXTH::SET); + // 2 line unidirectional mode + // Select as master + // Software slave management + // Enable self.registers.cr1.modify( - // 2 line unidirectional mode - CR1::BIDIMODE::CLEAR + - // Select as master - CR1::MSTR::SET + - // Software slave management - CR1::SSM::SET + - CR1::SSI::SET + - // Enable - CR1::SPE::SET, + CR1::BIDIMODE::CLEAR + CR1::MSTR::SET + CR1::SSM::SET + CR1::SSI::SET + CR1::SPE::SET, ); Ok(()) } @@ -464,7 +463,7 @@ impl<'a> spi::SpiMaster for Spi<'a> { self.write_byte(val)?; // loop till RXNE becomes 1 while !self.registers.sr.is_set(SR::RXNE) {} - Ok(self.registers.dr.read(DR::DR) as u8) + Ok(self.registers.dr.read(DR::DR)) } fn read_write_bytes( diff --git a/chips/stm32f303xc/src/syscfg.rs b/chips/stm32f303xc/src/syscfg.rs index 123f178c98..668d9189c1 100644 --- a/chips/stm32f303xc/src/syscfg.rs +++ b/chips/stm32f303xc/src/syscfg.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use enum_primitive::cast::FromPrimitive; use enum_primitive::enum_from_primitive; use kernel::platform::chip::ClockInterface; diff --git a/chips/stm32f303xc/src/tim2.rs b/chips/stm32f303xc/src/tim2.rs index 449ffd5ef8..8e00e2fa3c 100644 --- a/chips/stm32f303xc/src/tim2.rs +++ b/chips/stm32f303xc/src/tim2.rs @@ -1,4 +1,7 @@ -use cortexm4; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use cortexm4::support::atomic; use kernel::hil::time::{ Alarm, AlarmClient, Counter, Freq16KHz, OverflowClient, Ticks, Ticks32, Time, diff --git a/chips/stm32f303xc/src/usart.rs b/chips/stm32f303xc/src/usart.rs index 7e41fac9b2..8b4fe751bb 100644 --- a/chips/stm32f303xc/src/usart.rs +++ b/chips/stm32f303xc/src/usart.rs @@ -1,5 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + // use core::cell::Cell; use core::cell::Cell; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; use kernel::hil; use kernel::platform::chip::ClockInterface; use kernel::utilities::cells::{OptionalCell, TakeCell}; @@ -305,13 +310,15 @@ pub struct Usart<'a> { rx_position: Cell, rx_len: Cell, rx_status: Cell, + + deferred_call: DeferredCall, } impl<'a> Usart<'a> { - const fn new(base_addr: StaticRef, clock: UsartClock<'a>) -> Self { + fn new(base_addr: StaticRef, clock: UsartClock<'a>) -> Self { Self { registers: base_addr, - clock: clock, + clock, tx_client: OptionalCell::empty(), rx_client: OptionalCell::empty(), @@ -325,10 +332,12 @@ impl<'a> Usart<'a> { rx_position: Cell::new(0), rx_len: Cell::new(0), rx_status: Cell::new(USARTStateRX::Idle), + + deferred_call: DeferredCall::new(), } } - pub const fn new_usart1(rcc: &'a rcc::Rcc) -> Self { + pub fn new_usart1(rcc: &'a rcc::Rcc) -> Self { Self::new( USART1_BASE, UsartClock(rcc::PeripheralClock::new( @@ -338,7 +347,7 @@ impl<'a> Usart<'a> { ) } - pub const fn new_usart2(rcc: &'a rcc::Rcc) -> Self { + pub fn new_usart2(rcc: &'a rcc::Rcc) -> Self { Self::new( USART2_BASE, UsartClock(rcc::PeripheralClock::new( @@ -348,7 +357,7 @@ impl<'a> Usart<'a> { ) } - pub const fn new_usart3(rcc: &'a rcc::Rcc) -> Self { + pub fn new_usart3(rcc: &'a rcc::Rcc) -> Self { Self::new( USART3_BASE, UsartClock(rcc::PeripheralClock::new( @@ -423,17 +432,6 @@ impl<'a> Usart<'a> { } }); } - } else if self.tx_status.get() == USARTStateTX::AbortRequested { - self.tx_status.replace(USARTStateTX::Idle); - self.tx_client.map(|client| { - if let Some(buf) = self.tx_buffer.take() { - client.transmitted_buffer( - buf, - self.tx_position.get(), - Err(ErrorCode::CANCEL), - ); - } - }); } } @@ -468,18 +466,6 @@ impl<'a> Usart<'a> { } }); } - } else if self.rx_status.get() == USARTStateRX::AbortRequested { - self.rx_status.replace(USARTStateRX::Idle); - self.rx_client.map(|client| { - if let Some(buf) = self.rx_buffer.take() { - client.received_buffer( - buf, - self.rx_position.get(), - Err(ErrorCode::CANCEL), - hil::uart::Error::Aborted, - ); - } - }); } } @@ -500,6 +486,39 @@ impl<'a> Usart<'a> { } } +impl DeferredCallClient for Usart<'_> { + fn register(&'static self) { + self.deferred_call.register(self); + } + + fn handle_deferred_call(&self) { + if self.tx_status.get() == USARTStateTX::AbortRequested { + // alert client + self.tx_client.map(|client| { + self.tx_buffer.take().map(|buf| { + client.transmitted_buffer(buf, self.tx_position.get(), Err(ErrorCode::CANCEL)); + }); + }); + self.tx_status.set(USARTStateTX::Idle); + } + + if self.rx_status.get() == USARTStateRX::AbortRequested { + // alert client + self.rx_client.map(|client| { + self.rx_buffer.take().map(|buf| { + client.received_buffer( + buf, + self.rx_position.get(), + Err(ErrorCode::CANCEL), + hil::uart::Error::Aborted, + ); + }); + }); + self.rx_status.set(USARTStateRX::Idle); + } + } +} + impl<'a> hil::uart::Transmit<'a> for Usart<'a> { fn set_transmit_client(&self, client: &'a dyn hil::uart::TransmitClient) { self.tx_client.set(client); @@ -532,7 +551,11 @@ impl<'a> hil::uart::Transmit<'a> for Usart<'a> { fn transmit_abort(&self) -> Result<(), ErrorCode> { if self.tx_status.get() != USARTStateTX::Idle { + self.disable_transmit_interrupt(); self.tx_status.set(USARTStateTX::AbortRequested); + + self.deferred_call.set(); + Err(ErrorCode::BUSY) } else { Ok(()) @@ -545,7 +568,7 @@ impl hil::uart::Configure for Usart<'_> { if params.baud_rate != 115200 || params.stop_bits != hil::uart::StopBits::One || params.parity != hil::uart::Parity::None - || params.hw_flow_control != false + || params.hw_flow_control || params.width != hil::uart::Width::Eight { panic!( @@ -558,7 +581,7 @@ impl hil::uart::Configure for Usart<'_> { self.registers.cr1.modify(CR1::M1::CLEAR); // Set the stop bit length - 00: 1 Stop bits - self.registers.cr2.modify(CR2::STOP.val(0b00 as u32)); + self.registers.cr2.modify(CR2::STOP.val(0b00_u32)); // Set no parity self.registers.cr1.modify(CR1::PCE::CLEAR); @@ -568,8 +591,8 @@ impl hil::uart::Configure for Usart<'_> { // to Table 159 of reference manual, the value for BRR is 69.444 (0x45) // DIV_Fraction = 0x5 // DIV_Mantissa = 0x4 - self.registers.brr.modify(BRR::DIV_Fraction.val(0x5 as u32)); - self.registers.brr.modify(BRR::DIV_Mantissa.val(0x4 as u32)); + self.registers.brr.modify(BRR::DIV_Fraction.val(0x5_u32)); + self.registers.brr.modify(BRR::DIV_Mantissa.val(0x4_u32)); // Enable transmit block self.registers.cr1.modify(CR1::TE::SET); @@ -616,7 +639,11 @@ impl<'a> hil::uart::Receive<'a> for Usart<'a> { fn receive_abort(&self) -> Result<(), ErrorCode> { if self.rx_status.get() != USARTStateRX::Idle { + self.disable_receive_interrupt(); self.rx_status.set(USARTStateRX::AbortRequested); + + self.deferred_call.set(); + Err(ErrorCode::BUSY) } else { Ok(()) diff --git a/chips/stm32f303xc/src/wdt.rs b/chips/stm32f303xc/src/wdt.rs index 34a89862ac..74680d3ffa 100644 --- a/chips/stm32f303xc/src/wdt.rs +++ b/chips/stm32f303xc/src/wdt.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Window watchdog timer use crate::rcc; diff --git a/chips/stm32f401cc/Cargo.toml b/chips/stm32f401cc/Cargo.toml index 2ccc1b61d6..1abcec71c6 100644 --- a/chips/stm32f401cc/Cargo.toml +++ b/chips/stm32f401cc/Cargo.toml @@ -1,11 +1,18 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "stm32f401cc" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] cortexm4 = { path = "../../arch/cortex-m4" } kernel = { path = "../../kernel" } -stm32f4xx = { path = "../stm32f4xx" } +stm32f4xx = { path = "../stm32f4xx", features = ["stm32f401"] } enum_primitive = { path = "../../libraries/enum_primitive" } + +[lints] +workspace = true diff --git a/chips/stm32f401cc/src/chip_specs.rs b/chips/stm32f401cc/src/chip_specs.rs new file mode 100644 index 0000000000..e1c63bf03e --- /dev/null +++ b/chips/stm32f401cc/src/chip_specs.rs @@ -0,0 +1,36 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive SRL. +// +// Author: Ioan-Cristian CÎRSTEA + +//! STM32F401 specifications + +use stm32f4xx::chip_specific::clock_constants::{PllConstants, SystemClockConstants}; +use stm32f4xx::chip_specific::flash::{FlashChipSpecific, FlashLatency16}; + +pub enum Stm32f401Specs {} + +impl PllConstants for Stm32f401Specs { + const MIN_FREQ_MHZ: usize = 24; +} + +impl SystemClockConstants for Stm32f401Specs { + const APB1_FREQUENCY_LIMIT_MHZ: usize = 42; + const SYS_CLOCK_FREQUENCY_LIMIT_MHZ: usize = 84; +} + +impl FlashChipSpecific for Stm32f401Specs { + type FlashLatency = FlashLatency16; + + fn get_number_wait_cycles_based_on_frequency(frequency_mhz: usize) -> Self::FlashLatency { + match frequency_mhz { + 0..=30 => Self::FlashLatency::Latency0, + 31..=60 => Self::FlashLatency::Latency1, + 61..=90 => Self::FlashLatency::Latency2, + 91..=120 => Self::FlashLatency::Latency3, + 121..=150 => Self::FlashLatency::Latency4, + _ => Self::FlashLatency::Latency5, + } + } +} diff --git a/chips/stm32f401cc/src/interrupt_service.rs b/chips/stm32f401cc/src/interrupt_service.rs index 68c170f947..e84bd8e884 100644 --- a/chips/stm32f401cc/src/interrupt_service.rs +++ b/chips/stm32f401cc/src/interrupt_service.rs @@ -1,36 +1,37 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::chip_specs::Stm32f401Specs; use stm32f4xx::chip::Stm32f4xxDefaultPeripherals; -use stm32f4xx::deferred_calls::DeferredCallTask; pub struct Stm32f401ccDefaultPeripherals<'a> { - pub stm32f4: Stm32f4xxDefaultPeripherals<'a>, + pub stm32f4: Stm32f4xxDefaultPeripherals<'a, Stm32f401Specs>, // Once implemented, place Stm32f401cc specific peripherals here } impl<'a> Stm32f401ccDefaultPeripherals<'a> { pub unsafe fn new( - rcc: &'a crate::rcc::Rcc, + clocks: &'a crate::clocks::Clocks<'a, Stm32f401Specs>, exti: &'a crate::exti::Exti<'a>, - dma: &'a crate::dma1::Dma1<'a>, + dma1: &'a crate::dma::Dma1<'a>, + dma2: &'a crate::dma::Dma2<'a>, ) -> Self { Self { - stm32f4: Stm32f4xxDefaultPeripherals::new(rcc, exti, dma), + stm32f4: Stm32f4xxDefaultPeripherals::new(clocks, exti, dma1, dma2), } } - // Necessary for setting up circular dependencies - pub fn init(&'a self) { + + // Necessary for setting up circular dependencies & registering deferred calls + pub fn init(&'static self) { self.stm32f4.setup_circular_deps(); } } -impl<'a> kernel::platform::chip::InterruptService - for Stm32f401ccDefaultPeripherals<'a> -{ +impl<'a> kernel::platform::chip::InterruptService for Stm32f401ccDefaultPeripherals<'a> { unsafe fn service_interrupt(&self, interrupt: u32) -> bool { match interrupt { // put Stm32f401cc specific interrupts here _ => self.stm32f4.service_interrupt(interrupt), } } - unsafe fn service_deferred_call(&self, task: DeferredCallTask) -> bool { - self.stm32f4.service_deferred_call(task) - } } diff --git a/chips/stm32f401cc/src/lib.rs b/chips/stm32f401cc/src/lib.rs index a61ae875d6..682cf88827 100644 --- a/chips/stm32f401cc/src/lib.rs +++ b/chips/stm32f401cc/src/lib.rs @@ -1,9 +1,16 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + #![no_std] -use cortexm4::{generic_isr, unhandled_interrupt}; +use cortexm4::{unhandled_interrupt, CortexM4, CortexMVariant}; -pub use stm32f4xx::{adc, chip, dbg, dma1, exti, gpio, nvic, rcc, spi, syscfg, tim2, usart}; +pub use stm32f4xx::{ + adc, chip, clocks, dbg, dma, exti, flash, gpio, nvic, rcc, spi, syscfg, tim2, usart, +}; +pub mod chip_specs; pub mod interrupt_service; // Extracted from RM0368 Reference manual, Table 38 @@ -11,91 +18,91 @@ pub mod interrupt_service; // "used" ensures that the symbol is kept until the final binary #[cfg_attr(all(target_arch = "arm", target_os = "none"), used)] pub static IRQS: [unsafe extern "C" fn(); 85] = [ - generic_isr, // WWDG (0) - generic_isr, // PVD (1) - generic_isr, // TAMP_STAMP (2) - generic_isr, // RTC_WKUP (3) - generic_isr, // FLASH (4) - generic_isr, // RCC (5) - generic_isr, // EXTI0 (6) - generic_isr, // EXTI1 (7) - generic_isr, // EXTI2 (8) - generic_isr, // EXTI3 (9) - generic_isr, // EXTI4 (10) - generic_isr, // DMA1_Stream0 (11) - generic_isr, // DMA1_Stream1 (12) - generic_isr, // DMA1_Stream2 (13) - generic_isr, // DMA1_Stream3 (14) - generic_isr, // DMA1_Stream4 (15) - generic_isr, // DMA1_Stream5 (16) - generic_isr, // DMA1_Stream6 (17) - generic_isr, // ADC (18) - unhandled_interrupt, // (19) - unhandled_interrupt, // (20) - unhandled_interrupt, // (21) - unhandled_interrupt, // (22) - generic_isr, // EXTI9_5 (23) - generic_isr, // TIM1_BRK_TIM9 (24) - generic_isr, // TIM1_UP_TIM10 (25) - generic_isr, // TIM1_TRG_COM_TIM11 (26) - generic_isr, // TIM1_CC (27) - generic_isr, // TIM2 (28) - generic_isr, // TIM3 (29) - generic_isr, // TIM4 (30) - generic_isr, // I2C1_EV (31) - generic_isr, // I2C1_ER (32) - generic_isr, // I2C2_EV (33) - generic_isr, // I2C2_ER (34) - generic_isr, // SPI1 (35) - generic_isr, // SPI2 (36) - generic_isr, // USART1 (37) - generic_isr, // USART2 (38) - generic_isr, // USART3 (39) - generic_isr, // EXTI15_10 (40) - generic_isr, // RTC_Alarm (41) - generic_isr, // OTG_FS_WKUP (42) - unhandled_interrupt, // (43) - unhandled_interrupt, // (44) - unhandled_interrupt, // (45) - unhandled_interrupt, // (45) - generic_isr, // DMA1_Stream7 (47) - unhandled_interrupt, // (48) - generic_isr, // SDIO (49) - generic_isr, // TIM5 (50) - generic_isr, // SPI3 (51) - unhandled_interrupt, // (52) - unhandled_interrupt, // (53) - unhandled_interrupt, // (54) - unhandled_interrupt, // (55) - generic_isr, // DMA2_Stream0 (56) - generic_isr, // DMA2_Stream1 (57) - generic_isr, // DMA2_Stream2 (58) - generic_isr, // DMA2_Stream3 (59) - generic_isr, // DMA2_Stream4 (60) - unhandled_interrupt, // (61) - unhandled_interrupt, // (62) - unhandled_interrupt, // (63) - unhandled_interrupt, // (64) - unhandled_interrupt, // (65) - unhandled_interrupt, // (66) - generic_isr, // OTG_FS (67) - generic_isr, // DMA2_Stream5 (68) - generic_isr, // DMA2_Stream6 (69) - generic_isr, // DMA2_Stream7 (70) - generic_isr, // USART6 (71) - generic_isr, // I2C3_EV (72) - generic_isr, // I2C3_ER (73) - unhandled_interrupt, // (74) - unhandled_interrupt, // (75) - unhandled_interrupt, // (76) - unhandled_interrupt, // (77) - unhandled_interrupt, // (78) - unhandled_interrupt, // (79) - unhandled_interrupt, // (80) - generic_isr, // FPU (81) - unhandled_interrupt, // (82) - unhandled_interrupt, // (83) - generic_isr, // SPI4 (84) + CortexM4::GENERIC_ISR, // WWDG (0) + CortexM4::GENERIC_ISR, // PVD (1) + CortexM4::GENERIC_ISR, // TAMP_STAMP (2) + CortexM4::GENERIC_ISR, // RTC_WKUP (3) + CortexM4::GENERIC_ISR, // FLASH (4) + CortexM4::GENERIC_ISR, // RCC (5) + CortexM4::GENERIC_ISR, // EXTI0 (6) + CortexM4::GENERIC_ISR, // EXTI1 (7) + CortexM4::GENERIC_ISR, // EXTI2 (8) + CortexM4::GENERIC_ISR, // EXTI3 (9) + CortexM4::GENERIC_ISR, // EXTI4 (10) + CortexM4::GENERIC_ISR, // DMA1_Stream0 (11) + CortexM4::GENERIC_ISR, // DMA1_Stream1 (12) + CortexM4::GENERIC_ISR, // DMA1_Stream2 (13) + CortexM4::GENERIC_ISR, // DMA1_Stream3 (14) + CortexM4::GENERIC_ISR, // DMA1_Stream4 (15) + CortexM4::GENERIC_ISR, // DMA1_Stream5 (16) + CortexM4::GENERIC_ISR, // DMA1_Stream6 (17) + CortexM4::GENERIC_ISR, // ADC (18) + unhandled_interrupt, // (19) + unhandled_interrupt, // (20) + unhandled_interrupt, // (21) + unhandled_interrupt, // (22) + CortexM4::GENERIC_ISR, // EXTI9_5 (23) + CortexM4::GENERIC_ISR, // TIM1_BRK_TIM9 (24) + CortexM4::GENERIC_ISR, // TIM1_UP_TIM10 (25) + CortexM4::GENERIC_ISR, // TIM1_TRG_COM_TIM11 (26) + CortexM4::GENERIC_ISR, // TIM1_CC (27) + CortexM4::GENERIC_ISR, // TIM2 (28) + CortexM4::GENERIC_ISR, // TIM3 (29) + CortexM4::GENERIC_ISR, // TIM4 (30) + CortexM4::GENERIC_ISR, // I2C1_EV (31) + CortexM4::GENERIC_ISR, // I2C1_ER (32) + CortexM4::GENERIC_ISR, // I2C2_EV (33) + CortexM4::GENERIC_ISR, // I2C2_ER (34) + CortexM4::GENERIC_ISR, // SPI1 (35) + CortexM4::GENERIC_ISR, // SPI2 (36) + CortexM4::GENERIC_ISR, // USART1 (37) + CortexM4::GENERIC_ISR, // USART2 (38) + CortexM4::GENERIC_ISR, // USART3 (39) + CortexM4::GENERIC_ISR, // EXTI15_10 (40) + CortexM4::GENERIC_ISR, // RTC_Alarm (41) + CortexM4::GENERIC_ISR, // OTG_FS_WKUP (42) + unhandled_interrupt, // (43) + unhandled_interrupt, // (44) + unhandled_interrupt, // (45) + unhandled_interrupt, // (45) + CortexM4::GENERIC_ISR, // DMA1_Stream7 (47) + unhandled_interrupt, // (48) + CortexM4::GENERIC_ISR, // SDIO (49) + CortexM4::GENERIC_ISR, // TIM5 (50) + CortexM4::GENERIC_ISR, // SPI3 (51) + unhandled_interrupt, // (52) + unhandled_interrupt, // (53) + unhandled_interrupt, // (54) + unhandled_interrupt, // (55) + CortexM4::GENERIC_ISR, // DMA2_Stream0 (56) + CortexM4::GENERIC_ISR, // DMA2_Stream1 (57) + CortexM4::GENERIC_ISR, // DMA2_Stream2 (58) + CortexM4::GENERIC_ISR, // DMA2_Stream3 (59) + CortexM4::GENERIC_ISR, // DMA2_Stream4 (60) + unhandled_interrupt, // (61) + unhandled_interrupt, // (62) + unhandled_interrupt, // (63) + unhandled_interrupt, // (64) + unhandled_interrupt, // (65) + unhandled_interrupt, // (66) + CortexM4::GENERIC_ISR, // OTG_FS (67) + CortexM4::GENERIC_ISR, // DMA2_Stream5 (68) + CortexM4::GENERIC_ISR, // DMA2_Stream6 (69) + CortexM4::GENERIC_ISR, // DMA2_Stream7 (70) + CortexM4::GENERIC_ISR, // USART6 (71) + CortexM4::GENERIC_ISR, // I2C3_EV (72) + CortexM4::GENERIC_ISR, // I2C3_ER (73) + unhandled_interrupt, // (74) + unhandled_interrupt, // (75) + unhandled_interrupt, // (76) + unhandled_interrupt, // (77) + unhandled_interrupt, // (78) + unhandled_interrupt, // (79) + unhandled_interrupt, // (80) + CortexM4::GENERIC_ISR, // FPU (81) + unhandled_interrupt, // (82) + unhandled_interrupt, // (83) + CortexM4::GENERIC_ISR, // SPI4 (84) ]; pub unsafe fn init() { diff --git a/chips/stm32f412g/Cargo.toml b/chips/stm32f412g/Cargo.toml index b043c53219..851e93e0dc 100644 --- a/chips/stm32f412g/Cargo.toml +++ b/chips/stm32f412g/Cargo.toml @@ -1,11 +1,18 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "stm32f412g" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] cortexm4 = { path = "../../arch/cortex-m4" } kernel = { path = "../../kernel" } -stm32f4xx = { path = "../stm32f4xx" } +stm32f4xx = { path = "../stm32f4xx", features = ["stm32f412"] } enum_primitive = { path = "../../libraries/enum_primitive" } + +[lints] +workspace = true diff --git a/chips/stm32f412g/src/chip.rs b/chips/stm32f412g/src/chip.rs index 57f8e5e123..08ba1f5183 100644 --- a/chips/stm32f412g/src/chip.rs +++ b/chips/stm32f412g/src/chip.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use crate::interrupt::Stm32f412gInterruptService; use stm32f4xx::chip::Stm32f4xx; diff --git a/chips/stm32f412g/src/chip_specs.rs b/chips/stm32f412g/src/chip_specs.rs new file mode 100644 index 0000000000..1376369567 --- /dev/null +++ b/chips/stm32f412g/src/chip_specs.rs @@ -0,0 +1,34 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive SRL. +// +// Author: Ioan-Cristian CÎRSTEA + +//! STM32F412 specifications + +use stm32f4xx::chip_specific::clock_constants::{PllConstants, SystemClockConstants}; +use stm32f4xx::chip_specific::flash::{FlashChipSpecific, FlashLatency16}; + +pub enum Stm32f412Specs {} + +impl PllConstants for Stm32f412Specs { + const MIN_FREQ_MHZ: usize = 13; +} + +impl SystemClockConstants for Stm32f412Specs { + const APB1_FREQUENCY_LIMIT_MHZ: usize = 50; + const SYS_CLOCK_FREQUENCY_LIMIT_MHZ: usize = 100; +} + +impl FlashChipSpecific for Stm32f412Specs { + type FlashLatency = FlashLatency16; + + fn get_number_wait_cycles_based_on_frequency(frequency_mhz: usize) -> Self::FlashLatency { + match frequency_mhz { + 0..=30 => Self::FlashLatency::Latency0, + 31..=64 => Self::FlashLatency::Latency1, + 65..=90 => Self::FlashLatency::Latency2, + _ => Self::FlashLatency::Latency3, + } + } +} diff --git a/chips/stm32f412g/src/interrupt_service.rs b/chips/stm32f412g/src/interrupt_service.rs index f142dd8ed7..74e4e1e0bb 100644 --- a/chips/stm32f412g/src/interrupt_service.rs +++ b/chips/stm32f412g/src/interrupt_service.rs @@ -1,32 +1,36 @@ -use crate::stm32f412g_nvic; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::chip_specs::Stm32f412Specs; use stm32f4xx::chip::Stm32f4xxDefaultPeripherals; -use stm32f4xx::deferred_calls::DeferredCallTask; + +use crate::{stm32f412g_nvic, trng_registers}; pub struct Stm32f412gDefaultPeripherals<'a> { - pub stm32f4: Stm32f4xxDefaultPeripherals<'a>, + pub stm32f4: Stm32f4xxDefaultPeripherals<'a, Stm32f412Specs>, // Once implemented, place Stm32f412g specific peripherals here pub trng: stm32f4xx::trng::Trng<'a>, } impl<'a> Stm32f412gDefaultPeripherals<'a> { pub unsafe fn new( - rcc: &'a crate::rcc::Rcc, + clocks: &'a crate::clocks::Clocks<'a, Stm32f412Specs>, exti: &'a crate::exti::Exti<'a>, - dma: &'a crate::dma1::Dma1<'a>, + dma1: &'a crate::dma::Dma1<'a>, + dma2: &'a crate::dma::Dma2<'a>, ) -> Self { Self { - stm32f4: Stm32f4xxDefaultPeripherals::new(rcc, exti, dma), - trng: stm32f4xx::trng::Trng::new(rcc), + stm32f4: Stm32f4xxDefaultPeripherals::new(clocks, exti, dma1, dma2), + trng: stm32f4xx::trng::Trng::new(trng_registers::RNG_BASE, clocks), } } - // Necessary for setting up circular dependencies - pub fn init(&'a self) { + // Necessary for setting up circular dependencies & registering deferred calls + pub fn init(&'static self) { self.stm32f4.setup_circular_deps(); } } -impl<'a> kernel::platform::chip::InterruptService - for Stm32f412gDefaultPeripherals<'a> -{ +impl<'a> kernel::platform::chip::InterruptService for Stm32f412gDefaultPeripherals<'a> { unsafe fn service_interrupt(&self, interrupt: u32) -> bool { match interrupt { // put Stm32f412g specific interrupts here @@ -37,7 +41,4 @@ impl<'a> kernel::platform::chip::InterruptService _ => self.stm32f4.service_interrupt(interrupt), } } - unsafe fn service_deferred_call(&self, task: DeferredCallTask) -> bool { - self.stm32f4.service_deferred_call(task) - } } diff --git a/chips/stm32f412g/src/lib.rs b/chips/stm32f412g/src/lib.rs index 23aadc1195..1cba3a4b62 100644 --- a/chips/stm32f412g/src/lib.rs +++ b/chips/stm32f412g/src/lib.rs @@ -1,13 +1,20 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + #![no_std] -use cortexm4::generic_isr; +use cortexm4::{CortexM4, CortexMVariant}; pub use stm32f4xx::{ - adc, chip, dbg, dma1, exti, fsmc, gpio, i2c, nvic, rcc, spi, syscfg, tim2, trng, usart, + adc, chip, clocks, dbg, dma, exti, flash, fsmc, gpio, i2c, nvic, rcc, spi, syscfg, tim2, trng, + usart, }; +pub mod chip_specs; pub mod interrupt_service; pub mod stm32f412g_nvic; +mod trng_registers; // STM32F412g has total of 97 interrupts #[cfg_attr(all(target_arch = "arm", target_os = "none"), link_section = ".irqs")] @@ -19,103 +26,103 @@ pub mod stm32f412g_nvic; // related discussion. #[cfg_attr(all(target_arch = "arm", target_os = "none"), used)] pub static IRQS: [unsafe extern "C" fn(); 97] = [ - generic_isr, // WWDG (0) - generic_isr, // PVD (1) - generic_isr, // TAMP_STAMP (2) - generic_isr, // RTC_WKUP (3) - generic_isr, // FLASH (4) - generic_isr, // RCC (5) - generic_isr, // EXTI0 (6) - generic_isr, // EXTI1 (7) - generic_isr, // EXTI2 (8) - generic_isr, // EXTI3 (9) - generic_isr, // EXTI4 (10) - generic_isr, // DMA1_Stream0 (11) - generic_isr, // DMA1_Stream1 (12) - generic_isr, // DMA1_Stream2 (13) - generic_isr, // DMA1_Stream3 (14) - generic_isr, // DMA1_Stream4 (15) - generic_isr, // DMA1_Stream5 (16) - generic_isr, // DMA1_Stream6 (17) - generic_isr, // ADC (18) - generic_isr, // CAN1_TX (19) - generic_isr, // CAN1_RX0 (20) - generic_isr, // CAN1_RX1 (21) - generic_isr, // CAN1_SCE (22) - generic_isr, // EXTI9_5 (23) - generic_isr, // TIM1_BRK_TIM9 (24) - generic_isr, // TIM1_UP_TIM10 (25) - generic_isr, // TIM1_TRG_COM_TIM11 (26) - generic_isr, // TIM1_CC (27) - generic_isr, // TIM2 (28) - generic_isr, // TIM3 (29) - generic_isr, // TIM4 (30) - generic_isr, // I2C1_EV (31) - generic_isr, // I2C1_ER (32) - generic_isr, // I2C2_EV (33) - generic_isr, // I2C2_ER (34) - generic_isr, // SPI1 (35) - generic_isr, // SPI2 (36) - generic_isr, // USART1 (37) - generic_isr, // USART2 (38) - generic_isr, // USART3 (39) - generic_isr, // EXTI15_10 (40) - generic_isr, // RTC_Alarm (41) - generic_isr, // OTG_FS_WKUP (42) - generic_isr, // TIM8_BRK_TIM12 (43) - generic_isr, // TIM8_UP_TIM13 (44) - generic_isr, // TIM8_TRG_COM_TIM14 (45) - generic_isr, // TIM8_CC (46) - generic_isr, // DMA1_Stream7 (47) - generic_isr, // FMC (48) - generic_isr, // SDIO (49) - generic_isr, // TIM5 (50) - generic_isr, // SPI3 (51) - generic_isr, // UART4 (52) - generic_isr, // UART5 (53) - generic_isr, // TIM6_DAC (54) - generic_isr, // TIM7 (55) - generic_isr, // DMA2_Stream0 (56) - generic_isr, // DMA2_Stream1 (57) - generic_isr, // DMA2_Stream2 (58) - generic_isr, // DMA2_Stream3 (59) - generic_isr, // DMA2_Stream4 (60) - generic_isr, // ETH (61) - generic_isr, // ETH_WKUP (62) - generic_isr, // CAN2_TX (63) - generic_isr, // CAN2_RX0 (64) - generic_isr, // CAN2_RX1 (65) - generic_isr, // CAN2_SCE (66) - generic_isr, // OTG_FS (67) - generic_isr, // DMA2_Stream5 (68) - generic_isr, // DMA2_Stream6 (69) - generic_isr, // DMA2_Stream7 (70) - generic_isr, // USART6 (71) - generic_isr, // I2C3_EV (72) - generic_isr, // I2C3_ER (73) - generic_isr, // OTG_HS_EP1_OUT (74) - generic_isr, // OTG_HS_EP1_IN (75) - generic_isr, // OTG_HS_WKUP (76) - generic_isr, // OTG_HS (77) - generic_isr, // DCMI (78) - generic_isr, // CRYP (79) - generic_isr, // HASH_RNG (80) - generic_isr, // FPU (81) - generic_isr, // unused - generic_isr, // unused - generic_isr, // SPI4 (84) - generic_isr, // SPI5 (85) - generic_isr, // unused - generic_isr, // SAI1 (87) - generic_isr, // unused - generic_isr, // unused - generic_isr, // unused - generic_isr, // unused - generic_isr, // Quad-SPI (92) - generic_isr, // unused - generic_isr, // unused - generic_isr, // I2CFMP1 event (95) - generic_isr, // I2CFMP1 error (96) + CortexM4::GENERIC_ISR, // WWDG (0) + CortexM4::GENERIC_ISR, // PVD (1) + CortexM4::GENERIC_ISR, // TAMP_STAMP (2) + CortexM4::GENERIC_ISR, // RTC_WKUP (3) + CortexM4::GENERIC_ISR, // FLASH (4) + CortexM4::GENERIC_ISR, // RCC (5) + CortexM4::GENERIC_ISR, // EXTI0 (6) + CortexM4::GENERIC_ISR, // EXTI1 (7) + CortexM4::GENERIC_ISR, // EXTI2 (8) + CortexM4::GENERIC_ISR, // EXTI3 (9) + CortexM4::GENERIC_ISR, // EXTI4 (10) + CortexM4::GENERIC_ISR, // DMA1_Stream0 (11) + CortexM4::GENERIC_ISR, // DMA1_Stream1 (12) + CortexM4::GENERIC_ISR, // DMA1_Stream2 (13) + CortexM4::GENERIC_ISR, // DMA1_Stream3 (14) + CortexM4::GENERIC_ISR, // DMA1_Stream4 (15) + CortexM4::GENERIC_ISR, // DMA1_Stream5 (16) + CortexM4::GENERIC_ISR, // DMA1_Stream6 (17) + CortexM4::GENERIC_ISR, // ADC (18) + CortexM4::GENERIC_ISR, // CAN1_TX (19) + CortexM4::GENERIC_ISR, // CAN1_RX0 (20) + CortexM4::GENERIC_ISR, // CAN1_RX1 (21) + CortexM4::GENERIC_ISR, // CAN1_SCE (22) + CortexM4::GENERIC_ISR, // EXTI9_5 (23) + CortexM4::GENERIC_ISR, // TIM1_BRK_TIM9 (24) + CortexM4::GENERIC_ISR, // TIM1_UP_TIM10 (25) + CortexM4::GENERIC_ISR, // TIM1_TRG_COM_TIM11 (26) + CortexM4::GENERIC_ISR, // TIM1_CC (27) + CortexM4::GENERIC_ISR, // TIM2 (28) + CortexM4::GENERIC_ISR, // TIM3 (29) + CortexM4::GENERIC_ISR, // TIM4 (30) + CortexM4::GENERIC_ISR, // I2C1_EV (31) + CortexM4::GENERIC_ISR, // I2C1_ER (32) + CortexM4::GENERIC_ISR, // I2C2_EV (33) + CortexM4::GENERIC_ISR, // I2C2_ER (34) + CortexM4::GENERIC_ISR, // SPI1 (35) + CortexM4::GENERIC_ISR, // SPI2 (36) + CortexM4::GENERIC_ISR, // USART1 (37) + CortexM4::GENERIC_ISR, // USART2 (38) + CortexM4::GENERIC_ISR, // USART3 (39) + CortexM4::GENERIC_ISR, // EXTI15_10 (40) + CortexM4::GENERIC_ISR, // RTC_Alarm (41) + CortexM4::GENERIC_ISR, // OTG_FS_WKUP (42) + CortexM4::GENERIC_ISR, // TIM8_BRK_TIM12 (43) + CortexM4::GENERIC_ISR, // TIM8_UP_TIM13 (44) + CortexM4::GENERIC_ISR, // TIM8_TRG_COM_TIM14 (45) + CortexM4::GENERIC_ISR, // TIM8_CC (46) + CortexM4::GENERIC_ISR, // DMA1_Stream7 (47) + CortexM4::GENERIC_ISR, // FMC (48) + CortexM4::GENERIC_ISR, // SDIO (49) + CortexM4::GENERIC_ISR, // TIM5 (50) + CortexM4::GENERIC_ISR, // SPI3 (51) + CortexM4::GENERIC_ISR, // UART4 (52) + CortexM4::GENERIC_ISR, // UART5 (53) + CortexM4::GENERIC_ISR, // TIM6_DAC (54) + CortexM4::GENERIC_ISR, // TIM7 (55) + CortexM4::GENERIC_ISR, // DMA2_Stream0 (56) + CortexM4::GENERIC_ISR, // DMA2_Stream1 (57) + CortexM4::GENERIC_ISR, // DMA2_Stream2 (58) + CortexM4::GENERIC_ISR, // DMA2_Stream3 (59) + CortexM4::GENERIC_ISR, // DMA2_Stream4 (60) + CortexM4::GENERIC_ISR, // ETH (61) + CortexM4::GENERIC_ISR, // ETH_WKUP (62) + CortexM4::GENERIC_ISR, // CAN2_TX (63) + CortexM4::GENERIC_ISR, // CAN2_RX0 (64) + CortexM4::GENERIC_ISR, // CAN2_RX1 (65) + CortexM4::GENERIC_ISR, // CAN2_SCE (66) + CortexM4::GENERIC_ISR, // OTG_FS (67) + CortexM4::GENERIC_ISR, // DMA2_Stream5 (68) + CortexM4::GENERIC_ISR, // DMA2_Stream6 (69) + CortexM4::GENERIC_ISR, // DMA2_Stream7 (70) + CortexM4::GENERIC_ISR, // USART6 (71) + CortexM4::GENERIC_ISR, // I2C3_EV (72) + CortexM4::GENERIC_ISR, // I2C3_ER (73) + CortexM4::GENERIC_ISR, // OTG_HS_EP1_OUT (74) + CortexM4::GENERIC_ISR, // OTG_HS_EP1_IN (75) + CortexM4::GENERIC_ISR, // OTG_HS_WKUP (76) + CortexM4::GENERIC_ISR, // OTG_HS (77) + CortexM4::GENERIC_ISR, // DCMI (78) + CortexM4::GENERIC_ISR, // CRYP (79) + CortexM4::GENERIC_ISR, // HASH_RNG (80) + CortexM4::GENERIC_ISR, // FPU (81) + CortexM4::GENERIC_ISR, // unused + CortexM4::GENERIC_ISR, // unused + CortexM4::GENERIC_ISR, // SPI4 (84) + CortexM4::GENERIC_ISR, // SPI5 (85) + CortexM4::GENERIC_ISR, // unused + CortexM4::GENERIC_ISR, // SAI1 (87) + CortexM4::GENERIC_ISR, // unused + CortexM4::GENERIC_ISR, // unused + CortexM4::GENERIC_ISR, // unused + CortexM4::GENERIC_ISR, // unused + CortexM4::GENERIC_ISR, // Quad-SPI (92) + CortexM4::GENERIC_ISR, // unused + CortexM4::GENERIC_ISR, // unused + CortexM4::GENERIC_ISR, // I2CFMP1 event (95) + CortexM4::GENERIC_ISR, // I2CFMP1 error (96) ]; pub unsafe fn init() { diff --git a/chips/stm32f412g/src/stm32f412g_nvic.rs b/chips/stm32f412g/src/stm32f412g_nvic.rs index b67896e0d7..74451a9b04 100644 --- a/chips/stm32f412g/src/stm32f412g_nvic.rs +++ b/chips/stm32f412g/src/stm32f412g_nvic.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Named constants for NVIC ids specific to this chip pub const RNG: u32 = 80; diff --git a/chips/stm32f412g/src/trng_registers.rs b/chips/stm32f412g/src/trng_registers.rs new file mode 100644 index 0000000000..aecc157610 --- /dev/null +++ b/chips/stm32f412g/src/trng_registers.rs @@ -0,0 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! True random number generator + +use kernel::utilities::StaticRef; +use stm32f4xx::trng::RngRegisters; + +pub(crate) const RNG_BASE: StaticRef = + unsafe { StaticRef::new(0x5006_0800 as *const RngRegisters) }; diff --git a/chips/stm32f429zi/Cargo.toml b/chips/stm32f429zi/Cargo.toml index 20d273e691..ea5bbdfc78 100644 --- a/chips/stm32f429zi/Cargo.toml +++ b/chips/stm32f429zi/Cargo.toml @@ -1,11 +1,18 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "stm32f429zi" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] cortexm4 = { path = "../../arch/cortex-m4" } kernel = { path = "../../kernel" } -stm32f4xx = { path = "../stm32f4xx" } +stm32f4xx = { path = "../stm32f4xx", features = ["stm32f429"] } enum_primitive = { path = "../../libraries/enum_primitive" } + +[lints] +workspace = true diff --git a/chips/stm32f429zi/src/can_registers.rs b/chips/stm32f429zi/src/can_registers.rs new file mode 100644 index 0000000000..58c8ad8201 --- /dev/null +++ b/chips/stm32f429zi/src/can_registers.rs @@ -0,0 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! CAN + +use kernel::utilities::StaticRef; +use stm32f4xx::can::Registers; + +pub(crate) const CAN1_BASE: StaticRef = + unsafe { StaticRef::new(0x40006400 as *const Registers) }; diff --git a/chips/stm32f429zi/src/chip_specs.rs b/chips/stm32f429zi/src/chip_specs.rs new file mode 100644 index 0000000000..ea9c9e9d1a --- /dev/null +++ b/chips/stm32f429zi/src/chip_specs.rs @@ -0,0 +1,36 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive SRL. +// +// Author: Ioan-Cristian CÎRSTEA + +//! STM32F429 specifications + +use stm32f4xx::chip_specific::clock_constants::{PllConstants, SystemClockConstants}; +use stm32f4xx::chip_specific::flash::{FlashChipSpecific, FlashLatency16}; + +pub enum Stm32f429Specs {} + +impl PllConstants for Stm32f429Specs { + const MIN_FREQ_MHZ: usize = 13; +} + +impl SystemClockConstants for Stm32f429Specs { + const APB1_FREQUENCY_LIMIT_MHZ: usize = 45; + const SYS_CLOCK_FREQUENCY_LIMIT_MHZ: usize = 168; +} + +impl FlashChipSpecific for Stm32f429Specs { + type FlashLatency = FlashLatency16; + + fn get_number_wait_cycles_based_on_frequency(frequency_mhz: usize) -> Self::FlashLatency { + match frequency_mhz { + 0..=30 => Self::FlashLatency::Latency0, + 31..=60 => Self::FlashLatency::Latency1, + 61..=90 => Self::FlashLatency::Latency2, + 91..=120 => Self::FlashLatency::Latency3, + 121..=150 => Self::FlashLatency::Latency4, + _ => Self::FlashLatency::Latency5, + } + } +} diff --git a/chips/stm32f429zi/src/interrupt_service.rs b/chips/stm32f429zi/src/interrupt_service.rs index 1f3d66303d..385824527a 100644 --- a/chips/stm32f429zi/src/interrupt_service.rs +++ b/chips/stm32f429zi/src/interrupt_service.rs @@ -1,36 +1,66 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::chip_specs::Stm32f429Specs; use stm32f4xx::chip::Stm32f4xxDefaultPeripherals; -use stm32f4xx::deferred_calls::DeferredCallTask; + +use crate::{can_registers, stm32f429zi_nvic, trng_registers}; pub struct Stm32f429ziDefaultPeripherals<'a> { - pub stm32f4: Stm32f4xxDefaultPeripherals<'a>, + pub stm32f4: Stm32f4xxDefaultPeripherals<'a, Stm32f429Specs>, // Once implemented, place Stm32f429zi specific peripherals here + pub trng: stm32f4xx::trng::Trng<'a>, + pub can1: stm32f4xx::can::Can<'a>, + pub rtc: crate::rtc::Rtc<'a>, } impl<'a> Stm32f429ziDefaultPeripherals<'a> { pub unsafe fn new( - rcc: &'a crate::rcc::Rcc, + clocks: &'a crate::clocks::Clocks<'a, Stm32f429Specs>, exti: &'a crate::exti::Exti<'a>, - dma: &'a crate::dma1::Dma1<'a>, + dma1: &'a crate::dma::Dma1<'a>, + dma2: &'a crate::dma::Dma2<'a>, ) -> Self { Self { - stm32f4: Stm32f4xxDefaultPeripherals::new(rcc, exti, dma), + stm32f4: Stm32f4xxDefaultPeripherals::new(clocks, exti, dma1, dma2), + trng: stm32f4xx::trng::Trng::new(trng_registers::RNG_BASE, clocks), + can1: stm32f4xx::can::Can::new(clocks, can_registers::CAN1_BASE), + rtc: crate::rtc::Rtc::new(clocks), } } - // Necessary for setting up circular dependencies - pub fn init(&'a self) { + // Necessary for setting up circular dependencies and registering deferred calls + pub fn init(&'static self) { self.stm32f4.setup_circular_deps(); + kernel::deferred_call::DeferredCallClient::register(&self.can1); + kernel::deferred_call::DeferredCallClient::register(&self.rtc); } } -impl<'a> kernel::platform::chip::InterruptService - for Stm32f429ziDefaultPeripherals<'a> -{ +impl<'a> kernel::platform::chip::InterruptService for Stm32f429ziDefaultPeripherals<'a> { unsafe fn service_interrupt(&self, interrupt: u32) -> bool { match interrupt { // put Stm32f429zi specific interrupts here + stm32f429zi_nvic::HASH_RNG => { + self.trng.handle_interrupt(); + true + } + stm32f4xx::nvic::CAN1_TX => { + self.can1.handle_transmit_interrupt(); + true + } + stm32f4xx::nvic::CAN1_RX0 => { + self.can1.handle_fifo0_interrupt(); + true + } + stm32f4xx::nvic::CAN1_RX1 => { + self.can1.handle_fifo1_interrupt(); + true + } + stm32f4xx::nvic::CAN1_SCE => { + self.can1.handle_error_status_interrupt(); + true + } _ => self.stm32f4.service_interrupt(interrupt), } } - unsafe fn service_deferred_call(&self, task: DeferredCallTask) -> bool { - self.stm32f4.service_deferred_call(task) - } } diff --git a/chips/stm32f429zi/src/lib.rs b/chips/stm32f429zi/src/lib.rs index c3bb0cd758..ed2a7cb75b 100644 --- a/chips/stm32f429zi/src/lib.rs +++ b/chips/stm32f429zi/src/lib.rs @@ -1,11 +1,24 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + #![no_std] -use cortexm4::generic_isr; +use cortexm4::{CortexM4, CortexMVariant}; -pub use stm32f4xx::{adc, chip, dbg, dma1, exti, gpio, nvic, rcc, spi, syscfg, tim2, usart}; +pub use stm32f4xx::{ + adc, can, chip, clocks, dac, dbg, dma, exti, flash, gpio, nvic, rcc, spi, syscfg, tim2, trng, + usart, +}; +pub mod can_registers; +pub mod chip_specs; pub mod interrupt_service; pub mod stm32f429zi_nvic; +pub mod trng_registers; + +pub mod pwr; +pub mod rtc; // STM32F42xxx and STM32F43xxx has total of 91 interrupts #[cfg_attr(all(target_arch = "arm", target_os = "none"), link_section = ".irqs")] @@ -17,97 +30,97 @@ pub mod stm32f429zi_nvic; // related discussion. #[cfg_attr(all(target_arch = "arm", target_os = "none"), used)] pub static IRQS: [unsafe extern "C" fn(); 91] = [ - generic_isr, // WWDG (0) - generic_isr, // PVD (1) - generic_isr, // TAMP_STAMP (2) - generic_isr, // RTC_WKUP (3) - generic_isr, // FLASH (4) - generic_isr, // RCC (5) - generic_isr, // EXTI0 (6) - generic_isr, // EXTI1 (7) - generic_isr, // EXTI2 (8) - generic_isr, // EXTI3 (9) - generic_isr, // EXTI4 (10) - generic_isr, // DMA1_Stream0 (11) - generic_isr, // DMA1_Stream1 (12) - generic_isr, // DMA1_Stream2 (13) - generic_isr, // DMA1_Stream3 (14) - generic_isr, // DMA1_Stream4 (15) - generic_isr, // DMA1_Stream5 (16) - generic_isr, // DMA1_Stream6 (17) - generic_isr, // ADC (18) - generic_isr, // CAN1_TX (19) - generic_isr, // CAN1_RX0 (20) - generic_isr, // CAN1_RX1 (21) - generic_isr, // CAN1_SCE (22) - generic_isr, // EXTI9_5 (23) - generic_isr, // TIM1_BRK_TIM9 (24) - generic_isr, // TIM1_UP_TIM10 (25) - generic_isr, // TIM1_TRG_COM_TIM11 (26) - generic_isr, // TIM1_CC (27) - generic_isr, // TIM2 (28) - generic_isr, // TIM3 (29) - generic_isr, // TIM4 (30) - generic_isr, // I2C1_EV (31) - generic_isr, // I2C1_ER (32) - generic_isr, // I2C2_EV (33) - generic_isr, // I2C2_ER (34) - generic_isr, // SPI1 (35) - generic_isr, // SPI2 (36) - generic_isr, // USART1 (37) - generic_isr, // USART2 (38) - generic_isr, // USART3 (39) - generic_isr, // EXTI15_10 (40) - generic_isr, // RTC_Alarm (41) - generic_isr, // OTG_FS_WKUP (42) - generic_isr, // TIM8_BRK_TIM12 (43) - generic_isr, // TIM8_UP_TIM13 (44) - generic_isr, // TIM8_TRG_COM_TIM14 (45) - generic_isr, // TIM8_CC (46) - generic_isr, // DMA1_Stream7 (47) - generic_isr, // FMC (48) - generic_isr, // SDIO (49) - generic_isr, // TIM5 (50) - generic_isr, // SPI3 (51) - generic_isr, // UART4 (52) - generic_isr, // UART5 (53) - generic_isr, // TIM6_DAC (54) - generic_isr, // TIM7 (55) - generic_isr, // DMA2_Stream0 (56) - generic_isr, // DMA2_Stream1 (57) - generic_isr, // DMA2_Stream2 (58) - generic_isr, // DMA2_Stream3 (59) - generic_isr, // DMA2_Stream4 (60) - generic_isr, // ETH (61) - generic_isr, // ETH_WKUP (62) - generic_isr, // CAN2_TX (63) - generic_isr, // CAN2_RX0 (64) - generic_isr, // CAN2_RX1 (65) - generic_isr, // CAN2_SCE (66) - generic_isr, // OTG_FS (67) - generic_isr, // DMA2_Stream5 (68) - generic_isr, // DMA2_Stream6 (69) - generic_isr, // DMA2_Stream7 (70) - generic_isr, // USART6 (71) - generic_isr, // I2C3_EV (72) - generic_isr, // I2C3_ER (73) - generic_isr, // OTG_HS_EP1_OUT (74) - generic_isr, // OTG_HS_EP1_IN (75) - generic_isr, // OTG_HS_WKUP (76) - generic_isr, // OTG_HS (77) - generic_isr, // DCMI (78) - generic_isr, // CRYP (79) - generic_isr, // HASH_RNG (80) - generic_isr, // FPU (81) - generic_isr, // USART7 (82) - generic_isr, // USART8 (83) - generic_isr, // SPI4 (84) - generic_isr, // SPI5 (85) - generic_isr, // SPI6 (86) - generic_isr, // SAI1 (87) - generic_isr, // LCD-TFT (88) - generic_isr, // LCD-TFT (89) - generic_isr, // DMA2D(90) + CortexM4::GENERIC_ISR, // WWDG (0) + CortexM4::GENERIC_ISR, // PVD (1) + CortexM4::GENERIC_ISR, // TAMP_STAMP (2) + CortexM4::GENERIC_ISR, // RTC_WKUP (3) + CortexM4::GENERIC_ISR, // FLASH (4) + CortexM4::GENERIC_ISR, // RCC (5) + CortexM4::GENERIC_ISR, // EXTI0 (6) + CortexM4::GENERIC_ISR, // EXTI1 (7) + CortexM4::GENERIC_ISR, // EXTI2 (8) + CortexM4::GENERIC_ISR, // EXTI3 (9) + CortexM4::GENERIC_ISR, // EXTI4 (10) + CortexM4::GENERIC_ISR, // DMA1_Stream0 (11) + CortexM4::GENERIC_ISR, // DMA1_Stream1 (12) + CortexM4::GENERIC_ISR, // DMA1_Stream2 (13) + CortexM4::GENERIC_ISR, // DMA1_Stream3 (14) + CortexM4::GENERIC_ISR, // DMA1_Stream4 (15) + CortexM4::GENERIC_ISR, // DMA1_Stream5 (16) + CortexM4::GENERIC_ISR, // DMA1_Stream6 (17) + CortexM4::GENERIC_ISR, // ADC (18) + CortexM4::GENERIC_ISR, // CAN1_TX (19) + CortexM4::GENERIC_ISR, // CAN1_RX0 (20) + CortexM4::GENERIC_ISR, // CAN1_RX1 (21) + CortexM4::GENERIC_ISR, // CAN1_SCE (22) + CortexM4::GENERIC_ISR, // EXTI9_5 (23) + CortexM4::GENERIC_ISR, // TIM1_BRK_TIM9 (24) + CortexM4::GENERIC_ISR, // TIM1_UP_TIM10 (25) + CortexM4::GENERIC_ISR, // TIM1_TRG_COM_TIM11 (26) + CortexM4::GENERIC_ISR, // TIM1_CC (27) + CortexM4::GENERIC_ISR, // TIM2 (28) + CortexM4::GENERIC_ISR, // TIM3 (29) + CortexM4::GENERIC_ISR, // TIM4 (30) + CortexM4::GENERIC_ISR, // I2C1_EV (31) + CortexM4::GENERIC_ISR, // I2C1_ER (32) + CortexM4::GENERIC_ISR, // I2C2_EV (33) + CortexM4::GENERIC_ISR, // I2C2_ER (34) + CortexM4::GENERIC_ISR, // SPI1 (35) + CortexM4::GENERIC_ISR, // SPI2 (36) + CortexM4::GENERIC_ISR, // USART1 (37) + CortexM4::GENERIC_ISR, // USART2 (38) + CortexM4::GENERIC_ISR, // USART3 (39) + CortexM4::GENERIC_ISR, // EXTI15_10 (40) + CortexM4::GENERIC_ISR, // RTC_Alarm (41) + CortexM4::GENERIC_ISR, // OTG_FS_WKUP (42) + CortexM4::GENERIC_ISR, // TIM8_BRK_TIM12 (43) + CortexM4::GENERIC_ISR, // TIM8_UP_TIM13 (44) + CortexM4::GENERIC_ISR, // TIM8_TRG_COM_TIM14 (45) + CortexM4::GENERIC_ISR, // TIM8_CC (46) + CortexM4::GENERIC_ISR, // DMA1_Stream7 (47) + CortexM4::GENERIC_ISR, // FMC (48) + CortexM4::GENERIC_ISR, // SDIO (49) + CortexM4::GENERIC_ISR, // TIM5 (50) + CortexM4::GENERIC_ISR, // SPI3 (51) + CortexM4::GENERIC_ISR, // UART4 (52) + CortexM4::GENERIC_ISR, // UART5 (53) + CortexM4::GENERIC_ISR, // TIM6_DAC (54) + CortexM4::GENERIC_ISR, // TIM7 (55) + CortexM4::GENERIC_ISR, // DMA2_Stream0 (56) + CortexM4::GENERIC_ISR, // DMA2_Stream1 (57) + CortexM4::GENERIC_ISR, // DMA2_Stream2 (58) + CortexM4::GENERIC_ISR, // DMA2_Stream3 (59) + CortexM4::GENERIC_ISR, // DMA2_Stream4 (60) + CortexM4::GENERIC_ISR, // ETH (61) + CortexM4::GENERIC_ISR, // ETH_WKUP (62) + CortexM4::GENERIC_ISR, // CAN2_TX (63) + CortexM4::GENERIC_ISR, // CAN2_RX0 (64) + CortexM4::GENERIC_ISR, // CAN2_RX1 (65) + CortexM4::GENERIC_ISR, // CAN2_SCE (66) + CortexM4::GENERIC_ISR, // OTG_FS (67) + CortexM4::GENERIC_ISR, // DMA2_Stream5 (68) + CortexM4::GENERIC_ISR, // DMA2_Stream6 (69) + CortexM4::GENERIC_ISR, // DMA2_Stream7 (70) + CortexM4::GENERIC_ISR, // USART6 (71) + CortexM4::GENERIC_ISR, // I2C3_EV (72) + CortexM4::GENERIC_ISR, // I2C3_ER (73) + CortexM4::GENERIC_ISR, // OTG_HS_EP1_OUT (74) + CortexM4::GENERIC_ISR, // OTG_HS_EP1_IN (75) + CortexM4::GENERIC_ISR, // OTG_HS_WKUP (76) + CortexM4::GENERIC_ISR, // OTG_HS (77) + CortexM4::GENERIC_ISR, // DCMI (78) + CortexM4::GENERIC_ISR, // CRYP (79) + CortexM4::GENERIC_ISR, // HASH_RNG (80) + CortexM4::GENERIC_ISR, // FPU (81) + CortexM4::GENERIC_ISR, // USART7 (82) + CortexM4::GENERIC_ISR, // USART8 (83) + CortexM4::GENERIC_ISR, // SPI4 (84) + CortexM4::GENERIC_ISR, // SPI5 (85) + CortexM4::GENERIC_ISR, // SPI6 (86) + CortexM4::GENERIC_ISR, // SAI1 (87) + CortexM4::GENERIC_ISR, // LCD-TFT (88) + CortexM4::GENERIC_ISR, // LCD-TFT (89) + CortexM4::GENERIC_ISR, // DMA2D(90) ]; pub unsafe fn init() { diff --git a/chips/stm32f429zi/src/pwr.rs b/chips/stm32f429zi/src/pwr.rs new file mode 100644 index 0000000000..0a74bb707c --- /dev/null +++ b/chips/stm32f429zi/src/pwr.rs @@ -0,0 +1,97 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use kernel::utilities::registers::interfaces::ReadWriteable; +use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite}; +use kernel::utilities::StaticRef; +use kernel::ErrorCode; + +register_structs! { + /// Power control + PwrRegisters { + /// power control register + (0x000 => cr: ReadWrite), + /// power control/status register + (0x004 => csr: ReadWrite), + (0x008 => @END), + } +} +register_bitfields![u32, +CR [ + /// Low-power deep sleep + LPDS OFFSET(0) NUMBITS(1) [], + /// Power down deepsleep + PDDS OFFSET(1) NUMBITS(1) [], + /// Clear wakeup flag + CWUF OFFSET(2) NUMBITS(1) [], + /// Clear standby flag + CSBF OFFSET(3) NUMBITS(1) [], + /// Power voltage detector + /// enable + PVDE OFFSET(4) NUMBITS(1) [], + /// PVD level selection + PLS OFFSET(5) NUMBITS(3) [], + /// Disable backup domain write + /// protection + DBP OFFSET(8) NUMBITS(1) [], + /// Flash power down in Stop + /// mode + FPDS OFFSET(9) NUMBITS(1) [], + /// Low-Power Regulator Low Voltage in + /// deepsleep + LPLUDS OFFSET(10) NUMBITS(1) [], + /// Main regulator low voltage in deepsleep + /// mode + MRUDS OFFSET(11) NUMBITS(1) [], + + ADCDC1 OFFSET(13) NUMBITS(1) [], + /// Regulator voltage scaling output + /// selection + VOS OFFSET(14) NUMBITS(2) [ + Scale3 = 0b01, + Scale2 = 0b10, + Scale1 = 0b11, + ], + /// Over-drive enable + ODEN OFFSET(16) NUMBITS(1) [], + /// Over-drive switching + /// enabled + ODSWEN OFFSET(17) NUMBITS(1) [], + /// Under-drive enable in stop + /// mode + UDEN OFFSET(18) NUMBITS(2) [] +], +CSR [ + /// Wakeup flag + WUF OFFSET(0) NUMBITS(1) [], + /// Standby flag + SBF OFFSET(1) NUMBITS(1) [], + /// PVD output + PVDO OFFSET(2) NUMBITS(1) [], + /// Backup regulator ready + BRR OFFSET(3) NUMBITS(1) [], + /// Enable WKUP pin + EWUP OFFSET(8) NUMBITS(1) [], + /// Backup regulator enable + BRE OFFSET(9) NUMBITS(1) [], + /// Regulator voltage scaling output + /// selection ready bit + VOSRDY OFFSET(14) NUMBITS(1) [], + /// Over-drive mode ready + ODRDY OFFSET(16) NUMBITS(1) [], + /// Over-drive mode switching + /// ready + ODSWRDY OFFSET(17) NUMBITS(1) [], + /// Under-drive ready flag + UDRDY OFFSET(18) NUMBITS(2) [] +] +]; +const PWR_BASE: StaticRef = + unsafe { StaticRef::new(0x40007000 as *const PwrRegisters) }; + +#[inline(never)] +pub fn enable_backup_access() -> Result<(), ErrorCode> { + PWR_BASE.cr.modify(CR::DBP::SET); + Ok(()) +} diff --git a/chips/stm32f429zi/src/rtc.rs b/chips/stm32f429zi/src/rtc.rs new file mode 100644 index 0000000000..830962daa2 --- /dev/null +++ b/chips/stm32f429zi/src/rtc.rs @@ -0,0 +1,676 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Real Time Clock (RTC) driver for STM32f429zi. +//! +//! Author: Remus Rughinis +//! +//! # Hardware Interface Layer (HIL) +//! +//! The driver implements Date_Time HIL. The following features are available when using +//! the driver through HIL: +//! +//! + Set time from which real time clock should start counting +//! + Read current time from the RTC registers +//! + +use core::cell::Cell; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; +use kernel::hil::date_time; +use kernel::hil::date_time::{DateTimeClient, DateTimeValues, DayOfWeek, Month}; +use kernel::platform::chip::ClockInterface; +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; +use kernel::utilities::registers::{register_bitfields, ReadWrite}; +use kernel::utilities::StaticRef; +use kernel::ErrorCode; +use stm32f4xx::clocks::{phclk, Stm32f4Clocks}; + +/// Register block to control RTC +#[repr(C)] +pub struct RtcRegisters { + /// The RTC_TR is the calendar time shadow register. This register must be written in initialization mode only. + rtc_tr: ReadWrite, + /// The RTC_DR is the calendar date shadow register. This register must be written in initialization mode only. + rtc_dr: ReadWrite, + /// RTC control register + rtc_cr: ReadWrite, + /// RTC initialization and status register + rtc_isr: ReadWrite, + /// RTC prescaler register + rtc_prer: ReadWrite, + /// RTC wakeup timer register + rtc_wutr: ReadWrite, + /// RTC calibration register + rtc_calibr: ReadWrite, + /// RTC alarm A register + rtc_alrmar: ReadWrite, + /// RTC alarm B register + rtc_alrmbr: ReadWrite, + /// RTC write protection register + rtc_wpr: ReadWrite, + /// RTC sub second register + rtc_ssr: ReadWrite, + /// RTC shift control register + rtc_shiftr: ReadWrite, + /// RTC time stamp time register + rtc_tstr: ReadWrite, + /// RTC time stamp date register + rtc_tsdr: ReadWrite, + /// RTC time stamp sub second register + rtc_tsssr: ReadWrite, + /// RTC calibration register + rtc_calr: ReadWrite, + /// RTC tamper and alternate function configuration register + rtc_tafcr: ReadWrite, + /// RTC alarm A sub second register + rtc_alrmassr: ReadWrite, + /// RTC alarm B sub second register + rtc_alrmbssr: ReadWrite, + + /// The application can write or read data to and from these registers + rtc_bkpxr: [ReadWrite; 19], +} + +register_bitfields![u32, +RTC_TR[ + /// AM/PM notation. 0: AM or 24-hour format, 1: PM + PM OFFSET(22) NUMBITS(1) [], + /// Hour tens in BCD format + HT OFFSET(20) NUMBITS(2) [], + /// Hour units in BCD format + HU OFFSET(16) NUMBITS(4) [], + /// Minute tens in BCD format + MNT OFFSET(12) NUMBITS(3) [], + /// Minute units in BCD format + MNU OFFSET(8) NUMBITS(4) [], + /// Second tens in BCD format + ST OFFSET(4) NUMBITS(3) [], + /// Second units in BCD format + SU OFFSET(0) NUMBITS(4) [], +], +RTC_DR[ + /// Year tens in BCD format + YT OFFSET(20) NUMBITS(4) [], + /// Year units in BCD format + YU OFFSET(16) NUMBITS(4) [], + /// Week day units. 000: forbidden, 001: Monday ... 111: Sunday + WDU OFFSET(13) NUMBITS(3) [], + ///Month tens in BCD format + MT OFFSET(12) NUMBITS(1) [], + /// Month units in BCD format + MU OFFSET(8) NUMBITS(4) [], + /// Date tens in BCD format + DT OFFSET(4) NUMBITS(2) [], + /// Date units in BCD format + DU OFFSET(0) NUMBITS(4) [], +], +RTC_CR[ + /// Calibration output enable, enables the RTC_CALIB output + COE OFFSET(23) NUMBITS(1) [], + /// Output selection, used to select the flag to be routed to RTC_ALARM output + OSEL OFFSET(21) NUMBITS(2) [], + /// Output polarity, used to configure the polarity of RTC_ALARM output + POL OFFSET(20) NUMBITS(1) [], + /// Calibration output selection + COSEL OFFSET(19) NUMBITS(1) [], + /// Backup, memorizes whether daylight saving time change has been performed or not + BKP OFFSET(18) NUMBITS(1) [], + /// Subtract 1 hour (winter time change) + SUB1H OFFSET(17) NUMBITS(1) [], + /// Add 1 hour (summer time change) + ADD1H OFFSET(16) NUMBITS(1) [], + /// Timestamp interrupt enable + TSIE OFFSET(15) NUMBITS(1) [], + /// Wakeup timer interrupt enable + WUTIE OFFSET(14) NUMBITS(1) [], + /// Alarm B interrupt enable + ALRBIE OFFSET(13) NUMBITS(1) [], + /// Alarm A interrupt enable + ALRAIE OFFSET(12) NUMBITS(1) [], + /// Time stamp enable + TSE OFFSET(11) NUMBITS(1) [], + /// Wakeup timer enable + WUTE OFFSET(10) NUMBITS(1) [], + /// Alarm B enable + ALRBE OFFSET(9) NUMBITS(1) [], + /// Alarm A enable + ALRAE OFFSET(8) NUMBITS(1) [], + /// Coarse digital calibration enable + DCE OFFSET(7) NUMBITS(1) [], + /// Hour format + FMT OFFSET(6) NUMBITS(1) [], + /// Bypass the shadow registers + BYPSHAD OFFSET(5) NUMBITS(1) [], + /// Reference clock detection enable (50 or 60 Hz) + REFCKON OFFSET(4) NUMBITS(1) [], + /// Timestamp event active edge + TSEDGE OFFSET(3) NUMBITS(1) [], + /// Wakeup clock selection + WUCKSEL OFFSET(0) NUMBITS(3) [], +], +RTC_ISR[ + /// Recalibration Pending Flag + RECALPF OFFSET(16) NUMBITS(1) [], + /// TAMPER2 detection flag + TAMP2F OFFSET(14) NUMBITS(1) [], + /// TAMPER detection flag + TAMP1F OFFSET(13) NUMBITS(1) [], + /// Timestamp overflow flag + TSOVF OFFSET(12) NUMBITS(1) [], + /// Timestamp flag + TSF OFFSET(11) NUMBITS(1) [], + /// Wakeup timer flag + WUTF OFFSET(10) NUMBITS(1) [], + /// Alarm B flag + ALRBF OFFSET(9) NUMBITS(1) [], + /// Alarm A flag + ALRAF OFFSET(8) NUMBITS(1) [], + /// Initialization mode + INIT OFFSET(7) NUMBITS(1) [], + /// Initialization flag + INITF OFFSET(6) NUMBITS(1) [], + /// Registers synchronization flag + RSF OFFSET(5) NUMBITS(1) [], + /// Initialization status flag + INITS OFFSET(4) NUMBITS(1) [], + /// Shift operation pending + SHPF OFFSET(3) NUMBITS(1) [], + /// Wakeup timer write flag + WUTWF OFFSET(2) NUMBITS(1) [], + /// Alarm B write flag + ALRBWF OFFSET(1) NUMBITS(1) [], + /// Alarm A write flag + ALRAWF OFFSET(0) NUMBITS(1) [], +], +RTC_PRER[ + /// Asynchronous precaler factor + PREDIV_A OFFSET(16) NUMBITS(7) [], + /// Synchronous prescaler factor + PREDIV_S OFFSET(0) NUMBITS(15) [], +], +RTC_WUTR[ + /// Wakeup auto-reload value bits + WUT OFFSET(0) NUMBITS(16) [], +], +RTC_CALIBR[ + /// Digital calibration sign + DCS OFFSET(7) NUMBITS(1) [], + /// Digital calibration + DC OFFSET(0) NUMBITS(5) [], +], +RTC_ALRMAR[ + /// Alarm A date mask + MSK4 OFFSET(31) NUMBITS(1) [], + /// Week day selection + WDSEL OFFSET(30) NUMBITS(1) [], + /// Date tens in BCD format + DT OFFSET(28) NUMBITS(2) [], + /// Date units in BCD format + DU OFFSET(24) NUMBITS(4) [], + /// Alarm A hours mask + MSK3 OFFSET(23) NUMBITS(1) [], + /// AM/PM notation + PM OFFSET(22) NUMBITS(1) [], + /// Hour tens in BCD format + HT OFFSET(20) NUMBITS(2) [], + /// Hour units in BCD format + HU OFFSET(16) NUMBITS(4) [], + /// Alarm A minutes mask + MSK2 OFFSET(15) NUMBITS(1) [], + /// Minute tens in BCD format + MNT OFFSET(12) NUMBITS(3) [], + /// Minute units in BCD format + MNU OFFSET(8) NUMBITS(4) [], + /// Alarm A seconds mask + MSK1 OFFSET(7) NUMBITS(1) [], + /// Second tens in BCD format + ST OFFSET(4) NUMBITS(3) [], + /// Second units in BCD format + SU OFFSET(0) NUMBITS(4) [], +], +RTC_ALRMBR[ + /// Alarm B date mask + MSK4 OFFSET(31) NUMBITS(1) [], + /// Week day selection + WDSEL OFFSET(30) NUMBITS(1) [], + /// Date tens in BCD format + DT OFFSET(28) NUMBITS(2) [], + /// Date units in BCD format + DU OFFSET(24) NUMBITS(4) [], + /// Alarm B hours mask + MSK3 OFFSET(23) NUMBITS(1) [], + /// AM/PM notation + PM OFFSET(22) NUMBITS(1) [], + /// Hour tens in BCD format + HT OFFSET(20) NUMBITS(2) [], + /// Hour units in BCD format + HU OFFSET(16) NUMBITS(4) [], + /// Alarm B minutes mask + MSK2 OFFSET(15) NUMBITS(1) [], + /// Minute tens in BCD format + MNT OFFSET(12) NUMBITS(3) [], + /// Minute units in BCD format + MNU OFFSET(8) NUMBITS(4) [], + /// Alarm B seconds mask + MSK1 OFFSET(7) NUMBITS(1) [], + /// Second tens in BCD format + ST OFFSET(4) NUMBITS(3) [], + /// Second units in BCD format + SU OFFSET(0) NUMBITS(4) [], +], +RTC_WPR[ + /// Write protection key + KEY OFFSET(0) NUMBITS(8) [], +], +RTC_SSR[ + /// Sub second value + SS OFFSET(0) NUMBITS(16) [], +], +RTC_SHIFTR[ + /// Add one second + ADD1S OFFSET(31) NUMBITS(1) [], + /// Subtract a fraction of a second + SUBFS OFFSET(0) NUMBITS(15) [], +], +RTC_TSTR[ + /// AM/PM notation + PM OFFSET(22) NUMBITS(1) [], + /// Hour tens in BCD format + HT OFFSET(20) NUMBITS(2) [], + /// Hour units in BCD format + HU OFFSET(16) NUMBITS(4) [], + /// Minute tens in BCD format + MNT OFFSET(12) NUMBITS(3) [], + /// Minute units in BCD format + MNU OFFSET(8) NUMBITS(4) [], + /// Second tens in BCD format + ST OFFSET(4) NUMBITS(3) [], + /// Second units in BCD format + STU OFFSET(0) NUMBITS(4) [], +], +RTC_TSDR[ + /// Week day units. 000: forbidden, 001: Monday ... 111: Sunday + WDU OFFSET(13) NUMBITS(3) [], + ///Month tens in BCD format + MT OFFSET(12) NUMBITS(1) [], + /// Month units in BCD format + MU OFFSET(8) NUMBITS(4) [], + /// Date tens in BCD format + DT OFFSET(4) NUMBITS(2) [], + /// Date units in BCD format + DU OFFSET(0) NUMBITS(4) [], +], +RTC_TSSSR[ + /// Sub second value + SS OFFSET(0) NUMBITS(16) [], +], +RTC_CALR[ + /// Increase frequency of RTC by 488.0 ppm + CALP OFFSET(15) NUMBITS(1) [], + /// Use an 8-second calibration cycle period + CALW8 OFFSET(14) NUMBITS(1) [], + /// Use a 16-second calibration cycle period + CALW16 OFFSET(13) NUMBITS(1) [], + /// Calibration minus + CALM OFFSET(0) NUMBITS(9) [], +], +RTC_TAFCR[ + /// RTC_ALARM output type + ALARMOUTTYPE OFFSET(18) NUMBITS(1) [], + /// TIMESTAMP mapping + TSINSEL OFFSET(17) NUMBITS(1) [], + /// TAMPER1 mapping + TAMP1INSEL OFFSET(16) NUMBITS(1) [], + /// TAMPER pull-up disable + TAMPPUDIS OFFSET(15) NUMBITS(1) [], + /// Tamper prechange duration + TAMPPRCH OFFSET(13) NUMBITS(2) [], + /// Tamper filter count + TAMPFLT OFFSET(11) NUMBITS(2) [], + /// Tamper sampling frequency + TAMPFREQ OFFSET(8) NUMBITS(3) [], + /// Activate timestamp on tamper detection event + TAMPTS OFFSET(7) NUMBITS(1) [], + /// Active level for tamper 2 + TAMP2TRG OFFSET(4) NUMBITS(1) [], + /// Tamper 2 detection enable + TAMP2E OFFSET(3) NUMBITS(1) [], + /// Tamper interrupt enable + TAMPIE OFFSET(2) NUMBITS(1) [], + /// Active level for tamper 1 + TAMP1TRG OFFSET(1) NUMBITS(1) [], + /// Tamper 1 detection enable + TAMP1E OFFSET(0) NUMBITS(1) [], +], +RTC_ALRMASSR[ + /// Mask the most-significant bits starting at this bit + MASKSS OFFSET(24) NUMBITS(4) [], + /// Sub seconds value + SS OFFSET(0) NUMBITS(15) [], +], +RTC_ALRMBSSR[ + /// Mask the most-significant bits starting at this bit + MASKSS OFFSET(24) NUMBITS(4) [], + /// Sub seconds value + SS OFFSET(0) NUMBITS(15) [], +], +RTC_BKPXR[ + /// The application can write or read data to and from these registers + BKP OFFSET(0) NUMBITS(32) [], +], +]; + +pub struct Rtc<'a> { + registers: StaticRef, + client: OptionalCell<&'a dyn date_time::DateTimeClient>, + pub clock: phclk::PeripheralClock<'a>, + pub pwr_clock: phclk::PeripheralClock<'a>, + time: Cell, + + deferred_call: DeferredCall, + deferred_call_task: OptionalCell, +} + +#[derive(Clone, Copy)] +enum DeferredCallTask { + Get, + Set, +} + +impl<'a> DeferredCallClient for Rtc<'a> { + fn handle_deferred_call(&self) { + self.deferred_call_task.take().map(|value| match value { + DeferredCallTask::Get => self + .client + .map(|client| client.get_date_time_done(Ok(self.time.get()))), + DeferredCallTask::Set => self.client.map(|client| client.set_date_time_done(Ok(()))), + }); + } + fn register(&'static self) { + self.deferred_call.register(self); + } +} + +const RTC_BASE: StaticRef = + unsafe { StaticRef::new(0x40002800 as *const RtcRegisters) }; + +impl<'a> Rtc<'a> { + pub fn new(clocks: &'a dyn Stm32f4Clocks) -> Rtc<'a> { + Rtc { + registers: RTC_BASE, + client: OptionalCell::empty(), + clock: phclk::PeripheralClock::new(phclk::PeripheralClockType::RTC, clocks), + pwr_clock: phclk::PeripheralClock::new(phclk::PeripheralClockType::PWR, clocks), + time: Cell::new(DateTimeValues { + year: 0, + month: Month::January, + day: 1, + day_of_week: DayOfWeek::Sunday, + hour: 0, + minute: 0, + seconds: 0, + }), + deferred_call: DeferredCall::new(), + deferred_call_task: OptionalCell::empty(), + } + } + + fn dotw_try_from_u32(dotw: u32) -> Result { + match dotw { + 1 => Ok(DayOfWeek::Monday), + 2 => Ok(DayOfWeek::Tuesday), + 3 => Ok(DayOfWeek::Wednesday), + 4 => Ok(DayOfWeek::Thursday), + 5 => Ok(DayOfWeek::Friday), + 6 => Ok(DayOfWeek::Saturday), + 7 => Ok(DayOfWeek::Sunday), + _ => Err(ErrorCode::INVAL), + } + } + + fn dotw_into_u32(dotw: DayOfWeek) -> u32 { + match dotw { + DayOfWeek::Monday => 1, + DayOfWeek::Tuesday => 2, + DayOfWeek::Wednesday => 3, + DayOfWeek::Thursday => 4, + DayOfWeek::Friday => 5, + DayOfWeek::Saturday => 6, + DayOfWeek::Sunday => 7, + } + } + + fn month_try_from_u32(month_num: u32) -> Result { + match month_num { + 1 => Ok(Month::January), + 2 => Ok(Month::February), + 3 => Ok(Month::March), + 4 => Ok(Month::April), + 5 => Ok(Month::May), + 6 => Ok(Month::June), + 7 => Ok(Month::July), + 8 => Ok(Month::August), + 9 => Ok(Month::September), + 10 => Ok(Month::October), + 11 => Ok(Month::November), + 12 => Ok(Month::December), + _ => Err(ErrorCode::INVAL), + } + } + + fn month_into_u32(month: Month) -> u32 { + match month { + Month::January => 1, + Month::February => 2, + Month::March => 3, + Month::April => 4, + Month::May => 5, + Month::June => 6, + Month::July => 7, + Month::August => 8, + Month::September => 9, + Month::October => 10, + Month::November => 11, + Month::December => 12, + } + } + + #[inline(never)] + // This function is marked as #[inline(never)] in order to aid with the debugging process when + // disabling board memory protection + /// Bypass write protection + fn bypass_write_protection(&self) { + self.registers.rtc_wpr.modify(RTC_WPR::KEY.val(0xCA)); // Equivalent to 0xCA + self.registers.rtc_wpr.modify(RTC_WPR::KEY.val(0x53)); // Equivalent to 0x53 + } + + #[inline(never)] + // This function is marked as #[inline(never)] in order to aid with the debugging process when + // enabling board memory protection + /// Reactivate write protection + fn enable_write_protection(&self) { + self.registers.rtc_wpr.modify(RTC_WPR::KEY.val(0x42)); // Equivalent to 0x42 + } + + fn date_time_setup(&self, datetime: date_time::DateTimeValues) -> Result<(), ErrorCode> { + let month_num = Rtc::month_into_u32(datetime.month); + let dotw_num = Rtc::dotw_into_u32(datetime.day_of_week); + + if !(datetime.day >= 1 && datetime.day <= 31) { + return Err(ErrorCode::INVAL); + } + if !(datetime.hour <= 23) { + return Err(ErrorCode::INVAL); + } + if !(datetime.minute <= 59) { + return Err(ErrorCode::INVAL); + } + if !(datetime.seconds <= 59) { + return Err(ErrorCode::INVAL); + } + + self.registers.rtc_dr.modify( + RTC_DR::YT.val((datetime.year % 100) as u32 / 10) + + RTC_DR::YU.val((datetime.year % 100) as u32 % 10) + + RTC_DR::MT.val(month_num / 10) + + RTC_DR::MU.val(month_num % 10) + + RTC_DR::DT.val(datetime.day as u32 / 10) + + RTC_DR::DU.val(datetime.day as u32 % 10) + + RTC_DR::WDU.val(dotw_num), + ); + + self.registers.rtc_tr.modify( + RTC_TR::HT.val(datetime.hour as u32 / 10) + + RTC_TR::HU.val(datetime.hour as u32 % 10) + + RTC_TR::MNT.val(datetime.minute as u32 / 10) + + RTC_TR::MNU.val(datetime.minute as u32 % 10) + + RTC_TR::ST.val(datetime.seconds as u32 / 10) + + RTC_TR::SU.val(datetime.seconds as u32 % 10), + ); + + Ok(()) + } + + pub fn enter_init_mode(&self) -> Result<(), ErrorCode> { + self.bypass_write_protection(); + self.registers.rtc_isr.modify(RTC_ISR::INIT::SET); + + let mut cycle_counter = 100000; + while cycle_counter > 0 && !self.registers.rtc_isr.is_set(RTC_ISR::INITF) { + cycle_counter -= 1; + // wait until initialization phase mode is entered + } + if cycle_counter <= 0 { + return Err(ErrorCode::FAIL); + } + Ok(()) + } + pub fn exit_init_mode(&self) -> Result<(), ErrorCode> { + self.registers.rtc_isr.modify(RTC_ISR::INIT::CLEAR); + let mut cycle_counter = 100000; + while cycle_counter > 0 && !self.registers.rtc_isr.is_set(RTC_ISR::RSF) { + cycle_counter -= 1; + } + if cycle_counter <= 0 { + return Err(ErrorCode::FAIL); + } + + self.enable_write_protection(); + Ok(()) + } + + pub fn rtc_init(&self) -> Result<(), ErrorCode> { + self.enter_init_mode()?; + + self.registers + .rtc_prer + .modify(RTC_PRER::PREDIV_A.val(128 - 1)); + self.registers + .rtc_prer + .modify(RTC_PRER::PREDIV_S.val(256 - 1)); + + // 0: 24-hour format, 1: AM/PM format + self.registers.rtc_cr.modify(RTC_CR::FMT.val(0)); + + let datetime = date_time::DateTimeValues { + year: 0, + month: Month::January, + day: 1, + day_of_week: DayOfWeek::Monday, + + hour: 0, + minute: 0, + seconds: 0, + }; + self.date_time_setup(datetime)?; + + self.exit_init_mode()?; + Ok(()) + } + + pub fn enable_clock(&self) { + self.pwr_clock.enable(); + + // Enable access to the backup domain + match crate::pwr::enable_backup_access() { + Err(e) => panic!("{:?}", e), + _ => (), + } + + self.clock.enable(); + } +} + +impl<'a> date_time::DateTime<'a> for Rtc<'a> { + fn get_date_time(&self) -> Result<(), ErrorCode> { + match self.deferred_call_task.take() { + Some(DeferredCallTask::Set) => { + self.deferred_call_task.insert(Some(DeferredCallTask::Set)); + return Err(ErrorCode::BUSY); + } + Some(DeferredCallTask::Get) => { + self.deferred_call_task.insert(Some(DeferredCallTask::Get)); + return Err(ErrorCode::ALREADY); + } + _ => (), + } + + let month_num = + self.registers.rtc_dr.read(RTC_DR::MT) * 10 + self.registers.rtc_dr.read(RTC_DR::MU); + let month_name = Rtc::month_try_from_u32(month_num)?; + + let dotw_num = self.registers.rtc_dr.read(RTC_DR::WDU); + let dotw_name = Rtc::dotw_try_from_u32(dotw_num)?; + + let datetime = date_time::DateTimeValues { + hour: (self.registers.rtc_tr.read(RTC_TR::HT) * 10 + + self.registers.rtc_tr.read(RTC_TR::HU)) as u8, + minute: (self.registers.rtc_tr.read(RTC_TR::MNT) * 10 + + self.registers.rtc_tr.read(RTC_TR::MNU)) as u8, + seconds: (self.registers.rtc_tr.read(RTC_TR::ST) * 10 + + self.registers.rtc_tr.read(RTC_TR::SU)) as u8, + + year: (self.registers.rtc_dr.read(RTC_DR::YT) * 10 + + self.registers.rtc_dr.read(RTC_DR::YU)) as u16, + month: month_name, + day: (self.registers.rtc_dr.read(RTC_DR::DT) * 10 + + self.registers.rtc_dr.read(RTC_DR::DU)) as u8, + day_of_week: dotw_name, + }; + + self.time.replace(datetime); + + self.deferred_call_task.insert(Some(DeferredCallTask::Get)); + self.deferred_call.set(); + + Ok(()) + } + + fn set_date_time(&self, date_time: date_time::DateTimeValues) -> Result<(), ErrorCode> { + match self.deferred_call_task.take() { + Some(DeferredCallTask::Set) => { + self.deferred_call_task.insert(Some(DeferredCallTask::Set)); + return Err(ErrorCode::ALREADY); + } + Some(DeferredCallTask::Get) => { + self.deferred_call_task.insert(Some(DeferredCallTask::Get)); + return Err(ErrorCode::BUSY); + } + _ => (), + } + + self.enter_init_mode()?; + self.date_time_setup(date_time)?; + self.exit_init_mode()?; + + self.deferred_call_task.insert(Some(DeferredCallTask::Set)); + self.deferred_call.set(); + Ok(()) + } + + fn set_client(&self, client: &'a dyn DateTimeClient) { + self.client.set(client); + } +} diff --git a/chips/stm32f429zi/src/stm32f429zi_nvic.rs b/chips/stm32f429zi/src/stm32f429zi_nvic.rs index b9d62423ae..7252e9c121 100644 --- a/chips/stm32f429zi/src/stm32f429zi_nvic.rs +++ b/chips/stm32f429zi/src/stm32f429zi_nvic.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Named constants for NVIC ids specific to this chip pub const ETH: u32 = 61; diff --git a/chips/stm32f429zi/src/trng_registers.rs b/chips/stm32f429zi/src/trng_registers.rs new file mode 100644 index 0000000000..aecc157610 --- /dev/null +++ b/chips/stm32f429zi/src/trng_registers.rs @@ -0,0 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! True random number generator + +use kernel::utilities::StaticRef; +use stm32f4xx::trng::RngRegisters; + +pub(crate) const RNG_BASE: StaticRef = + unsafe { StaticRef::new(0x5006_0800 as *const RngRegisters) }; diff --git a/chips/stm32f446re/Cargo.toml b/chips/stm32f446re/Cargo.toml index 8b647c7a10..77c0fbe928 100644 --- a/chips/stm32f446re/Cargo.toml +++ b/chips/stm32f446re/Cargo.toml @@ -1,11 +1,18 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "stm32f446re" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] cortexm4 = { path = "../../arch/cortex-m4" } kernel = { path = "../../kernel" } -stm32f4xx = { path = "../stm32f4xx" } +stm32f4xx = { path = "../stm32f4xx", features = ["stm32f446"] } enum_primitive = { path = "../../libraries/enum_primitive" } + +[lints] +workspace = true diff --git a/chips/stm32f446re/src/chip_specs.rs b/chips/stm32f446re/src/chip_specs.rs new file mode 100644 index 0000000000..195ff246ea --- /dev/null +++ b/chips/stm32f446re/src/chip_specs.rs @@ -0,0 +1,36 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive SRL. +// +// Author: Ioan-Cristian CÎRSTEA + +//! STM32F446 specifications + +use stm32f4xx::chip_specific::clock_constants::{PllConstants, SystemClockConstants}; +use stm32f4xx::chip_specific::flash::{FlashChipSpecific, FlashLatency16}; + +pub enum Stm32f446Specs {} + +impl PllConstants for Stm32f446Specs { + const MIN_FREQ_MHZ: usize = 13; +} + +impl SystemClockConstants for Stm32f446Specs { + const APB1_FREQUENCY_LIMIT_MHZ: usize = 45; + const SYS_CLOCK_FREQUENCY_LIMIT_MHZ: usize = 168; +} + +impl FlashChipSpecific for Stm32f446Specs { + type FlashLatency = FlashLatency16; + + fn get_number_wait_cycles_based_on_frequency(frequency_mhz: usize) -> Self::FlashLatency { + match frequency_mhz { + 0..=30 => Self::FlashLatency::Latency0, + 31..=60 => Self::FlashLatency::Latency1, + 61..=90 => Self::FlashLatency::Latency2, + 91..=120 => Self::FlashLatency::Latency3, + 121..=150 => Self::FlashLatency::Latency4, + _ => Self::FlashLatency::Latency5, + } + } +} diff --git a/chips/stm32f446re/src/interrupt_service.rs b/chips/stm32f446re/src/interrupt_service.rs index 52ac6c7297..fb736b23c5 100644 --- a/chips/stm32f446re/src/interrupt_service.rs +++ b/chips/stm32f446re/src/interrupt_service.rs @@ -1,36 +1,37 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::chip_specs::Stm32f446Specs; use stm32f4xx::chip::Stm32f4xxDefaultPeripherals; -use stm32f4xx::deferred_calls::DeferredCallTask; pub struct Stm32f446reDefaultPeripherals<'a> { - pub stm32f4: Stm32f4xxDefaultPeripherals<'a>, + pub stm32f4: Stm32f4xxDefaultPeripherals<'a, Stm32f446Specs>, // Once implemented, place Stm32f446re specific peripherals here } impl<'a> Stm32f446reDefaultPeripherals<'a> { pub unsafe fn new( - rcc: &'a crate::rcc::Rcc, + clocks: &'a crate::clocks::Clocks<'a, Stm32f446Specs>, exti: &'a crate::exti::Exti<'a>, - dma: &'a crate::dma1::Dma1<'a>, + dma1: &'a crate::dma::Dma1<'a>, + dma2: &'a crate::dma::Dma2<'a>, ) -> Self { Self { - stm32f4: Stm32f4xxDefaultPeripherals::new(rcc, exti, dma), + stm32f4: Stm32f4xxDefaultPeripherals::new(clocks, exti, dma1, dma2), } } - // Necessary for setting up circular dependencies - pub fn init(&'a self) { + // Necessary for setting up circular dependencies & registering deferred + // calls + pub fn init(&'static self) { self.stm32f4.setup_circular_deps(); } } -impl<'a> kernel::platform::chip::InterruptService - for Stm32f446reDefaultPeripherals<'a> -{ +impl<'a> kernel::platform::chip::InterruptService for Stm32f446reDefaultPeripherals<'a> { unsafe fn service_interrupt(&self, interrupt: u32) -> bool { match interrupt { // put Stm32f446re specific interrupts here _ => self.stm32f4.service_interrupt(interrupt), } } - unsafe fn service_deferred_call(&self, task: DeferredCallTask) -> bool { - self.stm32f4.service_deferred_call(task) - } } diff --git a/chips/stm32f446re/src/lib.rs b/chips/stm32f446re/src/lib.rs index c526ffea09..3653af0405 100644 --- a/chips/stm32f446re/src/lib.rs +++ b/chips/stm32f446re/src/lib.rs @@ -1,11 +1,18 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + #![no_std] -pub use stm32f4xx::{chip, dbg, dma1, exti, gpio, nvic, rcc, spi, syscfg, tim2, usart}; +pub use stm32f4xx::{ + adc, chip, clocks, dbg, dma, exti, flash, gpio, nvic, rcc, spi, syscfg, tim2, usart, +}; +pub mod chip_specs; pub mod interrupt_service; pub mod stm32f446re_nvic; -use cortexm4::{generic_isr, unhandled_interrupt}; +use cortexm4::{unhandled_interrupt, CortexM4, CortexMVariant}; // STM32F446xx has total of 97 interrupts // Extracted from `CMSIS/Device/ST/STM32F4xx/Include/stm32f446xx.h` @@ -19,103 +26,103 @@ use cortexm4::{generic_isr, unhandled_interrupt}; // related discussion. #[cfg_attr(all(target_arch = "arm", target_os = "none"), used)] pub static IRQS: [unsafe extern "C" fn(); 97] = [ - generic_isr, // WWDG (0) - generic_isr, // PVD (1) - generic_isr, // TAMP_STAMP (2) - generic_isr, // RTC_WKUP (3) - generic_isr, // FLASH (4) - generic_isr, // RCC (5) - generic_isr, // EXTI0 (6) - generic_isr, // EXTI1 (7) - generic_isr, // EXTI2 (8) - generic_isr, // EXTI3 (9) - generic_isr, // EXTI4 (10) - generic_isr, // DMA1_Stream0 (11) - generic_isr, // DMA1_Stream1 (12) - generic_isr, // DMA1_Stream2 (13) - generic_isr, // DMA1_Stream3 (14) - generic_isr, // DMA1_Stream4 (15) - generic_isr, // DMA1_Stream5 (16) - generic_isr, // DMA1_Stream6 (17) - generic_isr, // ADC (18) - generic_isr, // CAN1_TX (19) - generic_isr, // CAN1_RX0 (20) - generic_isr, // CAN1_RX1 (21) - generic_isr, // CAN1_SCE (22) - generic_isr, // EXTI9_5 (23) - generic_isr, // TIM1_BRK_TIM9 (24) - generic_isr, // TIM1_UP_TIM10 (25) - generic_isr, // TIM1_TRG_COM_TIM11 (26) - generic_isr, // TIM1_CC (27) - generic_isr, // TIM2 (28) - generic_isr, // TIM3 (29) - generic_isr, // TIM4 (30) - generic_isr, // I2C1_EV (31) - generic_isr, // I2C1_ER (32) - generic_isr, // I2C2_EV (33) - generic_isr, // I2C2_ER (34) - generic_isr, // SPI1 (35) - generic_isr, // SPI2 (36) - generic_isr, // USART1 (37) - generic_isr, // USART2 (38) - generic_isr, // USART3 (39) - generic_isr, // EXTI15_10 (40) - generic_isr, // RTC_Alarm (41) - generic_isr, // OTG_FS_WKUP (42) - generic_isr, // TIM8_BRK_TIM12 (43) - generic_isr, // TIM8_UP_TIM13 (44) - generic_isr, // TIM8_TRG_COM_TIM14 (45) - generic_isr, // TIM8_CC (46) - generic_isr, // DMA1_Stream7 (47) - generic_isr, // FMC (48) - generic_isr, // SDIO (49) - generic_isr, // TIM5 (50) - generic_isr, // SPI3 (51) - generic_isr, // UART4 (52) - generic_isr, // UART5 (53) - generic_isr, // TIM6_DAC (54) - generic_isr, // TIM7 (55) - generic_isr, // DMA2_Stream0 (56) - generic_isr, // DMA2_Stream1 (57) - generic_isr, // DMA2_Stream2 (58) - generic_isr, // DMA2_Stream3 (59) - generic_isr, // DMA2_Stream4 (60) - unhandled_interrupt, // (61) - unhandled_interrupt, // (62) - generic_isr, // CAN2_TX (63) - generic_isr, // CAN2_RX0 (64) - generic_isr, // CAN2_RX1 (65) - generic_isr, // CAN2_SCE (66) - generic_isr, // OTG_FS (67) - generic_isr, // DMA2_Stream5 (68) - generic_isr, // DMA2_Stream6 (69) - generic_isr, // DMA2_Stream7 (70) - generic_isr, // USART6 (71) - generic_isr, // I2C3_EV (72) - generic_isr, // I2C3_ER (73) - generic_isr, // OTG_HS_EP1_OUT (74) - generic_isr, // OTG_HS_EP1_IN (75) - generic_isr, // OTG_HS_WKUP (76) - generic_isr, // OTG_HS (77) - generic_isr, // DCMI (78) - unhandled_interrupt, // (79) - unhandled_interrupt, // (80) - generic_isr, // FPU (81) - unhandled_interrupt, // (82) - unhandled_interrupt, // (83) - generic_isr, // SPI4 (84) - unhandled_interrupt, // (85) - unhandled_interrupt, // (86) - generic_isr, // SAI1 (87) - unhandled_interrupt, // (88) - unhandled_interrupt, // (89) - unhandled_interrupt, // (90) - generic_isr, // SAI2 (91) - generic_isr, // QUADSPI (92) - generic_isr, // CEC (93) - generic_isr, // SPDIF_RX (94) - generic_isr, // FMPI2C1_EV (95) - generic_isr, // FMPI2C1_ER (96) + CortexM4::GENERIC_ISR, // WWDG (0) + CortexM4::GENERIC_ISR, // PVD (1) + CortexM4::GENERIC_ISR, // TAMP_STAMP (2) + CortexM4::GENERIC_ISR, // RTC_WKUP (3) + CortexM4::GENERIC_ISR, // FLASH (4) + CortexM4::GENERIC_ISR, // RCC (5) + CortexM4::GENERIC_ISR, // EXTI0 (6) + CortexM4::GENERIC_ISR, // EXTI1 (7) + CortexM4::GENERIC_ISR, // EXTI2 (8) + CortexM4::GENERIC_ISR, // EXTI3 (9) + CortexM4::GENERIC_ISR, // EXTI4 (10) + CortexM4::GENERIC_ISR, // DMA1_Stream0 (11) + CortexM4::GENERIC_ISR, // DMA1_Stream1 (12) + CortexM4::GENERIC_ISR, // DMA1_Stream2 (13) + CortexM4::GENERIC_ISR, // DMA1_Stream3 (14) + CortexM4::GENERIC_ISR, // DMA1_Stream4 (15) + CortexM4::GENERIC_ISR, // DMA1_Stream5 (16) + CortexM4::GENERIC_ISR, // DMA1_Stream6 (17) + CortexM4::GENERIC_ISR, // ADC (18) + CortexM4::GENERIC_ISR, // CAN1_TX (19) + CortexM4::GENERIC_ISR, // CAN1_RX0 (20) + CortexM4::GENERIC_ISR, // CAN1_RX1 (21) + CortexM4::GENERIC_ISR, // CAN1_SCE (22) + CortexM4::GENERIC_ISR, // EXTI9_5 (23) + CortexM4::GENERIC_ISR, // TIM1_BRK_TIM9 (24) + CortexM4::GENERIC_ISR, // TIM1_UP_TIM10 (25) + CortexM4::GENERIC_ISR, // TIM1_TRG_COM_TIM11 (26) + CortexM4::GENERIC_ISR, // TIM1_CC (27) + CortexM4::GENERIC_ISR, // TIM2 (28) + CortexM4::GENERIC_ISR, // TIM3 (29) + CortexM4::GENERIC_ISR, // TIM4 (30) + CortexM4::GENERIC_ISR, // I2C1_EV (31) + CortexM4::GENERIC_ISR, // I2C1_ER (32) + CortexM4::GENERIC_ISR, // I2C2_EV (33) + CortexM4::GENERIC_ISR, // I2C2_ER (34) + CortexM4::GENERIC_ISR, // SPI1 (35) + CortexM4::GENERIC_ISR, // SPI2 (36) + CortexM4::GENERIC_ISR, // USART1 (37) + CortexM4::GENERIC_ISR, // USART2 (38) + CortexM4::GENERIC_ISR, // USART3 (39) + CortexM4::GENERIC_ISR, // EXTI15_10 (40) + CortexM4::GENERIC_ISR, // RTC_Alarm (41) + CortexM4::GENERIC_ISR, // OTG_FS_WKUP (42) + CortexM4::GENERIC_ISR, // TIM8_BRK_TIM12 (43) + CortexM4::GENERIC_ISR, // TIM8_UP_TIM13 (44) + CortexM4::GENERIC_ISR, // TIM8_TRG_COM_TIM14 (45) + CortexM4::GENERIC_ISR, // TIM8_CC (46) + CortexM4::GENERIC_ISR, // DMA1_Stream7 (47) + CortexM4::GENERIC_ISR, // FMC (48) + CortexM4::GENERIC_ISR, // SDIO (49) + CortexM4::GENERIC_ISR, // TIM5 (50) + CortexM4::GENERIC_ISR, // SPI3 (51) + CortexM4::GENERIC_ISR, // UART4 (52) + CortexM4::GENERIC_ISR, // UART5 (53) + CortexM4::GENERIC_ISR, // TIM6_DAC (54) + CortexM4::GENERIC_ISR, // TIM7 (55) + CortexM4::GENERIC_ISR, // DMA2_Stream0 (56) + CortexM4::GENERIC_ISR, // DMA2_Stream1 (57) + CortexM4::GENERIC_ISR, // DMA2_Stream2 (58) + CortexM4::GENERIC_ISR, // DMA2_Stream3 (59) + CortexM4::GENERIC_ISR, // DMA2_Stream4 (60) + unhandled_interrupt, // (61) + unhandled_interrupt, // (62) + CortexM4::GENERIC_ISR, // CAN2_TX (63) + CortexM4::GENERIC_ISR, // CAN2_RX0 (64) + CortexM4::GENERIC_ISR, // CAN2_RX1 (65) + CortexM4::GENERIC_ISR, // CAN2_SCE (66) + CortexM4::GENERIC_ISR, // OTG_FS (67) + CortexM4::GENERIC_ISR, // DMA2_Stream5 (68) + CortexM4::GENERIC_ISR, // DMA2_Stream6 (69) + CortexM4::GENERIC_ISR, // DMA2_Stream7 (70) + CortexM4::GENERIC_ISR, // USART6 (71) + CortexM4::GENERIC_ISR, // I2C3_EV (72) + CortexM4::GENERIC_ISR, // I2C3_ER (73) + CortexM4::GENERIC_ISR, // OTG_HS_EP1_OUT (74) + CortexM4::GENERIC_ISR, // OTG_HS_EP1_IN (75) + CortexM4::GENERIC_ISR, // OTG_HS_WKUP (76) + CortexM4::GENERIC_ISR, // OTG_HS (77) + CortexM4::GENERIC_ISR, // DCMI (78) + unhandled_interrupt, // (79) + unhandled_interrupt, // (80) + CortexM4::GENERIC_ISR, // FPU (81) + unhandled_interrupt, // (82) + unhandled_interrupt, // (83) + CortexM4::GENERIC_ISR, // SPI4 (84) + unhandled_interrupt, // (85) + unhandled_interrupt, // (86) + CortexM4::GENERIC_ISR, // SAI1 (87) + unhandled_interrupt, // (88) + unhandled_interrupt, // (89) + unhandled_interrupt, // (90) + CortexM4::GENERIC_ISR, // SAI2 (91) + CortexM4::GENERIC_ISR, // QUADSPI (92) + CortexM4::GENERIC_ISR, // CEC (93) + CortexM4::GENERIC_ISR, // SPDIF_RX (94) + CortexM4::GENERIC_ISR, // FMPI2C1_EV (95) + CortexM4::GENERIC_ISR, // FMPI2C1_ER (96) ]; pub unsafe fn init() { diff --git a/chips/stm32f446re/src/stm32f446re_nvic.rs b/chips/stm32f446re/src/stm32f446re_nvic.rs index 3d23da9189..1139be1bba 100644 --- a/chips/stm32f446re/src/stm32f446re_nvic.rs +++ b/chips/stm32f446re/src/stm32f446re_nvic.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Named constants for NVIC ids specific to this chip pub const SAI2: u32 = 91; diff --git a/chips/stm32f4xx/Cargo.toml b/chips/stm32f4xx/Cargo.toml index 04d9a470f8..dcc4c0e45b 100644 --- a/chips/stm32f4xx/Cargo.toml +++ b/chips/stm32f4xx/Cargo.toml @@ -1,10 +1,33 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "stm32f4xx" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] cortexm4 = { path = "../../arch/cortex-m4" } enum_primitive = { path = "../../libraries/enum_primitive" } kernel = { path = "../../kernel" } + +[features] +# Currently, Tock supports only these chips. +# When a new chip is added, add its identifier here and inside +# the chip's crate as a feature for the dependency. See +# stm32f429zi crate for an example. +stm32f401 = [] +stm32f412 = [] +stm32f429 = [] +stm32f446 = [] + +# These are unused and unsupported +stm32f410 = [] +stm32f411 = [] +stm32f413 = [] +stm32f423 = [] + +[lints] +workspace = true diff --git a/chips/stm32f4xx/src/adc.rs b/chips/stm32f4xx/src/adc.rs index 5019d62f3c..91b1c8f427 100644 --- a/chips/stm32f4xx/src/adc.rs +++ b/chips/stm32f4xx/src/adc.rs @@ -1,4 +1,8 @@ -use crate::rcc; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::clocks::{phclk, Stm32f4Clocks}; use core::cell::Cell; use kernel::hil; use kernel::platform::chip::ClockInterface; @@ -8,9 +12,6 @@ use kernel::utilities::registers::{register_bitfields, ReadOnly, ReadWrite}; use kernel::utilities::StaticRef; use kernel::ErrorCode; -pub trait EverythingClient: hil::adc::Client + hil::adc::HighSpeedClient {} -impl EverythingClient for C {} - #[repr(C)] struct AdcRegisters { sr: ReadWrite, @@ -309,17 +310,17 @@ pub struct Adc<'a> { common_registers: StaticRef, clock: AdcClock<'a>, status: Cell, - client: OptionalCell<&'static dyn hil::adc::Client>, + client: OptionalCell<&'a dyn hil::adc::Client>, } impl<'a> Adc<'a> { - pub const fn new(rcc: &'a rcc::Rcc) -> Adc { + pub const fn new(clocks: &'a dyn Stm32f4Clocks) -> Adc { Adc { registers: ADC1_BASE, common_registers: ADC_COMMON_BASE, - clock: AdcClock(rcc::PeripheralClock::new( - rcc::PeripheralClockType::APB2(rcc::PCLK2::ADC1), - rcc, + clock: AdcClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::APB2(phclk::PCLK2::ADC1), + clocks, )), status: Cell::new(ADCStatus::Off), client: OptionalCell::empty(), @@ -368,7 +369,7 @@ impl<'a> Adc<'a> { } } -struct AdcClock<'a>(rcc::PeripheralClock<'a>); +struct AdcClock<'a>(phclk::PeripheralClock<'a>); impl ClockInterface for AdcClock<'_> { fn is_enabled(&self) -> bool { @@ -384,7 +385,7 @@ impl ClockInterface for AdcClock<'_> { } } -impl hil::adc::Adc for Adc<'_> { +impl<'a> hil::adc::Adc<'a> for Adc<'a> { type Channel = Channel; fn sample(&self, channel: &Self::Channel) -> Result<(), ErrorCode> { @@ -426,13 +427,13 @@ impl hil::adc::Adc for Adc<'_> { Some(3300) } - fn set_client(&self, client: &'static dyn hil::adc::Client) { + fn set_client(&self, client: &'a dyn hil::adc::Client) { self.client.set(client); } } /// Not yet supported -impl hil::adc::AdcHighSpeed for Adc<'_> { +impl<'a> hil::adc::AdcHighSpeed<'a> for Adc<'a> { /// Capture buffered samples from the ADC continuously at a given /// frequency, calling the client whenever a buffer fills up. The client is /// then expected to either stop sampling or provide an additional buffer @@ -478,4 +479,6 @@ impl hil::adc::AdcHighSpeed for Adc<'_> { ) -> Result<(Option<&'static mut [u16]>, Option<&'static mut [u16]>), ErrorCode> { Err(ErrorCode::NOSUPPORT) } + + fn set_highspeed_client(&self, _client: &'a dyn hil::adc::HighSpeedClient) {} } diff --git a/chips/stm32f4xx/src/can.rs b/chips/stm32f4xx/src/can.rs new file mode 100644 index 0000000000..fe7c108ab0 --- /dev/null +++ b/chips/stm32f4xx/src/can.rs @@ -0,0 +1,1428 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022 +// Copyright OxidOS Automotive SRL 2022 +// +// Author: Teona Severin + +//! Low-level CAN driver for STM32F4XX chips +//! + +use crate::clocks::{phclk, Stm32f4Clocks}; +use core::cell::Cell; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; +use kernel::hil::can::{self, StandardBitTiming}; +use kernel::platform::chip::ClockInterface; +use kernel::utilities::cells::{OptionalCell, TakeCell}; +use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; +use kernel::utilities::registers::{register_bitfields, register_structs, ReadWrite}; +use kernel::utilities::StaticRef; + +pub const BRP_MIN_STM32: u32 = 0; +pub const BRP_MAX_STM32: u32 = 1023; + +pub const TX_MAILBOX_COUNT: usize = 3; +pub const RX_MAILBOX_COUNT: usize = 2; +pub const FILTER_COUNT: usize = 56; + +register_structs! { + pub Registers { + /// CAN control and status registers + (0x000 => can_mcr: ReadWrite), + /// CAN master status register + (0x004 => can_msr: ReadWrite), + /// CAN transmit status register + (0x008 => can_tsr: ReadWrite), + /// CAN receive FIFO 0 register + (0x00c => can_rf0r: ReadWrite), + /// CAN receive FIFO 1 registers + (0x010 => can_rf1r: ReadWrite), + /// CAN interrupt enable register + (0x014 => can_ier: ReadWrite), + /// CAN error status register + (0x018 => can_esr: ReadWrite), + /// CAN bit timing register + (0x01c => can_btr: ReadWrite), + (0x020 => _reserved0), + /// + /// + /// CAN MAILBOX REGISTERS + /// + /// CAN TX mailbox identifier registers + (0x180 => can_tx_mailbox: [TransmitMailBox; TX_MAILBOX_COUNT]), + /// CAN RX mailbox identifier registers + (0x1b0 => can_rx_mailbox: [ReceiveMailBox; RX_MAILBOX_COUNT]), + (0x1d0 => _reserved1), + /// + /// + /// CAN FILTER REGISTERS + /// + /// + /// CAN filter master register + (0x200 => can_fmr: ReadWrite), + /// CAN filter mode register + (0x204 => can_fm1r: ReadWrite), + (0x208 => _reserved2), + /// CAN filter scale register + (0x20c => can_fs1r: ReadWrite), + (0x210 => _reserved3), + /// CAN filter FIFO assignment register + (0x214 => can_ffa1r: ReadWrite), + (0x218 => _reserved4), + /// CAN filter activation register + (0x21c => can_fa1r: ReadWrite), + (0x220 => _reserved5), + /// Filter bank 0-27 for register 1-2 + (0x240 => can_firx: [ReadWrite; FILTER_COUNT]), + (0x320 => @END), + }, + + TransmitMailBox { + (0x00 => can_tir: ReadWrite), + (0x04 => can_tdtr: ReadWrite), + (0x08 => can_tdlr: ReadWrite), + (0x0c => can_tdhr: ReadWrite), + (0x010 => @END), + }, + + ReceiveMailBox { + (0x00 => can_rir: ReadWrite), + (0x04 => can_rdtr: ReadWrite), + (0x08 => can_rdlr: ReadWrite), + (0x0c => can_rdhr: ReadWrite), + (0x010 => @END), + } +} + +register_bitfields![u32, + CAN_MCR [ + /// Debug freeze + DBF OFFSET(16) NUMBITS(1) [], + /// bcXAN software master reset + RESET OFFSET(15) NUMBITS(1) [], + /// Time triggered communication mode + TTCM OFFSET(7) NUMBITS(1) [], + /// Automatic bus-off management + ABOM OFFSET(6) NUMBITS(1) [], + /// Automatic wakeup mode + AWUM OFFSET(5) NUMBITS(1) [], + /// No automatic retransmission + NART OFFSET(4) NUMBITS(1) [], + /// Receive FIFO locked mode + RFLM OFFSET(3) NUMBITS(1) [], + /// Transmit FIFO prioritY + TXFP OFFSET(2) NUMBITS(1) [], + /// Sleep mode request + SLEEP OFFSET(1) NUMBITS(1) [], + /// Initialization request + INRQ OFFSET(0) NUMBITS(1) [] + ], + CAN_MSR [ + /// CAN Rx signal + RX OFFSET(11) NUMBITS(1) [], + /// Last sample point + SAMP OFFSET(10) NUMBITS(1) [], + /// Receive mode + RXM OFFSET(9) NUMBITS(1) [], + /// Transmit mode + TXM OFFSET(8) NUMBITS(1) [], + /// Sleep acknowledge interrupt + SLAKI OFFSET(4) NUMBITS(1) [], + /// Wakeup interrupt + WKUI OFFSET(3) NUMBITS(1) [], + /// Error interrupt + ERRI OFFSET(2) NUMBITS(1) [], + /// Sleep acknowledge + SLAK OFFSET(1) NUMBITS(1) [], + /// Initialization acknowledge + INAK OFFSET(0) NUMBITS(1) [] + ], + CAN_TSR [ + /// Lowest priority flag for mailbox 2 + LOW2 OFFSET(31) NUMBITS(1) [], + /// Lowest priority flag for mailbox 1 + LOW1 OFFSET(30) NUMBITS(1) [], + /// Lowest priority flag for mailbox 0 + LOW0 OFFSET(29) NUMBITS(1) [], + /// Transmit mailbox 2 empty + TME2 OFFSET(28) NUMBITS(1) [], + /// Transmit mailbox 1 empty + TME1 OFFSET(27) NUMBITS(1) [], + /// Transmit mailbox 0 empty + TME0 OFFSET(26) NUMBITS(1) [], + /// Mailbox code + CODE OFFSET(24) NUMBITS(2) [], + /// Abort request for mailbox 2 + ABRQ2 OFFSET(23) NUMBITS(1) [], + /// Transmission error of mailbox 2 + TERR2 OFFSET(19) NUMBITS(1) [], + /// Arbitration lost for mailbox 2 + ALST2 OFFSET(18) NUMBITS(1) [], + /// Transmission OK of mailbox 2 + TXOK2 OFFSET(17) NUMBITS(1) [], + /// Request completed mailbox 2 + RQCP2 OFFSET(16) NUMBITS(1) [], + /// Abort request for mailbox 1 + ABRQ1 OFFSET(15) NUMBITS(1) [], + /// Transmission error of mailbox 1 + TERR1 OFFSET(11) NUMBITS(1) [], + /// Arbitration lost for mailbox 1 + ALST1 OFFSET(10) NUMBITS(1) [], + /// Transmission OK of mailbox 1 + TXOK1 OFFSET(9) NUMBITS(1) [], + /// Request completed mailbox 1 + RQCP1 OFFSET(8) NUMBITS(1) [], + /// Abort request for mailbox 0 + ABRQ0 OFFSET(7) NUMBITS(1) [], + /// Transmission error of mailbox 0 + TERR0 OFFSET(3) NUMBITS(1) [], + /// Arbitration lost for mailbox 0 + ALST0 OFFSET(2) NUMBITS(1) [], + /// Transmission OK of mailbox 0 + TXOK0 OFFSET(1) NUMBITS(1) [], + /// Request completed mailbox 0 + RQCP0 OFFSET(0) NUMBITS(1) [] + ], + CAN_RF0R [ + /// Release FIFO 0 output mailbox + RFOM0 OFFSET(5) NUMBITS(1) [], + /// FIFO 0 overrun + FOVR0 OFFSET(4) NUMBITS(1) [], + /// FIFO 0 full + FULL0 OFFSET(3) NUMBITS(1) [], + /// FIFO 0 message pending + FMP0 OFFSET(0) NUMBITS(2) [] + ], + CAN_RF1R [ + /// Release FIFO 1 output mailbox + RFOM1 OFFSET(5) NUMBITS(1) [], + /// FIFO 1 overrun + FOVR1 OFFSET(4) NUMBITS(1) [], + /// FIFO 1 full + FULL1 OFFSET(3) NUMBITS(1) [], + /// FIFO 1 message pending + FMP1 OFFSET(0) NUMBITS(2) [] + ], + CAN_IER [ + /// Sleep interrupt enable + SLKIE OFFSET(17) NUMBITS(1) [], + /// Wakeup interrupt enable + WKUIE OFFSET(16) NUMBITS(1) [], + /// Error interrupt enable + ERRIE OFFSET(15) NUMBITS(1) [], + /// Last error code interrupt enable + LECIE OFFSET(11) NUMBITS(1) [], + /// Bus-off interrupt enable + BOFIE OFFSET(10) NUMBITS(1) [], + /// Error passive interrupt enable + EPVIE OFFSET(9) NUMBITS(1) [], + /// Error warning interrupt enable + EWGIE OFFSET(8) NUMBITS(1) [], + /// FIFO 1 overrun interrupt enable + FOVIE1 OFFSET(6) NUMBITS(1) [], + /// FIFO 1 full interrupt enable + FFIE1 OFFSET(5) NUMBITS(1) [], + /// FIFO 1 message pending interrupt enable + FMPIE1 OFFSET(4) NUMBITS(1) [], + /// FIFO 0 overrun interrupt enable + FOVIE0 OFFSET(3) NUMBITS(1) [], + /// FIFO 0 full interrupt enable + FFIE0 OFFSET(2) NUMBITS(1) [], + /// FIFO 0 message pending interrupt enable + FMPIE0 OFFSET(1) NUMBITS(1) [], + /// Transmit mailbox empty interrupt enable + TMEIE OFFSET(0) NUMBITS(1) [] + ], + CAN_ESR [ + /// Receive error counter + REC OFFSET(24) NUMBITS(8) [], + /// Least significant byte of the 9-bit transmit error counter + TEC OFFSET(16) NUMBITS(8) [], + /// Last error code + LEC OFFSET(4) NUMBITS(3) [ + NoError = 0, + StuffError = 1, + FormError = 2, + AcknowledgmentError = 3, + BitRecessiveError = 4, + BitDominantError = 5, + CrcError = 6, + SetBySoftware = 7 + ], + /// Bus-off flag + BOFF OFFSET(2) NUMBITS(1) [], + /// Error passive flag + EPVF OFFSET(1) NUMBITS(1) [], + /// Error warning flag + EWGF OFFSET(0) NUMBITS(1) [] + ], + CAN_BTR [ + /// Silent mode (debug) + SILM OFFSET(31) NUMBITS(1) [], + /// Loop back mode (debug) + LBKM OFFSET(30) NUMBITS(1) [], + /// Resynchronization jump width + SJW OFFSET(24) NUMBITS(2) [], + /// Time segment 2 + TS2 OFFSET(20) NUMBITS(3) [], + /// Time segment 1 + TS1 OFFSET(16) NUMBITS(4) [], + /// Baud rate prescaler + BRP OFFSET(0) NUMBITS(10) [] + ], + /// + /// + /// CAN mailbox registers + /// + /// + CAN_TIxR [ + /// Standard identifier or extended identifier + STID OFFSET(21) NUMBITS(11) [], + /// Extended identifier + EXID OFFSET(3) NUMBITS(18) [], + /// Identifier extension + IDE OFFSET(2) NUMBITS(1) [], + /// Remote transmission request + RTR OFFSET(1) NUMBITS(1) [], + /// Transmit mailbox request + TXRQ OFFSET(0) NUMBITS(1) [] + ], + CAN_TDTxR [ + /// Message time stamp + TIME OFFSET(16) NUMBITS(16) [], + /// Transmit global time + TGT OFFSET(8) NUMBITS(1) [], + /// Data length code + DLC OFFSET(0) NUMBITS(4) [] + ], + CAN_TDLxR [ + /// Data byte 3 + DATA3 OFFSET(24) NUMBITS(8) [], + /// Data byte 2 + DATA2 OFFSET(16) NUMBITS(8) [], + /// Data byte 1 + DATA1 OFFSET(8) NUMBITS(8) [], + /// Data byte 0 + DATA0 OFFSET(0) NUMBITS(8) [] + ], + CAN_TDHxR [ + /// Data byte 7 + DATA7 OFFSET(24) NUMBITS(8) [], + /// Data byte 6 + DATA6 OFFSET(16) NUMBITS(8) [], + /// Data byte 5 + DATA5 OFFSET(8) NUMBITS(8) [], + /// Data byte 4 + DATA4 OFFSET(0) NUMBITS(8) [] + ], + CAN_RIxR [ + /// Standard identifier or extended identifier + STID OFFSET(21) NUMBITS(11) [], + /// Extended identifier + EXID OFFSET(3) NUMBITS(18) [], + /// Identifier extension + IDE OFFSET(2) NUMBITS(1) [], + /// Remote transmission request + RTR OFFSET(1) NUMBITS(1) [] + ], + CAN_RDTxR [ + /// Message time stamp + TIME OFFSET(16) NUMBITS(16) [], + /// Filter match index + FMI OFFSET(8) NUMBITS(8) [], + /// Data length code + DLC OFFSET(0) NUMBITS(4) [] + ], + CAN_RDLxR [ + /// Data byte 3 + DATA3 OFFSET(24) NUMBITS(8) [], + /// Data byte 2 + DATA2 OFFSET(16) NUMBITS(8) [], + /// Data byte 1 + DATA1 OFFSET(8) NUMBITS(8) [], + /// Data byte 0 + DATA0 OFFSET(0) NUMBITS(8) [] + ], + CAN_RDHxR [ + /// Data byte 7 + DATA7 OFFSET(24) NUMBITS(8) [], + /// Data byte 6 + DATA6 OFFSET(16) NUMBITS(8) [], + /// Data byte 5 + DATA5 OFFSET(8) NUMBITS(8) [], + /// Data byte 4 + DATA4 OFFSET(0) NUMBITS(8) [] + ], + /// + /// + /// CAN filter registers + /// + /// + CAN_FMR [ + /// CAN start bank + CANSB OFFSET(8) NUMBITS(6) [], + /// Filter initialization mode + FINIT OFFSET(0) NUMBITS(1) [] + ], + /// CAN filter mode register + CAN_FM1R [ + /// Filter mode + FBM OFFSET(0) NUMBITS(28) [] + ], + CAN_FS1R [ + /// Filter scale configuration + FSC OFFSET(0) NUMBITS(28) [] + ], + CAN_FFA1R [ + /// Filter FIFO assignment for filter x + FFA OFFSET(0) NUMBITS(28) [] + ], + CAN_FA1R [ + /// Filter active + FACT OFFSET(0) NUMBITS(28) [] + ], + CAN_FiRx [ + /// Filter bits + FB OFFSET(0) NUMBITS(32) [] + ] +]; + +#[derive(Copy, Clone, PartialEq)] +enum CanState { + Initialization, + Normal, + Sleep, + RunningError(can::Error), +} + +// The 4 possbile actions that the deferred call task can do. +#[derive(Copy, Clone, PartialEq)] +enum AsyncAction { + Enable, + AbortReceive, + Disabled, + EnableError(kernel::ErrorCode), +} + +#[repr(u32)] +enum BitSegment1 { + CanBtrTs1Min = 0b0000, + CanBtrTs1Max = 0b1111, +} + +#[repr(u32)] +enum BitSegment2 { + CanBtrTs2Min = 0b0000, + CanBtrTs2Max = 0b0111, +} + +#[repr(u32)] +enum SynchronizationJumpWidth { + CanBtrSjwMin = 0b00, + CanBtrSjwMax = 0b11, +} + +#[derive(Copy, Clone, PartialEq)] +pub enum CanInterruptMode { + TransmitInterrupt, + Fifo0Interrupt, + Fifo1Interrupt, + ErrorAndStatusChangeInterrupt, +} + +impl From for can::State { + fn from(state: CanState) -> Self { + match state { + CanState::Initialization | CanState::Sleep => can::State::Disabled, + CanState::Normal => can::State::Running, + CanState::RunningError(err) => can::State::Error(err), + } + } +} + +pub struct Can<'a> { + registers: StaticRef, + clock: CanClock<'a>, + can_state: Cell, + error_interrupt_counter: Cell, + fifo0_interrupt_counter: Cell, + fifo1_interrupt_counter: Cell, + failed_messages: Cell, + + // communication parameters + automatic_retransmission: Cell, + automatic_wake_up: Cell, + operating_mode: OptionalCell, + bit_timing: OptionalCell, + + // clients + controller_client: OptionalCell<&'static dyn can::ControllerClient>, + receive_client: + OptionalCell<&'static dyn can::ReceiveClient<{ can::STANDARD_CAN_PACKET_SIZE }>>, + transmit_client: + OptionalCell<&'static dyn can::TransmitClient<{ can::STANDARD_CAN_PACKET_SIZE }>>, + + // buffers for transmission and reception + rx_buffer: TakeCell<'static, [u8; can::STANDARD_CAN_PACKET_SIZE]>, + tx_buffer: TakeCell<'static, [u8; can::STANDARD_CAN_PACKET_SIZE]>, + + deferred_call: DeferredCall, + // deferred call task action + deferred_action: OptionalCell, +} + +impl<'a> Can<'a> { + pub fn new(clocks: &'a dyn Stm32f4Clocks, registers: StaticRef) -> Can<'a> { + Can { + registers, + clock: CanClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::APB1(phclk::PCLK1::CAN1), + clocks, + )), + can_state: Cell::new(CanState::Sleep), + error_interrupt_counter: Cell::new(0), + fifo0_interrupt_counter: Cell::new(0), + fifo1_interrupt_counter: Cell::new(0), + failed_messages: Cell::new(0), + automatic_retransmission: Cell::new(false), + automatic_wake_up: Cell::new(false), + operating_mode: OptionalCell::empty(), + bit_timing: OptionalCell::empty(), + controller_client: OptionalCell::empty(), + receive_client: OptionalCell::empty(), + transmit_client: OptionalCell::empty(), + rx_buffer: TakeCell::empty(), + tx_buffer: TakeCell::empty(), + deferred_call: DeferredCall::new(), + deferred_action: OptionalCell::empty(), + } + } + + /// This function is used for busy waiting and checks if the closure + /// received as an argument returns a true value for `times` times. + /// + /// Usage: check is the INAK bit in the CAN_MSR is set for 200_000 times. + /// ```ignore + /// Can::wait_for(200_000, || self.registers.can_msr.is_set(CAN_MSR::INAK)) + /// ``` + fn wait_for(times: usize, f: impl Fn() -> bool) -> bool { + for _ in 0..times { + if f() { + return true; + } + } + + false + } + + /// Enable the peripheral with the stored communication parameters: + /// bit timing settings and communication mode + pub fn enable(&self) -> Result<(), kernel::ErrorCode> { + // leave Sleep Mode + self.registers.can_mcr.modify(CAN_MCR::SLEEP::CLEAR); + + // request to enter the initialization mode + self.registers.can_mcr.modify(CAN_MCR::INRQ::SET); + + // After requesting to enter the initialization mode, the driver + // must wait for ACK from the peripheral - the INAK bit to be set + // (as explained in RM0090 Reference Manual, Chapter 32.4.1). + // This is done by checking the INAK bit 20_000 times or until it is set. + if !Can::wait_for(20000, || self.registers.can_msr.is_set(CAN_MSR::INAK)) { + return Err(kernel::ErrorCode::FAIL); + } + + self.can_state.set(CanState::Initialization); + + // After requesting to enter the initialization mode, the driver + // must wait for ACK from the peripheral - the SLAK bit to be cleared + // (as explained in RM0090 Reference Manual, Chapter 32.4, Figure 336). + // This is done by checking the SLAK bit 20_000 times or until it is cleared. + if !Can::wait_for(20000, || !self.registers.can_msr.is_set(CAN_MSR::SLAK)) { + return Err(kernel::ErrorCode::FAIL); + } + + // set communication mode + self.registers.can_mcr.modify(CAN_MCR::TTCM::CLEAR); + self.registers.can_mcr.modify(CAN_MCR::ABOM::CLEAR); + self.registers.can_mcr.modify(CAN_MCR::RFLM::CLEAR); + self.registers.can_mcr.modify(CAN_MCR::TXFP::CLEAR); + + match self.automatic_retransmission.get() { + true => self.registers.can_mcr.modify(CAN_MCR::AWUM::SET), + false => self.registers.can_mcr.modify(CAN_MCR::AWUM::CLEAR), + } + + match self.automatic_wake_up.get() { + true => self.registers.can_mcr.modify(CAN_MCR::NART::CLEAR), + false => self.registers.can_mcr.modify(CAN_MCR::NART::SET), + } + + if let Some(operating_mode_settings) = self.operating_mode.get() { + match operating_mode_settings { + can::OperationMode::Loopback => self.registers.can_btr.modify(CAN_BTR::LBKM::SET), + can::OperationMode::Monitoring => self.registers.can_btr.modify(CAN_BTR::SILM::SET), + can::OperationMode::Freeze => return Err(kernel::ErrorCode::INVAL), + _ => {} + } + } + + // set bit timing mode + if let Some(bit_timing_settings) = self.bit_timing.get() { + self.registers + .can_btr + .modify(CAN_BTR::TS1.val(bit_timing_settings.segment1 as u32)); + self.registers + .can_btr + .modify(CAN_BTR::TS2.val(bit_timing_settings.segment2 as u32)); + self.registers + .can_btr + .modify(CAN_BTR::SJW.val(bit_timing_settings.sync_jump_width)); + self.registers + .can_btr + .modify(CAN_BTR::BRP.val(bit_timing_settings.baud_rate_prescaler)); + } else { + self.enter_sleep_mode(); + return Err(kernel::ErrorCode::INVAL); + } + + Ok(()) + } + + /// Configure a filter to receive messages + pub fn config_filter(&self, filter_info: can::FilterParameters, enable: bool) { + // get position of the filter number + let filter_number = 1 << filter_info.number; + + // start filter configuration + self.registers.can_fmr.modify(CAN_FMR::FINIT::SET); + + // request filter number filter_number + self.registers.can_fa1r.modify( + CAN_FA1R::FACT.val(self.registers.can_fa1r.read(CAN_FA1R::FACT) & !filter_number), + ); + + // request filter width to be 32 or 16 bits + match filter_info.scale_bits { + can::ScaleBits::Bits16 => { + self.registers.can_fs1r.modify( + CAN_FS1R::FSC.val(self.registers.can_fs1r.read(CAN_FS1R::FSC) | filter_number), + ); + } + can::ScaleBits::Bits32 => { + self.registers.can_fs1r.modify( + CAN_FS1R::FSC.val(self.registers.can_fs1r.read(CAN_FS1R::FSC) & !filter_number), + ); + } + } + + self.registers.can_firx[(filter_info.number as usize) * 2].modify(CAN_FiRx::FB.val(0)); + self.registers.can_firx[(filter_info.number as usize) * 2 + 1].modify(CAN_FiRx::FB.val(0)); + + // request filter mode to be mask or list + match filter_info.identifier_mode { + can::IdentifierMode::List => { + self.registers.can_fm1r.modify( + CAN_FM1R::FBM.val(self.registers.can_fm1r.read(CAN_FM1R::FBM) | filter_number), + ); + } + can::IdentifierMode::Mask => { + self.registers.can_fm1r.modify( + CAN_FM1R::FBM.val(self.registers.can_fm1r.read(CAN_FM1R::FBM) & !filter_number), + ); + } + } + + // request fifo0 or fifo1 + if filter_info.fifo_number == 0 { + self.registers.can_ffa1r.modify( + CAN_FFA1R::FFA.val(self.registers.can_ffa1r.read(CAN_FFA1R::FFA) & !filter_number), + ); + } else { + self.registers.can_ffa1r.modify( + CAN_FFA1R::FFA.val(self.registers.can_ffa1r.read(CAN_FFA1R::FFA) | filter_number), + ); + } + + if enable { + self.registers.can_fa1r.modify( + CAN_FA1R::FACT.val(self.registers.can_fa1r.read(CAN_FA1R::FACT) | filter_number), + ); + } else { + self.registers.can_fa1r.modify( + CAN_FA1R::FACT.val(self.registers.can_fa1r.read(CAN_FA1R::FACT) & !filter_number), + ); + } + } + + pub fn enable_filter_config(&self) { + // activate the filter configuration + self.registers.can_fmr.modify(CAN_FMR::FINIT::CLEAR); + } + + pub fn enter_normal_mode(&self) -> Result<(), kernel::ErrorCode> { + // request to enter normal mode by clearing INRQ bit + self.registers.can_mcr.modify(CAN_MCR::INRQ::CLEAR); + + // After requesting to enter the normal mode, the driver + // must wait for ACK from the peripheral - the INAK bit to be cleared + // (as explained in RM0090 Reference Manual, Chapter 32.4.2). + // This is done by checking the INAK bit 20_000 times or until it is cleared. + if !Can::wait_for(20000, || !self.registers.can_msr.is_set(CAN_MSR::INAK)) { + return Err(kernel::ErrorCode::FAIL); + } + + self.can_state.set(CanState::Normal); + Ok(()) + } + + pub fn enter_sleep_mode(&self) { + // request to enter sleep mode by setting SLEEP bit + self.disable_irqs(); + self.registers.can_mcr.modify(CAN_MCR::SLEEP::SET); + self.can_state.set(CanState::Sleep); + } + + /// This function sends an 8-byte message + pub fn send_8byte_message( + &self, + id: can::Id, + dlc: usize, + rtr: u8, + ) -> Result<(), kernel::ErrorCode> { + self.enable_irq(CanInterruptMode::ErrorAndStatusChangeInterrupt); + if self.can_state.get() == CanState::Normal { + if let Some(tx_mailbox) = self.find_empty_mailbox() { + // set extended or standard id in registers + match id { + can::Id::Standard(id) => { + self.registers.can_tx_mailbox[tx_mailbox] + .can_tir + .modify(CAN_TIxR::IDE::CLEAR); + self.registers.can_tx_mailbox[tx_mailbox] + .can_tir + .modify(CAN_TIxR::STID.val(id as u32 & 0xeff)); + self.registers.can_tx_mailbox[tx_mailbox] + .can_tir + .modify(CAN_TIxR::EXID.val(0)); + } + can::Id::Extended(id) => { + self.registers.can_tx_mailbox[tx_mailbox] + .can_tir + .modify(CAN_TIxR::IDE::SET); + self.registers.can_tx_mailbox[tx_mailbox] + .can_tir + .modify(CAN_TIxR::STID.val((id & 0xffc0000) >> 18)); + self.registers.can_tx_mailbox[tx_mailbox] + .can_tir + .modify(CAN_TIxR::EXID.val(id & 0x003fffff)); + } + } + // write rtr + self.registers.can_tx_mailbox[tx_mailbox] + .can_tir + .modify(CAN_TIxR::RTR.val(rtr.into())); + // write dlc + self.registers.can_tx_mailbox[tx_mailbox] + .can_tdtr + .modify(CAN_TDTxR::DLC.val(dlc as u32)); + // write first 4 bytes of the data + match self.tx_buffer.map(|tx| { + self.registers.can_tx_mailbox[tx_mailbox] + .can_tdlr + .modify(CAN_TDLxR::DATA0.val(tx[0].into())); + self.registers.can_tx_mailbox[tx_mailbox] + .can_tdlr + .modify(CAN_TDLxR::DATA1.val(tx[1].into())); + self.registers.can_tx_mailbox[tx_mailbox] + .can_tdlr + .modify(CAN_TDLxR::DATA2.val(tx[2].into())); + self.registers.can_tx_mailbox[tx_mailbox] + .can_tdlr + .modify(CAN_TDLxR::DATA3.val(tx[3].into())); + // write the last 4 bytes of the data + self.registers.can_tx_mailbox[tx_mailbox] + .can_tdhr + .modify(CAN_TDHxR::DATA4.val(tx[4].into())); + self.registers.can_tx_mailbox[tx_mailbox] + .can_tdhr + .modify(CAN_TDHxR::DATA5.val(tx[5].into())); + self.registers.can_tx_mailbox[tx_mailbox] + .can_tdhr + .modify(CAN_TDHxR::DATA6.val(tx[6].into())); + self.registers.can_tx_mailbox[tx_mailbox] + .can_tdhr + .modify(CAN_TDHxR::DATA7.val(tx[7].into())); + + self.registers.can_tx_mailbox[tx_mailbox] + .can_tir + .modify(CAN_TIxR::TXRQ::SET); + }) { + Some(()) => Ok(()), + None => Err(kernel::ErrorCode::FAIL), + } + } else { + // no mailbox empty + self.failed_messages.replace(self.failed_messages.get() + 1); + Err(kernel::ErrorCode::BUSY) + } + } else { + Err(kernel::ErrorCode::OFF) + } + } + + pub fn find_empty_mailbox(&self) -> Option { + if self.registers.can_tsr.read(CAN_TSR::TME0) == 1 { + Some(0) + } else if self.registers.can_tsr.read(CAN_TSR::TME1) == 1 { + Some(1) + } else if self.registers.can_tsr.read(CAN_TSR::TME2) == 1 { + Some(2) + } else { + None + } + } + + pub fn is_enabled_clock(&self) -> bool { + self.clock.is_enabled() + } + + pub fn enable_clock(&self) { + self.clock.enable(); + } + + pub fn disable_clock(&self) { + self.clock.disable(); + } + + /// Handle the transmit interrupt. Check the status register for each + /// transmit mailbox to find out the mailbox that the message was sent from. + pub fn handle_transmit_interrupt(&self) { + let mut state = Ok(()); + if self.registers.can_esr.read(CAN_ESR::BOFF) == 1 { + state = Err(can::Error::BusOff) + } else { + if self.registers.can_tsr.read(CAN_TSR::RQCP0) == 1 { + // check status + state = if self.registers.can_tsr.read(CAN_TSR::TXOK0) == 1 { + Ok(()) + } else if self.registers.can_tsr.read(CAN_TSR::TERR0) == 1 { + Err(can::Error::Transmission) + } else if self.registers.can_tsr.read(CAN_TSR::ALST0) == 1 { + Err(can::Error::ArbitrationLost) + } else { + Ok(()) + }; + // mark the interrupt as handled + self.registers.can_tsr.modify(CAN_TSR::RQCP0::SET); + } + if self.registers.can_tsr.read(CAN_TSR::RQCP1) == 1 { + state = if self.registers.can_tsr.read(CAN_TSR::TXOK1) == 1 { + Ok(()) + } else if self.registers.can_tsr.read(CAN_TSR::TERR1) == 1 { + Err(can::Error::Transmission) + } else if self.registers.can_tsr.read(CAN_TSR::ALST1) == 1 { + Err(can::Error::ArbitrationLost) + } else { + Ok(()) + }; + // mark the interrupt as handled + self.registers.can_tsr.modify(CAN_TSR::RQCP1::SET); + } + if self.registers.can_tsr.read(CAN_TSR::RQCP2) == 1 { + state = if self.registers.can_tsr.read(CAN_TSR::TXOK2) == 1 { + Ok(()) + } else if self.registers.can_tsr.read(CAN_TSR::TERR2) == 1 { + Err(can::Error::Transmission) + } else if self.registers.can_tsr.read(CAN_TSR::ALST2) == 1 { + Err(can::Error::ArbitrationLost) + } else { + Ok(()) + }; + // mark the interrupt as handled + self.registers.can_tsr.modify(CAN_TSR::RQCP2::SET); + } + } + + match state { + Err(err) => self.can_state.set(CanState::RunningError(err)), + _ => {} + } + + self.transmit_client + .map(|transmit_client| match self.tx_buffer.take() { + Some(buf) => transmit_client.transmit_complete(state, buf), + None => {} + }); + } + + pub fn process_received_message( + &self, + rx_mailbox: usize, + ) -> (can::Id, usize, [u8; can::STANDARD_CAN_PACKET_SIZE]) { + let message_id = if self.registers.can_rx_mailbox[rx_mailbox] + .can_rir + .read(CAN_RIxR::IDE) + == 0 + { + can::Id::Standard( + self.registers.can_rx_mailbox[rx_mailbox] + .can_rir + .read(CAN_RIxR::STID) as u16, + ) + } else { + can::Id::Extended( + (self.registers.can_rx_mailbox[rx_mailbox] + .can_rir + .read(CAN_RIxR::STID) + << 18) + | (self.registers.can_rx_mailbox[rx_mailbox] + .can_rir + .read(CAN_RIxR::EXID)), + ) + }; + let message_length = self.registers.can_rx_mailbox[rx_mailbox] + .can_rdtr + .read(CAN_RDTxR::DLC) as usize; + let recv: u64 = ((self.registers.can_rx_mailbox[0].can_rdhr.get() as u64) << 32) + | (self.registers.can_rx_mailbox[0].can_rdlr.get() as u64); + let rx_buf = recv.to_le_bytes(); + self.rx_buffer.map(|rx| { + rx[..8].copy_from_slice(&rx_buf[..8]); + }); + + (message_id, message_length, rx_buf) + } + + pub fn handle_fifo0_interrupt(&self) { + if self.registers.can_rf0r.read(CAN_RF0R::FULL0) == 1 { + self.registers.can_rf0r.modify(CAN_RF0R::FULL0::SET); + } + + if self.registers.can_rf0r.read(CAN_RF0R::FOVR0) == 1 { + self.registers.can_rf0r.modify(CAN_RF0R::FOVR0::SET); + } + + if self.registers.can_rf0r.read(CAN_RF0R::FMP0) != 0 { + let (message_id, message_length, mut rx_buf) = self.process_received_message(0); + + self.receive_client.map(|receive_client| { + receive_client.message_received(message_id, &mut rx_buf, message_length, Ok(())) + }); + self.fifo0_interrupt_counter + .replace(self.fifo0_interrupt_counter.get() + 1); + + // mark the interrupt as handled + self.registers.can_rf0r.modify(CAN_RF0R::RFOM0::SET); + } + } + + pub fn handle_fifo1_interrupt(&self) { + if self.registers.can_rf1r.read(CAN_RF1R::FULL1) == 1 { + self.registers.can_rf1r.modify(CAN_RF1R::FULL1::SET); + } + + if self.registers.can_rf1r.read(CAN_RF1R::FOVR1) == 1 { + self.registers.can_rf1r.modify(CAN_RF1R::FOVR1::SET); + } + + if self.registers.can_rf1r.read(CAN_RF1R::FMP1) != 0 { + self.fifo1_interrupt_counter + .replace(self.fifo1_interrupt_counter.get() + 1); + let (message_id, message_length, mut rx_buf) = self.process_received_message(1); + self.receive_client.map(|receive_client| { + receive_client.message_received(message_id, &mut rx_buf, message_length, Ok(())) + }); + + // mark the interrupt as handled + self.registers.can_rf1r.modify(CAN_RF1R::RFOM1::SET); + } + } + + pub fn handle_error_status_interrupt(&self) { + // Check if there is a status change interrupt + if self.registers.can_msr.read(CAN_MSR::WKUI) == 1 { + // mark the interrupt as handled + self.registers.can_msr.modify(CAN_MSR::WKUI::SET); + } + if self.registers.can_msr.read(CAN_MSR::SLAKI) == 1 { + // mark the interrupt as handled + self.registers.can_msr.modify(CAN_MSR::SLAKI::SET); + } + + // Check if there is an error interrupt + // Warning flag + if self.registers.can_esr.read(CAN_ESR::EWGF) == 1 { + self.can_state + .set(CanState::RunningError(can::Error::Warning)); + } + // Passive flag + if self.registers.can_esr.read(CAN_ESR::EPVF) == 1 { + self.can_state + .set(CanState::RunningError(can::Error::Passive)); + } + // Bus-off flag + if self.registers.can_esr.read(CAN_ESR::BOFF) == 1 { + self.can_state + .set(CanState::RunningError(can::Error::BusOff)); + } + // Last Error Code + match self.registers.can_esr.read(CAN_ESR::LEC) { + 0x001 => self + .can_state + .set(CanState::RunningError(can::Error::Stuff)), + 0x010 => self.can_state.set(CanState::RunningError(can::Error::Form)), + 0x011 => self.can_state.set(CanState::RunningError(can::Error::Ack)), + 0x100 => self + .can_state + .set(CanState::RunningError(can::Error::BitRecessive)), + 0x101 => self + .can_state + .set(CanState::RunningError(can::Error::BitDominant)), + 0x110 => self.can_state.set(CanState::RunningError(can::Error::Crc)), + 0x111 => self + .can_state + .set(CanState::RunningError(can::Error::SetBySoftware)), + _ => {} + } + + self.error_interrupt_counter + .replace(self.error_interrupt_counter.get() + 1); + + match self.can_state.get() { + CanState::RunningError(err) => { + self.controller_client.map(|controller_client| { + controller_client.state_changed(kernel::hil::can::State::Error(err)); + }); + } + _ => {} + } + } + + pub fn enable_irq(&self, interrupt: CanInterruptMode) { + match interrupt { + CanInterruptMode::TransmitInterrupt => { + self.registers.can_ier.modify(CAN_IER::TMEIE::SET); + } + CanInterruptMode::Fifo0Interrupt => { + self.registers.can_ier.modify(CAN_IER::FMPIE0::SET); + self.registers.can_ier.modify(CAN_IER::FFIE0::SET); + self.registers.can_ier.modify(CAN_IER::FOVIE0::SET); + } + CanInterruptMode::Fifo1Interrupt => { + self.registers.can_ier.modify(CAN_IER::FMPIE1::SET); + self.registers.can_ier.modify(CAN_IER::FFIE1::SET); + self.registers.can_ier.modify(CAN_IER::FOVIE1::SET); + } + CanInterruptMode::ErrorAndStatusChangeInterrupt => { + self.registers.can_ier.modify(CAN_IER::ERRIE::SET); + self.registers.can_ier.modify(CAN_IER::EWGIE::SET); + self.registers.can_ier.modify(CAN_IER::EPVIE::SET); + self.registers.can_ier.modify(CAN_IER::BOFIE::SET); + self.registers.can_ier.modify(CAN_IER::LECIE::SET); + self.registers.can_ier.modify(CAN_IER::WKUIE::SET); + self.registers.can_ier.modify(CAN_IER::SLKIE::SET); + } + } + } + + pub fn disable_irq(&self, interrupt: CanInterruptMode) { + match interrupt { + CanInterruptMode::TransmitInterrupt => { + self.registers.can_ier.modify(CAN_IER::TMEIE::CLEAR); + } + CanInterruptMode::Fifo0Interrupt => { + self.registers.can_ier.modify(CAN_IER::FMPIE0::CLEAR); + self.registers.can_ier.modify(CAN_IER::FFIE0::CLEAR); + self.registers.can_ier.modify(CAN_IER::FOVIE0::CLEAR); + } + CanInterruptMode::Fifo1Interrupt => { + self.registers.can_ier.modify(CAN_IER::FMPIE1::CLEAR); + self.registers.can_ier.modify(CAN_IER::FFIE1::CLEAR); + self.registers.can_ier.modify(CAN_IER::FOVIE1::CLEAR); + } + CanInterruptMode::ErrorAndStatusChangeInterrupt => { + self.registers.can_ier.modify(CAN_IER::ERRIE::CLEAR); + self.registers.can_ier.modify(CAN_IER::EWGIE::CLEAR); + self.registers.can_ier.modify(CAN_IER::EPVIE::CLEAR); + self.registers.can_ier.modify(CAN_IER::BOFIE::CLEAR); + self.registers.can_ier.modify(CAN_IER::LECIE::CLEAR); + self.registers.can_ier.modify(CAN_IER::WKUIE::CLEAR); + self.registers.can_ier.modify(CAN_IER::SLKIE::CLEAR); + } + } + } + + pub fn enable_irqs(&self) { + self.enable_irq(CanInterruptMode::TransmitInterrupt); + self.enable_irq(CanInterruptMode::Fifo0Interrupt); + self.enable_irq(CanInterruptMode::Fifo1Interrupt); + self.enable_irq(CanInterruptMode::ErrorAndStatusChangeInterrupt); + } + + pub fn disable_irqs(&self) { + self.disable_irq(CanInterruptMode::TransmitInterrupt); + self.disable_irq(CanInterruptMode::Fifo0Interrupt); + self.disable_irq(CanInterruptMode::Fifo1Interrupt); + self.disable_irq(CanInterruptMode::ErrorAndStatusChangeInterrupt); + } +} + +impl DeferredCallClient for Can<'_> { + fn register(&'static self) { + self.deferred_call.register(self) + } + + fn handle_deferred_call(&self) { + match self.deferred_action.take() { + Some(action) => match action { + AsyncAction::Enable => { + if let Err(enable_err) = self.enter_normal_mode() { + self.controller_client.map(|controller_client| { + controller_client.state_changed(self.can_state.get().into()); + controller_client.enabled(Err(enable_err)); + }); + } + self.controller_client.map(|controller_client| { + controller_client.state_changed(can::State::Running); + controller_client.enabled(Ok(())); + }); + } + AsyncAction::AbortReceive => { + if let Some(rx) = self.rx_buffer.take() { + self.receive_client + .map(|receive_client| receive_client.stopped(rx)); + } + } + AsyncAction::Disabled => { + self.controller_client.map(|controller_client| { + controller_client.state_changed(self.can_state.get().into()); + controller_client.disabled(Ok(())); + }); + } + AsyncAction::EnableError(err) => { + self.controller_client.map(|controller_client| { + controller_client.state_changed(self.can_state.get().into()); + controller_client.enabled(Err(err)); + }); + } + }, + // todo no action set + None => todo!(), + } + } +} + +struct CanClock<'a>(phclk::PeripheralClock<'a>); + +impl ClockInterface for CanClock<'_> { + fn is_enabled(&self) -> bool { + self.0.is_enabled() + } + + fn enable(&self) { + self.0.enable(); + } + + fn disable(&self) { + self.0.disable(); + } +} + +impl can::Configure for Can<'_> { + const MIN_BIT_TIMINGS: can::BitTiming = can::BitTiming { + segment1: BitSegment1::CanBtrTs1Min as u8, + segment2: BitSegment2::CanBtrTs2Min as u8, + propagation: 0, + sync_jump_width: SynchronizationJumpWidth::CanBtrSjwMin as u32, + baud_rate_prescaler: BRP_MIN_STM32, + }; + + const MAX_BIT_TIMINGS: can::BitTiming = can::BitTiming { + segment1: BitSegment1::CanBtrTs1Max as u8, + segment2: BitSegment2::CanBtrTs2Max as u8, + propagation: 0, + sync_jump_width: SynchronizationJumpWidth::CanBtrSjwMax as u32, + baud_rate_prescaler: BRP_MAX_STM32, + }; + + const SYNC_SEG: u8 = 1; + + fn set_bitrate(&self, bitrate: u32) -> Result<(), kernel::ErrorCode> { + let bit_timing = Self::bit_timing_for_bitrate(16_000_000, bitrate)?; + self.set_bit_timing(bit_timing) + } + + fn set_bit_timing(&self, bit_timing: can::BitTiming) -> Result<(), kernel::ErrorCode> { + match self.can_state.get() { + CanState::Sleep => { + self.bit_timing.set(bit_timing); + Ok(()) + } + CanState::Normal | CanState::Initialization | CanState::RunningError(_) => { + Err(kernel::ErrorCode::BUSY) + } + } + } + + fn set_operation_mode(&self, mode: can::OperationMode) -> Result<(), kernel::ErrorCode> { + match self.can_state.get() { + CanState::Sleep => { + self.operating_mode.set(mode); + Ok(()) + } + CanState::Normal | CanState::Initialization | CanState::RunningError(_) => { + Err(kernel::ErrorCode::BUSY) + } + } + } + + fn get_bit_timing(&self) -> Result { + if let Some(bit_timing) = self.bit_timing.get() { + Ok(bit_timing) + } else { + Err(kernel::ErrorCode::INVAL) + } + } + + fn get_operation_mode(&self) -> Result { + if let Some(operation_mode) = self.operating_mode.get() { + Ok(operation_mode) + } else { + Err(kernel::ErrorCode::INVAL) + } + } + + fn set_automatic_retransmission(&self, automatic: bool) -> Result<(), kernel::ErrorCode> { + match self.can_state.get() { + CanState::Sleep => { + self.automatic_retransmission.replace(automatic); + Ok(()) + } + CanState::Normal | CanState::Initialization | CanState::RunningError(_) => { + Err(kernel::ErrorCode::BUSY) + } + } + } + + fn set_wake_up(&self, wake_up: bool) -> Result<(), kernel::ErrorCode> { + match self.can_state.get() { + CanState::Sleep => { + self.automatic_wake_up.replace(wake_up); + Ok(()) + } + CanState::Normal | CanState::Initialization | CanState::RunningError(_) => { + Err(kernel::ErrorCode::BUSY) + } + } + } + + fn get_automatic_retransmission(&self) -> Result { + Ok(self.automatic_retransmission.get()) + } + + fn get_wake_up(&self) -> Result { + Ok(self.automatic_wake_up.get()) + } + + fn receive_fifo_count(&self) -> usize { + 2 + } +} + +impl can::Controller for Can<'_> { + fn set_client(&self, client: Option<&'static dyn can::ControllerClient>) { + if let Some(client) = client { + self.controller_client.replace(client); + } else { + self.controller_client.clear(); + } + } + + fn enable(&self) -> Result<(), kernel::ErrorCode> { + match self.can_state.get() { + CanState::Sleep => { + if self.bit_timing.is_none() || self.operating_mode.is_none() { + Err(kernel::ErrorCode::INVAL) + } else { + let r = self.enable(); + // there is another deferred action that must be completed + if self.deferred_action.is_some() { + Err(kernel::ErrorCode::BUSY) + } else { + // set an Enable or an EnableError deferred action + match r { + Ok(()) => { + self.deferred_action.set(AsyncAction::Enable); + } + Err(err) => { + self.deferred_action.set(AsyncAction::EnableError(err)); + } + } + self.deferred_call.set(); + r + } + } + } + CanState::Normal | CanState::Initialization => Err(kernel::ErrorCode::ALREADY), + CanState::RunningError(_) => Err(kernel::ErrorCode::FAIL), + } + } + + fn disable(&self) -> Result<(), kernel::ErrorCode> { + match self.can_state.get() { + CanState::Normal | CanState::RunningError(_) => { + self.enter_sleep_mode(); + if self.deferred_action.is_some() { + // there is another deferred action that must be completed + return Err(kernel::ErrorCode::BUSY); + } else { + // set a Disable deferred action + self.deferred_action.set(AsyncAction::Disabled); + self.deferred_call.set(); + } + Ok(()) + } + CanState::Sleep | CanState::Initialization => Err(kernel::ErrorCode::OFF), + } + } + + fn get_state(&self) -> Result { + Ok(self.can_state.get().into()) + } +} + +impl can::Transmit<{ can::STANDARD_CAN_PACKET_SIZE }> for Can<'_> { + fn set_client( + &self, + client: Option<&'static dyn can::TransmitClient<{ can::STANDARD_CAN_PACKET_SIZE }>>, + ) { + if let Some(client) = client { + self.transmit_client.set(client); + } else { + self.transmit_client.clear(); + } + } + + fn send( + &self, + id: can::Id, + buffer: &'static mut [u8; can::STANDARD_CAN_PACKET_SIZE], + len: usize, + ) -> Result< + (), + ( + kernel::ErrorCode, + &'static mut [u8; can::STANDARD_CAN_PACKET_SIZE], + ), + > { + match self.can_state.get() { + CanState::Normal | CanState::RunningError(_) => { + self.tx_buffer.replace(buffer); + self.enable_irq(CanInterruptMode::TransmitInterrupt); + self.can_state.set(CanState::Normal); + match self.send_8byte_message(id, len, 0) { + Ok(()) => Ok(()), + Err(err) => Err((err, self.tx_buffer.take().unwrap())), + } + } + CanState::Sleep | CanState::Initialization => Err((kernel::ErrorCode::OFF, buffer)), + } + } +} + +impl can::Receive<{ can::STANDARD_CAN_PACKET_SIZE }> for Can<'_> { + fn set_client( + &self, + client: Option<&'static dyn can::ReceiveClient<{ can::STANDARD_CAN_PACKET_SIZE }>>, + ) { + if let Some(client) = client { + self.receive_client.set(client); + } else { + self.receive_client.clear(); + } + } + + fn start_receive_process( + &self, + buffer: &'static mut [u8; can::STANDARD_CAN_PACKET_SIZE], + ) -> Result< + (), + ( + kernel::ErrorCode, + &'static mut [u8; can::STANDARD_CAN_PACKET_SIZE], + ), + > { + match self.can_state.get() { + CanState::Normal | CanState::RunningError(_) => { + self.can_state.set(CanState::Normal); + self.config_filter( + can::FilterParameters { + number: 0, + scale_bits: can::ScaleBits::Bits32, + identifier_mode: can::IdentifierMode::Mask, + fifo_number: 0, + }, + true, + ); + self.config_filter( + can::FilterParameters { + number: 1, + scale_bits: can::ScaleBits::Bits32, + identifier_mode: can::IdentifierMode::Mask, + fifo_number: 1, + }, + true, + ); + self.enable_filter_config(); + self.enable_irq(CanInterruptMode::Fifo0Interrupt); + self.enable_irq(CanInterruptMode::Fifo1Interrupt); + self.rx_buffer.put(Some(buffer)); + Ok(()) + } + CanState::Sleep | CanState::Initialization => Err((kernel::ErrorCode::OFF, buffer)), + } + } + + fn stop_receive(&self) -> Result<(), kernel::ErrorCode> { + match self.can_state.get() { + CanState::Normal | CanState::RunningError(_) => { + self.can_state.set(CanState::Normal); + self.config_filter( + can::FilterParameters { + number: 0, + scale_bits: can::ScaleBits::Bits32, + identifier_mode: can::IdentifierMode::Mask, + fifo_number: 0, + }, + false, + ); + self.config_filter( + can::FilterParameters { + number: 1, + scale_bits: can::ScaleBits::Bits32, + identifier_mode: can::IdentifierMode::Mask, + fifo_number: 1, + }, + false, + ); + self.enable_filter_config(); + self.disable_irq(CanInterruptMode::Fifo0Interrupt); + self.disable_irq(CanInterruptMode::Fifo1Interrupt); + // there is another deferred action that must be completed + if self.deferred_action.is_some() { + Err(kernel::ErrorCode::BUSY) + // the chip does not own the buffer from the capsule + } else if self.rx_buffer.is_none() { + Err(kernel::ErrorCode::SIZE) + } else { + // set a AbortReceive deferred action + self.deferred_action.set(AsyncAction::AbortReceive); + self.deferred_call.set(); + Ok(()) + } + } + CanState::Sleep | CanState::Initialization => Err(kernel::ErrorCode::OFF), + } + } +} diff --git a/chips/stm32f4xx/src/chip.rs b/chips/stm32f4xx/src/chip.rs index 1441738428..af8e23a8f5 100644 --- a/chips/stm32f4xx/src/chip.rs +++ b/chips/stm32f4xx/src/chip.rs @@ -1,59 +1,58 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Chip trait setup. use core::fmt::Write; -use cortexm4; -use kernel::deferred_call; +use cortexm4::{CortexM4, CortexMVariant}; use kernel::platform::chip::Chip; use kernel::platform::chip::InterruptService; -use crate::dma1; +use crate::dma; use crate::nvic; -use crate::deferred_calls::DeferredCallTask; +use crate::chip_specific::chip_specs::ChipSpecs as ChipSpecsTrait; -pub struct Stm32f4xx<'a, I: InterruptService + 'a> { +pub struct Stm32f4xx<'a, I: InterruptService + 'a> { mpu: cortexm4::mpu::MPU, userspace_kernel_boundary: cortexm4::syscall::SysCall, interrupt_service: &'a I, } -pub struct Stm32f4xxDefaultPeripherals<'a> { +pub struct Stm32f4xxDefaultPeripherals<'a, ChipSpecs> { pub adc1: crate::adc::Adc<'a>, - pub dma_streams: [crate::dma1::Stream<'a>; 8], + pub dac: crate::dac::Dac<'a>, + pub dma1_streams: [crate::dma::Stream<'a, dma::Dma1<'a>>; 8], + pub dma2_streams: [crate::dma::Stream<'a, dma::Dma2<'a>>; 8], pub exti: &'a crate::exti::Exti<'a>, + pub flash: crate::flash::Flash, + pub fsmc: crate::fsmc::Fsmc<'a>, + pub gpio_ports: crate::gpio::GpioPorts<'a>, pub i2c1: crate::i2c::I2C<'a>, + pub clocks: &'a crate::clocks::Clocks<'a, ChipSpecs>, pub spi3: crate::spi::Spi<'a>, pub tim2: crate::tim2::Tim2<'a>, - pub usart2: crate::usart::Usart<'a>, - pub usart3: crate::usart::Usart<'a>, - pub gpio_ports: crate::gpio::GpioPorts<'a>, - pub fsmc: crate::fsmc::Fsmc<'a>, + pub usart1: crate::usart::Usart<'a, dma::Dma2<'a>>, + pub usart2: crate::usart::Usart<'a, dma::Dma1<'a>>, + pub usart3: crate::usart::Usart<'a, dma::Dma1<'a>>, } -impl<'a> Stm32f4xxDefaultPeripherals<'a> { +impl<'a, ChipSpecs: ChipSpecsTrait> Stm32f4xxDefaultPeripherals<'a, ChipSpecs> { pub fn new( - rcc: &'a crate::rcc::Rcc, + clocks: &'a crate::clocks::Clocks<'a, ChipSpecs>, exti: &'a crate::exti::Exti<'a>, - dma: &'a crate::dma1::Dma1<'a>, + dma1: &'a dma::Dma1<'a>, + dma2: &'a dma::Dma2<'a>, ) -> Self { Self { - adc1: crate::adc::Adc::new(rcc), - dma_streams: crate::dma1::new_dma1_stream(dma), + adc1: crate::adc::Adc::new(clocks), + clocks, + dac: crate::dac::Dac::new(clocks), + dma1_streams: dma::new_dma1_stream(dma1), + dma2_streams: dma::new_dma2_stream(dma2), exti, - i2c1: crate::i2c::I2C::new(rcc), - spi3: crate::spi::Spi::new( - crate::spi::SPI3_BASE, - crate::spi::SpiClock(crate::rcc::PeripheralClock::new( - crate::rcc::PeripheralClockType::APB1(crate::rcc::PCLK1::SPI3), - rcc, - )), - crate::dma1::Dma1Peripheral::SPI3_TX, - crate::dma1::Dma1Peripheral::SPI3_RX, - ), - tim2: crate::tim2::Tim2::new(rcc), - usart2: crate::usart::Usart::new_usart2(rcc), - usart3: crate::usart::Usart::new_usart3(rcc), - gpio_ports: crate::gpio::GpioPorts::new(rcc, exti), + flash: crate::flash::Flash::new(), fsmc: crate::fsmc::Fsmc::new( [ Some(crate::fsmc::FSMC_BANK1), @@ -61,38 +60,74 @@ impl<'a> Stm32f4xxDefaultPeripherals<'a> { Some(crate::fsmc::FSMC_BANK3), None, ], - rcc, + clocks, + ), + gpio_ports: crate::gpio::GpioPorts::new(clocks, exti), + i2c1: crate::i2c::I2C::new(clocks), + spi3: crate::spi::Spi::new( + crate::spi::SPI3_BASE, + crate::spi::SpiClock(crate::clocks::phclk::PeripheralClock::new( + crate::clocks::phclk::PeripheralClockType::APB1( + crate::clocks::phclk::PCLK1::SPI3, + ), + clocks, + )), + dma::Dma1Peripheral::SPI3_TX, + dma::Dma1Peripheral::SPI3_RX, ), + tim2: crate::tim2::Tim2::new(clocks), + usart1: crate::usart::Usart::new_usart1(clocks), + usart2: crate::usart::Usart::new_usart2(clocks), + usart3: crate::usart::Usart::new_usart3(clocks), } } - pub fn setup_circular_deps(&'a self) { + // Setup any circular dependencies and register deferred calls + pub fn setup_circular_deps(&'static self) { + self.clocks.set_flash(&self.flash); self.gpio_ports.setup_circular_deps(); + + // Note: Boards with a CAN bus present also need to register its + // deferred call. + kernel::deferred_call::DeferredCallClient::register(&self.usart1); + kernel::deferred_call::DeferredCallClient::register(&self.usart2); + kernel::deferred_call::DeferredCallClient::register(&self.usart3); + kernel::deferred_call::DeferredCallClient::register(&self.fsmc); } } -impl<'a> InterruptService for Stm32f4xxDefaultPeripherals<'a> { +impl<'a, ChipSpecs: ChipSpecsTrait> InterruptService + for Stm32f4xxDefaultPeripherals<'a, ChipSpecs> +{ unsafe fn service_interrupt(&self, interrupt: u32) -> bool { match interrupt { - nvic::DMA1_Stream1 => self.dma_streams - [dma1::Dma1Peripheral::USART3_RX.get_stream_idx()] + nvic::DMA1_Stream1 => self.dma1_streams + [dma::Dma1Peripheral::USART3_RX.get_stream_idx()] .handle_interrupt(), nvic::DMA1_Stream2 => { - self.dma_streams[dma1::Dma1Peripheral::SPI3_RX.get_stream_idx()].handle_interrupt() + self.dma1_streams[dma::Dma1Peripheral::SPI3_RX.get_stream_idx()].handle_interrupt() } - nvic::DMA1_Stream3 => self.dma_streams - [dma1::Dma1Peripheral::USART3_TX.get_stream_idx()] + nvic::DMA1_Stream3 => self.dma1_streams + [dma::Dma1Peripheral::USART3_TX.get_stream_idx()] .handle_interrupt(), - nvic::DMA1_Stream5 => self.dma_streams - [dma1::Dma1Peripheral::USART2_RX.get_stream_idx()] + nvic::DMA1_Stream5 => self.dma1_streams + [dma::Dma1Peripheral::USART2_RX.get_stream_idx()] .handle_interrupt(), - nvic::DMA1_Stream6 => self.dma_streams - [dma1::Dma1Peripheral::USART2_TX.get_stream_idx()] + nvic::DMA1_Stream6 => self.dma1_streams + [dma::Dma1Peripheral::USART2_TX.get_stream_idx()] .handle_interrupt(), nvic::DMA1_Stream7 => { - self.dma_streams[dma1::Dma1Peripheral::SPI3_TX.get_stream_idx()].handle_interrupt() + self.dma1_streams[dma::Dma1Peripheral::SPI3_TX.get_stream_idx()].handle_interrupt() } + nvic::DMA2_Stream5 => self.dma2_streams + [dma::Dma2Peripheral::USART1_RX.get_stream_idx()] + .handle_interrupt(), + nvic::DMA2_Stream7 => self.dma2_streams + [dma::Dma2Peripheral::USART1_TX.get_stream_idx()] + .handle_interrupt(), + + nvic::USART1 => self.usart1.handle_interrupt(), nvic::USART2 => self.usart2.handle_interrupt(), nvic::USART3 => self.usart3.handle_interrupt(), @@ -117,16 +152,9 @@ impl<'a> InterruptService for Stm32f4xxDefaultPeripherals<'a> } true } - - unsafe fn service_deferred_call(&self, task: DeferredCallTask) -> bool { - match task { - DeferredCallTask::Fsmc => self.fsmc.handle_interrupt(), - } - true - } } -impl<'a, I: InterruptService + 'a> Stm32f4xx<'a, I> { +impl<'a, I: InterruptService + 'a> Stm32f4xx<'a, I> { pub unsafe fn new(interrupt_service: &'a I) -> Self { Self { mpu: cortexm4::mpu::MPU::new(), @@ -136,18 +164,14 @@ impl<'a, I: InterruptService + 'a> Stm32f4xx<'a, I> { } } -impl<'a, I: InterruptService + 'a> Chip for Stm32f4xx<'a, I> { +impl<'a, I: InterruptService + 'a> Chip for Stm32f4xx<'a, I> { type MPU = cortexm4::mpu::MPU; type UserspaceKernelBoundary = cortexm4::syscall::SysCall; fn service_pending_interrupts(&self) { unsafe { loop { - if let Some(task) = deferred_call::DeferredCall::next_pending() { - if !self.interrupt_service.service_deferred_call(task) { - panic!("Unhandled deferred call"); - } - } else if let Some(interrupt) = cortexm4::nvic::next_pending() { + if let Some(interrupt) = cortexm4::nvic::next_pending() { if !self.interrupt_service.service_interrupt(interrupt) { panic!("unhandled interrupt {}", interrupt); } @@ -163,7 +187,7 @@ impl<'a, I: InterruptService + 'a> Chip for Stm32f4xx<'a, I> { } fn has_pending_interrupts(&self) -> bool { - unsafe { cortexm4::nvic::has_pending() || deferred_call::has_tasks() } + unsafe { cortexm4::nvic::has_pending() } } fn mpu(&self) -> &cortexm4::mpu::MPU { @@ -189,6 +213,6 @@ impl<'a, I: InterruptService + 'a> Chip for Stm32f4xx<'a, I> { } unsafe fn print_state(&self, write: &mut dyn Write) { - cortexm4::print_cortexm4_state(write); + CortexM4::print_cortexm_state(write); } } diff --git a/chips/stm32f4xx/src/chip_specific/chip_specs.rs b/chips/stm32f4xx/src/chip_specific/chip_specs.rs new file mode 100644 index 0000000000..c6dbd56415 --- /dev/null +++ b/chips/stm32f4xx/src/chip_specific/chip_specs.rs @@ -0,0 +1,17 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive SRL. +// +// Author: Ioan-Cristian CÎRSTEA + +//! Trait that encompasses chip specifications +//! +//! The main use of this trait is to be passed as a bound for the type parameter for chip +//! peripherals in crates such as `stm32f429zi`. + +use crate::chip_specific::clock_constants::ClockConstants; +use crate::chip_specific::flash::FlashChipSpecific; + +pub trait ChipSpecs: ClockConstants + FlashChipSpecific {} + +impl ChipSpecs for T {} diff --git a/chips/stm32f4xx/src/chip_specific/clock_constants.rs b/chips/stm32f4xx/src/chip_specific/clock_constants.rs new file mode 100644 index 0000000000..d76ef6021f --- /dev/null +++ b/chips/stm32f4xx/src/chip_specific/clock_constants.rs @@ -0,0 +1,32 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive SRL. +// +// Author: Ioan-Cristian CÎRSTEA + +//! Clock-related constants for a particular chip + +/// PLL-related constants for specific for a specific chip +pub trait PllConstants { + /// PLL minimum frequency in MHz + const MIN_FREQ_MHZ: usize; + /// PLL maximum frequency in MHz + // All boards support PLL frequencies up to 216MHz + const MAX_FREQ_MHZ: usize = 216; +} + +/// Generic clock constants for a specific chip +pub trait SystemClockConstants { + /// Maximum allowed APB1 frequency in MHz + const APB1_FREQUENCY_LIMIT_MHZ: usize; + /// Maximum allowed APB2 frequency in MHz + // APB2 frequency limit is twice the APB1 frequency limit + const APB2_FREQUENCY_LIMIT_MHZ: usize = Self::APB1_FREQUENCY_LIMIT_MHZ << 1; + /// Maximum allowed system clock frequency in MHz + const SYS_CLOCK_FREQUENCY_LIMIT_MHZ: usize; +} + +/// Clock constants for a specific chip +pub trait ClockConstants: SystemClockConstants + PllConstants {} + +impl ClockConstants for T {} diff --git a/chips/stm32f4xx/src/chip_specific/flash.rs b/chips/stm32f4xx/src/chip_specific/flash.rs new file mode 100644 index 0000000000..262d8b087a --- /dev/null +++ b/chips/stm32f4xx/src/chip_specific/flash.rs @@ -0,0 +1,74 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive SRL. +// +// Author: Ioan-Cristian CÎRSTEA + +//! Chip-specific flash code + +use core::fmt::Debug; + +pub trait FlashChipSpecific { + type FlashLatency: RegisterToFlashLatency + Clone + Copy + PartialEq + Debug + Into; + + // The number of wait cycles depends on two factors: system clock frequency and the supply + // voltage. Currently, this method assumes 2.7-3.6V voltage supply (default value). + // TODO: Take into account the power supply + // + // The number of wait cycles varies from chip to chip + fn get_number_wait_cycles_based_on_frequency(frequency_mhz: usize) -> Self::FlashLatency; +} + +pub trait RegisterToFlashLatency { + fn convert_register_to_enum(flash_latency_register: u32) -> Self; +} + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum FlashLatency16 { + Latency0, + Latency1, + Latency2, + Latency3, + Latency4, + Latency5, + Latency6, + Latency7, + Latency8, + Latency9, + Latency10, + Latency11, + Latency12, + Latency13, + Latency14, + Latency15, +} + +impl RegisterToFlashLatency for FlashLatency16 { + fn convert_register_to_enum(flash_latency_register: u32) -> Self { + match flash_latency_register { + 0 => Self::Latency0, + 1 => Self::Latency1, + 2 => Self::Latency2, + 3 => Self::Latency3, + 4 => Self::Latency4, + 5 => Self::Latency5, + 6 => Self::Latency6, + 7 => Self::Latency7, + 8 => Self::Latency8, + 9 => Self::Latency9, + 10 => Self::Latency10, + 11 => Self::Latency11, + 12 => Self::Latency12, + 13 => Self::Latency13, + 14 => Self::Latency14, + // The hardware supports 4-bit flash latency + _ => Self::Latency15, + } + } +} + +impl From for u32 { + fn from(val: FlashLatency16) -> Self { + val as u32 + } +} diff --git a/chips/stm32f4xx/src/chip_specific/mod.rs b/chips/stm32f4xx/src/chip_specific/mod.rs new file mode 100644 index 0000000000..1335063622 --- /dev/null +++ b/chips/stm32f4xx/src/chip_specific/mod.rs @@ -0,0 +1,17 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive SRL. +// +// Author: Ioan-Cristian CÎRSTEA + +//! This module contains all chip-specific code. +//! +//! Some models in the STM32F4 family may have additional features, while others not. Or they can +//! operate internally in different ways for the same feature. This module provides all the +//! chip-specific types and traits to be used by others modules in this crate or by other crates. + +pub mod chip_specs; +pub mod clock_constants; +pub mod flash; + +pub use chip_specs::ChipSpecs; diff --git a/chips/stm32f4xx/src/clocks/clocks.rs b/chips/stm32f4xx/src/clocks/clocks.rs new file mode 100644 index 0000000000..b2d88f2ad0 --- /dev/null +++ b/chips/stm32f4xx/src/clocks/clocks.rs @@ -0,0 +1,1030 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive SRL. +// +// Author: Ioan-Cristian CÎRSTEA + +//! STM32F4xx clock driver +//! +//! This crate provides drivers for various clocks: HSI, PLL, system, AHB, APB1 and APB2. +//! This documentation applies to the system clock, AHB, APB1 and APB2. For in-detail documentation +//! for HSI and PLL, check their documentation. +//! +//! # Features +//! +//! - [x] Dynamic system source +//! - [x] Hardware limits verification for AHB, APB1 and APB2. +//! - [x] Prescaler configuration for AHB, APB1 and APB2. +//! - [x] Support for MCO1 +//! +//! # Limitations +//! +//! - [ ] Precision of 1MHz +//! - [ ] No support for MCO2 +//! +//! # Usage [^usage_note] +//! +//! First, import the following enums: +//! +//! ```rust,ignore +//! // Assuming a STM32F429 chip. Change this to correspond to the chip model. +//! use stm32f429zi::rcc::APBPrescaler; +//! use stm32f429zi::rcc::AHBPrescaler; +//! use stm32f429zi::rcc::SysClockSource; +//! ``` +//! +//! A reference to the [crate::clocks::Clocks] is needed: +//! +//! ```rust,ignore +//! // Add this in board main.rs +//! let clocks = &peripherals.stm32f4.clocks; +//! ``` +//! +//! ## Retrieve the AHB frequency: +//! +//! ```rust,ignore +//! let ahb_frequency = clocks.get_ahb_frequency_mhz(); +//! debug!("Current AHB frequency is {}MHz", ahb_frequency); +//! ``` +//! +//! ## Retrieve the AHB prescaler: +//! +//! ```rust,ignore +//! let ahb_prescaler = clocks.get_ahb_prescaler(); +//! debug!("Current AHB prescaler is {:?}", ahb_prescaler); +//! ``` +//! +//! NOTE: If one wishes to get the usize equivalent value of [crate::clocks::Clocks::get_ahb_prescaler], to use in +//! computations for example, they must use [crate::rcc::AHBPrescaler].into() method: +//! +//! ```rust,ignore +//! let ahb_prescaler_usize: usize = clocks.get_ahb_prescaler().into(); +//! if ahb_prescaler_usize > 8 { +//! /* Do something */ +//! } +//! ``` +//! +//! ## Set the AHB prescaler +//! +//! ```rust,ignore +//! clocks.set_ahb_prescaler(AHBPrescaler::DivideBy4); +//! ``` +//! +//! ## APB1 and APB2 prescalers +//! +//! APB1 and APB2 prescalers are configured in a similar way as AHB prescaler, except that the +//! corresponding enum is APBPrescaler. +//! +//! ## Retrieve the system clock frequency: +//! +//! ```rust,ignore +//! let sys_frequency = clocks.get_sys_clock_frequency_mhz(); +//! debug!("Current system clock frequency is {}MHz", sys_frequency); +//! ``` +//! +//! ## Retrieve the system clock source: +//! +//! ```rust,ignore +//! let sys_source = clocks.get_sys_clock_source(); +//! debug!("Current system clock source is {:?}", sys_source); +//! ``` +//! +//! ## Change the system clock source to PLL: +//! +//! Changing the system clock source is a fastidious task because of AHB, APB1 and APB2 limits, +//! which are chip-dependent. This example assumes a STM32F429 chip. +//! +//! First, get a reference to the PLL +//! +//! ```rust,ignore +//! let pll = &peripherals.stm32f4.clocks.pll; +//! ``` +//! +//! Then, configure its frequency and enable it +//! ```rust,ignore +//! pll.set_frequency_mhz(50); +//! pll.enable(); +//! ``` +//! +//! STM32F429 maximum APB1 frequency is 45MHz, which is computed as following: +//! freq_APB1 = freq_sys / AHB_prescaler / APB1_prescaler +//! Default prescaler values are 1, which gives an frequency of 50MHz without modifying the +//! APB1 prescaler. As such, the APB1 prescaler must be changed. +//! +//! ```rust,ignore +//! clocks.set_apb1_prescaler(APBPrescaler::DivideBy2); +//! ``` +//! +//! Since the APB1 frequency limit is satisfied now, the system clock source can be safely changed. +//! +//! ```rust,ignore +//! clocks.set_sys_clock_source(SysClockSource::PLL); +//! ``` +//! +//! ## Another example of changing the system clock to PLL for STM32F429: +//! +//! As before, Pll clock is configured and enabled. +//! +//! ```rust,ignore +//! pll.set_frequency_mhz(100); +//! pll.enable(); +//! ``` +//! +//! Because of the high frequency of the PLL clock, both APB1 and APB2 prescalers must be +//! configured. +//! +//! ```rust,ignore +//! clocks.set_apb1_prescaler(APBPrescaler::DivideBy4); +//! clocks.set_apb2_prescaler(APBPrescaler::DivideBy2); +//! ``` +//! +//! As an alternative, the AHB prescaler could be configured to change both APB1 and APB2 +//! frequencies. +//! +//! ```rust,ignore +//! // Changing it to 2 wouldn't work, because it would give a frequency of 50MHz for the APB1. +//! clocks.set_ahb_prescaler(APBPrescaler::DivideBy4); +//! ``` +//! +//! Now, it's safe to change the system clock source: +//! +//! ```rust,ignore +//! clocks.set_sys_clock_source(SysClockSource::PLL); +//! ``` +//! +//! [^usage_note]: For the purpose of brevity, any error checking has been removed. + +use crate::chip_specific::ChipSpecs as ChipSpecsTrait; +use crate::clocks::hse::Hse; +use crate::clocks::hsi::Hsi; +use crate::clocks::hsi::HSI_FREQUENCY_MHZ; +use crate::clocks::pll::Pll; +use crate::flash::Flash; +use crate::rcc::AHBPrescaler; +use crate::rcc::APBPrescaler; +use crate::rcc::MCO1Divider; +use crate::rcc::MCO1Source; +use crate::rcc::PllSource; +use crate::rcc::Rcc; +use crate::rcc::SysClockSource; + +use kernel::debug; +use kernel::utilities::cells::OptionalCell; +use kernel::ErrorCode; + +/// Main struct for configuring on-board clocks. +pub struct Clocks<'a, ChipSpecs> { + rcc: &'a Rcc, + flash: OptionalCell<&'a Flash>, + /// High speed internal clock + pub hsi: Hsi<'a>, + /// High speed external clock + pub hse: Hse<'a>, + /// Main phase loop-lock clock + pub pll: Pll<'a, ChipSpecs>, +} + +impl<'a, ChipSpecs: ChipSpecsTrait> Clocks<'a, ChipSpecs> { + // The constructor must be called when the default peripherals are created + pub fn new(rcc: &'a Rcc) -> Self { + Self { + rcc, + flash: OptionalCell::empty(), + hsi: Hsi::new(rcc), + hse: Hse::new(rcc), + pll: Pll::new(rcc), + } + } + + // This method should be called when the dependencies are resolved + pub(crate) fn set_flash(&self, flash: &'a Flash) { + self.flash.set(flash); + } + + /// Set the AHB prescaler + /// + /// AHB bus, core, memory, DMA, Cortex System timer and FCLK Cortex free-running clock + /// frequencies are equal to the system clock frequency divided by the AHB prescaler. + /// + /// # Errors: + /// + /// + [Err]\([ErrorCode::FAIL]\) if changing the AHB prescaler doesn't preserve APB frequency + /// constraints + /// + [Err]\([ErrorCode::BUSY]\) if changing the AHB prescaler took too long. Retry. + pub fn set_ahb_prescaler(&self, prescaler: AHBPrescaler) -> Result<(), ErrorCode> { + // Changing the AHB prescaler affects the APB frequencies. A check must be done to ensure + // that the constraints are still valid + let divider: usize = prescaler.into(); + let new_ahb_frequency = self.get_sys_clock_frequency_mhz() / divider; + if !self.check_apb1_frequency_limit(new_ahb_frequency) + || !self.check_apb2_frequency_limit(new_ahb_frequency) + { + return Err(ErrorCode::FAIL); + } + + self.rcc.set_ahb_prescaler(prescaler); + + for _ in 0..16 { + if self.get_ahb_prescaler() == prescaler { + return Ok(()); + } + } + + Err(ErrorCode::BUSY) + } + + /// Get the current configured AHB prescaler + pub fn get_ahb_prescaler(&self) -> AHBPrescaler { + self.rcc.get_ahb_prescaler() + } + + /// Get the frequency of the AHB + pub fn get_ahb_frequency_mhz(&self) -> usize { + let ahb_divider: usize = self.get_ahb_prescaler().into(); + self.get_sys_clock_frequency_mhz() / ahb_divider + } + + // APB1 frequency must not be higher than the maximum allowable frequency. This method is + // called when the system clock source is changed. The ahb_frequency_mhz is the + // hypothetical future frequency. + fn check_apb1_frequency_limit(&self, ahb_frequency_mhz: usize) -> bool { + ahb_frequency_mhz + <= ChipSpecs::APB1_FREQUENCY_LIMIT_MHZ + * Into::::into(self.rcc.get_apb1_prescaler()) + } + + /// Set the APB1 prescaler. + /// + /// The APB1 peripheral clock frequency is equal to the AHB frequency divided by the APB1 + /// prescaler. + /// + /// # Errors: + /// + /// + [Err]\([ErrorCode::FAIL]\) if the desired prescaler would break the APB1 frequency limit + /// + [Err]\([ErrorCode::BUSY]\) if setting the prescaler took too long. Retry. + pub fn set_apb1_prescaler(&self, prescaler: APBPrescaler) -> Result<(), ErrorCode> { + let ahb_frequency = self.get_ahb_frequency_mhz(); + let divider: usize = prescaler.into(); + if ahb_frequency / divider > ChipSpecs::APB1_FREQUENCY_LIMIT_MHZ { + return Err(ErrorCode::FAIL); + } + + self.rcc.set_apb1_prescaler(prescaler); + + for _ in 0..16 { + if self.rcc.get_apb1_prescaler() == prescaler { + return Ok(()); + } + } + + Err(ErrorCode::BUSY) + } + + /// Get the current configured APB1 prescaler + pub fn get_apb1_prescaler(&self) -> APBPrescaler { + self.rcc.get_apb1_prescaler() + } + + /// Get the current APB1 frequency + pub fn get_apb1_frequency_mhz(&self) -> usize { + // Every enum variant can be converted into a usize + let divider: usize = self.rcc.get_apb1_prescaler().into(); + self.get_ahb_frequency_mhz() / divider + } + + // Same as for APB1, APB2 has a frequency limit that must be enforced by software + fn check_apb2_frequency_limit(&self, ahb_frequency_mhz: usize) -> bool { + ahb_frequency_mhz + <= ChipSpecs::APB2_FREQUENCY_LIMIT_MHZ + * Into::::into(self.rcc.get_apb2_prescaler()) + } + + /// Set the APB2 prescaler. + /// + /// The APB2 peripheral clock frequency is equal to the AHB frequency divided by the APB2 + /// prescaler. + /// + /// # Errors: + /// + /// + [Err]\([ErrorCode::FAIL]\) if the desired prescaler would break the APB2 frequency limit + /// + [Err]\([ErrorCode::BUSY]\) if setting the prescaler took too long. Retry. + pub fn set_apb2_prescaler(&self, prescaler: APBPrescaler) -> Result<(), ErrorCode> { + let current_ahb_frequency = self.get_ahb_frequency_mhz(); + let divider: usize = prescaler.into(); + if current_ahb_frequency / divider > ChipSpecs::APB2_FREQUENCY_LIMIT_MHZ { + return Err(ErrorCode::FAIL); + } + + self.rcc.set_apb2_prescaler(prescaler); + + for _ in 0..16 { + if self.rcc.get_apb2_prescaler() == prescaler { + return Ok(()); + } + } + + Err(ErrorCode::BUSY) + } + + /// Get the current configured APB2 prescaler + pub fn get_apb2_prescaler(&self) -> APBPrescaler { + self.rcc.get_apb2_prescaler() + } + + /// Get the current APB2 frequency + pub fn get_apb2_frequency_mhz(&self) -> usize { + // Every enum variant can be converted into a usize + let divider: usize = self.rcc.get_apb2_prescaler().into(); + self.get_ahb_frequency_mhz() / divider + } + + /// Set the system clock source + /// + /// # Errors: + /// + /// + [Err]\([ErrorCode::FAIL]\) if the source is not enabled. + /// + [Err]\([ErrorCode::SIZE]\) if the source frequency surpasses the system clock frequency + /// limit, or the APB1 and APB2 limits are not satisfied. + /// + [Err]\([ErrorCode::BUSY]\) if the source switching took too long. Retry. + pub fn set_sys_clock_source(&self, source: SysClockSource) -> Result<(), ErrorCode> { + // Immediately return if the required source is already configured as the system clock + // source. Should this maybe be Err(ErrorCode::ALREADY)? + if source == self.get_sys_clock_source() { + return Ok(()); + } + + // Ensure the source is enabled before configuring it as the system clock source + if let false = match source { + SysClockSource::HSI => self.hsi.is_enabled(), + SysClockSource::HSE => self.hse.is_enabled(), + SysClockSource::PLL => self.pll.is_enabled(), + } { + return Err(ErrorCode::FAIL); + } + + let current_frequency = self.get_sys_clock_frequency_mhz(); + + // Get the frequency of the source to be configured + let alternate_frequency = match source { + // The unwrap can't fail because the source clock status was checked before + SysClockSource::HSI => self.hsi.get_frequency_mhz().unwrap(), + SysClockSource::HSE => self.hse.get_frequency_mhz().unwrap(), + SysClockSource::PLL => self.pll.get_frequency_mhz().unwrap(), + }; + + // Check the alternate frequency is not higher than the system clock limit + if alternate_frequency > ChipSpecs::SYS_CLOCK_FREQUENCY_LIMIT_MHZ { + return Err(ErrorCode::SIZE); + } + + // Retrieve the currently configured AHB prescaler + let ahb_divider: usize = self.get_ahb_prescaler().into(); + // Compute the possible future AHB frequency + let ahb_frequency = alternate_frequency / ahb_divider; + + // APB1 frequency must not exceed APB1_FREQUENCY_LIMIT_MHZ + if !self.check_apb1_frequency_limit(ahb_frequency) { + return Err(ErrorCode::SIZE); + } + + // APB2 frequency must not exceed APB2_FREQUENCY_LIMIT_MHZ + if !self.check_apb2_frequency_limit(ahb_frequency) { + return Err(ErrorCode::SIZE); + } + + // The documentation recommends the following sequence when changing the system clock + // frequency: + // + // + if the desired frequency is higher than the current frequency, first change flash + // latency, then set the new system clock source. + // + if the desired frequency is lower than the current frequency, first change the system + // clock source, then set the flash latency + if alternate_frequency > current_frequency { + self.flash + .unwrap_or_panic() + .set_latency(alternate_frequency)?; + } + self.rcc.set_sys_clock_source(source); + if alternate_frequency < current_frequency { + self.flash + .unwrap_or_panic() + .set_latency(alternate_frequency)?; + } + + // If this point is reached, everything worked as expected + Ok(()) + } + + /// Get the current system clock source + pub fn get_sys_clock_source(&self) -> SysClockSource { + self.rcc.get_sys_clock_source() + } + + /// Get the current system clock frequency in MHz + pub fn get_sys_clock_frequency_mhz(&self) -> usize { + match self.get_sys_clock_source() { + // These unwraps can't panic because set_sys_clock_frequency ensures that the source is + // enabled. Also, Hsi and Pll structs ensure that the clocks can't be disabled when + // they are configured as the system clock + SysClockSource::HSI => self.hsi.get_frequency_mhz().unwrap(), + SysClockSource::HSE => self.hse.get_frequency_mhz().unwrap(), + SysClockSource::PLL => self.pll.get_frequency_mhz().unwrap(), + } + } + + /// Get the current system clock frequency in MHz from RCC registers instead of the cached + /// value. Used for debug only. + pub fn _get_sys_clock_frequency_mhz_no_cache(&self) -> usize { + match self.get_sys_clock_source() { + // These unwraps can't panic because set_sys_clock_frequency ensures that the source is + // enabled. Also, Hsi and Pll structs ensure that the clocks can't be disabled when + // they are configured as the system clock + SysClockSource::HSI => self.hsi.get_frequency_mhz().unwrap(), + SysClockSource::HSE => self.hse.get_frequency_mhz().unwrap(), + SysClockSource::PLL => { + let pll_source_frequency = match self.rcc.get_pll_clocks_source() { + PllSource::HSI => self.hsi.get_frequency_mhz().unwrap(), + PllSource::HSE => self.hse.get_frequency_mhz().unwrap(), + }; + self.pll + .get_frequency_mhz_no_cache(pll_source_frequency) + .unwrap() + } + } + } + + /// Set the frequency of the PLL clock. + /// + /// # Parameters + /// + /// + pll_source: PLL source clock (HSI or HSE) + /// + /// + desired_frequency_mhz: the desired frequency in MHz. Supported values: 24-216MHz for + /// STM32F401 and 13-216MHz for all the other chips + /// + /// # Errors + /// + /// + [Err]\([ErrorCode::INVAL]\): if the desired frequency can't be achieved + /// + [Err]\([ErrorCode::FAIL]\): if the PLL clock is already enabled. It must be disabled before + pub fn set_pll_frequency_mhz( + &self, + pll_source: PllSource, + desired_frequency_mhz: usize, + ) -> Result<(), ErrorCode> { + let source_frequency = match pll_source { + PllSource::HSI => HSI_FREQUENCY_MHZ, + PllSource::HSE => self.hse.get_frequency_mhz().unwrap(), + }; + self.pll + .set_frequency_mhz(pll_source, source_frequency, desired_frequency_mhz) + } + + /// Set the clock source for the microcontroller clock output 1 (MCO1) + /// + /// # Errors: + /// + /// + [Err]\([ErrorCode::FAIL]\) if the source apart from HSI is already enabled. + pub fn set_mco1_clock_source(&self, source: MCO1Source) -> Result<(), ErrorCode> { + match source { + MCO1Source::HSE => { + if !self.hse.is_enabled() { + return Err(ErrorCode::FAIL); + } + } + MCO1Source::PLL => { + if self.pll.is_enabled() { + return Err(ErrorCode::FAIL); + } + } + _ => (), + } + + self.rcc.set_mco1_clock_source(source); + + Ok(()) + } + + /// Get the clock source of the MCO1 + pub fn get_mco1_clock_source(&self) -> MCO1Source { + self.rcc.get_mco1_clock_source() + } + + /// Set MCO1 divider + /// + /// # Errors: + /// + /// + [Err]\([ErrorCode::FAIL]\) if the configured source apart from HSI is already enabled. + pub fn set_mco1_clock_divider(&self, divider: MCO1Divider) -> Result<(), ErrorCode> { + match self.get_mco1_clock_source() { + MCO1Source::PLL => { + if self.pll.is_enabled() { + return Err(ErrorCode::FAIL); + } + } + MCO1Source::HSI => (), + MCO1Source::HSE => (), + } + + self.rcc.set_mco1_clock_divider(divider); + + Ok(()) + } + + /// Get MCO1 divider + pub fn get_mco1_clock_divider(&self) -> MCO1Divider { + self.rcc.get_mco1_clock_divider() + } +} + +/// Stm32f4Clocks trait +/// +/// This can be used to control clocks without the need to keep a reference of the chip specific +/// Clocks struct, for instance by peripherals +pub trait Stm32f4Clocks { + /// Get RCC instance + fn get_rcc(&self) -> &Rcc; + + /// Get current AHB clock (HCLK) frequency in Hz + fn get_ahb_frequency(&self) -> usize; + + // Extend this to expose additional clock resources +} + +impl<'a, ChipSpecs: ChipSpecsTrait> Stm32f4Clocks for Clocks<'a, ChipSpecs> { + fn get_rcc(&self) -> &'a Rcc { + self.rcc + } + + fn get_ahb_frequency(&self) -> usize { + self.get_ahb_frequency_mhz() * 1_000_000 + } +} + +/// Tests for clocks functionalities +/// +/// These tests ensure the clocks are properly working. If any changes are made to the clock +/// module, make sure to run these tests. +/// +/// # Usage +/// +/// First, import the [crate::clocks] module inside the board main file: +/// +/// ```rust,ignore +/// // This example assumes a STM32F429 chip +/// use stm32f429zi::clocks; +/// ``` +/// +/// To run all the available tests, add this line before **kernel::process::load_processes()**: +/// +/// ```rust,ignore +/// clocks::tests::run_all(&peripherals.stm32f4.clocks); +/// ``` +/// +/// If everything works as expected, the following message should be printed on the kernel console: +/// +/// ```text +/// =============================================== +/// Testing clocks... +/// +/// =============================================== +/// Testing HSI... +/// Finished testing HSI. Everything is alright! +/// =============================================== +/// +/// +/// =============================================== +/// Testing PLL... +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// Testing PLL configuration... +/// Finished testing PLL configuration. +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// Testing PLL struct... +/// Finished testing PLL struct. +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// Finished testing PLL. Everything is alright! +/// =============================================== +/// +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// Testing clocks struct... +/// Finished testing clocks struct. Everything is alright! +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Finished testing clocks. Everything is alright! +/// =============================================== +/// ``` +/// +/// There is also the possibility to run a part of the test suite. Check the functions present in +/// this module for more details. +/// +/// # Errors +/// +/// If there are any errors, open an issue ticket at . Please provide the +/// output of the test execution. +pub mod tests { + use super::*; + + const LOW_FREQUENCY: usize = 25; + #[cfg(not(any( + feature = "stm32f401", + feature = "stm32f410", + feature = "stm32f411", + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423" + )))] + const HIGH_FREQUENCY: usize = 112; + #[cfg(any( + feature = "stm32f401", + feature = "stm32f410", + feature = "stm32f411", + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423" + ))] + const HIGH_FREQUENCY: usize = 80; + + fn set_default_configuration(clocks: &Clocks) { + assert_eq!(Ok(()), clocks.set_sys_clock_source(SysClockSource::HSI)); + assert_eq!(Ok(()), clocks.pll.disable()); + assert_eq!(Ok(()), clocks.set_ahb_prescaler(AHBPrescaler::DivideBy1)); + assert_eq!(Ok(()), clocks.set_apb1_prescaler(APBPrescaler::DivideBy1)); + assert_eq!(Ok(()), clocks.set_apb2_prescaler(APBPrescaler::DivideBy1)); + assert_eq!(HSI_FREQUENCY_MHZ, clocks.get_sys_clock_frequency_mhz()); + assert_eq!(HSI_FREQUENCY_MHZ, clocks.get_apb1_frequency_mhz()); + assert_eq!(HSI_FREQUENCY_MHZ, clocks.get_apb2_frequency_mhz()); + } + + // This macro ensure that the system clock frequency goes back to the default value to prevent + // changing the UART baud rate + macro_rules! check_and_panic { + ($left:expr, $right:expr, $clocks: ident) => { + match (&$left, &$right) { + (left_val, right_val) => { + if *left_val != *right_val { + set_default_configuration($clocks); + assert_eq!($left, $right); + } + } + }; + }; + } + + /// Test for the AHB and APB prescalers + /// + /// # Usage + /// + /// First, import the clock module: + /// + /// ```rust,ignore + /// // This test assumes a STM32F429 chip + /// use stm32f429zi::clocks; + /// ``` + /// + /// Then run the test: + /// + /// ```rust,ignore + /// clocks::test::test_prescalers(&peripherals.stm32f4.clocks); + /// ``` + pub fn test_prescalers(clocks: &Clocks) { + // This test requires a bit of setup. A system clock running at HIGH_FREQUENCY is configured. + check_and_panic!( + Ok(()), + clocks + .pll + .set_frequency_mhz(PllSource::HSI, HSI_FREQUENCY_MHZ, HIGH_FREQUENCY), + clocks + ); + check_and_panic!(Ok(()), clocks.pll.enable(), clocks); + check_and_panic!( + Ok(()), + clocks.set_apb1_prescaler(APBPrescaler::DivideBy4), + clocks + ); + check_and_panic!( + Ok(()), + clocks.set_apb2_prescaler(APBPrescaler::DivideBy2), + clocks + ); + check_and_panic!( + Ok(()), + clocks.set_sys_clock_source(SysClockSource::PLL), + clocks + ); + + // Trying to reduce the APB scaler to an invalid value should fail + check_and_panic!( + Err(ErrorCode::FAIL), + clocks.set_apb1_prescaler(APBPrescaler::DivideBy1), + clocks + ); + // The following assert will pass on these models because of the low system clock + // frequency limit + #[cfg(not(any( + feature = "stm32f401", + feature = "stm32f410", + feature = "stm32f411", + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423" + )))] + check_and_panic!( + Err(ErrorCode::FAIL), + clocks.set_apb2_prescaler(APBPrescaler::DivideBy1), + clocks + ); + // Any failure in changing the APB prescalers must preserve their values + check_and_panic!(APBPrescaler::DivideBy4, clocks.get_apb1_prescaler(), clocks); + check_and_panic!(APBPrescaler::DivideBy2, clocks.get_apb2_prescaler(), clocks); + + // Increasing the AHB prescaler should allow decreasing APB prescalers + check_and_panic!( + Ok(()), + clocks.set_ahb_prescaler(AHBPrescaler::DivideBy4), + clocks + ); + check_and_panic!( + Ok(()), + clocks.set_apb1_prescaler(APBPrescaler::DivideBy1), + clocks + ); + check_and_panic!( + Ok(()), + clocks.set_apb2_prescaler(APBPrescaler::DivideBy1), + clocks + ); + + // Now, decreasing the AHB prescaler would result in the violation of APB constraints + check_and_panic!( + Err(ErrorCode::FAIL), + clocks.set_ahb_prescaler(AHBPrescaler::DivideBy1), + clocks + ); + // Any failure in changing the AHB prescaler must preserve its value + check_and_panic!(AHBPrescaler::DivideBy4, clocks.get_ahb_prescaler(), clocks); + + // Revert to default configuration + set_default_configuration(clocks); + } + + /// Test for the [crate::clocks::Clocks] struct + /// + /// # Usage + /// + /// First, import the clock module: + /// + /// ```rust,ignore + /// // This test assumes a STM32F429 chip + /// use stm32f429zi::clocks; + /// ``` + /// + /// Then run the test: + /// + /// ```rust,ignore + /// clocks::test::test_clocks_struct(&peripherals.stm32f4.clocks); + /// ``` + pub fn test_clocks_struct(clocks: &Clocks) { + debug!(""); + debug!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + debug!("Testing clocks struct..."); + + // By default, the HSI clock is the system clock + check_and_panic!(SysClockSource::HSI, clocks.get_sys_clock_source(), clocks); + + // HSI frequency is 16MHz + check_and_panic!( + HSI_FREQUENCY_MHZ, + clocks.get_sys_clock_frequency_mhz(), + clocks + ); + + // APB1 default prescaler is 1 + check_and_panic!(APBPrescaler::DivideBy1, clocks.get_apb1_prescaler(), clocks); + + // APB1 default frequency is 16MHz + check_and_panic!(HSI_FREQUENCY_MHZ, clocks.get_apb1_frequency_mhz(), clocks); + + // APB2 default prescaler is 1 + check_and_panic!(APBPrescaler::DivideBy1, clocks.get_apb1_prescaler(), clocks); + + // APB2 default frequency is 16MHz + check_and_panic!(HSI_FREQUENCY_MHZ, clocks.get_apb2_frequency_mhz(), clocks); + + // Attempting to change the system clock source with a disabled source + check_and_panic!( + Err(ErrorCode::FAIL), + clocks.set_sys_clock_source(SysClockSource::PLL), + clocks + ); + + // Attempting to set twice the same system clock source is fine + check_and_panic!( + Ok(()), + clocks.set_sys_clock_source(SysClockSource::HSI), + clocks + ); + + // Change the system clock source to a low frequency so that APB prescalers don't need to be + // changed + check_and_panic!( + Ok(()), + clocks + .pll + .set_frequency_mhz(PllSource::HSI, HSI_FREQUENCY_MHZ, LOW_FREQUENCY), + clocks + ); + check_and_panic!(Ok(()), clocks.pll.enable(), clocks); + check_and_panic!( + Ok(()), + clocks.set_sys_clock_source(SysClockSource::PLL), + clocks + ); + check_and_panic!(SysClockSource::PLL, clocks.get_sys_clock_source(), clocks); + + // Now the system clock frequency is equal to 25MHz + check_and_panic!(LOW_FREQUENCY, clocks.get_sys_clock_frequency_mhz(), clocks); + + // APB1 and APB2 frequencies must also be 25MHz + check_and_panic!(LOW_FREQUENCY, clocks.get_apb1_frequency_mhz(), clocks); + check_and_panic!(LOW_FREQUENCY, clocks.get_apb2_frequency_mhz(), clocks); + + // Attempting to disable PLL when it is configured as the system clock must fail + check_and_panic!(Err(ErrorCode::FAIL), clocks.pll.disable(), clocks); + // Same for the HSI since it is used indirectly as a system clock through PLL + check_and_panic!(Err(ErrorCode::FAIL), clocks.hsi.disable(), clocks); + + // Revert to default system clock configuration + set_default_configuration(clocks); + + // Attempting to change the system clock frequency without correctly configuring the APB1 + // prescaler (freq_APB1 <= APB1_FREQUENCY_LIMIT_MHZ) and APB2 prescaler + // (freq_APB2 <= APB2_FREQUENCY_LIMIT_MHZ) must fail + check_and_panic!(Ok(()), clocks.pll.disable(), clocks); + check_and_panic!( + Ok(()), + clocks + .pll + .set_frequency_mhz(PllSource::HSI, HSI_FREQUENCY_MHZ, HIGH_FREQUENCY), + clocks + ); + check_and_panic!(Ok(()), clocks.pll.enable(), clocks); + check_and_panic!( + Err(ErrorCode::SIZE), + clocks.set_sys_clock_source(SysClockSource::PLL), + clocks + ); + + // Even if the APB1 prescaler is changed to 2, it must fail + // (HIGH_FREQUENCY / 2 > APB1_FREQUENCY_LIMIT_MHZ) + check_and_panic!( + Ok(()), + clocks.set_apb1_prescaler(APBPrescaler::DivideBy2), + clocks + ); + #[cfg(not(any( + feature = "stm32f401", + feature = "stm32f410", + feature = "stm32f411", + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423" + )))] + check_and_panic!( + Err(ErrorCode::SIZE), + clocks.set_sys_clock_source(SysClockSource::PLL), + clocks + ); + + // Configuring APB1 prescaler to 4 is fine, but APB2 prescaler is still wrong + check_and_panic!( + Ok(()), + clocks.set_apb1_prescaler(APBPrescaler::DivideBy4), + clocks + ); + #[cfg(not(any( + feature = "stm32f401", + feature = "stm32f410", + feature = "stm32f411", + feature = "stm32f412", + feature = "stm32f413", + feature = "stm32f423" + )))] + check_and_panic!( + Err(ErrorCode::SIZE), + clocks.set_sys_clock_source(SysClockSource::PLL), + clocks + ); + + // Configuring APB2 prescaler to 2 + check_and_panic!( + Ok(()), + clocks.set_apb2_prescaler(APBPrescaler::DivideBy2), + clocks + ); + + // Now the system clock source can be changed + check_and_panic!( + Ok(()), + clocks.set_sys_clock_source(SysClockSource::PLL), + clocks + ); + check_and_panic!(HIGH_FREQUENCY / 4, clocks.get_apb1_frequency_mhz(), clocks); + check_and_panic!(HIGH_FREQUENCY / 2, clocks.get_apb2_frequency_mhz(), clocks); + + // Revert to default system clock configuration + set_default_configuration(clocks); + + // This time, configure the AHB prescaler instead of APB prescalers + check_and_panic!( + Ok(()), + clocks.set_ahb_prescaler(AHBPrescaler::DivideBy4), + clocks + ); + check_and_panic!(Ok(()), clocks.pll.enable(), clocks); + check_and_panic!( + Ok(()), + clocks.set_sys_clock_source(SysClockSource::PLL), + clocks + ); + check_and_panic!(HIGH_FREQUENCY / 4, clocks.get_ahb_frequency_mhz(), clocks); + check_and_panic!(HIGH_FREQUENCY / 4, clocks.get_apb1_frequency_mhz(), clocks); + check_and_panic!(HIGH_FREQUENCY / 4, clocks.get_apb2_frequency_mhz(), clocks); + + // Revert to default configuration + set_default_configuration(clocks); + + debug!("Finished testing clocks struct. Everything is alright!"); + debug!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + debug!(""); + } + + /// Test for the microcontroller clock outputs + /// + /// # Usage + /// + /// First, import the clock module: + /// + /// ```rust,ignore + /// // This test assumes a STM32F429 chip + /// use stm32f429zi::clocks; + /// ``` + /// + /// Then run the test: + /// + /// ```rust,ignore + /// clocks::test::test_mco(&peripherals.stm32f4.clocks); + /// ``` + pub fn test_mco(clocks: &Clocks) { + debug!(""); + debug!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + debug!("Testing MCOs..."); + + // Set MCO1 source to PLL + assert_eq!(Ok(()), clocks.set_mco1_clock_source(MCO1Source::PLL)); + + // Set MCO1 divider to 3 + assert_eq!( + Ok(()), + clocks.set_mco1_clock_divider(MCO1Divider::DivideBy3) + ); + + // Enable PLL + assert_eq!(Ok(()), clocks.pll.enable()); + + // Attempting to change the divider while the PLL is running must fail + assert_eq!( + Err(ErrorCode::FAIL), + clocks.set_mco1_clock_divider(MCO1Divider::DivideBy2) + ); + + // Switch back to HSI + assert_eq!(Ok(()), clocks.set_mco1_clock_source(MCO1Source::HSI)); + + // Attempting to change the source to PLL when it is already enabled must fail + assert_eq!( + Err(ErrorCode::FAIL), + clocks.set_mco1_clock_source(MCO1Source::PLL) + ); + + debug!("Finished testing MCOs. Everything is alright!"); + debug!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + debug!(""); + } + + /// Run the entire test suite for all clocks + pub fn run_all(clocks: &Clocks) { + debug!(""); + debug!("==============================================="); + debug!("Testing clocks..."); + + crate::clocks::hsi::tests::run(&clocks.hsi); + crate::clocks::pll::tests::run(&clocks.pll); + test_prescalers(clocks); + test_clocks_struct(clocks); + test_mco(clocks); + + debug!("Finished testing clocks. Everything is alright!"); + debug!("==============================================="); + debug!(""); + } +} diff --git a/chips/stm32f4xx/src/clocks/hse.rs b/chips/stm32f4xx/src/clocks/hse.rs new file mode 100644 index 0000000000..22bdd4069b --- /dev/null +++ b/chips/stm32f4xx/src/clocks/hse.rs @@ -0,0 +1,218 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! HSE (high-speed external) clock driver for the STM32F4xx family. [^doc_ref] +//! +//! # Usage +//! +//! For the purposes of brevity, any error checking has been removed. In real applications, always +//! check the return values of the [Hse] methods. +//! +//! First, get a reference to the [Hse] struct: +//! ```rust,ignore +//! let hse = &base_peripherals.clocks.hse; +//! ``` +//! +//! ## Start the clock +//! +//! ```rust,ignore +//! hse.enable(stm32f429zi::rcc::HseMode::BYPASS); +//! ``` +//! +//! ## Set the clock frequency +//! ```rust,ignore +//! hse.set_frequency_mhz(8); +//! ``` +//! +//! ## Stop the clock +//! +//! ```rust,ignore +//! hse.disable(); +//! ``` +//! +//! ## Check if the clock is enabled +//! ```rust,ignore +//! if hse.is_enabled() { +//! /* Do something */ +//! } else { +//! /* Do something */ +//! } +//! ``` +//! +//! ## Get the frequency of the clock +//! ```rust,ignore +//! let hse_frequency_mhz = hse.get_frequency_mhz().unwrap(); +//! ``` +//! +//! [^doc_ref]: See 6.2.1 in the documentation. + +use crate::rcc::HseMode; +use crate::rcc::Rcc; + +use kernel::debug; +use kernel::utilities::cells::OptionalCell; +use kernel::ErrorCode; + +/// Main HSE clock structure +pub struct Hse<'a> { + rcc: &'a Rcc, + hse_frequency_mhz: OptionalCell, +} + +impl<'a> Hse<'a> { + /// Create a new instance of the HSE clock. + /// + /// # Parameters + /// + /// + rcc: an instance of [crate::rcc] + /// + /// # Returns + /// + /// An instance of the HSE clock. + pub(in crate::clocks) fn new(rcc: &'a Rcc) -> Self { + Self { + rcc, + hse_frequency_mhz: OptionalCell::empty(), + } + } + + /// Start the HSE clock. + /// + /// # Errors + /// + /// + [Err]\([ErrorCode::BUSY]\): disabling the HSE clock took to long. Retry to ensure it is running + pub fn enable(&self, source: HseMode) -> Result<(), ErrorCode> { + if source == HseMode::BYPASS { + self.rcc.enable_hse_clock_bypass(); + } + + self.rcc.enable_hse_clock(); + + for _ in 0..100 { + if self.rcc.is_ready_hse_clock() { + return Ok(()); + } + } + + Err(ErrorCode::BUSY) + } + + /// Stop the HSE clock. + /// + /// # Errors + /// + /// + [Err]\([ErrorCode::FAIL]\): if the HSE clock is configured as the system clock. + /// + [Err]\([ErrorCode::BUSY]\): disabling the HSE clock took to long. Retry to ensure it is + /// not running. + pub fn disable(&self) -> Result<(), ErrorCode> { + if self.rcc.is_hse_clock_system_clock() { + return Err(ErrorCode::FAIL); + } + + self.rcc.disable_hse_clock(); + + for _ in 0..10 { + if !self.rcc.is_ready_hse_clock() { + return Ok(()); + } + } + + Err(ErrorCode::BUSY) + } + + /// Check whether the HSE clock is enabled or not. + /// + /// # Returns + /// + /// + [false]: the HSE clock is not enabled + /// + [true]: the HSE clock is enabled + pub fn is_enabled(&self) -> bool { + self.rcc.is_enabled_hse_clock() + } + + /// Get the frequency in MHz of the HSE clock. + /// + /// # Returns + /// + /// + [Some]\(frequency_mhz\): if the HSE clock is enabled. + /// + [None]: if the HSE clock is disabled. + pub fn get_frequency_mhz(&self) -> Option { + if self.is_enabled() { + self.hse_frequency_mhz.get() + } else { + None + } + } + + /// Set the frequency in MHz of the HSE clock. + /// + /// # Parameters + /// + /// + frequency: HSE frequency in MHz + pub fn set_frequency_mhz(&self, frequency: usize) { + self.hse_frequency_mhz.set(frequency); + } +} + +/// Tests for the HSE clock +/// +/// This module ensures that the HSE clock works as expected. If changes are brought to the HSE +/// clock, ensure to run all the tests to see if anything is broken. +/// +/// # Usage +/// +/// First, import the [crate::clocks::hse] module in the desired board main file: +/// +/// ```rust,ignore +/// use stm32f429zi::clocks::hse; +/// ``` +/// +/// Then, to run the tests, put the following line before [kernel::process::load_processes]: +/// +/// ```rust,ignore +/// hse::tests::run(&peripherals.stm32f4.clocks.hse); +/// ``` +/// +/// If everything works as expected, the following message should be printed on the kernel console: +/// +/// ```text +/// =============================================== +/// Testing HSE... +/// Finished testing HSE. Everything is alright! +/// =============================================== +/// ``` +/// +/// **NOTE:** All these tests assume default boot configuration. +pub mod tests { + use super::*; + + /// Run the entire test suite. + pub fn run(hse: &Hse) { + debug!(""); + debug!("==============================================="); + debug!("Testing HSE..."); + + // By default, the HSE clock is disabled + assert!(!hse.is_enabled()); + + // HSE frequency is None + assert_eq!(None, hse.get_frequency_mhz()); + + // HSE should be enabled + assert_eq!(Ok(()), hse.enable(HseMode::BYPASS)); + + // HSE frequency is 8MHz + assert_eq!(Some(8), hse.get_frequency_mhz()); + + // Nothing should happen if the HSE clock is being enabled when already running + assert_eq!(Ok(()), hse.enable(HseMode::BYPASS)); + + // It is possible to disable the HSE clock since it is not the system clock source + assert_eq!(Ok(()), hse.disable()); + + debug!("Finished testing HSE. Everything is alright!"); + debug!("==============================================="); + debug!(""); + } +} diff --git a/chips/stm32f4xx/src/clocks/hsi.rs b/chips/stm32f4xx/src/clocks/hsi.rs new file mode 100644 index 0000000000..96efa2bf94 --- /dev/null +++ b/chips/stm32f4xx/src/clocks/hsi.rs @@ -0,0 +1,194 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive SRL. +// +// Author: Ioan-Cristian CÎRSTEA + +//! HSI (high-speed internal) clock driver for the STM32F4xx family. [^doc_ref] +//! +//! # Usage +//! +//! For the purposes of brevity, any error checking has been removed. In real applications, always +//! check the return values of the [Hsi] methods. +//! +//! First, get a reference to the [Hsi] struct: +//! ```rust,ignore +//! let hsi = &peripherals.stm32f4.clocks.hsi; +//! ``` +//! +//! ## Start the clock +//! +//! ```rust,ignore +//! hsi.enable(); +//! ``` +//! +//! ## Stop the clock +//! +//! ```rust,ignore +//! hsi.disable(); +//! ``` +//! +//! ## Check if the clock is enabled +//! ```rust,ignore +//! if hsi.is_enabled() { +//! /* Do something */ +//! } else { +//! /* Do something */ +//! } +//! ``` +//! +//! ## Get the frequency of the clock +//! ```rust,ignore +//! let hsi_frequency_mhz = hsi.get_frequency().unwrap(); +//! ``` +//! +//! [^doc_ref]: See 6.2.2 in the documentation. + +use crate::rcc::Rcc; + +use kernel::debug; +use kernel::ErrorCode; + +/// HSI frequency in MHz +pub const HSI_FREQUENCY_MHZ: usize = 16; + +/// Main HSI clock structure +pub struct Hsi<'a> { + rcc: &'a Rcc, +} + +impl<'a> Hsi<'a> { + /// Create a new instance of the HSI clock. + /// + /// # Parameters + /// + /// + rcc: an instance of [crate::rcc] + /// + /// # Returns + /// + /// An instance of the HSI clock. + pub(in crate::clocks) fn new(rcc: &'a Rcc) -> Self { + Self { rcc } + } + + /// Start the HSI clock. + /// + /// # Errors + /// + /// + [Err]\([ErrorCode::BUSY]\): if enabling the HSI clock took too long. Recall this method to + /// ensure the HSI clock is running. + pub fn enable(&self) -> Result<(), ErrorCode> { + self.rcc.enable_hsi_clock(); + + for _ in 0..100 { + if self.rcc.is_ready_hsi_clock() { + return Ok(()); + } + } + + Err(ErrorCode::BUSY) + } + + /// Stop the HSI clock. + /// + /// # Errors + /// + /// + [Err]\([ErrorCode::FAIL]\): if the HSI clock is configured as the system clock. + /// + [Err]\([ErrorCode::BUSY]\): disabling the HSI clock took to long. Retry to ensure it is + /// not running. + pub fn disable(&self) -> Result<(), ErrorCode> { + if self.rcc.is_hsi_clock_system_clock() { + return Err(ErrorCode::FAIL); + } + + self.rcc.disable_hsi_clock(); + + for _ in 0..10 { + if !self.rcc.is_ready_hsi_clock() { + return Ok(()); + } + } + + Err(ErrorCode::BUSY) + } + + /// Check whether the HSI clock is enabled or not. + /// + /// # Returns + /// + /// + [false]: the HSI clock is not enabled + /// + [true]: the HSI clock is enabled + pub fn is_enabled(&self) -> bool { + self.rcc.is_enabled_hsi_clock() + } + + /// Get the frequency in MHz of the HSI clock. + /// + /// # Returns + /// + /// + [Some]\(frequency_mhz\): if the HSI clock is enabled. + /// + [None]: if the HSI clock is disabled. + pub fn get_frequency_mhz(&self) -> Option { + if self.is_enabled() { + Some(HSI_FREQUENCY_MHZ) + } else { + None + } + } +} + +/// Tests for the HSI clock +/// +/// This module ensures that the HSI clock works as expected. If changes are brought to the HSI +/// clock, ensure to run all the tests to see if anything is broken. +/// +/// # Usage +/// +/// First, import the [crate::clocks::hsi] module in the desired board main file: +/// +/// ```rust,ignore +/// use stm32f429zi::clocks::hsi; +/// ``` +/// +/// Then, to run the tests, put the following line before [kernel::process::load_processes]: +/// +/// ```rust,ignore +/// hsi::tests::run(&peripherals.stm32f4.clocks.hsi); +/// ``` +/// +/// If everything works as expected, the following message should be printed on the kernel console: +/// +/// ```text +/// =============================================== +/// Testing HSI... +/// Finished testing HSI. Everything is alright! +/// =============================================== +/// ``` +/// +/// **NOTE:** All these tests assume default boot configuration. +pub mod tests { + use super::*; + + /// Run the entire test suite. + pub fn run(hsi: &Hsi) { + debug!(""); + debug!("==============================================="); + debug!("Testing HSI..."); + + // By default, the HSI clock is enabled + assert!(hsi.is_enabled()); + + // HSI frequency is 16MHz + assert_eq!(Some(HSI_FREQUENCY_MHZ), hsi.get_frequency_mhz()); + + // Nothing should happen if the HSI clock is being enabled when already running + assert_eq!(Ok(()), hsi.enable()); + + // Impossible to disable the HSI clock since it is the system clock source + assert_eq!(Err(ErrorCode::FAIL), hsi.disable()); + + debug!("Finished testing HSI. Everything is alright!"); + debug!("==============================================="); + debug!(""); + } +} diff --git a/chips/stm32f4xx/src/clocks/mod.rs b/chips/stm32f4xx/src/clocks/mod.rs new file mode 100644 index 0000000000..2e507bbaae --- /dev/null +++ b/chips/stm32f4xx/src/clocks/mod.rs @@ -0,0 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive SRL. +// +// Author: Ioan-Cristian CÎRSTEA + +pub mod clocks; +pub mod hse; +pub mod hsi; +pub mod phclk; +pub mod pll; + +pub use crate::clocks::clocks::tests; +pub use crate::clocks::clocks::{Clocks, Stm32f4Clocks}; diff --git a/chips/stm32f4xx/src/clocks/phclk.rs b/chips/stm32f4xx/src/clocks/phclk.rs new file mode 100644 index 0000000000..3595f8805e --- /dev/null +++ b/chips/stm32f4xx/src/clocks/phclk.rs @@ -0,0 +1,334 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::clocks::Stm32f4Clocks; +use crate::rcc::{APBPrescaler, Rcc, RtcClockSource}; +use kernel::platform::chip::ClockInterface; + +pub struct PeripheralClock<'a> { + pub clock: PeripheralClockType, + clocks: &'a dyn Stm32f4Clocks, +} + +/// Bus + Clock name for the peripherals +pub enum PeripheralClockType { + AHB1(HCLK1), + AHB2(HCLK2), + AHB3(HCLK3), + APB1(PCLK1), + APB2(PCLK2), + RTC, + PWR, +} + +/// Peripherals clocked by HCLK1 +pub enum HCLK1 { + DMA1, + DMA2, + GPIOH, + GPIOG, + GPIOF, + GPIOE, + GPIOD, + GPIOC, + GPIOB, + GPIOA, +} + +/// Peripherals clocked by HCLK3 +pub enum HCLK3 { + FMC, +} + +/// Peripherals clocked by HCLK2 +pub enum HCLK2 { + RNG, + OTGFS, +} + +/// Peripherals clocked by PCLK1 +pub enum PCLK1 { + TIM2, + USART2, + USART3, + SPI3, + I2C1, + CAN1, + DAC, +} + +/// Peripherals clocked by PCLK2 +pub enum PCLK2 { + USART1, + ADC1, + SYSCFG, +} + +impl<'a> PeripheralClock<'a> { + pub const fn new(clock: PeripheralClockType, clocks: &'a dyn Stm32f4Clocks) -> Self { + Self { clock, clocks } + } + + pub fn configure_rng_clock(&self) { + self.clocks.get_rcc().configure_rng_clock(); + } + + pub fn get_frequency(&self) -> u32 { + #[inline(always)] + fn tim_freq(rcc: &Rcc, hclk_freq: usize, prescaler: APBPrescaler) -> usize { + // Reference Manual RM0090 section 6.2 + // When TIMPRE bit of the RCC_DCKCFGR register is reset, if APBx prescaler is 1, then + // TIMxCLK = PCLKx, otherwise TIMxCLK = 2x PCLKx. + // When TIMPRE bit in the RCC_DCKCFGR register is set, if APBx prescaler is 1,2 or 4, + // then TIMxCLK = HCLK, otherwise TIMxCLK = 4x PCLKx. + if !rcc.is_enabled_tim_pre() { + match prescaler { + APBPrescaler::DivideBy1 | APBPrescaler::DivideBy2 => hclk_freq, + _ => hclk_freq / usize::from(prescaler) * 2, + } + } else { + match prescaler { + APBPrescaler::DivideBy1 | APBPrescaler::DivideBy2 | APBPrescaler::DivideBy4 => { + hclk_freq + } + _ => hclk_freq / usize::from(prescaler) * 4, + } + } + } + let rcc = self.clocks.get_rcc(); + let hclk_freq = self.clocks.get_ahb_frequency(); + match self.clock { + PeripheralClockType::AHB1(_) + | PeripheralClockType::AHB2(_) + | PeripheralClockType::AHB3(_) => hclk_freq as u32, + PeripheralClockType::APB1(ref v) => { + let prescaler = rcc.get_apb1_prescaler(); + match v { + PCLK1::TIM2 => tim_freq(rcc, hclk_freq, prescaler) as u32, + _ => (hclk_freq / usize::from(prescaler)) as u32, + } + } + PeripheralClockType::APB2(_) => { + let prescaler = rcc.get_apb2_prescaler(); + (hclk_freq / usize::from(prescaler)) as u32 + } + //TODO: implement clock frequency retrieval for RTC and PWR peripherals + PeripheralClockType::RTC => todo!(), + PeripheralClockType::PWR => todo!(), + } + } +} + +impl<'a> ClockInterface for PeripheralClock<'a> { + fn is_enabled(&self) -> bool { + let rcc = self.clocks.get_rcc(); + match self.clock { + PeripheralClockType::AHB1(ref v) => match v { + HCLK1::DMA1 => rcc.is_enabled_dma1_clock(), + HCLK1::DMA2 => rcc.is_enabled_dma2_clock(), + HCLK1::GPIOH => rcc.is_enabled_gpioh_clock(), + HCLK1::GPIOG => rcc.is_enabled_gpiog_clock(), + HCLK1::GPIOF => rcc.is_enabled_gpiof_clock(), + HCLK1::GPIOE => rcc.is_enabled_gpioe_clock(), + HCLK1::GPIOD => rcc.is_enabled_gpiod_clock(), + HCLK1::GPIOC => rcc.is_enabled_gpioc_clock(), + HCLK1::GPIOB => rcc.is_enabled_gpiob_clock(), + HCLK1::GPIOA => rcc.is_enabled_gpioa_clock(), + }, + PeripheralClockType::AHB2(ref v) => match v { + HCLK2::RNG => rcc.is_enabled_rng_clock(), + HCLK2::OTGFS => rcc.is_enabled_otgfs_clock(), + }, + PeripheralClockType::AHB3(ref v) => match v { + HCLK3::FMC => rcc.is_enabled_fmc_clock(), + }, + PeripheralClockType::APB1(ref v) => match v { + PCLK1::TIM2 => rcc.is_enabled_tim2_clock(), + PCLK1::USART2 => rcc.is_enabled_usart2_clock(), + PCLK1::USART3 => rcc.is_enabled_usart3_clock(), + PCLK1::I2C1 => rcc.is_enabled_i2c1_clock(), + PCLK1::SPI3 => rcc.is_enabled_spi3_clock(), + PCLK1::CAN1 => rcc.is_enabled_can1_clock(), + PCLK1::DAC => rcc.is_enabled_dac_clock(), + }, + PeripheralClockType::APB2(ref v) => match v { + PCLK2::USART1 => rcc.is_enabled_usart1_clock(), + PCLK2::ADC1 => rcc.is_enabled_adc1_clock(), + PCLK2::SYSCFG => rcc.is_enabled_syscfg_clock(), + }, + PeripheralClockType::RTC => rcc.is_enabled_rtc_clock(), + PeripheralClockType::PWR => rcc.is_enabled_pwr_clock(), + } + } + + fn enable(&self) { + let rcc = self.clocks.get_rcc(); + match self.clock { + PeripheralClockType::AHB1(ref v) => match v { + HCLK1::DMA1 => { + rcc.enable_dma1_clock(); + } + HCLK1::DMA2 => { + rcc.enable_dma2_clock(); + } + HCLK1::GPIOH => { + rcc.enable_gpioh_clock(); + } + HCLK1::GPIOG => { + rcc.enable_gpiog_clock(); + } + HCLK1::GPIOF => { + rcc.enable_gpiof_clock(); + } + HCLK1::GPIOE => { + rcc.enable_gpioe_clock(); + } + HCLK1::GPIOD => { + rcc.enable_gpiod_clock(); + } + HCLK1::GPIOC => { + rcc.enable_gpioc_clock(); + } + HCLK1::GPIOB => { + rcc.enable_gpiob_clock(); + } + HCLK1::GPIOA => { + rcc.enable_gpioa_clock(); + } + }, + PeripheralClockType::AHB2(ref v) => match v { + HCLK2::RNG => { + rcc.enable_rng_clock(); + } + HCLK2::OTGFS => { + rcc.enable_otgfs_clock(); + } + }, + PeripheralClockType::AHB3(ref v) => match v { + HCLK3::FMC => rcc.enable_fmc_clock(), + }, + PeripheralClockType::APB1(ref v) => match v { + PCLK1::TIM2 => { + rcc.enable_tim2_clock(); + } + PCLK1::USART2 => { + rcc.enable_usart2_clock(); + } + PCLK1::USART3 => { + rcc.enable_usart3_clock(); + } + PCLK1::I2C1 => { + rcc.enable_i2c1_clock(); + } + PCLK1::SPI3 => { + rcc.enable_spi3_clock(); + } + PCLK1::CAN1 => { + rcc.enable_can1_clock(); + } + PCLK1::DAC => { + rcc.enable_dac_clock(); + } + }, + PeripheralClockType::APB2(ref v) => match v { + PCLK2::USART1 => { + rcc.enable_usart1_clock(); + } + PCLK2::ADC1 => { + rcc.enable_adc1_clock(); + } + PCLK2::SYSCFG => { + rcc.enable_syscfg_clock(); + } + }, + PeripheralClockType::RTC => rcc.enable_rtc_clock(RtcClockSource::LSI), + PeripheralClockType::PWR => rcc.enable_pwr_clock(), + } + } + + fn disable(&self) { + let rcc = self.clocks.get_rcc(); + match self.clock { + PeripheralClockType::AHB1(ref v) => match v { + HCLK1::DMA1 => { + rcc.disable_dma1_clock(); + } + HCLK1::DMA2 => { + rcc.disable_dma2_clock(); + } + HCLK1::GPIOH => { + rcc.disable_gpioh_clock(); + } + HCLK1::GPIOG => { + rcc.disable_gpiog_clock(); + } + HCLK1::GPIOF => { + rcc.disable_gpiof_clock(); + } + HCLK1::GPIOE => { + rcc.disable_gpioe_clock(); + } + HCLK1::GPIOD => { + rcc.disable_gpiod_clock(); + } + HCLK1::GPIOC => { + rcc.disable_gpioc_clock(); + } + HCLK1::GPIOB => { + rcc.disable_gpiob_clock(); + } + HCLK1::GPIOA => { + rcc.disable_gpioa_clock(); + } + }, + PeripheralClockType::AHB2(ref v) => match v { + HCLK2::RNG => { + rcc.disable_rng_clock(); + } + HCLK2::OTGFS => { + rcc.disable_otgfs_clock(); + } + }, + PeripheralClockType::AHB3(ref v) => match v { + HCLK3::FMC => rcc.disable_fmc_clock(), + }, + PeripheralClockType::APB1(ref v) => match v { + PCLK1::TIM2 => { + rcc.disable_tim2_clock(); + } + PCLK1::USART2 => { + rcc.disable_usart2_clock(); + } + PCLK1::USART3 => { + rcc.disable_usart3_clock(); + } + PCLK1::I2C1 => { + rcc.disable_i2c1_clock(); + } + PCLK1::SPI3 => { + rcc.disable_spi3_clock(); + } + PCLK1::CAN1 => { + rcc.disable_can1_clock(); + } + PCLK1::DAC => { + rcc.disable_dac_clock(); + } + }, + PeripheralClockType::APB2(ref v) => match v { + PCLK2::USART1 => { + rcc.disable_usart1_clock(); + } + PCLK2::ADC1 => { + rcc.disable_adc1_clock(); + } + PCLK2::SYSCFG => { + rcc.disable_syscfg_clock(); + } + }, + PeripheralClockType::RTC => rcc.disable_rtc_clock(), + PeripheralClockType::PWR => rcc.disable_pwr_clock(), + } + } +} diff --git a/chips/stm32f4xx/src/clocks/pll.rs b/chips/stm32f4xx/src/clocks/pll.rs new file mode 100644 index 0000000000..741401459a --- /dev/null +++ b/chips/stm32f4xx/src/clocks/pll.rs @@ -0,0 +1,761 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive SRL. +// +// Author: Ioan-Cristian CÎRSTEA + +//! Main phase-locked loop (PLL) clock driver for the STM32F4xx family. [^doc_ref] +//! +//! Many boards of the STM32F4xx family provide several PLL clocks. However, all of them have a +//! main PLL clock. This driver is designed for the main PLL clock. It will be simply referred as +//! the PLL clock. +//! +//! The PLL clock is composed of two outputs: +//! +//! + the main one used for the system clock +//! + the PLL48CLK used for USB OTG FS, the random number generator and SDIO clocks +//! +//! # Implemented features +//! +//! - [x] Default configuration of 96MHz with reduced PLL jitter +//! - [x] 1MHz frequency precision +//! - [x] Support for 13-216MHz frequency range +//! - [x] Support for PLL48CLK output +//! +//! # Missing features +//! +//! - [ ] Precision higher than 1MHz +//! - [ ] Source selection +//! - [ ] Precise control over the PLL48CLK frequency +//! +//! # Usage +//! +//! For the purposes of brevity, any error checking has been removed. In real applications, always +//! check the return values of the [Pll] methods. +//! +//! First, get a reference to the [Pll] struct: +//! ```rust,ignore +//! let pll = &peripherals.stm32f4.clocks.pll; +//! ``` +//! +//! ## Start the clock with a given frequency +//! +//! ```rust,ignore +//! pll.set_frequency_mhz(PllSource::HSI, HSI_FREQUENCY_MHZ, 100); // 100MHz +//! pll.enable(); +//! ``` +//! +//! ## Stop the clock +//! +//! ```rust,ignore +//! pll.disable(); +//! ``` +//! +//! ## Check whether the PLL clock is running or not +//! ```rust,ignore +//! if pll.is_enabled() { +//! // do something... +//! } else { +//! // do something... +//! } +//! ``` +//! +//! ## Check the clock frequency +//! +//! ```rust,ignore +//! let optional_pll_frequency = pll.get_frequency_mhz(); +//! if let None = optional_pll_frequency { +//! /* Clock stopped */ +//! } +//! let pll_frequency = optional_pll_frequency.unwrap(); +//! /* Computations based on the PLL frequency */ +//! ``` +//! +//! ## Reconfigure the clock once started +//! +//! ```rust,ignore +//! pll.disable(); // The PLL clock can't be configured while running +//! pll.set_frequency_mhz(PllSource::HSI, HSI_FREQUENCY_MHZ, 50); // 50MHz +//! pll.enable(); +//! ``` +//! +//! ## Configure the PLL clock so that PLL48CLK output is correctly calibrated +//! ```rust,ignore +//! // The frequency of the PLL clock must be 1, 1.5, 2, 2.5, 3, 3.5 or 4 x 48MHz in order to get +//! // 48MHz output. Otherwise, the driver will attempt to get the closest frequency lower than 48MHz +//! pll.set_frequency_mhz(PllSource::HSI, HSI_FREQUENCY_MHZ, 72); // 72MHz = 48MHz * 1.5 +//! pll.enable(); +//! ``` +//! +//! ## Check if the PLL48CLK output is calibrated. +//! ```rust,ignore +//! if !pll.is_pll48_calibrated() { +//! /* Handle the case when it is not calibrated */ +//! } +//! ``` +//! +//! ## Get the frequency of the PLL48CLK output +//! +//! ```rust,ignore +//! let optional_pll48_frequency = pll.get_frequency_mhz(); +//! if let None = optional_pll48_frequency { +//! /* Clock stopped */ +//! } +//! let pll48_frequency = optional_pll48_frequency.unwrap(); +//! ``` +//! +//! [^doc_ref]: See 6.2.3 in the documentation. + +use crate::chip_specific::clock_constants; +use crate::clocks::hsi::HSI_FREQUENCY_MHZ; +use crate::rcc::Rcc; +use crate::rcc::SysClockSource; +use crate::rcc::{PllSource, PLLM, PLLP, PLLQ}; +use crate::rcc::{DEFAULT_PLLM_VALUE, DEFAULT_PLLN_VALUE, DEFAULT_PLLP_VALUE, DEFAULT_PLLQ_VALUE}; + +use kernel::debug; +use kernel::utilities::cells::OptionalCell; +use kernel::ErrorCode; + +use core::cell::Cell; +use core::marker::PhantomData; + +/// Main PLL clock structure. +pub struct Pll<'a, PllConstants> { + rcc: &'a Rcc, + frequency_mhz: OptionalCell, + pll48_frequency_mhz: OptionalCell, + pll48_calibrated: Cell, + _marker: PhantomData, +} + +impl<'a, PllConstants: clock_constants::PllConstants> Pll<'a, PllConstants> { + // Create a new instance of the PLL clock. + // + // The instance of the PLL clock is configured to run at 96MHz and with minimal PLL jitter + // effects. + // + // # Parameters + // + // + rcc: an instance of [crate::rcc] + // + // # Returns + // + // An instance of the PLL clock. + pub(in crate::clocks) fn new(rcc: &'a Rcc) -> Self { + const PLLP: usize = match DEFAULT_PLLP_VALUE { + PLLP::DivideBy2 => 2, + PLLP::DivideBy4 => 4, + PLLP::DivideBy6 => 6, + PLLP::DivideBy8 => 8, + }; + const PLLM: usize = DEFAULT_PLLM_VALUE as usize; + const PLLQ: usize = DEFAULT_PLLQ_VALUE as usize; + Self { + rcc, + frequency_mhz: OptionalCell::new(HSI_FREQUENCY_MHZ / PLLM * DEFAULT_PLLN_VALUE / PLLP), + pll48_frequency_mhz: OptionalCell::new( + HSI_FREQUENCY_MHZ / PLLM * DEFAULT_PLLN_VALUE / PLLQ, + ), + pll48_calibrated: Cell::new(true), + _marker: PhantomData, + } + } + + // The caller must ensure the desired frequency lies between MIN_FREQ_MHZ and + // MAX_FREQ_MHZ. Otherwise, the return value makes no sense. + fn compute_pllp(desired_frequency_mhz: usize) -> PLLP { + if desired_frequency_mhz < 55 { + PLLP::DivideBy8 + } else if desired_frequency_mhz < 73 { + PLLP::DivideBy6 + } else if desired_frequency_mhz < 109 { + PLLP::DivideBy4 + } else { + PLLP::DivideBy2 + } + } + + // The caller must ensure the desired frequency lies between MIN_FREQ_MHZ and + // MAX_FREQ_MHZ. Otherwise, the return value makes no sense. + fn compute_plln( + desired_frequency_mhz: usize, + pll_source_clock_freq: usize, + pllp: PLLP, + ) -> usize { + let vco_input_frequency: usize = pll_source_clock_freq / DEFAULT_PLLM_VALUE as usize; + desired_frequency_mhz * Into::::into(pllp) / vco_input_frequency + } + + // The caller must ensure the VCO output frequency lies between 100 and 432MHz. Otherwise, the + // return value makes no sense. + fn compute_pllq(vco_output_frequency_mhz: usize) -> PLLQ { + for pllq in 3..10 { + if 48 * pllq >= vco_output_frequency_mhz { + return match pllq { + 3 => PLLQ::DivideBy3, + 4 => PLLQ::DivideBy4, + 5 => PLLQ::DivideBy5, + 6 => PLLQ::DivideBy6, + 7 => PLLQ::DivideBy7, + 8 => PLLQ::DivideBy8, + _ => PLLQ::DivideBy9, + }; + } + } + unreachable!("The previous for loop should always return"); + } + + /// Set the PLL source clock + fn set_pll_source_clock(&self, source: PllSource) -> Result<(), ErrorCode> { + if self.is_enabled() { + Err(ErrorCode::FAIL) + } else { + self.rcc.set_pll_clocks_source(source); + Ok(()) + } + } + + /// Start the PLL clock. + /// + /// # Errors + /// + /// + [Err]\([ErrorCode::BUSY]\): if enabling the PLL clock took too long. Recall this method to + /// ensure the PLL clock is running. + pub fn enable(&self) -> Result<(), ErrorCode> { + // Enable the PLL clock + self.rcc.enable_pll_clock(); + + // Wait until the PLL clock is locked. + // 200 was obtained by running tests in release mode + for _ in 0..200 { + if self.rcc.is_locked_pll_clock() { + return Ok(()); + } + } + + // If waiting for the PLL clock took too long, return ErrorCode::BUSY + Err(ErrorCode::BUSY) + } + + /// Stop the PLL clock. + /// + /// # Errors + /// + /// + [Err]\([ErrorCode::FAIL]\): if the PLL clock is configured as the system clock. + /// + [Err]\([ErrorCode::BUSY]\): disabling the PLL clock took to long. Retry to ensure it is + /// not running. + pub fn disable(&self) -> Result<(), ErrorCode> { + // Can't disable the PLL clock when it is used as the system clock + if self.rcc.get_sys_clock_source() == SysClockSource::PLL { + return Err(ErrorCode::FAIL); + } + + // Disable the PLL clock + self.rcc.disable_pll_clock(); + + // Wait to unlock the PLL clock + // 10 was obtained by testing in release mode + for _ in 0..10 { + if !self.rcc.is_locked_pll_clock() { + return Ok(()); + } + } + + // If the waiting was too long, return ErrorCode::BUSY + Err(ErrorCode::BUSY) + } + + /// Check whether the PLL clock is enabled or not. + /// + /// # Returns + /// + /// + [false]: the PLL clock is not enabled + /// + [true]: the PLL clock is enabled + pub fn is_enabled(&self) -> bool { + self.rcc.is_enabled_pll_clock() + } + + /// Set the frequency of the PLL clock. + /// + /// The PLL clock has two outputs: + /// + /// + main output used for configuring the system clock + /// + a second output called PLL48CLK used by OTG USB FS (48MHz), the random number generator + /// (≤ 48MHz) and the SDIO (≤ 48MHz) clocks. + /// + /// When calling this method, the given frequency is set for the main output. The method will + /// attempt to configure the PLL48CLK output to 48MHz, or to the highest value less than 48MHz + /// if it is not possible to get a precise 48MHz. In order to obtain a precise 48MHz frequency + /// (for the OTG USB FS peripheral), one should call this method with a frequency of 1, 1.5, 2, + /// 2.5 ... 4 x 48MHz. + /// + /// # Parameters + /// + /// + pll_source: PLL source clock (HSI or HSE) + /// + /// + source_frequency: the frequency of the PLL source clock in MHz. For the HSI the frequency + /// is fixed to 16MHz. For the HSE, the frequency is hardware-dependent + /// + /// + desired_frequency_mhz: the desired frequency in MHz. Supported values: 24-216MHz for + /// STM32F401 and 13-216MHz for all the other chips + /// + /// # Errors + /// + /// + [Err]\([ErrorCode::INVAL]\): if the desired frequency can't be achieved + /// + [Err]\([ErrorCode::FAIL]\): if the PLL clock is already enabled. It must be disabled before + /// configuring it. + pub(super) fn set_frequency_mhz( + &self, + pll_source: PllSource, + source_frequency: usize, + desired_frequency_mhz: usize, + ) -> Result<(), ErrorCode> { + // Check for errors: + // + PLL clock running + // + invalid frequency + if self.rcc.is_enabled_pll_clock() { + return Err(ErrorCode::FAIL); + } else if desired_frequency_mhz < PllConstants::MIN_FREQ_MHZ + || desired_frequency_mhz > PllConstants::MAX_FREQ_MHZ + { + return Err(ErrorCode::INVAL); + } + + // The output frequencies for the PLL clock is computed as following: + // Source frequency / PLLM = VCO input frequency (must range from 1MHz to 2MHz) + // VCO output frequency = VCO input frequency * PLLN (must range from 100MHz to 432MHz) + // PLL output frequency = VCO output frequency / PLLP + // PLL48CLK = VCO output frequency / PLLQ + + // Set PLL source (HSI or HSE) + if self.set_pll_source_clock(pll_source) != Ok(()) { + return Err(ErrorCode::FAIL); + } + + // Compute PLLP + let pllp = Self::compute_pllp(desired_frequency_mhz); + self.rcc.set_pll_clock_p_divider(pllp); + + // Compute PLLN + let plln = Self::compute_plln(desired_frequency_mhz, source_frequency, pllp); + self.rcc.set_pll_clock_n_multiplier(plln); + + // Compute PLLQ + let vco_output_frequency = source_frequency / DEFAULT_PLLM_VALUE as usize * plln; + let pllq = Self::compute_pllq(vco_output_frequency); + self.rcc.set_pll_clock_q_divider(pllq); + + // Check if PLL48CLK is calibrated, e.g. its frequency is exactly 48MHz + let pll48_frequency = vco_output_frequency / pllq as usize; + self.pll48_calibrated + .set(pll48_frequency == 48 && vco_output_frequency % pllq as usize == 0); + + // Cache the frequency so it is not computed every time a get method is called + self.frequency_mhz.set(desired_frequency_mhz); + self.pll48_frequency_mhz.set(pll48_frequency); + + Ok(()) + } + + /// Get the frequency in MHz of the PLL clock. + /// + /// # Returns + /// + /// + [Some]\(frequency_mhz\): if the PLL clock is enabled. + /// + [None]: if the PLL clock is disabled. + pub fn get_frequency_mhz(&self) -> Option { + if self.is_enabled() { + self.frequency_mhz.get() + } else { + None + } + } + + /// Get the frequency in MHz of the PLL clock from RCC registers instead of using the cached + /// value. + /// + /// # Returns + /// + /// + [Some]\(frequency_mhz\): if the PLL clock is enabled. + /// + [None]: if the PLL clock is disabled. + pub fn get_frequency_mhz_no_cache(&self, source_frequency: usize) -> Option { + if self.is_enabled() { + let pllm = self.rcc.get_pll_clocks_m_divider() as usize; + let plln = self.rcc.get_pll_clock_n_multiplier(); + let pllp: usize = self.rcc.get_pll_clock_p_divider().into(); + Some(source_frequency / pllm * plln / pllp) + } else { + None + } + } + + /// Get the frequency in MHz of the PLL48 clock. + /// + /// **NOTE:** If the PLL clock was not configured with a frequency multiple of 48MHz, the + /// returned value is inaccurate. + /// + /// # Returns + /// + /// + [Some]\(frequency_mhz\): if the PLL clock is enabled. + /// + [None]: if the PLL clock is disabled. + pub fn get_frequency_mhz_pll48(&self) -> Option { + if self.is_enabled() { + self.pll48_frequency_mhz.get() + } else { + None + } + } + + /// Check if the PLL48 clock is calibrated (its output is exactly 48MHz). + /// + /// A frequency of 48MHz is required for USB OTG FS. + /// + /// # Returns + /// + /// + [true]: the PLL48 clock frequency is exactly 48MHz. + /// + [false]: the PLL48 clock is not exactly 48MHz. + pub fn is_pll48_calibrated(&self) -> bool { + self.pll48_calibrated.get() + } +} + +/// Tests for the PLL clock +/// +/// This module ensures that the PLL clock works as expected. If changes are brought to the PLL +/// clock, ensure to run all the tests to see if anything is broken. +/// +/// # Usage +/// +/// First, import the [crate::clocks::pll] module inside the board main file: +/// +/// ```rust,ignore +/// use stm32f429zi::pll; +/// ``` +/// To run all the available tests, add this line before **kernel::process::load_processes()**: +/// +/// ```rust,ignore +/// pll::tests::run(&peripherals.stm32f4.clocks.pll); +/// ``` +/// +/// If everything works as expected, the following message should be printed on the kernel console: +/// +/// ```text +/// =============================================== +/// Testing PLL... +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// Testing PLL configuration... +/// Finished testing PLL configuration. +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// Testing PLL struct... +/// Finished testing PLL struct. +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// Finished testing PLL. Everything is alright! +/// =============================================== +/// ``` +/// +/// There is also the possibility to run a part of the test suite. Check the functions present in +/// this module for more details. +/// +/// # Errors +/// +/// If there are any errors, open an issue ticket at . Please provide the +/// output of the test execution. +pub mod tests { + use super::*; + + // Depending on the default PLLM value, the computed PLLN value changes. + const MULTIPLIER: usize = match DEFAULT_PLLM_VALUE { + PLLM::DivideBy8 => 1, + PLLM::DivideBy16 => 2, + }; + + /// Test if the configuration parameters are correctly computed for a given frequency. + /// + /// # Usage + /// + /// ```rust,ignore + /// use stm32f429zi::pll; // Import the pll module + /// /* Code goes here */ + /// pll::test::test_pll_config(&peripherals.stm32f4.pll); // Run the tests + /// ``` + pub fn test_pll_config() { + debug!("Testing PLL configuration..."); + + // 13 or 24MHz --> minimum value + let mut pllp = Pll::::compute_pllp(PllConstants::MIN_FREQ_MHZ); + assert_eq!(PLLP::DivideBy8, pllp); + let mut plln = + Pll::::compute_plln(PllConstants::MIN_FREQ_MHZ, HSI_FREQUENCY_MHZ, pllp); + + #[cfg(not(feature = "stm32f401"))] + assert_eq!(52 * MULTIPLIER, plln); + #[cfg(feature = "stm32f401")] + assert_eq!(96 * MULTIPLIER, plln); + + let mut vco_output_frequency_mhz = HSI_FREQUENCY_MHZ / DEFAULT_PLLM_VALUE as usize * plln; + let mut pllq = Pll::::compute_pllq(vco_output_frequency_mhz); + + #[cfg(not(feature = "stm32f401"))] + assert_eq!(PLLQ::DivideBy3, pllq); + #[cfg(feature = "stm32f401")] + assert_eq!(PLLQ::DivideBy4, pllq); + + // 25MHz --> minimum required value for Ethernet devices + pllp = Pll::::compute_pllp(25); + assert_eq!(PLLP::DivideBy8, pllp); + plln = Pll::::compute_plln(25, HSI_FREQUENCY_MHZ, pllp); + assert_eq!(100 * MULTIPLIER, plln); + vco_output_frequency_mhz = HSI_FREQUENCY_MHZ / DEFAULT_PLLM_VALUE as usize * plln; + pllq = Pll::::compute_pllq(vco_output_frequency_mhz); + assert_eq!(PLLQ::DivideBy5, pllq); + + // 54MHz --> last frequency before PLLP becomes DivideBy6 + pllp = Pll::::compute_pllp(54); + assert_eq!(PLLP::DivideBy8, pllp); + plln = Pll::::compute_plln(54, HSI_FREQUENCY_MHZ, pllp); + assert_eq!(216 * MULTIPLIER, plln); + vco_output_frequency_mhz = HSI_FREQUENCY_MHZ / DEFAULT_PLLM_VALUE as usize * plln; + pllq = Pll::::compute_pllq(vco_output_frequency_mhz); + assert_eq!(PLLQ::DivideBy9, pllq); + + // 55MHz --> PLLP becomes DivideBy6 + pllp = Pll::::compute_pllp(55); + assert_eq!(PLLP::DivideBy6, pllp); + plln = Pll::::compute_plln(55, HSI_FREQUENCY_MHZ, pllp); + assert_eq!(165 * MULTIPLIER, plln); + vco_output_frequency_mhz = HSI_FREQUENCY_MHZ / DEFAULT_PLLM_VALUE as usize * plln; + pllq = Pll::::compute_pllq(vco_output_frequency_mhz); + assert_eq!(PLLQ::DivideBy7, pllq); + + // 70MHz --> Another value for PLLP::DivideBy6 + pllp = Pll::::compute_pllp(70); + assert_eq!(PLLP::DivideBy6, pllp); + plln = Pll::::compute_plln(70, HSI_FREQUENCY_MHZ, pllp); + assert_eq!(210 * MULTIPLIER, plln); + vco_output_frequency_mhz = HSI_FREQUENCY_MHZ / DEFAULT_PLLM_VALUE as usize * plln; + pllq = Pll::::compute_pllq(vco_output_frequency_mhz); + assert_eq!(PLLQ::DivideBy9, pllq); + + // 72MHz --> last frequency before PLLP becomes DivideBy4 + pllp = Pll::::compute_pllp(72); + assert_eq!(PLLP::DivideBy6, pllp); + plln = Pll::::compute_plln(72, HSI_FREQUENCY_MHZ, pllp); + assert_eq!(216 * MULTIPLIER, plln); + vco_output_frequency_mhz = HSI_FREQUENCY_MHZ / DEFAULT_PLLM_VALUE as usize * plln; + pllq = Pll::::compute_pllq(vco_output_frequency_mhz); + assert_eq!(PLLQ::DivideBy9, pllq); + + // 73MHz --> PLLP becomes DivideBy4 + pllp = Pll::::compute_pllp(73); + assert_eq!(PLLP::DivideBy4, pllp); + plln = Pll::::compute_plln(73, HSI_FREQUENCY_MHZ, pllp); + assert_eq!(146 * MULTIPLIER, plln); + vco_output_frequency_mhz = HSI_FREQUENCY_MHZ / DEFAULT_PLLM_VALUE as usize * plln; + pllq = Pll::::compute_pllq(vco_output_frequency_mhz); + assert_eq!(PLLQ::DivideBy7, pllq); + + // 100MHz --> Another value for PLLP::DivideBy4 + pllp = Pll::::compute_pllp(100); + assert_eq!(PLLP::DivideBy4, pllp); + plln = Pll::::compute_plln(100, HSI_FREQUENCY_MHZ, pllp); + assert_eq!(200 * MULTIPLIER, plln); + vco_output_frequency_mhz = HSI_FREQUENCY_MHZ / DEFAULT_PLLM_VALUE as usize * plln; + pllq = Pll::::compute_pllq(vco_output_frequency_mhz); + assert_eq!(PLLQ::DivideBy9, pllq); + + // 108MHz --> last frequency before PLLP becomes DivideBy2 + pllp = Pll::::compute_pllp(108); + assert_eq!(PLLP::DivideBy4, pllp); + plln = Pll::::compute_plln(108, HSI_FREQUENCY_MHZ, pllp); + assert_eq!(216 * MULTIPLIER, plln); + vco_output_frequency_mhz = HSI_FREQUENCY_MHZ / DEFAULT_PLLM_VALUE as usize * plln; + pllq = Pll::::compute_pllq(vco_output_frequency_mhz); + assert_eq!(PLLQ::DivideBy9, pllq); + + // 109MHz --> PLLP becomes DivideBy2 + pllp = Pll::::compute_pllp(109); + assert_eq!(PLLP::DivideBy2, pllp); + plln = Pll::::compute_plln(109, HSI_FREQUENCY_MHZ, pllp); + assert_eq!(109 * MULTIPLIER, plln); + vco_output_frequency_mhz = HSI_FREQUENCY_MHZ / DEFAULT_PLLM_VALUE as usize * plln; + pllq = Pll::::compute_pllq(vco_output_frequency_mhz); + assert_eq!(PLLQ::DivideBy5, pllq); + + // 125MHz --> Another value for PLLP::DivideBy2 + pllp = Pll::::compute_pllp(125); + assert_eq!(PLLP::DivideBy2, pllp); + plln = Pll::::compute_plln(125, HSI_FREQUENCY_MHZ, pllp); + assert_eq!(125 * MULTIPLIER, plln); + vco_output_frequency_mhz = HSI_FREQUENCY_MHZ / DEFAULT_PLLM_VALUE as usize * plln; + pllq = Pll::::compute_pllq(vco_output_frequency_mhz); + assert_eq!(PLLQ::DivideBy6, pllq); + + // 180MHz --> Max frequency for the CPU + pllp = Pll::::compute_pllp(180); + assert_eq!(PLLP::DivideBy2, pllp); + plln = Pll::::compute_plln(180, HSI_FREQUENCY_MHZ, pllp); + assert_eq!(180 * MULTIPLIER, plln); + vco_output_frequency_mhz = HSI_FREQUENCY_MHZ / DEFAULT_PLLM_VALUE as usize * plln; + pllq = Pll::::compute_pllq(vco_output_frequency_mhz); + assert_eq!(PLLQ::DivideBy8, pllq); + + // 216MHz --> Max frequency for the PLL due to the VCO output frequency limit + pllp = Pll::::compute_pllp(216); + assert_eq!(PLLP::DivideBy2, pllp); + plln = Pll::::compute_plln(216, HSI_FREQUENCY_MHZ, pllp); + assert_eq!(216 * MULTIPLIER, plln); + vco_output_frequency_mhz = HSI_FREQUENCY_MHZ / DEFAULT_PLLM_VALUE as usize * plln; + pllq = Pll::::compute_pllq(vco_output_frequency_mhz); + assert_eq!(PLLQ::DivideBy9, pllq); + + debug!("Finished testing PLL configuration."); + } + + /// Check if the PLL works as expected. + /// + /// **NOTE:** it is highly recommended to call [test_pll_config] + /// first to check whether the configuration parameters are correctly computed. + /// + /// # Usage + /// + /// ```rust,ignore + /// use stm32f429zi::pll; // Import the PLL module + /// /* Code goes here */ + /// pll::test::test_pll_struct(&peripherals.stm32f4.pll); // Run the tests + /// ``` + pub fn test_pll_struct<'a, PllConstants: clock_constants::PllConstants>( + pll: &'a Pll<'a, PllConstants>, + ) { + debug!("Testing PLL struct..."); + // Make sure the PLL clock is disabled + assert_eq!(Ok(()), pll.disable()); + assert!(!pll.is_enabled()); + + // Attempting to configure the PLL with either too high or too low frequency + assert_eq!( + Err(ErrorCode::INVAL), + pll.set_frequency_mhz(PllSource::HSI, HSI_FREQUENCY_MHZ, 12) + ); + assert_eq!( + Err(ErrorCode::INVAL), + pll.set_frequency_mhz(PllSource::HSI, HSI_FREQUENCY_MHZ, 217) + ); + + // Start the PLL with the default configuration. + assert_eq!(Ok(()), pll.enable()); + + // Make sure the PLL is enabled. + assert!(pll.is_enabled()); + + // By default, the PLL clock is set to 96MHz + assert_eq!(Some(96), pll.get_frequency_mhz()); + + // By default, the PLL48 clock is correctly calibrated + assert!(pll.is_pll48_calibrated()); + + // Impossible to configure the PLL clock once it is enabled. + assert_eq!( + Err(ErrorCode::FAIL), + pll.set_frequency_mhz(PllSource::HSI, HSI_FREQUENCY_MHZ, 50) + ); + + // Stop the PLL in order to reconfigure it. + assert_eq!(Ok(()), pll.disable()); + + // Configure the PLL clock to run at 25MHz + assert_eq!( + Ok(()), + pll.set_frequency_mhz(PllSource::HSI, HSI_FREQUENCY_MHZ, 25) + ); + + // Start the PLL with the new configuration + assert_eq!(Ok(()), pll.enable()); + + // get_frequency() method should reflect the new change + assert_eq!(Some(25), pll.get_frequency_mhz()); + + // Since 25 is not a multiple of 48, the PLL48 clock is not correctly calibrated + assert!(!pll.is_pll48_calibrated()); + + // The expected PLL48 clock value in this case should be approximately 40 MHz. + // It is actually exactly 40MHz in this particular case. + assert_eq!(Some(40), pll.get_frequency_mhz_pll48()); + + // Stop the PLL clock + assert_eq!(Ok(()), pll.disable()); + + // Attempting to get the frequency of the PLL clock when it is disabled should return None. + assert_eq!(None, pll.get_frequency_mhz()); + // Same for PLL48 clock + assert_eq!(None, pll.get_frequency_mhz_pll48()); + + // Attempting to configure the PLL clock with a frequency multiple of 48MHz + assert_eq!( + Ok(()), + pll.set_frequency_mhz(PllSource::HSI, HSI_FREQUENCY_MHZ, 144) + ); + assert_eq!(Ok(()), pll.enable()); + assert_eq!(Some(144), pll.get_frequency_mhz()); + + // PLL48 clock output should be correctly calibrated + assert!(pll.is_pll48_calibrated()); + assert_eq!(Some(48), pll.get_frequency_mhz_pll48()); + + // Reconfigure the clock for 100MHz + assert_eq!(Ok(()), pll.disable()); + assert_eq!( + Ok(()), + pll.set_frequency_mhz(PllSource::HSI, HSI_FREQUENCY_MHZ, 100) + ); + assert_eq!(Ok(()), pll.enable()); + assert_eq!(Some(100), pll.get_frequency_mhz()); + + // In this case, the PLL48 clock is not correctly calibrated. Its frequency is + // approximately 44MHz. + assert!(!pll.is_pll48_calibrated()); + assert_eq!(Some(44), pll.get_frequency_mhz_pll48()); + + // Configure the clock to 72MHz = 48MHz * 1.5 + assert_eq!(Ok(()), pll.disable()); + assert_eq!( + Ok(()), + pll.set_frequency_mhz(PllSource::HSI, HSI_FREQUENCY_MHZ, 72) + ); + assert_eq!(Ok(()), pll.enable()); + assert_eq!(Some(72), pll.get_frequency_mhz()); + + // In this case, the PLL48 clock is correctly calibrated + assert!(pll.is_pll48_calibrated()); + assert_eq!(Some(48), pll.get_frequency_mhz_pll48()); + + // Turn off the PLL clock + assert_eq!(Ok(()), pll.disable()); + assert!(!pll.is_enabled()); + + debug!("Finished testing PLL struct."); + } + + /// Run the entire test suite. + /// + /// # Usage + /// + /// ```rust,ignore + /// use stm32f429zi::pll; // Import the PLL module + /// /* Code goes here */ + /// pll::test::run(&peripherals.stm32f4.pll); // Run the tests + /// ``` + pub fn run<'a, PllConstants: clock_constants::PllConstants>(pll: &'a Pll<'a, PllConstants>) { + debug!(""); + debug!("==============================================="); + debug!("Testing PLL..."); + debug!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + test_pll_config::(); + debug!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + test_pll_struct(pll); + debug!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + debug!("Finished testing PLL. Everything is alright!"); + debug!("==============================================="); + debug!(""); + } +} diff --git a/chips/stm32f4xx/src/dac.rs b/chips/stm32f4xx/src/dac.rs new file mode 100644 index 0000000000..ea649fc2bd --- /dev/null +++ b/chips/stm32f4xx/src/dac.rs @@ -0,0 +1,226 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::clocks::{phclk, Stm32f4Clocks}; +use core::cell::Cell; +use kernel::hil; +use kernel::platform::chip::ClockInterface; +use kernel::utilities::registers::interfaces::{ReadWriteable, Writeable}; +use kernel::utilities::registers::{register_bitfields, ReadWrite, WriteOnly}; +use kernel::utilities::StaticRef; +use kernel::ErrorCode; + +/// DAC +#[repr(C)] +pub struct DacRegisters { + cr: ReadWrite, + swtrigr: WriteOnly, + dhr12r1: ReadWrite, + dhr8r1: ReadWrite, + dhr12r2: ReadWrite, + dhr12l2: ReadWrite, + dhr8r2: ReadWrite, + dhr12rd: ReadWrite, + dhr12ld: ReadWrite, + dhr8rd: ReadWrite, + dor1: ReadWrite, + dor2: ReadWrite, +} + +register_bitfields![u32, + /// Control register + CR [ + /// DAC channel 2 DMA underrun interrupt enable + DMAUDRIE2 OFFSET(29) NUMBITS(1) [], + /// DAC channel 2 DMA enable + DMAEN2 OFFSET(28) NUMBITS(1) [], + /// DAC channel2 mask/amplitude selector + MAMP2 OFFSET(24) NUMBITS(4) [], + /// DAC channel2 noise/triangle wave generation enable + WAVE2 OFFSET(22) NUMBITS(2) [], + /// DAC channel2 trigger selection + TSEL2 OFFSET(19) NUMBITS(3) [], + /// DAC channel2 trigger enable + TEN2 OFFSET(18) NUMBITS(1) [], + /// DAC channel2 output buffer disable + BOFF2 OFFSET(17) NUMBITS(1) [], + /// DAC channel2 enable + EN2 OFFSET(16) NUMBITS(1) [], + /// DAC channel 1 DMA underrun interrupt enable + DMAUDRIE1 OFFSET(13) NUMBITS(1) [], + /// DAC channel 1 DMA enable + DMAEN1 OFFSET(12) NUMBITS(1) [], + /// DAC channel1 mask/amplitude selector + MAMP1 OFFSET(8) NUMBITS(4) [], + /// DAC channel1 noise/triangle wave generation enable + WAVE1 OFFSET(6) NUMBITS(2) [], + /// DAC channel2 trigger selection + TSEL1 OFFSET(3) NUMBITS(3) [], + /// DAC channel2 trigger enable + TEN1 OFFSET(2) NUMBITS(1) [], + /// DAC channel2 output buffer disable + BOFF1 OFFSET(1) NUMBITS(1) [], + /// DAC channel1 enable + EN1 OFFSET(0) NUMBITS(1) [], + ], + /// Software trigger register + SWTRIGR [ + /// DAC channel2 software trigger + SWTRIG2 OFFSET(1) NUMBITS(1) [], + /// DAC channel1 software trigger + SWTRIG1 OFFSET(0) NUMBITS(1) [] + ], + /// Channel1 12-bit right-aligned data holding register + DHR12R1 [ + /// DAC channel1 12-bit right-aligned data + DACC1DHR OFFSET(0) NUMBITS(12) [] + ], + /// Channel1 8-bit right aligned data holding register + DHR8R1 [ + /// DAC Channel1 8-bit right-aligned data + DACC1DHR OFFSET(0) NUMBITS(8) [] + ], + /// Channel2 12-bit right aligned data holding register + DHR12R2 [ + /// DAC channel2 12-bit right aligned data + DACC2DHR OFFSET(0) NUMBITS(12) [] + ], + /// Channel2 12-bit left aligned data holding register + DHR12L2 [ + /// DAC channel2 12-bit left-aligned data + DACC2DHR OFFSET(0) NUMBITS(12) [] + ], + /// Channel2 8-bit right-aligned data holding register + DHR8R2 [ + /// DAC channel2 8-bit right-aligned data + DACC2DHR OFFSET(0) NUMBITS(8) [] + ], + /// Dual DAC 12-bit right-aligned data holding register + DHR12RD [ + /// DAC channel2 12-bit right-aligned data + DACC2DHR OFFSET(16) NUMBITS(12) [], + /// DAC channel1 12-bit right-aligned data + DACC1DHR OFFSET(0) NUMBITS(12) [] + ], + /// Dual DAC 12-bit left aligned data holding register + DHR12LD [ + /// DAC channel2 12-bit left-aligned data + DACC2DHR OFFSET(16) NUMBITS(12) [], + /// DAC channel1 12-bit left-aligned data + DACC1DHR OFFSET(0) NUMBITS(12) [] + ], + /// Dual DAC 8-bit right aligned data holding register + DHR8RD [ + /// DAC channel2 8-bit right-aligned data + DACC2DHR OFFSET(8) NUMBITS(8) [], + /// DAC channel1 8-bit right-aligned data + DACC1DHR OFFSET(0) NUMBITS(8) [] + ], + /// DAC Channel 1 data output register + DOR1 [ + /// DAC channel1 data output + DACC1DOR OFFSET(0) NUMBITS(12) [] + ], + /// DAC Channel 2 data output register + DOR2 [ + /// DAC channel2 data output + DACC2DOR OFFSET(0) NUMBITS(12) [] + ], + /// DAC status register + SR [ + /// DAC channel2 DMA underrun flag + DMAUDR2 OFFSET(29) NUMBITS(1) [], + /// DAC channel1 DMA underrun flag + DMAUDR1 OFFSET(13) NUMBITS(1) [] + ] +]; + +const DAC_BASE: StaticRef = + unsafe { StaticRef::new(0x40007400 as *const DacRegisters) }; + +pub struct Dac<'a> { + registers: StaticRef, + clock: DacClock<'a>, + initialized: Cell, + enabled: Cell, +} + +impl<'a> Dac<'a> { + pub const fn new(clocks: &'a dyn Stm32f4Clocks) -> Self { + Self { + registers: DAC_BASE, + clock: DacClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::APB1(phclk::PCLK1::DAC), + clocks, + )), + initialized: Cell::new(false), + enabled: Cell::new(false), + } + } + + fn initialize(&self) -> Result<(), ErrorCode> { + if !self.is_enabled_clock() { + self.enable_clock(); + } + + // Clear BOFF1, TEN1, TSEL1, WAVE1 and MAMP1 bits + self.registers.cr.modify(CR::BOFF1::CLEAR); + self.registers.cr.modify(CR::TEN1::CLEAR); + self.registers.cr.modify(CR::TSEL1::CLEAR); + self.registers.cr.modify(CR::WAVE1::CLEAR); + self.registers.cr.modify(CR::MAMP1::CLEAR); + + self.enable(); + + Ok(()) + } + + fn enable(&self) { + self.registers.cr.modify(CR::EN1::SET); + } + + // Not currently using interrupt. + pub fn handle_interrupt(&self) {} + + fn is_enabled_clock(&self) -> bool { + self.clock.is_enabled() + } + + fn enable_clock(&self) { + self.clock.enable(); + } +} + +struct DacClock<'a>(phclk::PeripheralClock<'a>); + +impl ClockInterface for DacClock<'_> { + fn is_enabled(&self) -> bool { + self.0.is_enabled() + } + + fn enable(&self) { + self.0.enable(); + } + + fn disable(&self) { + self.0.disable(); + } +} + +impl hil::dac::DacChannel for Dac<'_> { + fn set_value(&self, value: usize) -> Result<(), ErrorCode> { + if !self.initialized.get() { + self.initialize()?; + } + + if !self.enabled.get() { + self.enable(); + } + + self.registers + .dhr12r1 + .write(DHR12R1::DACC1DHR.val(value as u32)); + Ok(()) + } +} diff --git a/chips/stm32f4xx/src/dbg.rs b/chips/stm32f4xx/src/dbg.rs index 6bf63611ea..d2ccf9fac4 100644 --- a/chips/stm32f4xx/src/dbg.rs +++ b/chips/stm32f4xx/src/dbg.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::utilities::registers::interfaces::ReadWriteable; use kernel::utilities::registers::{register_bitfields, ReadOnly, ReadWrite}; use kernel::utilities::StaticRef; diff --git a/chips/stm32f4xx/src/dma1.rs b/chips/stm32f4xx/src/dma.rs similarity index 62% rename from chips/stm32f4xx/src/dma1.rs rename to chips/stm32f4xx/src/dma.rs index 7fb32000c3..5a14d16648 100644 --- a/chips/stm32f4xx/src/dma1.rs +++ b/chips/stm32f4xx/src/dma.rs @@ -1,17 +1,23 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use core::fmt::Debug; + use kernel::platform::chip::ClockInterface; use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; use kernel::utilities::registers::{register_bitfields, ReadOnly, ReadWrite}; use kernel::utilities::StaticRef; +use crate::clocks::{phclk, Stm32f4Clocks}; use crate::nvic; -use crate::rcc; use crate::spi; use crate::usart; /// DMA controller #[repr(C)] -struct Dma1Registers { +pub struct DmaRegisters { /// low interrupt status register lisr: ReadOnly, /// high interrupt status register @@ -703,14 +709,11 @@ register_bitfields![u32, ] ]; -const DMA1_BASE: StaticRef = - unsafe { StaticRef::new(0x40026000 as *const Dma1Registers) }; - /// The DMA stream number. What other microcontrollers refer to as "channel", /// STM32F446RE refers to as "streams". STM32F446RE has eight streams. A stream /// transfers data between memory and peripheral. #[repr(u8)] -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum StreamId { Stream0 = 0, Stream1 = 1, @@ -726,9 +729,8 @@ pub enum StreamId { /// basically STM32F446RE's way of selecting the peripheral for the stream. /// Nevertheless, the use of the term channel here is confusing. Table 28 /// describes the mapping between stream, channel, and peripherals. -#[allow(dead_code)] #[repr(u32)] -enum ChannelId { +pub enum ChannelId { Channel0 = 0b000, Channel1 = 0b001, Channel2 = 0b010, @@ -740,123 +742,63 @@ enum ChannelId { } /// DMA transfer direction. Section 9.5.5 -#[allow(dead_code)] #[repr(u32)] -enum Direction { +pub enum Direction { PeripheralToMemory = 0b00, MemoryToPeripheral = 0b01, MemoryToMemory = 0b10, } /// DMA data size. Section 9.5.5 -#[allow(dead_code)] #[repr(u32)] -enum Size { +pub enum Size { Byte = 0b00, HalfWord = 0b01, Word = 0b10, } -struct Msize(Size); -struct Psize(Size); +pub struct Msize(Size); +pub struct Psize(Size); /// DMA transfer mode. Section 9.5.10 -#[allow(dead_code)] #[repr(u32)] -enum FifoSize { +pub enum FifoSize { Quarter = 0b00, Half = 0b01, ThreeFourths = 0b10, Full = 0b11, } -#[allow(dead_code)] -enum TransferMode { +pub enum TransferMode { Direct, Fifo(FifoSize), } -/// List of peripherals managed by DMA1 -#[allow(non_camel_case_types, non_snake_case)] -#[derive(Copy, Clone, PartialEq)] -pub enum Dma1Peripheral { - USART2_TX, - USART2_RX, - USART3_TX, - USART3_RX, - SPI3_TX, - SPI3_RX, -} - -impl Dma1Peripheral { - // Returns the IRQ number of the stream associated with the peripheral. Used - // to enable interrupt on the NVIC. - pub fn get_stream_irqn(&self) -> u32 { - match self { - Dma1Peripheral::SPI3_TX => nvic::DMA1_Stream7, - Dma1Peripheral::USART2_TX => nvic::DMA1_Stream6, - Dma1Peripheral::USART2_RX => nvic::DMA1_Stream5, - Dma1Peripheral::USART3_TX => nvic::DMA1_Stream3, - Dma1Peripheral::SPI3_RX => nvic::DMA1_Stream2, - Dma1Peripheral::USART3_RX => nvic::DMA1_Stream1, - } - } - - pub fn get_stream_idx<'a>(&self) -> usize { - usize::from(StreamId::from(*self) as u8) - } -} - -impl From for StreamId { - fn from(pid: Dma1Peripheral) -> StreamId { - match pid { - Dma1Peripheral::SPI3_TX => StreamId::Stream7, - Dma1Peripheral::USART2_TX => StreamId::Stream6, - Dma1Peripheral::USART2_RX => StreamId::Stream5, - Dma1Peripheral::USART3_TX => StreamId::Stream3, - Dma1Peripheral::SPI3_RX => StreamId::Stream2, - Dma1Peripheral::USART3_RX => StreamId::Stream1, - } - } -} - -pub struct Stream<'a> { +/// This struct refers to a DMA Stream +/// +/// What other microcontrollers refer to as "channel", STM32F4XX refers to as "streams". +/// STM32F4XX has eight streams per DMA. +/// A stream transfers data between memory and peripheral. +pub struct Stream<'a, DMA: StreamServer<'a>> { streamid: StreamId, - client: OptionalCell<&'a dyn StreamClient>, + client: OptionalCell<&'a dyn StreamClient<'a, DMA>>, buffer: TakeCell<'static, [u8]>, - peripheral: OptionalCell, - dma1: &'a Dma1<'a>, + peripheral: OptionalCell, + dma: &'a DMA, } -pub fn new_dma1_stream<'a>(dma: &'a Dma1) -> [Stream<'a>; 8] { - [ - Stream::new(StreamId::Stream0, dma), - Stream::new(StreamId::Stream1, dma), - Stream::new(StreamId::Stream2, dma), - Stream::new(StreamId::Stream3, dma), - Stream::new(StreamId::Stream4, dma), - Stream::new(StreamId::Stream5, dma), - Stream::new(StreamId::Stream6, dma), - Stream::new(StreamId::Stream7, dma), - ] -} - -pub trait StreamClient { - fn transfer_done(&self, pid: Dma1Peripheral); -} - -impl<'a> Stream<'a> { - const fn new(streamid: StreamId, dma1: &'a Dma1<'a>) -> Self { +impl<'a, DMA: StreamServer<'a>> Stream<'a, DMA> { + fn new(streamid: StreamId, dma: &'a DMA) -> Self { Self { - streamid: streamid, + streamid, buffer: TakeCell::empty(), client: OptionalCell::empty(), peripheral: OptionalCell::empty(), - dma1, + dma, } } - pub fn set_client(&self, client: &'a dyn StreamClient) { + pub fn set_client(&self, client: &'a dyn StreamClient<'a, DMA>) { self.client.set(client); } @@ -865,12 +807,24 @@ impl<'a> Stream<'a> { self.client.map(|client| { self.peripheral.map(|pid| { - client.transfer_done(*pid); + client.transfer_done(pid); }); }); } - pub fn setup(&self, pid: Dma1Peripheral) { + pub fn setup(&self, pid: DMA::Peripheral) { + // A Dma::Peripheral always corresponds to a certain stream. + // So make sure we use the correct peripheral with the right channel. + // See section 10.3.3 "Channel selection" of the RM0090 reference manual. + if self.streamid != pid.into() { + panic!( + "Error: Peripheral {:?} with stream id {:?} was assigned to wrong Dma Stream: {:?}", + pid, + pid.into(), + self.streamid + ); + } + self.peripheral.set(pid); // Setup is called before interrupts are enabled on the NVIC @@ -900,7 +854,7 @@ impl<'a> Stream<'a> { // 2 self.set_peripheral_address(); // 3 - self.set_memory_address(&buf[0] as *const u8 as u32); + self.set_memory_address(core::ptr::from_ref::(&buf[0]) as u32); // 4 self.set_data_items(len as u32); // 5 @@ -931,388 +885,292 @@ impl<'a> Stream<'a> { fn set_channel(&self) { self.peripheral.map(|pid| { - match pid { - Dma1Peripheral::SPI3_TX => { - // SPI3_RX Stream 7, Channel 0 - self.dma1 - .registers - .s7cr - .modify(S7CR::CHSEL.val(ChannelId::Channel0 as u32)); - } - Dma1Peripheral::USART2_TX => { - // USART2_TX Stream 6, Channel 4 - self.dma1 - .registers - .s6cr - .modify(S6CR::CHSEL.val(ChannelId::Channel4 as u32)); - } - Dma1Peripheral::USART2_RX => { - // USART2_RX Stream 5, Channel 4 - self.dma1 - .registers - .s5cr - .modify(S5CR::CHSEL.val(ChannelId::Channel4 as u32)); - } - Dma1Peripheral::USART3_TX => { - // USART3_TX Stream 3, Channel 4 - self.dma1 - .registers - .s3cr - .modify(S3CR::CHSEL.val(ChannelId::Channel4 as u32)); - } - Dma1Peripheral::SPI3_RX => { - // SPI3_RX Stream 2, Channel 0 - self.dma1 - .registers - .s2cr - .modify(S2CR::CHSEL.val(ChannelId::Channel0 as u32)); - } - Dma1Peripheral::USART3_RX => { - // USART3_RX Stream 1, Channel 4 - self.dma1 - .registers - .s1cr - .modify(S1CR::CHSEL.val(ChannelId::Channel4 as u32)); - } - } + self.stream_set_channel(pid.channel_id()); }); } + fn stream_set_channel(&self, channel_id: ChannelId) { + match self.streamid { + StreamId::Stream0 => self + .dma + .registers() + .s0cr + .modify(S0CR::CHSEL.val(channel_id as u32)), + StreamId::Stream1 => self + .dma + .registers() + .s1cr + .modify(S1CR::CHSEL.val(channel_id as u32)), + StreamId::Stream2 => self + .dma + .registers() + .s2cr + .modify(S2CR::CHSEL.val(channel_id as u32)), + StreamId::Stream3 => self + .dma + .registers() + .s3cr + .modify(S3CR::CHSEL.val(channel_id as u32)), + StreamId::Stream4 => self + .dma + .registers() + .s4cr + .modify(S4CR::CHSEL.val(channel_id as u32)), + StreamId::Stream5 => self + .dma + .registers() + .s5cr + .modify(S5CR::CHSEL.val(channel_id as u32)), + StreamId::Stream6 => self + .dma + .registers() + .s6cr + .modify(S6CR::CHSEL.val(channel_id as u32)), + StreamId::Stream7 => self + .dma + .registers() + .s7cr + .modify(S7CR::CHSEL.val(channel_id as u32)), + } + } + fn set_direction(&self) { self.peripheral.map(|pid| { - match pid { - Dma1Peripheral::SPI3_TX => { - // SPI3_TX Stream 7 - self.dma1 - .registers - .s7cr - .modify(S7CR::DIR.val(Direction::MemoryToPeripheral as u32)); - } - Dma1Peripheral::USART2_TX => { - // USART2_TX Stream 6 - self.dma1 - .registers - .s6cr - .modify(S6CR::DIR.val(Direction::MemoryToPeripheral as u32)); - } - Dma1Peripheral::USART2_RX => { - // USART2_RX Stream 5 - self.dma1 - .registers - .s5cr - .modify(S5CR::DIR.val(Direction::PeripheralToMemory as u32)); - } - Dma1Peripheral::USART3_TX => { - // USART3_TX Stream 3 - self.dma1 - .registers - .s3cr - .modify(S3CR::DIR.val(Direction::MemoryToPeripheral as u32)); - } - Dma1Peripheral::SPI3_RX => { - // SPI3_RX Stream 2 - self.dma1 - .registers - .s2cr - .modify(S2CR::DIR.val(Direction::PeripheralToMemory as u32)); - } - Dma1Peripheral::USART3_RX => { - // USART3_RX Stream 1 - self.dma1 - .registers - .s1cr - .modify(S1CR::DIR.val(Direction::PeripheralToMemory as u32)); - } - } + self.stream_set_direction(pid.direction()); }); } + fn stream_set_direction(&self, direction: Direction) { + match self.streamid { + StreamId::Stream0 => self + .dma + .registers() + .s0cr + .modify(S0CR::DIR.val(direction as u32)), + StreamId::Stream1 => self + .dma + .registers() + .s1cr + .modify(S1CR::DIR.val(direction as u32)), + StreamId::Stream2 => self + .dma + .registers() + .s2cr + .modify(S2CR::DIR.val(direction as u32)), + StreamId::Stream3 => self + .dma + .registers() + .s3cr + .modify(S3CR::DIR.val(direction as u32)), + StreamId::Stream4 => self + .dma + .registers() + .s4cr + .modify(S4CR::DIR.val(direction as u32)), + StreamId::Stream5 => self + .dma + .registers() + .s5cr + .modify(S5CR::DIR.val(direction as u32)), + StreamId::Stream6 => self + .dma + .registers() + .s6cr + .modify(S6CR::DIR.val(direction as u32)), + StreamId::Stream7 => self + .dma + .registers() + .s7cr + .modify(S7CR::DIR.val(direction as u32)), + } + } + fn set_peripheral_address(&self) { self.peripheral.map(|pid| { - match pid { - Dma1Peripheral::SPI3_TX => { - // SPI3_TX Stream 7 - self.dma1 - .registers - .s7par - .set(spi::get_address_dr(spi::SPI3_BASE)); - } - Dma1Peripheral::USART2_TX => { - // USART2_TX Stream 6 - self.dma1 - .registers - .s6par - .set(usart::get_address_dr(usart::USART2_BASE)); - } - Dma1Peripheral::USART2_RX => { - // USART2_RX Stream 5 - self.dma1 - .registers - .s5par - .set(usart::get_address_dr(usart::USART2_BASE)); - } - Dma1Peripheral::USART3_TX => { - // USART3_TX Stream 3 - self.dma1 - .registers - .s3par - .set(usart::get_address_dr(usart::USART3_BASE)); - } - Dma1Peripheral::SPI3_RX => { - // SPI3_RX Stream 2 - self.dma1 - .registers - .s2par - .set(spi::get_address_dr(spi::SPI3_BASE)); - } - Dma1Peripheral::USART3_RX => { - // USART3_RX Stream 1 - self.dma1 - .registers - .s1par - .set(usart::get_address_dr(usart::USART3_BASE)); - } - } + self.stream_set_peripheral_address(pid.address()); }); } + fn stream_set_peripheral_address(&self, address: u32) { + match self.streamid { + StreamId::Stream0 => self.dma.registers().s0par.set(address), + StreamId::Stream1 => self.dma.registers().s1par.set(address), + StreamId::Stream2 => self.dma.registers().s2par.set(address), + StreamId::Stream3 => self.dma.registers().s3par.set(address), + StreamId::Stream4 => self.dma.registers().s4par.set(address), + StreamId::Stream5 => self.dma.registers().s5par.set(address), + StreamId::Stream6 => self.dma.registers().s6par.set(address), + StreamId::Stream7 => self.dma.registers().s7par.set(address), + } + } + fn set_peripheral_address_increment(&self) { - self.peripheral.map(|pid| { - match pid { - Dma1Peripheral::SPI3_TX => { - // SPI3_TX Stream 7 - self.dma1.registers.s7cr.modify(S7CR::PINC::CLEAR); - } - Dma1Peripheral::USART2_TX => { - // USART2_TX Stream 6 - self.dma1.registers.s6cr.modify(S6CR::PINC::CLEAR); - } - Dma1Peripheral::USART2_RX => { - // USART2_RX Stream 5 - self.dma1.registers.s5cr.modify(S5CR::PINC::CLEAR); - } - Dma1Peripheral::USART3_TX => { - // USART3_TX Stream 3 - self.dma1.registers.s3cr.modify(S3CR::PINC::CLEAR); - } - Dma1Peripheral::SPI3_RX => { - // SPI3_RX Stream 2 - self.dma1.registers.s2cr.modify(S2CR::PINC::CLEAR); - } - Dma1Peripheral::USART3_RX => { - // USART3_RX Stream 1 - self.dma1.registers.s1cr.modify(S1CR::PINC::CLEAR); - } - } - }); + match self.streamid { + StreamId::Stream0 => self.dma.registers().s0cr.modify(S0CR::PINC::CLEAR), + StreamId::Stream1 => self.dma.registers().s1cr.modify(S1CR::PINC::CLEAR), + StreamId::Stream2 => self.dma.registers().s2cr.modify(S2CR::PINC::CLEAR), + StreamId::Stream3 => self.dma.registers().s3cr.modify(S3CR::PINC::CLEAR), + StreamId::Stream4 => self.dma.registers().s4cr.modify(S4CR::PINC::CLEAR), + StreamId::Stream5 => self.dma.registers().s5cr.modify(S5CR::PINC::CLEAR), + StreamId::Stream6 => self.dma.registers().s6cr.modify(S6CR::PINC::CLEAR), + StreamId::Stream7 => self.dma.registers().s7cr.modify(S7CR::PINC::CLEAR), + } } fn set_memory_address(&self, buf_addr: u32) { - self.peripheral.map(|pid| { - match pid { - Dma1Peripheral::SPI3_TX => { - // SPI3_TX Stream 7 - self.dma1.registers.s7m0ar.set(buf_addr); - } - Dma1Peripheral::USART2_TX => { - // USART2_TX Stream 6 - self.dma1.registers.s6m0ar.set(buf_addr); - } - Dma1Peripheral::USART2_RX => { - // USART2_RX Stream 5 - self.dma1.registers.s5m0ar.set(buf_addr); - } - Dma1Peripheral::USART3_TX => { - // USART3_TX Stream 3 - self.dma1.registers.s3m0ar.set(buf_addr); - } - Dma1Peripheral::SPI3_RX => { - // SPI3_RX Stream 2 - self.dma1.registers.s2m0ar.set(buf_addr); - } - Dma1Peripheral::USART3_RX => { - // USART3_RX Stream 1 - self.dma1.registers.s1m0ar.set(buf_addr); - } - } - }); + match self.streamid { + StreamId::Stream0 => self.dma.registers().s0m0ar.set(buf_addr), + StreamId::Stream1 => self.dma.registers().s1m0ar.set(buf_addr), + StreamId::Stream2 => self.dma.registers().s2m0ar.set(buf_addr), + StreamId::Stream3 => self.dma.registers().s3m0ar.set(buf_addr), + StreamId::Stream4 => self.dma.registers().s4m0ar.set(buf_addr), + StreamId::Stream5 => self.dma.registers().s5m0ar.set(buf_addr), + StreamId::Stream6 => self.dma.registers().s6m0ar.set(buf_addr), + StreamId::Stream7 => self.dma.registers().s7m0ar.set(buf_addr), + } } fn set_memory_address_increment(&self) { - self.peripheral.map(|pid| { - match pid { - Dma1Peripheral::SPI3_TX => { - // SPI3_TX Stream 7 - self.dma1.registers.s7cr.modify(S7CR::MINC::SET); - } - Dma1Peripheral::USART2_TX => { - // USART2_TX Stream 6 - self.dma1.registers.s6cr.modify(S6CR::MINC::SET); - } - Dma1Peripheral::USART2_RX => { - // USART2_RX Stream 5 - self.dma1.registers.s5cr.modify(S5CR::MINC::SET); - } - Dma1Peripheral::USART3_TX => { - // USART3_TX Stream 3 - self.dma1.registers.s3cr.modify(S3CR::MINC::SET); - } - Dma1Peripheral::SPI3_RX => { - // SPI3_RX Stream 2 - self.dma1.registers.s2cr.modify(S2CR::MINC::SET); - } - Dma1Peripheral::USART3_RX => { - // USART3_RX Stream 1 - self.dma1.registers.s1cr.modify(S1CR::MINC::SET); - } - } - }); + match self.streamid { + StreamId::Stream0 => self.dma.registers().s0cr.modify(S0CR::MINC::SET), + StreamId::Stream1 => self.dma.registers().s1cr.modify(S1CR::MINC::SET), + StreamId::Stream2 => self.dma.registers().s2cr.modify(S2CR::MINC::SET), + StreamId::Stream3 => self.dma.registers().s3cr.modify(S3CR::MINC::SET), + StreamId::Stream4 => self.dma.registers().s4cr.modify(S4CR::MINC::SET), + StreamId::Stream5 => self.dma.registers().s5cr.modify(S5CR::MINC::SET), + StreamId::Stream6 => self.dma.registers().s6cr.modify(S6CR::MINC::SET), + StreamId::Stream7 => self.dma.registers().s7cr.modify(S7CR::MINC::SET), + } } fn get_data_items(&self) -> u32 { match self.streamid { - StreamId::Stream0 => self.dma1.registers.s0ndtr.get(), - StreamId::Stream1 => self.dma1.registers.s1ndtr.get(), - StreamId::Stream2 => self.dma1.registers.s2ndtr.get(), - StreamId::Stream3 => self.dma1.registers.s3ndtr.get(), - StreamId::Stream4 => self.dma1.registers.s4ndtr.get(), - StreamId::Stream5 => self.dma1.registers.s5ndtr.get(), - StreamId::Stream6 => self.dma1.registers.s6ndtr.get(), - StreamId::Stream7 => self.dma1.registers.s7ndtr.get(), + StreamId::Stream0 => self.dma.registers().s0ndtr.get(), + StreamId::Stream1 => self.dma.registers().s1ndtr.get(), + StreamId::Stream2 => self.dma.registers().s2ndtr.get(), + StreamId::Stream3 => self.dma.registers().s3ndtr.get(), + StreamId::Stream4 => self.dma.registers().s4ndtr.get(), + StreamId::Stream5 => self.dma.registers().s5ndtr.get(), + StreamId::Stream6 => self.dma.registers().s6ndtr.get(), + StreamId::Stream7 => self.dma.registers().s7ndtr.get(), } } fn set_data_items(&self, data_items: u32) { match self.streamid { StreamId::Stream0 => { - self.dma1.registers.s0ndtr.set(data_items); + self.dma.registers().s0ndtr.set(data_items); } StreamId::Stream1 => { - self.dma1.registers.s1ndtr.set(data_items); + self.dma.registers().s1ndtr.set(data_items); } StreamId::Stream2 => { - self.dma1.registers.s2ndtr.set(data_items); + self.dma.registers().s2ndtr.set(data_items); } StreamId::Stream3 => { - self.dma1.registers.s3ndtr.set(data_items); + self.dma.registers().s3ndtr.set(data_items); } StreamId::Stream4 => { - self.dma1.registers.s4ndtr.set(data_items); + self.dma.registers().s4ndtr.set(data_items); } StreamId::Stream5 => { - self.dma1.registers.s5ndtr.set(data_items); + self.dma.registers().s5ndtr.set(data_items); } StreamId::Stream6 => { - self.dma1.registers.s6ndtr.set(data_items); + self.dma.registers().s6ndtr.set(data_items); } StreamId::Stream7 => { - self.dma1.registers.s7ndtr.set(data_items); + self.dma.registers().s7ndtr.set(data_items); } } } fn set_data_width_for_peripheral(&self) { - self.peripheral.map(|pid| match pid { - Dma1Peripheral::SPI3_TX => { - self.stream_set_data_width(Msize(Size::Byte), Psize(Size::Byte)) - } - Dma1Peripheral::USART2_TX => { - self.stream_set_data_width(Msize(Size::Byte), Psize(Size::Byte)) - } - Dma1Peripheral::USART2_RX => { - self.stream_set_data_width(Msize(Size::Byte), Psize(Size::Byte)) - } - Dma1Peripheral::USART3_TX => { - self.stream_set_data_width(Msize(Size::Byte), Psize(Size::Byte)) - } - Dma1Peripheral::SPI3_RX => { - self.stream_set_data_width(Msize(Size::Byte), Psize(Size::Byte)) - } - Dma1Peripheral::USART3_RX => { - self.stream_set_data_width(Msize(Size::Byte), Psize(Size::Byte)) - } + self.peripheral.map(|pid| { + let (msize, psize) = pid.data_width(); + self.stream_set_data_width(msize, psize); }); } fn stream_set_data_width(&self, msize: Msize, psize: Psize) { match self.streamid { StreamId::Stream0 => { - self.dma1 - .registers + self.dma + .registers() .s0cr .modify(S0CR::PSIZE.val(psize.0 as u32)); - self.dma1 - .registers + self.dma + .registers() .s0cr .modify(S0CR::MSIZE.val(msize.0 as u32)); } StreamId::Stream1 => { - self.dma1 - .registers + self.dma + .registers() .s1cr .modify(S1CR::PSIZE.val(psize.0 as u32)); - self.dma1 - .registers + self.dma + .registers() .s1cr .modify(S1CR::MSIZE.val(msize.0 as u32)); } StreamId::Stream2 => { - self.dma1 - .registers + self.dma + .registers() .s2cr .modify(S2CR::PSIZE.val(psize.0 as u32)); - self.dma1 - .registers + self.dma + .registers() .s2cr .modify(S2CR::MSIZE.val(msize.0 as u32)); } StreamId::Stream3 => { - self.dma1 - .registers + self.dma + .registers() .s3cr .modify(S3CR::PSIZE.val(psize.0 as u32)); - self.dma1 - .registers + self.dma + .registers() .s3cr .modify(S3CR::MSIZE.val(msize.0 as u32)); } StreamId::Stream4 => { - self.dma1 - .registers + self.dma + .registers() .s4cr .modify(S4CR::PSIZE.val(psize.0 as u32)); - self.dma1 - .registers + self.dma + .registers() .s4cr .modify(S4CR::MSIZE.val(msize.0 as u32)); } StreamId::Stream5 => { - self.dma1 - .registers + self.dma + .registers() .s5cr .modify(S5CR::PSIZE.val(psize.0 as u32)); - self.dma1 - .registers + self.dma + .registers() .s5cr .modify(S5CR::MSIZE.val(msize.0 as u32)); } StreamId::Stream6 => { - self.dma1 - .registers + self.dma + .registers() .s6cr .modify(S6CR::PSIZE.val(psize.0 as u32)); - self.dma1 - .registers + self.dma + .registers() .s6cr .modify(S6CR::MSIZE.val(msize.0 as u32)); } StreamId::Stream7 => { - self.dma1 - .registers + self.dma + .registers() .s7cr .modify(S7CR::PSIZE.val(psize.0 as u32)); - self.dma1 - .registers + self.dma + .registers() .s7cr .modify(S7CR::MSIZE.val(msize.0 as u32)); } @@ -1320,25 +1178,8 @@ impl<'a> Stream<'a> { } fn set_transfer_mode_for_peripheral(&self) { - self.peripheral.map(|pid| match pid { - Dma1Peripheral::SPI3_TX => { - self.stream_set_transfer_mode(TransferMode::Fifo(FifoSize::Full)); - } - Dma1Peripheral::USART2_TX => { - self.stream_set_transfer_mode(TransferMode::Fifo(FifoSize::Full)); - } - Dma1Peripheral::USART2_RX => { - self.stream_set_transfer_mode(TransferMode::Fifo(FifoSize::Full)); - } - Dma1Peripheral::USART3_TX => { - self.stream_set_transfer_mode(TransferMode::Fifo(FifoSize::Full)); - } - Dma1Peripheral::SPI3_RX => { - self.stream_set_transfer_mode(TransferMode::Fifo(FifoSize::Full)); - } - Dma1Peripheral::USART3_RX => { - self.stream_set_transfer_mode(TransferMode::Fifo(FifoSize::Full)); - } + self.peripheral.map(|pid| { + self.stream_set_transfer_mode(pid.transfer_mode()); }); } @@ -1346,74 +1187,74 @@ impl<'a> Stream<'a> { match self.streamid { StreamId::Stream0 => match transfer_mode { TransferMode::Direct => { - self.dma1.registers.s0fcr.modify(S0FCR::DMDIS::CLEAR); + self.dma.registers().s0fcr.modify(S0FCR::DMDIS::CLEAR); } TransferMode::Fifo(s) => { - self.dma1.registers.s0fcr.modify(S0FCR::DMDIS::SET); - self.dma1.registers.s0fcr.modify(S0FCR::FTH.val(s as u32)); + self.dma.registers().s0fcr.modify(S0FCR::DMDIS::SET); + self.dma.registers().s0fcr.modify(S0FCR::FTH.val(s as u32)); } }, StreamId::Stream1 => match transfer_mode { TransferMode::Direct => { - self.dma1.registers.s1fcr.modify(S1FCR::DMDIS::CLEAR); + self.dma.registers().s1fcr.modify(S1FCR::DMDIS::CLEAR); } TransferMode::Fifo(s) => { - self.dma1.registers.s1fcr.modify(S1FCR::DMDIS::SET); - self.dma1.registers.s1fcr.modify(S1FCR::FTH.val(s as u32)); + self.dma.registers().s1fcr.modify(S1FCR::DMDIS::SET); + self.dma.registers().s1fcr.modify(S1FCR::FTH.val(s as u32)); } }, StreamId::Stream2 => match transfer_mode { TransferMode::Direct => { - self.dma1.registers.s2fcr.modify(S2FCR::DMDIS::CLEAR); + self.dma.registers().s2fcr.modify(S2FCR::DMDIS::CLEAR); } TransferMode::Fifo(s) => { - self.dma1.registers.s2fcr.modify(S2FCR::DMDIS::SET); - self.dma1.registers.s2fcr.modify(S2FCR::FTH.val(s as u32)); + self.dma.registers().s2fcr.modify(S2FCR::DMDIS::SET); + self.dma.registers().s2fcr.modify(S2FCR::FTH.val(s as u32)); } }, StreamId::Stream3 => match transfer_mode { TransferMode::Direct => { - self.dma1.registers.s3fcr.modify(S3FCR::DMDIS::CLEAR); + self.dma.registers().s3fcr.modify(S3FCR::DMDIS::CLEAR); } TransferMode::Fifo(s) => { - self.dma1.registers.s3fcr.modify(S3FCR::DMDIS::SET); - self.dma1.registers.s3fcr.modify(S3FCR::FTH.val(s as u32)); + self.dma.registers().s3fcr.modify(S3FCR::DMDIS::SET); + self.dma.registers().s3fcr.modify(S3FCR::FTH.val(s as u32)); } }, StreamId::Stream4 => match transfer_mode { TransferMode::Direct => { - self.dma1.registers.s4fcr.modify(S4FCR::DMDIS::CLEAR); + self.dma.registers().s4fcr.modify(S4FCR::DMDIS::CLEAR); } TransferMode::Fifo(s) => { - self.dma1.registers.s4fcr.modify(S4FCR::DMDIS::SET); - self.dma1.registers.s4fcr.modify(S4FCR::FTH.val(s as u32)); + self.dma.registers().s4fcr.modify(S4FCR::DMDIS::SET); + self.dma.registers().s4fcr.modify(S4FCR::FTH.val(s as u32)); } }, StreamId::Stream5 => match transfer_mode { TransferMode::Direct => { - self.dma1.registers.s5fcr.modify(S5FCR::DMDIS::CLEAR); + self.dma.registers().s5fcr.modify(S5FCR::DMDIS::CLEAR); } TransferMode::Fifo(s) => { - self.dma1.registers.s5fcr.modify(S5FCR::DMDIS::SET); - self.dma1.registers.s5fcr.modify(S5FCR::FTH.val(s as u32)); + self.dma.registers().s5fcr.modify(S5FCR::DMDIS::SET); + self.dma.registers().s5fcr.modify(S5FCR::FTH.val(s as u32)); } }, StreamId::Stream6 => match transfer_mode { TransferMode::Direct => { - self.dma1.registers.s6fcr.modify(S6FCR::DMDIS::CLEAR); + self.dma.registers().s6fcr.modify(S6FCR::DMDIS::CLEAR); } TransferMode::Fifo(s) => { - self.dma1.registers.s6fcr.modify(S6FCR::DMDIS::SET); - self.dma1.registers.s6fcr.modify(S6FCR::FTH.val(s as u32)); + self.dma.registers().s6fcr.modify(S6FCR::DMDIS::SET); + self.dma.registers().s6fcr.modify(S6FCR::FTH.val(s as u32)); } }, StreamId::Stream7 => match transfer_mode { TransferMode::Direct => { - self.dma1.registers.s7fcr.modify(S7FCR::DMDIS::CLEAR); + self.dma.registers().s7fcr.modify(S7FCR::DMDIS::CLEAR); } TransferMode::Fifo(s) => { - self.dma1.registers.s7fcr.modify(S7FCR::DMDIS::SET); - self.dma1.registers.s7fcr.modify(S7FCR::FTH.val(s as u32)); + self.dma.registers().s7fcr.modify(S7FCR::DMDIS::SET); + self.dma.registers().s7fcr.modify(S7FCR::FTH.val(s as u32)); } }, } @@ -1421,55 +1262,55 @@ impl<'a> Stream<'a> { fn enable(&self) { match self.streamid { - StreamId::Stream0 => self.dma1.registers.s0cr.modify(S0CR::EN::SET), - StreamId::Stream1 => self.dma1.registers.s1cr.modify(S1CR::EN::SET), - StreamId::Stream2 => self.dma1.registers.s2cr.modify(S2CR::EN::SET), - StreamId::Stream3 => self.dma1.registers.s3cr.modify(S3CR::EN::SET), - StreamId::Stream4 => self.dma1.registers.s4cr.modify(S4CR::EN::SET), - StreamId::Stream5 => self.dma1.registers.s5cr.modify(S5CR::EN::SET), - StreamId::Stream6 => self.dma1.registers.s6cr.modify(S6CR::EN::SET), - StreamId::Stream7 => self.dma1.registers.s7cr.modify(S7CR::EN::SET), + StreamId::Stream0 => self.dma.registers().s0cr.modify(S0CR::EN::SET), + StreamId::Stream1 => self.dma.registers().s1cr.modify(S1CR::EN::SET), + StreamId::Stream2 => self.dma.registers().s2cr.modify(S2CR::EN::SET), + StreamId::Stream3 => self.dma.registers().s3cr.modify(S3CR::EN::SET), + StreamId::Stream4 => self.dma.registers().s4cr.modify(S4CR::EN::SET), + StreamId::Stream5 => self.dma.registers().s5cr.modify(S5CR::EN::SET), + StreamId::Stream6 => self.dma.registers().s6cr.modify(S6CR::EN::SET), + StreamId::Stream7 => self.dma.registers().s7cr.modify(S7CR::EN::SET), } } fn disable(&self) { match self.streamid { - StreamId::Stream0 => self.dma1.registers.s0cr.modify(S0CR::EN::CLEAR), - StreamId::Stream1 => self.dma1.registers.s1cr.modify(S1CR::EN::CLEAR), - StreamId::Stream2 => self.dma1.registers.s2cr.modify(S2CR::EN::CLEAR), - StreamId::Stream3 => self.dma1.registers.s3cr.modify(S3CR::EN::CLEAR), - StreamId::Stream4 => self.dma1.registers.s4cr.modify(S4CR::EN::CLEAR), - StreamId::Stream5 => self.dma1.registers.s5cr.modify(S5CR::EN::CLEAR), - StreamId::Stream6 => self.dma1.registers.s6cr.modify(S6CR::EN::CLEAR), - StreamId::Stream7 => self.dma1.registers.s7cr.modify(S7CR::EN::CLEAR), + StreamId::Stream0 => self.dma.registers().s0cr.modify(S0CR::EN::CLEAR), + StreamId::Stream1 => self.dma.registers().s1cr.modify(S1CR::EN::CLEAR), + StreamId::Stream2 => self.dma.registers().s2cr.modify(S2CR::EN::CLEAR), + StreamId::Stream3 => self.dma.registers().s3cr.modify(S3CR::EN::CLEAR), + StreamId::Stream4 => self.dma.registers().s4cr.modify(S4CR::EN::CLEAR), + StreamId::Stream5 => self.dma.registers().s5cr.modify(S5CR::EN::CLEAR), + StreamId::Stream6 => self.dma.registers().s6cr.modify(S6CR::EN::CLEAR), + StreamId::Stream7 => self.dma.registers().s7cr.modify(S7CR::EN::CLEAR), } } fn clear_transfer_complete_flag(&self) { match self.streamid { StreamId::Stream0 => { - self.dma1.registers.lifcr.write(LIFCR::CTCIF0::SET); + self.dma.registers().lifcr.write(LIFCR::CTCIF0::SET); } StreamId::Stream1 => { - self.dma1.registers.lifcr.write(LIFCR::CTCIF1::SET); + self.dma.registers().lifcr.write(LIFCR::CTCIF1::SET); } StreamId::Stream2 => { - self.dma1.registers.lifcr.write(LIFCR::CTCIF2::SET); + self.dma.registers().lifcr.write(LIFCR::CTCIF2::SET); } StreamId::Stream3 => { - self.dma1.registers.lifcr.write(LIFCR::CTCIF3::SET); + self.dma.registers().lifcr.write(LIFCR::CTCIF3::SET); } StreamId::Stream4 => { - self.dma1.registers.hifcr.write(HIFCR::CTCIF4::SET); + self.dma.registers().hifcr.write(HIFCR::CTCIF4::SET); } StreamId::Stream5 => { - self.dma1.registers.hifcr.write(HIFCR::CTCIF5::SET); + self.dma.registers().hifcr.write(HIFCR::CTCIF5::SET); } StreamId::Stream6 => { - self.dma1.registers.hifcr.write(HIFCR::CTCIF6::SET); + self.dma.registers().hifcr.write(HIFCR::CTCIF6::SET); } StreamId::Stream7 => { - self.dma1.registers.hifcr.write(HIFCR::CTCIF7::SET); + self.dma.registers().hifcr.write(HIFCR::CTCIF7::SET); } } } @@ -1477,44 +1318,236 @@ impl<'a> Stream<'a> { // We only interrupt on TC (Transfer Complete) fn interrupt_enable(&self) { match self.streamid { - StreamId::Stream0 => self.dma1.registers.s0cr.modify(S0CR::TCIE::SET), - StreamId::Stream1 => self.dma1.registers.s1cr.modify(S1CR::TCIE::SET), - StreamId::Stream2 => self.dma1.registers.s2cr.modify(S2CR::TCIE::SET), - StreamId::Stream3 => self.dma1.registers.s3cr.modify(S3CR::TCIE::SET), - StreamId::Stream4 => self.dma1.registers.s4cr.modify(S4CR::TCIE::SET), - StreamId::Stream5 => self.dma1.registers.s5cr.modify(S5CR::TCIE::SET), - StreamId::Stream6 => self.dma1.registers.s6cr.modify(S6CR::TCIE::SET), - StreamId::Stream7 => self.dma1.registers.s7cr.modify(S7CR::TCIE::SET), + StreamId::Stream0 => self.dma.registers().s0cr.modify(S0CR::TCIE::SET), + StreamId::Stream1 => self.dma.registers().s1cr.modify(S1CR::TCIE::SET), + StreamId::Stream2 => self.dma.registers().s2cr.modify(S2CR::TCIE::SET), + StreamId::Stream3 => self.dma.registers().s3cr.modify(S3CR::TCIE::SET), + StreamId::Stream4 => self.dma.registers().s4cr.modify(S4CR::TCIE::SET), + StreamId::Stream5 => self.dma.registers().s5cr.modify(S5CR::TCIE::SET), + StreamId::Stream6 => self.dma.registers().s6cr.modify(S6CR::TCIE::SET), + StreamId::Stream7 => self.dma.registers().s7cr.modify(S7CR::TCIE::SET), } } // We only interrupt on TC (Transfer Complete) fn disable_interrupt(&self) { match self.streamid { - StreamId::Stream0 => self.dma1.registers.s0cr.modify(S0CR::TCIE::CLEAR), - StreamId::Stream1 => self.dma1.registers.s1cr.modify(S1CR::TCIE::CLEAR), - StreamId::Stream2 => self.dma1.registers.s2cr.modify(S2CR::TCIE::CLEAR), - StreamId::Stream3 => self.dma1.registers.s3cr.modify(S3CR::TCIE::CLEAR), - StreamId::Stream4 => self.dma1.registers.s4cr.modify(S4CR::TCIE::CLEAR), - StreamId::Stream5 => self.dma1.registers.s5cr.modify(S5CR::TCIE::CLEAR), - StreamId::Stream6 => self.dma1.registers.s6cr.modify(S6CR::TCIE::CLEAR), - StreamId::Stream7 => self.dma1.registers.s7cr.modify(S7CR::TCIE::CLEAR), + StreamId::Stream0 => self.dma.registers().s0cr.modify(S0CR::TCIE::CLEAR), + StreamId::Stream1 => self.dma.registers().s1cr.modify(S1CR::TCIE::CLEAR), + StreamId::Stream2 => self.dma.registers().s2cr.modify(S2CR::TCIE::CLEAR), + StreamId::Stream3 => self.dma.registers().s3cr.modify(S3CR::TCIE::CLEAR), + StreamId::Stream4 => self.dma.registers().s4cr.modify(S4CR::TCIE::CLEAR), + StreamId::Stream5 => self.dma.registers().s5cr.modify(S5CR::TCIE::CLEAR), + StreamId::Stream6 => self.dma.registers().s6cr.modify(S6CR::TCIE::CLEAR), + StreamId::Stream7 => self.dma.registers().s7cr.modify(S7CR::TCIE::CLEAR), + } + } +} + +/// Interface required for each Peripheral by the DMA Stream. +/// +/// The data defined here may vary by Peripheral. It is used by the DMA Stream +/// to correctly configure the DMA. +/// +/// To implement a new Peripheral, add it to the corresponding enum (Dma1-/Dma2Peripheral) +/// and add its data to the impl of this trait. +pub trait StreamPeripheral { + fn transfer_mode(&self) -> TransferMode; + + fn data_width(&self) -> (Msize, Psize); + + fn channel_id(&self) -> ChannelId; + + fn direction(&self) -> Direction; + + fn address(&self) -> u32; +} + +pub trait StreamServer<'a> { + type Peripheral: StreamPeripheral + core::marker::Copy + PartialEq + Into + Debug; + + fn registers(&self) -> &DmaRegisters; +} + +pub trait StreamClient<'a, DMA: StreamServer<'a>> { + fn transfer_done(&self, pid: DMA::Peripheral); +} + +struct DmaClock<'a>(phclk::PeripheralClock<'a>); + +impl ClockInterface for DmaClock<'_> { + fn is_enabled(&self) -> bool { + self.0.is_enabled() + } + + fn enable(&self) { + self.0.enable(); + } + + fn disable(&self) { + self.0.disable(); + } +} + +// ########################## DMA 1 ###################################### + +/// List of peripherals managed by DMA1 +#[allow(non_camel_case_types, non_snake_case)] +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum Dma1Peripheral { + USART2_TX, + USART2_RX, + USART3_TX, + USART3_RX, + SPI3_TX, + SPI3_RX, +} + +impl Dma1Peripheral { + // Returns the IRQ number of the stream associated with the peripheral. Used + // to enable interrupt on the NVIC. + pub fn get_stream_irqn(&self) -> u32 { + match self { + Dma1Peripheral::SPI3_TX => nvic::DMA1_Stream7, + Dma1Peripheral::USART2_TX => nvic::DMA1_Stream6, + Dma1Peripheral::USART2_RX => nvic::DMA1_Stream5, + Dma1Peripheral::USART3_TX => nvic::DMA1_Stream3, + Dma1Peripheral::SPI3_RX => nvic::DMA1_Stream2, + Dma1Peripheral::USART3_RX => nvic::DMA1_Stream1, } } + + pub fn get_stream_idx(&self) -> usize { + usize::from(StreamId::from(*self) as u8) + } } +impl From for StreamId { + fn from(pid: Dma1Peripheral) -> StreamId { + match pid { + Dma1Peripheral::SPI3_TX => StreamId::Stream7, + Dma1Peripheral::USART2_TX => StreamId::Stream6, + Dma1Peripheral::USART2_RX => StreamId::Stream5, + Dma1Peripheral::USART3_TX => StreamId::Stream3, + Dma1Peripheral::SPI3_RX => StreamId::Stream2, + Dma1Peripheral::USART3_RX => StreamId::Stream1, + } + } +} + +impl StreamPeripheral for Dma1Peripheral { + fn transfer_mode(&self) -> TransferMode { + TransferMode::Fifo(FifoSize::Full) + } + + fn data_width(&self) -> (Msize, Psize) { + (Msize(Size::Byte), Psize(Size::Byte)) + } + + fn channel_id(&self) -> ChannelId { + match self { + Dma1Peripheral::SPI3_TX => { + // SPI3_RX Stream 7, Channel 0 + ChannelId::Channel0 + } + Dma1Peripheral::USART2_TX => { + // USART2_TX Stream 6, Channel 4 + ChannelId::Channel4 + } + Dma1Peripheral::USART2_RX => { + // USART2_RX Stream 5, Channel 4 + ChannelId::Channel4 + } + Dma1Peripheral::USART3_TX => { + // USART3_TX Stream 3, Channel 4 + ChannelId::Channel4 + } + Dma1Peripheral::SPI3_RX => { + // SPI3_RX Stream 2, Channel 0 + ChannelId::Channel0 + } + Dma1Peripheral::USART3_RX => { + // USART3_RX Stream 1, Channel 4 + ChannelId::Channel4 + } + } + } + + fn direction(&self) -> Direction { + match self { + Dma1Peripheral::SPI3_TX => Direction::MemoryToPeripheral, + Dma1Peripheral::USART2_TX => Direction::MemoryToPeripheral, + Dma1Peripheral::USART2_RX => Direction::PeripheralToMemory, + Dma1Peripheral::USART3_TX => Direction::MemoryToPeripheral, + Dma1Peripheral::SPI3_RX => Direction::PeripheralToMemory, + Dma1Peripheral::USART3_RX => Direction::PeripheralToMemory, + } + } + + fn address(&self) -> u32 { + match self { + Dma1Peripheral::SPI3_TX => spi::get_address_dr(spi::SPI3_BASE), + Dma1Peripheral::USART2_TX => usart::get_address_dr(usart::USART2_BASE), + Dma1Peripheral::USART2_RX => usart::get_address_dr(usart::USART2_BASE), + Dma1Peripheral::USART3_TX => usart::get_address_dr(usart::USART3_BASE), + Dma1Peripheral::SPI3_RX => spi::get_address_dr(spi::SPI3_BASE), + Dma1Peripheral::USART3_RX => usart::get_address_dr(usart::USART3_BASE), + } + } +} + +pub fn new_dma1_stream<'a>(dma: &'a Dma1) -> [Stream<'a, Dma1<'a>>; 8] { + [ + Stream::new(StreamId::Stream0, dma), + Stream::new(StreamId::Stream1, dma), + Stream::new(StreamId::Stream2, dma), + Stream::new(StreamId::Stream3, dma), + Stream::new(StreamId::Stream4, dma), + Stream::new(StreamId::Stream5, dma), + Stream::new(StreamId::Stream6, dma), + Stream::new(StreamId::Stream7, dma), + ] +} + +const DMA1_BASE: StaticRef = + unsafe { StaticRef::new(0x40026000 as *const DmaRegisters) }; + +/// Dma1 is kept as a separate type from Dma2 in order to allow for more compile-time checks. +/// +/// Excerpt from the discussion on this decision: +/// +/// > It's definitely a tradeoff between having code duplication vs. more checks at compile time. +/// > With the current implementation the Dma is propagated to the types of Usart & SPI, so usart1 is a Usart\. +/// > In theory, we could simply have a single DmaPeripheral enum, that contains all peripherals, with a single, non-generic Stream struct implementation. +/// > This way we wouldn't have any code duplication and both Usart and Spi would no longer have to be generic over the Dma or its peripheral. +/// > The disadvantage then would be, that one could create a Usart instance for usart1 and accidentally pass it a stream of dma1, instead of dma2. Currently, this is impossible, as they are of different types. +/// > +/// > We could have these checks at runtime, with the Peripheral reporting which Dma it belongs to and the system panicking if it's set up incorrectly, like I have added for checking whether the peripheral is added to the right stream. +/// > +/// > So we basically have three options here: +/// > 1. Keep Stream\ +/// > 2. Change to Stream\ +/// > 3. Remove Generics from Stream & add runtime checks +/// > +/// > In order of most code duplication & compile-time safety to least. +/// +/// The decision to stick with separate types for DMA 1 and DMA 2 was made because: +/// +/// > Static checks are good, and the code duplication here looks manageable (i.e. it's pretty formulaic and unlikely to need to change much if at all). +/// +/// For details, see [the full discussion](https://github.com/tock/tock/pull/2936#discussion_r792908212). pub struct Dma1<'a> { - registers: StaticRef, - clock: Dma1Clock<'a>, + registers: StaticRef, + clock: DmaClock<'a>, } impl<'a> Dma1<'a> { - pub const fn new(rcc: &'a rcc::Rcc) -> Dma1 { + pub const fn new(clocks: &'a dyn Stm32f4Clocks) -> Dma1 { Dma1 { registers: DMA1_BASE, - clock: Dma1Clock(rcc::PeripheralClock::new( - rcc::PeripheralClockType::AHB1(rcc::HCLK1::DMA1), - rcc, + clock: DmaClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::AHB1(phclk::HCLK1::DMA1), + clocks, )), } } @@ -1532,18 +1565,131 @@ impl<'a> Dma1<'a> { } } -struct Dma1Clock<'a>(rcc::PeripheralClock<'a>); +impl<'a> StreamServer<'a> for Dma1<'a> { + type Peripheral = Dma1Peripheral; -impl ClockInterface for Dma1Clock<'_> { - fn is_enabled(&self) -> bool { - self.0.is_enabled() + fn registers(&self) -> &DmaRegisters { + &self.registers } +} - fn enable(&self) { - self.0.enable(); +// ########################## DMA 2 ###################################### + +/// List of peripherals managed by DMA2 +#[allow(non_camel_case_types, non_snake_case)] +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum Dma2Peripheral { + USART1_TX, + USART1_RX, +} + +impl Dma2Peripheral { + // Returns the IRQ number of the stream associated with the peripheral. Used + // to enable interrupt on the NVIC. + pub fn get_stream_irqn(&self) -> u32 { + match self { + Dma2Peripheral::USART1_TX => nvic::DMA2_Stream7, + Dma2Peripheral::USART1_RX => nvic::DMA2_Stream5, // could also be Stream 2, chosen arbitrarily + } } - fn disable(&self) { - self.0.disable(); + pub fn get_stream_idx(&self) -> usize { + usize::from(StreamId::from(*self) as u8) + } +} + +impl From for StreamId { + fn from(pid: Dma2Peripheral) -> StreamId { + match pid { + Dma2Peripheral::USART1_TX => StreamId::Stream7, + Dma2Peripheral::USART1_RX => StreamId::Stream5, + } + } +} + +impl StreamPeripheral for Dma2Peripheral { + fn transfer_mode(&self) -> TransferMode { + TransferMode::Fifo(FifoSize::Full) + } + + fn data_width(&self) -> (Msize, Psize) { + (Msize(Size::Byte), Psize(Size::Byte)) + } + + fn channel_id(&self) -> ChannelId { + match self { + // USART1_TX Stream 7, Channel 4 + Dma2Peripheral::USART1_TX => ChannelId::Channel4, + // USART1_RX Stream 5, Channel 4 + Dma2Peripheral::USART1_RX => ChannelId::Channel4, + } + } + + fn direction(&self) -> Direction { + match self { + Dma2Peripheral::USART1_TX => Direction::MemoryToPeripheral, + Dma2Peripheral::USART1_RX => Direction::PeripheralToMemory, + } + } + + fn address(&self) -> u32 { + match self { + Dma2Peripheral::USART1_TX => usart::get_address_dr(usart::USART1_BASE), + Dma2Peripheral::USART1_RX => usart::get_address_dr(usart::USART1_BASE), + } + } +} + +pub fn new_dma2_stream<'a>(dma: &'a Dma2) -> [Stream<'a, Dma2<'a>>; 8] { + [ + Stream::new(StreamId::Stream0, dma), + Stream::new(StreamId::Stream1, dma), + Stream::new(StreamId::Stream2, dma), + Stream::new(StreamId::Stream3, dma), + Stream::new(StreamId::Stream4, dma), + Stream::new(StreamId::Stream5, dma), + Stream::new(StreamId::Stream6, dma), + Stream::new(StreamId::Stream7, dma), + ] +} + +const DMA2_BASE: StaticRef = + unsafe { StaticRef::new(0x40026400 as *const DmaRegisters) }; + +/// For an explanation of why this is its own type, see the docs for the Dma1 struct. +pub struct Dma2<'a> { + registers: StaticRef, + clock: DmaClock<'a>, +} + +impl<'a> Dma2<'a> { + pub const fn new(clocks: &'a dyn Stm32f4Clocks) -> Dma2 { + Dma2 { + registers: DMA2_BASE, + clock: DmaClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::AHB1(phclk::HCLK1::DMA2), + clocks, + )), + } + } + + pub fn is_enabled_clock(&self) -> bool { + self.clock.is_enabled() + } + + pub fn enable_clock(&self) { + self.clock.enable(); + } + + pub fn disable_clock(&self) { + self.clock.disable(); + } +} + +impl<'a> StreamServer<'a> for Dma2<'a> { + type Peripheral = Dma2Peripheral; + + fn registers(&self) -> &DmaRegisters { + &self.registers } } diff --git a/chips/stm32f4xx/src/exti.rs b/chips/stm32f4xx/src/exti.rs index c3a9ccb05b..058413a880 100644 --- a/chips/stm32f4xx/src/exti.rs +++ b/chips/stm32f4xx/src/exti.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use cortexm4::support::atomic; use enum_primitive::cast::FromPrimitive; use enum_primitive::enum_from_primitive; @@ -328,21 +332,21 @@ const EXTI_BASE: StaticRef = /// there is *no* one-to-one mapping between the 23 lines to NVIC IRQs. The 23 /// lines going into NVIC translates to 14 IRQs on NVIC. /// -/// - EXTI0 (6) -/// - EXTI1 (7) -/// - EXTI2 (8) -/// - EXTI3 (9) -/// - EXTI4 (10) -/// - EXTI9_5 (23) -/// - EXTI15_10 (40) +/// - `EXTI0` (6) +/// - `EXTI1` (7) +/// - `EXTI2` (8) +/// - `EXTI3` (9) +/// - `EXTI4` (10) +/// - `EXTI9_5` (23) +/// - `EXTI15_10` (40) /// -/// - EXTI16 -> PVD (1) -/// - EXTI17 -> RTC_Alarm (41) -/// - EXTI18 -> OTG_FS_WKUP (42) -/// - EXTI19 -> -/// - EXTI20 -> OTG_FS (67) -/// - EXTI21 -> TAMP_STAMP (2) -/// - EXTI22 -> RTC_WKUP (3) +/// - `EXTI16` -> `PVD` (1) +/// - `EXTI17` -> `RTC_Alarm` (41) +/// - `EXTI18` -> `OTG_FS_WKUP` (42) +/// - `EXTI19` -> `` +/// - `EXTI20` -> `OTG_FS` (67) +/// - `EXTI21` -> `TAMP_STAMP` (2) +/// - `EXTI22` -> `RTC_WKUP` (3) /// /// The EXTI_PR (pending) register when set, generates a level-triggered /// interrupt on the NVIC. This means, that its the responsibility of the IRQ diff --git a/chips/stm32f4xx/src/flash.rs b/chips/stm32f4xx/src/flash.rs new file mode 100644 index 0000000000..f162ef7627 --- /dev/null +++ b/chips/stm32f4xx/src/flash.rs @@ -0,0 +1,508 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright OxidOS Automotive SRL. + +//! STM32F4xx flash driver +//! +//! This driver provides basic functionalities for the entire STM32F4 series. +//! +//! # Features +//! +//! - [x] Configuring latency based on the system clock frequency +//! +//! # Missing features +//! +//! - [ ] Support for different power supplies +//! - [ ] Instruction prefetch +//! - [ ] Instruction and data cache +//! +//! +//! # Usage +//! +//! To use this driver, a reference to the Flash peripheral is required: +//! +//! ```rust,ignore +//! // Inside the board main.rs +//! let flash = &peripherals.stm32f4.flash; +//! ``` +//! +//! ## Retrieve the current flash latency +//! +//! ```rust,ignore +//! let flash_latency = flash.get_latency() as usize; +//! debug!("Current flash latency is {}", flash_latency); +//! ``` + +use crate::chip_specific::flash::FlashChipSpecific as FlashChipSpecificTrait; +use crate::chip_specific::flash::FlashLatency16; +use crate::chip_specific::flash::RegisterToFlashLatency; + +use kernel::debug; +use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; +use kernel::utilities::registers::{register_bitfields, ReadWrite, WriteOnly}; +use kernel::utilities::StaticRef; +use kernel::ErrorCode; + +use core::marker::PhantomData; + +#[repr(C)] +struct FlashRegisters { + /// Flash access control register + acr: ReadWrite, + /// Flash key register + keyr: WriteOnly, + /// Flash option key register + optkeyr: WriteOnly, + /// Status register + sr: ReadWrite, + /// Control register + cr: ReadWrite, + /// Flash option control register + optcr: ReadWrite, + /// Flash option control register 1 + #[cfg(feature = "stm32f429")] + optcr1: ReadWrite, +} + +register_bitfields![u32, + ACR [ + /// Latency + // NOTE: This bit field can be either 3 or 4 bits long + LATENCY OFFSET(0) NUMBITS(4) [], + /// Prefetch enable + PRFTEN OFFSET(8) NUMBITS(1) [], + /// Instruction cache enable + ICEN OFFSET(9) NUMBITS(1) [], + /// Data cache enable + DCEN OFFSET(10) NUMBITS(1) [], + /// Instruction cache reset + ICRST OFFSET(11) NUMBITS(1) [], + /// Data cache reset + DCRST OFFSET(12) NUMBITS(1) [] + ], + KEYR [ + /// FPEC key + KEY OFFSET(0) NUMBITS(32) [] + ], + OPTKEYR [ + /// Option byte key + OPTKEY OFFSET(0) NUMBITS(32) [] + ], + SR [ + /// End of operation + EOP OFFSET(0) NUMBITS(1) [], + /// Operation error + OPERR OFFSET(1) NUMBITS(1) [], + /// Write protection error + WRPERR OFFSET(4) NUMBITS(1) [], + /// Programming alignment error + PGAERR OFFSET(5) NUMBITS(1) [], + /// Programming parallelism error + PGPERR OFFSET(6) NUMBITS(1) [], + /// Programming sequence error + PGSERR OFFSET(7) NUMBITS(1) [], + /// Read protection error + // NOTE: This bit field is not available on STM32F405, STM32F415, STM32F407 and STM32F417 + RDERR OFFSET(8) NUMBITS(1) [], + /// Busy + BSY OFFSET(16) NUMBITS(1) [] + ], + CR [ + /// Programming + PG OFFSET(0) NUMBITS(1) [], + /// Sector Erase + SER OFFSET(1) NUMBITS(1) [], + /// Mass Erase of sectors 0 to 11 + MER OFFSET(2) NUMBITS(1) [], + /// Sector number + // NOTE: This bit field can be either 4 or 5 bits long depending on the chip model + SNB OFFSET(3) NUMBITS(5) [], + /// Program size + PSIZE OFFSET(8) NUMBITS(2) [], + /// Mass Erase of sectors 12 to 23 + // NOTE: This bit is not available on all chip models + MER1 OFFSET(15) NUMBITS(1) [], + /// Start + STRT OFFSET(16) NUMBITS(1) [], + /// End of operation interrupt enable + EOPIE OFFSET(24) NUMBITS(1) [], + /// Error interrupt enable + ERRIE OFFSET(25) NUMBITS(1) [], + /// Lock + LOCK OFFSET(31) NUMBITS(1) [] + ], + OPTCR [ + /// Option lock + OPTLOCK OFFSET(0) NUMBITS(1) [], + /// Option start + OPTSTRT OFFSET(1) NUMBITS(1) [], + /// BOR reset Level + BOR_LEV OFFSET(2) NUMBITS(2) [], + /// WDG_SW User option bytes + WDG_SW OFFSET(5) NUMBITS(1) [], + /// nRST_STOP User option bytes + nRST_STOP OFFSET(6) NUMBITS(1) [], + /// nRST_STDBY User option bytes + nRST_STDBY OFFSET(7) NUMBITS(1) [], + /// Read protect + RDP OFFSET(8) NUMBITS(8) [], + /// Not write protect + // NOTE: The length of this bit field varies with the chip model + nWRP OFFSET(16) NUMBITS(12) [] + ], + OPTCR1 [ + /// Not write protect + // NOTE: The length of this bit field varies with the chip model + nWRP OFFSET(16) NUMBITS(12) [] + ] +]; + +// All chips models have the same FLASH_BASE +const FLASH_BASE: StaticRef = + unsafe { StaticRef::new(0x40023C00 as *const FlashRegisters) }; + +/// Main Flash struct +pub struct Flash { + registers: StaticRef, + _marker: PhantomData, +} + +impl Flash { + // Flash constructor. It should be called when creating Stm32f4xxDefaultPeripherals. + pub(crate) fn new() -> Self { + Self { + registers: FLASH_BASE, + _marker: PhantomData, + } + } + + fn read_latency_from_register(&self) -> u32 { + self.registers.acr.read(ACR::LATENCY) + } + + // TODO: Take into the account the power supply + // + // NOTE: This method is pub(crate) to prevent modifying the flash latency from board files. + // Flash latency is dependent on the system clock frequency. Other peripherals will modify this + // when appropriate. + pub(crate) fn set_latency(&self, sys_clock_frequency: usize) -> Result<(), ErrorCode> { + let flash_latency = + FlashChipSpecific::get_number_wait_cycles_based_on_frequency(sys_clock_frequency); + self.registers + .acr + .modify(ACR::LATENCY.val(flash_latency.into())); + + // Wait until the flash latency is set + // The value 16 was chosen randomly, but it behaves well in tests. It can be tuned in a + // future revision of the driver. + for _ in 0..16 { + if self.get_latency() == flash_latency { + return Ok(()); + } + } + + // Return BUSY if setting the frequency took too long. The caller can either: + // + // + recall this method + // + or busy wait get_latency() until the flash latency has the desired value + Err(ErrorCode::BUSY) + } + + pub(crate) fn get_latency(&self) -> FlashChipSpecific::FlashLatency { + FlashChipSpecific::FlashLatency::convert_register_to_enum(self.read_latency_from_register()) + } +} + +/// Tests for the STM32F4xx flash driver. +/// +/// If any contributions are made to this driver, it is highly recommended to run these tests to +/// ensure that everything still works as expected. The tests are chip agnostic. They can be run +/// on any STM32F4 chips. +/// +/// # Usage +/// +/// First, the flash module must be imported: +/// +/// ```rust,ignore +/// // Change this line depending on the chip the board is using +/// use stm32f429zi::flash; +/// ``` +/// +/// Then, get a reference to the peripheral: +/// +/// ```rust,ignore +/// // Inside the board main.rs +/// let flash = &peripherals.stm32f4.flash; +/// ``` +/// +/// To run all tests: +/// +/// ```rust,ignore +/// flash::tests::run_all(flash); +/// ``` +/// +/// The following output should be printed: +/// +/// ```text +/// =============================================== +/// Testing setting flash latency... +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// Testing number of wait cycles based on the system frequency... +/// Finished testing number of wait cycles based on the system clock frequency. Everything is alright! +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// Testing setting flash latency... +/// Finished testing setting flash latency. Everything is alright! +/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +/// +/// Finished testing flash. Everything is alright! +/// =============================================== +/// ``` +/// +/// To run individual tests, see the functions in this module. +/// +/// # Errors +/// +/// In case of any errors, open an issue ticket at . Please provide +/// the output of the test execution. +pub mod tests { + use super::*; + use crate::clocks::hsi::HSI_FREQUENCY_MHZ; + + const AHB_ETHERNET_MINIMUM_FREQUENCY_MHZ: usize = 25; + // Different chips have different maximum values for APB1 + const APB1_MAX_FREQUENCY_MHZ_1: usize = 42; + const APB1_MAX_FREQUENCY_MHZ_2: usize = 45; + const APB1_MAX_FREQUENCY_MHZ_3: usize = 50; + // Different chips have different maximum values for APB2 + const APB2_MAX_FREQUENCY_MHZ_1: usize = 84; + #[cfg(not(feature = "stm32f401"))] // Not needed for this chip model + const APB2_MAX_FREQUENCY_MHZ_2: usize = 90; + #[cfg(not(feature = "stm32f401"))] // Not needed for this chip model + const APB2_MAX_FREQUENCY_MHZ_3: usize = 100; + // Many STM32F4 chips allow a maximum frequency of 168MHz and some of them 180MHz if overdrive + // is turned on + #[cfg(not(any(feature = "stm32f401", feature = "stm32f412",)))] // Not needed for these chips + const SYS_MAX_FREQUENCY_NO_OVERDRIVE_MHZ: usize = 168; + #[cfg(not(any(feature = "stm32f401", feature = "stm32f412",)))] // Not needed for these chips + const SYS_MAX_FREQUENCY_OVERDRIVE_MHZ: usize = 180; + // Default PLL frequency + #[cfg(not(feature = "stm32f401"))] // Not needed for this chip model + const PLL_FREQUENCY_MHZ: usize = 96; + + //#[cfg(any( + //feature = "stm32f401", + //feature = "stm32f412", + //feature = "stm32f429", + //feature = "stm32f446" + //))] + /// Test for the mapping between the system clock frequency and flash latency + /// + /// It is highly recommended to run this test since everything else depends on it. + pub fn test_get_number_wait_cycles_based_on_frequency< + FlashChipSpecific: FlashChipSpecificTrait, + >() { + debug!(""); + debug!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + debug!("Testing number of wait cycles based on the system frequency..."); + + assert_eq!( + FlashChipSpecific::FlashLatency::Latency0, + FlashChipSpecific::get_number_wait_cycles_based_on_frequency(HSI_FREQUENCY_MHZ) + ); + + assert_eq!( + FlashChipSpecific::FlashLatency::Latency0, + FlashChipSpecific::get_number_wait_cycles_based_on_frequency( + AHB_ETHERNET_MINIMUM_FREQUENCY_MHZ + ) + ); + + assert_eq!( + FlashChipSpecific::FlashLatency::Latency1, + FlashChipSpecific::get_number_wait_cycles_based_on_frequency(APB1_MAX_FREQUENCY_MHZ_1) + ); + assert_eq!( + FlashChipSpecific::FlashLatency::Latency1, + FlashChipSpecific::get_number_wait_cycles_based_on_frequency(APB1_MAX_FREQUENCY_MHZ_2) + ); + assert_eq!( + FlashChipSpecific::FlashLatency::Latency1, + FlashChipSpecific::get_number_wait_cycles_based_on_frequency(APB1_MAX_FREQUENCY_MHZ_3) + ); + + assert_eq!( + FlashChipSpecific::FlashLatency::Latency2, + FlashChipSpecific::get_number_wait_cycles_based_on_frequency(APB2_MAX_FREQUENCY_MHZ_1) + ); + + // STM32F401 maximum clock frequency is 84MHz + #[cfg(not(feature = "stm32f401"))] + { + assert_eq!( + FlashChipSpecific::FlashLatency::Latency2, + FlashChipSpecific::get_number_wait_cycles_based_on_frequency( + APB2_MAX_FREQUENCY_MHZ_2 + ) + ); + + assert_eq!( + FlashChipSpecific::FlashLatency::Latency3, + FlashChipSpecific::get_number_wait_cycles_based_on_frequency( + APB2_MAX_FREQUENCY_MHZ_3 + ) + ); + + assert_eq!( + FlashChipSpecific::FlashLatency::Latency3, + FlashChipSpecific::get_number_wait_cycles_based_on_frequency(PLL_FREQUENCY_MHZ) + ); + } + + #[cfg(not(any(feature = "stm32f401", feature = "stm32f412",)))] + // Not needed for these chips + { + assert_eq!( + FlashChipSpecific::FlashLatency::Latency5, + FlashChipSpecific::get_number_wait_cycles_based_on_frequency( + SYS_MAX_FREQUENCY_NO_OVERDRIVE_MHZ + ) + ); + + assert_eq!( + FlashChipSpecific::FlashLatency::Latency5, + FlashChipSpecific::get_number_wait_cycles_based_on_frequency( + SYS_MAX_FREQUENCY_OVERDRIVE_MHZ + ) + ); + } + + debug!("Finished testing number of wait cycles based on the system clock frequency. Everything is alright!"); + debug!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + debug!(""); + } + + /// Test for the set_flash() method + /// + /// If there is no error, the following output will be printed on the console: + /// + /// ```text + /// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + /// Testing setting flash latency... + /// Finished testing setting flash latency. Everything is alright! + /// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + /// ``` + pub fn test_set_flash_latency< + FlashChipSpecific: FlashChipSpecificTrait, + >( + flash: &Flash, + ) { + debug!(""); + debug!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + debug!("Testing setting flash latency..."); + + assert_eq!(Ok(()), flash.set_latency(HSI_FREQUENCY_MHZ)); + assert_eq!( + FlashChipSpecific::FlashLatency::Latency0, + flash.get_latency() + ); + + assert_eq!( + Ok(()), + flash.set_latency(AHB_ETHERNET_MINIMUM_FREQUENCY_MHZ) + ); + assert_eq!( + FlashChipSpecific::FlashLatency::Latency0, + flash.get_latency() + ); + + assert_eq!(Ok(()), flash.set_latency(APB1_MAX_FREQUENCY_MHZ_1)); + assert_eq!( + FlashChipSpecific::FlashLatency::Latency1, + flash.get_latency() + ); + + assert_eq!(Ok(()), flash.set_latency(APB1_MAX_FREQUENCY_MHZ_2)); + assert_eq!( + FlashChipSpecific::FlashLatency::Latency1, + flash.get_latency() + ); + + assert_eq!(Ok(()), flash.set_latency(APB1_MAX_FREQUENCY_MHZ_3)); + assert_eq!( + FlashChipSpecific::FlashLatency::Latency1, + flash.get_latency() + ); + + assert_eq!(Ok(()), flash.set_latency(APB2_MAX_FREQUENCY_MHZ_1)); + + // STM32F401 maximum system clock frequency is 84MHz + #[cfg(not(feature = "stm32f401"))] + { + assert_eq!(Ok(()), flash.set_latency(APB2_MAX_FREQUENCY_MHZ_2)); + + assert_eq!(Ok(()), flash.set_latency(APB2_MAX_FREQUENCY_MHZ_3)); + assert_eq!( + FlashChipSpecific::FlashLatency::Latency3, + flash.get_latency() + ); + + assert_eq!(Ok(()), flash.set_latency(PLL_FREQUENCY_MHZ)); + assert_eq!( + FlashChipSpecific::FlashLatency::Latency3, + flash.get_latency() + ); + } + + // Low entries STM32F4 chips don't support frequencies higher than 100 MHz, + // but the foundation and advanced ones support system clock frequencies up to + // 180MHz + #[cfg(not(any(feature = "stm32f401", feature = "stm32f412",)))] + { + assert_eq!( + Ok(()), + flash.set_latency(SYS_MAX_FREQUENCY_NO_OVERDRIVE_MHZ) + ); + assert_eq!( + FlashChipSpecific::FlashLatency::Latency5, + flash.get_latency() + ); + + assert_eq!(Ok(()), flash.set_latency(SYS_MAX_FREQUENCY_OVERDRIVE_MHZ)); + assert_eq!( + FlashChipSpecific::FlashLatency::Latency5, + flash.get_latency() + ); + } + + // Revert to default settings + assert_eq!(Ok(()), flash.set_latency(HSI_FREQUENCY_MHZ)); + assert_eq!( + FlashChipSpecific::FlashLatency::Latency0, + flash.get_latency() + ); + + debug!("Finished testing setting flash latency. Everything is alright!"); + debug!("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"); + debug!(""); + } + + /// Run the entire test suite + pub fn run_all>( + flash: &Flash, + ) { + debug!(""); + debug!("==============================================="); + debug!("Testing setting flash latency..."); + + test_get_number_wait_cycles_based_on_frequency::(); + test_set_flash_latency::(flash); + + debug!("Finished testing flash. Everything is alright!"); + debug!("==============================================="); + debug!(""); + } +} diff --git a/chips/stm32f4xx/src/fsmc.rs b/chips/stm32f4xx/src/fsmc.rs index ee4775b295..2d3dc44b5b 100644 --- a/chips/stm32f4xx/src/fsmc.rs +++ b/chips/stm32f4xx/src/fsmc.rs @@ -1,16 +1,18 @@ -use crate::rcc; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use crate::clocks::{phclk, Stm32f4Clocks}; use core::cell::Cell; -use kernel::deferred_call::DeferredCall; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; use kernel::hil::bus8080::{Bus8080, BusWidth, Client}; use kernel::platform::chip::ClockInterface; use kernel::utilities::cells::{OptionalCell, TakeCell}; -use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; +use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; use kernel::utilities::registers::{register_bitfields, ReadWrite}; use kernel::utilities::StaticRef; use kernel::ErrorCode; -use crate::deferred_calls::DeferredCallTask; - /// FSMC peripheral interface #[repr(C)] struct FsmcBankRegisters { @@ -128,11 +130,6 @@ register_bitfields![u32, ] ]; -/// This mechanism allows us to schedule "interrupts" even if the hardware -/// does not support them. -static DEFERRED_CALL: DeferredCall = - unsafe { DeferredCall::new(DeferredCallTask::Fsmc) }; - const FSMC_BASE: StaticRef = unsafe { StaticRef::new(0xA000_0000 as *const FsmcBankRegisters) }; @@ -170,22 +167,26 @@ pub struct Fsmc<'a> { buffer: TakeCell<'static, [u8]>, bus_width: Cell, len: Cell, + + deferred_call: DeferredCall, } impl<'a> Fsmc<'a> { - pub const fn new(bank_addr: [Option>; 4], rcc: &'a rcc::Rcc) -> Self { + pub fn new(bank_addr: [Option>; 4], clocks: &'a dyn Stm32f4Clocks) -> Self { Self { registers: FSMC_BASE, bank: bank_addr, - clock: FsmcClock(rcc::PeripheralClock::new( - rcc::PeripheralClockType::AHB3(rcc::HCLK3::FMC), - rcc, + clock: FsmcClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::AHB3(phclk::HCLK3::FMC), + clocks, )), client: OptionalCell::empty(), buffer: TakeCell::empty(), bus_width: Cell::new(1), len: Cell::new(0), + + deferred_call: DeferredCall::new(), } } @@ -238,46 +239,66 @@ impl<'a> Fsmc<'a> { self.clock.disable(); } - pub fn handle_interrupt(&self) { - self.buffer.take().map_or_else( - || { - self.client.map(move |client| { - client.command_complete(None, 0, Ok(())); - }); - }, - |buffer| { - self.client.map(move |client| { - client.command_complete(Some(buffer), self.len.get(), Ok(())); - }); - }, - ); - } - #[inline] pub fn read_reg(&self, bank: FsmcBanks) -> Option { self.bank[bank as usize].map_or(None, |bank| Some(bank.ram.get())) } + #[cfg(all(target_arch = "arm", target_os = "none"))] #[inline] fn write_reg(&self, bank: FsmcBanks, addr: u16) { + use kernel::utilities::registers::interfaces::Writeable; self.bank[bank as usize].map(|bank| bank.reg.set(addr)); - #[cfg(all(target_arch = "arm", target_os = "none"))] unsafe { + use core::arch::asm; asm!("dsb 0xf"); } } + #[cfg(all(target_arch = "arm", target_os = "none"))] #[inline] fn write_data(&self, bank: FsmcBanks, data: u16) { + use kernel::utilities::registers::interfaces::Writeable; self.bank[bank as usize].map(|bank| bank.ram.set(data)); - #[cfg(all(target_arch = "arm", target_os = "none"))] unsafe { + use core::arch::asm; asm!("dsb 0xf"); } } + + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + fn write_reg(&self, _bank: FsmcBanks, _addr: u16) { + unimplemented!() + } + + #[cfg(not(all(target_arch = "arm", target_os = "none")))] + fn write_data(&self, _bank: FsmcBanks, _data: u16) { + unimplemented!() + } } -struct FsmcClock<'a>(rcc::PeripheralClock<'a>); +impl DeferredCallClient for Fsmc<'_> { + fn register(&'static self) { + self.deferred_call.register(self); + } + + fn handle_deferred_call(&self) { + self.buffer.take().map_or_else( + || { + self.client.map(move |client| { + client.command_complete(None, 0, Ok(())); + }); + }, + |buffer| { + self.client.map(move |client| { + client.command_complete(Some(buffer), self.len.get(), Ok(())); + }); + }, + ); + } +} + +struct FsmcClock<'a>(phclk::PeripheralClock<'a>); impl ClockInterface for FsmcClock<'_> { fn is_enabled(&self) -> bool { @@ -298,7 +319,7 @@ impl Bus8080<'static> for Fsmc<'_> { match addr_width { BusWidth::Bits8 => { self.write_reg(FsmcBanks::Bank1, addr as u16); - DEFERRED_CALL.set(); + self.deferred_call.set(); Ok(()) } _ => Err(ErrorCode::NOSUPPORT), @@ -316,20 +337,19 @@ impl Bus8080<'static> for Fsmc<'_> { for pos in 0..len { let mut data: u16 = 0; for byte in 0..bytes { - data = data - | (buffer[bytes * pos - + match data_width { - BusWidth::Bits8 | BusWidth::Bits16LE => byte, - BusWidth::Bits16BE => (bytes - byte - 1), - }] as u16) - << (8 * byte); + data |= (buffer[bytes * pos + + match data_width { + BusWidth::Bits8 | BusWidth::Bits16LE => byte, + BusWidth::Bits16BE => bytes - byte - 1, + }] as u16) + << (8 * byte); } self.write_data(FsmcBanks::Bank1, data); } self.buffer.replace(buffer); self.bus_width.set(bytes); self.len.set(len); - DEFERRED_CALL.set(); + self.deferred_call.set(); Ok(()) } else { Err((ErrorCode::NOMEM, buffer)) @@ -350,7 +370,7 @@ impl Bus8080<'static> for Fsmc<'_> { buffer[bytes * pos + match data_width { BusWidth::Bits8 | BusWidth::Bits16LE => byte, - BusWidth::Bits16BE => (bytes - byte - 1), + BusWidth::Bits16BE => bytes - byte - 1, }] = (data >> (8 * byte)) as u8; } } else { @@ -360,7 +380,7 @@ impl Bus8080<'static> for Fsmc<'_> { self.buffer.replace(buffer); self.bus_width.set(bytes); self.len.set(len); - DEFERRED_CALL.set(); + self.deferred_call.set(); Ok(()) } else { Err((ErrorCode::NOMEM, buffer)) diff --git a/chips/stm32f4xx/src/gpio.rs b/chips/stm32f4xx/src/gpio.rs index 6be26257fc..6aa571ec8b 100644 --- a/chips/stm32f4xx/src/gpio.rs +++ b/chips/stm32f4xx/src/gpio.rs @@ -1,4 +1,7 @@ -use cortexm4; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use cortexm4::support::atomic; use enum_primitive::cast::FromPrimitive; use enum_primitive::enum_from_primitive; @@ -9,8 +12,8 @@ use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeabl use kernel::utilities::registers::{register_bitfields, ReadOnly, ReadWrite, WriteOnly}; use kernel::utilities::StaticRef; +use crate::clocks::{phclk, Stm32f4Clocks}; use crate::exti::{self, LineId}; -use crate::rcc; /// General-purpose I/Os #[repr(C)] @@ -507,7 +510,7 @@ impl PinId { pub fn get_pin_number(&self) -> u8 { let mut pin_num = *self as u8; - pin_num = pin_num & 0b00001111; + pin_num &= 0b00001111; pin_num } @@ -605,63 +608,63 @@ pub struct GpioPorts<'a> { } impl<'a> GpioPorts<'a> { - pub fn new(rcc: &'a rcc::Rcc, exti: &'a exti::Exti<'a>) -> Self { + pub fn new(clocks: &'a dyn Stm32f4Clocks, exti: &'a exti::Exti<'a>) -> Self { Self { ports: [ Port { registers: GPIOA_BASE, - clock: PortClock(rcc::PeripheralClock::new( - rcc::PeripheralClockType::AHB1(rcc::HCLK1::GPIOA), - rcc, + clock: PortClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::AHB1(phclk::HCLK1::GPIOA), + clocks, )), }, Port { registers: GPIOB_BASE, - clock: PortClock(rcc::PeripheralClock::new( - rcc::PeripheralClockType::AHB1(rcc::HCLK1::GPIOB), - rcc, + clock: PortClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::AHB1(phclk::HCLK1::GPIOB), + clocks, )), }, Port { registers: GPIOC_BASE, - clock: PortClock(rcc::PeripheralClock::new( - rcc::PeripheralClockType::AHB1(rcc::HCLK1::GPIOC), - rcc, + clock: PortClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::AHB1(phclk::HCLK1::GPIOC), + clocks, )), }, Port { registers: GPIOD_BASE, - clock: PortClock(rcc::PeripheralClock::new( - rcc::PeripheralClockType::AHB1(rcc::HCLK1::GPIOD), - rcc, + clock: PortClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::AHB1(phclk::HCLK1::GPIOD), + clocks, )), }, Port { registers: GPIOE_BASE, - clock: PortClock(rcc::PeripheralClock::new( - rcc::PeripheralClockType::AHB1(rcc::HCLK1::GPIOE), - rcc, + clock: PortClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::AHB1(phclk::HCLK1::GPIOE), + clocks, )), }, Port { registers: GPIOF_BASE, - clock: PortClock(rcc::PeripheralClock::new( - rcc::PeripheralClockType::AHB1(rcc::HCLK1::GPIOF), - rcc, + clock: PortClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::AHB1(phclk::HCLK1::GPIOF), + clocks, )), }, Port { registers: GPIOG_BASE, - clock: PortClock(rcc::PeripheralClock::new( - rcc::PeripheralClockType::AHB1(rcc::HCLK1::GPIOG), - rcc, + clock: PortClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::AHB1(phclk::HCLK1::GPIOG), + clocks, )), }, Port { registers: GPIOH_BASE, - clock: PortClock(rcc::PeripheralClock::new( - rcc::PeripheralClockType::AHB1(rcc::HCLK1::GPIOH), - rcc, + clock: PortClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::AHB1(phclk::HCLK1::GPIOH), + clocks, )), }, ], @@ -739,7 +742,7 @@ impl Port<'_> { } } -struct PortClock<'a>(rcc::PeripheralClock<'a>); +struct PortClock<'a>(phclk::PeripheralClock<'a>); impl ClockInterface for PortClock<'_> { fn is_enabled(&self) -> bool { @@ -866,9 +869,9 @@ impl<'a> Pin<'a> { } pub unsafe fn enable_interrupt(&'static self) { - let exti_line_id = LineId::from_u8(self.pinid.get_pin_number() as u8).unwrap(); + let exti_line_id = LineId::from_u8(self.pinid.get_pin_number()).unwrap(); - self.exti.associate_line_gpiopin(exti_line_id, &self); + self.exti.associate_line_gpiopin(exti_line_id, self); } pub fn set_exti_lineid(&self, lineid: exti::LineId) { @@ -1201,7 +1204,7 @@ impl<'a> hil::gpio::Interrupt<'a> for Pin<'a> { unsafe { atomic(|| { self.exti_lineid.map(|lineid| { - let l = lineid.clone(); + let l = lineid; // disable the interrupt self.exti.mask_interrupt(l); @@ -1232,7 +1235,7 @@ impl<'a> hil::gpio::Interrupt<'a> for Pin<'a> { unsafe { atomic(|| { self.exti_lineid.map(|lineid| { - let l = lineid.clone(); + let l = lineid; self.exti.mask_interrupt(l); self.exti.clear_pending(l); }); @@ -1246,6 +1249,6 @@ impl<'a> hil::gpio::Interrupt<'a> for Pin<'a> { fn is_pending(&self) -> bool { self.exti_lineid - .map_or(false, |&mut lineid| self.exti.is_pending(lineid)) + .map_or(false, |lineid| self.exti.is_pending(lineid)) } } diff --git a/chips/stm32f4xx/src/i2c.rs b/chips/stm32f4xx/src/i2c.rs index ee7299e992..a1bfd7d8ff 100644 --- a/chips/stm32f4xx/src/i2c.rs +++ b/chips/stm32f4xx/src/i2c.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::cell::Cell; use kernel::hil; @@ -8,7 +12,7 @@ use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeabl use kernel::utilities::registers::{register_bitfields, ReadWrite}; use kernel::utilities::StaticRef; -use crate::rcc; +use crate::clocks::{phclk, Stm32f4Clocks}; pub enum I2CSpeed { Speed100k, @@ -187,10 +191,10 @@ pub struct I2C<'a> { master_client: OptionalCell<&'a dyn hil::i2c::I2CHwMasterClient>, buffer: TakeCell<'static, [u8]>, - tx_position: Cell, - rx_position: Cell, - tx_len: Cell, - rx_len: Cell, + tx_position: Cell, + rx_position: Cell, + tx_len: Cell, + rx_len: Cell, slave_address: Cell, @@ -206,12 +210,12 @@ enum I2CStatus { } impl<'a> I2C<'a> { - pub const fn new(rcc: &'a rcc::Rcc) -> Self { + pub fn new(clocks: &'a dyn Stm32f4Clocks) -> Self { Self { registers: I2C1_BASE, - clock: I2CClock(rcc::PeripheralClock::new( - rcc::PeripheralClockType::APB1(rcc::PCLK1::I2C1), - rcc, + clock: I2CClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::APB1(phclk::PCLK1::I2C1), + clocks, )), master_client: OptionalCell::empty(), @@ -286,7 +290,7 @@ impl<'a> I2C<'a> { // send the next byte if self.buffer.is_some() && self.tx_position.get() < self.tx_len.get() { self.buffer.map(|buf| { - let byte = buf[self.tx_position.get() as usize]; + let byte = buf[self.tx_position.get()]; self.registers.dr.write(DR::DR.val(byte as u32)); self.tx_position.set(self.tx_position.get() + 1); }); @@ -298,7 +302,7 @@ impl<'a> I2C<'a> { let byte = self.registers.dr.read(DR::DR); if self.buffer.is_some() && self.rx_position.get() < self.rx_len.get() { self.buffer.map(|buf| { - buf[self.rx_position.get() as usize] = byte as u8; + buf[self.rx_position.get()] = byte as u8; self.rx_position.set(self.rx_position.get() + 1); }); } @@ -400,8 +404,8 @@ impl<'a> I2C<'a> { } } -impl i2c::I2CMaster for I2C<'_> { - fn set_master_client(&self, master_client: &'static dyn I2CHwMasterClient) { +impl<'a> i2c::I2CMaster<'a> for I2C<'a> { + fn set_master_client(&self, master_client: &'a dyn I2CHwMasterClient) { self.master_client.replace(master_client); } fn enable(&self) { @@ -414,8 +418,8 @@ impl i2c::I2CMaster for I2C<'_> { &self, addr: u8, data: &'static mut [u8], - write_len: u8, - read_len: u8, + write_len: usize, + read_len: usize, ) -> Result<(), (Error, &'static mut [u8])> { if self.status.get() == I2CStatus::Idle { self.reset(); @@ -434,7 +438,7 @@ impl i2c::I2CMaster for I2C<'_> { &self, addr: u8, data: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (Error, &'static mut [u8])> { if self.status.get() == I2CStatus::Idle { self.reset(); @@ -452,7 +456,7 @@ impl i2c::I2CMaster for I2C<'_> { &self, addr: u8, buffer: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (Error, &'static mut [u8])> { if self.status.get() == I2CStatus::Idle { self.reset(); @@ -468,7 +472,7 @@ impl i2c::I2CMaster for I2C<'_> { } } -struct I2CClock<'a>(rcc::PeripheralClock<'a>); +struct I2CClock<'a>(phclk::PeripheralClock<'a>); impl ClockInterface for I2CClock<'_> { fn is_enabled(&self) -> bool { diff --git a/chips/stm32f4xx/src/lib.rs b/chips/stm32f4xx/src/lib.rs index 3fbca4387f..5b417afd48 100644 --- a/chips/stm32f4xx/src/lib.rs +++ b/chips/stm32f4xx/src/lib.rs @@ -1,22 +1,27 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Peripheral implementations for the STM32F4xx MCU. //! //! STM32F446RE: #![crate_name = "stm32f4xx"] #![crate_type = "rlib"] -#![feature(const_fn_trait_bound)] -#![feature(asm)] #![no_std] pub mod chip; +pub mod chip_specific; pub mod nvic; // Peripherals pub mod adc; +pub mod can; +pub mod dac; pub mod dbg; -pub mod deferred_calls; -pub mod dma1; +pub mod dma; pub mod exti; +pub mod flash; pub mod fsmc; pub mod gpio; pub mod i2c; @@ -27,10 +32,10 @@ pub mod tim2; pub mod trng; pub mod usart; -use cortexm4::{ - hard_fault_handler, initialize_ram_jump_to_main, svc_handler, systick_handler, - unhandled_interrupt, -}; +// Clocks +pub mod clocks; + +use cortexm4::{initialize_ram_jump_to_main, unhandled_interrupt, CortexM4, CortexMVariant}; extern "C" { // _estack is not really a function, but it makes the types work @@ -47,20 +52,20 @@ extern "C" { pub static BASE_VECTORS: [unsafe extern "C" fn(); 16] = [ _estack, initialize_ram_jump_to_main, - unhandled_interrupt, // NMI - hard_fault_handler, // Hard Fault - unhandled_interrupt, // MemManage - unhandled_interrupt, // BusFault - unhandled_interrupt, // UsageFault + unhandled_interrupt, // NMI + CortexM4::HARD_FAULT_HANDLER, // Hard Fault + unhandled_interrupt, // MemManage + unhandled_interrupt, // BusFault + unhandled_interrupt, // UsageFault unhandled_interrupt, unhandled_interrupt, unhandled_interrupt, unhandled_interrupt, - svc_handler, // SVC - unhandled_interrupt, // DebugMon + CortexM4::SVC_HANDLER, // SVC + unhandled_interrupt, // DebugMon unhandled_interrupt, - unhandled_interrupt, // PendSV - systick_handler, // SysTick + unhandled_interrupt, // PendSV + CortexM4::SYSTICK_HANDLER, // SysTick ]; pub unsafe fn init() { diff --git a/chips/stm32f4xx/src/nvic.rs b/chips/stm32f4xx/src/nvic.rs index a12cca42a4..a4c6f46528 100644 --- a/chips/stm32f4xx/src/nvic.rs +++ b/chips/stm32f4xx/src/nvic.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Named constants for NVIC ids shared across the stm32f4xx family of chips #![allow(non_upper_case_globals)] diff --git a/chips/stm32f4xx/src/rcc.rs b/chips/stm32f4xx/src/rcc.rs index e8ac03b0c6..5059ac44f5 100644 --- a/chips/stm32f4xx/src/rcc.rs +++ b/chips/stm32f4xx/src/rcc.rs @@ -1,4 +1,7 @@ -use kernel::platform::chip::ClockInterface; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; use kernel::utilities::registers::{register_bitfields, ReadWrite}; use kernel::utilities::StaticRef; @@ -100,41 +103,21 @@ register_bitfields![u32, /// Main PLL (PLL) division factor for USB OTG FS, SDIO and random num PLLQ OFFSET(24) NUMBITS(4) [], /// Main PLL(PLL) and audio PLL (PLLI2S) entry clock source - PLLSRC OFFSET(22) NUMBITS(1) [], - /// Main PLL (PLL) division factor for main system clock - PLLP1 OFFSET(17) NUMBITS(1) [], + PLLSRC OFFSET(22) NUMBITS(1) [ + HSI = 0, + HSE = 1, + ], /// Main PLL (PLL) division factor for main system clock - PLLP0 OFFSET(16) NUMBITS(1) [], - /// Main PLL (PLL) multiplication factor for VCO - PLLN8 OFFSET(14) NUMBITS(1) [], - /// Main PLL (PLL) multiplication factor for VCO - PLLN7 OFFSET(13) NUMBITS(1) [], - /// Main PLL (PLL) multiplication factor for VCO - PLLN6 OFFSET(12) NUMBITS(1) [], - /// Main PLL (PLL) multiplication factor for VCO - PLLN5 OFFSET(11) NUMBITS(1) [], - /// Main PLL (PLL) multiplication factor for VCO - PLLN4 OFFSET(10) NUMBITS(1) [], - /// Main PLL (PLL) multiplication factor for VCO - PLLN3 OFFSET(9) NUMBITS(1) [], - /// Main PLL (PLL) multiplication factor for VCO - PLLN2 OFFSET(8) NUMBITS(1) [], - /// Main PLL (PLL) multiplication factor for VCO - PLLN1 OFFSET(7) NUMBITS(1) [], + PLLP OFFSET(16) NUMBITS(2) [ + DivideBy2 = 0b00, + DivideBy4 = 0b01, + DivideBy6 = 0b10, + DivideBy8 = 0b11, + ], /// Main PLL (PLL) multiplication factor for VCO - PLLN0 OFFSET(6) NUMBITS(1) [], - /// Division factor for the main PLL (PLL) and audio PLL (PLLI2S) inpu - PLLM5 OFFSET(5) NUMBITS(1) [], - /// Division factor for the main PLL (PLL) and audio PLL (PLLI2S) inpu - PLLM4 OFFSET(4) NUMBITS(1) [], - /// Division factor for the main PLL (PLL) and audio PLL (PLLI2S) inpu - PLLM3 OFFSET(3) NUMBITS(1) [], - /// Division factor for the main PLL (PLL) and audio PLL (PLLI2S) inpu - PLLM2 OFFSET(2) NUMBITS(1) [], - /// Division factor for the main PLL (PLL) and audio PLL (PLLI2S) inpu - PLLM1 OFFSET(1) NUMBITS(1) [], - /// Division factor for the main PLL (PLL) and audio PLL (PLLI2S) inpu - PLLM0 OFFSET(0) NUMBITS(1) [] + PLLN OFFSET(6) NUMBITS(9) [], + /// Division factor for the main PLL (PLL) and audio PLL (PLLI2S) input + PLLM OFFSET(0) NUMBITS(6) [] ], CFGR [ /// Microcontroller clock output 2 @@ -156,13 +139,13 @@ register_bitfields![u32, /// AHB prescaler HPRE OFFSET(4) NUMBITS(4) [], /// System clock switch status - SWS1 OFFSET(3) NUMBITS(1) [], - /// System clock switch status - SWS0 OFFSET(2) NUMBITS(1) [], - /// System clock switch - SW1 OFFSET(1) NUMBITS(1) [], + SWS OFFSET(2) NUMBITS(2) [], /// System clock switch - SW0 OFFSET(0) NUMBITS(1) [] + SW OFFSET(0) NUMBITS(2) [ + HSI = 0b00, + HSE = 0b01, + PLL = 0b10, + ] ], CIR [ /// Clock security system interrupt clear @@ -723,534 +706,830 @@ register_bitfields![u32, const RCC_BASE: StaticRef = unsafe { StaticRef::new(0x40023800 as *const RccRegisters) }; +// Default values when the hardware is reset. Uncomment if you need them. +//pub(crate) const RESET_PLLM_VALUE: usize = PLLM::DivideBy16; // M = 16 +//pub(crate) const RESET_PLLP_VALUE: PLLP = PLLP::DivideBy2; // P = 2 +//pub(crate) const RESET_PLLQ_VALUE: PLLQ = PLLQ::DivideBy4; // Q = 4 +pub(crate) const RESET_PLLN_VALUE: usize = 0b011_000_000; // N = 192 + +// Default PLL configuration. See Rcc::init_pll_clock() for more details. +// +// Choose PLLM::DivideBy8 for reduced PLL jitter or PLLM::DivideBy16 for default hardware +// configuration +pub(crate) const DEFAULT_PLLM_VALUE: PLLM = PLLM::DivideBy8; +// DON'T CHANGE THIS VALUE +pub(crate) const DEFAULT_PLLN_VALUE: usize = RESET_PLLN_VALUE; +// Dynamically computing the default PLLP value based on the PLLM value +pub(crate) const DEFAULT_PLLP_VALUE: PLLP = match DEFAULT_PLLM_VALUE { + PLLM::DivideBy16 => PLLP::DivideBy2, + PLLM::DivideBy8 => PLLP::DivideBy4, +}; +// Dynamically computing the default PLLQ value based on the PLLM value +pub(crate) const DEFAULT_PLLQ_VALUE: PLLQ = match DEFAULT_PLLM_VALUE { + PLLM::DivideBy16 => PLLQ::DivideBy4, + PLLM::DivideBy8 => PLLQ::DivideBy8, +}; + pub struct Rcc { registers: StaticRef, } +pub enum RtcClockSource { + LSI, + LSE, + HSERTC, +} + impl Rcc { - pub const fn new() -> Rcc { - Rcc { + pub fn new() -> Self { + let rcc = Self { registers: RCC_BASE, + }; + rcc.init(); + rcc + } + + // Some clocks need to be initialized before use + fn init(&self) { + self.init_pll_clock(); + } + + // Init the PLL clock. The default configuration: + // + if DEFAULT_PLLM_VALUE == PLLM::DivideBy8: + // + 2MHz VCO input frequency for reduced PLL jitter: freq_VCO_input = freq_source / PLLM + // + 384MHz VCO output frequency: freq_VCO_output = freq_VCO_input * PLLN + // + 96MHz main output frequency: freq_PLL = freq_VCO_output / PLLP + // + 48MHz PLL48CLK output frequency: freq_PLL48CLK = freq_VCO_output / PLLQ + // + if DEFAULT_PLLM_VALUE == PLLM::DivideBy16: (default hardware configuration) + // + 1MHz VCO input frequency for reduced PLL jitter: freq_VCO_input = freq_source / PLLM + // + 384MHz VCO output frequency: freq_VCO_output = freq_VCO_input * PLLN + // + 96MHz main output frequency: freq_PLL = freq_VCO_output / PLLP + // + 48MHz PLL48CLK output frequency: freq_PLL48CLK = freq_VCO_output / PLLQ + fn init_pll_clock(&self) { + self.set_pll_clocks_source(PllSource::HSI); + self.set_pll_clocks_m_divider(DEFAULT_PLLM_VALUE); + self.set_pll_clock_n_multiplier(DEFAULT_PLLN_VALUE); + self.set_pll_clock_p_divider(DEFAULT_PLLP_VALUE); + self.set_pll_clock_q_divider(DEFAULT_PLLQ_VALUE); + } + + // Get the current system clock source + pub(crate) fn get_sys_clock_source(&self) -> SysClockSource { + match self.registers.cfgr.read(CFGR::SWS) { + 0b00 => SysClockSource::HSI, + 0b01 => SysClockSource::HSE, + _ => SysClockSource::PLL, + // Uncomment this when PPLLR support is added. Also change the above match arm to + // 0b10 => SysClockSource::PLL, + //_ => SysClockSource::PPLLR, + } + } + + // Set the system clock source + // The source must be enabled + // NOTE: The flash latency also needs to be configured when changing the system clock frequency + pub(crate) fn set_sys_clock_source(&self, source: SysClockSource) { + self.registers.cfgr.modify(CFGR::SW.val(source as u32)); + } + + pub(crate) fn is_hsi_clock_system_clock(&self) -> bool { + let system_clock_source = self.get_sys_clock_source(); + system_clock_source == SysClockSource::HSI + || system_clock_source == SysClockSource::PLL + && self.registers.pllcfgr.read(PLLCFGR::PLLSRC) == PllSource::HSI as u32 + } + + pub(crate) fn is_hse_clock_system_clock(&self) -> bool { + let system_clock_source = self.get_sys_clock_source(); + system_clock_source == SysClockSource::HSE + || system_clock_source == SysClockSource::PLL + && self.registers.pllcfgr.read(PLLCFGR::PLLSRC) == PllSource::HSE as u32 + } + + /* HSI clock */ + // The HSI clock must not be configured as the system clock, either directly or indirectly. + pub(crate) fn disable_hsi_clock(&self) { + self.registers.cr.modify(CR::HSION::CLEAR); + } + + pub(crate) fn enable_hsi_clock(&self) { + self.registers.cr.modify(CR::HSION::SET); + } + + pub(crate) fn is_enabled_hsi_clock(&self) -> bool { + self.registers.cr.is_set(CR::HSION) + } + + // Indicates whether the HSI oscillator is stable + pub(crate) fn is_ready_hsi_clock(&self) -> bool { + self.registers.cr.is_set(CR::HSIRDY) + } + + /* HSE clock */ + pub(crate) fn disable_hse_clock(&self) { + self.registers.cr.modify(CR::HSEON::CLEAR); + self.registers.cr.modify(CR::HSEBYP::CLEAR); + } + + pub(crate) fn enable_hse_clock_bypass(&self) { + self.registers.cr.modify(CR::HSEBYP::SET); + } + + pub(crate) fn enable_hse_clock(&self) { + self.registers.cr.modify(CR::HSEON::SET); + } + + pub(crate) fn is_enabled_hse_clock(&self) -> bool { + self.registers.cr.is_set(CR::HSEON) + } + + // Indicates whether the HSE oscillator is stable + pub(crate) fn is_ready_hse_clock(&self) -> bool { + self.registers.cr.is_set(CR::HSERDY) + } + + /* Main PLL clock*/ + + // The main PLL clock must not be configured as the system clock. + pub(crate) fn disable_pll_clock(&self) { + self.registers.cr.modify(CR::PLLON::CLEAR); + } + + pub(crate) fn enable_pll_clock(&self) { + self.registers.cr.modify(CR::PLLON::SET); + } + + pub(crate) fn is_enabled_pll_clock(&self) -> bool { + self.registers.cr.is_set(CR::PLLON) + } + + // The PLL clock is locked when its signal is stable + pub(crate) fn is_locked_pll_clock(&self) -> bool { + self.registers.cr.is_set(CR::PLLRDY) + } + + pub(crate) fn get_pll_clocks_source(&self) -> PllSource { + match self.registers.pllcfgr.read(PLLCFGR::PLLSRC) { + 0b0 => PllSource::HSI, + _ => PllSource::HSE, + } + } + + // This method must be called only when all PLL clocks are disabled + pub(crate) fn set_pll_clocks_source(&self, source: PllSource) { + self.registers + .pllcfgr + .modify(PLLCFGR::PLLSRC.val(source as u32)); + } + + pub(crate) fn get_pll_clocks_m_divider(&self) -> PLLM { + match self.registers.pllcfgr.read(PLLCFGR::PLLM) { + 8 => PLLM::DivideBy8, + 16 => PLLM::DivideBy16, + _ => panic!("Unexpected PLLM divider"), + } + } + + // This method must be called only when all PLL clocks are disabled + pub(crate) fn set_pll_clocks_m_divider(&self, m: PLLM) { + self.registers.pllcfgr.modify(PLLCFGR::PLLM.val(m as u32)); + } + + pub(crate) fn get_pll_clock_n_multiplier(&self) -> usize { + self.registers.pllcfgr.read(PLLCFGR::PLLN) as usize + } + + // This method must be called only if the main PLL clock is disabled + pub(crate) fn set_pll_clock_n_multiplier(&self, n: usize) { + self.registers.pllcfgr.modify(PLLCFGR::PLLN.val(n as u32)); + } + + pub(crate) fn get_pll_clock_p_divider(&self) -> PLLP { + match self.registers.pllcfgr.read(PLLCFGR::PLLP) { + 0b00 => PLLP::DivideBy2, + 0b01 => PLLP::DivideBy4, + 0b10 => PLLP::DivideBy6, + _ => PLLP::DivideBy8, + } + } + + // This method must be called only if the main PLL clock is disabled + pub(crate) fn set_pll_clock_p_divider(&self, p: PLLP) { + self.registers.pllcfgr.modify(PLLCFGR::PLLP.val(p as u32)); + } + + pub(crate) fn _get_pll_clock_q_divider(&self) -> PLLQ { + match self.registers.pllcfgr.read(PLLCFGR::PLLQ) { + 3 => PLLQ::DivideBy3, + 4 => PLLQ::DivideBy4, + 5 => PLLQ::DivideBy5, + 6 => PLLQ::DivideBy6, + 7 => PLLQ::DivideBy7, + 8 => PLLQ::DivideBy8, + 9 => PLLQ::DivideBy9, + _ => panic!("Unexpected PLLQ divider"), + } + } + + // This method must be called only if the main PLL clock is disabled + pub(crate) fn set_pll_clock_q_divider(&self, q: PLLQ) { + self.registers.pllcfgr.modify(PLLCFGR::PLLQ.val(q as u32)); + } + + /* AHB prescaler */ + + pub(crate) fn set_ahb_prescaler(&self, ahb_prescaler: AHBPrescaler) { + self.registers + .cfgr + .modify(CFGR::HPRE.val(ahb_prescaler as u32)); + } + + pub(crate) fn get_ahb_prescaler(&self) -> AHBPrescaler { + match self.registers.cfgr.read(CFGR::HPRE) { + 0b1000 => AHBPrescaler::DivideBy2, + 0b1001 => AHBPrescaler::DivideBy4, + 0b1010 => AHBPrescaler::DivideBy8, + 0b1011 => AHBPrescaler::DivideBy16, + 0b1100 => AHBPrescaler::DivideBy64, + 0b1101 => AHBPrescaler::DivideBy128, + 0b1110 => AHBPrescaler::DivideBy256, + 0b1111 => AHBPrescaler::DivideBy512, + _ => AHBPrescaler::DivideBy1, } } - fn configure_rng_clock(&self) { + /* APB1 prescaler */ + + pub(crate) fn set_apb1_prescaler(&self, apb1_prescaler: APBPrescaler) { + self.registers + .cfgr + .modify(CFGR::PPRE1.val(apb1_prescaler as u32)); + } + + pub(crate) fn get_apb1_prescaler(&self) -> APBPrescaler { + match self.registers.cfgr.read(CFGR::PPRE1) { + 0b100 => APBPrescaler::DivideBy2, + 0b101 => APBPrescaler::DivideBy4, + 0b110 => APBPrescaler::DivideBy8, + 0b111 => APBPrescaler::DivideBy16, + _ => APBPrescaler::DivideBy1, // 0b0xx means no division + } + } + + /* APB2 prescaler */ + + pub(crate) fn set_apb2_prescaler(&self, apb2_prescaler: APBPrescaler) { + self.registers + .cfgr + .modify(CFGR::PPRE2.val(apb2_prescaler as u32)); + } + + pub(crate) fn get_apb2_prescaler(&self) -> APBPrescaler { + match self.registers.cfgr.read(CFGR::PPRE2) { + 0b100 => APBPrescaler::DivideBy2, + 0b101 => APBPrescaler::DivideBy4, + 0b110 => APBPrescaler::DivideBy8, + 0b111 => APBPrescaler::DivideBy16, + _ => APBPrescaler::DivideBy1, // 0b0xx means no division + } + } + + pub(crate) fn set_mco1_clock_source(&self, source: MCO1Source) { + self.registers.cfgr.modify(CFGR::MCO1.val(source as u32)); + } + + pub(crate) fn get_mco1_clock_source(&self) -> MCO1Source { + match self.registers.cfgr.read(CFGR::MCO1) { + 0b00 => MCO1Source::HSI, + // When LSE or HSE are added, uncomment the following lines + //0b01 => MCO1Source::LSE, + 0b10 => MCO1Source::HSE, + // 0b11 corresponds to MCO1Source::PLL + _ => MCO1Source::PLL, + } + } + + pub(crate) fn set_mco1_clock_divider(&self, divider: MCO1Divider) { + self.registers + .cfgr + .modify(CFGR::MCO1PRE.val(divider as u32)); + } + + pub(crate) fn get_mco1_clock_divider(&self) -> MCO1Divider { + match self.registers.cfgr.read(CFGR::MCO1PRE) { + 0b100 => MCO1Divider::DivideBy2, + 0b101 => MCO1Divider::DivideBy3, + 0b110 => MCO1Divider::DivideBy4, + 0b111 => MCO1Divider::DivideBy5, + _ => MCO1Divider::DivideBy1, + } + } + + pub(crate) fn configure_rng_clock(&self) { self.registers.pllcfgr.modify(PLLCFGR::PLLQ.val(2)); self.registers.cr.modify(CR::PLLON::SET); } // I2C1 clock - fn is_enabled_i2c1_clock(&self) -> bool { + pub(crate) fn is_enabled_i2c1_clock(&self) -> bool { self.registers.apb1enr.is_set(APB1ENR::I2C1EN) } - fn enable_i2c1_clock(&self) { + pub(crate) fn enable_i2c1_clock(&self) { self.registers.apb1enr.modify(APB1ENR::I2C1EN::SET); self.registers.apb1rstr.modify(APB1RSTR::I2C1RST::SET); self.registers.apb1rstr.modify(APB1RSTR::I2C1RST::CLEAR); } - fn disable_i2c1_clock(&self) { + pub(crate) fn disable_i2c1_clock(&self) { self.registers.apb1enr.modify(APB1ENR::I2C1EN::CLEAR) } // SPI3 clock - fn is_enabled_spi3_clock(&self) -> bool { + pub(crate) fn is_enabled_spi3_clock(&self) -> bool { self.registers.apb1enr.is_set(APB1ENR::SPI3EN) } - fn enable_spi3_clock(&self) { + pub(crate) fn enable_spi3_clock(&self) { self.registers.apb1enr.modify(APB1ENR::SPI3EN::SET) } - fn disable_spi3_clock(&self) { + pub(crate) fn disable_spi3_clock(&self) { self.registers.apb1enr.modify(APB1ENR::SPI3EN::CLEAR) } // TIM2 clock + pub(crate) fn is_enabled_tim_pre(&self) -> bool { + self.registers.dckcfgr.is_set(DCKCFGR::TIMPRE) + } - fn is_enabled_tim2_clock(&self) -> bool { + pub(crate) fn is_enabled_tim2_clock(&self) -> bool { self.registers.apb1enr.is_set(APB1ENR::TIM2EN) } - fn enable_tim2_clock(&self) { + pub(crate) fn enable_tim2_clock(&self) { self.registers.apb1enr.modify(APB1ENR::TIM2EN::SET) } - fn disable_tim2_clock(&self) { + pub(crate) fn disable_tim2_clock(&self) { self.registers.apb1enr.modify(APB1ENR::TIM2EN::CLEAR) } // SYSCFG clock - fn is_enabled_syscfg_clock(&self) -> bool { + pub(crate) fn is_enabled_syscfg_clock(&self) -> bool { self.registers.apb2enr.is_set(APB2ENR::SYSCFGEN) } - fn enable_syscfg_clock(&self) { + pub(crate) fn enable_syscfg_clock(&self) { self.registers.apb2enr.modify(APB2ENR::SYSCFGEN::SET) } - fn disable_syscfg_clock(&self) { + pub(crate) fn disable_syscfg_clock(&self) { self.registers.apb2enr.modify(APB2ENR::SYSCFGEN::CLEAR) } // DMA1 clock - fn is_enabled_dma1_clock(&self) -> bool { + pub(crate) fn is_enabled_dma1_clock(&self) -> bool { self.registers.ahb1enr.is_set(AHB1ENR::DMA1EN) } - fn enable_dma1_clock(&self) { + pub(crate) fn enable_dma1_clock(&self) { self.registers.ahb1enr.modify(AHB1ENR::DMA1EN::SET) } - fn disable_dma1_clock(&self) { + pub(crate) fn disable_dma1_clock(&self) { self.registers.ahb1enr.modify(AHB1ENR::DMA1EN::CLEAR) } + // DMA2 clock + pub(crate) fn is_enabled_dma2_clock(&self) -> bool { + self.registers.ahb1enr.is_set(AHB1ENR::DMA2EN) + } + + pub(crate) fn enable_dma2_clock(&self) { + self.registers.ahb1enr.modify(AHB1ENR::DMA2EN::SET) + } + + pub(crate) fn disable_dma2_clock(&self) { + self.registers.ahb1enr.modify(AHB1ENR::DMA2EN::CLEAR) + } + // GPIOH clock - fn is_enabled_gpioh_clock(&self) -> bool { + pub(crate) fn is_enabled_gpioh_clock(&self) -> bool { self.registers.ahb1enr.is_set(AHB1ENR::GPIOHEN) } - fn enable_gpioh_clock(&self) { + pub(crate) fn enable_gpioh_clock(&self) { self.registers.ahb1enr.modify(AHB1ENR::GPIOHEN::SET) } - fn disable_gpioh_clock(&self) { + pub(crate) fn disable_gpioh_clock(&self) { self.registers.ahb1enr.modify(AHB1ENR::GPIOHEN::CLEAR) } // GPIOG clock - fn is_enabled_gpiog_clock(&self) -> bool { + pub(crate) fn is_enabled_gpiog_clock(&self) -> bool { self.registers.ahb1enr.is_set(AHB1ENR::GPIOGEN) } - fn enable_gpiog_clock(&self) { + pub(crate) fn enable_gpiog_clock(&self) { self.registers.ahb1enr.modify(AHB1ENR::GPIOGEN::SET) } - fn disable_gpiog_clock(&self) { + pub(crate) fn disable_gpiog_clock(&self) { self.registers.ahb1enr.modify(AHB1ENR::GPIOGEN::CLEAR) } // GPIOF clock - fn is_enabled_gpiof_clock(&self) -> bool { + pub(crate) fn is_enabled_gpiof_clock(&self) -> bool { self.registers.ahb1enr.is_set(AHB1ENR::GPIOFEN) } - fn enable_gpiof_clock(&self) { + pub(crate) fn enable_gpiof_clock(&self) { self.registers.ahb1enr.modify(AHB1ENR::GPIOFEN::SET) } - fn disable_gpiof_clock(&self) { + pub(crate) fn disable_gpiof_clock(&self) { self.registers.ahb1enr.modify(AHB1ENR::GPIOFEN::CLEAR) } // GPIOE clock - fn is_enabled_gpioe_clock(&self) -> bool { + pub(crate) fn is_enabled_gpioe_clock(&self) -> bool { self.registers.ahb1enr.is_set(AHB1ENR::GPIOEEN) } - fn enable_gpioe_clock(&self) { + pub(crate) fn enable_gpioe_clock(&self) { self.registers.ahb1enr.modify(AHB1ENR::GPIOEEN::SET) } - fn disable_gpioe_clock(&self) { + pub(crate) fn disable_gpioe_clock(&self) { self.registers.ahb1enr.modify(AHB1ENR::GPIOEEN::CLEAR) } // GPIOD clock - fn is_enabled_gpiod_clock(&self) -> bool { + pub(crate) fn is_enabled_gpiod_clock(&self) -> bool { self.registers.ahb1enr.is_set(AHB1ENR::GPIODEN) } - fn enable_gpiod_clock(&self) { + pub(crate) fn enable_gpiod_clock(&self) { self.registers.ahb1enr.modify(AHB1ENR::GPIODEN::SET) } - fn disable_gpiod_clock(&self) { + pub(crate) fn disable_gpiod_clock(&self) { self.registers.ahb1enr.modify(AHB1ENR::GPIODEN::CLEAR) } // GPIOC clock - fn is_enabled_gpioc_clock(&self) -> bool { + pub(crate) fn is_enabled_gpioc_clock(&self) -> bool { self.registers.ahb1enr.is_set(AHB1ENR::GPIOCEN) } - fn enable_gpioc_clock(&self) { + pub(crate) fn enable_gpioc_clock(&self) { self.registers.ahb1enr.modify(AHB1ENR::GPIOCEN::SET) } - fn disable_gpioc_clock(&self) { + pub(crate) fn disable_gpioc_clock(&self) { self.registers.ahb1enr.modify(AHB1ENR::GPIOCEN::CLEAR) } // GPIOB clock - fn is_enabled_gpiob_clock(&self) -> bool { + pub(crate) fn is_enabled_gpiob_clock(&self) -> bool { self.registers.ahb1enr.is_set(AHB1ENR::GPIOBEN) } - fn enable_gpiob_clock(&self) { + pub(crate) fn enable_gpiob_clock(&self) { self.registers.ahb1enr.modify(AHB1ENR::GPIOBEN::SET) } - fn disable_gpiob_clock(&self) { + pub(crate) fn disable_gpiob_clock(&self) { self.registers.ahb1enr.modify(AHB1ENR::GPIOBEN::CLEAR) } // GPIOA clock - fn is_enabled_gpioa_clock(&self) -> bool { + pub(crate) fn is_enabled_gpioa_clock(&self) -> bool { self.registers.ahb1enr.is_set(AHB1ENR::GPIOAEN) } - fn enable_gpioa_clock(&self) { + pub(crate) fn enable_gpioa_clock(&self) { self.registers.ahb1enr.modify(AHB1ENR::GPIOAEN::SET) } - fn disable_gpioa_clock(&self) { + pub(crate) fn disable_gpioa_clock(&self) { self.registers.ahb1enr.modify(AHB1ENR::GPIOAEN::CLEAR) } // FMC - fn is_enabled_fmc_clock(&self) -> bool { + pub(crate) fn is_enabled_fmc_clock(&self) -> bool { self.registers.ahb3enr.is_set(AHB3ENR::FMCEN) } - fn enable_fmc_clock(&self) { + pub(crate) fn enable_fmc_clock(&self) { self.registers.ahb3enr.modify(AHB3ENR::FMCEN::SET) } - fn disable_fmc_clock(&self) { + pub(crate) fn disable_fmc_clock(&self) { self.registers.ahb3enr.modify(AHB3ENR::FMCEN::CLEAR) } + // USART1 clock + pub(crate) fn is_enabled_usart1_clock(&self) -> bool { + self.registers.apb2enr.is_set(APB2ENR::USART1EN) + } + + pub(crate) fn enable_usart1_clock(&self) { + self.registers.apb2enr.modify(APB2ENR::USART1EN::SET) + } + + pub(crate) fn disable_usart1_clock(&self) { + self.registers.apb2enr.modify(APB2ENR::USART1EN::CLEAR) + } + // USART2 clock - fn is_enabled_usart2_clock(&self) -> bool { + pub(crate) fn is_enabled_usart2_clock(&self) -> bool { self.registers.apb1enr.is_set(APB1ENR::USART2EN) } - fn enable_usart2_clock(&self) { + pub(crate) fn enable_usart2_clock(&self) { self.registers.apb1enr.modify(APB1ENR::USART2EN::SET) } - fn disable_usart2_clock(&self) { + pub(crate) fn disable_usart2_clock(&self) { self.registers.apb1enr.modify(APB1ENR::USART2EN::CLEAR) } // USART3 clock - fn is_enabled_usart3_clock(&self) -> bool { + pub(crate) fn is_enabled_usart3_clock(&self) -> bool { self.registers.apb1enr.is_set(APB1ENR::USART3EN) } - fn enable_usart3_clock(&self) { + pub(crate) fn enable_usart3_clock(&self) { self.registers.apb1enr.modify(APB1ENR::USART3EN::SET) } - fn disable_usart3_clock(&self) { + pub(crate) fn disable_usart3_clock(&self) { self.registers.apb1enr.modify(APB1ENR::USART3EN::CLEAR) } // ADC1 clock - fn is_enabled_adc1_clock(&self) -> bool { + pub(crate) fn is_enabled_adc1_clock(&self) -> bool { self.registers.apb2enr.is_set(APB2ENR::ADC1EN) } - fn enable_adc1_clock(&self) { + pub(crate) fn enable_adc1_clock(&self) { self.registers.apb2enr.modify(APB2ENR::ADC1EN::SET) } - fn disable_adc1_clock(&self) { + pub(crate) fn disable_adc1_clock(&self) { self.registers.apb2enr.modify(APB2ENR::ADC1EN::CLEAR) } + // DAC clock + + pub(crate) fn is_enabled_dac_clock(&self) -> bool { + self.registers.apb1enr.is_set(APB1ENR::DACEN) + } + + pub(crate) fn enable_dac_clock(&self) { + self.registers.apb1enr.modify(APB1ENR::DACEN::SET) + } + + pub(crate) fn disable_dac_clock(&self) { + self.registers.apb1enr.modify(APB1ENR::DACEN::CLEAR) + } + // RNG clock - fn is_enabled_rng_clock(&self) -> bool { + pub(crate) fn is_enabled_rng_clock(&self) -> bool { self.registers.ahb2enr.is_set(AHB2ENR::RNGEN) } - fn enable_rng_clock(&self) { + pub(crate) fn enable_rng_clock(&self) { self.registers.ahb2enr.modify(AHB2ENR::RNGEN::SET); } - fn disable_rng_clock(&self) { + pub(crate) fn disable_rng_clock(&self) { self.registers.ahb2enr.modify(AHB2ENR::RNGEN::CLEAR); } // OTGFS clock - fn is_enabled_otgfs_clock(&self) -> bool { + pub(crate) fn is_enabled_otgfs_clock(&self) -> bool { self.registers.ahb2enr.is_set(AHB2ENR::OTGFSEN) } - fn enable_otgfs_clock(&self) { + pub(crate) fn enable_otgfs_clock(&self) { self.registers.ahb2enr.modify(AHB2ENR::OTGFSEN::SET); } - fn disable_otgfs_clock(&self) { + pub(crate) fn disable_otgfs_clock(&self) { self.registers.ahb2enr.modify(AHB2ENR::OTGFSEN::CLEAR); } + + // CAN1 clock + + pub(crate) fn is_enabled_can1_clock(&self) -> bool { + self.registers.apb1enr.is_set(APB1ENR::CAN1EN) + } + + pub(crate) fn enable_can1_clock(&self) { + self.registers.apb1rstr.modify(APB1RSTR::CAN1RST::SET); + self.registers.apb1rstr.modify(APB1RSTR::CAN1RST::CLEAR); + self.registers.apb1enr.modify(APB1ENR::CAN1EN::SET); + } + + pub(crate) fn disable_can1_clock(&self) { + self.registers.apb1enr.modify(APB1ENR::CAN1EN::CLEAR); + } + + // RTC clock + pub(crate) fn source_into_u32(source: RtcClockSource) -> u32 { + match source { + RtcClockSource::LSE => 1, + RtcClockSource::LSI => 2, + RtcClockSource::HSERTC => 3, + } + } + + pub(crate) fn enable_lsi_clock(&self) { + self.registers.csr.modify(CSR::LSION::SET); + } + + pub(crate) fn is_enabled_pwr_clock(&self) -> bool { + self.registers.apb1enr.is_set(APB1ENR::PWREN) + } + + pub(crate) fn enable_pwr_clock(&self) { + // Enable the power interface clock + self.registers.apb1enr.modify(APB1ENR::PWREN::SET); + } + + pub(crate) fn disable_pwr_clock(&self) { + self.registers.apb1enr.modify(APB1ENR::PWREN::CLEAR); + } + + pub(crate) fn is_enabled_rtc_clock(&self) -> bool { + self.registers.bdcr.is_set(BDCR::RTCEN) + } + + pub(crate) fn enable_rtc_clock(&self, source: RtcClockSource) { + // Enable LSI + self.enable_lsi_clock(); + let mut counter = 1_000; + while counter > 0 && !self.registers.csr.is_set(CSR::LSION) { + counter -= 1; + } + if counter == 0 { + panic!("Unable to activate lsi clock"); + } + + // Select RTC clock source + let source_num = Rcc::source_into_u32(source); + self.registers.bdcr.modify(BDCR::RTCSEL.val(source_num)); + + // Enable RTC clock + self.registers.bdcr.modify(BDCR::RTCEN::SET); + } + + pub(crate) fn disable_rtc_clock(&self) { + self.registers.bdcr.modify(BDCR::RTCEN.val(1)); + self.registers.bdcr.modify(BDCR::RTCSEL.val(0)); + } } -/// Clock sources for CPU -pub enum CPUClock { - HSE, - HSI, - PLLCLK, - PPLLR, +#[derive(Copy, Clone, Debug, PartialEq)] +pub(crate) enum PLLP { + DivideBy2 = 0b00, + DivideBy4 = 0b01, + DivideBy6 = 0b10, + DivideBy8 = 0b11, } -pub struct PeripheralClock<'a> { - pub clock: PeripheralClockType, - rcc: &'a Rcc, +impl From for usize { + // (variant_value + 1) * 2 = X for X in DivideByX + fn from(item: PLLP) -> Self { + (item as usize + 1) << 1 + } } -/// Bus + Clock name for the peripherals -pub enum PeripheralClockType { - AHB1(HCLK1), - AHB2(HCLK2), - AHB3(HCLK3), - APB1(PCLK1), - APB2(PCLK2), +// Theoretically, the PLLM value can range from 2 to 63. However, the current implementation was +// designed to support 1MHz frequency precision. In a future update, PLLM will become a usize. +#[allow(dead_code)] +pub(crate) enum PLLM { + DivideBy8 = 8, + DivideBy16 = 16, } -/// Peripherals clocked by HCLK1 -pub enum HCLK1 { - DMA1, - GPIOH, - GPIOG, - GPIOF, - GPIOE, - GPIOD, - GPIOC, - GPIOB, - GPIOA, +#[derive(Copy, Clone, Debug, PartialEq)] +// Due to the restricted values for PLLM, PLLQ 2/10-15 values are meaningless. +pub(crate) enum PLLQ { + DivideBy3 = 3, + DivideBy4, + DivideBy5, + DivideBy6, + DivideBy7, + DivideBy8, + DivideBy9, } -/// Peripherals clocked by HCLK3 -pub enum HCLK3 { - FMC, +/// Clock sources for the CPU +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum SysClockSource { + HSI = 0b00, + HSE = 0b01, + PLL = 0b10, + // NOTE: not all STM32F4xx boards support this source. + //PPLLR = 0b11, Uncomment this when support for PPLLR is added } -/// Peripherals clocked by HCLK2 -pub enum HCLK2 { - RNG, - OTGFS, +pub enum PllSource { + HSI = 0b0, + HSE = 0b1, } -/// Peripherals clocked by PCLK1 -pub enum PCLK1 { - TIM2, - USART2, - USART3, - SPI3, - I2C1, +pub enum MCO1Source { + HSI = 0b00, + //LSE = 0b01, // When support for LSE is added, uncomment this + HSE = 0b10, + PLL = 0b11, } -/// Peripherals clocked by PCLK2 -pub enum PCLK2 { - ADC1, - SYSCFG, +pub enum MCO1Divider { + DivideBy1 = 0b000, + DivideBy2 = 0b100, + DivideBy3 = 0b101, + DivideBy4 = 0b110, + DivideBy5 = 0b111, } -impl<'a> PeripheralClock<'a> { - pub const fn new(clock: PeripheralClockType, rcc: &'a Rcc) -> Self { - Self { clock, rcc } - } +/// HSE Mode +#[derive(PartialEq)] +pub enum HseMode { + BYPASS, + CRYSTAL, +} - pub fn configure_rng_clock(&self) { - self.rcc.configure_rng_clock(); - } +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum AHBPrescaler { + DivideBy1 = 0b0000, + DivideBy2 = 0b1000, + DivideBy4 = 0b1001, + DivideBy8 = 0b1010, + DivideBy16 = 0b1011, + DivideBy64 = 0b1100, + DivideBy128 = 0b1101, + DivideBy256 = 0b1110, + DivideBy512 = 0b1111, } -impl<'a> ClockInterface for PeripheralClock<'a> { - fn is_enabled(&self) -> bool { - match self.clock { - PeripheralClockType::AHB1(ref v) => match v { - HCLK1::DMA1 => self.rcc.is_enabled_dma1_clock(), - HCLK1::GPIOH => self.rcc.is_enabled_gpioh_clock(), - HCLK1::GPIOG => self.rcc.is_enabled_gpiog_clock(), - HCLK1::GPIOF => self.rcc.is_enabled_gpiof_clock(), - HCLK1::GPIOE => self.rcc.is_enabled_gpioe_clock(), - HCLK1::GPIOD => self.rcc.is_enabled_gpiod_clock(), - HCLK1::GPIOC => self.rcc.is_enabled_gpioc_clock(), - HCLK1::GPIOB => self.rcc.is_enabled_gpiob_clock(), - HCLK1::GPIOA => self.rcc.is_enabled_gpioa_clock(), - }, - PeripheralClockType::AHB2(ref v) => match v { - HCLK2::RNG => self.rcc.is_enabled_rng_clock(), - HCLK2::OTGFS => self.rcc.is_enabled_otgfs_clock(), - }, - PeripheralClockType::AHB3(ref v) => match v { - HCLK3::FMC => self.rcc.is_enabled_fmc_clock(), - }, - PeripheralClockType::APB1(ref v) => match v { - PCLK1::TIM2 => self.rcc.is_enabled_tim2_clock(), - PCLK1::USART2 => self.rcc.is_enabled_usart2_clock(), - PCLK1::USART3 => self.rcc.is_enabled_usart3_clock(), - PCLK1::I2C1 => self.rcc.is_enabled_i2c1_clock(), - PCLK1::SPI3 => self.rcc.is_enabled_spi3_clock(), - }, - PeripheralClockType::APB2(ref v) => match v { - PCLK2::ADC1 => self.rcc.is_enabled_adc1_clock(), - PCLK2::SYSCFG => self.rcc.is_enabled_syscfg_clock(), - }, +impl From for usize { + fn from(item: AHBPrescaler) -> usize { + match item { + AHBPrescaler::DivideBy1 => 1, + AHBPrescaler::DivideBy2 => 2, + AHBPrescaler::DivideBy4 => 4, + AHBPrescaler::DivideBy8 => 8, + AHBPrescaler::DivideBy16 => 16, + AHBPrescaler::DivideBy64 => 64, + AHBPrescaler::DivideBy128 => 128, + AHBPrescaler::DivideBy256 => 256, + AHBPrescaler::DivideBy512 => 512, } } +} - fn enable(&self) { - match self.clock { - PeripheralClockType::AHB1(ref v) => match v { - HCLK1::DMA1 => { - self.rcc.enable_dma1_clock(); - } - HCLK1::GPIOH => { - self.rcc.enable_gpioh_clock(); - } - HCLK1::GPIOG => { - self.rcc.enable_gpiog_clock(); - } - HCLK1::GPIOF => { - self.rcc.enable_gpiof_clock(); - } - HCLK1::GPIOE => { - self.rcc.enable_gpioe_clock(); - } - HCLK1::GPIOD => { - self.rcc.enable_gpiod_clock(); - } - HCLK1::GPIOC => { - self.rcc.enable_gpioc_clock(); - } - HCLK1::GPIOB => { - self.rcc.enable_gpiob_clock(); - } - HCLK1::GPIOA => { - self.rcc.enable_gpioa_clock(); - } - }, - PeripheralClockType::AHB2(ref v) => match v { - HCLK2::RNG => { - self.rcc.enable_rng_clock(); - } - HCLK2::OTGFS => { - self.rcc.enable_otgfs_clock(); - } - }, - PeripheralClockType::AHB3(ref v) => match v { - HCLK3::FMC => self.rcc.enable_fmc_clock(), - }, - PeripheralClockType::APB1(ref v) => match v { - PCLK1::TIM2 => { - self.rcc.enable_tim2_clock(); - } - PCLK1::USART2 => { - self.rcc.enable_usart2_clock(); - } - PCLK1::USART3 => { - self.rcc.enable_usart3_clock(); - } - PCLK1::I2C1 => { - self.rcc.enable_i2c1_clock(); - } - PCLK1::SPI3 => { - self.rcc.enable_spi3_clock(); - } - }, - PeripheralClockType::APB2(ref v) => match v { - PCLK2::ADC1 => { - self.rcc.enable_adc1_clock(); - } - PCLK2::SYSCFG => { - self.rcc.enable_syscfg_clock(); - } - }, - } - } +#[derive(Clone, Copy, PartialEq, Debug)] +pub enum APBPrescaler { + DivideBy1 = 0b000, // No division + DivideBy2 = 0b100, + DivideBy4 = 0b101, + DivideBy8 = 0b110, + DivideBy16 = 0b111, +} - fn disable(&self) { - match self.clock { - PeripheralClockType::AHB1(ref v) => match v { - HCLK1::DMA1 => { - self.rcc.disable_dma1_clock(); - } - HCLK1::GPIOH => { - self.rcc.disable_gpioh_clock(); - } - HCLK1::GPIOG => { - self.rcc.disable_gpiog_clock(); - } - HCLK1::GPIOF => { - self.rcc.disable_gpiof_clock(); - } - HCLK1::GPIOE => { - self.rcc.disable_gpioe_clock(); - } - HCLK1::GPIOD => { - self.rcc.disable_gpiod_clock(); - } - HCLK1::GPIOC => { - self.rcc.disable_gpioc_clock(); - } - HCLK1::GPIOB => { - self.rcc.disable_gpiob_clock(); - } - HCLK1::GPIOA => { - self.rcc.disable_gpioa_clock(); - } - }, - PeripheralClockType::AHB2(ref v) => match v { - HCLK2::RNG => { - self.rcc.disable_rng_clock(); - } - HCLK2::OTGFS => { - self.rcc.disable_otgfs_clock(); - } - }, - PeripheralClockType::AHB3(ref v) => match v { - HCLK3::FMC => self.rcc.disable_fmc_clock(), - }, - PeripheralClockType::APB1(ref v) => match v { - PCLK1::TIM2 => { - self.rcc.disable_tim2_clock(); - } - PCLK1::USART2 => { - self.rcc.disable_usart2_clock(); - } - PCLK1::USART3 => { - self.rcc.disable_usart3_clock(); - } - PCLK1::I2C1 => { - self.rcc.disable_i2c1_clock(); - } - PCLK1::SPI3 => { - self.rcc.disable_spi3_clock(); - } - }, - PeripheralClockType::APB2(ref v) => match v { - PCLK2::ADC1 => { - self.rcc.disable_adc1_clock(); - } - PCLK2::SYSCFG => { - self.rcc.disable_syscfg_clock(); - } - }, +impl From for usize { + fn from(item: APBPrescaler) -> Self { + match item { + APBPrescaler::DivideBy1 => 1, + APBPrescaler::DivideBy2 => 2, + APBPrescaler::DivideBy4 => 4, + APBPrescaler::DivideBy8 => 8, + APBPrescaler::DivideBy16 => 16, } } } diff --git a/chips/stm32f4xx/src/spi.rs b/chips/stm32f4xx/src/spi.rs index 605eded6fa..fe3e6269a1 100644 --- a/chips/stm32f4xx/src/spi.rs +++ b/chips/stm32f4xx/src/spi.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::cell::Cell; use core::cmp; use kernel::ErrorCode; @@ -11,9 +15,9 @@ use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; use kernel::utilities::registers::{register_bitfields, ReadOnly, ReadWrite}; use kernel::utilities::StaticRef; -use crate::dma1; -use crate::dma1::Dma1Peripheral; -use crate::rcc; +use crate::clocks::phclk; +use crate::dma; +use crate::dma::{Dma1, Dma1Peripheral}; /// Serial peripheral interface #[repr(C)] @@ -139,7 +143,7 @@ register_bitfields![u32, // for use by dma1 pub(crate) fn get_address_dr(regs: StaticRef) -> u32 { - ®s.dr as *const ReadWrite as u32 + core::ptr::addr_of!(regs.dr) as u32 } pub const SPI3_BASE: StaticRef = @@ -152,9 +156,9 @@ pub struct Spi<'a> { // SPI slave support not yet implemented master_client: OptionalCell<&'a dyn hil::spi::SpiMasterClient>, - tx_dma: OptionalCell<&'a dma1::Stream<'a>>, + tx_dma: OptionalCell<&'a dma::Stream<'a, Dma1<'a>>>, tx_dma_pid: Dma1Peripheral, - rx_dma: OptionalCell<&'a dma1::Stream<'a>>, + rx_dma: OptionalCell<&'a dma::Stream<'a, Dma1<'a>>>, rx_dma_pid: Dma1Peripheral, dma_len: Cell, @@ -166,8 +170,8 @@ pub struct Spi<'a> { } // for use by `set_dma` -pub struct TxDMA<'a>(pub &'a dma1::Stream<'a>); -pub struct RxDMA<'a>(pub &'a dma1::Stream<'a>); +pub struct TxDMA<'a>(pub &'a dma::Stream<'a, Dma1<'a>>); +pub struct RxDMA<'a>(pub &'a dma::Stream<'a, Dma1<'a>>); impl<'a> Spi<'a> { pub const fn new( @@ -183,9 +187,9 @@ impl<'a> Spi<'a> { master_client: OptionalCell::empty(), tx_dma: OptionalCell::empty(), - tx_dma_pid: tx_dma_pid, + tx_dma_pid, rx_dma: OptionalCell::empty(), - rx_dma_pid: rx_dma_pid, + rx_dma_pid, dma_len: Cell::new(0), transfers_in_progress: Cell::new(0), @@ -336,10 +340,10 @@ impl<'a> Spi<'a> { } } -impl<'a> spi::SpiMaster for Spi<'a> { +impl<'a> spi::SpiMaster<'a> for Spi<'a> { type ChipSelect = &'a crate::gpio::Pin<'a>; - fn set_client(&self, client: &'static dyn SpiMasterClient) { + fn set_client(&self, client: &'a dyn SpiMasterClient) { self.master_client.set(client); } @@ -347,18 +351,18 @@ impl<'a> spi::SpiMaster for Spi<'a> { // enable error interrupt (used only for debugging) // self.registers.cr2.modify(CR2::ERRIE::SET); + // 2 line unidirectional mode + // Select as master + // Software slave management + // 8 bit data frame format + // Enable self.registers.cr1.modify( - // 2 line unidirectional mode - CR1::BIDIMODE::CLEAR + - // Select as master - CR1::MSTR::SET + - // Software slave management - CR1::SSM::SET + - CR1::SSI::SET + - // 8 bit data frame format - CR1::DFF::CLEAR + - // Enable - CR1::SPE::SET, + CR1::BIDIMODE::CLEAR + + CR1::MSTR::SET + + CR1::SSM::SET + + CR1::SSI::SET + + CR1::DFF::CLEAR + + CR1::SPE::SET, ); Ok(()) } @@ -411,8 +415,8 @@ impl<'a> spi::SpiMaster for Spi<'a> { } } - /// We *only* support 1Mhz. If `rate` is set to any value other than - /// `1_000_000`, then this function panics + /// We *only* support 1Mhz and 4MHz. If `rate` is set to any value other than + /// `1_000_000` or `4_000_000`, then this function panics. fn set_rate(&self, rate: u32) -> Result { match rate { 1_000_000 => self.set_cr(|| { @@ -423,19 +427,20 @@ impl<'a> spi::SpiMaster for Spi<'a> { // HSI is 16Mhz and Fpclk is also 16Mhz. 0b001 is Fpclk / 4 self.registers.cr1.modify(CR1::BR.val(0b001)); }), - _ => panic!("rate must be 1_000_000, 4_000_000"), + _ => panic!("SPI rate must be 1_000_000 or 4_000_000"), } Ok(rate) } - /// We *only* support 1Mhz. If we need to return any other value other than - /// `1_000_000`, then this function panics + /// We *only* support 1Mhz and 4MHz. If we need to return any other value + /// than `1_000_000` or `4_000_000`, then this function panics. fn get_rate(&self) -> u32 { - if self.registers.cr1.read(CR1::BR) != 0b011 { - panic!("rate not set to 1_000_000"); + // HSI is 16Mhz and Fpclk is also 16Mhz + match self.registers.cr1.read(CR1::BR) { + 0b011 => 1_000_000, // 0b011 is Fpclk / 16 => 1 MHz + 0b001 => 4_000_000, // 0b001 is Fpclk / 4 => 4 MHz + _ => panic!("Current SPI rate not supported by tock OS!"), } - - 1_000_000 } fn set_polarity(&self, polarity: ClockPolarity) -> Result<(), ErrorCode> { @@ -470,8 +475,8 @@ impl<'a> spi::SpiMaster for Spi<'a> { } } -impl dma1::StreamClient for Spi<'_> { - fn transfer_done(&self, pid: dma1::Dma1Peripheral) { +impl<'a> dma::StreamClient<'a, Dma1<'a>> for Spi<'a> { + fn transfer_done(&self, pid: Dma1Peripheral) { if pid == self.tx_dma_pid { self.disable_tx(); } @@ -505,7 +510,7 @@ impl dma1::StreamClient for Spi<'_> { } } -pub struct SpiClock<'a>(pub rcc::PeripheralClock<'a>); +pub struct SpiClock<'a>(pub phclk::PeripheralClock<'a>); impl ClockInterface for SpiClock<'_> { fn is_enabled(&self) -> bool { diff --git a/chips/stm32f4xx/src/syscfg.rs b/chips/stm32f4xx/src/syscfg.rs index 447a9949ba..09a8ba4b82 100644 --- a/chips/stm32f4xx/src/syscfg.rs +++ b/chips/stm32f4xx/src/syscfg.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use enum_primitive::cast::FromPrimitive; use enum_primitive::enum_from_primitive; use kernel::platform::chip::ClockInterface; @@ -5,8 +9,8 @@ use kernel::utilities::registers::interfaces::ReadWriteable; use kernel::utilities::registers::{register_bitfields, ReadOnly, ReadWrite}; use kernel::utilities::StaticRef; +use crate::clocks::{phclk, Stm32f4Clocks}; use crate::gpio; -use crate::rcc; /// System configuration controller #[repr(C)] @@ -121,12 +125,12 @@ pub struct Syscfg<'a> { } impl<'a> Syscfg<'a> { - pub const fn new(rcc: &'a rcc::Rcc) -> Self { + pub const fn new(clocks: &'a dyn Stm32f4Clocks) -> Self { Self { registers: SYSCFG_BASE, - clock: SyscfgClock(rcc::PeripheralClock::new( - rcc::PeripheralClockType::APB2(rcc::PCLK2::SYSCFG), - rcc, + clock: SyscfgClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::APB2(phclk::PCLK2::SYSCFG), + clocks, )), } } @@ -226,7 +230,7 @@ impl<'a> Syscfg<'a> { } } -struct SyscfgClock<'a>(rcc::PeripheralClock<'a>); +struct SyscfgClock<'a>(phclk::PeripheralClock<'a>); impl ClockInterface for SyscfgClock<'_> { fn is_enabled(&self) -> bool { diff --git a/chips/stm32f4xx/src/tim2.rs b/chips/stm32f4xx/src/tim2.rs index 6cce48d09e..98e09387c5 100644 --- a/chips/stm32f4xx/src/tim2.rs +++ b/chips/stm32f4xx/src/tim2.rs @@ -1,7 +1,10 @@ -use cortexm4; +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use cortexm4::support::atomic; use kernel::hil::time::{ - Alarm, AlarmClient, Counter, Freq16KHz, OverflowClient, Ticks, Ticks32, Time, + Alarm, AlarmClient, Counter, Freq16KHz, Frequency, OverflowClient, Ticks, Ticks32, Time, }; use kernel::platform::chip::ClockInterface; use kernel::utilities::cells::OptionalCell; @@ -10,8 +13,8 @@ use kernel::utilities::registers::{register_bitfields, ReadWrite, WriteOnly}; use kernel::utilities::StaticRef; use kernel::ErrorCode; +use crate::clocks::{phclk, Stm32f4Clocks}; use crate::nvic; -use crate::rcc; /// General purpose timers #[repr(C)] @@ -316,12 +319,12 @@ pub struct Tim2<'a> { } impl<'a> Tim2<'a> { - pub const fn new(rcc: &'a rcc::Rcc) -> Self { + pub const fn new(clocks: &'a dyn Stm32f4Clocks) -> Self { Self { registers: TIM2_BASE, - clock: Tim2Clock(rcc::PeripheralClock::new( - rcc::PeripheralClockType::APB1(rcc::PCLK1::TIM2), - rcc, + clock: Tim2Clock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::APB1(phclk::PCLK1::TIM2), + clocks, )), client: OptionalCell::empty(), irqn: nvic::TIM2, @@ -348,14 +351,23 @@ impl<'a> Tim2<'a> { // starts the timer pub fn start(&self) { - // TIM2 uses PCLK1. By default PCLK1 uses HSI running at 16Mhz. // Before calling set_alarm, we assume clock to TIM2 has been // enabled. self.registers.arr.set(0xFFFF_FFFF - 1); - // Prescale 16Mhz to 16Khz, by dividing it by 1000. We need set EGR.UG - // in order for the prescale value to become active. - self.registers.psc.set((999 - 1) as u32); + + let clk_freq = self.clock.0.get_frequency(); + + // TIM2 uses PCLK1. Set the prescaler to the current PCLK1 frequency divided by the wanted + // frequency (16KHz). + // WARNING: When PCLK1 is not a multiple of 16KHz (e.g. PCLK1 == 25MHz), the prescaler is + // the truncated division result, which would cause loss of timer precision + // TODO: We could use a 1KHz or 1MHz frequency instead of 16KHz to cover most clock frequencies + // or use a parametric frequency (generic/argument) + let psc = clk_freq / Freq16KHz::frequency(); + self.registers.psc.set(psc - 1); + + // We need set EGR.UG in order for the prescale value to become active. self.registers.egr.write(EGR::UG::SET); self.registers.cr1.modify(CR1::CEN::SET); } @@ -441,7 +453,7 @@ impl<'a> Alarm<'a> for Tim2<'a> { } } -struct Tim2Clock<'a>(rcc::PeripheralClock<'a>); +struct Tim2Clock<'a>(phclk::PeripheralClock<'a>); impl ClockInterface for Tim2Clock<'_> { fn is_enabled(&self) -> bool { diff --git a/chips/stm32f4xx/src/trng.rs b/chips/stm32f4xx/src/trng.rs index 2fba28b83a..c16a2efdd8 100644 --- a/chips/stm32f4xx/src/trng.rs +++ b/chips/stm32f4xx/src/trng.rs @@ -1,6 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! True random number generator -use crate::rcc; +use crate::clocks::{phclk, Stm32f4Clocks}; use kernel::hil; use kernel::hil::entropy::Continue; use kernel::platform::chip::ClockInterface; @@ -10,9 +14,6 @@ use kernel::utilities::registers::{register_bitfields, ReadOnly, ReadWrite}; use kernel::utilities::StaticRef; use kernel::ErrorCode; -const RNG_BASE: StaticRef = - unsafe { StaticRef::new(0x5006_0800 as *const RngRegisters) }; - #[repr(C)] pub struct RngRegisters { cr: ReadWrite, @@ -57,12 +58,15 @@ pub struct Trng<'a> { } impl<'a> Trng<'a> { - pub const fn new(rcc: &'a rcc::Rcc) -> Trng<'a> { + pub const fn new( + registers: StaticRef, + clocks: &'a dyn Stm32f4Clocks, + ) -> Trng<'a> { Trng { - registers: RNG_BASE, - clock: RngClock(rcc::PeripheralClock::new( - rcc::PeripheralClockType::AHB2(rcc::HCLK2::RNG), - rcc, + registers, + clock: RngClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::AHB2(phclk::HCLK2::RNG), + clocks, )), client: OptionalCell::empty(), } @@ -107,7 +111,7 @@ impl<'a> Trng<'a> { } } -struct RngClock<'a>(rcc::PeripheralClock<'a>); +struct RngClock<'a>(phclk::PeripheralClock<'a>); impl ClockInterface for RngClock<'_> { fn is_enabled(&self) -> bool { diff --git a/chips/stm32f4xx/src/usart.rs b/chips/stm32f4xx/src/usart.rs index 68e8a5295b..9c2f20ebbc 100644 --- a/chips/stm32f4xx/src/usart.rs +++ b/chips/stm32f4xx/src/usart.rs @@ -1,15 +1,19 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::cell::Cell; +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; use kernel::hil; use kernel::platform::chip::ClockInterface; -use kernel::utilities::cells::OptionalCell; +use kernel::utilities::cells::{OptionalCell, TakeCell}; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; use kernel::utilities::registers::{register_bitfields, ReadWrite}; use kernel::utilities::StaticRef; use kernel::ErrorCode; -use crate::dma1; -use crate::dma1::Dma1Peripheral; -use crate::rcc; +use crate::clocks::{phclk, Stm32f4Clocks}; +use crate::dma; /// Universal synchronous asynchronous receiver transmitter #[repr(C)] @@ -145,6 +149,10 @@ register_bitfields![u32, ] ]; +// See Table 13. STM32F427xx and STM32F429xx register boundary addresses +// of the STM32F429zi datasheet +pub const USART1_BASE: StaticRef = + unsafe { StaticRef::new(0x40011000 as *const UsartRegisters) }; pub const USART2_BASE: StaticRef = unsafe { StaticRef::new(0x40004400 as *const UsartRegisters) }; pub const USART3_BASE: StaticRef = @@ -152,7 +160,7 @@ pub const USART3_BASE: StaticRef = // for use by dma1 pub(crate) fn get_address_dr(regs: StaticRef) -> u32 { - ®s.dr as *const ReadWrite as u32 + core::ptr::addr_of!(regs.dr) as u32 } #[allow(non_camel_case_types)] @@ -160,6 +168,7 @@ pub(crate) fn get_address_dr(regs: StaticRef) -> u32 { enum USARTStateRX { Idle, DMA_Receiving, + Aborted(Result<(), ErrorCode>, hil::uart::Error), } #[allow(non_camel_case_types)] @@ -167,81 +176,114 @@ enum USARTStateRX { enum USARTStateTX { Idle, DMA_Transmitting, + Aborted(Result<(), ErrorCode>), Transfer_Completing, // DMA finished, but not all bytes sent } -pub struct Usart<'a> { +pub struct Usart<'a, DMA: dma::StreamServer<'a>> { registers: StaticRef, clock: UsartClock<'a>, tx_client: OptionalCell<&'a dyn hil::uart::TransmitClient>, rx_client: OptionalCell<&'a dyn hil::uart::ReceiveClient>, - tx_dma: OptionalCell<&'a dma1::Stream<'a>>, - tx_dma_pid: Dma1Peripheral, - rx_dma: OptionalCell<&'a dma1::Stream<'a>>, - rx_dma_pid: Dma1Peripheral, + tx_dma: OptionalCell<&'a dma::Stream<'a, DMA>>, + tx_dma_pid: DMA::Peripheral, + rx_dma: OptionalCell<&'a dma::Stream<'a, DMA>>, + rx_dma_pid: DMA::Peripheral, tx_len: Cell, rx_len: Cell, usart_tx_state: Cell, usart_rx_state: Cell, + + partial_tx_buffer: TakeCell<'static, [u8]>, + partial_tx_len: Cell, + + partial_rx_buffer: TakeCell<'static, [u8]>, + partial_rx_len: Cell, + + deferred_call: DeferredCall, } // for use by `set_dma` -pub struct TxDMA<'a>(pub &'a dma1::Stream<'a>); -pub struct RxDMA<'a>(pub &'a dma1::Stream<'a>); +pub struct TxDMA<'a, DMA: dma::StreamServer<'a>>(pub &'a dma::Stream<'a, DMA>); +pub struct RxDMA<'a, DMA: dma::StreamServer<'a>>(pub &'a dma::Stream<'a, DMA>); + +impl<'a> Usart<'a, dma::Dma1<'a>> { + pub fn new_usart2(clocks: &'a dyn Stm32f4Clocks) -> Self { + Self::new( + USART2_BASE, + UsartClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::APB1(phclk::PCLK1::USART2), + clocks, + )), + dma::Dma1Peripheral::USART2_TX, + dma::Dma1Peripheral::USART2_RX, + ) + } -impl<'a> Usart<'a> { - const fn new( + pub fn new_usart3(clocks: &'a dyn Stm32f4Clocks) -> Self { + Self::new( + USART3_BASE, + UsartClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::APB1(phclk::PCLK1::USART3), + clocks, + )), + dma::Dma1Peripheral::USART3_TX, + dma::Dma1Peripheral::USART3_RX, + ) + } +} + +impl<'a> Usart<'a, dma::Dma2<'a>> { + pub fn new_usart1(clocks: &'a dyn Stm32f4Clocks) -> Self { + Self::new( + USART1_BASE, + UsartClock(phclk::PeripheralClock::new( + phclk::PeripheralClockType::APB2(phclk::PCLK2::USART1), + clocks, + )), + dma::Dma2Peripheral::USART1_TX, + dma::Dma2Peripheral::USART1_RX, + ) + } +} + +impl<'a, DMA: dma::StreamServer<'a>> Usart<'a, DMA> { + fn new( base_addr: StaticRef, clock: UsartClock<'a>, - tx_dma_pid: Dma1Peripheral, - rx_dma_pid: Dma1Peripheral, - ) -> Self { - Self { + tx_dma_pid: DMA::Peripheral, + rx_dma_pid: DMA::Peripheral, + ) -> Usart<'a, DMA> { + Usart { registers: base_addr, - clock: clock, + clock, tx_client: OptionalCell::empty(), rx_client: OptionalCell::empty(), tx_dma: OptionalCell::empty(), - tx_dma_pid: tx_dma_pid, + tx_dma_pid, rx_dma: OptionalCell::empty(), - rx_dma_pid: rx_dma_pid, + rx_dma_pid, tx_len: Cell::new(0), rx_len: Cell::new(0), usart_tx_state: Cell::new(USARTStateTX::Idle), usart_rx_state: Cell::new(USARTStateRX::Idle), - } - } - pub const fn new_usart2(rcc: &'a rcc::Rcc) -> Self { - Self::new( - USART2_BASE, - UsartClock(rcc::PeripheralClock::new( - rcc::PeripheralClockType::APB1(rcc::PCLK1::USART2), - rcc, - )), - Dma1Peripheral::USART2_TX, - Dma1Peripheral::USART2_RX, - ) - } + partial_tx_buffer: TakeCell::empty(), + partial_tx_len: Cell::new(0), - pub const fn new_usart3(rcc: &'a rcc::Rcc) -> Self { - Self::new( - USART3_BASE, - UsartClock(rcc::PeripheralClock::new( - rcc::PeripheralClockType::APB1(rcc::PCLK1::USART3), - rcc, - )), - Dma1Peripheral::USART3_TX, - Dma1Peripheral::USART3_RX, - ) + partial_rx_buffer: TakeCell::empty(), + partial_rx_len: Cell::new(0), + + deferred_call: DeferredCall::new(), + } } pub fn is_enabled_clock(&self) -> bool { @@ -256,7 +298,7 @@ impl<'a> Usart<'a> { self.clock.disable(); } - pub fn set_dma(&self, tx_dma: TxDMA<'a>, rx_dma: RxDMA<'a>) { + pub fn set_dma(&self, tx_dma: TxDMA<'a, DMA>, rx_dma: RxDMA<'a, DMA>) { self.tx_dma.set(tx_dma.0); self.rx_dma.set(rx_dma.0); } @@ -264,26 +306,61 @@ impl<'a> Usart<'a> { // According to section 25.4.13, we need to make sure that USART TC flag is // set before disabling the DMA TX on the peripheral side. pub fn handle_interrupt(&self) { - self.clear_transmit_complete(); - self.disable_transmit_complete_interrupt(); + if self.registers.sr.is_set(SR::TC) { + self.clear_transmit_complete(); + self.disable_transmit_complete_interrupt(); - // Ignore if USARTStateTX is in some other state other than - // Transfer_Completing. - if self.usart_tx_state.get() == USARTStateTX::Transfer_Completing { - self.disable_tx(); - self.usart_tx_state.set(USARTStateTX::Idle); + // Ignore if USARTStateTX is in some other state other than + // Transfer_Completing. + if self.usart_tx_state.get() == USARTStateTX::Transfer_Completing { + self.disable_tx(); + self.usart_tx_state.set(USARTStateTX::Idle); - // get buffer - let buffer = self.tx_dma.map_or(None, |tx_dma| tx_dma.return_buffer()); - let len = self.tx_len.get(); - self.tx_len.set(0); + // get buffer + let buffer = self.tx_dma.map_or(None, |tx_dma| tx_dma.return_buffer()); + let len = self.tx_len.get(); + self.tx_len.set(0); - // alert client - self.tx_client.map(|client| { - buffer.map(|buf| { - client.transmitted_buffer(buf, len, Ok(())); + // alert client + self.tx_client.map(|client| { + buffer.map(|buf| { + client.transmitted_buffer(buf, len, Ok(())); + }); }); - }); + } + } + + if self.is_enabled_error_interrupt() && self.registers.sr.is_set(SR::ORE) { + let _ = self.registers.dr.get(); // clear overrun error + if self.usart_rx_state.get() == USARTStateRX::DMA_Receiving { + self.usart_rx_state.set(USARTStateRX::Idle); + + self.disable_rx(); + self.disable_error_interrupt(); + + // get buffer + let (buffer, len) = self.rx_dma.map_or((None, 0), |rx_dma| { + // `abort_transfer` also disables the stream + rx_dma.abort_transfer() + }); + + // The number actually received is the difference between + // the requested number and the number remaining in DMA transfer. + let count = self.rx_len.get() - len as usize; + self.rx_len.set(0); + + // alert client + self.rx_client.map(|client| { + buffer.map(|buf| { + client.received_buffer( + buf, + count, + Err(ErrorCode::CANCEL), + hil::uart::Error::OverrunError, + ); + }) + }); + } } } @@ -315,9 +392,23 @@ impl<'a> Usart<'a> { self.registers.cr3.modify(CR3::DMAR::CLEAR); } + // enable interrupts for framing, overrun and noise errors + fn enable_error_interrupt(&self) { + self.registers.cr3.modify(CR3::EIE::SET); + } + + // disable interrupts for framing, overrun and noise errors + fn disable_error_interrupt(&self) { + self.registers.cr3.modify(CR3::EIE::CLEAR); + } + + // check if interrupts for framing, overrun and noise errors are enbaled + fn is_enabled_error_interrupt(&self) -> bool { + self.registers.cr3.is_set(CR3::EIE) + } + fn abort_tx(&self, rcode: Result<(), ErrorCode>) { self.disable_tx(); - self.usart_tx_state.set(USARTStateTX::Idle); // get buffer let (mut buffer, len) = self.tx_dma.map_or((None, 0), |tx_dma| { @@ -330,17 +421,21 @@ impl<'a> Usart<'a> { let count = self.tx_len.get() - len as usize; self.tx_len.set(0); - // alert client - self.tx_client.map(|client| { - buffer.take().map(|buf| { - client.transmitted_buffer(buf, count, rcode); - }); - }); + if let Some(buf) = buffer.take() { + self.partial_tx_buffer.replace(buf); + self.partial_tx_len.set(count); + + self.usart_tx_state.set(USARTStateTX::Aborted(rcode)); + + self.deferred_call.set(); + } else { + self.usart_tx_state.set(USARTStateTX::Idle); + } } fn abort_rx(&self, rcode: Result<(), ErrorCode>, error: hil::uart::Error) { self.disable_rx(); - self.usart_rx_state.set(USARTStateRX::Idle); + self.disable_error_interrupt(); // get buffer let (mut buffer, len) = self.rx_dma.map_or((None, 0), |rx_dma| { @@ -353,12 +448,16 @@ impl<'a> Usart<'a> { let count = self.rx_len.get() - len as usize; self.rx_len.set(0); - // alert client - self.rx_client.map(|client| { - buffer.take().map(|buf| { - client.received_buffer(buf, count, rcode, error); - }); - }); + if let Some(buf) = buffer.take() { + self.partial_rx_buffer.replace(buf); + self.partial_rx_len.set(count); + + self.usart_rx_state.set(USARTStateRX::Aborted(rcode, error)); + + self.deferred_call.set(); + } else { + self.usart_rx_state.set(USARTStateRX::Idle); + } } fn enable_transmit_complete_interrupt(&self) { @@ -372,9 +471,122 @@ impl<'a> Usart<'a> { fn clear_transmit_complete(&self) { self.registers.sr.modify(SR::TC::CLEAR); } + + fn transfer_done(&self, pid: DMA::Peripheral) { + if pid == self.tx_dma_pid { + self.usart_tx_state.set(USARTStateTX::Transfer_Completing); + self.enable_transmit_complete_interrupt(); + } else if pid == self.rx_dma_pid { + // In case of RX, we can call the client directly without having + // to trigger an interrupt. + if self.usart_rx_state.get() == USARTStateRX::DMA_Receiving { + self.disable_rx(); + self.disable_error_interrupt(); + self.usart_rx_state.set(USARTStateRX::Idle); + + // get buffer + let buffer = self.rx_dma.map_or(None, |rx_dma| rx_dma.return_buffer()); + + let length = self.rx_len.get(); + self.rx_len.set(0); + + // alert client + self.rx_client.map(|client| { + buffer.map(|buf| { + client.received_buffer(buf, length, Ok(()), hil::uart::Error::None); + }); + }); + } + } + } + + fn set_baud_rate(&self, baud_rate: u32) -> Result<(), ErrorCode> { + // USARTDIV calculation based on stm32-rs stm32f4xx-hal: + // https://github.com/stm32-rs/stm32f4xx-hal/blob/v0.20.0/src/serial/uart_impls.rs#L145 + // + // The equation to calculate USARTDIV is this: + // + // (Taken from STM32F411xC/E Reference Manual, Section 19.3.4, Equation 1) + // + // 16 bit oversample: OVER8 = 0 + // 8 bit oversample: OVER8 = 1 + // + // USARTDIV = (pclk) + // ------------------------ + // 8 x (2 - OVER8) x (baud) + // + // BUT, the USARTDIV has 4 "fractional" bits, which effectively means that we need to + // "correct" the equation as follows: + // + // USARTDIV = (pclk) * 16 + // ------------------------ + // 8 x (2 - OVER8) x (baud) + // + // When OVER8 is enabled, we can only use the lowest three fractional bits, so we'll need + // to shift those last four bits right one bit + + let pclk_freq = self.clock.0.get_frequency(); + + let (mantissa, fraction) = if (pclk_freq / 16) >= baud_rate { + // We have the ability to oversample to 16 bits, take advantage of it. + // + // We also add `baud / 2` to the `pclk_freq` to ensure rounding of values to the + // closest scale, rather than the floored behavior of normal integer division. + let div = (pclk_freq + (baud_rate / 2)) / baud_rate; + + self.registers.cr1.modify(CR1::OVER8::CLEAR); + + (div >> 4, div & 0x0F) + } else if (pclk_freq / 8) >= baud_rate { + // We are close enough to pclk where we can only + // oversample 8. + + // See note above regarding `baud` and rounding. + let div = ((pclk_freq * 2) + (baud_rate / 2)) / baud_rate; + + self.registers.cr1.modify(CR1::OVER8::SET); + + // Ensure the the fractional bits (only 3) are right-aligned. + (div >> 4, (div & 0x0F) >> 1) + } else { + return Err(ErrorCode::INVAL); + }; + + self.registers.brr.modify(BRR::DIV_Mantissa.val(mantissa)); + self.registers.brr.modify(BRR::DIV_Fraction.val(fraction)); + Ok(()) + } } -impl<'a> hil::uart::Transmit<'a> for Usart<'a> { +impl<'a, DMA: dma::StreamServer<'a>> DeferredCallClient for Usart<'a, DMA> { + fn register(&'static self) { + self.deferred_call.register(self); + } + + fn handle_deferred_call(&self) { + if let USARTStateTX::Aborted(rcode) = self.usart_tx_state.get() { + // alert client + self.tx_client.map(|client| { + self.partial_tx_buffer.take().map(|buf| { + client.transmitted_buffer(buf, self.partial_tx_len.get(), rcode); + }); + }); + self.usart_tx_state.set(USARTStateTX::Idle); + } + + if let USARTStateRX::Aborted(rcode, error) = self.usart_rx_state.get() { + // alert client + self.rx_client.map(|client| { + self.partial_rx_buffer.take().map(|buf| { + client.received_buffer(buf, self.partial_rx_len.get(), rcode, error); + }); + }); + self.usart_rx_state.set(USARTStateRX::Idle); + } + } +} + +impl<'a, DMA: dma::StreamServer<'a>> hil::uart::Transmit<'a> for Usart<'a, DMA> { fn set_transmit_client(&self, client: &'a dyn hil::uart::TransmitClient) { self.tx_client.set(client); } @@ -420,35 +632,26 @@ impl<'a> hil::uart::Transmit<'a> for Usart<'a> { } } -impl<'a> hil::uart::Configure for Usart<'a> { +impl<'a, DMA: dma::StreamServer<'a>> hil::uart::Configure for Usart<'a, DMA> { fn configure(&self, params: hil::uart::Parameters) -> Result<(), ErrorCode> { - if params.baud_rate != 115200 - || params.stop_bits != hil::uart::StopBits::One + if params.stop_bits != hil::uart::StopBits::One || params.parity != hil::uart::Parity::None - || params.hw_flow_control != false + || params.hw_flow_control || params.width != hil::uart::Width::Eight { - panic!( - "Currently we only support uart setting of 115200bps 8N1, no hardware flow control" - ); + panic!("Currently we only support uart setting of 8N1, no hardware flow control"); } // Configure the word length - 0: 1 Start bit, 8 Data bits, n Stop bits self.registers.cr1.modify(CR1::M::CLEAR); // Set the stop bit length - 00: 1 Stop bits - self.registers.cr2.modify(CR2::STOP.val(0b00 as u32)); + self.registers.cr2.modify(CR2::STOP.val(0b00_u32)); // Set no parity self.registers.cr1.modify(CR1::PCE::CLEAR); - // Set the baud rate. By default OVER8 is 0 (oversampling by 16) and - // PCLK1 is at 16Mhz. The desired baud rate is 115.2KBps. So according - // to Table 149 of reference manual, the value for BRR is 8.6875 - // DIV_Fraction = 0.6875 * 16 = 11 = 0xB - // DIV_Mantissa = 8 = 0x8 - self.registers.brr.modify(BRR::DIV_Fraction.val(0xB as u32)); - self.registers.brr.modify(BRR::DIV_Mantissa.val(0x8 as u32)); + self.set_baud_rate(params.baud_rate)?; // Enable transmit block self.registers.cr1.modify(CR1::TE::SET); @@ -463,7 +666,7 @@ impl<'a> hil::uart::Configure for Usart<'a> { } } -impl<'a> hil::uart::Receive<'a> for Usart<'a> { +impl<'a, DMA: dma::StreamServer<'a>> hil::uart::Receive<'a> for Usart<'a, DMA> { fn set_receive_client(&self, client: &'a dyn hil::uart::ReceiveClient) { self.rx_client.set(client); } @@ -489,6 +692,8 @@ impl<'a> hil::uart::Receive<'a> for Usart<'a> { self.usart_rx_state.set(USARTStateRX::DMA_Receiving); + self.enable_error_interrupt(); + // enable dma rx on the peripheral side self.enable_rx(); Ok(()) @@ -504,36 +709,19 @@ impl<'a> hil::uart::Receive<'a> for Usart<'a> { } } -impl dma1::StreamClient for Usart<'_> { - fn transfer_done(&self, pid: dma1::Dma1Peripheral) { - if pid == self.tx_dma_pid { - self.usart_tx_state.set(USARTStateTX::Transfer_Completing); - self.enable_transmit_complete_interrupt(); - } else if pid == self.rx_dma_pid { - // In case of RX, we can call the client directly without having - // to trigger an interrupt. - if self.usart_rx_state.get() == USARTStateRX::DMA_Receiving { - self.disable_rx(); - self.usart_rx_state.set(USARTStateRX::Idle); - - // get buffer - let buffer = self.rx_dma.map_or(None, |rx_dma| rx_dma.return_buffer()); - - let length = self.rx_len.get(); - self.rx_len.set(0); +impl<'a> dma::StreamClient<'a, dma::Dma1<'a>> for Usart<'a, dma::Dma1<'a>> { + fn transfer_done(&self, pid: dma::Dma1Peripheral) { + self.transfer_done(pid); + } +} - // alert client - self.rx_client.map(|client| { - buffer.map(|buf| { - client.received_buffer(buf, length, Ok(()), hil::uart::Error::None); - }); - }); - } - } +impl<'a> dma::StreamClient<'a, dma::Dma2<'a>> for Usart<'a, dma::Dma2<'a>> { + fn transfer_done(&self, pid: dma::Dma2Peripheral) { + self.transfer_done(pid); } } -struct UsartClock<'a>(rcc::PeripheralClock<'a>); +struct UsartClock<'a>(phclk::PeripheralClock<'a>); impl ClockInterface for UsartClock<'_> { fn is_enabled(&self) -> bool { diff --git a/chips/swerv/Cargo.toml b/chips/swerv/Cargo.toml index 394f5e2a1e..c2acf98818 100644 --- a/chips/swerv/Cargo.toml +++ b/chips/swerv/Cargo.toml @@ -1,10 +1,17 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "swerv" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] tock-registers = { path = "../../libraries/tock-register-interface" } kernel = { path = "../../kernel" } riscv-csr = { path = "../../libraries/riscv-csr" } + +[lints] +workspace = true diff --git a/chips/swerv/src/eh1_pic.rs b/chips/swerv/src/eh1_pic.rs index 06505780ca..8a29fc890f 100644 --- a/chips/swerv/src/eh1_pic.rs +++ b/chips/swerv/src/eh1_pic.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Platform Level Interrupt Control peripheral driver for SweRV EH1. use kernel::utilities::cells::VolatileCell; diff --git a/chips/swerv/src/eh1_timer.rs b/chips/swerv/src/eh1_timer.rs index c78ab7ea93..e0aa6fe0d7 100644 --- a/chips/swerv/src/eh1_timer.rs +++ b/chips/swerv/src/eh1_timer.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Internal Timer use kernel::hil::time; @@ -176,7 +180,7 @@ impl<'a> Alarm<'a> for Timer<'a> { } fn minimum_dt(&self) -> Self::Ticks { - Self::Ticks::from(1 as u32) + Self::Ticks::from(1_u32) } } diff --git a/chips/swerv/src/lib.rs b/chips/swerv/src/lib.rs index a2dab69f42..0bdfd64086 100644 --- a/chips/swerv/src/lib.rs +++ b/chips/swerv/src/lib.rs @@ -1,6 +1,9 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Drivers and support modules for SweRV SoCs -#![feature(const_fn_trait_bound)] #![no_std] #![crate_name = "swerv"] #![crate_type = "rlib"] diff --git a/chips/swervolf-eh1/Cargo.toml b/chips/swervolf-eh1/Cargo.toml index 96461350db..7d19c0484e 100644 --- a/chips/swervolf-eh1/Cargo.toml +++ b/chips/swervolf-eh1/Cargo.toml @@ -1,11 +1,18 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "swervolf-eh1" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] swerv = { path = "../swerv" } rv32i = { path = "../../arch/rv32i" } kernel = { path = "../../kernel" } + +[lints] +workspace = true diff --git a/chips/swervolf-eh1/src/chip.rs b/chips/swervolf-eh1/src/chip.rs index 6d193d7cfd..af6d015472 100644 --- a/chips/swervolf-eh1/src/chip.rs +++ b/chips/swervolf-eh1/src/chip.rs @@ -1,7 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! High-level setup and interrupt mapping for the chip. use core::fmt::Write; -use kernel; +use core::ptr::addr_of; use kernel::platform::chip::{Chip, InterruptService}; use kernel::utilities::cells::VolatileCell; use kernel::utilities::registers::interfaces::{ReadWriteable, Readable}; @@ -23,7 +27,7 @@ pub const IRQ_UART: u32 = 1; /// This is a fake value used to indicate a timer1 interrupt pub const IRQ_TIMER1: u32 = 0xFFFF_FFFF; -pub struct SweRVolf<'a, I: InterruptService<()> + 'a> { +pub struct SweRVolf<'a, I: InterruptService + 'a> { userspace_kernel_boundary: SysCall, pic: &'a Pic, scheduler_timer: swerv::eh1_timer::Timer<'static>, @@ -45,7 +49,7 @@ impl<'a> SweRVolfDefaultPeripherals<'a> { } } -impl<'a> InterruptService<()> for SweRVolfDefaultPeripherals<'a> { +impl<'a> InterruptService for SweRVolfDefaultPeripherals<'a> { unsafe fn service_interrupt(&self, interrupt: u32) -> bool { match interrupt { IRQ_UART => { @@ -60,20 +64,16 @@ impl<'a> InterruptService<()> for SweRVolfDefaultPeripherals<'a> { } true } - - unsafe fn service_deferred_call(&self, _: ()) -> bool { - false - } } -impl<'a, I: InterruptService<()> + 'a> SweRVolf<'a, I> { +impl<'a, I: InterruptService + 'a> SweRVolf<'a, I> { pub unsafe fn new( pic_interrupt_service: &'a I, mtimer: &'static crate::syscon::SysCon, ) -> Self { Self { userspace_kernel_boundary: SysCall::new(), - pic: &PIC, + pic: &*addr_of!(PIC), scheduler_timer: swerv::eh1_timer::Timer::new(swerv::eh1_timer::TimerNumber::ZERO), mtimer, pic_interrupt_service, @@ -101,7 +101,7 @@ impl<'a, I: InterruptService<()> + 'a> SweRVolf<'a, I> { } } -impl<'a, I: InterruptService<()> + 'a> kernel::platform::chip::Chip for SweRVolf<'a, I> { +impl<'a, I: InterruptService + 'a> kernel::platform::chip::Chip for SweRVolf<'a, I> { type MPU = (); type UserspaceKernelBoundary = SysCall; @@ -145,7 +145,7 @@ impl<'a, I: InterruptService<()> + 'a> kernel::platform::chip::Chip for SweRVolf } } - if !mip.matches_any(mip::mtimer::SET) + if !mip.any_matching_bits_set(mip::mtimer::SET) && !unsafe { TIMER0_IRQ.get() } && !unsafe { TIMER1_IRQ.get() } && self.pic.get_saved_interrupts().is_none() @@ -163,7 +163,7 @@ impl<'a, I: InterruptService<()> + 'a> kernel::platform::chip::Chip for SweRVolf fn has_pending_interrupts(&self) -> bool { let mip = CSR.mip.extract(); self.pic.get_saved_interrupts().is_some() - || mip.matches_any(mip::mtimer::SET) + || mip.any_matching_bits_set(mip::mtimer::SET) || unsafe { TIMER0_IRQ.get() } || unsafe { TIMER1_IRQ.get() } } diff --git a/chips/swervolf-eh1/src/lib.rs b/chips/swervolf-eh1/src/lib.rs index 004e947873..2ef71704f5 100644 --- a/chips/swervolf-eh1/src/lib.rs +++ b/chips/swervolf-eh1/src/lib.rs @@ -1,6 +1,9 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Drivers and chip support for SweRVolf. -#![feature(asm, const_fn_trait_bound, naked_functions)] #![no_std] #![crate_name = "swervolf_eh1"] #![crate_type = "rlib"] diff --git a/chips/swervolf-eh1/src/syscon.rs b/chips/swervolf-eh1/src/syscon.rs index d6753cc011..916b113553 100644 --- a/chips/swervolf-eh1/src/syscon.rs +++ b/chips/swervolf-eh1/src/syscon.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! System Controller driver. use kernel::hil::time::{self, Ticks64}; diff --git a/chips/swervolf-eh1/src/uart.rs b/chips/swervolf-eh1/src/uart.rs index 4fc02af1fa..4d36021289 100644 --- a/chips/swervolf-eh1/src/uart.rs +++ b/chips/swervolf-eh1/src/uart.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! ns16550 compatible UART driver. pub const UART_BASE: StaticRef = @@ -47,7 +51,7 @@ pub struct Uart<'a> { } impl<'a> Uart<'a> { - pub const fn new(base: StaticRef) -> Uart<'a> { + pub fn new(base: StaticRef) -> Uart<'a> { Uart { registers: base, tx_client: OptionalCell::empty(), @@ -100,7 +104,7 @@ impl hil::uart::Configure for Uart<'_> { if params.parity != hil::uart::Parity::None { return Err(ErrorCode::NOSUPPORT); } - if params.hw_flow_control != false { + if params.hw_flow_control { return Err(ErrorCode::NOSUPPORT); } diff --git a/chips/virtio/Cargo.toml b/chips/virtio/Cargo.toml new file mode 100644 index 0000000000..6d3756e2c2 --- /dev/null +++ b/chips/virtio/Cargo.toml @@ -0,0 +1,15 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[package] +name = "virtio" +version.workspace = true +authors.workspace = true +edition.workspace = true + +[dependencies] +kernel = { path = "../../kernel" } + +[lints] +workspace = true diff --git a/chips/virtio/README.md b/chips/virtio/README.md new file mode 100644 index 0000000000..d6858fedcf --- /dev/null +++ b/chips/virtio/README.md @@ -0,0 +1,10 @@ +VIRTIO MMIO Devices +=================== + +This crate contains drivers for interacting with +[virtio](https://wiki.osdev.org/Virtio) devices via MMIO. The +[formal specification](https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html) +defines a "Virtio Over MMIO" that we implement here. + +This psuedo-chip can be included on platforms that use virtio to emulate I/O +devices. diff --git a/chips/virtio/src/devices/mod.rs b/chips/virtio/src/devices/mod.rs new file mode 100644 index 0000000000..8b0b61b2c0 --- /dev/null +++ b/chips/virtio/src/devices/mod.rs @@ -0,0 +1,145 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use kernel::ErrorCode; + +pub mod virtio_net; +pub mod virtio_rng; + +/// VirtIO Device Types. +/// +/// VirtIO is a flexible bus which can be used to expose various kinds of +/// virtual devices, such as network drivers, serial consoles, block devices or +/// random number generators. A VirtIO bus endpoint announces which type of +/// device it represents (and hence also which rules and semantics the VirtIO +/// driver should follow). +/// +/// This enum maps the VirtIO device IDs to human-readable variants of an enum, +/// which can be used throughout the code base. Users should not rely on this +/// enum not being extended. Whenever an official device ID is missing, it can +/// be added to this enumeration. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +#[repr(u32)] +#[non_exhaustive] +pub enum VirtIODeviceType { + NetworkCard = 1, + BlockDevice = 2, + Console = 3, + EntropySource = 4, + TraditionalMemoryBallooning = 5, + IoMemory = 6, + RPMSG = 7, + SCSIHost = 8, + Transport9P = 9, + Mac80211Wlan = 10, + RPROCSerial = 11, + VirtIOCAIF = 12, + MemoryBalloon = 13, + GPUDevice = 14, + TimerClockDevice = 15, + InputDevice = 16, + SocketDevice = 17, + CryptoDevice = 18, + SignalDistributionModule = 19, + PstoreDevice = 20, + IOMMUDevice = 21, + MemoryDevice = 22, +} + +impl VirtIODeviceType { + /// Try to create a [`VirtIODeviceType`] enum variant from a supplied + /// numeric device ID. + pub fn from_device_id(id: u32) -> Option { + use VirtIODeviceType as DT; + + match id { + 1 => Some(DT::NetworkCard), + 2 => Some(DT::BlockDevice), + 3 => Some(DT::Console), + 4 => Some(DT::EntropySource), + 5 => Some(DT::TraditionalMemoryBallooning), + 6 => Some(DT::IoMemory), + 7 => Some(DT::RPMSG), + 8 => Some(DT::SCSIHost), + 9 => Some(DT::Transport9P), + 10 => Some(DT::Mac80211Wlan), + 11 => Some(DT::RPROCSerial), + 12 => Some(DT::VirtIOCAIF), + 13 => Some(DT::MemoryBalloon), + 14 => Some(DT::GPUDevice), + 15 => Some(DT::TimerClockDevice), + 16 => Some(DT::InputDevice), + 17 => Some(DT::SocketDevice), + 18 => Some(DT::CryptoDevice), + 19 => Some(DT::SignalDistributionModule), + 20 => Some(DT::PstoreDevice), + 21 => Some(DT::IOMMUDevice), + 22 => Some(DT::MemoryDevice), + _ => None, + } + } + + /// Convert a [`VirtIODeviceType`] variant to its corresponding device ID. + pub fn to_device_id(device_type: VirtIODeviceType) -> u32 { + device_type as u32 + } +} + +/// VirtIO Device Driver. +/// +/// This trait is to be implemented by drivers for exposed VirtIO devices, using +/// the transports provided in [`crate::transports`] and queues in +/// [`crate::queues`] to communicate with VirtIO devices. +pub trait VirtIODeviceDriver { + /// VirtIO feature negotiation. + /// + /// This function is passed all driver-specific feature bits which the + /// device exposes. Based on this function, the driver can select which + /// features to enable through the return value of this function. This + /// function is executed through the VirtIO transport, potentially before + /// the device is initialized. As such, implementations of this function + /// should be pure and only depend on the `offered_features` input + /// parameter. + fn negotiate_features(&self, offered_features: u64) -> Option; + + /// VirtIO device type which the driver supports. + /// + /// This function must return the VirtIO device type that the driver is able + /// to drive. Implementations of this function must be pure and return a + /// constant value. + fn device_type(&self) -> VirtIODeviceType; + + /// Hook called before the transport indicates `DRIVER_OK` to the device. + /// + /// Because this trait must be object safe, a device cannot convey arbitrary + /// errors through this interface. When this function returns an error, the + /// transport will indicate `FAILED` to the device and return the error to + /// the caller of + /// [`VirtIOTransport::initialize`](super::transports::VirtIOTransport::initialize). + /// The driver can store a more elaborate error internally and expose it + /// through a custom interface. + /// + /// A default implementation of this function is provided which does nothing + /// and returns `Ok(())`. + fn pre_device_initialization(&self) -> Result<(), ErrorCode> { + Ok(()) + } + + /// Hook called after the transport indicated `DRIVER_OK` to the device. + /// + /// Because this trait must be object safe, a device cannot convey arbitrary + /// errors through this interface. When this function returns an error, the + /// transport will **NOT** indicate `FAILED` to the device, but return the + /// error to the caller of + /// [`VirtIOTransport::initialize`](super::transports::VirtIOTransport::initialize). + /// The driver can store a more elaborate error internally and expose it + /// through a custom interface. The driver remains responsible for any + /// deinitialization of the device as a result of this error. + /// + /// A default implementation of this function is provided which does nothing + /// and returns `Ok(())`. + fn device_initialized(&self) -> Result<(), ErrorCode> { + Ok(()) + } +} diff --git a/chips/virtio/src/devices/virtio_net.rs b/chips/virtio/src/devices/virtio_net.rs new file mode 100644 index 0000000000..0a5e910aff --- /dev/null +++ b/chips/virtio/src/devices/virtio_net.rs @@ -0,0 +1,247 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use core::cell::Cell; + +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::registers::{register_bitfields, LocalRegisterCopy}; +use kernel::ErrorCode; + +use super::super::devices::{VirtIODeviceDriver, VirtIODeviceType}; +use super::super::queues::split_queue::{SplitVirtqueue, SplitVirtqueueClient, VirtqueueBuffer}; + +register_bitfields![u64, + VirtIONetFeatures [ + VirtIONetFCsum OFFSET(0) NUMBITS(1), + VirtIONetFGuestCsum OFFSET(1) NUMBITS(1), + VirtIONetFCtrlGuestOffloads OFFSET(2) NUMBITS(1), + VirtIONetFMtu OFFSET(3) NUMBITS(1), + VirtIONetFMac OFFSET(5) NUMBITS(1), + VirtIONetFGuestTso4 OFFSET(7) NUMBITS(1), + VirtIONetFGuestTso6 OFFSET(8) NUMBITS(1), + VirtIONetFGuestEcn OFFSET(9) NUMBITS(1), + VirtIONetFGuestUfo OFFSET(10) NUMBITS(1), + VirtIONetFHostTso4 OFFSET(11) NUMBITS(1), + VirtIONetFHostTso6 OFFSET(12) NUMBITS(1), + VirtIONetFHostEcn OFFSET(13) NUMBITS(1), + VirtIONetFHostUfo OFFSET(14) NUMBITS(1), + VirtIONetFMrgRxbuf OFFSET(15) NUMBITS(1), + VirtIONetFStatus OFFSET(16) NUMBITS(1), + VirtIONetFCtrlVq OFFSET(17) NUMBITS(1), + VirtIONetFCtrlRx OFFSET(18) NUMBITS(1), + VirtIONetFCtrlVlan OFFSET(19) NUMBITS(1), + VirtIONetFGuestAnnounce OFFSET(21) NUMBITS(1), + VirtIONetFMq OFFSET(22) NUMBITS(1), + VirtIONetFCtrlMacAddr OFFSET(23) NUMBITS(1), + // these feature bits would not be passed through the driver, as + // they are in a region reserved for future extensions? + VirtIONetFRscExt OFFSET(61) NUMBITS(1), + VirtIONetFStandby OFFSET(62) NUMBITS(1), + ] +]; + +pub struct VirtIONet<'a> { + id: Cell, + rxqueue: &'a SplitVirtqueue<'static, 'static, 2>, + txqueue: &'a SplitVirtqueue<'static, 'static, 2>, + tx_header: OptionalCell<&'static mut [u8; 12]>, + rx_header: OptionalCell<&'static mut [u8]>, + rx_buffer: OptionalCell<&'static mut [u8]>, + client: OptionalCell<&'a dyn VirtIONetClient>, +} + +impl<'a> VirtIONet<'a> { + pub fn new( + id: usize, + txqueue: &'a SplitVirtqueue<'static, 'static, 2>, + tx_header: &'static mut [u8; 12], + rxqueue: &'a SplitVirtqueue<'static, 'static, 2>, + rx_header: &'static mut [u8], + rx_buffer: &'static mut [u8], + ) -> VirtIONet<'a> { + txqueue.enable_used_callbacks(); + rxqueue.enable_used_callbacks(); + + VirtIONet { + id: Cell::new(id), + rxqueue, + txqueue, + tx_header: OptionalCell::new(tx_header), + client: OptionalCell::empty(), + rx_header: OptionalCell::new(rx_header), + rx_buffer: OptionalCell::new(rx_buffer), + } + } + + pub fn id(&self) -> usize { + self.id.get() + } + + pub fn set_client(&self, client: &'a dyn VirtIONetClient) { + self.client.set(client); + } + + // This is not executed as part of the `device_initialized` hook to avoid + // missing any packets if a client has not been registered, and because this + // device can be used in a transmit-only fashion before invoking this + // function. + pub fn enable_rx(&self) { + // To start operation, put the receive buffers into the device initially + let rx_buffer = self.rx_buffer.take().unwrap(); + let rx_buffer_len = rx_buffer.len(); + + let mut buffer_chain = [ + Some(VirtqueueBuffer { + buf: self.rx_header.take().unwrap(), + len: 12, + device_writeable: true, + }), + Some(VirtqueueBuffer { + buf: rx_buffer, + len: rx_buffer_len, + device_writeable: true, + }), + ]; + + self.rxqueue + .provide_buffer_chain(&mut buffer_chain) + .unwrap(); + } + + pub fn return_rx_buffer(&self, buf: &'static mut [u8]) { + assert!(self.rx_buffer.is_none()); + assert!(self.rx_header.is_some()); + self.rx_buffer.replace(buf); + + // Re-register the RX buffer with the Virtqueue: + self.enable_rx(); + } + + pub fn send_packet( + &self, + packet: &'static mut [u8], + packet_len: usize, + ) -> Result<(), (&'static mut [u8], ErrorCode)> { + // Try to get a hold of the header buffer + // + // Otherwise, the device is currently busy transmissing a buffer + // + // TODO: Implement simultaneous transmissions + let mut packet_buf = Some(VirtqueueBuffer { + buf: packet, + len: packet_len, + device_writeable: false, + }); + + let header_buf = self + .tx_header + .take() + .ok_or(ErrorCode::BUSY) + .map_err(|ret| (packet_buf.take().unwrap().buf, ret))?; + + // Write the header + // + // TODO: Can this be done more elegantly using a struct of registers? + header_buf[0] = 0; // flags -> we don't want checksumming + header_buf[1] = 0; // gso -> no checksumming or fragmentation + header_buf[2] = 0; // hdr_len_low + header_buf[3] = 0; // hdr_len_high + header_buf[4] = 0; // gso_size + header_buf[5] = 0; // gso_size + header_buf[6] = 0; // csum_start + header_buf[7] = 0; // csum_start + header_buf[8] = 0; // csum_offset + header_buf[9] = 0; // csum_offsetb + header_buf[10] = 0; // num_buffers + header_buf[11] = 0; // num_buffers + + let mut buffer_chain = [ + Some(VirtqueueBuffer { + buf: header_buf, + len: 12, + device_writeable: false, + }), + packet_buf.take(), + ]; + + self.txqueue + .provide_buffer_chain(&mut buffer_chain) + .map_err(move |ret| (buffer_chain[1].take().unwrap().buf, ret))?; + + Ok(()) + } +} + +impl<'a> SplitVirtqueueClient<'static> for VirtIONet<'a> { + fn buffer_chain_ready( + &self, + queue_number: u32, + buffer_chain: &mut [Option>], + bytes_used: usize, + ) { + if queue_number == self.rxqueue.queue_number().unwrap() { + // Received a packet + + let rx_header = buffer_chain[0].take().expect("No header buffer").buf; + // TODO: do something with the header + self.rx_header.replace(rx_header); + + let rx_buffer = buffer_chain[1].take().expect("No rx content buffer").buf; + self.client.map(move |client| { + client.packet_received(self.id.get(), rx_buffer, bytes_used - 12) + }); + } else if queue_number == self.txqueue.queue_number().unwrap() { + // Sent a packet + + let header_buf = buffer_chain[0].take().expect("No header buffer").buf; + self.tx_header.replace(header_buf.try_into().unwrap()); + + let packet_buf = buffer_chain[1].take().expect("No packet buffer").buf; + self.client + .map(move |client| client.packet_sent(self.id.get(), packet_buf)); + } else { + panic!("Callback from unknown queue"); + } + } +} + +impl<'a> VirtIODeviceDriver for VirtIONet<'a> { + fn negotiate_features(&self, offered_features: u64) -> Option { + let offered_features = + LocalRegisterCopy::::new(offered_features); + let mut negotiated_features = LocalRegisterCopy::::new(0); + + if offered_features.is_set(VirtIONetFeatures::VirtIONetFMac) { + // VIRTIO_NET_F_MAC offered, which means that the device has a MAC + // address. Accept this feature, which is required for this driver + // for now. + negotiated_features.modify(VirtIONetFeatures::VirtIONetFMac::SET); + } else { + return None; + } + + // TODO: QEMU doesn't offer this, but don't we need it? Does QEMU + // implicitly provide the feature but not offer it? Find out! + // if offered_features & (1 << 15) != 0 { + // // VIRTIO_NET_F_MRG_RXBUF + // // + // // accept + // negotiated_features |= 1 << 15; + // } else { + // panic!("Missing NET_F_MRG_RXBUF"); + // } + + // Ignore everything else + Some(negotiated_features.get()) + } + + fn device_type(&self) -> VirtIODeviceType { + VirtIODeviceType::NetworkCard + } +} + +pub trait VirtIONetClient { + fn packet_sent(&self, id: usize, buffer: &'static mut [u8]); + fn packet_received(&self, id: usize, buffer: &'static mut [u8], len: usize); +} diff --git a/chips/virtio/src/devices/virtio_rng.rs b/chips/virtio/src/devices/virtio_rng.rs new file mode 100644 index 0000000000..6a55550419 --- /dev/null +++ b/chips/virtio/src/devices/virtio_rng.rs @@ -0,0 +1,209 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use core::cell::Cell; + +use kernel::deferred_call::{DeferredCall, DeferredCallClient}; +use kernel::hil::rng::{Client as RngClient, Continue as RngCont, Rng}; +use kernel::utilities::cells::OptionalCell; +use kernel::ErrorCode; + +use super::super::devices::{VirtIODeviceDriver, VirtIODeviceType}; +use super::super::queues::split_queue::{SplitVirtqueue, SplitVirtqueueClient, VirtqueueBuffer}; + +pub struct VirtIORng<'a, 'b> { + virtqueue: &'a SplitVirtqueue<'a, 'b, 1>, + buffer_capacity: Cell, + callback_pending: Cell, + deferred_call: DeferredCall, + client: OptionalCell<&'a dyn RngClient>, +} + +impl<'a, 'b> VirtIORng<'a, 'b> { + pub fn new(virtqueue: &'a SplitVirtqueue<'a, 'b, 1>) -> VirtIORng<'a, 'b> { + VirtIORng { + virtqueue, + buffer_capacity: Cell::new(0), + callback_pending: Cell::new(false), + deferred_call: DeferredCall::new(), + client: OptionalCell::empty(), + } + } + + pub fn provide_buffer(&self, buf: &'b mut [u8]) -> Result { + let len = buf.len(); + if len < 4 { + // We don't yet support merging of randomness of multiple buffers + // + // Allowing a buffer with less than 4 elements will cause + // the callback to never be called, while the buffer is + // reinserted into the queue + return Err((buf, ErrorCode::INVAL)); + } + + let mut buffer_chain = [Some(VirtqueueBuffer { + buf, + len, + device_writeable: true, + })]; + + let res = self.virtqueue.provide_buffer_chain(&mut buffer_chain); + + match res { + Err(ErrorCode::NOMEM) => { + // Hand back the buffer, the queue MUST NOT write partial + // buffer chains + let buf = buffer_chain[0].take().unwrap().buf; + Err((buf, ErrorCode::NOMEM)) + } + Err(e) => panic!("Unexpected error {:?}", e), + Ok(()) => { + let mut cap = self.buffer_capacity.get(); + cap += len; + self.buffer_capacity.set(cap); + Ok(cap) + } + } + } + + fn buffer_chain_callback( + &self, + buffer_chain: &mut [Option>], + bytes_used: usize, + ) { + // Disable further callbacks, until we're sure we need them + // + // The used buffers should stay in the queue until a client is + // ready to consume them + self.virtqueue.disable_used_callbacks(); + + // We only have buffer chains of a single buffer + let buf = buffer_chain[0].take().unwrap().buf; + + // We have taken out a buffer, hence decrease the available capacity + assert!(self.buffer_capacity.get() >= buf.len()); + + // It could've happened that we don't require the callback any + // more, hence check beforehand + let cont = if self.callback_pending.get() { + // The callback is no longer pending + self.callback_pending.set(false); + + let mut u32randiter = buf[0..bytes_used].chunks(4).filter_map(|slice| { + if slice.len() < 4 { + None + } else { + Some(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]])) + } + }); + + // For now we don't use left-over randomness and assume the + // client has consumed the entire iterator + self.client + .map(|client| client.randomness_available(&mut u32randiter, Ok(()))) + .unwrap_or(RngCont::Done) + } else { + RngCont::Done + }; + + if let RngCont::More = cont { + // Returning more is the equivalent of calling .get() on + // the Rng trait. + + // TODO: what if this call fails? + let _ = self.get(); + } + + // In any case, reinsert the buffer for further processing + self.provide_buffer(buf).expect("Buffer reinsertion failed"); + } +} + +impl<'a, 'b> Rng<'a> for VirtIORng<'a, 'b> { + fn get(&self) -> Result<(), ErrorCode> { + // Minimum buffer capacity must be 4 bytes for a single 32-bit + // word + if self.buffer_capacity.get() < 4 { + Err(ErrorCode::FAIL) + } else if self.client.is_none() { + Err(ErrorCode::FAIL) + } else if self.callback_pending.get() { + Err(ErrorCode::OFF) + } else if self.virtqueue.used_descriptor_chains_count() < 1 { + // There is no buffer ready in the queue, so let's rely + // purely on queue callbacks to notify us of the next + // incoming one + self.callback_pending.set(true); + self.virtqueue.enable_used_callbacks(); + Ok(()) + } else { + // There is a buffer in the virtqueue, get it and return + // it to a client in a deferred call + self.callback_pending.set(true); + self.deferred_call.set(); + Ok(()) + } + } + + fn cancel(&self) -> Result<(), ErrorCode> { + // Cancel by setting the callback_pending flag to false which + // MUST be checked prior to every callback + self.callback_pending.set(false); + + // For efficiency reasons, also unsubscribe from the virtqueue + // callbacks, which will let the buffers remain in the queue + // for future use + self.virtqueue.disable_used_callbacks(); + + Ok(()) + } + + fn set_client(&self, client: &'a dyn RngClient) { + self.client.set(client); + } +} + +impl<'a, 'b> SplitVirtqueueClient<'b> for VirtIORng<'a, 'b> { + fn buffer_chain_ready( + &self, + _queue_number: u32, + buffer_chain: &mut [Option>], + bytes_used: usize, + ) { + self.buffer_chain_callback(buffer_chain, bytes_used) + } +} + +impl<'a, 'b> DeferredCallClient for VirtIORng<'a, 'b> { + fn register(&'static self) { + self.deferred_call.register(self); + } + + fn handle_deferred_call(&self) { + // Try to extract a descriptor chain + if let Some((mut chain, bytes_used)) = self.virtqueue.pop_used_buffer_chain() { + self.buffer_chain_callback(&mut chain, bytes_used) + } else { + // If we don't get a buffer, this must be a race condition + // which should not occur + // + // Prior to setting a deferred call, all virtqueue + // interrupts must be disabled so that no used buffer is + // removed before the deferred call callback + panic!("VirtIO RNG: deferred call callback with empty queue"); + } + } +} + +impl<'a, 'b> VirtIODeviceDriver for VirtIORng<'a, 'b> { + fn negotiate_features(&self, _offered_features: u64) -> Option { + // We don't support any special features and do not care about + // what the device offers. + Some(0) + } + + fn device_type(&self) -> VirtIODeviceType { + VirtIODeviceType::EntropySource + } +} diff --git a/chips/virtio/src/lib.rs b/chips/virtio/src/lib.rs new file mode 100644 index 0000000000..27c99d412b --- /dev/null +++ b/chips/virtio/src/lib.rs @@ -0,0 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! VirtIO support for Tock. + +#![no_std] +#![crate_name = "virtio"] +#![crate_type = "rlib"] + +pub mod devices; +pub mod queues; +pub mod transports; diff --git a/chips/virtio/src/queues/mod.rs b/chips/virtio/src/queues/mod.rs new file mode 100644 index 0000000000..09e979ce32 --- /dev/null +++ b/chips/virtio/src/queues/mod.rs @@ -0,0 +1,98 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! VirtIO Virtqueues. +//! +//! This module and its submodules provide abstractions for and +//! implementations of VirtIO Virtqueues. For more information, see +//! the documentation of the [`Virtqueue`] trait. + +pub mod split_queue; + +/// A set of addresses representing a Virtqueue in memory. +pub struct VirtqueueAddresses { + pub descriptor_area: u64, + pub driver_area: u64, + pub device_area: u64, +} + +/// A VirtIO Virtqueue. +/// +/// Virtqueues are VirtIO's mechanism to exchange data between a host and a +/// guest. Each queue instance provides a bidirectional communication channel to +/// send data from the guest (VirtIO driver) to the host (VirtIO device) and +/// back. Typically, a given Virtqueue is only used for communication in a +/// single direction. A VirtIO device can support multiple Virtqueues. +/// +/// Fundamentally, every Virtqueue refers to three distinct regions in memory: +/// +/// - the **descriptor area**: this memory region contains an array of so-called +/// Virtqueue descriptors. Each descriptor is a data structure containing +/// metadata concerning a buffer shared by the guest (VirtIO driver) into the +/// Virtqueue, such as its guest-physical address in memory and +/// length. Multiple shared buffers in distinct memory locations can be +/// chained into a single buffer. +/// +/// - the **available ring**: this available ring is a data structure maintained +/// by the guest (VirtIO driver). It contains a ring-buffer which is used for +/// the guest to share descriptors (as maintained in the _descriptor area_) +/// with the host (VirtIO device). +/// +/// - the **used ring**: buffers shared with the host (VirtIO device) will +/// eventually be returned to the guest (VirtIO driver) by the device placing +/// them into the used ring. The host will further issue an interrupt, which +/// shall be routed to the Virtqueue by means of the +/// [`Virtqueue::used_interrupt`] method. +pub trait Virtqueue { + /// Negotiate the number of used descriptors in the Virtqueue. + /// + /// The method is presented with the maximum number of queue elements the + /// intended device can support. The method must return a value smaller or + /// equal than `device_max_elements`. The device's addresses as returned by + /// [`Virtqueue::physical_addresses`] after Virtqueue initialization must + /// have a memory layout adhering to the [Virtual I/O Device (VIRTIO) + /// Specification, Version + /// 1.1](https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html), + /// for at least the returned number of queue elements. + /// + /// This method may be called any number of times before initialization of + /// the [`Virtqueue`]. Only its latest returned value is valid and must be + /// passed to [`Virtqueue::initialize`]. + fn negotiate_queue_size(&self, device_max_elements: usize) -> usize; + + /// Initialize the Virtqueue. + /// + /// This method must bring the Virtqueue into a state where it can be + /// exposed to a VirtIO transport based on the return value of + /// [`Virtqueue::physical_addresses`]. A Virtqueue must refuse operations + /// which require it to be initialized until this method has been called. + /// + /// The passed `queue_number` must identify the queue to the device. After + /// returning from [`Virtqueue::initialize`], calls into the Virtqueue + /// (except for [`Virtqueue::physical_addresses`]) may attempt to + /// communicate with the VirtIO device referencing this passed + /// `queue_number`. In practice, this means that the VirtIO device should be + /// made aware of this [`Virtqueue`] promptly after calling this method, at + /// least before invoking [`Virtqueue::used_interrupt`]. + /// + /// The provided `queue_elements` is the latest negotiated queue size, as + /// returned by [`Virtqueue::negotiate_queue_size`]. + fn initialize(&self, queue_number: u32, queue_elements: usize); + + /// The physical addresses of the Virtqueue descriptors, available and used ring. + /// + /// The returned addresses and their memory contents must adhere to the + /// [Virtual I/O Device (VIRTIO) Specification, Version + /// 1.1](https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html) + /// + /// This method must not be called before the Virtqueue has been + /// initialized. + fn physical_addresses(&self) -> VirtqueueAddresses; + + /// Interrupt indicating that a VirtIO device may have placed a buffer into + /// this Virtqueue's used ring. + /// + /// A [`Virtqueue`] must be tolerant of spurious calls to this method. + fn used_interrupt(&self); +} diff --git a/chips/virtio/src/queues/split_queue.rs b/chips/virtio/src/queues/split_queue.rs new file mode 100644 index 0000000000..8a32656f71 --- /dev/null +++ b/chips/virtio/src/queues/split_queue.rs @@ -0,0 +1,918 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! VirtIO Split Virtqueue implementation. +//! +//! This module contains an implementation of a Split Virtqueue, as defined in +//! 2.6 Split Virtqueues of the [Virtual I/O Device (VIRTIO) Specification, +//! Version +//! 1.1](https://docs.oasis-open.org/virtio/virtio/v1.1/csprd01/virtio-v1.1-csprd01.html). +//! This implementation can be used in conjunction with the VirtIO transports +//! defined in [`transports`](`super::super::transports`) and +//! [`devices`](`super::super::devices`) to interact with VirtIO-compatible +//! devices. + +use core::cell::Cell; +use core::cmp; +use core::marker::PhantomData; +use core::ptr::NonNull; +use core::slice; + +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; +use kernel::utilities::registers::{register_bitfields, InMemoryRegister}; +use kernel::ErrorCode; + +use super::super::queues::{Virtqueue, VirtqueueAddresses}; +use super::super::transports::VirtIOTransport; + +pub const DESCRIPTOR_ALIGNMENT: usize = 16; +pub const AVAILABLE_RING_ALIGNMENT: usize = 2; +pub const USED_RING_ALIGNMENT: usize = 4; + +register_bitfields![u16, + DescriptorFlags [ + Next OFFSET(0) NUMBITS(1) [], + WriteOnly OFFSET(1) NUMBITS(1) [], + Indirect OFFSET(2) NUMBITS(1) [] + ], + AvailableRingFlags [ + NoInterrupt OFFSET(0) NUMBITS(1) [] + ], + UsedRingFlags [ + NoNotify OFFSET(0) NUMBITS(1) [] + ], +]; + +// This is an unsafe workaround of an unsafe workaround. +// +// Unfortunately, the Rust core library defines defaults on arrays not in a +// generic way, but only for arrays up to 32 elements. Hence, for arrays using a +// const-generic argument for their length, `Default::<[$SOMETHING_NON_DEFAULT; +// CONST_GENERIC_USIZE]>::default()` does not work in Rust (as of +// 2022-11-26). Instead, we need to use this horrible and unsafe hack as +// documented here, which initializes an array of `MaybeUninit`s and transmutes: +// (https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html#initializing-an-array-element-by-element) +// +// However, Rust is also unable to transmute from a `MaybeUninit` to a +// generic `T`, even though the former is explicitly layout-compatible with the +// latter. Hence, we have to use another hack described in the following +// comment: +// https://github.com/rust-lang/rust/issues/62875#issuecomment-513834029 +// +// This function encapsulates all of this unsafe code and should be safe to use +// until Rust adds support for constructing arrays over a const-generic length +// from the contained types' default values. +// +// In large parts, this function is a verbatim copy of Rust's example for +// element-wise array initialization using `MaybeUninit`: +// https://doc.rust-lang.org/stable/std/mem/union.MaybeUninit.html#initializing-an-array-element-by-element +fn init_constgeneric_default_array() -> [T; N] { + // Create an uninitialized array of `MaybeUninit`. The `assume_init` is safe + // because the type we are claiming to have initialized here is a bunch of + // `MaybeUninit`s, which do not require initialization. + let mut uninit_arr: [core::mem::MaybeUninit; N] = + unsafe { core::mem::MaybeUninit::uninit().assume_init() }; + + // Dropping a `MaybeUninit` does nothing, so if there is a panic during this + // loop, we have a memory leak, but there is no memory safety issue. + for elem in &mut uninit_arr[..] { + elem.write(T::default()); + } + + // Everything is initialized. We'd like to transmute the `[MaybeUnit; N]` + // array into a `[T; N]`, but transmuting `MaybeUninit` to `T` (where T + // is generic) is not (yet) supported. Hence we need to take a pointer to + // this array, cast it to the correct type, read the pointer back and forget + // the original `[MaybeUnit; N]` array, as described here: + // https://github.com/rust-lang/rust/issues/62875#issuecomment-513834029 + let uninit_arr_ptr: *mut [core::mem::MaybeUninit; N] = &mut uninit_arr as *mut _; + let transmuted: [T; N] = unsafe { core::ptr::read(uninit_arr_ptr.cast::<[T; N]>()) }; + + // With the original value forgotten and new value recreated from its + // pointer, return it: + transmuted +} + +/// A single Virtqueue descriptor. +/// +/// Implements the memory layout of a single Virtqueue descriptor of a +/// split-virtqueue, to be placed into the queue's descriptor table, as defined +/// in section 2.6.5 of the spec. +#[repr(C)] +pub struct VirtqueueDescriptor { + /// Guest physical address of the buffer to share + addr: InMemoryRegister, + /// Length of the shared buffer + len: InMemoryRegister, + /// Descriptor flags + flags: InMemoryRegister, + /// Pointer to the next entry in the descriptor queue (if two + /// buffers are chained) + next: InMemoryRegister, +} + +impl Default for VirtqueueDescriptor { + fn default() -> VirtqueueDescriptor { + VirtqueueDescriptor { + addr: InMemoryRegister::new(0), + len: InMemoryRegister::new(0), + flags: InMemoryRegister::new(0), + next: InMemoryRegister::new(0), + } + } +} + +/// The Virtqueue descriptor table. +/// +/// This table is provided to the VirtIO device (host) as a means to communicate +/// information about shared buffers, maintained in the individual +/// [`VirtqueueDescriptor`] elements. Elements in this table are referenced by +/// the [`VirtqueueAvailableRing`] and [`VirtqueueUsedRing`] for exposing them +/// to the VirtIO device in order, and receiving exposed ("used") buffers back +/// from the device. +/// +/// Multiple entries of the descriptor table can be chained in order to treat +/// disjoint regions of memory as a single buffer through the +/// `VirtqueueDescriptor::next` field, where the value of this field indexes +/// into this table. +#[repr(C, align(16))] +pub struct VirtqueueDescriptors([VirtqueueDescriptor; MAX_QUEUE_SIZE]); + +impl Default for VirtqueueDescriptors { + fn default() -> Self { + VirtqueueDescriptors(init_constgeneric_default_array()) + } +} + +// This is required to be able to implement Default and hence to +// initialize an entire array of default values with size specified by +// a constant. +#[repr(transparent)] +pub struct VirtqueueAvailableElement(InMemoryRegister); + +/// The Virtqueue available ring. +/// +/// This struct is exposed to the VirtIO device as a means to share buffers +/// (pointed to by descriptors of the [`VirtqueueDescriptors`] descriptors +/// table) with the VirtIO device (host). It avoids the need for explicit +/// locking by using two distinct rings, each undirectionally exchanging +/// information about used buffers. When a new buffer is placed into the +/// available ring, the VirtIO driver (guest) must increment `idx` to the index +/// where it would place the next available descriptor pointer in the ring +/// field. After such an update, the queue must inform the device about this +/// change through a call to [`VirtIOTransport::queue_notify`]. Given that +/// volatile writes cannot be reordered with respect to each other, changes to +/// the available ring are guaranteed to be visible to the VirtIO device (host). +#[repr(C, align(2))] +pub struct VirtqueueAvailableRing { + /// Virtqueue available ring flags. + flags: InMemoryRegister, + /// Incrementing index, pointing to where the driver would put the next + /// descriptor entry in the ring (modulo the queue size). + /// + /// The driver must not decrement this field. There is no way to "unexpose" + /// buffers. + idx: InMemoryRegister, + /// Ring containing the shared buffers (indices into the + /// [`VirtqueueDescriptors`] descriptor table). + ring: [VirtqueueAvailableElement; MAX_QUEUE_SIZE], + /// "Used event" queue notification suppression mechanism. + /// + /// This field is only honored by the VirtIO device if the EventIdx feature + /// was negotiated. + /// + /// The driver can set this field to a target `idx` value of the + /// [`VirtqueueUsedRing`] to indicate to the device that notifications are + /// unnecessary until the device writes a buffer with the corresponding + /// index into the used ring. + used_event: InMemoryRegister, +} + +impl Default for VirtqueueAvailableElement { + fn default() -> VirtqueueAvailableElement { + VirtqueueAvailableElement(InMemoryRegister::new(0)) + } +} + +impl Default for VirtqueueAvailableRing { + fn default() -> Self { + VirtqueueAvailableRing { + flags: InMemoryRegister::new(0), + idx: InMemoryRegister::new(0), + ring: init_constgeneric_default_array(), + used_event: InMemoryRegister::new(0), + } + } +} + +/// The Virtqueue used ring. +/// +/// This struct is exposed to the VirtIO device for the device to indicate which +/// shared buffers (through the [`VirtqueueAvailableRing`] have been processed. +/// It works similar to the available ring, but must never be written by the +/// VirtIO driver (guest) after it has been shared with the device, and as long +/// as the device is initialized. +#[repr(C, align(4))] +pub struct VirtqueueUsedRing { + /// Virtqueue used ring flags. + flags: InMemoryRegister, + /// Incrementing index, pointing to where the device would put the next + /// descriptor entry in the ring (modulo the queue size). + /// + /// The device must not decrement this field. There is no way to "take back" + /// buffers. + idx: InMemoryRegister, + /// Ring containing the used buffers (indices into the + /// [`VirtqueueDescriptors`] descriptor table). + ring: [VirtqueueUsedElement; MAX_QUEUE_SIZE], + /// "Available event" queue notification suppression mechanism. + /// + /// This field must only be honored by the VirtIO driver if the EventIdx + /// feature was negotiated. + /// + /// The device can set this field to a target `idx` value of the + /// [`VirtqueueAvailableRing`] to indicate to the driver that notifications + /// are unnecessary until the driver writes a buffer with the corresponding + /// index into the available ring. + avail_event: InMemoryRegister, +} + +impl Default for VirtqueueUsedRing { + fn default() -> Self { + VirtqueueUsedRing { + flags: InMemoryRegister::new(0), + idx: InMemoryRegister::new(0), + ring: init_constgeneric_default_array(), + avail_event: InMemoryRegister::new(0), + } + } +} + +/// A single element of the [`VirtqueueUsedRing`]. +#[repr(C)] +pub struct VirtqueueUsedElement { + /// Index into the [`VirtqueueDescriptors`] descriptor table indicating the + /// head element of the returned descriptor chain. + id: InMemoryRegister, + /// Total length of the descriptor chain which was used by the device. + /// + /// Commonly this is used as a mechanism to communicate how much data the + /// device has written to a shared buffer. + len: InMemoryRegister, +} + +impl Default for VirtqueueUsedElement { + fn default() -> VirtqueueUsedElement { + VirtqueueUsedElement { + id: InMemoryRegister::new(0), + len: InMemoryRegister::new(0), + } + } +} + +/// A helper struct to manage the state of the Virtqueue available ring. +/// +/// This struct reduces the complexity of the [`SplitVirtqueue`] implementation +/// by encapsulating operations which depend on and modify the state of the +/// driver-controlled available ring of the Virtqueue. It is essentially a +/// glorified ring-buffer state machine, following the semantics as defined by +/// VirtIO for the Virtqueue's available ring. +struct AvailableRingHelper { + max_elements: Cell, + start: Cell, + end: Cell, + empty: Cell, +} + +impl AvailableRingHelper { + pub fn new(max_elements: usize) -> AvailableRingHelper { + AvailableRingHelper { + max_elements: Cell::new(max_elements), + start: Cell::new(0), + end: Cell::new(0), + empty: Cell::new(true), + } + } + + fn ring_wrapping_add(&self, a: u16, b: u16) -> u16 { + if self.max_elements.get() - (a as usize) - 1 < (b as usize) { + b - (self.max_elements.get() - a as usize) as u16 + } else { + a + b + } + } + + /// Reset the state of the available ring. + /// + /// This must be called before signaling to the device that the driver is + /// initialized. It takes the maximum queue elements as the `max_elements` + /// parameter, as negotiated with the device. + pub fn reset(&self, max_elements: usize) { + self.max_elements.set(max_elements); + self.start.set(0); + self.end.set(0); + self.empty.set(true); + } + + /// Whether the available ring of the Virtqueue is empty. + pub fn is_empty(&self) -> bool { + self.empty.get() + } + + /// Whether the available ring of the Virtqueue is full. + pub fn is_full(&self) -> bool { + !self.empty.get() && self.start.get() == self.end.get() + } + + /// Try to insert an element into the Virtqueue available ring. + /// + /// If there is space in the Virtqueue's available ring, this increments the + /// internal state and returns the index of the element to be + /// written. Otherwise, it returns `None`. + pub fn insert(&self) -> Option { + if !self.is_full() { + let pos = self.end.get(); + self.end.set(self.ring_wrapping_add(pos, 1)); + self.empty.set(false); + Some(pos) + } else { + None + } + } + + /// Try to remove an element from the Virtqueue available ring. + /// + /// If there is an element in the Virtqueue's available ring, this removes + /// it from its internal state and returns the index of that element. + pub fn pop(&self) -> Option { + if !self.is_empty() { + let pos = self.start.get(); + self.start.set(self.ring_wrapping_add(pos, 1)); + if self.start.get() == self.end.get() { + self.empty.set(true); + } + Some(pos) + } else { + None + } + } +} + +/// Internal representation of a slice of memory passed held in the Virtqueue. +/// +/// Because of Tock's architecture combined with Rust's reference lifetime +/// rules, buffers are generally passed around as `&mut [u8]` slices with a +/// `'static` lifetime. Thus, clients pass a mutable static reference into the +/// Virtqueue, losing access. When the device has processed the provided buffer, +/// access is restored by passing the slice back to the client. +/// +/// However, clients may not wish to expose the full slice length to the +/// device. They cannot simply subslice the slice, as that would mean that +/// clients loose access to the "sliced off" portion of the slice. Instead, +/// clients pass a seperate `length` parameter when inserting a buffer into a +/// virtqueue, by means of the [`VirtqueueBuffer`] struct. This information is +/// then written to the VirtIO descriptor. +/// +/// Yet, to be able to reconstruct the entire buffer and hand it back to the +/// client, information such as the original length must be recorded. We cannot +/// retain the `&'static mut [u8]` in scope though, as the VirtIO device (host) +/// writing to it would violate Rust's memory aliasing rules. Thus, we convert a +/// slice into this struct (converting the slice into its raw parts) for +/// safekeeping, until we reconstruct a byte-slice from it to hand it back to +/// the client. +/// +/// While we technically retain an identical pointer in the +/// [`VirtqueueDescriptors`] descriptor table, we record it here nonetheless, as +/// a method to sanity check internal buffer management consistency. +struct SharedDescriptorBuffer<'b> { + ptr: NonNull, + len: usize, + _lt: PhantomData<&'b mut [u8]>, +} + +impl<'b> SharedDescriptorBuffer<'b> { + pub fn from_slice(slice: &'b mut [u8]) -> SharedDescriptorBuffer<'b> { + SharedDescriptorBuffer { + ptr: NonNull::new(slice.as_mut_ptr()).unwrap(), + len: slice.len(), + _lt: PhantomData, + } + } + + pub fn into_slice(self) -> &'b mut [u8] { + // SAFETY: This is guaranteed to be safe because this struct can only be + // using the `from_slice()` constructor, `ptr` and `len` cannot be + // modified after this struct is created, and this method consumes the + // struct. + unsafe { slice::from_raw_parts_mut(self.ptr.as_ptr(), self.len) } + } +} + +/// A slice of memory to be shared with a VirtIO device. +/// +/// The [`VirtqueueBuffer`] allows to limit the portion of the passed slice to +/// be shared with the device through the `len` field. Furthermore, the device +/// can be asked to not write to the shared buffer by setting `device_writeable` +/// to `false`. +/// +/// The [`SplitVirtqueue`] does not actually enfore that a VirtIO device adheres +/// to the `device_writeable` flag, although compliant devices should. +pub struct VirtqueueBuffer<'b> { + pub buf: &'b mut [u8], + pub len: usize, + pub device_writeable: bool, +} + +/// A VirtIO split Virtqueue. +/// +/// For documentation on Virtqueues in general, please see the [`Virtqueue` +/// trait documentation](Virtqueue). +/// +/// A split Virtqueue is split into separate memory areas, namely: +/// +/// - a **descriptor table** (VirtIO driver / guest writeable, +/// [`VirtqueueDescriptors`]) +/// +/// - an **available ring** (VirtIO driver / guest writeable, +/// [`VirtqueueAvailableRing`]) +/// +/// - a **used ring** (VirtIO device / host writeable, [`VirtqueueUsedRing`]) +/// +/// Each of these areas must be located physically-contiguous in guest-memory +/// and have different alignment constraints. +/// +/// This is in constrast to _packed Virtqueues_, which use memory regions that +/// are read and written by both the VirtIO device (host) and VirtIO driver +/// (guest). +pub struct SplitVirtqueue<'a, 'b, const MAX_QUEUE_SIZE: usize> { + descriptors: &'a mut VirtqueueDescriptors, + available_ring: &'a mut VirtqueueAvailableRing, + used_ring: &'a mut VirtqueueUsedRing, + + available_ring_state: AvailableRingHelper, + last_used_idx: Cell, + + transport: OptionalCell<&'a dyn VirtIOTransport>, + + initialized: Cell, + queue_number: Cell, + max_elements: Cell, + + descriptor_buffers: [OptionalCell>; MAX_QUEUE_SIZE], + + client: OptionalCell<&'a dyn SplitVirtqueueClient<'b>>, + used_callbacks_enabled: Cell, +} + +impl<'a, 'b, const MAX_QUEUE_SIZE: usize> SplitVirtqueue<'a, 'b, MAX_QUEUE_SIZE> { + pub fn new( + descriptors: &'a mut VirtqueueDescriptors, + available_ring: &'a mut VirtqueueAvailableRing, + used_ring: &'a mut VirtqueueUsedRing, + ) -> Self { + assert!(core::ptr::from_ref(descriptors) as usize % DESCRIPTOR_ALIGNMENT == 0); + assert!(core::ptr::from_ref(available_ring) as usize % AVAILABLE_RING_ALIGNMENT == 0); + assert!(core::ptr::from_ref(used_ring) as usize % USED_RING_ALIGNMENT == 0); + + SplitVirtqueue { + descriptors, + available_ring, + used_ring, + + available_ring_state: AvailableRingHelper::new(MAX_QUEUE_SIZE), + last_used_idx: Cell::new(0), + + transport: OptionalCell::empty(), + + initialized: Cell::new(false), + queue_number: Cell::new(0), + max_elements: Cell::new(MAX_QUEUE_SIZE), + + descriptor_buffers: init_constgeneric_default_array(), + + client: OptionalCell::empty(), + used_callbacks_enabled: Cell::new(false), + } + } + + /// Set the [`SplitVirtqueueClient`]. + pub fn set_client(&self, client: &'a dyn SplitVirtqueueClient<'b>) { + self.client.set(client); + } + + /// Set the underlying [`VirtIOTransport`]. This must be done prior to + /// initialization. + pub fn set_transport(&self, transport: &'a dyn VirtIOTransport) { + assert!(!self.initialized.get()); + self.transport.set(transport); + } + + /// Get the queue number associated with this Virtqueue. + /// + /// Prior to initialization the SplitVirtqueue does not have an associated + /// queue number and will return `None`. + pub fn queue_number(&self) -> Option { + if self.initialized.get() { + Some(self.queue_number.get()) + } else { + None + } + } + + /// Get the number of free descriptor slots in the descriptor table. + /// + /// This takes into account the negotiated maximum queue length. + pub fn free_descriptor_count(&self) -> usize { + assert!(self.initialized.get()); + self.descriptor_buffers + .iter() + .take(self.max_elements.get()) + .fold(0, |count, descbuf_entry| { + if descbuf_entry.is_none() { + count + 1 + } else { + count + } + }) + } + + /// Get the number of (unprocessed) descriptor chains in the Virtqueue's + /// used ring. + pub fn used_descriptor_chains_count(&self) -> usize { + let pending_chains = self + .used_ring + .idx + .get() + .wrapping_sub(self.last_used_idx.get()); + + // If we ever have more than max_elements pending descriptors, + // the used ring increased too fast and has overwritten data + assert!(pending_chains as usize <= self.max_elements.get()); + + pending_chains as usize + } + + /// Remove an element from the Virtqueue's used ring.q + /// + /// If `self.last_used_idx.get() == self.used_ring.idx.get()` (e.g. we don't + /// have an unprocessed used buffer chain) this will return + /// `None`. Otherwise it will return the remove ring element's index, as + /// well as the number of processed bytes as reported by the VirtIO device. + /// + /// This will update `self.last_used_idx`. + /// + /// The caller is responsible for keeping the available ring in sync, + /// freeing one entry if a used buffer was removed through this method. + fn remove_used_chain(&self) -> Option<(usize, usize)> { + assert!(self.initialized.get()); + + let pending_chains = self.used_descriptor_chains_count(); + + if pending_chains > 0 { + let last_used_idx = self.last_used_idx.get(); + + // Remove the element one below the index (as 0 indicates + // _no_ buffer has been written), hence the index points + // to the next element to be written + let ring_pos = (last_used_idx as usize) % self.max_elements.get(); + let chain_top_idx = self.used_ring.ring[ring_pos].id.get(); + let written_len = self.used_ring.ring[ring_pos].len.get(); + + // Increment our local idx counter + self.last_used_idx.set(last_used_idx.wrapping_add(1)); + + Some((chain_top_idx as usize, written_len as usize)) + } else { + None + } + } + + /// Add an element to the available queue. + /// + /// Returns either the inserted ring index or `None` if the Virtqueue's + /// available ring is fully occupied. + /// + /// This will update the available ring's `idx` field. + /// + /// The caller is responsible for notifying the device about any inserted + /// available buffers. + fn add_available_descriptor(&self, descriptor_chain_head: usize) -> Option { + assert!(self.initialized.get()); + + if let Some(element_pos) = self.available_ring_state.insert() { + // Write the element + self.available_ring.ring[element_pos as usize] + .0 + .set(descriptor_chain_head as u16); + + // TODO: Perform a suitable memory barrier using a method exposed by + // the transport. For now, we don't negotiate + // VIRTIO_F_ORDER_PLATFORM, which means that any device which + // requires proper memory barriers (read: not implemented in + // software, like QEMU) should refuse operation. We use volatile + // memory accesses, so read/write reordering by the compiler is not + // an issue. + + // Update the idx + self.available_ring + .idx + .set(self.available_ring.idx.get().wrapping_add(1)); + + Some(element_pos as usize) + } else { + None + } + } + + fn add_descriptor_chain( + &self, + buffer_chain: &mut [Option>], + ) -> Result { + assert!(self.initialized.get()); + + // Get size of actual chain, until the first None + let queue_length = buffer_chain + .iter() + .take_while(|elem| elem.is_some()) + .count(); + + // Make sure we have sufficient space available + // + // This takes into account the negotiated max size and will + // only list free iterators within that range + if self.free_descriptor_count() < queue_length { + return Err(ErrorCode::NOMEM); + } + + // Walk over the descriptor table & buffer chain in parallel, + // inserting where empty + // + // We don't need to do any bounds checking here, if we run + // over the boundary it's safe to panic as something is + // seriously wrong with `free_descriptor_count` + let mut i = 0; + let mut previous_descriptor: Option = None; + let mut head = None; + let queuebuf_iter = buffer_chain.iter_mut().peekable(); + for queuebuf in queuebuf_iter.take_while(|queuebuf| queuebuf.is_some()) { + // Take the queuebuf out of the caller array + let taken_queuebuf = queuebuf.take().expect("queuebuf is None"); + + // Sanity check the buffer: the subslice length may never + // exceed the slice length + assert!(taken_queuebuf.buf.len() >= taken_queuebuf.len); + + while self.descriptor_buffers[i].is_some() { + i += 1; + + // We should never run over the end, as we should have + // sufficient free descriptors + assert!(i < self.descriptor_buffers.len()); + } + + // Alright, we found a slot to insert the descriptor + // + // Check if it's the first one and store it's index as head + if head.is_none() { + head = Some(i); + } + + // Write out the descriptor + let desc = &self.descriptors.0[i]; + desc.len.set(taken_queuebuf.len as u32); + assert!(desc.len.get() > 0); + desc.addr.set(taken_queuebuf.buf.as_ptr() as u64); + desc.flags.write(if taken_queuebuf.device_writeable { + DescriptorFlags::WriteOnly::SET + } else { + DescriptorFlags::WriteOnly::CLEAR + }); + + // Now that we know our descriptor position, check whether + // we must chain ourself to a previous descriptor + if let Some(prev_index) = previous_descriptor { + self.descriptors.0[prev_index] + .flags + .modify(DescriptorFlags::Next::SET); + self.descriptors.0[prev_index].next.set(i as u16); + } + + // Finally, store the full slice for reference. We don't store a + // proper Rust slice reference, as this would violate aliasing + // requirements: while the buffer is in the chain, it may be written + // by the VirtIO device. + // + // This can be changed to something slightly more elegant, once the + // NonNull functions around slices have been stabilized: + // https://doc.rust-lang.org/stable/std/ptr/struct.NonNull.html#method.slice_from_raw_parts + self.descriptor_buffers[i] + .replace(SharedDescriptorBuffer::from_slice(taken_queuebuf.buf)); + + // Set ourself as the previous descriptor, as we know the position + // of `next` only in the next loop iteration. + previous_descriptor = Some(i); + + // Increment the counter to not check the current + // descriptor entry again + i += 1; + } + + Ok(head.expect("No head added to the descriptor table")) + } + + fn remove_descriptor_chain( + &self, + top_descriptor_index: usize, + ) -> [Option>; MAX_QUEUE_SIZE] { + assert!(self.initialized.get()); + + let mut res: [Option>; MAX_QUEUE_SIZE] = + init_constgeneric_default_array(); + + let mut i = 0; + let mut next_index: Option = Some(top_descriptor_index); + + while let Some(current_index) = next_index { + // Get a reference over the current descriptor + let current_desc = &self.descriptors.0[current_index]; + + // Check whether we have a chained descriptor and store that in next_index + if current_desc.flags.is_set(DescriptorFlags::Next) { + next_index = Some(current_desc.next.get() as usize); + } else { + next_index = None; + } + + // Recover the slice originally associated with this + // descriptor & delete it from the buffers array + // + // The caller may have provided us a larger Rust slice, + // but indicated to only provide a subslice to VirtIO, + // hence we'll use the stored original slice and also + // return the subslice length + let supplied_slice = self.descriptor_buffers[current_index] + .take() + .expect("Virtqueue descriptors and slices out of sync") + .into_slice(); + assert!(supplied_slice.as_mut_ptr() as u64 == current_desc.addr.get()); + + // Reconstruct the input VirtqueueBuffer to hand it back + res[i] = Some(VirtqueueBuffer { + buf: supplied_slice, + len: current_desc.len.get() as usize, + device_writeable: current_desc.flags.is_set(DescriptorFlags::WriteOnly), + }); + + // Zero the descriptor + current_desc.addr.set(0); + current_desc.len.set(0); + current_desc.flags.set(0); + current_desc.next.set(0); + + // Increment the loop iterator + i += 1; + } + + res + } + + /// Provide a single chain of buffers to the device. + /// + /// This method will iterate over the passed slice until it encounters the + /// first `None`. It will first validate that the number of buffers can be + /// inserted into its descriptor table, and if not return + /// `Err(ErrorCode::NOMEM)`. If sufficient space is available, it takes the + /// passed buffers out of the provided `Option`s until encountering the + /// first `None` and shares this buffer chain with the device. + /// + /// When the device has finished processing the passed buffer chain, it is + /// returned to the client either through the + /// [`SplitVirtqueueClient::buffer_chain_ready`] callback, or can be + /// retrieved through the [`SplitVirtqueue::pop_used_buffer_chain`] method. + pub fn provide_buffer_chain( + &self, + buffer_chain: &mut [Option>], + ) -> Result<(), ErrorCode> { + assert!(self.initialized.get()); + + // Try to add the chain into the descriptor array + let descriptor_chain_head = self.add_descriptor_chain(buffer_chain)?; + + // Now make it available to the device. If there was sufficient space + // available to add the chain's descriptors (of which there may be + // multiple), there should also be sufficient space in the available + // ring (where a multi-descriptor chain will occupy only one elements). + self.add_available_descriptor(descriptor_chain_head) + .expect("Insufficient space in available ring"); + + // Notify the queue. This must not fail, given that the SplitVirtqueue + // requires a transport to be set prior to initialization. + self.transport + .map(|t| t.queue_notify(self.queue_number.get())) + .unwrap(); + + Ok(()) + } + + /// Attempt to take a buffer chain out of the Virtqueue used ring. + /// + /// Returns `None` if the used ring is empty. + pub fn pop_used_buffer_chain( + &self, + ) -> Option<([Option>; MAX_QUEUE_SIZE], usize)> { + assert!(self.initialized.get()); + + self.remove_used_chain() + .map(|(descriptor_idx, bytes_used)| { + // Get the descriptor chain + let chain = self.remove_descriptor_chain(descriptor_idx); + + // Remove the first entry of the available ring, since we + // got a single buffer back and can therefore make another + // buffer available to the device without risking an + // overflow of the used ring + self.available_ring_state.pop(); + + (chain, bytes_used) + }) + } + + /// Disable callback delivery for the + /// [`SplitVirtqueueClient::buffer_chain_ready`] method on the registered + /// client. + pub fn enable_used_callbacks(&self) { + self.used_callbacks_enabled.set(true); + } + + /// Enable callback delivery for the + /// [`SplitVirtqueueClient::buffer_chain_ready`] method on the registered + /// client. + /// + /// Callback delivery is enabled by default. If this is not desired, call + /// this method prior to registering a client. + pub fn disable_used_callbacks(&self) { + self.used_callbacks_enabled.set(false); + } +} + +impl<'a, 'b, const MAX_QUEUE_SIZE: usize> Virtqueue for SplitVirtqueue<'a, 'b, MAX_QUEUE_SIZE> { + fn used_interrupt(&self) { + assert!(self.initialized.get()); + // A buffer MAY have been put into the used in by the device + // + // Try to extract all pending used buffers and return them to + // the clients via callbacks + + while self.used_callbacks_enabled.get() { + if let Some((mut chain, bytes_used)) = self.pop_used_buffer_chain() { + self.client.map(move |client| { + client.buffer_chain_ready(self.queue_number.get(), chain.as_mut(), bytes_used) + }); + } else { + break; + } + } + } + + fn physical_addresses(&self) -> VirtqueueAddresses { + VirtqueueAddresses { + descriptor_area: core::ptr::from_ref(self.descriptors) as u64, + driver_area: core::ptr::from_ref(self.available_ring) as u64, + device_area: core::ptr::from_ref(self.used_ring) as u64, + } + } + + fn negotiate_queue_size(&self, max_elements: usize) -> usize { + assert!(!self.initialized.get()); + let negotiated = cmp::min(MAX_QUEUE_SIZE, max_elements); + self.max_elements.set(negotiated); + self.available_ring_state.reset(negotiated); + negotiated + } + + fn initialize(&self, queue_number: u32, _queue_elements: usize) { + assert!(!self.initialized.get()); + + // The transport must be set prior to initialization: + assert!(self.transport.is_some()); + + // TODO: Zero the queue + // + // For now we assume all passed queue buffers are already + // zeroed + + self.queue_number.set(queue_number); + self.initialized.set(true); + } +} + +pub trait SplitVirtqueueClient<'b> { + fn buffer_chain_ready( + &self, + queue_number: u32, + buffer_chain: &mut [Option>], + bytes_used: usize, + ); +} diff --git a/chips/virtio/src/transports/mmio.rs b/chips/virtio/src/transports/mmio.rs new file mode 100644 index 0000000000..955686894a --- /dev/null +++ b/chips/virtio/src/transports/mmio.rs @@ -0,0 +1,412 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! VirtIO memory mapped device driver + +use kernel::utilities::cells::OptionalCell; +use kernel::utilities::registers::interfaces::{ReadWriteable, Readable, Writeable}; +use kernel::utilities::registers::{ + register_bitfields, InMemoryRegister, ReadOnly, ReadWrite, WriteOnly, +}; +use kernel::utilities::StaticRef; + +use super::super::devices::{VirtIODeviceDriver, VirtIODeviceType}; +use super::super::queues::Virtqueue; +use super::super::transports::{VirtIOInitializationError, VirtIOTransport}; + +// Magic string "virt" every device has to expose +const VIRTIO_MAGIC_VALUE: [u8; 4] = [0x76, 0x69, 0x72, 0x74]; + +#[repr(C)] +pub struct VirtIOMMIODeviceRegisters { + /// 0x000 Magic string "virt" for identification + magic_value: ReadOnly, + /// 0x004 Device version number + device_version: ReadOnly, + /// 0x008 VirtIO Subsystem Device ID + device_id: ReadOnly, + /// 0x00C VirtIO Subsystem Vendor ID + vendor_id: ReadOnly, + /// 0x010 Flags representing features the device supports + device_features: ReadOnly, + /// 0x014 Device (host) features word selection + device_features_sel: WriteOnly, + // 0x018 - 0x01C: reserved + _reversed0: [u32; 2], + /// 0x020 Flags representing features understood and activated by the driver + driver_features: WriteOnly, + /// 0x024 Activated (guest) features word selection + driver_features_sel: WriteOnly, + // 0x028 - 0x02C: reserved + _reserved1: [u32; 2], + /// 0x030 Virtual queue index + queue_sel: WriteOnly, + /// 0x034 Maximum virtual queue size + queue_num_max: ReadOnly, + /// 0x038 Virtual queue size + queue_num: WriteOnly, + // 0x03C - 0x40: reserved + _reserved2: [u32; 2], + /// 0x044 Virtual queue ready bit + queue_ready: ReadWrite, + // 0x048 - 0x04C: reserved + _reserved3: [u32; 2], + /// 0x050 Queue notifier + queue_notify: WriteOnly, + // 0x054 - 0x05C: reserved + _reserved4: [u32; 3], + /// 0x060 Interrupt status + interrupt_status: ReadOnly, + /// 0x064 Interrupt acknowledge + interrupt_ack: WriteOnly, + // 0x068 - 0x06C: reserved + _reserved5: [u32; 2], + /// 0x070 Device status + device_status: ReadWrite, + // 0x074 - 0x07C: reserved + _reserved6: [u32; 3], + /// 0x080 - 0x084 Virtual queue's Descriptor Area 64-bit long physical address + queue_desc_low: WriteOnly, + queue_desc_high: WriteOnly, + // 0x088 - 0x08C: reserved + _reserved7: [u32; 2], + /// 0x090 - 0x094 Virtual queue's Driver Area 64-bit long physical address + queue_driver_low: WriteOnly, + queue_driver_high: WriteOnly, + // 0x098 - 0x09C: reserved + _reserved8: [u32; 2], + /// 0x0A0 - 0x0A4 Virtual queue's Device Area 64-bit long physical address + queue_device_low: WriteOnly, + queue_device_high: WriteOnly, + // 0x0A8 - 0x0AC: reserved + _reserved9: [u32; 21], + /// 0x0FC Configuration atomicity value + config_generation: ReadOnly, + /// 0x100 - 0x19C device configuration space + /// + /// This is individually defined per device, with a variable + /// size. TODO: How to address this properly? Just hand around + /// addresses to this? + config: [u32; 40], +} + +register_bitfields![u32, + DeviceStatus [ + Acknowledge OFFSET(0) NUMBITS(1) [], + Driver OFFSET(1) NUMBITS(1) [], + Failed OFFSET(7) NUMBITS(1) [], + FeaturesOk OFFSET(3) NUMBITS(1) [], + DriverOk OFFSET(2) NUMBITS(1) [], + DeviceNeedsReset OFFSET(6) NUMBITS(1) [] + ], + DeviceFeatures [ + // TODO + Dummy OFFSET(0) NUMBITS(1) [] + ], + InterruptStatus [ + UsedBuffer OFFSET(0) NUMBITS(1) [], + ConfigChange OFFSET(1) NUMBITS(1) [] + ] +]; + +register_bitfields![u64, + TransportFeatures [ + RingIndirectDesc OFFSET(28) NUMBITS(1) [], + RingEventIdx OFFSET(29) NUMBITS(1) [], + Version1 OFFSET(32) NUMBITS(1) [], + AccessPlatform OFFSET(33) NUMBITS(1) [], + RingPacked OFFSET(34) NUMBITS(1) [], + InOrder OFFSET(35) NUMBITS(1) [], + OrderPlatform OFFSET(36) NUMBITS(1) [], + SRIOV OFFSET(37) NUMBITS(1) [] + ] +]; + +pub struct VirtIOMMIODevice { + regs: StaticRef, + device_type: OptionalCell, + queues: OptionalCell<&'static [&'static dyn Virtqueue]>, +} + +impl VirtIOMMIODevice { + pub const fn new(regs: StaticRef) -> VirtIOMMIODevice { + VirtIOMMIODevice { + regs, + device_type: OptionalCell::empty(), + queues: OptionalCell::empty(), + } + } + + pub fn handle_interrupt(&self) { + assert!(self.queues.is_some()); + + let isr = self.regs.interrupt_status.extract(); + // Acknowledge all interrupts immediately so that the interrupts is deasserted + self.regs.interrupt_ack.set(isr.get()); + + if isr.is_set(InterruptStatus::UsedBuffer) { + // Iterate over all queues, checking for new buffers in + // the used ring + self.queues.map(|queues| { + for queue in queues.iter() { + queue.used_interrupt(); + } + }); + } + + if isr.is_set(InterruptStatus::ConfigChange) { + // TODO: this should probably be handled? + } + } + + /// Partial initialization routine as per 4.2.3.1 MMIO-specific + /// device initialization + /// + /// This can be used to query the VirtIO transport information + /// (e.g. whether it's a supported transport and the attached + /// device) + pub fn query(&self) -> Option { + // Verify that we are talking to a VirtIO MMIO device... + if self.regs.magic_value.get() != u32::from_le_bytes(VIRTIO_MAGIC_VALUE) { + panic!("Not a VirtIO MMIO device"); + } + + // with version 2 + if self.regs.device_version.get() != 0x0002 { + panic!( + "Unknown VirtIO MMIO device version: {}", + self.regs.device_version.get() + ); + } + + // Extract the device type + VirtIODeviceType::from_device_id(self.regs.device_id.get()) + } +} + +impl VirtIOTransport for VirtIOMMIODevice { + fn initialize( + &self, + driver: &dyn VirtIODeviceDriver, + queues: &'static [&'static dyn Virtqueue], + ) -> Result { + // Initialization routine as per 4.2.3.1 MMIO-specific device + // initialization + + // Verify that we are talking to a VirtIO MMIO device... + if self.regs.magic_value.get() != u32::from_le_bytes(VIRTIO_MAGIC_VALUE) { + return Err(VirtIOInitializationError::NotAVirtIODevice); + } + + // with version 2 + if self.regs.device_version.get() != 0x0002 { + return Err(VirtIOInitializationError::InvalidTransportVersion); + } + + // Extract the device type, which will later function as an indicator + // for initialized + let device_id = self.regs.device_id.get(); + let device_type = VirtIODeviceType::from_device_id(device_id) + .ok_or(VirtIOInitializationError::UnknownDeviceType(device_id))?; + + if device_type != driver.device_type() { + return Err(VirtIOInitializationError::IncompatibleDriverDeviceType( + device_type, + )); + } + + // All further initialization as per 3.1 Device Initialization + + // 1. Reset the device (by writing 0x0 to the device status register) + self.regs.device_status.set(0x0000); + + // 2. Set the ACKNOWLEDGE status bit: the guest OS has noticed the + // device + self.regs + .device_status + .modify(DeviceStatus::Acknowledge::SET); + + // 3. Set the DRIVER status bit: the guest OS knows how to drive the + // device + // + // TODO: Maybe not always the case? + self.regs.device_status.modify(DeviceStatus::Driver::SET); + + // 4. Read device feature bits, write the subset of feature bits + // understood by OS & driver to the device + // + // Feature bits 0-23 are for the driver, 24-37 for the transport & + // queue, 38 and above reserved. The caller may therefore only negotiate + // bits 0-23 using the supplied closure, others are possibly initialized + // by us. + // + // The features must be read 32 bits at a time, which are chosen using + // DeviceFeaturesSel. + + // Read the virtual 64-bit register + // + // This is guaranteed to be consistent, the device MUST NOT change + // its features during operation + self.regs.device_features_sel.set(0); + let mut device_features_reg: u64 = self.regs.device_features.get() as u64; + self.regs.device_features_sel.set(1); + device_features_reg |= (self.regs.device_features.get() as u64) << 32; + + // Negotiate the transport features + let offered_transport_features: InMemoryRegister = + InMemoryRegister::new(device_features_reg); + let selected_transport_features: InMemoryRegister = + InMemoryRegister::new(0x0000000000000000); + + // Sanity check: Version1 must be offered AND accepted + if !offered_transport_features.is_set(TransportFeatures::Version1) { + return Err(VirtIOInitializationError::InvalidVirtIOVersion); + } else { + selected_transport_features.modify(TransportFeatures::Version1::SET); + } + + // Negotiate the driver features. The driver can only select feature + // bits for the specific device type, which are assigned to be bits with + // indices in the range of 0 to 23. + let driver_negotiated = + if let Some(nf) = driver.negotiate_features(device_features_reg & 0xFFF) { + // Mask the driver's response by the device-specific feature bits. + nf & 0xFFF + } else { + // The driver does not like the offered features, indicate this + // failure to the device and report an error: + self.regs.device_status.modify(DeviceStatus::Failed::SET); + return Err(VirtIOInitializationError::FeatureNegotiationFailed { + offered: offered_transport_features.get(), + accepted: None, + }); + }; + + let selected_features = selected_transport_features.get() | driver_negotiated; + + // Write the virtual 64-bit register + self.regs.driver_features_sel.set(0); + self.regs + .driver_features + .set((selected_features & 0xFFFF) as u32); + self.regs.driver_features_sel.set(1); + self.regs + .driver_features + .set((selected_features >> 32 & 0xFFFF) as u32); + + // 5. Set the FEATURES_OK status bit. We MUST NOT accept new feature + // bits after this step. + self.regs + .device_status + .modify(DeviceStatus::FeaturesOk::SET); + + // 6. Re-read device status to ensure that FEATURES_OK is still set, + // otherwise the drive does not support the subset of features & is + // unusable. + if !self.regs.device_status.is_set(DeviceStatus::FeaturesOk) { + // The device does not like the accepted features, indicate + // this failure to the device and report an error: + self.regs.device_status.modify(DeviceStatus::Failed::SET); + return Err(VirtIOInitializationError::FeatureNegotiationFailed { + offered: offered_transport_features.get(), + accepted: Some(selected_features), + }); + } + + // 7. Perform device specific setup + // + // A device has a number of virtqueues it supports. We try to initialize + // all virtqueues passed in as the `queues` parameter, and ignore others + // potentially required by the device. If the `queues` parameter + // provides more queues than the device can take, abort and fail the + // configuration. The device should not use the queues until fully + // configured. + // + // Implementation of the algorithms of 4.2.3.2 + for (index, queue) in queues.iter().enumerate() { + // Select the queue + self.regs.queue_sel.set(index as u32); + + // Verify that the queue is not already in use (shouldn't be, since + // we've just reset) + if self.regs.queue_ready.get() != 0 { + self.regs.device_status.modify(DeviceStatus::Failed::SET); + return Err(VirtIOInitializationError::DeviceError); + } + + // Read the maximum queue size (number of elements) from + // QueueNumMax. If the returned value is zero, the queue is not + // available + let queue_num_max = self.regs.queue_num_max.get() as usize; + if queue_num_max == 0 { + self.regs.device_status.modify(DeviceStatus::Failed::SET); + return Err(VirtIOInitializationError::VirtqueueNotAvailable(index)); + } + + // Negotiate the queue size, choosing a value fit for QueueNumMax + // and the buffer sizes of the passed in queue. This sets the + // negotiated value in the queue for later operation. + let queue_num = queue.negotiate_queue_size(queue_num_max); + + // Zero the queue memory + queue.initialize(index as u32, queue_num); + + // Notify the device about the queue size + self.regs.queue_num.set(queue_num as u32); + + // Write the physical queue addresses + let addrs = queue.physical_addresses(); + self.regs.queue_desc_low.set(addrs.descriptor_area as u32); + self.regs + .queue_desc_high + .set((addrs.descriptor_area >> 32) as u32); + self.regs.queue_driver_low.set(addrs.driver_area as u32); + self.regs + .queue_driver_high + .set((addrs.driver_area >> 32) as u32); + self.regs.queue_device_low.set(addrs.device_area as u32); + self.regs + .queue_device_high + .set((addrs.device_area >> 32) as u32); + + // Set queue to ready + self.regs.queue_ready.set(0x0001); + } + + // Store the queue references for later usage + self.queues.set(queues); + + // Call the hook pre "device-initialization" (setting DRIVER_OK). + driver + .pre_device_initialization() + .map_err(VirtIOInitializationError::DriverPreInitializationError)?; + + // 8. Set the DRIVER_OK status bit + self.regs.device_status.modify(DeviceStatus::DriverOk::SET); + + // The device is now "live" + self.device_type.set(device_type); + + driver.device_initialized().map_err(|err| { + VirtIOInitializationError::DriverInitializationError(device_type, err) + })?; + + Ok(device_type) + } + + fn queue_notify(&self, queue_id: u32) { + // TODO: better way to report an error here? This shouldn't usually be + // triggered. + assert!( + queue_id + < self + .queues + .get() + .expect("VirtIO transport not initialized") + .len() as u32 + ); + + self.regs.queue_notify.set(queue_id); + } +} diff --git a/chips/virtio/src/transports/mod.rs b/chips/virtio/src/transports/mod.rs new file mode 100644 index 0000000000..c2c24777c9 --- /dev/null +++ b/chips/virtio/src/transports/mod.rs @@ -0,0 +1,99 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! VirtIO transports. +//! +//! This module and its submodules provide abstractions for and implementations +//! of VirtIO transports. For more information, see the documentation of the +//! [`VirtIOTransport`] trait. + +use kernel::ErrorCode; + +use super::devices::{VirtIODeviceDriver, VirtIODeviceType}; +use super::queues::Virtqueue; + +pub mod mmio; + +#[derive(Debug, Copy, Clone)] +pub enum VirtIOInitializationError { + /// Device does not identify itself or can be recognized as a VirtIO device. + NotAVirtIODevice, + /// An unknown or incompatible VirtIO standard version. + InvalidVirtIOVersion, + /// An unknown or incompatible VirtIO transport device version. + InvalidTransportVersion, + /// Unknown VirtIO device type (as defined by the [`VirtIODeviceType`] enum). + UnknownDeviceType(u32), + /// Driver does not support driving the recognized device type. + IncompatibleDriverDeviceType(VirtIODeviceType), + /// Feature negotiation between the device and transport + device driver has + /// failed. + /// + /// The device offered the `offered` features, the driver has either not + /// accepted this feature set `accepted == None` or has responded with the + /// `accepted` feature bitset, which the device has subsequently not + /// acknowledged. + FeatureNegotiationFailed { offered: u64, accepted: Option }, + /// The requested [`Virtqueue`] with respective index is not available with + /// this VirtIO device. + VirtqueueNotAvailable(usize), + /// An error was reported by the + /// [`VirtIODeviceDriver::pre_device_initialization`] function. The device + /// has been put into the `FAILED` state. + DriverPreInitializationError(ErrorCode), + /// An error was reported by the [`VirtIODeviceDriver::device_initialized`] + /// function. The device status has been previously indicated as `DRIVER_OK` + /// and the transport has **NOT** put it into the `FAILED` state, although + /// the driver might have. The initialization has continued as usual, + /// despite reporting this error, hence it also carries the device type. + DriverInitializationError(VirtIODeviceType, ErrorCode), + /// An invariant was violated by the device. This error should not occur + /// assuming a compliant device, transport and driver implementation. + DeviceError, +} + +/// VirtIO transports. +/// +/// VirtIO can be used over multiple different transports, such as over a PCI +/// bus, an MMIO device or channel IO. This trait provides a basic abstraction +/// over such transports. +pub trait VirtIOTransport { + /// Initialize the VirtIO transport using a device driver instance. + /// + /// This function is expected to run the basic initialization routine as + /// defined for the various VirtIO transports. As part of this routine, it + /// shall + /// + /// - negotiate device features, + /// - invoke the driver `pre_device_initialization` hook before and + /// `device_initialized` hook after announcing the `DRIVER_OK` device + /// status flag to the device, + /// - register the passed [`Virtqueue`]s with the device, calling the + /// [`Virtqueue::initialize`] function with the registered queue ID + /// _before_ registration, + /// - as well as perform any other required initialization of the VirtIO + /// transport. + /// + /// The passed [`Virtqueue`]s are registered with a queue ID matching their + /// offset in the supplied slice. + /// + /// If the initialization fails, it shall report this condition to the + /// device _(setting `FAILED`) if_ it has started initializing the device + /// (setting the `ACKNOWLEDGE` device status flag), and return an + /// appropriate [`VirtIOInitializationError`]. Otherwise, it shall return + /// the type of device connected to this VirtIO transport. + fn initialize( + &self, + driver: &dyn VirtIODeviceDriver, + queues: &'static [&'static dyn Virtqueue], + ) -> Result; + + /// Notify the device of a changed [`Virtqueue`]. + /// + /// Whenever a queue has been updated (e.g. move descriptors from the used + /// to available ring) and these updates shall be made visible to the + /// driver, the queue can invoke this function, passing its own respective + /// queue ID. + fn queue_notify(&self, queue_id: u32); +} diff --git a/doc/.gitignore b/doc/.gitignore index bd2c55a078..670fd39258 100644 --- a/doc/.gitignore +++ b/doc/.gitignore @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + rustdoc/ website.zip diff --git a/doc/BluetoothLEStack.md b/doc/BluetoothLEStack.md deleted file mode 100644 index 10385a9c9d..0000000000 --- a/doc/BluetoothLEStack.md +++ /dev/null @@ -1,196 +0,0 @@ -Bluetooth Low Energy Design Document -==================================== - - - - - -- [System call interface](#system-call-interface) - * [Device address](#device-address) - * [Advertising](#advertising) - * [Scanning](#scanning) - * [Connection-oriented communication](#connection-oriented-communication) -- [Hardware Interface Layer (HIL)](#hardware-interface-layer-hil) - - - -## System call interface - -The system call interface is modeled after the HCI interface defined in the -Bluetooth specification. - -### Device address - -The kernel assigns the device address. The process may read the device address -using an `allow` system call. - -### Advertising - -For advertising, the system call interface allows a process to configure an -advertising payload, advertising event type, scan response payload, interval and -tx power. Permissible advertising types include: - - * Connectable undirected - - * Connectable directed - - * Non-connectable undirected - - * Scannable undirected - -The driver is _not_ responsible for validating that the payload for these -advertising types follows any particular specification. Advertising event types -that require particular interactions at the link-layer with peer devices (e.g. -scanning or establishing connections) are not permissible: - - * Scan request - - * Scan response - - * Connect request - -Scan response are sent automatically if a scan response payload is configured. -Scan request and connection requests are handled by other parts of the system -call interface. - -To set up an advertisement: - - 1. Configure the advertisement payload, type, interval, tx power and, - optionally, scan response payload. - - * Advertisement payload `allow` - - * Advertisement type `command` - - * If the advertising type is scannable, you SHOULD configure a scan - response payload using `allow` - - * Advertisement interval `command` - - * Advertisement tx power `command` - - 2. Start periodic advertising using a `command` - -Any changes to the configuration while periodic advertising is happening will -take effect in a future advertising event. The kernel will use best effort to -reconfigure advertising in as few events as possible. - -To stop advertising - - 1. Stop periodic advertising a `command` - -### Scanning - -### Connection-oriented communication - -## Hardware Interface Layer (HIL) - -The Bluetooth Low Energy Radio HIL defines a cross-platform interface for -interacting with on-chip BLE radios (i.e. it does not necessarily work for -radios on a dedicated IC connected over a bus). - -The goal of this interface is to expose low-level details of the radio that are -common across platforms, except in cases where abstraction is needed for common -cases to meet timing constraints. - - -```rust -pub trait BleRadio { - /// Sets the channel on which to transmit or receive packets. - /// - /// Returns Err(ErrorCode::BUSY) if the radio is currently transmitting or - /// receiving, otherwise Ok(()). - fn set_channel(&self, channel: RadioChannel) -> Result<(), ErrorCode>; - - /// Sets the transmit power - /// - /// Returns Err(ErrorCode::BUSY) if the radio is currently transmitting or - /// receiving, otherwise Ok(()). - fn set_tx_power(&self, power: u8) -> Result<(), ErrorCode>; - - /// Transmits a packet over the radio - /// - /// Returns Err(ErrorCode::BUSY) if the radio is currently transmitting or - /// receiving, otherwise Ok(()). - fn transmit_packet( - &self, - buf: &'static mut [u8], - disable: bool) -> Result<(), ErrorCode>; - - /// Receives a packet of at most `buf.len()` size - /// - /// Returns Err(ErrorCode::BUSY) if the radio is currently transmitting or - /// receiving, otherwise Ok(()). - fn receive_packet(&self, buf: &'static mut [u8]) -> Result<(), ErrorCode>; - - // Aborts an ongoing transmision - // - // Returns None if no transmission was ongoing, or the buffer that was - // being transmitted. - fn abort_tx(&self) -> Option<&'static mut [u8]>; - - // Aborts an ongoing reception - // - // Returns None if no transmission was ongoing, or the buffer that was // - // being received into. The returned buffer may or may not have some populated - // bytes. - fn abort_rx(&self) -> Option<&'static mut [u8]>; - - // Disable periodic advertisements - // - // Returns always Ok(()) because it does not respect whether - // the driver is actively advertising or not - fn disable(&self) -> Result<(), ErrorCode>; -} - -pub trait RxClient { - fn receive_event(&self, buf: &'static mut [u8], len: u8, result: Result<(), ErrorCode>); -} - -pub trait TxClient { - fn transmit_event(&self, buf: &'static mut [u8], result: Result<(), ErrorCode>); -} - -pub enum RadioChannel { - DataChannel0 = 4, - DataChannel1 = 6, - DataChannel2 = 8, - DataChannel3 = 10, - DataChannel4 = 12, - DataChannel5 = 14, - DataChannel6 = 16, - DataChannel7 = 18, - DataChannel8 = 20, - DataChannel9 = 22, - DataChannel10 = 24, - DataChannel11 = 28, - DataChannel12 = 30, - DataChannel13 = 32, - DataChannel14 = 34, - DataChannel15 = 36, - DataChannel16 = 38, - DataChannel17 = 40, - DataChannel18 = 42, - DataChannel19 = 44, - DataChannel20 = 46, - DataChannel21 = 48, - DataChannel22 = 50, - DataChannel23 = 52, - DataChannel24 = 54, - DataChannel25 = 56, - DataChannel26 = 58, - DataChannel27 = 60, - DataChannel28 = 62, - DataChannel29 = 64, - DataChannel30 = 66, - DataChannel31 = 68, - DataChannel32 = 70, - DataChannel33 = 72, - DataChannel34 = 74, - DataChannel35 = 76, - DataChannel36 = 78, - AdvertisingChannel37 = 2, - AdvertisingChannel38 = 26, - AdvertisingChannel39 = 80, -} -``` diff --git a/doc/CodeReview.md b/doc/CodeReview.md index b0e389f7ea..e0284f5387 100644 --- a/doc/CodeReview.md +++ b/doc/CodeReview.md @@ -5,13 +5,13 @@ This document describes how the Tock [core working group](./wg/core/README.md) merges pull requests for the main Tock repository. - + -- [1. Introduction](#1-introduction) -- [2. Pull Requests](#2-pull-requests) -- [3. Continuous Integration](#3-continuous-integration) +- [Introduction](#introduction) +- [Pull Requests](#pull-requests) +- [Continuous Integration](#continuous-integration) * [CI Organization](#ci-organization) + [The short answer: `make prepush`](#the-short-answer-make-prepush) + [The complete CI setup](#the-complete-ci-setup) @@ -19,14 +19,24 @@ merges pull requests for the main Tock repository. - [`ci-setup-*`](#ci-setup-) - [`ci-runner-*[-*]`](#ci-runner--) - [`ci-all`](#ci-all) -- [4. Reviews](#4-reviews) +- [Comments and Review Criteria](#comments-and-review-criteria) + * [General Review Principles](#general-review-principles) + * [Review Guide by Repository Subsystem](#review-guide-by-repository-subsystem) + + [Core Kernel (`/kernel` crate) Not Including HILs](#core-kernel-kernel-crate-not-including-hils) + + [HILs](#hils) + + [Capsules](#capsules) + + [Chips](#chips) + + [Boards](#boards) + + [Arch](#arch) + + [Libraries](#libraries) +- [Reviews](#reviews) - [Other Tock Repositories](#other-tock-repositories) * [Userland Repositories](#userland-repositories) * [Tertiary Repositories](#tertiary-repositories) -## 1. Introduction +## Introduction As Tock supports more chips and services, changes to core interfaces or capsules will increasingly trigger bugs or integration problems. This @@ -37,7 +47,7 @@ change as problems or issues arise. Active development occurs on the master branch. Periodic releases (discussed more below) are made on branches. -## 2. Pull Requests +## Pull Requests Any pull request against the master branch is reviewed by the core Tock team. Pull requests fall into two categories: @@ -84,7 +94,7 @@ possible responses: Core team members can change their votes at any time, based on discussion, changes, or further thought. -## 3. Continuous Integration +## Continuous Integration Tock leans heavily on automated integration testing and as a project is generally willing to explore new and novel means of testing for hardware @@ -172,7 +182,198 @@ A meta target that runs every piece of CI possible. If this passes locally, all upstream CI checks should pass. -## 4. Reviews +## Comments and Review Criteria + +Most pull requests will receive comments and reviews via Github from core team +members and other interested parties. Likely, only trivial pull requests (e.g. +spelling/formatting fixes, test fixes, or documentation/comment updates) will be +merged without discussion. + +The types and detail of the comments will vary based on the type of change and +the location within the repository of the changes. Changes marked "significant" +will receive a more thorough review. Similarly, changes to code that is used +widely (e.g. changes within the core `kernel` crate) will be more scrutinized. + +To help pull request reviewers and pull request submitters alike, we document +review principles that will be used when evaluating pull requests. + +### General Review Principles + +**PR Mechanics** + +- Is this a self-contained change, or should portions of the pull request be + split into separate PRs? Note, this is not evaluated by number of files or + changed lines, but rather by the semantic meaning of the changes. +- Are the commits all relevant to the change, or are there possibly unrelated + branches that are unintentionally included? +- Does the PR provide enough explanation to help reviewers understand its + purpose, and to explain the change for future readers of the code? Does the PR + link to relevant tracking issues or other discussions? Are there existing + discussions that should be referenced? + +**Documentation and Comments** + +- Many core designs of Tock are documented in specific markdown files. Does this + PR change any of those designs/details, and are the corresponding documents + updated? +- Does the change include proper rustdoc comments for new files and new data + structures? +- If the change is user-facing (i.e. part of a public API or command line + interface) does it include enough helpful information for users to understand + how to use it? +- If documentation is removed, is it clear that it is no longer accurate or + needed? Or is there justification for removing the documentation? + +**Code** + +- Is `unsafe` used? If so, the changes in this PR need to be carefully + evaluated. The purpose of `unsafe` must be documented and why it is used + correctly must be explained in a comment. +- Is `unsafe` used when code is not actually memory or type unsafe (i.e. does + not violate the Rust safety model)? This should not be marked `unsafe` and + instead should likely be marked safe or use a capability. +- Does the code use interrupts and callbacks? If so, the code MUST NOT issue a + callback from a downcall. The callback may ONLY be called in response to an + interrupt. Using deferred calls is often necessary to remedy this. +- Are Rust features (i.e. conditional compilation and `#[cfg]`) used? These must + be clearly motivated and documented, and are only permitted in specific cases. +- Are `lib.rs` or `mod.rs` files added? In general these should only be used to + reference other modules and setup exports. Actual OS logic should be in + descriptively named files. +- `static_init!()` (and similar) must only be called from board crates. +- Is any new functionality both publicly exported and have invariants which + cannot be enforced by the type system or other automated means (e.g., they + provide access to sensitive core kernel data structures)? If so, this should + likely be guarded with a capability. +- Uses of `#inline` directives should explain in an adjacent comment why they + are needed. + +### Review Guide by Repository Subsystem + +In addition to general code review practices, certain review principles are only +applicable in specific portions of the tock kernel repository. + +#### Core Kernel (`/kernel` crate) Not Including HILs + +In general, any substantial changes to the kernel crate should be accompanied +first by a discussion issue. This permits discussion on if the change should be +included in the Tock kernel and if so how it should be implemented. + +All substantial changes should be clearly documented. New files should use `//!` +comments to explain their purpose, and all functions and data structures should +be clearly documented with `///` comments. Often additional documentation in +discrete markdown files is required as well. + +Additionally, particularly subtle or extensively discussed rationale should be +included in the source file directly (often with a `//` comment). This leaves a +clear trace of how key design decisions in Tock were decided and why certain +aspects may not use the most intuitive design. This helps avoid re-hashing +discussions and assist new users with understanding the kernel. + +All `unsafe` usage MUST be accompanied by a comment starting with `### Safety` +that discusses exactly why the unsafe code is necessary and what checks are +needed and completed to ensure the use of `unsafe` does not trigger undefined +behavior. + +All new exports from the core kernel crate must be carefully examined. Certain +functionality is only safe within the core kernel. As essentially every crate in +Tock uses `kernel` as a dependency, anything exported can be used broadly. +Functionality which is sensitive but _must_ be exported must be guarded by a +capability. + +#### HILs + +New HILs should follow the [TRD on HIL design](./reference/trd3-hil-design.md). + +HILs should be well documented and not specifically matched to a single hardware +platform. + +All valid errors should be enumerated. + +HIL naming should be reasonably consistent and clear. + +#### Capsules + +Capsules should explain what they do in comments but do not need to be +rigorously commented. + +**Virtualizers** + +Virtualizers multiplex an underlying resource for multiple users. + +- The `Mux` struct should handle all interrupts, and route callbacks to specific + virtualizer users. +- The virtualizer should provide the same interface (i.e. HIL) as it uses from + the underlying shared resource. + +**Syscall Drivers** + +Syscall drivers implement `SyscallDriver` to provide interfaces for userspace. + +- These drivers must support potential calls from multiple processes. They do + not need to be fully virtualized, e.g. a driver which rejects syscalls from + all but the first process to access it is acceptable, but drivers must not + break if multiple processes attempt access. +- They must return `CommandReturn::SUCCESS` for `command_id==0`. +- They should use the first argument to any upcalls as a ReturnCode. +- They should only provide an interface to userspace on top of some resource, + and should not implement additional functionality which may also be useful + within the kernel. The additional functionality should be a separate capsule. + +#### Chips + +Often changes within chip crates are difficult to test as not many reviewers may +have the specific hardware. Review comments often rely on visual inspection of +the code. + +Files in a chip crate should avoid giving the impression of functionality which +is not actually implemented. This means avoiding peripheral files which only +contain registers or return `ErrorCode::NOSUPPORT` for all methods. A peripheral +must implement at least basic functionality to be merged in mainline Tock. + +Chip crates should be properly named. Many chips use nested crates to represent +families of chips and to share implementations. + +Rust `cfg` features should be avoided. However, in circumstances where different +chips differ in very small ways, and those differences are well understand and +likely documented in a datashet, chip-variant configs may be used. They should +be contained to a single file (i.e. not scattered throughout the crate). It +should be entirely unambiguous whether a feature is set or not (i.e. it should +be based on physical hardware where it is obvious which chip a user has). +Generally, this means `cfg` directives should be an explicit list of chips or'd +together. Rarely, if ever, is a `cfg(not ...)` the correct approach for anything +outside of unit tests. + +#### Boards + +Changes to boards are generally left to the maintainer or original contributor +of the board. Generally boards are thought of as examples or starting points, +and may vary in terms of what functionality is exposed. + +New boards should explain how someone can get the hardware and how to get +started running Tock and applications. + +#### Arch + +Changes to the architecture crate are somewhat uncommon. + +Any assembly should be clearly documented and explained why it is needed to be +in assembly. + +#### Libraries + +The libraries folder in the tock repo contains which is used by the Tock kernel +but is also logically distinct from the kernel and could be used outside of +Tock. + +If code is added to libraries from other sources it should be clearly +attributed. + +Changes to libraries may affect other (i.e. non-Tock) projects. Certain changes +may require discussions on how to include them without breaking downstream +users. + +## Reviews To be merged, a pull request requires two Accept and no Discuss votes. The review period begins when a review is requested from the Github team diff --git a/doc/CodeSize.md b/doc/CodeSize.md deleted file mode 100644 index d3839c0178..0000000000 --- a/doc/CodeSize.md +++ /dev/null @@ -1,259 +0,0 @@ -# Minimizing Tock code size - -Many embedded applications are ultimately limited by the flash space available on -the board in use. This document provides tips on how to write Rust code such that -it does not require an undue amount of flash, and highlights some options which -can be used to reduce the size required for a particular image. - -## Code Style: tips for keeping Rust code small - -#### When to use generic types with trait bounds versus trait objects (`dyn`) - -Polymorphic structs and functions are one of the biggest sources of bloat in Rust -binaries -- use of generic types can lead to bloat from monomorphization, while -use of trait objects introduces vtables into the binary and limits opportunities -for inlining. - -Use `dyn` when the function in question will be called with multiple concrete types; -otherwise code size is increased for every concrete type used (monomorphization). - -```rust -fn set_gpio_client(&dyn GpioClientTrait) -> Self {//...} - -// elsewhere -let radio: Radio = Radio::new(); -set_gpio_client(&radio); - -let button: Button = Button::new(); -set_gpio_client(&button); -``` - -Use generics with trait bounds when the function is only ever called with a single public type -per board; this reduces code size and run time cost. This increases source code -complexity for decreased image size and decreased clock cycles used. - -```rust -// On a given chip, there is only a single FlashController. We use generics so -// that there can be a shared interface by all FlashController's on different chips, -// but in a given binary this function will never be called with multiple types. -impl<'a, F: FlashController> StorageDriverBackend<'a, F> { - pub fn new( - storage: &'a StorageController<'a, F>, - ) -> Self { ... } - -``` - -Similarly, only use const generics when there will not be monomorphization, or if the body of the -method which would be monomorphized is sufficiently small that it will be inlined anyways. - -#### Non-generic-inner-functions - -Sometimes, generic monomorphization is unavoidable (much of the code in grant.rs is an example -of this). When generics must be used despite functions being called with multiple different types, -use the non-generic-inner-function method, written about at -https://www.possiblerust.com/pattern/non-generic-inner-functions , and applied in our codebase -(see [here](https://github.com/tock/tock/pull/2648) for an example). - -#### Panics -Panics add substantial code size to Tock binaries -- on the order of 50-75 bytes per panic. Returning -errors is much cheaper than panicing, and also produces more dependable code. Whenever possible, return -errors instead of panicing. Often, this will not only mean avoiding explicit panics: many core library -functions panic internally depending on the input. - -The most common panics in Tock binaries are from array accesses, but these can often be ergonomically -replaced with result-based error handling: - -```rust -// BAD: produces bloat -fn do_stuff(&mut self) -> Result<(), ErrorCode> { - if self.my_array[4] == 7 { - self.other_array[3] = false; - Ok(()) - } else { - Err(ErrorCode::SIZE) - } -} -``` -```rust -// GOOD -fn do_stuff(&mut self) -> Result<(), ErrorCode> { - if self.my_array.get(4).ok_or(ErrorCode::FAIL)? == 7 { - *(self.other_array.get_mut(3).ok_or(ErrorCode::FAIL)?) = false; - Ok(()) - } else { - Err(ErrorCode::SIZE) - } -} -``` - -Similarly, avoid code that could divide by 0, and avoid signed division which could -divide a types MIN value by -1. Finally, avoid using `unwrap()` / `expect()`, -and make sure to give the compiler enough information that it can guarantee `copy_from_slice()` -is only being called on two slices of equal length. - - -#### Formatting Overhead -Implementations of `fmt::Debug` and `fmt::Display` are expensive -- the core library functions they -rely on include multiple panics and lots of (size) expensive formatting/unicode code that is unneccessary -for simple use cases. This is well-documented elsewhere: https://jamesmunns.com/blog/fmt-unreasonably-expensive/ . Accordingly, use `#[derive(Debug)]` and `fmt::Display` sparingly. For simple enums, manual `to_string(&self) -> &str` methods can be subtantially cheaper. For example, consider the following enum/use: - -```rust -// BAD -#[derive(Debug)] -enum TpmState { - Idle, - Ready, - CommandReception, - CommandExecutionComplete, - CommandExecution, - CommandCompletion, -} - -let tpm_state = TpmState::Idle; -debug!("{:?}", tpm_state); -``` -```rust -// GOOD -enum TpmState { - Idle, - Ready, - CommandReception, - CommandExecutionComplete, - CommandExecution, - CommandCompletion, -} - -impl TpmState { - fn to_string(&self) -> &str { - use TpmState::*; - match self { - Idle => "Idle", - Ready => "Ready", - CommandReception => "CommandReception", - CommandExecutionComplete => "CommandExecutionComplete", - CommandExecution => "CommandExecution", - CommandCompletion => "CommandCompletion", - } - } -} - -let tpm_state = TpmState::Idle; -debug!("{}", tpm_state.to_string()); -``` - -The latter example is 112 bytes smaller than the former, despite being functionally equivalent. - -For structs with runtime values that cannot easily be turned into `&str` representations -this process is not so straightforward, consider whether -the substantial overhead of calling these methods is worth the debugability improvement. - -#### 64 bit division - -Avoid all 64 bit division/modulus, it adds ~1kB if used, as the software techniques for performing -these are speed oriented. Often bit manipulation approaches will be much cheaper, especially if -one of the operands to the division is a compile-time constant. - -#### Global Arrays -For global `const`/`static mut` variables, don’t store collections in arrays unless all -elements of the array are used. - -The canonical example of this is GPIO -- if you have 100 GPIO pins, but your binary only uses 3 of them: -```rust -pub const GPIO_PINS: [Pin; 100] = [//...]; //BAD -- UNUSED PINS STILL IN BINARY -``` -```rust -// GOOD APPROACH -pub const GPIO_PIN_0: Pin = Pin::new(0); -pub const GPIO_PIN_1: Pin = Pin::new(1); -pub const GPIO_PIN_2: Pin = Pin::new(2); -// ...and so on. -``` -The latter approach ensures that the compiler can remove pins which are not used from the binary. - -#### Combine register accesses -Combine register accesses into as few volatile operations as possible. E.g. -```rust -regs.dcfg.modify(DevConfig::DEVSPD::FullSpeed1_1); -regs.dcfg.modify(DevConfig::DESCDMA::SET); -regs.dcfg.modify(DevConfig::DEVADDR.val(0)); -``` -is much more expensive than -```rust -regs.dcfg.modify( - DevConfig::DEVSPD::FullSpeed1_1 + DevConfig::DESCDMA::SET + DevConfig::DEVADDR.val(0), -); -``` -because each individual modify is volatile so the compiler cannot optimize the calls together. - -#### Minimize calls to `Grant::enter()` -Grants are fundamental to Tock's architecture, but the internal implementation of Grant's are -relatively complex. Further, Grant's are generic over all types that are stored in Grants, so -multiple copies of many Grant functions end up in the binary. The largest of these is -`Grant::enter()`, which is called often in capsule code. That said, it is often possible to -reduce the number of calls to this method. For example: you can combine calls to apps.enter(): -```rust -// BAD -- DONT DO THIS -match command_num { - 0 => self.apps.enter(|app, _| {app.perform_cmd_0()}, - 1 => self.apps.enter(|app, _| {app.perform_cmd_1()}, -} -``` -```rust -// GOOD -- DO THIS -self.apps.enter(|app, _| { - match command_num { - 0 => app.perform_cmd_0(), - 1 => app.perform_cmd_1(), - } -}) -``` -The latter saves ~100 bytes because each additional call to `Grant::enter()` leads to an additional -monomorphized copy of the body of `Grant::enter()`. - -#### Scattered additional tips -* Avoid calling functions in `core::str`, there is lots of overhead here that is not optimized out. - For example: if you have a space seperated string, using `text.split_ascii_whitespace()` - costs 150 more bytes than using `text.as_bytes().split(|b| *b == b' ');`. -* Avoid static mut globals when possible, and favor global constants. static mut variables - are placed in .relocate, so they consume both flash and RAM, and cannot be optimized as well - because the compiler cannot make its normal aliasing assumptions. -* Use const generics to pass sized arrays instead of slices, unless this will lead to monomorphization. - In addition to removing panics on array accesses, this allows for passing smaller objects - (references to arrays are just a pointer, slices are pointer + length), and lets the compiler - make additional optimizations based on the known array length. -* Test the effect of #[inline(always/never)] directives, sometimes the result will surprise you. - If the savings are small, it is usually better to leave it up to the compiler, for increased - resilience to future changes. -* For functions that will not be inlined, try to keep arguments/returns in registers. - On RISC-V, this means using <= 8 1-word arguments, no arguments > 2 words, and - <= 2 words in return value - - -## Reducing the size of an out-of-tree board - -In general, upstream Tock strives to produce small binaries. However, there is often a tension -between code size, debugability, and user friendliness. As a result, upstream Tock does not -always choose the most minimal configuration possible. For out-of-tree boards especially focused -on code size, there are a few steps you can take to further reduce code size: - -* Disable the `debug_panic_info` option in `kernel/src/config.rs` -- this will remove a lot - of debug information that is provided in panics, but can reduce code size by 8-12 kB. -* Implement your own peripheral struct that does not include peripherals you do not need. - Often, the `DefaultPeripherals` struct for a chip may include peripherals not used in your - project, and the structure of the interrupt handler means that you will pay the code size - cost of the unused peripherals unless you implement your own Peripheral struct. The option - to do this was first introduced in https://github.com/tock/tock/pull/2069 and is explained there. -* Modify your panic handler to not use the `PanicInfo` struct. This will allow LLVM to optimize - out the paths, panic messages, line numbers, etc. which would otherwise be stored in the binary - to allow users to backtrace and debug panics. -* Remove the implementation of `debug!()`: if you really want size savings, and are ok not printing - anything, you can remove the implementation of `debug!()` and replace it with an empty macro. - This will remove the code associated with any calls to `debug!()` in the core kernel or chip - crates that you depend on, as well as any remaining code associated with the fmt machinery. -* Fine-tune your inline-threshold. This can have a significant impact, but the ideal value - is dependent on your particular code base, and changes as the compiler does -- update it when - you update the compiler version! In practice, we have observed that very small values are often - optimal (e.g., in the range of 2 to 10). This is done by passing `-C inline-threshold=x` to rustc. -* Try `opt-level=s` instead of `opt-level=z`. In practice, `s` (when combined with a reduced - inline threshold) often seems to produce smaller binaries. This is worth revisiting periodically, - given that `z` is supposed to lead to smaller binaries than `s` diff --git a/doc/Compilation.md b/doc/Compilation.md deleted file mode 100644 index 6ba87d051d..0000000000 --- a/doc/Compilation.md +++ /dev/null @@ -1,251 +0,0 @@ -# How does Tock compile? - -There are two types of compilation artifacts in Tock: the kernel and user-level -processes (i.e. apps). Each type compiles differently. In addition, each -platform has a different way of programming the kernel and processes. Below is -an explanation of both kernel and process compilation as well as some examples -of how platforms program each onto an actual board. - - - - - -- [Compiling the kernel](#compiling-the-kernel) - * [Life of a Tock compilation](#life-of-a-tock-compilation) - * [LLVM Binutils](#llvm-binutils) - * [Special `.apps` section](#special-apps-section) -- [Compiling a process](#compiling-a-process) - * [Position Independent Code](#position-independent-code) - * [Tock Binary Format](#tock-binary-format) - * [Tock Application Bundle](#tock-application-bundle) - + [TAB Format](#tab-format) - + [Metadata](#metadata) -- [Loading the kernel and processes onto a board](#loading-the-kernel-and-processes-onto-a-board) - - - -## Compiling the kernel - -The kernel is divided into five Rust crates (i.e. packages): - - * A core kernel crate containing key kernel operations such as handling - interrupts and scheduling processes, shared kernel libraries such as - `TakeCell`, and the Hardware Interface Layer (HIL) definitions. This is - located in the `kernel/` folder. - - * An architecture (e.g. _ARM Cortex M4_) crate that implements context - switching, and provides memory protection and systick drivers. This is - located in the `arch/` folder. - - * A chip-specific (e.g. _Atmel SAM4L_) crate which handles interrupts and - implements the hardware abstraction layer for a chip's peripherals. This is - located in the `chips/` folder. - - * One (or more) crates for hardware independent drivers and virtualization - layers. This is the `capsules/` folder in Tock. External projects using - Tock may create additional crates for their own drivers. - - * A platform specific (e.g. _Imix_) crate that configures the chip and - its peripherals, assigns peripherals to drivers, sets up virtualization - layers, and defines a system call interface. This is located in `boards/`. - -These crates are compiled using [Cargo](http://doc.crates.io), Rust's package -manager, with the platform crate as the base of the dependency graph. In -practice, the use of Cargo is masked by the Makefile system in Tock. Users can -simply type `make` from the proper directory in `boards/` to build the kernel -for that platform. - -Internally, the Makefile is simply invoking Cargo to handle the build. For -example, `make` on the imix platform translates to: - -```bash -$ cargo build --release --target=thumbv7em-none-eabi -``` - -The `--release` argument tells Cargo to invoke the Rust compiler with -optimizations turned on. `--target` points Cargo to the target specification -which includes the LLVM data-layout definition and architecture definitions for -the compiler. - - -### Life of a Tock compilation - -When Cargo begins compiling the platform crate, it first resolves all -dependencies recursively. It chooses package versions that satisfy the -requirements across the dependency graph. Dependencies are defined in each -crate's `Cargo.toml` file and refer to paths in the local file-system, a -remote git repository, or a package published on [crates.io](http://crates.io). - -Second, Cargo compiles each crate in turn as dependencies are satisfied. Each -crate is compiled as an `rlib` (an `ar` archive containing object files) -and combined into an executable ELF file by the compilation of the platform -crate. - -You can see each command executed by `cargo` by passing it the `--verbose` -argument. In our build system, you can run `make V=1` to see the verbose -commands. - - -### LLVM Binutils - -Tock uses the `lld`, `objcopy`, and `size` tools included with the Rust -toolchain to produce kernel binaries that are executed on microcontrollers. This -has three main ramifications: - -1. The tools are not entirely feature-compatible with the GNU versions. While - they are very similar, there are edge cases where they do not behave exactly - the same. This will likely improve with time, but it is worth noting in case - unexpected issues arise. -2. The tools will automatically update with Rust versions. The tools are - provided in the `llvm-tools` rustup component that is compiled for and ships - with every version of the Rust toolchain. Therefore, if Rust updates the - version they use in the Rust repository, Tock will also see those updates. -3. Tock no longer relies on an external dependency to provide these tools. That - should ensure that all Tock developers are using the same version of the - tools. - -### Special `.apps` section - -Tock kernels include a `.apps` section in the kernel .elf file that is at the -same physical address where applications will be loaded. When compiling the -kernel, this is just a placeholder and is not populated with any meaningful -data. It exists to make it easy to update the kernel .elf file with an -application binary to make a monolithic .elf file so that the kernel and apps -can be flashed together. - -When the Tock build system creates the kernel binary, it explicitly removes this -section so that the placeholder is not included in the kernel binary. - -To use the special `.apps` section, `objcopy` can replace the placeholder with an actual app binary. The general command looks like: - -```bash -$ arm-none-eabi-objcopy --update-section .apps=libtock-c/examples/c_hello/build/cortex-m4/cortex-m4.tbf target/thumbv7em-none-eabi/release/stm32f412gdiscovery.elf target/thumbv7em-none-eabi/release/stm32f4discovery-app.elf -``` - -This replaces the placeholder section `.apps` with the "c_hello" application TBF -in the stm32f412gdiscovery.elf kernel ELF, and creates a new .elf called -`stm32f4discovery-app.elf`. - -## Compiling a process - -Unlike many other embedded systems, compilation of application code is entirely -separated from the kernel in Tock. An application is combined with at least two -libraries: `libtock` and `newlib` and built into a free-standing binary. The -binary can then be uploaded onto a Tock platform with an already existing -kernel to be loaded and run. - -Tock can support any programming language and compiler provided they meet -the following requirements: - - 1. The application must be built as position independent code (PIC). - - 2. The application must be linked with a loader script that places Flash - contents above address `0x80000000` and RAM contents below it. - - 3. The application binary must start with a header detailing the location of - sections in the binary. - -The first requirement is explained directly below while the other two are -detailed in [Tock Binary Format](#tock-binary-format). - - -### Position Independent Code - -Since Tock loads applications separately from the kernel and is capable of -running multiple applications concurrently, applications cannot know in advance -at which address they will be loaded. This problem is common to many computer -systems and is typically addressed by dynamically linking and loading code at -runtime. - -Tock, however, makes a different choice and requires applications to be compiled -as position independent code. Compiling with PIC makes all control flow relative -to the current PC, rather than using jumps to specified absolute addresses. All -data accesses are relative to the start of the data segment for that app, and -the address of the data segment is stored in a register referred to as the `base -register`. This allows the segments in Flash and RAM to be placed anywhere, and -the OS only has to correctly initialize the base register. - -PIC code can be inefficient on some architectures such as x86, but the ARM -instruction set is optimized for PIC operation and allows most code to execute -with little to no overhead. Using PIC still requires some fixup at runtime, but -the relocations are simple and cause only a one-time cost when an application -is loaded. A more in-depth discussion of dynamically loading applications can -be found on the Tock website: [Dynamic Code Loading on a -MCU](http://www.tockos.org/blog/2016/dynamic-loading/). - -For applications compiled with `arm-none-eabi-gcc`, building PIC code for Tock -requires four flags: - - - `-fPIC`: only emit code that uses relative addresses. - - `-msingle-pic-base`: force the use of a consistent _base register_ for the - data sections. - - `-mpic-register=r9`: use register r9 as the base register. - - `-mno-pic-data-is-text-relative`: do not assume that the data segment is - placed at a constant offset from the text segment. - -Each Tock application uses a linker script that places Flash at address -`0x80000000` and SRAM at address `0x00000000`. This allows relocations pointing -at Flash to be easily differentiated from relocations pointing at RAM. - -### Tock Binary Format - -In order to be loaded correctly, applications must follow the [Tock Binary -Format](TockBinaryFormat.md). This means the initial bytes of a Tock app must -follow this format so that Tock can load the application correctly. - -In practice, this is automatically handled for applications. As part of the -compilation process, a tool called [Elf to TAB](https://github.com/tock/elf2tab) -does the conversion from ELF to Tock's expected binary format, ensuring that -sections are placed in the expected order, adding a section that lists necessary -load-time relocations, and creating the TBF header. - - -### Tock Application Bundle - -To support ease-of-use and distributable applications, Tock applications are -compiled for multiple architectures and bundled together into a "Tock -Application Bundle" or `.tab` file. This creates a standalone file for an -application that can be flashed onto any board that supports Tock, and removes -the need for the board to be specified when the application is compiled. -The TAB has enough information to be flashed on many or all Tock compatible -boards, and the correct binary is chosen when the application is flashed -and not when it is compiled. - -#### TAB Format - -`.tab` files are `tar`ed archives of TBF compatible binaries along with a -`metadata.toml` file that includes some extra information about the application. -A simplified example command that creates a `.tab` file is: - -```bash -tar cf app.tab cortex-m0.bin cortex-m4.bin metadata.toml -``` - -#### Metadata - -The `metadata.toml` file in the `.tab` file is a TOML file that contains a -series of key-value pairs, one per line, that provides more detailed information -and can help when flashing the application. Existing fields: - -``` -tab-version = 1 // TAB file format version -name = "" // Package name of the application -only-for-boards = // Optional list of board kernels that this application supports -build-date = 2017-03-20T19:37:11Z // When the application was compiled -``` - -## Loading the kernel and processes onto a board - -There is no particular limitation on how code can be loaded onto a board. JTAG -and various bootloaders are all equally possible. For example, the `hail` and -`imix` platforms primarily use the serial "tock-bootloader", and the other -platforms use jlink or openocd to flash code over a JTAG connection. In general, -these methods are subject to change based on whatever is easiest for users of -the platform. - -In order to support multiple concurrent applications, the easiest option is to -use `tockloader` ([git repo](https://github.com/tock/tockloader)) to -manage multiple applications on a platform. Importantly, while applications -currently share the same upload process as the kernel, they are planned to -support additional methods in the future. Application loading through wireless -methods especially is targeted for future editions of Tock. diff --git a/doc/Configuration.md b/doc/Configuration.md deleted file mode 100644 index d5eb56335d..0000000000 --- a/doc/Configuration.md +++ /dev/null @@ -1,49 +0,0 @@ -Configuration -============= - -Because Tock is meant to run on various platforms (spanning multiple -architectures and various available peripherals), and with multiple use cases in -mind (for example, "production" vs. debug build with various levels of debugging -detail), Tock provides various configuration options so that each build can be -adapted to each use case. - -In Tock, configuration follows some principles to avoid pitfalls of "ifdef" -conditional code (which can be tricky to test). This is currently done in two -ways. - -- **Separation of the code into multiple packages.** Each level of abstraction - (core kernel, CPU architecture, chip, board) has its own package, so that - configuring a board is done by depending on the relevant chip and declaring - the relevant drivers for peripherals avaialble on the board. You can see more - details on the [compilation page](Compilation.md). - -- **Custom kernel configuration.** To facilitate fine-grained configuration of - the kernel (for example to enable tracing the syscalls to the debug output), a - `Config` struct is defined in `kernel/src/config.rs`. The `Config` struct defines - a collection of boolean values which can be imported throughout the kernel - crate to configure the behavior of the kernel. The values of these booleans - are determined by cargo features, which allows for individual boards to determine - which features of the kernel crate are included without users having to manually - modify the code in the kernel crate. Notably, Tock requires that these features *only* - modify values in the global `CONFIG` constant, -- in general, features are not - allowed to be used directly throughout the rest of the crate. Because of how - feature unification works, all features are off-by-default, so if the Tock kernel - wants a default value for a config option to be turning something on, the feature - should be named appropriately -- e.g. the `no_debug_panics` feature is enabled to - set the `debug_panics` config option to `false`. - In order to enable any feature, modify the Cargo.toml in your board file as follows: - ```toml - [dependencies] - # Turn off debug_panics, turn on trace_syscalls - kernel = { path = "../../kernel", features = ["no_debug_panics", "trace_syscalls"]} - ``` - These features should not be set from any crate other than the top-level board crate. - If you prefer not to rely on the features, you can still directly modify the boolean - config value in kernel/src/config.rs if you prefer -- this can be easier when rapidly - debugging on an upstream board, for example. - - To use the configuration, simply read the values. For example, to use a - boolean configuration, just use an if statement: the fact that the - configuration is `const` should allow the compiler to optimize away dead code - (so that this configuration has zero cost), while still checking syntax and - types. diff --git a/doc/Design.md b/doc/Design.md deleted file mode 100644 index 66aa7523af..0000000000 --- a/doc/Design.md +++ /dev/null @@ -1,414 +0,0 @@ -Tock Design -=========== - - - - - -- [Architecture](#architecture) - * [Capsules](#capsules) - * [Processes](#processes) - + [Memory Layout](#memory-layout) - * [Grants](#grants) -- [In-Kernel Design Principles](#in-kernel-design-principles) - * [Role of HILs](#role-of-hils) - * [Split-phase Operation](#split-phase-operation) - * [No External Dependencies](#no-external-dependencies) - * [Using `unsafe` and Capabilities](#using-unsafe-and-capabilities) - * [Ease of Use and Understanding](#ease-of-use-and-understanding) - * [Demonstrated Features](#demonstrated-features) - * [Merge Aggressively, Archive Unabashedly](#merge-aggressively-archive-unabashedly) - - - -Most operating systems provide isolation between components using a process-like -abstraction: each component is given its own slice of the system memory (for -its stack, heap, data) that is not accessible by other components. Processes -are great because they provide a convenient abstraction for both isolation and -concurrency. However, on resource-limited systems, like microcontrollers with -much less than 1MB of memory, this approach leads to a trade-off between -isolation granularity and resource consumption. - -Tock's architecture resolves this trade-off by using a language sandbox to -isolated components and a cooperative scheduling model for concurrency in the -kernel. As a result, isolation is (more or less) free in terms of resource -consumption at the expense of preemptive scheduling (so a malicious component -could block the system by, e.g., spinning in an infinite loop). - -To first order, all components in Tock, including those in the kernel, are -mutually distrustful. Inside the kernel, Tock achieves this with a -language-based isolation abstraction called _capsules_ that incurs no memory or -computation overhead. In user-space, Tock uses (more-or-less) a traditional -process model where process are isolated from the kernel and each other using -hardware protection mechanisms. - -In addition, Tock is designed with other embedded systems-specific goals in -mind. Tock favors overall reliability of the system and discourages components -(prevents when possible) from preventing system progress when buggy. - -## Architecture - -![Tock architecture](architecture.png) - -Tock includes three architectural components: a small trusted kernel, written in -Rust, which implements a hardware abstraction layer (HAL); scheduler; and -platform-specific configuration. Other system components are implemented in one -of two protection mechanisms: **capsules**, which are compiled with the kernel and -use Rust’s type and module systems for safety, and **processes**, which use the MPU -for protection at runtime. - -System components (an application, driver, virtualization layer, etc.) can be -implemented in either a capsule or process, but each mechanism trades off -concurrency and safety with memory consumption, performance, and granularity. - -| Category | Capsule | Process | -| ---------------------- | ----------- | -------------- | -| Protection | Language | Hardware | -| Memory Overhead | None | Separate stack | -| Protection Granularity | Fine | Coarse | -| Concurrency | Cooperative | Preemptive | -| Update at Runtime | No | Yes | - -As a result, each is more appropriate for implementing different components. In -general, drivers and virtualization layers are implemented as capsules, while -applications and complex drivers using existing code/libraries, such as -networking stacks, are implemented as processes. - -### Capsules - -A capsule is a Rust struct and associated functions. Capsules interact with each -other directly, accessing exposed fields and calling functions in other -capsules. Trusted platform configuration code initializes them, giving them -access to any other capsules or kernel resources they need. Capsules can protect -internal state by not exporting certain functions or fields. - -Capsules run inside the kernel in privileged hardware mode, but Rust’s type and -module systems protect the core kernel from buggy or malicious capsules. Because -type and memory safety are enforced at compile-time, there is no overhead -associated with safety, and capsules require minimal error checking. For -example, a capsule never has to check the validity of a reference. If the -reference exists, it points to valid memory of the right type. This allows -extremely fine-grained isolation since there is virtually no overhead to -splitting up components. - -Rust’s language protection offers strong safety guarantees. Unless a capsule is -able to subvert the Rust type system, it can only access resources explicitly -granted to it, and only in ways permitted by the interfaces those resources -expose. However, because capsules are cooperatively scheduled in the same -single-threaded event loop as the kernel, they must be trusted for system -liveness. If a capsule panics, or does not yield back to the event handler, the -system can only recover by restarting. - -### Processes - -Processes are independent applications that are isolated from the kernel and run -with reduced privileges in separate execution threads from the kernel. The -kernel schedules processes preemptively, so processes have stronger system -liveness guarantees than capsules. Moreover, uses hardware protection to enforce -process isolation at runtime. This allows processes to be written in any -language and to be safely loaded at runtime. - -#### Memory Layout - -Processes are isolated from each other, the kernel, and the underlying hardware -explicitly by the hardware Memory Protection Unit (MPU). The MPU limits which -memory addresses a process can access. Accesses outside of a process’s permitted -region result in a fault and trap to the kernel. - -Code, stored in flash, is made -accessible with a read-only memory protection region. Each process is allocated -a contiguous region of RAM. One novel aspect of a process is the presence of a -“grant” region at the top of the address space. This is memory allocated to the -process covered by a memory protection region that the process can neither read -nor write. The grant region, discussed below, is needed for the kernel to be able -to borrow memory from a process in order to ensure liveness and safety in -response to system calls. - -### Grants - -Capsules are not allowed to allocate memory dynamically since dynamic -allocation in the kernel makes it hard to predict if memory will be exhausted. -A single capsule with poor memory management could cause the rest of the kernel -to fail. Moreover, since it uses a single stack, the kernel cannot easily -recover from capsule failures. - -However, capsules often need to dynamically allocate memory in response to -process requests. For example, a virtual timer driver must allocate a structure -to hold metadata for each new timer any process creates. Therefore, Tock allows -capsules to dynamically allocate from the memory of a process making a request. - -It is unsafe, though, for a capsule to directly hold a reference to process -memory. Processes crash and can be dynamically loaded, so, without explicit -checks throughout the kernel code, it would not be possible to ensure that a -reference to process memory is still valid. - -For a capsule to safely allocate memory from a process, the kernel must enforce -three properties: - - 1. Allocated memory does not allow capsules to break the type system. - - 2. Capsules can only access pointers to process memory while the process is - alive. - - 3. The kernel must be able to reclaim memory from a terminated process. - -Tock provides a safe memory allocation mechanism that meets these three -requirements through memory grants. Capsules can allocate data of arbitrary -type from the memory of processes that interact with them. This memory is -allocated from the grant segment. - -Just as with buffers passed through allow, references to granted memory are -wrapped in a type-safe struct that ensures the process is still alive before -dereferencing. Unlike shared buffers, which can only be a buffer type in a -capsule, granted memory can be defined as any type. Therefore, processes cannot -access this memory since doing so might violate type-safety. - -## In-Kernel Design Principles - -To help meet Tock's goals, encourage portability across hardware, and ensure a -sustainable operating system, several design principles have emerged over time -for the Tock kernel. These are general principles that new contributions to the -kernel should try to uphold. However, these principles have been informed by -Tock's development, and will likely continue to evolve as Tock and the Rust -ecosystem evolve. - -### Role of HILs - -Generally, the Tock kernel is structured into three layers: - -1. Chip-specific drivers: these typically live in a crate in the - `chips` subdirectory, or an equivalent crate in an different repository - (e.g. the Titan port is out of tree but its `h1b` create is the - equivalent here). These drivers have implementations that are specific - to the hardware of a particular microcontroller. Ideally, - their implementation is fairly simple, and they merely adhere to a - common interface (a HIL). That's not always the case, but that's - the ideal. - -2. Chip-agnostic, portable, peripheral drivers and subsystems. These - typically live in the `capsules` crate. These include things like - virtual alarms and virtual I2C stack, as well as drivers for - hardware peripherals not on the chip itself (e.g. sensors, radios, - etc). These drivers typically rely on the chip-specific drivers - through the HILs. - -3. System call drivers, also typically found in the `capsules` - crate. These are the drivers that implement a particular part of - the system call interfaces, and are often even more abstracted from - the hardware than (2) - for example, the temperature sensor system - call driver can use any temperature sensor, including several - implemented as portable peripheral drivers. - - - The system call interface is another point of - standardization that can be implemented in various ways. So it’s - perfectly reasonable to have several implementations of the same - system call interface that use completely different hardware - stacks, and therefore HILs and chip-specific drivers (e.g. a - console driver that operates over USB might just be implemented as - a different system call driver that implements the same system - calls, rather than trying to fit USB into the UART HIL). - -Because of their importance, the interfaces between these layers are a key part -of Tock's design and implementation. These interfaces are called Tock's Hardware -Interface Layer, or HIL. A HIL is a portable collection of Rust traits that can -be implemented in either a portable or a non-portable way. An example of a -non-portable implementation of a HIL is an Alarm that is implemented in terms of -counter and compare registers of a specific chip, while an example of a portable -implementation is a virtualization layer that multiplexes multiple Alarms top of -a single underlying Alarm. - -A HIL consists of one or more Rust traits that are intended to be used together. -In some cases, implementations may only implement a subset of a HIL's traits. -For example the analog-to-digital (ADC) conversion HIL may have traits both for -single and streams of samples. A particular implementation may only support -single samples and so not implement the streaming traits. - -The choice of particular HIL interfaces is pretty important, and we have some -general principles we follow: - -1. HIL implementations should be fairly general. If we have an interface that - doesn't work very well across different hardware, we probably have the wrong - interface - it's either too high level, or too low level, or it's just not - flexible enough. But HILs shouldn't generally be designed to optimize for - particular applications or hardware, and definitely not for a particular - combination of applications and hardware. If there are cases where that is - really truly necessary, a driver can be very chip or board specific and - circumvent the HILs entirely. - - Sometimes there are useful interfaces that some chips can provide natively, - while other chips lack the necessary hardware support, but the functionality - could be emulated in some way. In these cases, Tock sometimes uses - "advanced" traits in HILs that enable a chip to expose its more - sophisticated features while not requiring that all implementors of the HIL - have to implement the function. For example, the UART HIL includes a - `ReceiveAdvanced` trait that includes a special function - `receive_automatic()` which receives bytes on the UART until a pause between - bytes is detected. This is supported directly by the SAM4L hardware, but can - also be emulated using timers and GPIO interrupts. By including this in an - advanced trait, capsules can still use the interface but other UART - implementations that do not have that required feature do not have to - implement it. - -2. A HIL implementation may assume it is the only way the device will be used. - As a result, Tock tries to avoid having more than one HIL for a particular - service or abstraction, because it will not, in general, be possible for the - kernel to support simultaneously using different HILs for the same device. - For example, suppose there were two different HILs for a UART with slightly - different APIs. The chip-specific implementation of each one will need to - read and write hardware registers and handle interrupts, so they cannot exist - simultaneously. By allowing a HIL to assume it is the only way the device - will be used, Tock allows HILs to precisely define their semantics without - having to worry about potential future conflicts or use cases. - - - -### Split-phase Operation - -While processes are time sliced and preemptive in Tock, the kernel is -not. Everything is run-to-completion. That is an important design -choice because it allows the kernel to avoid allocating lots of stacks -for lots of tasks, and it makes it possible to reason more simply -about static and other shared variables. - -Therefore, all I/O operations in the Tock kernel are asynchronous and -non-blocking. A method call starts an operation and returns immediately. When -the operation completes, the struct implementing the operation calls a callback. -Tock uses callbacks rather than closures because closures typically require -dynamic memory allocation, which the kernel avoids and does not generally -support. - -This design does add complexity when writing drivers as a blocking API is -generally simpler to use. However, this is a conscious choice to favor overall -safety of the kernel (e.g. avoiding running out of memory or preventing other -code from running on time) over functional correctness of individual drivers -(because they might be more error-prone, not because they cannot be written -correctly). - -There are limited cases when the kernel can briefly block. For example, the -SAM4L's GPIO controller can take up to 5 cycles to become ready between -operations. Technically, a completely asynchronous driver would make this -split-phase: the operation returns immediately, and issues a callback when it -completes. However, because just setting up the callback will take more than 5 -cycles, spinning for 5 cycles is not only simpler, it's also cheaper. The -implementation therefore spins for a handful of cycles before returning, such -that the operation is synchronous. These cases are rare, though: the operation -has to be so fast that it's not worth allowing other code to run during the -delay. - -### No External Dependencies - -Tock chooses to not use any external libraries for any of the crates in the -kernel. This is done to promote safety, as auditing the Tock code only requires -inspecting the code in the Tock repository. Tock tries to be very specific with -its use of `unsafe`, and tries to ensure that when it is used it is clear as to -why. With external dependencies it would be significantly more challenging to -ensure that uses of `unsafe` are valid, particularly as external libraries -evolve. - -We also realize, however, that external libraries can be very useful. Tock's -compromise has been to pull in specific portions of libraries into the -`libraries` folder. This puts the library's source in the same repository, while -keeping the library as a clearly separate crate. We do try to limit how often -this happens. - -In the future, we hope that `cargo` and other Rust tools make it significantly -easier to audit and manage dependencies. For example, cargo currently has no -mechanism to emit an error if a dependency uses `unsafe`. If new tools emerge -that help ensure that dependent code is safe, Tock would likely be able to -leverage external dependencies. - -### Using `unsafe` and Capabilities - -Tock attempts to minimize the amount of unsafe code in the kernel. Of course, -there are a number of operations that the kernel must do which fundamentally -violate Rust's memory safety guarantees, and we try to compartmentalize these -operations and explain how to use them in an ultimately safe manner. - -For operations that violate Rust safety, Tock marks the functions, structs, and -traits as `unsafe`. This restricts the crates that can use these elements. -Generally, Tock tries to make it clear where an unsafe operation is occurring by -requiring the `unsafe` keyword be present. For example, with memory-mapped -input/output (MMIO) registers, casting an arbitrary pointer to a struct that -represents those registers violates memory safety unless the register map and -address are verified to be correct. To denote this, doing the cast is clearly -marked as `unsafe`. However, once the cast is complete, accessing those -registers no longer violates memory safety. Therefore, using the registers does -not require the `unsafe` keyword. - -Not all potentially dangerous code violates Rust's safety model, however. For -example, stopping a process from running on the board does not violate -language-level safety, but is still a potentially problematic operation from a -security and system reliability standpoint, as not all kernel code should be -able halt arbitrary processes (in particular, untrusted capsules should not have -this access to this API). One way to restrict access to these types of functions -would be to re-use the `unsafe` mechanism, since cargo will emit a warning if -code that is prohibited from using `unsafe` attempts to invoke an `unsafe` -function. However, this muddles the use of unsafe, and makes it difficult to -understand if code potentially violates safety or is a restricted API. - -Instead, Tock uses -[capabilities](Soundness.md#capabilities-restricting-access-to-certain-functions-and-operations) -to restrict access to important APIs. As such, any public APIs inside the kernel -that should be very restricted in what other code can use them should require a -specific capability in their function signatures. This prevents code that has -not explicitly been granted the capability from calling the protected API. - -To promote the principle of least privilege, capabilities are relatively -fine-grained and provide narrow access to specific APIs. This means that -generally new APIs will require defining new capabilities. - -### Ease of Use and Understanding - -Whenever possible, Tock's design optimizes to lower the barrier for new users or -developers to understand and use Tock. Sometimes, this means intentionally -making a design choice that prioritizes readability or clarity over performance. - -As an example, Tock generally avoids using Rust's -[features](https://doc.rust-lang.org/1.0.0/book/conditional-compilation.html) -and `#[cfg()]` attribute to enable conditional compilation. While using a set of -features can lead to optimizing exactly what code should be included when the -kernel is built, it also makes it very difficult for users unfamiliar with the -features to decide which features to enable and when. Likely, these users will -use the default configuration, reducing the benefit of having the features -available. Also, conditional compilation makes it very difficult to understand -exactly what version of the kernel is running on any particular board as the -features can substantially change what code is running. Finally, the non-default -options are unlikely to be tested as robustly as the default configuration, -leading to versions of the kernel which are no longer available. - -Tock also tries to ensure Tock "just works" for users. This manifests by trying -to minimize the number of steps to get Tock running. The build system uses -`make` which is familiar to many developers, and just running `make` in a board -folder will compile the kernel. The most supported boards (Hail and imix) can -then be programmed by just running `make install`. Installing an app just -requires one more command: `tockloader install blink`. Tockloader will continue -to expand to support the ease-of-use with Tock. Now, "just works" is a design -goal that Tock is not completely meeting. But, future design decisions should -continue to encourage Tock to "just work". - -### Demonstrated Features - -Tock discourages adding functionality to the kernel unless a clear use case has -been established. For example, adding a red-black tree implementation to -`kernel/src/common` might be useful in the future for some new Tock feature. -However, that would be unlikely to be merged without a use case inside of the -kernel that motivates needing a red-black tree. This general principle provides -a starting point for evaluating new features in pull requests. - -Requiring a use case also makes the code more likely to be tested and used, as -well as updated as other internal kernel APIs change. - -### Merge Aggressively, Archive Unabashedly - -As an experimental embedded operating system with roots in academic research, -Tock is likely to receive contributions of new, risky, experimental, or narrowly -focused code that may or may not be useful for the long-term growth of Tock. -Rather than use a "holding" or "contribution" repository for new, experimental -code, Tock tries to merge new features into mainline Tock. This both eases the -maintenance burden of the code (it doesn't have to be maintained out-of-tree) -and makes the feature more visible. - -However, not all features catch on, or are completed, or prove useful, and having the -code in mainline Tock becomes an overall maintenance burden. In these cases, Tock -will move the code to an [archive repository](https://github.com/tock/tock-archive/). diff --git a/doc/ExternalDependencies.md b/doc/ExternalDependencies.md new file mode 100644 index 0000000000..1424438d5c --- /dev/null +++ b/doc/ExternalDependencies.md @@ -0,0 +1,237 @@ +External Dependencies +===================== + + + + + +- [External Dependency Design](#external-dependency-design) + * [Rationale](#rationale) + * [Dependency Structure of Tock-Internal Crates](#dependency-structure-of-tock-internal-crates) +- [External Dependency Selection](#external-dependency-selection) + * [General Guidelines for Dependency Selection](#general-guidelines-for-dependency-selection) + + [Provide Important Functionality](#provide-important-functionality) + + [Project Maturity](#project-maturity) + + [Limited Sub-dependencies](#limited-sub-dependencies) + * [Board-Specific External Dependencies](#board-specific-external-dependencies) + * [Capsule Crate-Specific External Dependencies](#capsule-crate-specific-external-dependencies) +- [Including the Dependency](#including-the-dependency) + * [Including Capsule Crate-Specific External Dependencies](#including-capsule-crate-specific-external-dependencies) + * [Including Board-Specific External Dependencies](#including-board-specific-external-dependencies) + * [Documenting the Dependency and its Tree](#documenting-the-dependency-and-its-tree) +- [Design Goals and Alternative Approaches](#design-goals-and-alternative-approaches) + + + +Tock's general policy is the kernel does not include external dependencies (i.e. +rust crates outside of the `tock/tock` repository) that are not part of the Rust +standard library. However, on a limited, case-by-case basis with appropriate +safeguards, external dependencies can be used in the Tock kernel. The rationale +and policy for this is described in this document. This document only applies to +the Tock kernel binary itself, not userspace or other tools or binaries within +the Tock project. + + +## External Dependency Design + +This document describes both Tock's external dependency policy and mechanism, as +well as the rationale behind the approach. + + +### Rationale + +Tock limits its use of external libraries for all crates in the kernel. This is +done to promote safety, as auditing the Tock code only requires inspecting the +code in the Tock repository. Tock tries to be very specific with its use of +`unsafe`, and tries to ensure that when it is used it is clear as to why. With +external dependencies, verifying uses of `unsafe` are valid is more challenging +to, particularly as external libraries evolve. + +External dependencies also typically themselves rely on dependencies, so +including one external crate likely pulls in several external crates. As of May +2023, cargo does not provide a robust way to audit and prohibit `unsafe` within +a dependency hierarchy. Also, the dependency chain for an external crate is +largely hidden from developers using the external crate. Lacking automated +tools, managing dependencies is a manual process, and to limit overhead Tock +generally avoids external dependencies. + + + +### Dependency Structure of Tock-Internal Crates + +Following from the above, an external dependency added to a crate which is +depended on internally within Tock (e.g. the `kernel` crate) will have a higher +impact than a dependency added to a crate with no reverse dependencies (e.g. a +board crate). Thus, this policy is increasingly liberal with crate-types that +have fewer reverse dependencies. + +This document considers Tock's crate structure by referring to the following +types of crates internal to Tock: + +- the kernel crate: `kernel/` +- arch crates: crates in the `arch/` directory +- chip crates: crates in the `chips/` directory +- board crates: crates in the `boards/` directory +- capsule crates: crates in the `capsules/` directory + +Furthermore, this policy assumes the following rules regarding crate +dependencies internal to Tock: + +- a _board crate_ is not a dependency of any other Tock-internal crate +- a _chip crate_ is only a dependency of _board crates_ or other _chip crates_ +- a _capsule crate_ is only a dependency of other _capsule crates_ or _board + crates_ +- an _arch crate_ may only depend on the _kernel crate_ and other _arch crates_ +- the _kernel crate_ does not depend on _arch_, _chip_, _board_, or _capsule + crates_ + +## External Dependency Selection + +External dependencies can be added to Tock on a case-by-case basis. Each +dependency will be reviewed for inclusion, according to the criteria in this +section. The requirements are intentionally strict. + +There are two general methods to for including an external dependency in the +Tock kernel: capsule-specific or board-specific external dependencies. + +### General Guidelines for Dependency Selection + +In general, the following guidelines can provide an indication whether an +external dependency is suitable for inclusion in Tock. + +#### Provide Important Functionality + +The external crate provides important functionality that could not easily or +realistically be provided by the Tock developers. + +Such functionality includes: + +* Cryptography libraries. Writing cryptographically secure code that is both + correct and resistant to attacks is challenging. Leveraging validated, + high-quality cryptographic libraries instead of Tock-specific cryptographic + code increases the security of the Tock kernel. + +#### Project Maturity + +The external crate being added should be a mature project, with a high quality +of code. The project must be well regarded in the Rust community. + +#### Limited Sub-dependencies + +The external crate should have a limited sub-dependency tree. The fewer +dependencies the crate introduces the more likely it is to be accepted. There is +no set threshold, instead this is evaluated on a case-by-case basis. + +### Board-Specific External Dependencies + +As board crates are generally regarded as use-case specific, managed by specific +chip and board maintainers, and audited by those maintainers, Tock is more +flexible with including external dependencies in those crates. + +Examples of when a board may want to use an external library: + +* Wireless protocols. + * Wireless implementations are difficult to get the correct timing. + * Wireless protocols are also very expensive to certify. + +Note, however, that _only_ the board crate itself may include such an external +dependency in its `Cargo.toml` file. + +A possible way to have other crates indirectly use such a dependency is through +a wrapper-trait. Such traits abstract the external dependency in a way that +allows other crates to still be built without the dependency included. While +using a wrapper-trait is not required, in certain scenarios wrapper-traits may +be useful or desirable. + +### Capsule Crate-Specific External Dependencies + +Capsules are a mechanism to provide semi-trusted infrastructure to a Tock board, +for instance non chip-specific peripheral drivers (see +[Design](https://book.tockos.org/doc/design)). As such, external dependencies +may be useful to implement complex subsystems. Examples for this are wireless or +networking protocols such as Bluetooth Low Energy or TCP. + +To support such use-cases without forcing all boards to include external +dependencies, capsules are split into multiple crates: + +- The `capsules/core` crate contains drivers and abstractions deemed essential + to most boards' operation, in addition to commonly used infrastructure and + _virtualizers_. It must not have any external dependencies. + +- The `capsules/extra` crate contains miscellaneous drivers and abstractions + which do not fit into other capsule crates. It must not have any external + dependencies. + +Capsule crates other than `core` and `extra` _may_ include external +dependencies. The granularity of such crates may range from implementing an +entire subsystem (e.g. a TCP/IP stack) to a single module providing some +isolated functionality. Whether an external dependency may be added to a given +crate, and the granularity of said crate, is evaluated on a case-by-case +basis. Concerns to take into account could be the utility, complexity and +quality of the external dependency, and whether the capsule would provide value +without this dependency. + +Newly contributed code or code from `capsules/extra` can be moved to a new +capsule crate when deemed necessary; this is evaluated on a case-by-case basis. + +## Including the Dependency + +To help ensure maintainability and to promote transparency with including +external dependencies, Tock follows a specific policy for their inclusion. + +### Including Capsule Crate-Specific External Dependencies + +Capsules other than `capsules/core` and `capsules/extra` may include external +dependencies directly in their `Cargo.toml` file and use them directly. + +### Including Board-Specific External Dependencies + +Board crates may include external dependencies directly in their `Cargo.toml` +file and use them directly. + +### Documenting the Dependency and its Tree + +Each crate that includes an external dependency in its `Cargo.toml` file must +include a section titled "External Dependencies" in its README. Each external +dependency must be listed along with its dependency tree. This documentation +must be included in the PR that adds the external dependency. + +The Tock dependency tree can be generated by running `cargo tree`. The tree +should be updated whenever a dependency change is made. + +## Design Goals and Alternative Approaches + +While exploring a policy for including external dependencies, the Tock project +considered many options. This resulted in establishing a list of goals for an +external dependency approach. These goals were converged upon over multiple +discussions of the Tock developers. + +Goals: + +- Boards which do not need or want the functionality provided by the external + dependency can ensure the dependency is not included in the kernel build. +- Boards which do not use the dependency do not have to compile the dependency. +- Boards should have discretion on which code to include in their build. +- All uses of the external dependency in the Tock code base are explicit and + obvious. +- The location within the Tock code tree for external dependencies is clear and + consistent, and there is a consistent format to document the dependency. +- There is not undue overhead or boilerplate required to add an external + dependency. + +These goals necessitate a few design decisions. For example, as crates are the +smallest unit of compilation in Rust, external dependencies must be included +through new crates added to the Tock source tree so they can be individually +included or excluded in specific builds. Also, crates provide a namespace to use +to identify when external dependencies are being incorporated. + +Additionally, we avoid using traits or HIL-like interfaces for dependencies +(i.e. core Tock capsules/modules would use a Tock-defined trait much like +capsules use HILs, and a wrapper would use the external dependency to implement +the trait) to avoid the overhead of implementing and maintaining a wrapper to +implement the trait. While architecturally this has advantages, the overhead was +deemed too burdensome for the expected benefit. + +We explicitly document the goals to help motivate the specific design in the +remainder of this document. Also, this policy may change in the future, but +these goals should be considered in any future updates. diff --git a/doc/Getting_Started.md b/doc/Getting_Started.md index 3a5b5ee02a..ca691ce6d0 100644 --- a/doc/Getting_Started.md +++ b/doc/Getting_Started.md @@ -8,66 +8,63 @@ developing Tock. -- [Requirements](#requirements) - * [Super Quick Setup](#super-quick-setup) +- [Super Quick Setup to Build the Tock Kernel](#super-quick-setup-to-build-the-tock-kernel) +- [Detailed Setup for Building the Tock Kernel](#detailed-setup-for-building-the-tock-kernel) * [Installing Requirements](#installing-requirements) + [Rust (nightly)](#rust-nightly) - + [Tockloader](#tockloader) -- [Compiling the Kernel](#compiling-the-kernel) -- [Loading the kernel onto a board](#loading-the-kernel-onto-a-board) - * [Installing `JLinkExe`](#installing-jlinkexe) - * [Installing `openocd`](#installing-openocd) - * [(Linux): Adding a `udev` rule](#linux-adding-a-udev-rule) -- [Installing your first application](#installing-your-first-application) -- [Compiling applications](#compiling-applications) + * [Compiling the Kernel](#compiling-the-kernel) +- [Hardware and Running Tock](#hardware-and-running-tock) + * [Tockloader](#tockloader) + * [Programming Adapter](#programming-adapter) + + [Installing `JLinkExe`](#installing-jlinkexe) + + [Installing `openocd`](#installing-openocd) + * [Loading the Kernel onto a Board](#loading-the-kernel-onto-a-board) +- [Installing Applications](#installing-applications) + * [Compiling Your Own Applications](#compiling-your-own-applications) - [Developing TockOS](#developing-tockos) * [Formatting Rust source code](#formatting-rust-source-code) * [Keeping build tools up to date](#keeping-build-tools-up-to-date) -## Requirements +## Super Quick Setup to Build the Tock Kernel -1. [Rust](http://www.rust-lang.org/) -2. [rustup](https://rustup.rs/) to install Rust (version >= 1.11.0) -3. Command line utilities: make -4. A supported board or QEMU configuration. - - If you are just starting to work with TockOS, you should look in - the [`boards/` subdirectory](../boards/README.md) and choose one of - the options with `tockloader` support to load applications, as that - is the configuration that most examples and tutorials assume. - - **Note:** QEMU support in Tock is in the early stages. Please be - sure to check whether and how QEMU is supported for a board based on - the table in the [`boards/` subdirectory](../boards/README.md). - The `make ci-job-qemu` target is the authority on QEMU support. - - * Info about testing Tock on QEMU - * 01/08/2020 : Among the boards supported by Tock, [SiFive HiFive1 RISC-V Board](../boards/hifive1/#running-in-qemu) can be tested in QEMU. - -### Super Quick Setup - -Nix: -``` -$ nix-shell -``` +If you just want to get started quickly, follow these steps for your +environment: MacOS: ``` -$ curl https://sh.rustup.rs -sSf | sh -$ pip3 install --upgrade tockloader +$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +$ pipx install tockloader +$ pipx ensurepath ``` Ubuntu: ``` -$ curl https://sh.rustup.rs -sSf | sh -$ pip3 install --upgrade tockloader --user +$ sudo apt install -y build-essential python3-pip curl +$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +$ pipx install tockloader +$ pipx ensurepath $ grep -q dialout <(groups $(whoami)) || sudo usermod -a -G dialout $(whoami) # Note, will need to reboot if prompted for password ``` +Nix: +``` +$ nix-shell +``` + Then build the kernel by running `make` in the `boards/` directory. + +## Detailed Setup for Building the Tock Kernel + +To build the Tock kernel, you will need: + +1. [Rust](http://www.rust-lang.org/) +2. [rustup](https://rustup.rs/) (version >= 1.23.0) to install Rust +3. Host toolchain (gcc, glibc) +4. Command line utilities: make, find + ### Installing Requirements These steps go into a little more depth. Note that the build system is capable @@ -75,12 +72,12 @@ of installing some of these tools, but you can also install them yourself. #### Rust (nightly) -We are using `nightly-2021-12-04`. We require +We are using `nightly-2024-07-08`. We require installing it with [rustup](http://www.rustup.rs) so you can manage multiple versions of Rust and continue using stable versions for other Rust code: ```bash -$ curl https://sh.rustup.rs -sSf | sh +$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` This will install `rustup` in your home directory, so you will need to @@ -90,179 +87,146 @@ to your `$PATH`. Then install the correct nightly version of Rust: ```bash -$ rustup install nightly-2021-12-04 +$ rustup install nightly-2024-07-08 ``` -#### Tockloader - -`tockloader` programs the kernel and applications onto boards, and also has -features that are generally useful for all Tock boards, such as easy-to-manage -serial connections, along with the ability to list, add, replace, and remove -applications over JTAG (or USB if a bootloader is installed). - -1. [tockloader](https://github.com/tock/tockloader) (version >= 1.0) - -Tockloader is a Python application and can be installed with the Python -package manager (pip). - -```bash -(Linux): pip3 install --upgrade tockloader --user -(MacOS): pip3 install --upgrade tockloader -``` - -## Compiling the Kernel +### Compiling the Kernel Tock builds a unique kernel for every _board_ it supports. Boards include -details like pulling together the correct chips and pin assignments. To -build a kernel, first choose a board, then navigate to that board directory. -e.g. `cd boards/nordic/nrf52840dk ; make`. +details like pulling together the correct chips and pin assignments. To build a +kernel, first choose a board, then navigate to that board directory. e.g. `cd +boards/nordic/nrf52840dk ; make`. Some boards have special build options that can only be used within the board's -directory. All boards share a few common targets: +directory. All boards share a few common targets: - - `all` (default): Compile Tock for this board. - - `debug`: Generate build(s) for debugging support, details vary per board. - - `doc`: Build documentation for this board. - - `clean`: Remove built artifacts for this board. - - `flash`: Load code using JTAG, if available. - - `program`: Load code using a bootloader, if available. +- `all` (default): Compile Tock for this board. +- `debug`: Generate build(s) for debugging support, details vary per board. +- `doc`: Build documentation for this board. +- `clean`: Remove built artifacts for this board. +- `install`: Load the kernel onto the board. -The [board-specific READMEs](../boards/README.md) in each board's -subdirectory provide more details for each platform. +The [board-specific READMEs](../boards/README.md) in each board's subdirectory +provide more details for each platform. -## Loading the kernel onto a board -The process to load the kernel onto the board depends on the board. You should -be able to program the kernel by running: +## Hardware and Running Tock - $ make install +To run the Tock kernel you need: -For some boards, you will need a programming adapter to flash code. Depending on -the board and which adapter it supports, you will need either the free `openocd` -or Segger's proprietary `JLinkExe`. Programming adapters are available as -standalone devices (for example the [JLink EDU JTAG -debugger](https://www.segger.com/j-link-edu.html) available on -[Digikey](https://www.digikey.com/product-detail/en/segger-microcontroller-systems/8.08.90-J-LINK-EDU/899-1008-ND/2263130)), -but most development boards come with an onboard programming and debugging -adapter. In that case, the board you use determines which software you will need -and the `Makefile` in the board directory will know which one to call. Again, -the [board-specific READMEs](../boards/README.md) provide the required details. +1. A supported board or QEMU configuration +2. Tockloader +3. A programming adapter for loading code (required for most boards) -### Installing `JLinkExe` +If you are just starting to work with TockOS, you should look in the [`boards/` +subdirectory](../boards/README.md) and choose one of the options with +`tockloader` support to load applications, as that is the configuration that +most examples and tutorials assume. -`JLink` is available [from the Segger -website](https://www.segger.com/downloads/jlink). You want to install -the "J-Link Software and Documentation Pack". There are various -packages available depending on operating system. We require a version -greater than or equal to `5.0`. +If you do not have a supported hardware board, Tock has some limited support for +running the kernel in [QEMU](https://www.qemu.org/). As of 01/08/2020, the +[SiFive HiFive1 RISC-V Board](../boards/hifive1/#running-in-qemu) can be tested +in QEMU. -### Installing `openocd` +> **Note:** QEMU support in Tock is in the early stages. Please be sure to check +> whether and how QEMU is supported for a board based on the table in the +> [`boards/` subdirectory](../boards/README.md). The `make ci-job-qemu` target +> is the authority on QEMU support. -`Openocd` works with various programming and debugging adapters. For -most purposes, available distribution packages are sufficient and it can -be installed with: +### Tockloader + +[tockloader](https://github.com/tock/tockloader) programs the kernel and +applications onto boards, and also has features that are generally useful for +all Tock boards, such as easy-to-manage serial connections, along with the +ability to list, add, replace, and remove applications over JTAG (or USB if a +bootloader is installed). + +Tockloader is a Python application and can be installed with the Python package +manager for executables (pipx): ```bash -(Linux/Debian): sudo apt-get install openocd -(MacOS): brew install open-ocd +$ pipx install tockloader +$ pipx ensurepath ``` -We require at least version `0.8.0` to support the SAM4L on `imix` if -you choose to flash it using an adapter instead of the bootloader. -Some boards (at the time of writing the HiFive1 RISC-V board) may -require newer or unreleased versions, in that case you should follow -the installation instructions on the [`openocd` -website](http://openocd.org/getting-openocd/). +### Programming Adapter -### (Linux): Adding a `udev` rule +For some boards, you will need a programming adapter to flash code. Check the +"Interface" column in the [boards README](../boards/README.md) for the board you +have to see what the default programming adapter you need is. There are +generally four options: -Depending on which programming adapter you use, you may want to add a -`udev` rule in `/etc/udev/rules.d` that allows you to interact with -the board as a user instead of as root. If you install the `deb` -packet of the `JLink` software it will automatically install a -`/etc/udev/rules.d/99-jlink.rules` that allows everyone to access the -adapter. If you use something else, like for example the onboard -programmer of a ST Nucleo board, you could install something like this as -`/etc/udev/rules.d/99-stlinkv2-1.rules`: - -``` -# stm32 nucleo boards, with onboard st/linkv2-1 -# ie, STM32F0, STM32F4. -# STM32VL has st/linkv1, which is quite different +1. `Bootloader`: This means the board supports the Tock bootloader and you do + not need any special programming adapter. Tockloader has built-in support. +2. `jLink`: This is a proprietary tool for loading code onto microcontrollers + from Segger. You will need to install this if you do not already have it. See + the instructions below. +3. `openocd`: This is a free programming adapter which you will need to install + if you do not already have it. See the instructions below. +4. `custom`: The board uses some other programming adapter, likely a + microcontroller-specific tool. See the board's README for how to get started. -SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="374b", \ - MODE:="0660", GROUP="dialout", \ - SYMLINK+="stlinkv2-1_%n" -``` +#### Installing `JLinkExe` -## Installing your first application +`JLink` is available [from the Segger +website](https://www.segger.com/downloads/jlink). You want to install the +"J-Link Software and Documentation Pack". There are various packages available +depending on operating system. We require a version greater than or equal to +`5.0`. -A kernel alone isn't much use, as an embedded developer you want to -see some LEDs blink. Fortunately, there is an example `blink` app -available from the TockOS app repository which `tockloader` can -download and install (if you are using a board that is supported -by `tockloader`). +#### Installing `openocd` -For certain boards (e.g. Hail and imix), `tockloader` can read -attributes from the board to configure how it communicates with the -board. For many boards, however, `tockloader` cannot know -which board and communication method you want to use, so you have to -tell it explicitly. For example: +`Openocd` works with various programming and debugging adapters. For most +purposes, available distribution packages are sufficient and it can be installed +with: ```bash -$ tockloader install --board nrf52dk --jlink blink -Could not find TAB named "blink" locally. - -[0] No -[1] Yes - -Would you like to check the online TAB repository for that app?[0] 1 -Installing apps on the board... -Using known arch and jtag-device for known board nrf52dk -Finished in 2.567 seconds +(Ubuntu): sudo apt-get install openocd +(MacOS): brew install open-ocd ``` -Boards that use `openocd` will of course require the parameter -`--openocd` instead of `--jlink`. If your board has a serial -bootloader, `tockloader` should work without any additional arguments: +We require at least version `0.10.0`. - $ tockloader install blink +### Loading the Kernel onto a Board -However, you can specify the board type manually as well: +The process to load the kernel onto the board depends on the board. You should +be able to program the kernel by changing to the correct board directory in +`tock/boards/` and running: - $ tockloader install --board imix blink +``` +$ make install +``` + +## Installing Applications -You can also tell it which serial port to use (which is useful if you -have multiple boards plugged in) by passing it the `--port` parameter -like `--port /dev/ttyACM0` to use `/dev/ttyACM0`: +A kernel alone isn't much use, as an embedded developer you want to see some +LEDs blink. Fortunately, there is an example `blink` app available from the +TockOS app repository which `tockloader` can download and install. - $ tockloader --port /dev/ttyACM0 install blink +To install blink, run: -To see the list of boards `tockloader` knows about you can run: +``` +$ tockloader install blink +``` - $ tockloader list-known-boards +Tockloader will automatically detect your board, download the app from the Tock +website, and flash it for you. -If everything has worked until here, the LEDs on your board should now -display a binary counter. Congratulations, you have a working TockOS -installation on your board! +If everything went well, the LEDs on your board should now display a binary +counter. Congratulations, you have a working TockOS installation on your board! -## Compiling applications +### Compiling Your Own Applications -The last remaining step is to compile applications locally. -All user-level code lives in two separate repositories: +You can also compile applications locally. All user-level code lives in two +separate repositories: - [libtock-c](https://github.com/tock/libtock-c): C and C++ apps. - [libtock-rs](https://github.com/tock/libtock-rs): Rust apps. -The C version of the Tock library and the example applications is older and more -stable, so it is a good idea to look at these first. So look at the [libtock-c -README](https://github.com/tock/libtock-c/blob/master/README.md) and follow the -steps therein. Then you can do the same for the [libtock-rs -README](https://github.com/tock/libtock-rs/blob/master/README.md). This should -give you a first impression of how to build and deploy applications for TockOS. +You can use either version by following the steps in their respective READMEs: +[libtock-c README](https://github.com/tock/libtock-c/blob/master/README.md) and +[libtock-rs README](https://github.com/tock/libtock-rs/blob/master/README.md). -For an introduction on how applications work in TockOS, have a look at -the ["Userland" document](Userland.md) in this directory. ## Developing TockOS @@ -280,5 +244,5 @@ from the root of the repository to format all rust code in the repository. Occasionally, Tock updates to a new nightly version of Rust. The build system automatically checks whether the versions of `rustc` and `rustup` are correct for the build requirements, and updates them when necessary. After the -installation of the initial four requirements, you shouldn't have to worry -about keeping them up to date. +installation of the initial four requirements, you shouldn't have to worry about +keeping them up to date. diff --git a/doc/Lifetimes.md b/doc/Lifetimes.md deleted file mode 100644 index c803395cf3..0000000000 --- a/doc/Lifetimes.md +++ /dev/null @@ -1,91 +0,0 @@ -# Lifetimes - -Values in the Tock kernel can be allocated in three ways: - - 1. **Static allocation**. Statically allocated values are never deallocated. - These values are represented as Rust "borrows" with a `'static` lifetime. - - 2. **Stack allocation**. Stack allocated values have a lexically bound - lifetime. That is, we know by looking at the source code when they will be - deallocated. When you create a reference to such a value, the Rust type - system ensures that reference is never used after the value is deallocated - by assigning a "lifetime" to the reference. - - 3. **Grant values**. Values allocated from a process's grant region have a - runtime-dependent lifetime. For example, when they are deallocated depends - on whether the processes crashes. Since we can't represent - runtime-dependent lifetimes in Rust's type-system, references to grant - values in Tock are done through the `Grant` type, which is owned by its - referrer. - -Next we'll discuss how Rust's notion of lifetimes maps to the lifetimes of -values in Tock and how this affects the use of different types of values in the -kernel. - - - - - -- [Rust lifetimes](#rust-lifetimes) -- [Buffer management](#buffer-management) -- [Circular dependencies](#circular-dependencies) - - - -## Rust lifetimes - -Each reference (called a _borrow_) in Rust has _lifetime_ associated with its -type that determines in what scope it is valid. The lifetime of a reference -must be more constrained than the value it was borrowed from. The compiler, in -turn, ensures that references cannot escape their valid scope. - -As a result, data structures that store a reference must declare the minimal -lifetime of that reference. For example: - -```rust -struct Foo<'a> { - bar: &'a Bar -} -``` - -defines a data structure `Foo` that contains a reference to another type, -`Bar`. The reference has a lifetime `'a`, which is a type parameter of `Foo`. -Note that `'a` is an arbitrary choice of name for the lifetime, such as `E` in -a generic `List`. It is also possible to use the explicit lifetime -`'static` rather than a type parameter when the reference should always live -forever, regardless of how long the containing type (e.g. `Foo`) lives: - -```rust -struct Foo { - bar: &'static Bar -} -``` - -## Buffer management - -Buffers used in asynchronous hardware operations must be static. On the one -hand, we need to guarantee (to the hardware) that the buffer will not be -deallocated before the hardware relinquishes its pointer. On the other hand, -the hardware has no way of telling us (i.e. the Rust compiler) that it will -only access the buffer within a certain lexical bound (because we are using the -hardware asynchronously). To resolve this, buffers passed to hardware should be -allocated statically. - -## Circular dependencies - -Tock uses circular dependencies to give capsules access to each other. -Specifically, two capsules that depend on each other will each have a field -containing a reference to the other. For example, a client of the timer `Alarm` trait -needs a reference to an instance of the timer in order to start/stop it, while -the instance of timer needs a reference to the client in order to propagate -events. This is handled by the `set_client` function, which allows the platform -definition to connect objects after creation. - -```rust -impl Foo<'a> { - fn set_client(&self, client: &'a Client) { - self.client.set(client); - } -} -``` - diff --git a/doc/Maintenance.md b/doc/Maintenance.md index d594d9b5d8..3d7e92efa1 100644 --- a/doc/Maintenance.md +++ b/doc/Maintenance.md @@ -1,7 +1,7 @@ # Tock Maintenance This document describes some elements of how the Tock [core working -group](../wg/core/README.md) maintains the Tock project. +group](wg/core/README.md) maintains the Tock project. diff --git a/doc/Memory_Isolation.md b/doc/Memory_Isolation.md deleted file mode 100644 index 4fdb908d94..0000000000 --- a/doc/Memory_Isolation.md +++ /dev/null @@ -1,110 +0,0 @@ -Memory Isolation -============= - -This document describes how memory is isolated in Tock, in terms of access -permissions of the kernel and processes. Before reading this, make sure you -have a good understanding of the [design of Tock](Design.md) and the [Tock -memory layout](Memory_Layout.md). - - - - - -- [Process Isolation](#process-isolation) - * [Flash](#flash) - * [RAM](#ram) - - - -Memory isolation is a key property of Tock. Without it, processes could just -access any part of memory, and the security of the entire system would be -compromised. The reason for this is that although Rust preserves memory safety (e.g. no double frees or -buffer overflows) and type safety at compile-time, this doesn't prevent -processes, which can be written in any language, from accessing certain -addresses which they should not have access to in memory. Some other component -is necessary to prevent this from happening, or systems can not safely support -untrusted processes. - -To support untrusted applications, Tock uses the memory protection units (MPU) -provided by many embedded microcontrollers. The MPU is a hardware component -which can configure access permissions for certain memory regions. Three -fundamental access types can be set for these memory regions: read (R), write -(W) and execute (X). Full access implies all three access types are allowed in a -certain memory region. - -Since processes are by default not allowed to access each others' memory, the MPU -has to be configured for each process based on where that process is in flash -and what memory it has allocated to it. The MPU configuration is hence -different for each process. Therefore, with each context switch to a userland -process, Tock reconfigures the MPU for that process. - -When the system is executing kernel code, the MPU is disabled. This means there -are no hardware restrictions preventing the kernel from accessing the entire -address space. Instead, what the kernel can do is restricted by the -Rust type system. For example, a capsule (which cannot use `unsafe`) cannot access -a process's memory because it cannot create and dereference an arbitrary -pointer. In general, Tock tries to minimize the amount of trusted code (i.e. -code that can call `unsafe`), and tries to encapsulate code that does need -`unsafe` to make it clear what that code does and how to use it in a manner that -does not violate overall system safety. - - -## Process Isolation - -From an architecture perspective, processes are considered to be arbitrary code -that may be buggy or even malicious. Therefore, Tock takes care to ensure that -misbehaving applications do not compromise the integrity of the overall system. - -### Flash - -Flash is the nonvolatile memory space on a microcontroller. Generally, processes -cannot access arbitrary addresses in flash, and are certainly prohibited from -accessing bootloader or kernel code. They are also prohibited from reading or -writing the nonvolatile regions of other processes. - -Processes do have access to their own memory in flash. Certain regions, -including their Tock Binary Format (TBF) header and a protected region after the -header, are read-only, as the kernel must be able to ensure the integrity of the -header. In particular, the kernel needs to know the total size of the app to find -the next app in flash. The kernel may also wish to store nonvolatile information -about the app (e.g. how many times it has entered a failure state) that the app -should not be able to alter. - -The remainder of the app, and in particular the actual code of the app, is -considered to be owned by the app. The app can read the flash to execute its own -code. If the MCU uses flash for its nonvolatile memory the app can not likely -directly modify its own flash region, as flash typically requires some hardware -peripheral interaction to erase or write flash. In this case, the app would -require kernel support to modify its flash region. - - -### RAM - -Process RAM is memory space divided between all running apps. The figure below -shows the memory space of a process. - -![Process' RAM](processram.png) - -A process has full access to a portion of its own RAM region. A segment of the -RAM region, called the grant region, is reserved for use only by the kernel on -behalf of the process. Because it contains kernel data structures, processes -cannot read or write the grant region. - -The remainder of the process's memory region can be used as the process sees -fit, likely for a stack, heap, and data section. The process entirely controls -how these are used. There are `mem` syscalls that the process can use to inform -the kernel of where it has placed its stack and heap, but these are entirely -used for debugging. The kernel does not need to know how the process has -organized its memory for normal operation. - -Processes can choose to explicitly share portions of their RAM with the kernel -through the use of `allow` syscalls. This gives capsules read/write access to -the process's memory for use with a specific capsule operation. - -Processes can communicate with each other through an [inter-process -communication (IPC) mechanism](https://book.tockos.org/tutorials/05_ipc.html). -To use IPC, processes specify a buffer in their RAM to use as a shared buffer, -and then notify the kernel that they would like to share this buffer with other -processes. Then, other users of this IPC mechanism are allowed to read and write -this buffer. Outside of IPC, a process is never able to read or write other -processes' RAM. diff --git a/doc/Memory_Layout.md b/doc/Memory_Layout.md deleted file mode 100644 index f0dccea2be..0000000000 --- a/doc/Memory_Layout.md +++ /dev/null @@ -1,114 +0,0 @@ -Memory Layout -============= - -This document describes how the memory in Tock is structured and used for the -kernel, applications, and supporting state. - - - - - -- [Flash](#flash) - * [Kernel code](#kernel-code) - * [Process code](#process-code) -- [RAM](#ram) - * [Kernel RAM](#kernel-ram) - * [Process RAM](#process-ram) -- [Hardware Implementations](#hardware-implementations) - * [SAM4L](#sam4l) - + [Flash](#flash-1) - + [RAM](#ram-1) - + [Overview](#overview) - - - -Tock is intended to run on microcontrollers like the Cortex-M, which have -non-volatile flash memory (for code) and RAM (for stack and data) in a single -address space. While the Cortex-M architecture specifies a high-level layout of -the address space, the exact layout of Tock can differ from board to board. -Most boards simply define the beginning and end of flash and SRAM in their -`layout.ld` file and then include the [generic Tock memory -map](../boards/kernel_layout.ld). - - -## Flash - -The nonvolatile flash memory holds the kernel code and a linked-list of sorts -of [process code](TockBinaryFormat.md). - -### Kernel code - -The kernel code is split into two major regions. The first is `.text`, which -holds the vector table, program code, initialization routines, and other -read-only data. This section is written to the beginning of flash. - -The second major region following up the `.text` region is the `.relocate` -region. It holds values that need to exist in SRAM, but have non-zero initial -values that Tock copies from flash to SRAM as part of its initialization (see -[Startup](Startup.md)). - -### Process code - -Processes are placed in flash starting at a known address which can be -retrieved in the kernel using the symbol `_sapps`. Each process starts with a -Tock Binary Format (TBF) header and then the actual application binary. -Processes are placed continuously in flash, and each process's TBF header -includes the entire size of the process in flash. This creates a linked-list -structure that the kernel uses to traverse apps. The end of the valid processes -are denoted by an invalid TBF header. Typically the flash page after the last -valid process is set to all 0x00 or 0xFF. - -## RAM - -The RAM holds the data currently being used by both the kernel and processes. - -### Kernel RAM -The kernel RAM contains three major regions: - -1. Kernel stack. -2. Kernel data: initialized memory, copied from flash at boot. -3. Kernel BSS: uninitialized memory, zeroed at boot. - -### Process RAM -The process RAM is memory space divided between all running apps. - -A process's RAM contains four major regions: - -1. Process stack -2. Process data -3. Process heap -4. Grant - -The figure below shows the memory space of one process. - -![Process' RAM](processram.png) - -## Hardware Implementations - -### SAM4L - -The SAM4L is a microcontroller used on the Hail and Imix platforms, among -others. The structure of its flash and RAM is as follows. - -#### Flash - -| Address Range | Length (bytes) | Content | Description | -|-----------------|----------------|------------|--------------------------------------------------------------------------------------------------| -| 0x0-3FF | 1024 | Bootloader | Reserved flash for the bootloader. Likely the vector table. | -| 0x400-0x5FF | 512 | Flags | Reserved space for flags. If the bootloader is present, the first 14 bytes are "TOCKBOOTLOADER". | -| 0x600-0x9FF | 1024 | Attributes | Up to 16 key-value pairs of attributes that describe the board and the software running on it. | -| 0xA00-0xFFFF | 61.5k | Bootloader | The software bootloader provides non-JTAG methods of programming the kernel and applications. | -| 0x10000-0x2FFFF | 128k | Kernel | Flash space for the kernel. | -| 0x30000-0x7FFFF | 320k | Apps | Flash space for applications. | - -#### RAM - -| Address Range | Length (bytes) | Content | Description | -|-----------------------|----------------|--------------------|---------------------------------------------------------------------------------------------------| -| 0x20000000-0x2000FFFF | 64k | Kernel and app RAM | The kernel links with all of the RAM, and then allocates a buffer internally for application use. | - -#### Overview - -The following image gives an example of how things are currently laid out in practice. It shows the address space of both flash and RAM with three running applications: crc, ip_sense, and analog_comparator. - -![Process memory layout](process_memory_layout.png) diff --git a/doc/Mutable_References.md b/doc/Mutable_References.md deleted file mode 100644 index b652d0737a..0000000000 --- a/doc/Mutable_References.md +++ /dev/null @@ -1,374 +0,0 @@ -# Mutable References in Tock - Memory Containers (Cells) - -Borrows are a critical part of the Rust language that help provide its -safety guarantees. However, when there is no dynamic memory allocation -(no heap), event-driven code runs into challenges with Rust's borrow -semantics. Often multiple structs need to -be able to call (share) a struct based on what events occur. For example, -a struct representing a radio interface needs to handle callbacks -both from the bus it uses as well as handle calls from higher layers of -a networking stack. Both of these callers need to be able to change the -state of the radio struct, but Rust's borrow checker does not allow them -to both have mutable references to the struct. - -To solve this problem, Tock builds on the observation that having two -references to a struct that can modify it is safe, as long as no references -to memory inside the struct are leaked (there is no interior mutability). -Tock uses *memory containers*, a set of types that allow mutability -but not interior mutability, to achieve this goal. The Rust standard -library has two memory container types, `Cell` and `RefCell`. Tock uses -`Cell` extensively, but also adds five new memory container types, each -of which is tailored to a specific use common in kernel code. - - - - - -- [Brief Overview of Borrowing in Rust](#brief-overview-of-borrowing-in-rust) -- [Issues with Borrowing in Event-Driven code](#issues-with-borrowing-in-event-driven-code) -- [`Cell`s in Tock](#cells-in-tock) -- [The `TakeCell` abstraction](#the-takecell-abstraction) - * [Example use of `take` and `replace`](#example-use-of-take-and-replace) - * [Example use of `map`](#example-use-of-map) - + [`map` variants](#map-variants) -- [`MapCell`](#mapcell) -- [`OptionalCell`](#optionalcell) - * [Comparison to `TakeCell`](#comparison-to-takecell) -- [`VolatileCell`](#volatilecell) -- [Cell Extensions](#cell-extensions) - * [`NumericCellExt`](#numericcellext) - - - -## Brief Overview of Borrowing in Rust - -Ownership and Borrowing are two design features in Rust which -prevent race conditions and make it impossible to write code that produces -dangling pointers. - -Borrowing is the Rust mechanism to allow references to -memory. Similar to references in C++ and other languages, borrows make -it possible to efficiently pass large structures by passing pointers -rather than copying the entire structure. The Rust compiler, however, -limits borrows so that they cannot create race conditions, which are -caused by concurrent writes or concurrent reads and writes to -memory. Rust limits code to either a single mutable (writeable) -reference or any number of read-only references. - -If a piece of code has a mutable reference to a piece of memory, it's -also important that other code does not have any references within -that memory. Otherwise, the language is not safe. For example, consider -this case of an `enum` which can be either a pointer or a value: - -```rust -enum NumOrPointer { - Num(u32), - Pointer(&'static mut u32) -} -``` - -A Rust `enum` is like a type-safe C union. Suppose that code has both -a mutable reference to a `NumOrPointer` and a read-only reference to -the encapsulated `Pointer`. If the code with the `NumOrPointer` -reference changes it to be a `Num`, it can then set the `Num` to be -any value. However, the reference to `Pointer` can still access the -memory as a pointer. As these two representations use the same memory, -this means that the reference to `Num` can create any pointer it -wants, breaking Rust's type safety: - -```rust -// n.b. illegal example -let external : &mut NumOrPointer; -match external { - &mut Pointer(ref mut internal) => { - // This would violate safety and - // write to memory at 0xdeadbeef - *external = Num(0xdeadbeef); - *internal = 12345; - }, - ... -} -``` - -As the Tock kernel is single -threaded, it doesn't have race conditions and so in some cases it may -be safe for there to be multiple references, as long as they do not -point inside each other (as in the number/pointer example). But Rust -doesn't know this, so its rules still hold. In practice, Rust's rules -cause problems in event-driven code. - -## Issues with Borrowing in Event-Driven code - -Event-driven code often requires multiple writeable references to -the same object. Consider, for example, an event-driven embedded -application that periodically samples a sensor and receives commands -over a serial port. At any given time, this application can have two -or three event callbacks registered: a timer, sensor data acquisition, -and receiving a command. Each callback is registered with a different -component in the kernel, and each of these components requires a -reference to the object to issue a callback on. That is, the generator -of each callback requires its own writeable reference to the -application. Rust's rules, however, do not allow multiple mutable -references. - -## `Cell`s in Tock - -Tock uses several [Cell](https://doc.rust-lang.org/core/cell/) types for -different data types. This -table summarizes the various types, and more detail is included below. - -| Cell Type | Best Used For | Example | Common Uses | -|----------------|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------| -| `Cell` | Primitive types | `Cell`, [`sched/mod.rs`](../kernel/src/kernel.rs) | State variables (holding an `enum`), true/false flags, integer parameters like length. | -| `TakeCell` | Small static buffers | `TakeCell<'static, [u8]>`, [`spi.rs`](../capsules/src/spi_peripheral.rs) | Holding static buffers that will receive or send data. | -| `MapCell` | Large static buffers | `MapCell`, [`spi.rs`](../capsules/src/spi_controller.rs) | Delegating reference to large buffers (e.g. application buffers). | -| `OptionalCell` | Optional parameters | `client: OptionalCell<&'static hil::nonvolatile_storage::NonvolatileStorageClient>`, [`nonvolatile_to_pages.rs`](../capsules/src/nonvolatile_to_pages.rs) | Keeping state that can be uninitialized, like a Client before one is set. | -| `VolatileCell` | Registers | `VolatileCell` | Accessing MMIO registers, used by `tock_registers` crate. | - -## The `TakeCell` abstraction - -While the different memory containers each have specialized uses, most of their -operations are common across the different types. We therefore explain the basic -use of memory containers in the context of TakeCell, and the additional/specialized -functionality of each other type in its own section. -From `tock/libraries/tock-cells/src/take_cell.rs`: - -> A `TakeCell` is a potential reference to mutable memory. Borrow rules are -> enforced by forcing clients to either move the memory out of the cell or -> operate on a borrow within a closure. - -A TakeCell can be full or empty: it is like a safe pointer that can be -null. If code wants to operate on the data contained in the TakeCell, -it must either move the data out of the TakeCell (making it empty), or -it must do so within a closure with a `map` call. Using `map` passes a -block of code for the TakeCell to execute. Using a closure allows -code to modify the contents of the TakeCell inline, without any danger -of a control path accidentally not replacing the value. However, -because it is a closure, a reference to the contents of the TakeCell -cannot escape. - -TakeCell allows code to modify its contents when it has a normal -(non-mutable) reference. This in turn means that if a structure -stores its state in TakeCells, then code which has a regular -(non-mutable) reference to the structure can change the contents -of the TakeCell and therefore modify the structure. Therefore, -it is possible for multiple callbacks to have references to -the structure and modify its state. - -### Example use of `take` and `replace` - -When `TakeCell.take()` is called, ownership of a location in memory -moves out of the cell. It can then be freely used by whoever took it -(as they own it) and then put back with `TakeCell.put()` or -`TakeCell.replace()`. - -For example, this piece of code from `chips/nrf51/src/clock.rs` -sets the callback client for a hardware clock: - -```rust -pub fn set_client(&self, client: &'static ClockClient) { - self.client.replace(client); -} -``` - -If there is a current client, it's replaced with `client`. If -`self.client` is empty, then it's filled with `client`. - -This piece of code from `chips/sam4l/src/dma.rs` cancels a -current direct memory access (DMA) operation, removing the -buffer in the current transaction from the TakeCell with a -call to `take`: - -```rust -pub fn abort_transfer(&self) -> Option<&'static mut [u8]> { - self.registers - .idr - .write(Interrupt::TERR::SET + Interrupt::TRC::SET + Interrupt::RCZ::SET); - - // Reset counter - self.registers.tcr.write(TransferCounter::TCV.val(0)); - - self.buffer.take() -} -``` - - -### Example use of `map` - -Although the contents of a TakeCell can be directly accessed through -a combination of `take` and `replace`, Tock code typically uses -`TakeCell.map()`, which wraps the provided closure between a -`TakeCell.take()` and `TakeCell.replace()`. This approach has the -advantage that a bug in control flow that doesn't correctly `replace` -won't accidentally leave the TakeCell empty. - -Here is a simple use of `map`, taken from `chips/sam4l/src/dma.rs`: - -```rust -pub fn disable(&self) { - let registers: &SpiRegisters = unsafe { &*self.registers }; - - self.dma_read.map(|read| read.disable()); - self.dma_write.map(|write| write.disable()); - registers.cr.set(0b10); -} -``` - -Both `dma_read` and `dma_write` are of type `TakeCell<&'static mut DMAChannel>`, -that is, a TakeCell for a mutable reference to a DMA channel. By calling `map`, -the function can access the reference and call the `disable` function. If -the TakeCell has no reference (it is empty), then `map` does nothing. - -Here is a more complex example use of `map`, taken from `chips/sam4l/src/spi.rs`: - -```rust -self.client.map(|cb| { - txbuf.map(|txbuf| { - cb.read_write_done(txbuf, rxbuf, len); - }); -}); -``` - -In this example, `client` is a `TakeCell<&'static SpiMasterClient>`. -The closure passed to `map` has a single argument, the value which the -TakeCell contains. So in this case, `cb` is the reference to an -`SpiMasterClient`. Note that the closure passed to `client.map` then -itself contains a closure, which uses `cb` to invoke a callback passing -`txbuf`. - - -#### `map` variants - -`TakeCell.map()` provides a convenient method for interacting with a -`TakeCell`'s stored contents, but it also hides the case when the `TakeCell` is -empty by simply not executing the closure. To allow for handling the cases when -the `TakeCell` is empty, rust (and by extension Tock) provides additional -functions. - -The first is `.map_or()`. This is useful for returning a value both when the -`TakeCell` is empty and when it has a contained value. For example, rather than: - -```rust -let return = if txbuf.is_some() { - txbuf.map(|txbuf| { - write_done(txbuf); - }); - Ok(()) -} else { - Err(ErrorCode::RESERVE) -}; -``` - -`.map_or()` allows us to do this instead: - -```rust -let return = txbuf.map_or(Err(ErrorCode::RESERVE), |txbuf| { - write_done(txbuf); - Ok(()) -}); -``` - -If the `TakeCell` is empty, the first argument (the error code) is returned, -otherwise the closure is executed and `Ok(())` is returned. - -Sometimes we may want to execute different code based on whether the `TakeCell` -is empty or not. Again, we could do this: - -```rust -if txbuf.is_some() { - txbuf.map(|txbuf| { - write_done(txbuf); - }); -} else { - write_done_failure(); -}; -``` - -Instead, however, we can use the `.map_or_else()` function. This allows us to -pass in two closures, one for if the `TakeCell` is empty, and one for if it has -contents: - -```rust -txbuf.map_or_else(|| { - write_done_failure(); -}, |txbuf| { - write_done(txbuf); -}); -``` - -Note, in both the `.map_or()` and `.map_or_else()` cases, the first argument -corresponds to when the `TakeCell` is empty. - - -## `MapCell` - -A `MapCell` is very similar to a `TakeCell` in its purpose and interface. -What differs is the underlying implementation. In a `TakeCell`, when -something `take()`s the contents of the cell, the memory inside is actually -moved. This is a performance problem if the data in a `TakeCell` is -large, but saves both cycles and memory if the data is small (like a -pointer or slice) because the internal `Option` can be optimized in many cases -and the code operates on registers as opposed to memory. On the flip side, -`MapCell`s introduce some accounting overhead for small types and require a -minimum number of cycles to access. - -The [commit that introduced `MapCell`][mapcell] includes some performance -benchmarks, but exact performance will vary based on the usage scenario. -Generally speaking, medium to large sized buffers should prefer `MapCell`s. - -[mapcell]: https://github.com/tock/tock/commit/5f7246d4af139864f567cebf15bfc0b49e17b787) - - -## `OptionalCell` - -[`OptionalCell`](https://github.com/tock/tock/blob/master/libraries/tock-cells/src/optional_cell.rs) -is effectively a wrapper for a `Cell` that contains an `Option`, like: -`Cell>`. This to an extent mirrors the `TakeCell` interface, where the -`Option` is hidden from the user. So instead of `my_optional_cell.get().map(|| -{})`, the code can be: `my_optional_cell.map(|| {})`. - -`OptionalCell` can hold the same values that `Cell` can, but can also be just -`None` if the value is effectively unset. Using an `OptionalCell` (like a -`NumCell`) makes the code clearer and hides extra tedious function calls. - -### Comparison to `TakeCell` - -`TakeCell` and `OptionalCell` are quite similar, but the key differentiator is -the `Copy` bound required for items to use some of the methods defined on `OptionalCell`, such as `map()`. -The `Copy` bound enables safe "reentrant" access to the stored value, because multiple accesses will be operating -on different copies of the same stored item. The semantic difference -is the name: a `TakeCell` is designed for something that must literally be -taken, e.g. commonly a buffer that is given to a different subsystem in a way -not easily captured by the Rust borrow mechansims (commonly when a buffer is -passed into, borrowed, "by" a hardware peripheral, and returned when hardware -event has filled the buffer). [#2360](https://github.com/tock/tock/pull/2360) -has some examples where trying to convert a `TakeCell` into an `OptionalCell` -does not work. - -## `VolatileCell` - -A `VolatileCell` is just a helper type for doing volatile reads and writes to a -value. This is mostly used for accessing memory-mapped I/O registers. The -`get()` and `set()` functions are wrappers around `core::ptr::read_volatile()` -and `core::ptr::write_volatile()`. - - -## Cell Extensions - -In addition to custom types, Tock adds [extensions][extension_trait] to some of -the standard cells to enhance and ease usability. The mechanism here is to add -traits to existing data types to enhance their ability. To use extensions, -authors need only `use kernel::common::cells::THE_EXTENSION` to pull the new -traits into scope. - -### `NumericCellExt` - -[`NumericCellExt`](https://github.com/tock/tock/blob/master/libraries/tock-cells/src/numeric_cell_ext.rs) -extends cells that contain "numeric" types (like `usize` or `i32`) to provide -some convenient functions (`add()` and `subtract()`, for example). This -extension makes for cleaner code when storing numbers that are increased or -decreased. For example, with a typical `Cell`, adding one to the stored value -looks like: `my_cell.set(my_cell.get() + 1)`. With a `NumericCellExt` it is a -little easier to understand: `my_cell.increment()` (or `my_cell.add(1)`). - -[extension_trait]: https://github.com/aturon/rfcs/blob/extension-trait-conventions/text/0000-extension-trait-conventions.md diff --git a/doc/NestedBoards.md b/doc/NestedBoards.md new file mode 100644 index 0000000000..08b04cc050 --- /dev/null +++ b/doc/NestedBoards.md @@ -0,0 +1,88 @@ +Nested Boards +============= + +Some hardware platforms are designed to be extended with additional hardware +(e.g., shields, plug-in sensors) or serve as the basis for other hardware +platforms. This describes how Tock boards can support this use case. + + + + + +- [Overview](#overview) + * [Rationale](#rationale) +- [Platform Board Setup](#platform-board-setup) + + + +Overview +-------- + +Some boards in Tock can serve as a platform for other boards the be built on top +of. Each board is its own crate, and the platform board becomes a dependency for +the dependent board. The platform board crate is both a library and a binary, +and the binary is implemented almost entirely within the library. The dependent +board also uses the platform board's library. + +The dependent board "inherits" all of the platform board's functionality and +then can extend it by instantiating additional capsules. + +### Rationale + +This design attempts to meet the following goals for nested boards in Tock: + +1. A "normal" Tock board can be a platform board with no changes. +2. The implementation is accomplished with only standard Rust/cargo mechanisms + and without macros. +3. It is easy to reason about the functionality included in the dependent board. + +The first goal promotes consistency among boards, easing the learning curve for +new users and reducing the overhead of maintaining the boards. It also means +that any board can become a platform board, as the board does not change. The +second goal helps promote code readability, as there are no custom scripts or +macros generating new code. The third goal reduces the mental complexity of +nested boards, as there are a limited ways that the platform board and dependent +board can interact, meaning not every imaginable configuration is possible. +Further, complete customizability is _not_ a goal, and two hardware platforms +which are related but sufficiently dissimilar should be implemented as +completely separate boards. + +Unfortunately, we do not know of a way to implement nested boards while meeting +all of those goals. In particular, the limitations on how Rust binary crates +work mean they cannot be dependencies for other crates. As a result, our +approach for nested boards satisfies goals two and three while compromising +somewhat on goal one. The upside of nested boards is sufficient to offset the +drawbacks. + +Platform Board Setup +-------------------- + +The platform board crate supports both a library and a binary. To do this, its +`Cargo.toml` file looks something like: + +```toml +[package] +name = "nrf52840dk" + +[lib] +name = "nrf52840dk_lib" + +[[bin]] +name = "nrf52840dk" +path = "src/main.rs" +``` + +Then, the platform board crate includes two files: `main.rs` and `lib.rs`. The +lib.rs file should expose a function with a signature similar to: + +```rust +pub unsafe fn start() -> ( + &'static kernel::Kernel, + Platform, + &'static nrf52840::chip::NRF52<'static, Nrf52840DefaultPeripherals<'static>>, + &'static Nrf52DefaultPeripherals<'static>, +); +``` + +The main.rs file then calls start before loading processes and starting the +kernel loop. diff --git a/doc/Networking_Stack.md b/doc/Networking_Stack.md deleted file mode 100644 index b1a821d1c1..0000000000 --- a/doc/Networking_Stack.md +++ /dev/null @@ -1,653 +0,0 @@ -Tock Networking Stack Design Document -===================================== - -_NOTE: This document is a work in progress._ - -This document describes the design of the Networking stack on Tock. - -The design described in this document is based off of ideas contributed by -Phil Levis, Amit Levy, Paul Crews, Hubert Teo, Mateo Garcia, Daniel Giffin, and -Hudson Ayers. - -### Table of Contents - -This document is split into several sections. These are as follows: - -1. Principles - Describes the main principles which the design of - this stack intended to meet, - along with some justification of why these principles matter. Ultimately, - the design should follow from these principles. - -2. Stack Diagram - Graphically depicts the layout of the stack - -3. Explanation of queuing - Describes where packets are queued prior to - transmission. - -4. List of Traits - Describes the traits which will exist at each layer of the - stack. For traits that may seem surprisingly complex, provide examples of - specific messages that require this more complex trait as opposed to the - more obvious, simpler trait that might be expected. - -5. Explanation of Queuing - Describe queueing principles for this stack - -6. Description of rx path - -7. Description of the userland interface to the networking stack - -8. Implementation Details - Describes how certain implementations of these - traits will work, providing some examples with pseudocode or commented - explanations of functionality - -9. Example Message Traversals - Shows how different example messages (Thread or - otherwise) will traverse the stack - -## Principles - -1. Keep the simple case simple - - Sending an IP packet via an established network should not - require a more complicated interface than send(destination, packet) - - If functionality were added to allow for the transmission of IP packets over - the BLE interface, this IP send function should not have to deal with any - options or MessageInfo structs that include 802.15.4 layer information. - - This principle reflects a desire to limit the complexity of Thread/the - tock networking stack to the capsules that implement the stack. This - prevents the burden of this complexity from being passed up to whatever - applications use Thread - -2. Layering is separate from encapsulation - - Libraries that handle encapsulation should not be contained within any - specific layering construct. For example, If the Thread control unit wants - to encapsulate a UDP payload inside of a UDP packet inside of an IP packet, - it should be able to do so using encapsulation libraries and get the - resulting IP packet without having to pass through all of the protocol layers - - Accordingly, implementations of layers can take advantage of these - encapsulation libraries, but are not required to. - -3. Dataplane traits are Thread-independent - - For example, the IP trait should not make any assumption that send() - will be called for a message that will be passed down to the 15.4 layer, in - case this IP trait is used on top of an implementation that passes IP - packets down to be sent over a BLE link layer. Accordingly the IP trait - can not expose any arguments regarding 802.15.4 security parameters. - - Even for instances where the only implementation of a trait in the near - future will be a Thread-based implementation, the traits should not - require anything that limit such a trait to Thread-based implementations - -4. Transmission and reception APIs are decoupled - - This allows for instances where receive and send\_done callbacks should - be delivered to different clients (ex: Server listening on all addresses - but also sending messages from specific addresses) - - Prevents send path from having to navigate the added complexity required - for Thread to determine whether to forward received messages up the stack - -## Stack Diagram - -``` -IPv6 over ethernet: Non-Thread 15.4: Thread Stack: Encapsulation Libraries -+-------------------+-------------------+----------------------------+ -| Application |-------------------\ -----------------------------------------+-------------+---+----------+ \ -|TCP Send| UDP Send |TCP Send| UDP Send | | TCP Send | | UDP Send |--\ v -+--------+----------+--------+----------+ +----------+ +----------+ \ +------------+ +------------+ -| IP Send | IP Send | | IP Send | \ -----> | UDP Packet | | TCP Packet | -| | | +-------------------------+ \ / +------------+ +------------+ -| | | | \ / +-----------+ -| | | | -+-------> | IP Packet | -| | | THREAD | / +-----------+ -| IP Send calls eth | IP Send calls 15.4| <--------|------> +-------------------------+ -| 6lowpan libs with | 6lowpan libs with | | \ -------> | 6lowpan compress_Packet | -| default values | default values | | \ +-------------------------+ -| | | | \ +-------------------------+ -| | + +-----------| ------> | 6lowpan fragment_Packet | -| | | | 15.4 Send | +-------------------------+ -|-------------------|-------------------+----------------------------+ -| ethernet | IEEE 802.15.4 Link Layer | -+-------------------+------------------------------------------------+ -``` - -Notes on the stack: -- IP messages sent via Thread networks are sent through Thread using an IP Send - method that exposes only the parameters specified in the IP\_Send trait. - Other parameters of the message (6lowpan decisions, link layer parameters, - many IP header options) are decided by Thread. -- The stack provides an interface for the application layer to send - raw IPv6 packets over Thread. -- When the Thread control plane generates messages (MLE messages etc.), they are - formatted using calls to the encapsulation libraries and then delivered to the - 802.15.4 layer using the 15.4 send function -- This stack design allows Thread to control header elements from transport down - to link layer, and to set link layer security parameters and more as required - for certain packers -- The application can either directly send IP messages using the IP Send - implementation exposed by the Thread stack or it can use the UDP Send - and TCP send implementation exposed by the Thread stack. If the application - uses the TCP or UDP send implementations it must use the transport packet library - to insert its payload inside a packet and set certain header fields. - The transport send method uses the IP Packet library to set certain - IP fields before handing the packet off to Thread. Thread then sets other - parameters at other layers as needed before sending the packet off via the - 15.4 send function implemented for Thread. -- Note that currently this design leaves it up to the application layer to - decide what interface any given packet will be transmitted from. This is - because currently we are working towards a minimum functional stack. - However, once this is working we intend to add a layer below the application - layer that would handle interface multiplexing by destination address via a - forwarding table. This should be straightforward to add in to our current - design. -- This stack does not demonstrate a full set of functionality we are planning to - implement now. Rather it demonstrates how this setup allows for multiple - implementations of each layer based off of traits and libraries such that a - flexible network stack can be configured, rather than creating a network - stack designed such that applications can only use Thread. - - -## Explanation of Queuing - -Queuing happens at the application layer in this stack. -The userland interface to the -networking stack (described in greater detail in Networking\_Userland.md) -already handles queueing multiple packets sent from userland apps. -In the kernel, any application which wishes to send multiple UDP packets must -handle queueing itself, waiting for a send\_done to return from the radio -before calling send on the next packet in a series of packets. - -## List of Traits - -This section describes a number of traits which must be implemented by any -network stack. It is expected that multiple implementations of some of these -traits may exist to allow for Tock to support more than just Thread networking. - -Before discussing these traits - a note on buffers: - -> Prior implementations of the tock networking stack passed around references -> to 'static mut [u8] to pass packets along the stack. This is not ideal from a -> standpoint of wanting -> to be able to prevent as many errors as possible at compile time. The next iteration -> of code will pass 'typed' buffers up and down the stack. There are a number -> of packet library traits defined below (e.g. IPPacket, UDPPacket, etc.). -> Transport Layer traits will be implemented by a struct that will contain at least one field - -> a [u8] buffer with lifetime 'a. Lower level traits will simply contain -> payload fields that are Transport Level packet traits (thanks to a -> TransportPacket enum). This design allows for all buffers passed to -> be passed as type 'UDPPacket', 'IPPacket', etc. An added advantage of this -> design is that each buffer can easily be operated on using the library -> functions associated with this buffer type. - - -The traits below are organized by the network layer they would typically be -associated with. - -### Transport Layer - -Thus far, the only transport layer protocol implemented in Tock is UDP. - -Documentation describing the structs and traits that define the UDP layer can -be found in capsules/src/net/udp/(udp.rs, udp\_send.rs, udp\_recv.rs) - -Additionally, a driver exists that provides a userland interface via which -udp packets can be sent and received. This is described in greater detail in -Networking\_Userland.md - - -### Network Stack Receive Path - -- The radio in the kernel has a single `RxClient`, which is set as the mac layer (awake_mac, typically) -- The mac layer (i.e. `AwakeMac`) has a single `RxClient`, which is the mac_device(`ieee802154::Framer::framer`) -- The Mac device has a single receive client - `MuxMac` (virtual MAC device). -- The `MuxMac` can have multiple "users" which are of type `MacUser` -- Any received packet is passed to ALL MacUsers, which are expected to filter packets themselves accordingly. -- Right now, we initialize two MacUsers in the kernel (in main.rs/components). These are the 'radio_mac', which is the MacUser for the RadioDriver that enables the userland interface to directly send 802154 frames, and udp_mac, the mac layer that is ultimately associated with the udp userland interface. -- The udp_mac MacUser has a single receive client, which is the `sixlowpan_state` struct -- `sixlowpan_state` has a single rx_client, which in our case is a single struct that implements the `ip_receive ` trait. -- the `ip_receive` implementing struct (`IP6RecvStruct`) has a single client, which is udp_recv, a `UDPReceive` struct. -- The UDPReceive struct is a field of the UDPDriver, which ultimately passes the packets up to userland. - -So what are the implications of all this? - -1) Currently, any userland app could receive udp packets intended for -anyone else if the app implmenets 6lowpan itself on the received raw frames. - -2) Currently, packets are only muxed at the Mac layer. - -3) Right now the IPReceive struct receives all IP packets sent to the MAC address of this device, and soon will drop all packets sent to non-local addresses. Right now, the device effectively only has one address anyway, as we only support 6lowpan over 15.4, and as we haven't implemented a loopback interface on the IP_send path. If, in the future, we implement IP forwarding on Tock, we will need to add an IPSend object to the IPReceiver which would then retransmit any packets received that were not destined for local addresses. - -## Explanation of Configuration - -This section describes how the IP stack can be configured, including setting -addresses and other parameters of the MAC layer. - -* Source IP address: An array of local interfaces on the device is contained in main.rs. -Currently, this array contains two hardcoded addresses, and one address generated from the -unique serial number on the sam4l. - -* Destination IP address: The destination IP address is configured by passing the address -to the send_to() call when sending IPv6 packets. - -* src MAC address: This address is configured in main.rs. Currently, the src mac address -for each device is configured by default to be a 16-bit short address representing the last 16 bits -of the unique 120 bit serial number on the sam4l. However, userland apps can change the src address -by calling ieee802154_set_address() - -* dst MAC address: This is currently a constant set in main.rs. (DST_MAC_ADDR). In the future -this will change, once Tock implements IPv6 Neighbor Discovery. - -* src pan: This is set via a constant configured in main.rs (PAN_ID). The same constant is used -for the dst pan. - -* dst pan: Same as src_pan. If we need to support use of the broadcast PAN as a dst_pan, this -may change. - -* radio channel: Configured as a constant in main.rs (RADIO_CHANNEL). - -## Tock Userland Networking Design - -This section describes the current userland interface for the networking stack -on Tock. This section should serve as a description of the abstraction -provided by libTock - what the exact system call interface looks like or how -libTock or the kernel implements this functionality is out-of-scope of this -document. - -### Overview -The Tock networking stack and libTock should attempt to expose a networking -interface that is similar to the POSIX networking interface. The primary -motivation for this design choice is that application programmers are used -to the POSIX networking interface design, and significant amounts of code -have already been written for POSIX-style network interfaces. By designing -the libTock networking interface to be as similar to POSIX as possible, we -hope to improve developer experience while enabling the easy transition of -networking code to Tock. - -### Design - -udp.c and udp.h in libtock-c/libtock define the userland interface to the -Tock networking stack. These files interact with capsules/src/net/udp/driver.rs -in the main tock repository. driver.rs implements an interface for sending -and receiving UDP messages. It also exposes a list of interace addresses to -the application layer. The primary functionality embedded in the UDP driver -is within the allow(), subscribe(), and command() calls which can be made to -the driver. - -Details of this driver can be found in `doc/syscalls` folder - -udp.c and udp.h in libtock-c make it easy to interact with this driver interface. -Important functions available to userland apps written in c include: - -`udp_socket()` - sets the port on which the app will receive udp packets, - and sets the `src_port` of outgoing packets sent via that socket. Once socket - binding is implemented in the kernel, this function will handle reserving ports - to listen on and send from. - -`udp_close()` - currently just returns success, but once socket binding has been - implemented in the kernel, this function will handle freeing bound ports. - -`udp_send_to()` - Sends a udp packet to a specified addr/port pair, returns the result - of the tranmission once the radio has transmitted it (or once a failure has occured). - -`udp_recv_from_sync()` - Pass an interface to listen on and an incoming source address - to listen for. Sets up a callback to wait for a received packet, and yeilds until that - callback is triggered. This function never returns if a packet is not received. - -`udp_recv_from()` - Pass an interface to listen on and an incoming source address to - listen for. However, this takes in a buffer to which the received packet should be placed, - and returns the callback that will be triggered when a packet is received. - -`udp_list_ifaces()` - Populates the passed pointer of ipv6 addresses with the available - ipv6 addresses of the interfaces on the device. Right now this merely returns a constant - hardcoded into the UDP driver, but should change to return the source IP addresses held - in the network configuration file once that is created. Returns up to `len` addresses. - -Other design notes: - -The current design of the driver has a few limitations, these include: - -- Currently, any app can listen on any address/port pair - -- The current tx implementation allows for starvation, e.g. an app with an earlier app ID can - starve a later ID by sending constantly. - -#### POSIX Socket API Functions -Below is a fairly comprehensive overview of the POSIX networking socket -interface. Note that much of this functionality pertains to TCP or connection- -based protocols, which we currently do not handle. - -- `socket(domain, type, protocol) -> int fd` - - `domain`: AF\_INET, AF\_INET6, AF\_UNIX - - `type`: SOCK\_STREAM (TCP), SOCK\_DGRAM (UDP), SOCK\_SEQPACKET (?), SOCK\_RAW - - `protocol`: IPPROTO\_TCP, IPPROTO\_SCTP, IPPROTO\_UDP, IPPROTO\_DCCP - -- `bind(socketfd, my_addr, addrlen) -> int success` - - `socketfd`: Socket file descriptor to bind to - - `my_addr`: Address to bind on - - `addrlen`: Length of address - -- `listen(socketfd, backlog) -> int success` - - `socketfd`: Socket file descriptor - - `backlog`: Number of pending connections to be queued - - Only necessary for stream-oriented data modes - -- `connect(socketfd, addr, addrlen) -> int success` - - `socketfd`: Socket file descriptor to connect with - - `addr`: Address to connect to (server protocol address) - - `addrlen`: Length of address - - When used with connectionless protocols, defines the remote address for - sending and receiving data, allowing the use of functions such as `send()` - and `recv()` and preventing the reception of datagrams from other sources. - -- `accept(socketfd, cliaddr, addrlen) -> int success` - - `socketfd`: Socket file descriptor of the listening socket that has the - connection queued - - `cliaddr`: A pointer to an address to receive the client's address information - - `addrlen`: Specifies the size of the client address structure - -- `send(socketfd, buffer, length, flags) -> int success` - - `socketfd`: Socket file descriptor to send on - - `buffer`: Buffer to send - - `length`: Length of buffer to send - - `flags`: Various flags for the transmission - - Note that the `send()` function will only send a message when the `socketfd` - is connected (including for connectionless sockets) - -- `sendto(socketfd, buffer, length, flags, dst_addr, addrlen) -> int success` - - `socketfd`: Socket file descriptor to send on - - `buffer`: Buffer to send - - `length`: Length of buffer to send - - `flags`: Various flags for the transmission - - `dst_addr`: Address to send to (ignored for connection type sockets) - - `addrlen`: Length of `dst_addr` - - Note that if the socket is a connection type, dst_addr will be ignored. - -- `recv(socketfd, buffer, length, flags)` - - `socketfd`: Socket file descriptor to receive on - - `buffer`: Buffer where the message will be stored - - `length`: Length of buffer - - `flags`: Type of message reception - - Typically used with connected sockets as it does not permit the application - to retrieve the source address of received data. - -- `recvfrom(socketfd, buffer, length, flags, address, addrlen)` - - `socketfd`: Socket file descriptor to receive on - - `buffer`: Buffer to store the message - - `length`: Length of the buffer - - `flags`: Various flags for reception - - `address`: Pointer to a structure to store the sending address - - `addrlen`: Length of address structure - - Normally used with connectionless sockets as it permits the application to - retrieve the source address of received data - -- `close(socketfd)` - - `socketfd`: Socket file descriptor to delete - -- `gethostbyname()/gethostbyaddr()` - Legacy interfaces for resolving host names and addresses - -- `select(nfds, readfds, writefds, errorfds, timeout)` - - `nfds`: The range of file descriptors to be tested (0..nfds) - - `readfds`: On input, specifies file descriptors to be checked to see if they - are ready to be read. On output, indicates which file descriptors are ready - to be read - - `writefds`: Same as readfds, but for writing - - `errorfds`: Same as readfds, writefds, but for errors - - `timeout`: A structure that indicates the max amount of time to block if - no file descriptors are ready. If None, blocks indefinitely - -- `poll(fds, nfds, timeout)` - - `fds`: Array of structures for file descriptors to be checked. The array - members are structures which contain the file descriptor, and events - to check for plus areas to write which events occurred - - `nfds`: Number of elements in the fds array - - `timeout`: If 0 return immediately, or if -1 block indefinitely. Otherwise, - wait at least `timeout` milliseconds for an event to occur - -- `getsockopt()/setsockopt()` - -#### Tock Userland API -Below is a list of desired functionality for the libTock userland API. - -- `struct sock_addr_t` - `ipv6_addr_t`: IPv6 address (single or ANY) - `port_t`: Transport level port (single or ANY) - -- `struct sock_handle_t` - Opaque to the user; allocated in userland by malloc (or on the stack) - -- `list_ifaces() -> iface[]` - `ifaces`: A list of `ipv6_addr_t, name` pairs corresponding to all - interfaces available - -- `udp_socket(sock_handle_t, sock_addr_t) -> int socketfd` - `socketfd`: Socket object to be initialized as a UDP socket with the given - address information - `sock_addr_t`: Contains an IPv6 address and a port - -- `udp_close(sock_handle_t)` - `sock_handle_t`: Socket to close - -- `send_to(sock_handle_t, buffer, length, sock_addr_t)` - - `sock_handle_t`: Socket to send using - - `buffer`: Buffer to send - - `length`: Length of buffer to send - - `sock_addr_t`: Address struct (IPv6 address, port) to send the packet from - -- `recv_from(sock_handle_t, buffer, length, sock_addr_t)` - - `sock_handle_t`: Receiving socket - - `buffer`: Buffer to receive into - - `length`: Length of buffer - - `sock_addr_t`: Struct where the kernel writes the received packet's sender - information - -#### Differences Between the APIs - -There are two major differences between the proposed Tock APIs and the standard -POSIX APIs. First, the POSIX APIs must support connection-based protocols such -as TCP, whereas the Tock API is only concerned with connectionless, datagram -based protocols. Second, the POSIX interface has a concept of the `sock_addr_t` -structure, which is used to encapsulate information such as port numbers to -bind on and interface addresses. This makes `bind_to_port` redundant in POSIX, -as we can simply set the port number in the `sock_addr_t` struct when binding. -I think one of the major questions is whether to adopt this convention, or to -use the above definitions for at least the first iteration. - -### Example: `ip_sense` - -An example use of the userland networking stack can be found in libtock-c/examples/ip\_sense - -## Implementation Details for potential future Thread implementation - -This section was written when the networking stack was incomplete, and aspects -may be outdated. This goes for all sections following this point in the document. - -The Thread specification determines an entire control plane that spans many -different layers in the OSI networking model. To adequately understand the -interactions and dependencies between these layers' behaviors, it might help to -trace several types of messages and see how each layer processes the different -types of messages. Let's trace carefully the way OpenThread handles messages. - -We begin with the most fundamental message: a data-plane message that does not -interact with the Thread control plane save for passing through a -Thread-defined network interface. Note that some of the procedures in the below -traces will not make sense when taken independently: the responsibility-passing -will only make sense when all the message types are taken as a whole. -Additionally, no claim is made as to whether or not this sequence of callbacks -is the optimal way to express these interactions: it is just OpenThread's way -of doing it. - -### Data plane: IPv6 datagram - -1. Upper layer (application) wants to send a payload - - Provides payload - - Specifies the IP6 interface to send it on (via some identifier) - - Specifies protocol (IP6 next header field) - - Specifies destination IP6 address - - Possibly doesn't specify source IP6 address -2. IP6 interface dispatcher (with knowledge of all the interfaces) fills in the - IP6 header and produces an IP6 message - - Payload, protocol, and destination address used directly from the upper layer - - Source address is more complicated - - If the address is specified and is not multicast, it is used directly - - If the address is unspecified or multicast, source address is determined - from the specific IP6 selected AND the destination address via a matching scheme on - the addresses associated with the interface. - - Now that the addresses are determined, the IP6 layer computes the pseudoheader - checksum. - - If the application layer's payload has a checksum that includes the pseudoheader - (UDP, ICMP6), this partial checksum is now used to update the checksum field in the payload. -3. The actual IP6 interface (Thread-controlled) tries to send that message - - First step is to determine whether the message can be sent immediately or not (sleepy child or not). - This passes the message to the scheduler. This is important for sleepy children where there is a - control scheme that determines when messages are sent. - - Next, determine the MAC src/dest addresses. - - If this is a direct transmission, there is a source matching scheme to determine if the destination address - used should be short or long. The same length is used for the source MAC address, obtained from the MAC interface. - - Notify the MAC layer to notify you that your message can be sent. -4. The MAC layer schedules its transmissions and determines that it can send the above message - - MAC sets the transmission power - - MAC sets the channel differently depending on the message type -5. The IP6 interface fills up the frame. This is the chance for the IP6 interface to do things like - fragmentation, retransmission, and so on. The MAC layer just wants a frame. - - XXX: The IP6 interface fills up the MAC header. This should really be the responsibility of the MAC layer. - Anyway, here is what is done: - - Channel, source PAN ID, destination PAN ID, and security modes are determined by message type. - Note that the channel set by the MAC layer is sometimes overwritten. - - A mesh extension header is added for some messages. (eg. indirect transmissions) - - The IP6 message is then 6LoWPAN-compressed/fragmented into the payload section of the frame. -6. The MAC layer receives the raw frame and tries to send it - - MAC sets the sequence number of the frame (from the previous sequence number for the correct link neighbor), - if it is not a retransmission - - The frame is secured if needed. This is another can of worms: - - Frame counter is dependent on the link neighbor and whether or not the frame is a retransmission - - Key is dependent on which key id mode is selected, and also the link neighbor's key sequence - - Key sequence != frame counter - - One particular mode requires using a key, source and frame counter that is a Thread-defined constant. - - The frame is transmitted, an ACK is waited for, and the process completes. - -As you can see, the data dependencies are nowhere as clean as the OSI model -dictates. The complexity mostly arises because - -- Layer 4 checksum can include IPv6 pseudoheader -- IP6 source address (mesh local? link local? multicast?) is determined by - interface and destination address -- MAC src/dest addresses are dependent on the next device on the route to the - IP6 destination address -- Channel, src/dest PAN ID, security is dependent on message type -- Mesh extension header presence is dependent on message type -- Sequence number is dependent on message type and destination - -Note that all of the MAC layer dependencies in step 5 can be pre-decided so -that the MAC layer is the only one responsible for writing the MAC header. - -This gives a pretty good overview of what minimally needs to be done to even be -able to send normal IPv6 datagrams, but does not cover all of Thread's -complexities. Next, we look at some control-plane messages. - -### Control plane: MLE messages - -1. The MLE layer encapsulates its messages in UDP on a constant port - - Security is determined by MLE message type. If MLE-layer security is - required, the frame is secured using the same CCM* encryption scheme used - in the MAC layer, but with a different key discipline. - - MLE key sequence is global across a single Thread device - - MLE sets IP6 source address to the interface's link local address -2. This UDP-encapsulated MLE message is sent to the IP6 dispatch again -3. The actual IP6 interface (Thread-controlled) tries to send that message -4. The MAC layer schedules the transmission -5. The IP6 interface fills up the frame. - - MLE messages disable link-layer security when MLE-layer security is - present. However, if link-layer security is disabled and the MLE message - doesn't fit in a single frame, link-layer security is enabled so that - fragmentation can proceed. -6. The MAC layer receives the raw frame and tries to send it - -The only cross-layer dependency introduced by the MLE layer is the dependency -between MLE-layer security and link-layer security. Whether or not the MLE -layer sits atop an actual UDP socket is an implementation detail. - -### Control plane: Mesh forwarding - -If Thread REED devices are to be eventually supported in Tock, then we must -also consider this case. If a frame is sent to a router which is not its final -destination, then the router must forward that message to the next hop. - -1. The MAC layer receives a frame, decrypts it and passes it to the IP6 interface -2. The IP6 reception reads the frame and realizes that it is an indirect - transmission that has to be forwarded again - - The frame must contain a mesh header, and the HopsLeft field in it should - be decremented - - The rest of the payload remains the same - - Hence, the IP6 interface needs to send a raw 6LoWPAN-compressed frame -3. The IP6 transmission interface receives a raw 6LoWPAN-compressed frame to be - transmitted again - - This frame must still be scheduled: it might be destined for a sleepy - device that is not yet awake -4. The MAC layer schedules the transmission -5. The IP6 transmission interface copies the frame to be retransmitted - verbatim, but with the modified mesh header and a new MAC header -6. The MAC layer receives the raw frame and tries to send it - -This example shows that the IP6 transmission interface may need to handle more -message types than just IP6 datagrams: there is a case where it is convenient -to be able to handle a datagram that is already 6LoWPAN compressed. - -### Control plane: MAC data polling - -From time to time, a sleepy edge device will wake up and begin polling its -parent to check if any frames are available for it. This is done via a MAC -command frame, which must still be sent through the transmission pipeline with -link security enabled (Key ID mode 1). OpenThread does this by routing it -through the IP6 transmission interface, which arguably isn't the right choice. - -1. Data poll manager send a data poll message directly to the IP6 transmission - interface, skipping the IP6 dispatch -2. The IP6 transmission interface notices the different type of message, which - always warrants a direct transmission. -3. The MAC layer schedules the transmission -4. The IP6 transmission interface fills in the frame - - The MAC dest is set to the parent of this node and the MAC src is set to be - the same length as the address of the parent - - The payload is filled up to contain the Data Request MAC command - - The MAC security level and key ID mode is also fixed for MAC commands under - the Thread specification -5. The MAC layer secures the frame and sends it out - -We could imagine giving the data poll manager direct access as a client of the -MAC layer to avoid having to shuffle data through the IP6 transmission -interface. This is only justified because MAC command frames are never -6LoWPAN-compressed or fragmented, nor do they depend on the IP6 interface in -any way. - -### Control plane: Child supervision - -This type of message behaves similarly to the MAC data polls. The message is -essentially and empty MAC frame, but OpenThread chooses to also route it -through the IP6 transmission interface. It would be far better to allow a child -supervision implementation to be a direct client of the MAC interface. - -### Control plane: Joiner entrust and MLE announce - -These two message types are also explicitly marked, because they require a -specific Key ID Mode to be selected when producing the frame for the MAC -interface. - -### Caveat about MAC layer security - -So far, it seems like we can expect the MAC layer to have no cross-layer -dependencies: it receives frames with a completely specified description of how -they are to be secured and transmitted, and just does so. However, this is not -entirely the case. - -When the frame is being secured, the key ID mode has been set by the upper -layers as described above, and this key ID mode is used to select between a few -different key disciplines. For example, mode 0 is only used by Joiner entrust -messages and uses the Thread KEK sequence. Mode 1 uses the MAC key sequence and -Mode 2 is a constant key used only in MLE announce messages. Hence, this key ID -mode selection is actually enabling an upper layer to determine the specific -key being used in the link layer. - -Note that we cannot just reduce this dependency by allowing the upper layer to -specify the key used in MAC encryption. During frame reception, the MAC layer -itself has to know which key to use in order to decrypt the frames correctly. diff --git a/doc/OutOfTree.md b/doc/OutOfTree.md index 54aacd8771..78e6ce2e77 100644 --- a/doc/OutOfTree.md +++ b/doc/OutOfTree.md @@ -34,7 +34,7 @@ abreast of Tock development: wait at least one week to merge to allow for feedback. Finally, please don't hesitate to -[ask for help](https://kiwiirc.com/client/irc.freenode.net/tock). +[ask for help](https://github.com/tock/tock/#keep-up-to-date). Structure @@ -53,6 +53,7 @@ something like: │   └── my_board │   ├── Cargo.toml │   ├── Makefile + │   ├── layout.ld │   └── src │   └── main.rs ├── my_drivers @@ -96,6 +97,7 @@ to elements of Tock. name = "my_board" version = "0.1.0" authors = ["Example Developer "] + build = "../../tock/boards/build.rs" [profile.dev] panic = "abort" @@ -115,7 +117,9 @@ to elements of Tock. my_drivers = { path = "../../my_drivers" } ``` - +If using a linker script named `layout.ld`, your board crate can make use of the +Tock submodule's `build.rs` script to ensure it rebuilds when any linker scripts +change. Everything Else --------------- diff --git a/doc/Overview.md b/doc/Overview.md deleted file mode 100644 index c0e230708a..0000000000 --- a/doc/Overview.md +++ /dev/null @@ -1,128 +0,0 @@ -# Tock Overview - -Tock is a secure, embedded operating system for Cortex-M and RISC-V -microcontrollers. Tock assumes the hardware includes a memory protection unit -(MPU), as systems without an MPU cannot simultaneously support untrusted -processes and retain Tock's safety and security properties. The Tock kernel and -its extensions (called *capsules*) are written in Rust. - -Tock can run multiple, independent untrusted processes written in -any language. The number of processes Tock can simultaneously support -is constrained by MCU flash and RAM. Tock can be configured to use different -scheduling algorithms, but the default Tock scheduler is preemptive and -uses a round-robin policy. Tock uses a microkernel architecture: complex -drivers and services are often implemented as untrusted processes, which -other processes, such as applications, can invoke through inter-process -commmunication (IPC). - -This document gives an overview of Tock's architecture, the different -classes of code in Tock, the protection mechanisms it uses, and how this -structure is reflected in the software's directory structure. - - - - - -- [Tock Architecture](#tock-architecture) -- [Tock Directory Structure](#tock-directory-structure) - - - -## Tock Architecture - -![Tock architecture](tock-stack.png) - -The above Figure shows Tock's architecture. Code falls into one of three -categories: the *core kernel*, *capsules*, and *processes*. - -The core kernel and capsules are both written in Rust. Rust is a -type-safe systems language; other documents discuss the language and -its implications to kernel design in greater detail, but the -key idea is that Rust code can't use memory differently than intended -(e.g., overflow buffers, forge pointers, or have pointers to dead -stack frames). Because these restrictions prevent many things that -an OS kernel has to do (such as access a peripheral that exists at a -memory address specified in a datasheet), the very small core kernel -is allowed to break them by using "unsafe" Rust code. Capsules, -however, cannot use unsafe features. This means that the core kernel -code is very small and carefully written, while new capsules added -to the kernel are safe code and so do not have to be trusted. - -Processes can be written in any language. The kernel protects itself and -other processes from bad process code by using a hardware memory -protection unit (MPU). If a process tries to access memory it's not -allowed to, this triggers an exception. The kernel handles this exception -and kills the process. - -The kernel provides four major system calls: - - * command: makes a call from the process into the kernel - * subscribe: registers a callback in the process for an upcall from the kernel - * allow: gives kernel access to memory in the process - * yield: suspends process until after a callback is invoked - -Every system call except yield is non-blocking. Commands that -might take a long time (such as sending a message over a UART) -return immediately and issue a callback when they complete. -The yield system call blocks the process until a callback -is invoked; userland code typically implements blocking -functions by invoking a command and then using yield to wait -until the callback completes. - -The command, subscribe, and allow system calls all take a driver -ID as their first parameter. This indicates which driver in the -kernel that system call is intended for. Drivers are capsules that -implement the system call. - - -## Tock Directory Structure - -Tock has several principal code directories. - -- **arch**: stores architecture-specific code. I.e., code that -is Cortex-M0 and Cortex-M4 specific. This includes code for performing -context switches and making system calls (trapping from user code to -kernel code). - -- **boards**: contains code for specific Tock platforms, such as -the imix, the Hail, and the nrf52dk. This is typically the structure -that defines all of the capsules the kernel has, the code to configure the -MCU's IO pins into the proper states, initializing the kernel and loading -processes. The principal file in this directory is `main.rs`, and the -principal initialization function is `main` (which executes when the MCU -resets after RAM has been initialized). The board code also defines how system -call device identifiers map to capsules, in the `with_driver` function. - -- **capsules**: contains MCU-independent kernel extensions that -can build on top of chip-specific implementations of particular peripherals. -Some capsules provide system calls. For example, the `spi` module in capsules -builds on top of a chip's SPI implementation to provide system calls on -top of it. - -- **chips**: contains microcontroller-specific code, such as the -implementations of SPI, I2C, GPIO, UART, and other microcontroller-specific -code. The distinction between chips and boards is the difference between -a microcontroller and a full platform. For example, many microcontrollers -have multiple UARTs. Which UART is the principal way to communicate with -Tock, or which is used to control another chip, is defined by how the chip -is placed on board and which pins are exposed. So a chip provides the UART -implementation, but a board defines which UART is used for what. - -- **doc**: contains the documentation for Tock, including -specifications for internal interfaces and tutorials. - -- **kernel**: contains microcontroller-independent kernel code, -such as the scheduler, processes, and memory management. This directory -and arch are where all core kernel code reside. - -- **libraries**: contains libraries that we use internally and share -externally. Several primitives have been created for Tock that we think could -also be useful to other projects. This is a location where each crate is -located. - -- **tools**: contains associated tools to help in compilation and -code maintenance, such as checking code formatting, converting binaries, -and build scripts. - -- **vagrant**: contains information on how to get Tock running in a -virtual machine-esque environment. diff --git a/doc/Porting.md b/doc/Porting.md deleted file mode 100644 index 97689bd786..0000000000 --- a/doc/Porting.md +++ /dev/null @@ -1,277 +0,0 @@ -Porting Tock -============ - -This guide covers how to port Tock to a new platform. - -_It is a work in progress. Comments and pull requests are appreciated!_ - - - - - -- [Overview](#overview) -- [Crate Details](#crate-details) - * [`arch` Crate](#arch-crate) - * [`chip` Crate](#chip-crate) - * [`board` Crate](#board-crate) - + [Board Support](#board-support) - - [`panic!`s (aka `io.rs`)](#panics-aka-iors) - - [Board Cargo.toml, build.rs](#board-cargotoml-buildrs) - - [Board Makefile](#board-makefile) - * [Getting the built kernel onto a board](#getting-the-built-kernel-onto-a-board) - - [Board README](#board-readme) - + [Loading Apps](#loading-apps) - + [Common Pitfalls](#common-pitfalls) -- [Adding a Platform to Tock Repository](#adding-a-platform-to-tock-repository) - - - -Overview --------- - -At a high level, to port Tock to a new platform you will need to create a new -"board" as a crate, as well as potentially add additional "chip" and "arch" -crates. The board crate specifies the exact resources available on a hardware -platform by stitching capsules together with the chip crates (e.g. assigning -pins, setting baud rates, allocating hardware peripherals etc.). The chip crate -implements the peripheral drivers (e.g. UART, GPIO, alarms, etc.) for a specific -microcontroller by implementing the traits found in `kernel/src/hil`. If your -platform uses a microncontroller already supported by Tock then you can use the -existing chip crate. The arch crate implements the low-level code for a specific -hardware architecture (e.g. what happens when the chip first boots and how -system calls are implemented). - -Crate Details -------------- - -This section includes more details on what is required to implement each type of -crate for a new hardware platform. - -### `arch` Crate - -Tock currently supports the ARM Cortex-M0, Cortex-M3, and Cortex M4, and the -riscv32imac architectures. There is not much architecture-specific code in Tock, -the list is pretty much: - - - Syscall entry/exit - - Interrupt configuration - - Top-half interrupt handlers - - MPU configuration (if appropriate) - - Power management configuration (if appropriate) - -It would likely be fairly easy to port Tock to another ARM Cortex M -(specifically the M0+, M23, M4F, or M7) or another riscv32 variant. It will -probably be more work to port Tock to other architectures. While we aim to be -architecture agnostic, this has only been tested on a small number of -architectures. - -If you are interested in porting Tock to a new architecture, it's likely best -to reach out to us via email or Slack before digging in too deep. - - -### `chip` Crate - -The `chip` crate is specific to a particular microcontroller, but should attempt -to be general towards a family of microcontrollers. For example, support for the -`nRF58240` and `nRF58230` microcontrollers is shared in the `chips/nrf52` and -`chips/nrf5x` crates. This helps reduce duplicated code and simplifies adding -new specific microcontrollers. - -The `chip` crate contains microcontroller-specific implementations of the -interfaces defined in `kernel/src/hil`. - -Chips have a lot of features and Tock supports a large number of interfaces to -express them. Build up the implementation of a new chip incrementally. Get -reset and initialization code working. Set it up to run on the chip's default -clock and add a GPIO interface. That's a good point to put together a minimal -board that uses the chip and validate with an end-to-end userland application -that uses GPIOs. - -Once you have something small like GPIOs working, it's a great time to open a -pull request to Tock. This lets others know about your efforts with this chip -and can hopefully attract additional support. It also is a chance to get some -feedback from the Tock core team before you have written too much code. - -Moving forward, chips tend to break down into reasonable units of work. -Implement something like `kernel::hil::UART` for your chip, then submit a pull -request. Pick a new peripheral and repeat! - -Historically, Tock chips defined peripherals as `static mut` global variables, which made -them easy to access but encouraged use of unsafe code and prevented boards from -instantiating only the set of peripherals they needed. Now, peripherals are instantiated -at runtime in `main.rs`, which resolves these issues. To prevent each board from having -to instantiate peripherals individually, chips should provide a `ChipNameDefaultPeripherals` -struct that defines and creates all peripherals available for the chip in Tock. This will be -used by upstream boards using the chip, without forcing the overhead and code size of all peripherals -on more minimal out-of-tree boards. - - -### `board` Crate - -The `board` crate, in `boards/src`, is specific to a physical hardware platform. -The board file essentially configures the kernel to support the specific -hardware setup. This includes instantiating drivers for sensors, mapping -communication buses to those sensors, configuring GPIO pins, etc. - -Tock is leveraging "components" for setting up board crates. Components are -contained structs that include all of the setup code for a particular driver, -and only require boards to pass in the specific options that are unique to the -particular platform. For example: - -```rust -let ambient_light = AmbientLightComponent::new(board_kernel, mux_i2c, mux_alarm) - .finalize(components::isl29035_component_helper!(sam4l::ast::Ast)); -``` - -instantiates the component for an ambient light sensor. Board initiation should -be largely done using components, but not all components have been created yet, -so board files are generally a mix of components and verbose driver -instantiation. The best bet is to start from an existing board's `main.rs` file -and adapt it. Initially, you will likely want to delete most of the capsules and -add them slowly as you get things working. - -> Warning: Components are singletons, that is they may not be instantiated multiple -> times. Components should only be instantiated in the reset handler to avoid -> any multiple instantiations. - -#### Board Support - -In addition to kernel code, boards also require some support files. These -specify metadata such as the board name, how to load code onto the board, and -anything special that userland applications may need for this board. - -##### `panic!`s (aka `io.rs`) - -Each board must author a custom routine to handle `panic!`s. Most `panic!` -machinery is handled by the Tock kernel, but the board author must provide -some minimalist access to hardware interfaces, specifically LEDs and/or UART. - -As a first step, it is simplest to just get LED-based `panic!` working. Have -your `panic!` handler set up a prominent LED and then call -[kernel::debug::panic_blink_forever](https://docs.tockos.org/kernel/debug/fn.panic_blink_forever.html). - -If UART is available, the kernel is capable of printing a lot of very helpful -additional debugging information. However, as we are in a `panic!` situation, -it's important to strip this down to a minimalist implementation. In particular, -the supplied UART must be synchronous (note that this in contrast to the rest of -the kernel UART interfaces, which are all asynchronous). Usually implementing a -very simple `Writer` that simply writes one byte at a time directly to the UART -is easiest/best. It is not important that `panic!` UART writer be efficient. -You can then replace the call to -[kernel::debug::panic_blink_forever](https://docs.tockos.org/kernel/debug/fn.panic_blink_forever.html) -with a call to -[kernel::debug::panic](https://docs.tockos.org/kernel/debug/fn.panic.html). - -For largely historical reasons, panic implementations for all boards live in -a file named `io.rs` adjacent to the board's `main.rs` file. - -##### Board Cargo.toml, build.rs - -Every board crate must author a top-level manifest, `Cargo.toml`. In general, -you can probably simply copy this from another board, modifying the board name -and author(s) as appropriate. Note that Tock also includes a build script, -`build.rs`, that you should also copy. The build script simply adds a -dependency on the kernel layout. - -##### Board Makefile - -There is a Makefile in the root of every board crate, at a minimum, the board -Makefile must include: - -```make -# Makefile for building the tock kernel for the Hail platform - -TARGET=thumbv7em-none-eabi # Target triple -PLATFORM=hail # Board name here - -include ../Makefile.common # ../ assumes board lives in $(TOCK)/boards/ -``` - -Tock provides `boards/Makefile.common` that drives most of the build system. -In general, you should not need to -dig into this Makefile -- if something doesn't seem to be working, hop on slack -and ask. - -###### Getting the built kernel onto a board - -In addition to building the kernel, the board Makefile should include rules -for getting code onto the board. This will naturally be fairly board-specific, -but Tock does have two targets normally supplied: - - - _program_: For "plug-'n-plug" loading. Usually these are boards with a - bootloader or some other support IC. The expectation is that during normal - operation, a user could simply plug in a board and type `make program` to - load code. - - _flash_: For "more direct" loading. Usually this means that a JTAG or some - equivalent interface is being used. Often it implies that external - hardware is required, though some of the development kit boards have an - integrated JTAG on-board, so external hardware is not a hard and fast - rule. - - _install_: This should be an alias to either `program` or `flash`, whichever - is the preferred approach for this board. - -If you don't support _program_ or _flash_, you should define an empty rule -that explains how to program the board: - -```make -.PHONY: program - echo "To program, run SPEICAL_COMMAND" - exit 1 -``` - -##### Board README - -Every board must have a `README.md` file included in the top level of the crate. -This file must: - -- Provide links to information about the platform and how to purchase/acquire - the platform. If there are different versions of the platform the version used - in testing should be clearly specified. -- Include an overview on how to program the hardware, including any additional - dependencies that are required. - -#### Loading Apps - -Ideally, [Tockloader](https://github.com/tock/tockloader) will support loading -apps on to your board (perhaps with some flags set to specific values). If that -is not the case, please create an issue on the Tockloader repo so we can update -the tool to support loading code onto your board. - -#### Common Pitfalls - -- Make sure you are careful when setting up the board `main.rs` file. In - particular, it is important to ensure that all of the required `set_client` - functions for capsules are called so that callbacks are not lost. Forgetting - these often results in the platform looking like it doesn't do anything. - - -Adding a Platform to Tock Repository ------------------------------------- - -After creating a new platform, we would be thrilled to have it included in -mainline Tock. However, Tock has a few guidelines for the minimum requirements -of a board that is merged into the main Tock repository: - -1. The hardware must be widely available. Generally that means the hardware - platform can be purchased online. -2. The port of Tock to the platform must include at least: - - `Console` support so that `debug!()` and `printf()` work. - - Timer support. - - GPIO support with interrupt functionality. -3. The contributor must be willing to maintain the platform, at least initially, - and help test the platform for future releases. - -With these requirements met we should be able to merge the platform into Tock -relatively quickly. In the pull request to add the platform, you should add this -checklist: - -```md -### New Platform Checklist - -- [ ] Hardware is widely available. -- [ ] I can support the platform, which includes release testing for the platform, at least initially. -- Basic features are implemented: - - [ ] `Console`, including `debug!()` and userspace `printf()`. - - [ ] Timers. - - [ ] GPIO with interrupts. -``` diff --git a/doc/Porting_v1_Capsules_to_v2.md b/doc/Porting_v1_Capsules_to_v2.md deleted file mode 100644 index 47858b7784..0000000000 --- a/doc/Porting_v1_Capsules_to_v2.md +++ /dev/null @@ -1,454 +0,0 @@ -Porting Tock 1.x Capsules to Tock 2.0 -============ - -This guide covers how to port Tock capsules from the 1.x system call API -to the 2.x system call API. It outlines how the API has changed and -gives code examples. - - - - - -- [Overview](#overview) -- [Tock 2.0 System Call API](#tock-20-system-call-api) - * [`SyscallDriver`](#syscalldriver) -- [Porting Capsules and Example Code](#porting-capsules-and-example-code) - * [Examples of command and `CommandResult`](#examples-of-command-and-commandresult) - + [ReturnCode versus ErrorCode](#returncode-versus-errorcode) - * [Examples of `allow_readwrite` and `allow_readonly`](#examples-of-allow_readwrite-and-allow_readonly) - * [The new subscription mechanism](#the-new-subscription-mechanism) - * [Using `ReadOnlyProcessBuffer` and `ReadWriteProcessBuffer`: `console`](#using-readonlyprocessbuffer-and-readwriteprocessbuffer-console) - * [Using `ReadOnlyProcessBuffer` and `ReadWriteProcessBuffer`: `spi_controller`](#using-readonlyprocessbuffer-and-readwriteprocessbuffer-spi_controller) - - - -Overview --------- - -Version 2 of the Tock operating system changes the system call API and -ABI in several ways. This document describes the changes and their -implications to capsule implementations. It gives guidance on how to -port a capsule from Tock 1.x to 2.0. - -Tock 2.0 System Call API -------------- - -The Tock system call API is implemented in the `Driver` trait. Tock -2.0 updates this trait to be more precise and correctly support Rust's -memory semantics. - -### `SyscallDriver` - -This is the signature for the 2.0 `Driver` trait: - -```rust -pub trait SyscallDriver { - fn command(&self, which: usize, r2: usize, r3: usize, caller_id: ProcessId) -> CommandResult { - CommandResult::failure(ErrorCode::NOSUPPORT) - } - - fn allow_readwrite( - &self, - process_id: ProcessId, - allow_num: usize, - buffer: ReadWriteProcessBuffer, - ) -> Result { - Err((slice, ErrorCode::NOSUPPORT)) - } - - fn allow_readonly( - &self, - process_id: ProcessId, - allow_num: usize, - buffer: ReadOnlyProcessBuffer, - ) -> Result { - Err((slice, ErrorCode::NOSUPPORT)) - } - - fn allocate_grant(&self, appid: ProcessId) -> Result<(), crate::process::Error>; -} -``` - - -The first thing to note is that there are now two versions of the old `allow` -method: one for a read/write buffer and one for a read-only buffer. They -pass different types of slices. - -The second thing to note is that the two methods that pass pointers, -`allow_readwrite` and `allow_readonly`, return a `Result`. -The success case (`Ok`) returns a pointer back in the form of -an application slice. The failure case (`Err`) returns the same structure -back but also has an `ErrorCode`. - -These two methods follow a swapping calling convention: you pass in -a pointer and get one back. If the call fails, you get back the one -you passed in. If the call succeeds, you get back the one the capsule -previously had. That is, you call `allow_readwrite` with an -application slice A and it succeeds, then the next successful call to -`allow_readwrite` will return A. - -These swapping semantics allow the kernel to maintain an invariant -that there is only one instance of a particular application slice at -any time. Since an application slice represents a region of -application memory, having two objects representing the same region of -memory violates Rust's memory guarantees. When the scheduler calls -`allow_readwrite`, `allow_readonly` or `subscribe`, it moves the -application slice or callback into the capsule. The capsule, in turn, -moves the previous one out. - -The `command` method behaves differently, because commands only -operate on values, not pointers. Each command has its own arguments and -number of return types. This is encapsulated within `CommandResult`. - -The third thing to note is that there is no longer a `subscribe()` method. This -has been removed and instead all upcalls are managed entirely by the kernel. -Scheduling an upcall is now done with a provided object from entering a grant. - -The fourth thing to note is the new `allocate_grant()` method. This allows the -kernel to request that a capsule enters its grant region so that it is allocated -for the specific process. This should be implemented with a roughly boilerplate -implementation [described below](#the-new-subscription-mechanism). - -Porting Capsules and Example Code -------------------- - -The major change you'll see in porting your code is that capsule logic -becomes simpler: `Options` have been replaced by structures, and -there's a basic structure to swapping application slices. - - -### Examples of command and `CommandResult` - -The LED capsule implements only commands, so it provides a very simple -example of what commands look like. - -```rust - fn command(&self, command_num: usize, data: usize, _: usize, _: ProcessId) -> CommandResult { - self.leds - .map(|leds| { - match command_num { -... - // on - 1 => { - if data >= leds.len() { - CommandResult::failure(ErrorCode::INVAL) /* led out of range */ - } else { - leds[data].on(); - CommandResult::success() - } - }, - -``` - -The capsule dispatches on the command number. It uses the first -argument, `data`, as which LED to turn activate. It then returns -either a `CommandResult::Success` (generated with -`CommandResult::success()`) or a `CommandResult::Failure` (generated -with `CommandResult::failure()`). - -A `CommandResult` is a wrapper around a `GenericSyscallReturnValue`, -constraining it to the versions of `GenericSyscallReturnValue` that -can be returned by a command. - -Here is a slightly more complex implementation of `command`, from the -`console` capsule. - -```rust - fn command(&self, cmd_num: usize, arg1: usize, _: usize, appid: ProcessId) -> CommandResult{ - let res = match cmd_num { - 0 => Ok(Ok(())), - 1 => { // putstr - let len = arg1; - self.apps.enter(appid, |app, _| { - self.send_new(appid, app, len) - }).map_err(ErrorCode::from) - }, - 2 => { // getnstr - let len = arg1; - self.apps.enter(appid, |app, _| { - self.receive_new(appid, app, len) - }).map_err(ErrorCode::from) - }, - 3 => { // Abort RX - self.uart.receive_abort(); - Ok(Ok(())) - } - _ => Err(ErrorCode::NOSUPPORT) - }; - match res { - Ok(r) => { - CommandResult::from(r), - }, - Err(e) => CommandResult::failure(e) - } - } -``` - -This implementation is more complex because it uses a grant region -that stores per-process state. `Grant::enter` returns a -`Result, grant::Error>`. An outer `Err` return type means the -grant could not be entered successfully and the closure was not invoked: -this returns what grant error occurred. An `Ok` return type means the -closure was executed, but it is possible that an error occurred during -its execution. So there are three cases: - - - Ok(Ok(())) - - Ok(Err(ErrorCode:: error cases)) - - Err(grant::Error) - -The bottom `match` statement separates these two. In the `Ok()` case, -it checks whether the inner Result contains an `Err(ErrorCode)`. -If not (`Err`), this means it was a success, and the result was a -success, so it returns a `CommandResult::Success`. If it can be converted -into an error code, or if the grant produced an error, it returns a -`CommandResult::Failure`. - -One of the requirements of commands in 2.0 is that each individual -`command_num` have a single failure return type and a single success -return size. This means that for a given `command_num`, it is not allowed -for it to sometimes return `CommandResult::Success` and other times return -`Command::SuccessWithValue`, as these are different sizes. As part of easing -this transition, Tock 2.0 removed the `SuccessWithValue` variant of -`ReturnCode`, and then later in the transition removed `ReturnCode` entirely, -replacing all uses of `ReturnCode` with `Result<(), ErrorCode>`. - -If, while porting, you encounter a construction of `ReturnCode::SuccessWithValue{v}` -in `command()` for an out-of-tree capsule, replace it with a construction of -`CommandResult::success_u32(v)`, and make sure that it is impossible for that -command_num to return `CommandResult::Success` in any other scenario. - -#### ReturnCode versus ErrorCode - -Because the new system call ABI explicitly distinguishes failures and -successes, it replaces `ReturnCode` with `ErrorCode` to denote which -error in failure cases. `ErrorCode` is simply `ReturnCode` without any -success cases, and with names that remove the leading E since it's -obvious they are an error: `ErrorCode::FAIL` is the equivalent of -`ReturnCode::EFAIL`. `ReturnCode` is still used in the kernel, -but may be deprecated in time. - -### Examples of `allow_readwrite` and `allow_readonly` - -Because `ReadWriteProcessBuffer` and `ReadOnlyProcessBuffer` represent access to -userspace memory, the kernel tightly constrains how these objects -are constructed and passed. They do not implement `Copy` or `Clone`, -so only one instance of these objects exists in the kernel at any -time. - -Note that `console` has one `ReadOnlyProcessBuffer` for printing/`putnstr` -and one `ReadWriteProcessBuffer` for reading/`getnstr`. Here is a -sample implementation of `allow_readwrite` for the `console` capsule: - -```rust -pub struct App { - write_buffer: ReadOnlyProcessBuffer, -... - fn allow_readonly( - &self, - process_id: ProcessId, - allow_num: usize, - mut buffer: ReadOnlyProcessBuffer, - ) -> Result { - let res = match allow_num { - 1 => self - .apps - .enter(appid, |process_id, _| { - mem::swap(&mut process_id.write_buffer, &mut buffer); - }) - .map_err(ErrorCode::from), - _ => Err(ErrorCode::NOSUPPORT), - }; - - if let Err(e) = res { - Err((slice, e)) - } else { - Ok(slice) - } - } -``` - - -The implementation is quite simple: if there is a valid grant region, the -method swaps the passed `ReadOnlyProcessBuffer` and the one in the `App` region, -returning the one that was in the app region. It then returns `slice`, -which is either the passed slice or the swapped out one. - -### The new subscription mechanism - -Tock 2.0 introduces a guarantee for the subscribe syscall that for every unique -subscribe (i.e. `(driver_num, subscribe_num)` tuple), userspace will be returned -the previously subscribe upcall (or null if this is the first subscription). -This guarantee means that once an upcall is returned, the kernel will never -schedule the upcall again (unless it is re-subscribed in the future), and -userspace can deallocate the upcall function if it so chooses. - -Providing this guarantee necessitates changes to the capsule interface for -declaring and using upcalls. To declare upcalls, a capsule now provides the -number of upcalls as a templated value on `Grant`. - -```rust -struct capsule { - ... - apps: Grant, - ... -} -``` - -The second parameter tells the kernel how many upcalls to save. Capsules no -longer can store an object of type `Upcall` in their grant region. - -To ensure that the kernel can store the upcalls, a capsule must implement the -`allocate_grant()` method. The typical implementation looks like: - -```rust -fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::procs::Error> { - self.apps.enter(processid, |_, _| {}) -} -``` - -Finally to schedule an upcall any calls to `app.upcall.schedule()` should be -replaced with code like: - -```rust -self.apps.enter(appid, |app, upcalls| { - upcalls.schedule_upcall(upcall_number, (r0, r1, r2)); -}); -``` - -The parameter `upcall_number` matches the `subscribe_num` the process used with -the subscribe syscall. - - - - -### Using `ReadOnlyProcessBuffer` and `ReadWriteProcessBuffer`: `console` - -One key change in the Tock 2.0 API is explicitly acknowledging that -application slices may disappear at any time. For example, if a process -passes a slice into the kernel, it can later swap it out with a later -allow call. Similarly, application grants may disappear at any time. - -This means that `ReadWriteProcessBuffer` and `ReadOnlyProcessBuffer` now -do not allow you to obtain their pointers and lengths. Instead, -they provide a `map_or` method. This is how `console` uses this, -for example, to copy process data into its write buffer and -call the underlying `transmit_buffer`: - -```rust - fn send(&self, process_id: ProcessId, app: &mut App) { - if self.tx_in_progress.is_none() { - self.tx_in_progress.set(process_id); - self.tx_buffer.take().map(|buffer| { - let len = app.write_buffer.map_or(0, |data| data.len()); - if app.write_remaining > len { - // A slice has changed under us and is now smaller than - // what we need to write -- just write what we can. - app.write_remaining = len; - } - let transaction_len = app.write_buffer.map_or(0, |data| { - for (i, c) in data[data.len() - app.write_remaining..data.len()] - .iter() - .enumerate() - { - if buffer.len() <= i { - return i; - } - buffer[i] = *c; - } - app.write_remaining - }); - - app.write_remaining -= transaction_len; - let (_err, _opt) = self.uart.transmit_buffer(buffer, transaction_len); - }); - } else { - app.pending_write = true; - } - } -``` - -Note that the implementation looks at the length of the slice: it doesn't copy -it out into grant state. If a slice was suddenly truncated, it checks and -adjust the amount it has written. - -### Using `ReadOnlyProcessBuffer` and `ReadWriteProcessBuffer`: `spi_controller` - -This is a second example, taken from `spi_controller`. Because SPI transfers -are bidirectional, there is an RX buffer and a TX buffer. However, a client -can ignore what it receives, and only pass a TX buffer if it wants: the RX -buffer can be zero length. As with other bus transfers, the SPI driver -needs to handle the case when its buffers change in length under it. -For example, a client may make the following calls: - - 1. `allow_readwrite(rx_buf, 200)` - 2. `allow_readonly(tx_buf, 200)` - 3. `command(SPI_TRANSFER, 200)` - 4. (after some time, while transfer is ongoing) `allow_readonly(tx_buf2, 100)` - -Because the underlying SPI tranfer typically uses DMA, the buffer -passed to the peripheral driver is `static`. The `spi_controller` has -fixed-size static buffers. It performs a transfer by copying -application slice data into/from these buffers. A very long -application transfer may be broken into multiple low-level transfers. - -If a transfer is smaller than the static buffer, it is simple: -`spi_controller` copies the application slice data into its static -transmit buffer and starts the transfer. If the process rescinds the -buffer, it doesn't matter, as the capsule has the data. Similarly, the -presence of a receive application slice only matters when the transfer -completes, and the capsule decides whether to copy what it received out. - -The principal complexity is when the buffers change during a low-level -transfer and then the capsule needs to figure out whether to continue -with a subsequent low-level transfer or finish the operation. The code -needs to be careful to not access past the end of a slice and cause a -kernel panic. - -The code looks like this: - -```rust - // Assumes checks for busy/etc. already done - // Updates app.index to be index + length of op - fn do_next_read_write(&self, app: &mut App) { - let write_len = self.kernel_write.map_or(0, |kwbuf| { - let mut start = app.index; - let tmp_len = app.app_write.map_or(0, |src| { - let len = cmp::min(app.len - start, self.kernel_len.get()); - let end = cmp::min(start + len, src.len()); - start = cmp::min(start, end); - - for (i, c) in src.as_ref()[start..end].iter().enumerate() { - kwbuf[i] = *c; - } - end - start - }); - app.index = start + tmp_len; - tmp_len - }); - self.spi_master.read_write_bytes( - self.kernel_write.take().unwrap(), - self.kernel_read.take(), - write_len, - ); - } -``` - - -The capsule keeps track of its current write position with `app.index`. This -points to the first byte of untransmitted data. When a transfer starts -in response to a system call, the capsule checks that the requested length -of the transfer is not longer than the length of the transmit buffer, and -also that the receive buffer is either zero or at least as long. The -total length of a transfer is stored in `app.len`. - -But if the transmit buffer is swapped during a transfer, it may be -shorter than `app.index`. In the above code, the variable `len` stores -the desired length of the low-level transfer: it's the minimum of data -remaining in the transfer and the size of the low-level static buffer. -The variable `end` stores the index of the last byte that can be -safely transmitted: it is the minimum of the low-level transfer end -(`start` + `len`) and the length of the application slice -(`src.len()`). Note that `end` can be smaller than `start` if -the application slice is shorter than the current write position. -To handle this case, `start` is set to be the minimum of `start` and -`end`: the transfer will be of length zero. diff --git a/doc/README.md b/doc/README.md index 06251b3eb7..cc8b44cb62 100644 --- a/doc/README.md +++ b/doc/README.md @@ -1,46 +1,29 @@ Tock Documentation ================== -Here you can find guides on how Tock works and its [internal -interfaces](reference). For short tutorials and longer courses on how to use -Tock, see the [Tock OS Book](https://book.tockos.org). +General kernel documentation is in the [Tock Book](https://book.tockos.org/doc). +Information about Tock policies and development practices is here. This folder +also contains documentation on [syscall interfaces](reference). -Tock Guides ------------ +For short tutorials and longer courses on how to use Tock, see the [Tock OS +Book](https://book.tockos.org). -### Overview and Design of Tock -- **[Overview](Overview.md)** - Overview of the OS and this repository. -- **[Design](Design.md)** - Design of the Tock primitives that make safety and security possible. -- **[Threat Model](threat_model/README.md)** - Detailed description of Tock's security properties. - -### Tock Implementation -- **[Lifetimes](Lifetimes.md)** - How Rust lifetimes are used in Tock. -- **[Mutable References](Mutable_References.md)** - How Tock safely shares resources between components. -- **[Soundness](Soundness.md)** - How Tock safely uses unsafe code. -- **[Compilation](Compilation.md)** - How the kernel and applications are compiled. -- **[Tock Binary Format](TockBinaryFormat.md)** - How Tock application binaries are specified. -- **[Memory Layout](Memory_Layout.md)** - How memory is divided for Tock. -- **[Memory Isolation](Memory_Isolation.md)** - How memory is isolated in Tock. -- **[Registers](../libraries/tock-register-interface/README.md)** - How memory-mapped registers are handled in Tock. -- **[Startup](Startup.md)** - What happens when Tock boots. -- **[Syscalls](Syscalls.md)** - Kernel/Userland abstraction. -- **[Userland](Userland.md)** - Description of userland applications. -- **[Networking Stack](Networking_Stack.md)** - Design of the networking stack in Tock. -- **[Configuration](Configuration.md)** - Configuration options for the kernel. +Tock Policies +------------- ### Interface Details - **[Syscall Interfaces](syscalls)** - API between userland and the kernel. - **[Internal Kernel Interfaces](reference)** - Hardware Interface Layers (HILs) for kernel components. -### Tock Setup and Usage +### Tock Setup and Development - **[Getting Started](Getting_Started.md)** - Installing the Tock toolchain and programming hardware. -- **[Porting Tock](Porting.md)** - Guide to add new platforms. +- **[Repository Structure](Repository.md)** - How the tock/ repo is organized. +- **[Nested Boards](NestedBoards.md)** - How Tock supports nesting board platforms. - **[Out of Tree Boards](OutOfTree.md)** - Best practices for maintaining boards not in Tock master. -- **[Debugging Help](debugging)** - Guides for various debugging techniques. - **[Style](Style.md)** - Stylistic aspects of Tock code. -- **[Code Size](CodeSize.md)** - Guide for how to write code and configure Tock to reduce code size. +- **[External Dependencies](ExternalDependencies.md)** - Policy for including external dependencies. ### Management of Tock - **[Working Groups](wg)** - Development groups for specific aspects of Tock. - **[Code Review Process](CodeReview.md)** - Process for pull request reviews. -- **[Tock Management](Management.md)** - Management processes for Tock, including releases. +- **[Tock Management](Maintenance.md)** - Management processes for Tock, including releases. diff --git a/doc/Repository.md b/doc/Repository.md new file mode 100644 index 0000000000..725333d4c7 --- /dev/null +++ b/doc/Repository.md @@ -0,0 +1,51 @@ +# Tock Repository + +Tock has several principal code directories. + +- **arch**: stores architecture-specific code. I.e., code that +is Cortex-M0 and Cortex-M4 specific. This includes code for performing +context switches and making system calls (trapping from user code to +kernel code). + +- **boards**: contains code for specific Tock platforms, such as +the imix, the Hail, and the nrf52dk. This is typically the structure +that defines all of the capsules the kernel has, the code to configure the +MCU's IO pins into the proper states, initializing the kernel and loading +processes. The principal file in this directory is `main.rs`, and the +principal initialization function is `main` (which executes when the MCU +resets after RAM has been initialized). The board code also defines how system +call device identifiers map to capsules, in the `with_driver` function. + +- **capsules**: contains MCU-independent kernel extensions that +can build on top of chip-specific implementations of particular peripherals. +Some capsules provide system calls. For example, the `spi` module in capsules +builds on top of a chip's SPI implementation to provide system calls on +top of it. + +- **chips**: contains microcontroller-specific code, such as the +implementations of SPI, I2C, GPIO, UART, and other microcontroller-specific +code. The distinction between chips and boards is the difference between +a microcontroller and a full platform. For example, many microcontrollers +have multiple UARTs. Which UART is the principal way to communicate with +Tock, or which is used to control another chip, is defined by how the chip +is placed on board and which pins are exposed. So a chip provides the UART +implementation, but a board defines which UART is used for what. + +- **doc**: contains the documentation for Tock, including +specifications for internal interfaces and tutorials. + +- **kernel**: contains microcontroller-independent kernel code, +such as the scheduler, processes, and memory management. This directory +and arch are where all core kernel code reside. + +- **libraries**: contains libraries that we use internally and share +externally. Several primitives have been created for Tock that we think could +also be useful to other projects. This is a location where each crate is +located. + +- **tools**: contains associated tools to help in compilation and +code maintenance, such as checking code formatting, converting binaries, +and build scripts. + +- **vagrant**: contains information on how to get Tock running in a +virtual machine-esque environment. diff --git a/doc/Soundness.md b/doc/Soundness.md deleted file mode 100644 index eeac90730e..0000000000 --- a/doc/Soundness.md +++ /dev/null @@ -1,175 +0,0 @@ -Soundness and Unsafe Issues ---------------------------- - -An operating system necessarily must use unsafe code. This document explains -the rationale behind some of the key mechanisms in Tock that do use unsafe code -but should still preserve safety in the overall OS. - - - - - -- [`static_init!`](#static_init) - * [Use](#use) - * [Soundness](#soundness) - * [Alternatives](#alternatives) -- [Capabilities: Restricting Access to Certain Functions and Operations](#capabilities-restricting-access-to-certain-functions-and-operations) - * [Capability Examples](#capability-examples) - - - -## `static_init!` - -The "type" of `static_init!` is basically: - -```rust -T => (fn() -> T) -> &'static mut T -``` - -Meaning that given a function that returns something of type `T`, `static_init!` -returns a mutable reference to `T` with static lifetime. - -This is effectively meant to be equivalent to declaring a mutable static -variable: - -```rust -static mut MY_VAR: SomeT = SomeT::const_constructor(); -``` - -Then creating a reference to it: - -```rust -let my_ref: &'static mut = &mut MY_VAR; -``` - -However, the rvalue in static declarations must be `const` (because Rust doesn't -have pre-initialization sections). So `static_init!` basically allows static -variables that have non-const initializers. - -Note that in both of these cases, the caller must wrap the calls in `unsafe` -since references a mutable variable is unsafe (due to aliasing rules). - -### Use - -`static_init!` is used in Tock to initialize capsules, which will eventually -reference each other. In all cases, these references are immutable. It is -important for these to be statically allocated for two reasons. First, it helps -surface memory pressure issues at link time (if they are allocated on the stack, -they won't trivially show up as out-of-memory link errors if the stack isn't -sized properly). Second, the lifetimes of mutually-dependent capsules needs to -be equal, and `'static` is a convenient way of achieving this. - -However, in a few cases, it is useful to start with a mutable reference in order -to enforce _who_ can make certain calls. For example, setting up buffers in the -SPI driver is, for practical reasons, deferred until after construction but we -would like to enforce that it can only be called by the platform initialization -function (before `main` starts). This is enforced because all references after -the platform is setup are immutable, and the `config_buffers` method takes an -`&mut self` (_Note: it looks like this is not strictly necessary, so maybe not a -big deal if we can't do this_). - -### Soundness - -The thing that would make the use of `static_init!` unsafe is if it was used to -create aliases to mutable references. The fact that it returns an `&'static mut` -is a red flag, so it bears explanation why I think this is OK. - -Just as with any `&mut`, as soon as it is reborrowed it can no longer be used. -What we do in Tock, specifically, is use it mutably in some cases immediately -after calling `static_init!`, then reborrow it immutably to pass into capsules. -If a particular capsule happened to treat accept an `&mut`, the compiler would -try to move the reference and it would either fail that call (if it's already -reborrowed immutably elsewhere) or disallow further reborrows. Note that this is -fine if it is indeed not used as a shared reference (although I don't think we -have examples of that use). - -It is important, though, that the same code calling `static_init!` is not -executed twice. This creates two major issues. First, it could technically -result in multiple mutable references. Second, it would run the constructor -twice, which may create other soundness or functional issues with existing -references to the same memory. I believe this is not different that code that -takes a mutable reference to a static variable. Again, importantly both cases -must be wrapped with `unsafe`. - -### Alternatives - -It seems technically possible to return an immutable static reference from -`static_init!` instead. It would require a bit of code changes, and wouldn't -allow us to restrict certain capsule methods to initialization, but may not be a -particularly big deal. - -Also, something something static variables of type `Option` everywhere (ugh... -but maybe reasonable). - - -## Capabilities: Restricting Access to Certain Functions and Operations - -Certain operations and functions, particularly those in the kernel crate, are -not "unsafe" from a language perspective, but are unsafe from an isolation and -system operation perspective. For example, restarting a process, conceptually, -does not violate type or memory safety (even though the specific implementation -in Tock does), but it would violate overall system safety if any code in the -kernel could restart any arbitrary process. Therefore, Tock must be careful with -how it provides a function like `restart_process()`, and, in particular, must -not allow capsules, which are untrusted code that must be sandboxed by Rust, to -have access to the `restart_process()` function. - -Luckily, Rust provides a primitive for doing this restriction: use of the -`unsafe` keyword. Any function marked as `unsafe` can only be called from a -different `unsafe` function or from an `unsafe` block. Therefore, by removing -the ability to define an `unsafe` block, using the `#![forbid(unsafe_code)]` -attribute in a crate, all modules in that crate cannot call any functions marked -with `unsafe`. In the case of Tock, the capsules crate is marked with this -attribute, and therefore all capsules cannot use `unsafe` functions. While this -approach is effective, it is very coarse-grained: it provides either access to -all `unsafe` functions or none. To provide more nuanced control, Tock includes -a mechanism called Capabilities. - -Capabilities are essentially zero-memory objects that are required to call -certain functions. Abstractly, restricted functions, like `restart_process()`, -would require that the caller has a certain capability: - - restart_process(process_id: usize, capability: ProcessRestartCapability) {} - -Any attempt to call that function without possessing that capability would -result in code that does not compile. To prevent unauthorized uses of -capabilities, capabilities can only be created by trusted code. In Tock, this is -implemented by defining capabilities as unsafe traits, which can only be -implemented for an object by code capable of calling `unsafe`. Therefore, code -in the untrusted capsules crate cannot generate a capability on its own, and -instead must be passed the capability by module in a different crate. - -Capabilities can be defined for very broad purposes or very narrowly, and code -can "request" multiple capabilities. Multiple capabilities in Tock can be passed -by implementing multiple capability traits for a single object. - -### Capability Examples - -1. One example of how capabilities are useful in Tock is with loading processes. - Loading processes is left as a responsibility of the board, since a board may - choose to handle its processes in a certain way, or not support userland - processes at all. However, the kernel crate provides a helpful function - called `load_processes()` that provides the Tock standard method for finding - and loading processes. This function is defined in the kernel crate so that - all Tock boards can share it, which necessitates that the function be made - public. This has the effect that _all_ modules with access to the kernel - crate can call `load_processes()`, even though calling it twice would lead to - unwanted behavior. One approach is to mark the function as `unsafe`, so only - trusted code can call it. This is effective, but not explicit, and conflates - language-level safety with system operation-level safety. By instead - requiring that the caller of `load_processes()` has a certain capability, the - expectations of the caller are more explicit, and the unsafe function does - not have to be repurposed. - -2. A similar example is a function like `restart_all_processes()` which causes - all processes on the board to enter a fault state and restart from their - original `_start` point with all grants removed. Again, this is a function - that could violate the system-level goals, but could be very useful in - certain situations or for debugging grant cleanup when apps fail. Unlike - `load_processes()`, however, it might make sense for a capsule to be able to - call `restart_all_processes()`, in response to a certain event or to act as a - watchdog. In that case, restricting access by marking it as `unsafe` will not - work: capsules cannot call unsafe code. By using capabilities, only a caller - with the correct capability can call `restart_all_processes()`, and - individual boards can be very explicit about which capsules they grant which - capabilities. diff --git a/doc/Startup.md b/doc/Startup.md deleted file mode 100644 index adaeed1fa9..0000000000 --- a/doc/Startup.md +++ /dev/null @@ -1,186 +0,0 @@ -Tock Startup -============ - -This document walks through how all of the components of Tock start up. - - - - - -- [Optional Bootloader](#optional-bootloader) -- [Tock first instructions](#tock-first-instructions) - * [ARM Vector Table and IRQ table](#arm-vector-table-and-irq-table) - * [RISC-V](#risc-v) -- [Reset Handler](#reset-handler) - * [Memory Initialization](#memory-initialization) - * [RISC-V Trap setup](#risc-v-trap-setup) - * [MCU Setup](#mcu-setup) - * [Peripheral and Capsule Initialization](#peripheral-and-capsule-initialization) -- [Application Startup](#application-startup) -- [Scheduler Execution](#scheduler-execution) - - - -When a microcontroller boots (or resets, or services an interrupt) it loads an -address for a function from a table indexed by interrupt type known as the -_vector table_. The location of the vector table in memory is chip-specific, -thus it is placed in a special section for linking. - -Cortex-M microcontrollers expect a vector table to be at address 0x00000000. -This can either be a software bootloader or the Tock kernel itself. - -RISC-V gives hardware designers a great deal of design freedom for how -booting works. Typically, after coming out of reset, a RISC-V processor -will start executing out of ROM but this may be configurable. The HiFive1 -board, for example, supports booting out ROM, One-Time programmable (OTP) -memory or a QSPI flash controller. - -## Optional Bootloader - -Many Tock boards (including Hail and imix) use a software bootloader that -executes when the MCU first boots. The bootloader provides a way to talk to the -chip over serial and to load new code, as well as potentially other -administrative tasks. When the bootloader has finished, it tells the MCU that -the vector table has moved (to a known address), and then jumps to a new -address. - -## Tock first instructions - -### ARM Vector Table and IRQ table - -On ARM chips, Tock splits the vector table into two sections, `.vectors` which -hold the first 16 entries, common to all ARM cores, and `.irqs`, which is -appended to the end and holds chip-specific interrupts. - -In the source code then, the vector table will appear as an array that is -marked to be placed into the `.vectors` section. - -In Rust, a vector table will look something like this: -```rust -#[link_section=".vectors"] -#[used] // Ensures that the symbol is kept until the final binary -pub static BASE_VECTORS: [unsafe extern fn(); 16] = [ - _estack, // Initial stack pointer value - tock_kernel_reset_handler, // Tock's reset handler function - /* NMI */ unhandled_interrupt, // Generic handler function - ... -``` - -In C, a vector table will look something like this: - -```c -__attribute__ ((section(".vectors"))) -interrupt_function_t interrupt_table[] = { - (interrupt_function_t) (&_estack), - tock_kernel_reset_handler, - NMI_Handler, -``` - -At the time of this writing (November 2018), typical chips (like the `sam4l` and -`nrf52`) use the same handler for all interrupts, and look something like: - -```rust -#[link_section = ".vectors"] -#[used] // Ensures that the symbol is kept until the final binary -pub static IRQS: [unsafe extern "C" fn(); 80] = [generic_isr; 80]; -``` - -### RISC-V - -All RISC-V boards are linked to run the `_start` function as the first -function that gets run before jumping to `main`. This is currently -inline assembly as of this writing: - -```rust -#[cfg(all(target_arch = "riscv32", target_os = "none"))] -#[link_section = ".riscv.start"] -#[export_name = "_start"] -#[naked] -pub extern "C" fn _start() { - unsafe { - asm! (" - -``` - -## Reset Handler - -On boot, the MCU calls the reset handler function defined in vector -table. In Tock, the implementation of the reset handler function is -platform-specific and defined in `boards//src/main.rs` for each -board. - -### Memory Initialization - -The first operation the reset handler does is setup the kernel's memory by -copying it from flash. For the SAM4L, this is in the `init()` function in -`chips/sam4l/src/lib.rs`. - -### RISC-V Trap setup - -The `mtvec` register needs to be set on RISC-V to handle traps. Setting -of the vectors is handled by chip specific functions. The common RISC-V trap -handler is `_start_trap`, defined in `arch/rv32i/src/lib.rs`. - -### MCU Setup - -Any normal MCU initialization is typically handled next. This includes -things like enabling the correct clocks or setting up DMA channels. - -### Peripheral and Capsule Initialization - -After the MCU is set up, `main` initializes peripherals and -capsules. Peripherals are on-chip subsystems, such as UARTs, ADCs, and -SPI buses; they are chip-specific code that read and write -memory-mapped I/O registers and are found in the corresponding `chips` -directory. While peripherals are chip-specific implementations, they -typically provide hardware-independent traits, called hardware -independent layer (HIL) traits, found in `kernel/src/hil`. - -Capsules are software abstractions and services; they are -chip-independent and found in the `capsules` directory. For example, -on the imix and hail platforms, the SAM4L SPI peripheral is -implemented in `chips/sam4l/src/spi.rs`, while the capsule that -virtualizes the SPI so multiple capsules can share it is in -`capsules/src/virtual_spi.rs`. This virtualizer can be -chip-independent because the chip-specific code implements the SPI HIL -(`kernel/src/hil/spi.rs`). The capsule that implements a system call -API to the SPI for processes is in `capsules/src/spi.rs`. - -Boards that initialize many peripherals and capsules use the `Component` -trait to encapsulate this complexity from `main`. The `Component` -trait (`kernel/src/component.rs`) encapsulates any initialization a -particular peripheral, capsule, or set of capsules need inside a -call to the function `finalize()`. Changing what the build of the kernel -includes involve changing just which Components are initialized, rather -than changing many lines of `main`. Components are typically -found in the `components` crate in the `/boards` folder, but may also be -board-specifc and found inside a `components` subdirectory of the board -directory, e.g. `boards/imix/src/imix_components`. - -## Application Startup - -Once the kernel components have been setup and initialized, the applications -must be loaded. This procedure essentially iterates over the processes stored in -flash, extracts and validates their Tock Binary Format header, and adds them to -an internal array of process structs. - -An example version of this loop is in `kernel/src/process.rs` as the -`load_processes()` function. After setting up pointers, it tries to create a -process from the starting address in flash and with a given amount of memory -remaining. If the header is validated, it tries to load the process into memory -and initialize all of the bookkeeping in the kernel associated with the process. -This can fail if the process needs more memory than is available on the chip. If -the process is successfully loaded the kernel importantly notes the address of -the application's entry function which is called when the process is started. - -The load process loop ends when the kernel runs out of statically allocated -memory to store processes in, available RAM for processes, or there is an -invalid TBF header in flash. - -## Scheduler Execution - -Tock provides a `Scheduler` trait that serves as an abstraction to allow for -plugging in different scheduling algorithms. Schedulers should be initialized -at the end of the reset handler. -The final thing that the reset handler must do is call `kernel.kernel_loop()`. -This starts the Tock scheduler and the main operation of the kernel. diff --git a/doc/Syscalls.md b/doc/Syscalls.md deleted file mode 100644 index a9a4a8990a..0000000000 --- a/doc/Syscalls.md +++ /dev/null @@ -1,313 +0,0 @@ -Syscalls -======== - -This document explains how [system -calls](https://en.wikipedia.org/wiki/System_call) work in Tock with regards -to both the kernel and applications. [TRD104](reference/trd104-syscalls.md) contains -the more formal specification -of the system call API and ABI for 32-bit systems. -This document describes the considerations behind the system call design. - - - -- [Overview of System Calls in Tock](#overview-of-system-calls-in-tock) -- [Process State](#process-state) -- [Startup](#startup) -- [System Call Invocation](#system-call-invocation) -- [The Context Switch](#the-context-switch) - * [Context Switch Interface](#context-switch-interface) - * [Cortex-M Architecture Details](#cortex-m-architecture-details) - * [RISC-V Architecture Details](#risc-v-architecture-details) -- [How System Calls Connect to Drivers](#how-system-calls-connect-to-drivers) -- [Error and Return Types](#error-and-return-types) - * [Naming Conventions](#naming-conventions) - * [Type Descriptions](#type-descriptions) -- [Allocated Driver Numbers](#allocated-driver-numbers) - - - -## Overview of System Calls in Tock - -System calls are the method used to send information from applications to the -kernel. Rather than directly calling a function in the kernel, applications -trigger a service call (`svc`) interrupt which causes a context switch to the -kernel. The kernel then uses the values in registers and the stack at the time -of the interrupt call to determine how to route the system call and which -driver function to call with which data values. - -Using system calls has three advantages. First, the act of triggering a service -call interrupt can be used to change the processor state. Rather than being in -unprivileged mode (as applications are run) and limited by the Memory -Protection Unit (MPU), after the service call the kernel switches to privileged -mode where it has full control of system resources (more detail on ARM -[processor -modes](http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0553a/CHDIGFCA.html)). - - -Second, context switching to the kernel allows it to do other resource handling -before returning to the application. This could include running other -applications, servicing queued upcalls, or many other activities. - -Finally, -and most importantly, using system calls allows applications to be built -independently from the kernel. The entire codebase of the kernel could change, -but as long as the system call interface remains identical, applications do not -even need to be recompiled to work on the platform. Applications, when -separated from the kernel, no longer need to be loaded at the same time as the -kernel. They could be uploaded at a later time, modified, and then have a new -version uploaded, all without modifying the kernel running on a platform. - -## Process State - -In Tock, a process can be in one of seven states: - -- **Running**: Normal operation. A Running process is eligible to be scheduled - for execution, although is subject to being paused by Tock to allow interrupt - handlers or other processes to run. During normal operation, a process remains - in the Running state until it explicitly yields. Upcalls from other kernel - operations are not delivered to Running processes (i.e. upcalls do not - interrupt processes), rather they are enqueued until the process yields. -- **Yielded**: Suspended operation. A Yielded process will not be scheduled by - Tock. Processes often yield while they are waiting for I/O or other operations - to complete and have no immediately useful work to do. Whenever the kernel - issues an upcall to a Yielded process, the process is transitioned to the - Running state. -- **Fault**: Erroneous operation. A Fault-ed process will not be scheduled by - Tock. Processes enter the Fault state by performing an illegal operation, such - as accessing memory outside of their address space. -- **Terminated** The process ended itself by calling the `Exit` system call and - the kernel has not restarted it. -- **Unstarted** The process has not yet started; this state is typically very - short-lived, between process loading and it started. However, in cases when - processes might be loaded for a long time without running, this state might be - long-lived. -- **StoppedRunning**, **StoppedYielded** These states correspond to a process - that was in either the Running or Yielded state but was then explicitly - stopped by the kernel (e.g., by the process console). A process in these - states will not be made runnable until it is restarted, at which point it will - continue execution where it was stopped. - -## Startup - -Upon process initialization, a single function call task is added to its -upcall queue. The function is determined by the ENTRY point in the process -TBF header (typically the `_start` symbol) and is passed the following -arguments in registers `r0` - `r3`: - - * r0: the base address of the process code - * r1: the base address of the processes allocated memory region - * r2: the total amount of memory in its region - * r3: the current process memory break - -## System Call Invocation - -A process invokes a system call by triggering a software interrupt that -transitions the microcontroller to supervisor/kernel mode. The exact -mechanism for this is architecture-specific. [TRD104](reference/trd104-syscalls.md) -specifies how userspace and the kernel pass values to each other for -CortexM and RISCV32I platforms. - -## The Context Switch - -Handling a context switch is one of the few pieces of architecture-specific -Tock code. The code is located -in `lib.rs` within the `arch/` folder under the appropriate architecture. As -this code deals with low-level functionality in the processor it is written in -assembly wrapped as Rust function calls. - -### Context Switch Interface - -The architecture crates (in the `/arch` folder) are responsible for implementing -the `UserspaceKernelBoundary` trait which defines the functions needed to allow the -kernel to correctly switch to userspace. These functions handle the -architecture-specific details of how the context switch occurs, such as which -registers are saved on the stack, where the stack pointer is stored, and how -data is passed for the Tock syscall interface. - -### Cortex-M Architecture Details - -Starting in the kernel before any application has been run but after the -process has been created, the kernel calls `switch_to_user`. This code sets up -registers for the application, including the PIC base register and the process -stack pointer, then triggers a service call interrupt with a call to `svc`. -The `svc` handler code automatically determines if the system desired a switch -to application or to kernel and sets the processor mode. Finally, the `svc` -handler returns, directing the PC to the entry point of the app. - -The application runs in unprivileged mode while executing. When it needs to use -a kernel resource it issues a syscall by running `svc` instruction. The -`svc_handler` determines that it should switch to the kernel from an app, sets -the processor mode to privileged, and returns. Since the stack has changed to -the kernel's stack pointer (rather than the process stack pointer), execution -returns to `switch_to_user` immediately after the `svc` that led to the -application starting. `switch_to_user` saves registers and returns to the kernel -so the system call can be processed. - -On the next `switch_to_user` call, the application will resume execution based -on the process stack pointer, which points to the instruction after the system -call that switched execution to the kernel. - -Syscalls may clobber userspace memory, as the kernel may write to buffers -previously given to it using Allow. The kernel will not clobber any userspace -registers except for the return value register (`r0`). However, Yield must be -treated as clobbering more registers, as it can call an upcall in userspace -before returning. This upcall can clobber r0-r3, r12, and lr. See [this -comment](https://github.com/tock/libtock-c/blob/f5004277ec88c2afe8f473a06b74aa2faba70d68/libtock/tock.c#L49) -in the libtock-c syscall code for more information about Yield. - -### RISC-V Architecture Details - -Tock assumes that a RISC-V platform that supports context switching has two -privilege modes: machine mode and user mode. - -The RISC-V architecture provides very lean support for context -switching, providing significant flexibility in software on how to -support context switches. The hardware guarantees the following will -happen during a context switch: when switching from kernel mode to -user mode by calling the `mret` instruction, the PC is set to the -value in the `mepc` CSR, and the privilege mode is set to the value in -the `MPP` bits of the `mstatus` CSR. When switching from user mode to -kernel mode using the `ecall` instruction, the PC of the `ecall` -instruction is saved to the `mepc` CSR, the correct bits are set in -the `mcause` CSR, and the privilege mode is restored to machine -mode. The kernel can store 32 bits of state in the `mscratch` CSR. - -Tock handles context switching using the following process. When -switching to userland, all register contents are saved to the kernel's -stack. Additionally, a pointer to a per-process struct of stored -process state and the PC of where in the kernel to resume executing -after the process switches back to kernel mode are stored to the -kernel's stack. Then, the PC of the process to start executing is put -into the `mepc` CSR, the kernel stack pointer is saved in `mscratch`, -and the previous contents of the app's registers from the per-process -stored state struct are copied back into the registers. Then `mret` is -called to switch to user mode and begin executing the app. - -An application calls a system call with the `ecall` instruction. This -causes the trap handler to execute. The trap handler checks -`mscratch`, and if the value is nonzero then it contains the stack -pointer of the kernel and this trap must have happened while the -system was executing an application. Then, the kernel stack pointer -from `mscratch` is used to find the pointer to the stored state -struct, and all process registers are saved. The trap handler also -saves the process PC from the `mepc` CSR and the `mcause` CSR. It then -loads the kernel address of where to resume the context switching code -to `mepc` and calls `mret` to exit the trap handler. Back in the -context switching code, the kernel restores its registers from its -stack. Then, using the contents of `mcause` the kernel decides why the -application stopped executing, and if it was a system call which one it is. -Returning the context switch reason ends the -context switching process. - -All values for the system call functions are passed in registers -`a0-a4`. No values are stored to the application stack. The return -value for system call is set in a0. In most system calls the kernel will not -clobber any userspace registers except for this return value register -(`a0`). However, the `yield()` system call results in a upcall executing -in the process. This can clobber all caller saved registers, as well -as the return address (`ra`) register. - -## How System Calls Connect to Drivers - -After a system call is made, the call is handled and routed by the Tock kernel -in [`sched.rs`](../kernel/src/kernel.rs) through a series of steps. - -1. For Command, Subscribe, Allow Read-Write, and Allow Read-Only system -call classes, the kernel calls a platform-defined system call filter -function. This function determines if the kernel should handle the -system call or not. Yield, Exit, and Memop system calls are not -filtered. This filter function allows the kernel to impose security -policies that limit which system calls a process might invoke. The -filter function takes the system call and which process issued the system call -to return a `Result<(), ErrorCode>` to signal if the system call should be -handled or if an error should be returned to the process. If the -filter function disallows the system call it returns `Err(ErrorCode)` and -the `ErrorCode` is provided to the process as the return code for the -system call. Otherwise, the system call proceeds. _The filter interface is -unstable and may be changed in the future._ - -2. The kernel scheduler loop handles the Exit and Yield system call classes. - -3. To handle Memop system calls, the scheduler loop invokes the `memop` module, -which implements the Memop class. - -4. Allow Read-Write, Allow Read-Only, Subscribe, and Command follow a -more complex execution path because are implemented by drivers. To -route these system calls, the scheduler loop calls a struct that -implements the `SyscallDriverLookup` trait. This trait has a `with_driver()` -function that the driver number as an argument and returns either a -reference to the corresponding driver or `None` if it is not -installed. The kernel uses the returned reference to call the -appropriate system call function on that driver with the remaining -system call arguments. - -An example board that implements the `SyscallDriverLookup` trait looks something like -this: - - ```rust - struct TestBoard { - console: &'static Console<'static, usart::USART>, - } - - impl SyscallDriverLookup for TestBoard { - fn with_driver(&self, driver_num: usize, f: F) -> R - where F: FnOnce(Option<&kernel::syscall::SyscallDriver>) -> R - { - - match driver_num { - 0 => f(Some(self.console)), // use capsules::console::DRIVER_NUM rather than 0 in real code - _ => f(None), - } - } - } - ``` - -`TestBoard` then supports one driver, the UART console, and maps it to driver -number 0. Any `command`, `subscribe`, and `allow` sycalls to driver number 0 -will get routed to the console, and all other driver numbers will return -`Err(ErrorCode::NODEVICE)`. - -## Error and Return Types - -Tock includes some defined types and conventions for errors and return values -between the kernel and userspace. - -### Naming Conventions - -- `*Code` (e.g. `ErrorCode`, `StatusCode`): These types are mappings between - numeric values and semantic meanings. These can always be encoded in a - `usize`. -- `*Return` (e.g. `SyscallReturn`): These are more complex return types that can - include arbitrary values, errors, or `*Code` types. - -### Type Descriptions - -- `*Code` Types: - - `ErrorCode`: A standard set of errors and their numeric representations in - Tock. This is used to represent errors for syscalls, and elsewhere in the - kernel. - - `StatusCode`: All errors in `ErrorCode` plus a Success value (represented by - 0). This is used to pass a success/error status between the kernel and - userspace. - - `StatusCode` is a pseudotype that is not actually defined as a concrete Rust - type. Instead, it is always encoded as a `usize`. Even though it is not a - concrete type, it is useful to be able to return to it conceptually, so we - give it the name `StatusCode`. - - The intended use of `StatusCode` is to convey success/failure to userspace - in upcalls. To try to keep things simple, we use the same numeric - representations in `StatusCode` as we do with `ErrorCode`. - -- `*Return` Types: - - `SyscallReturn`: The return type for a syscall. Includes whether the syscall - succeeded or failed, optionally additional data values, and in the case of - failure an `ErrorCode`. - -## Allocated Driver Numbers - -All documented drivers are in the [doc/syscalls](syscalls/README.md) folder. - -The `with_driver()` function takes an argument `driver_num` to identify the -driver. `driver_num` whose highest bit is set is private and can be used by -out-of-tree drivers. diff --git a/doc/TockBinaryFormat.md b/doc/TockBinaryFormat.md deleted file mode 100644 index d4ef918c60..0000000000 --- a/doc/TockBinaryFormat.md +++ /dev/null @@ -1,465 +0,0 @@ -# Tock Binary Format - - - - - -- [App Linked List](#app-linked-list) -- [Empty Tock Apps](#empty-tock-apps) -- [TBF Header](#tbf-header) - * [TBF Header Base](#tbf-header-base) - * [TLV Elements](#tlv-elements) - * [TLV Types](#tlv-types) - + [`1` Main](#1-main) - + [`2` Writeable Flash Region](#2-writeable-flash-region) - + [`3` Package Name](#3-package-name) - + [`5` Fixed Addresses](#5-fixed-addresses) - + [`6` Permissions](#6-permissions) - + [`7` Persistent ACL](#7-persistent-acl) - + [`8` Kernel Version](#8-kernel-version) -- [Code](#code) - - - -Tock process binaries are must be in the Tock Binary Format (TBF). A TBF -includes a header portion, which encodes meta-data about the process, followed -by a binary blob which is executed directly, followed by optional padding. - -``` -Tock App Binary: - -Start of app -> +-------------------+ - | TBF Header | - +-------------------+ - | Compiled app | - | binary | - | | - | | - +-------------------+ - | Optional padding | - +-------------------+ -``` - -The header is interpreted by the kernel (and other tools, like tockloader) to -understand important aspects of the app. In particular, the kernel must know -where in the application binary is the entry point that it should start -executing when running the app for the first time. - -After the header the app is free to include whatever binary data it wants, and -the format is completely up to the app. All support for relocations must be -handled by the app itself, for example. - -Finally, the app binary can be padded to a specific length. This is necessary -for MPU restrictions where length and starting points must be at powers of two. - -## App Linked List - -Apps in Tock create an effective linked list structure in flash. That is, the -start of the next app is immediately at the end of the previous app. Therefore, -the TBF header must specify the length of the app so that the kernel can find -the start of the next app. - -If there is a gap between apps an "empty app" can be inserted to keep the linked -list structure intact. - -Also, functionally Tock apps are sorted by size from longest to shortest. This -is to match MPU rules about alignment. - -## Empty Tock Apps - -An "app" need not contain any code. An app can be marked as disabled and -effectively act as padding between apps. - -## TBF Header - -The fields of the TBF header are as shown below. All fields in the header are -little-endian. - -```rust -struct TbfHeader { - version: u16, // Version of the Tock Binary Format (currently 2) - header_size: u16, // Number of bytes in the complete TBF header - total_size: u32, // Total padded size of the program image in bytes, including header - flags: u32, // Various flags associated with the application - checksum: u32, // XOR of all 4 byte words in the header, including existing optional structs - - // Optional structs. All optional structs start on a 4-byte boundary. - main: Option, - pic_options: Option, - name: Option, - flash_regions: Option, - fixed_address: Option, - permissions: Option, - persistent_acl: Option, -} - -// Identifiers for the optional header structs. -enum TbfHeaderTypes { - TbfHeaderMain = 1, - TbfHeaderWriteableFlashRegions = 2, - TbfHeaderPackageName = 3, - TbfHeaderPicOption1 = 4, - TbfHeaderFixedAddresses = 5, - TbfHeaderPermissions = 6, - TbfHeaderPersistent = 7, - TbfHeaderKernelVersion = 8, -} - -// Type-length-value header to identify each struct. -struct TbfHeaderTlv { - tipe: TbfHeaderTypes, // 16 bit specifier of which struct follows - // When highest bit of the 16 bit specifier is set - // it indicates out-of-tree (private) TLV entry - length: u16, // Number of bytes of the following struct -} - -// Main settings required for all apps. If this does not exist, the "app" is -// considered padding and used to insert an empty linked-list element into the -// app flash space. -struct TbfHeaderMain { - base: TbfHeaderTlv, - init_fn_offset: u32, // The function to call to start the application - protected_size: u32, // The number of bytes the application cannot write - minimum_ram_size: u32, // How much RAM the application is requesting -} - -// Optional package name for the app. -struct TbfHeaderPackageName { - base: TbfHeaderTlv, - package_name: [u8], // UTF-8 string of the application name -} - -// A defined flash region inside of the app's flash space. -struct TbfHeaderWriteableFlashRegion { - writeable_flash_region_offset: u32, - writeable_flash_region_size: u32, -} - -// One or more specially identified flash regions the app intends to write. -struct TbfHeaderWriteableFlashRegions { - base: TbfHeaderTlv, - writeable_flash_regions: [TbfHeaderWriteableFlashRegion], -} - -// Fixed and required addresses for process RAM and/or process flash. -struct TbfHeaderV2FixedAddresses { - base: TbfHeaderTlv, - start_process_ram: u32, - start_process_flash: u32, -} - -struct TbfHeaderDriverPermission { - driver_number: u32, - offset: u32, - allowed_commands: u64, -} - -// A list of permissions for this app -struct TbfHeaderV2Permissions { - base: TbfHeaderTlv, - length: u16, - perms: [TbfHeaderDriverPermission], -} - -// A list of persistent access permissions -struct TbfHeaderV2PersistentAcl { - base: TbfHeaderTlv, - write_id: u32, - read_length: u16, - read_ids: [u32], - access_length: u16, - access_ids: [u32], -} - -// Kernel Version -struct TbfHeaderV2KernelVersion { - base: TbfHeaderTlv, - major: u16, - minor: u16 -} -``` - -Since all headers are a multiple of four bytes, and all TLV structures must be a -multiple of four bytes, the entire TBF header will always be a multiple of four -bytes. - - -### TBF Header Base - -The TBF header contains a base header, followed by a sequence of -type-length-value encoded elements. All fields in both the base header and TLV -elements are little-endian. The base header is 16 bytes, and has 5 fields: - -``` -0 2 4 6 8 -+-------------+-------------+---------------------------+ -| Version | Header Size | Total Size | -+-------------+-------------+---------------------------+ -| Flags | Checksum | -+---------------------------+---------------------------+ -``` - - * `Version` a 16-bit unsigned integer specifying the TBF header version. - Always `2`. - * `Header Size` a 16-bit unsigned integer specifying the length of the - entire TBF header in bytes (including the base header and all TLV - elements). - * `Total Size` a 32-bit unsigned integer specifying the total size of the - TBF in bytes (including the header). - * `Flags` specifies properties of the process. - ``` - 3 2 1 0 - 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - | Reserved |S|E| - +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ - ``` - - - Bit 0 marks the process enabled. A `1` indicates the process is - enabled. Disabled processes will not be launched at startup. - - Bit 1 marks the process as sticky. A `1` indicates the process is - sticky. Sticky processes require additional confirmation to be erased. - For example, `tockloader` requires the `--force` flag erase them. This - is useful for services running as processes that should always be - available. - - Bits 2-31 are reserved and should be set to 0. - * `Checksum` the result of XORing each 4-byte word in the header, excluding - the word containing the checksum field itself. - -### TLV Elements - -The header is followed immediately by a sequence of TLV elements. TLV -elements are aligned to 4 bytes. If a TLV element size is not 4-byte aligned, it -will be padded with up to 3 bytes. Each element begins with a 16-bit type and -16-bit length followed by the element data: - -``` -0 2 4 -+-------------+-------------+-----...---+ -| Type | Length | Data | -+-------------+-------------+-----...---+ -``` - - * `Type` is a 16-bit unsigned integer specifying the element type. - * `Length` is a 16-bit unsigned integer specifying the size of the data field - in bytes. - * `Data` is the element specific data. The format for the `data` field is - determined by its `type`. - -### TLV Types - -TBF may contain arbitrary element types. To avoid type ID collisions -between elements defined by the Tock project and elements defined -out-of-tree, the ID space is partitioned into two segments. Type IDs -defined by the Tock project will have their high bit (bit 15) unset, -and type IDs defined out-of-tree should have their high bit set. - -#### `1` Main - -The `Main` element has three 32-bit fields: - -``` -0 2 4 6 8 -+-------------+-------------+---------------------------+ -| Type (1) | Length (12) | init_offset | -+-------------+-------------+---------------------------+ -| protected_size | min_ram_size | -+---------------------------+---------------------------+ -``` - - * `init_offset` the offset in bytes from the beginning of binary payload - (i.e. the actual application binary) that contains the first instruction to - execute (typically the `_start` symbol). - * `protected_size` the amount of flash, in bytes, after the header, to - prevent the process from writing to. - * `minimum_ram_size` the minimum amount of memory, in bytes, the process - needs. - -If the Main TLV header is not present, these values all default to `0`. - -#### `2` Writeable Flash Region - -`Writeable flash regions` indicate portions of the binary that the process -intends to mutate in flash. - -``` -0 2 4 6 8 -+-------------+-------------+---------------------------+ -| Type (2) | Length (8) | offset | -+-------------+-------------+-------------+-------------+ -| size | -+---------------------------+ -``` - - * `offset` the offset from the beginning of the binary of the writeable - region. - * `size` the size of the writeable region. - - -#### `3` Package Name - -The `Package name` specifies a unique name for the binary. Its only field is -an UTF-8 encoded package name. - -``` -0 2 4 -+-------------+-------------+----------...-+ -| Type (3) | Length | package_name | -+-------------+-------------+----------...-+ -``` - - * `package_name` is an UTF-8 encoded package name - -#### `5` Fixed Addresses - -`Fixed Addresses` allows processes to specify specific addresses they need for -flash and RAM. Tock supports position-independent apps, but not all apps are -position-independent. This allows the kernel (and other tools) to avoid loading -a non-position-independent binary at an incorrect location. - -``` -0 2 4 6 8 -+-------------+-------------+---------------------------+ -| Type (5) | Length (8) | ram_address | -+-------------+-------------+-------------+-------------+ -| flash_address | -+---------------------------+ -``` - - * `ram_address` the address in memory the process's memory address must start - at. If a fixed address is not required this should be set to `0xFFFFFFFF`. - * `flash_address` the address in flash that the process binary (not the - header) must be located at. This would match the value provided for flash to - the linker. If a fixed address is not required this should be set to - `0xFFFFFFFF`. - -#### `6` Permissions - -The `Permissions` section allows an app to specify driver permissions that it -is allowed to use. All driver syscalls that an app will use must be listed. The -list should not include drivers that are not being used by the app. - -The data is stored in the optional `TbfHeaderV2Permissions` field. This -includes an array of all the `perms`. - -``` -0 2 4 -+-------------+-------------+---------...--+ -| Type (6) | Length | perms | -+-------------+-------------+---------...--+ -``` - -The `perms` array is made up of a number of elements of -`TbfHeaderDriverPermission`. The length of the TLV can be used to determine -the number of array elements. The elements in `TbfHeaderDriverPermission` are -described below: - -```text -Driver Permission Structure: -0 2 4 6 8 -+-------------+-------------+---------------------------+ -| driver_number | offset | -+-------------+-------------+-------------+-------------+ -| allowed_commands | -+-------------------------------------------------------+ -``` - -* `driver_number` is the number of the driver that is allowed. This for example - could be `0x00000` to indicate that the `Alarm` syscalls are allowed. -* `allowed_commands` is a bit mask of the allowed commands. For example a value - of `0b0001` indicates that only command 0 is allowed. `0b0111` would indicate - that commands 2, 1 and 0 are all allowed. Note that this assumes `offset` is - 0, for more details on `offset` see below. -* The `offset` field in `TbfHeaderDriverPermission` indicates the offset of the - `allowed_commands` bitmask. All of the examples described in the paragraph - above assume an `offset` of 0. The `offset` field indicates the start of the - `allowed_commands` bitmask. The `offset` is multiple by 64 (the size of the - `allowed_commands` bitmask). For example an `offset` of 1 and a - `allowed_commands` value of `0b0001` indicates that command 64 is allowed. - -Subscribe and allow commands are always allowed as long as the specific -`driver_number` has been specified. If a `driver_number` has not been specified -for the capsule driver then `allow` and `subscribe` will be blocked. - -Multiple `TbfHeaderDriverPermission` with the same `driver_numer` can be -included, so long as no `offset` is repeated for a single driver. When -multiple `offset`s and `allowed_commands`s are used they are ORed together, -so that they all apply. - -#### `7` Persistent ACL - -The `Persistent ACL` section is used to identify what access the app has to -persistent storage. - -The data is stored in the `TbfHeaderV2PersistentAcl` field, which includes a -`write_id` and a number of `read_ids`. - -``` -0 2 4 6 8 x x+2 -+-------------+---------------------------+-------------+---------...--+-------------+---------...--+ -| Type (6) | write_id | read_length | read_ids |access_ids| access_ids | -+-------------+-------------+-------------+-------------+---------...--+-------------+---------...--+ -``` - -`write_id` indicates the id that all new persistent data is written with. -All new data created will be stored with permissions from the `write_id` -field. For existing data see the `access_ids` section below. -Only apps with the same id listed in the `read_ids` can read the data. -Apps with the same `access_ids` or `write_id` can overwrite the data. -`write_id` does not need to be unique, that is multiple apps can have the -same id. -A `write_id` of `0x00` indicates that the app can not perform write operations. - -`read_ids` list all of the ids that this app has permission to read. The -`read_length` specifiies the length of the `read_ids` in elements (not bytes). -`read_length` can be `0` indicating that there are no `read_ids`. - -`access_ids` list all of the ids that this app has permission to write. -`access_ids` are different to `write_id` in that `write_id` applies to new data -while `access_ids` allows modification of existing data. -The `access_length` specifiies the length of the `access_ids` in elements (not bytes). -`access_length` can be `0` indicating that there are no `access_ids`. - -For example an app has a `write_id` of `1`, `read_ids` of `2, 3` and -`access_ids` of `3, 4`. If the app was to write new data, it would be stored -with id `1`. The app is able to read data stored with id `2` or `3`, note that -it can not read the data that it writes. The app is also able to overwrite -existing data that was stored with id `3` or `4`. - -An example of when `access_ids` would be useful is on a system where each app -logs errors in its own write_region. An error-reporting app reports these -errors over the network, and once the reported errors are acked erases them -from the log. In this case `access_ids` allow an app to erase multiple -different regions. - -#### `8` Kernel Version - -The `compatibility` header is designed to prevent the kernel -from running applications that are not compatible with it. - -It defines the following two items: -* `Kernel major` or `V` is the kernel major number (for Tock 2.0, it is 2) -* `Kernel minor` or `v` is the kernel minor number (for Tock 2.0, it is 0) - -Apps defining this header are compatible with kernel version ^V.v (>= V.v and < (V+1).0) - -The kernel version header refers only to the ABI and API exposed by the kernel -itself, it does not cover API changes within drivers. - -A kernel major and minor version guarantees the ABI for exchanging -data between kernel and userspace and the the system call numbers. - -``` -0 2 4 6 8 -+-------------+-------------+---------------------------+ -| Type (8) | Length (4) | Kernel major| Kernel minor| -+-------------+-------------+---------------------------+ -``` - - -## Code - -The process code itself has no particular format. It will reside in flash, -but the specific address is determined by the platform. Code in the binary -should be able to execute successfully at any address, e.g. using position -independent code. diff --git a/doc/Userland.md b/doc/Userland.md deleted file mode 100644 index 0e78ead8f3..0000000000 --- a/doc/Userland.md +++ /dev/null @@ -1,301 +0,0 @@ -Userland -======== - -This document explains how application code works in Tock. This is not a guide -to writing applications, but rather documentation of the overall design -of how applications function. - - - - - -- [Overview of Processes in Tock](#overview-of-processes-in-tock) -- [System Calls](#system-calls) -- [Upcalls and Termination](#upcalls-and-termination) -- [Inter-Process Communication](#inter-process-communication) - * [Services](#services) - * [Clients](#clients) -- [Application Entry Point](#application-entry-point) -- [Stack and Heap](#stack-and-heap) -- [Debugging](#debugging) -- [Applications](#applications) - - - -## Overview of Processes in Tock - -Processes in Tock run application code meant to accomplish some type -of task for the end user. Processes run in user mode. Unlike kernel -code, which runs in supervisor mode and handles device drivers, -chip-specific details, as well as general operating system tasks, -appliction code running in processes is independent of the details of -the underlying hardware (except the instruction set -architecture). Unlike many existing embedded operating systems, in -Tock processes are not compiled with the kernel. Instead they are -entirely separate code that interact with the kernel and each other -through [system calls](https://en.wikipedia.org/wiki/System_call). - -Since processes are not a part of the kernel, application code running -in a process may be written in any language that can be compiled into -code capable of running on a microcontroller. Tock supports running -multiple processes concurrently. Co-operatively multiprogramming is -the default, but processes may also be time sliced. Processes may -share data with each other via Inter-Process Communication (IPC) -through system calls. - -Processes run code in unprivileged mode (e.g., user mode on CortexM or -RV32I microcontrollers). The Tock kernel uses hardware memory -protection (an MPU on CortexM and a PMP on RV32I) to restrict which -addresses application code running in a process can access. A process -makes system calls to access hardware peripherals or modify what -memory is accessible to it. - -Tock supports dynamically loading and unloading independently compiled -applications. In this setting, applications not not know at compile -time what address they will be installed at and loaded from. To be -dynamically loadable, application code must be compiled as [position -independent -code](https://en.wikipedia.org/wiki/Position-independent_code) -(PIC). This allows them to be run from any address they happen to be -loaded into. - -In some cases, applications may know their location at compile-time. This -happens, for example, in cases where the kernel and applications are combined -into a single cryptographically signed binary that is accepted by -a secure bootloader. In these cases, compiling an application with -explicit addresses works. - -Tock supports running multiple processes at the same time. The maximum -number of processes supported by the kernel is typically a compile-time -constant in the range of 2-4, but is limited only by the available RAM -and Flash resources of the chip. Tock scheduling generally assumes that -it is a small number (e.g., uses O(n) scheduling algorithms). - - -## System Calls - -System calls are how processes and the kernel share data and interact. These -could include commands to drivers, subscriptions to callbacks, granting of -memory to the kernel so it can store data related to the application, -communication with other application code, and many others. In practice, -system calls are made through library code and the application need not -deal with them directly. - -For example, consider the following system call that sets a GPIO pin high: - -```c -int gpio_set(GPIO_Pin_t pin) { - return command(GPIO_DRIVER_NUM, 2, pin); -} -``` - -The command system call itself is implemented as the ARM assembly instruction -`svc` (service call): - -```c -int __attribute__((naked)) -command(uint32_t driver, uint32_t command, int data) { - asm volatile("svc 2\nbx lr" ::: "memory", "r0"); -} -``` - -A detailed description of Tock's system call API and ABI can be found in -[TRD104](reference/trd104-syscalls.md). The [system call -documentation](./Syscalls.md) describes how the are implemented in the -kernel. - - -## Upcalls and Termination - -The Tock kernel is completely non-blocking, and it pushes this -asynchronous behavior to userspace code. This means that system calls -(with one exception) do not block. Instead, they always return very quickly. -Long-running operations (e.g., sending data over a bus, sampling a sensor) -signal their completion to userspace through upcalls. An upcall is a function -call the kernel makes on userspace code. - -Yield system calls are the exception to this non-blocking rule. The yield-wait -system call blocks until the kernel invokes an upcall on the process. -The kernel only invokes upcalls when a process issues the yield system call: -it does not invoke upcalls at arbitrary points in the program. - -For example, consider the case of when a process wants to sleep for 100 -milliseconds. The timer library might break this into three operations: - -1. It registers an upcall for the timer system call driver with a Subscribe -system call. -2. It tells the timer system call driver to issue an upcall in 100 -milliseconds by invoking a Command system call. -3. It calls the yield-wait system call. This causes the process to block -until the timer upcall executes. The kernel pushes a stack frame onto -the process to execute the upcall; this function call returns to the -instruction after yield was invoked. - -When a process registers an upcall with a call to a Subscribe system call, -it may pass a pointer `userdata`. The kernel does not access or use this -data: it simply passes it back on each invocation of the upcall. This -allows a process to register the same function as multiple upcalls, and -distinguish them by the data passed in the argument. - -It is important to note that upcalls are not executed until a process -calls `yield`. The kernel will enqueue upcalls as events occur within -the kernel, but the application will not handle them until it yields. - -Applications which are "finished" -should call an Exit system call. There are two variants of Exit: -exit-terminate and exit-restart. They differ in what they signal to -the kernel: does the application wish to stop running, or be rebooted? - -## Inter-Process Communication - -Inter-process communication (IPC) allows for separate processes to -communicate directly through shared buffers. IPC in Tock is -implemented with a service-client model. Each process can support one -service. The service is identified by the name of the application running -in the process, which is -included in the Tock Binary Format Header for the application. A process can -communicate with multiple services and will get a unique handle for -each discovered service. Clients and services communicate through -shared buffers. Each client can share some of its own application -memory with the service and then notify the service to instruct it to -parse the shared buffer. - -### Services - -Services are named by the package name included in the app's TBF header. -To register a service, an app can call `ipc_register_svc()` to setup a callback. -This callback will be called whenever a client calls notify on that service. - -### Clients - -Clients must first discover services they wish to use with the function -`ipc_discover()`. They can then share a buffer with the service by calling -`ipc_share()`. To instruct the service to do something with the buffer, the -client can call `ipc_notify_svc()`. If the app wants to get notifications from -the service, it must call `ipc_register_client_cb()` to receive events from when -the service when the service calls `ipc_notify_client()`. - -See `ipc.h` in `libtock-c` for more information on these functions. - -## Application Entry Point - -An application specifies the first function the kernel should call by setting -the variable `init_fn_offset` in its TBF header. This function should have the -following signature: - -```c -void _start(void* text_start, void* mem_start, void* memory_len, void* app_heap_break); -``` - -The Tock kernel tries to impart no restrictions on the stack and heap layout of -application processes. As such, a process starts in a very minimal environment, -with an initial stack sufficient to support a syscall, but not much more. -Application startup routines should first -[move their program break](/doc/syscalls/memop.md#operation-type-0-brk) to accommodate -their desired layout, and then setup local stack and heap tracking in accordance -with their runtime. - - -## Stack and Heap - -Applications can specify memory requirements by setting the `minimum_ram_size` variable -in their TBF headers. Note that the Tock kernel treats this as a minimum, -depending on the underlying platform, the amount of memory may be larger than -requested, but will never be smaller. - -If there is insufficient memory to load your application, the kernel will fail -during loading and print a message. - -If an application exceeds its alloted memory during runtime, the application -will crash (see the [Debugging](#debugging) section for an example). - -## Debugging - -If an application crashes, Tock provides a very detailed stack dump. -By default, when an application crashes Tock prints a crash dump over the -platform's default console interface. When your application crashes, -we recommend looking at this output very carefully: often we have spent -hours trying to track down a bug which in retrospect was quite obviously -indicated in the dump, if we had just looked at the right fields. - -Note that because an application is relocated when it is loaded, the binaries -and debugging .lst files generated when the app was originally compiled will not -match the actual executing application on the board. To generate matching files -(and in particular a matching .lst file), you can use the `make debug` target -app directory to create an appropriate .lst file that matches how the -application was actually executed. See the end of the debug print out for an -example command invocation. - -``` ----| Fault Status |--- -Data Access Violation: true -Forced Hard Fault: true -Faulting Memory Address: 0x00000000 -Fault Status Register (CFSR): 0x00000082 -Hard Fault Status Register (HFSR): 0x40000000 - ----| App Status |--- -App: crash_dummy - [Fault] - Events Queued: 0 Syscall Count: 0 Dropped Callback Count: 0 - Restart Count: 0 - Last Syscall: None - - ╔═══════════╤══════════════════════════════════════════╗ - ║ Address │ Region Name Used | Allocated (bytes) ║ - ╚0x20006000═╪══════════════════════════════════════════╝ - │ ▼ Grant 948 | 948 - 0x20005C4C ┼─────────────────────────────────────────── - │ Unused - 0x200049F0 ┼─────────────────────────────────────────── - │ ▲ Heap 0 | 4700 S - 0x200049F0 ┼─────────────────────────────────────────── R - │ Data 496 | 496 A - 0x20004800 ┼─────────────────────────────────────────── M - │ ▼ Stack 72 | 2048 - 0x200047B8 ┼─────────────────────────────────────────── - │ Unused - 0x20004000 ┴─────────────────────────────────────────── - ..... - 0x00030400 ┬─────────────────────────────────────────── F - │ App Flash 976 L - 0x00030030 ┼─────────────────────────────────────────── A - │ Protected 48 S - 0x00030000 ┴─────────────────────────────────────────── H - - R0 : 0x00000000 R6 : 0x20004894 - R1 : 0x00000001 R7 : 0x20004000 - R2 : 0x00000000 R8 : 0x00000000 - R3 : 0x00000000 R10: 0x00000000 - R4 : 0x00000000 R11: 0x00000000 - R5 : 0x20004800 R12: 0x12E36C82 - R9 : 0x20004800 (Static Base Register) - SP : 0x200047B8 (Process Stack Pointer) - LR : 0x000301B7 - PC : 0x000300AA - YPC : 0x000301B6 - - APSR: N 0 Z 1 C 1 V 0 Q 0 - GE 0 0 0 0 - EPSR: ICI.IT 0x00 - ThumbBit true - - Cortex-M MPU - Region 0: base: 0x20004000, length: 8192 bytes; ReadWrite (0x3) - Region 1: base: 0x30000, length: 1024 bytes; ReadOnly (0x6) - Region 2: Unused - Region 3: Unused - Region 4: Unused - Region 5: Unused - Region 6: Unused - Region 7: Unused - -To debug, run `make debug RAM_START=0x20004000 FLASH_INIT=0x30059` -in the app's folder and open the .lst file. -``` - -## Applications - -For example applications, see the language specific userland repos: - -- [libtock-c](https://github.com/tock/libtock-c): C and C++ apps. -- [libtock-rs](https://github.com/tock/libtock-rs): Rust apps. diff --git a/doc/architecture.png b/doc/architecture.png deleted file mode 100644 index 6f29696d6a..0000000000 Binary files a/doc/architecture.png and /dev/null differ diff --git a/doc/courses/2017-11-SenSys/README.md b/doc/courses/2017-11-SenSys/README.md deleted file mode 100644 index f37f91d62d..0000000000 --- a/doc/courses/2017-11-SenSys/README.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -location: Delft, Netherlands -date: November 5, 2017 ---- - -# Tock OS Training @ SenSys 2017 - ---- -**NOTE** - -This course was primarily designed in November of 2017, but has been updated -through May 2018. It is a useful introduction to Tock but is no longer updated -as Tock evolves in preparation for new courses that will be used. -For example, this course uses Tock version 1.2, and current Tock -development is on version 2.x. To follow this course, please checkout -the commit below to use Tock at a point where the tutorial is known to -be working. - -```bash -$ git checkout 1203437bff05667bb0636dc9ab69e1daca13c2a2 -``` ---- - -This course introduces you to Tock, a secure embedded operating system for -sensor networks and the Internet of Things. Tock is the first operating system -to allow multiple untrusted applications to run concurrently on a -microcontroller-based computer. The Tock kernel is written in Rust, a -memory-safe systems language that does not rely on a garbage collector. -Userspace applications are run in single-threaded processes that can be written -in any language. A paper describing Tock's goals, design, and implementation was -published at the SOSP'17 conference and is available -[here](https://www.amitlevy.com/papers/tock-sosp2017.pdf). - -In this course, you will learn the basic Tock system architecture, how to write -a userspace process in C, Tock's system call interface, and fill in code for a -small kernel extension written in Rust. The course assumes experience -programming embedded devices and fluency in C. It assumes no knowledge of Rust, -although knowing Rust will allow you to be more creative in the Rust programming -part of the course. diff --git a/doc/courses/2018-11-SenSys/README.md b/doc/courses/2018-11-SenSys/README.md deleted file mode 100644 index 96d5292056..0000000000 --- a/doc/courses/2018-11-SenSys/README.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -location: Shenzhen, China -date: November 4, 2018 ---- - -# Tock OS Training @ SenSys 2018 - ---- -**NOTE** - -This course was primarily designed in October of 2018. It is a useful -introduction to Tock but is no longer updated. For example, this course -uses Tock version 1.2, and current Tock development is on version 2.x. - ---- - -This course introduces you to Tock, a secure embedded operating system for -sensor networks and the Internet of Things. Tock is the first operating system -to allow multiple untrusted applications to run concurrently on a -microcontroller-based computer. The Tock kernel is written in Rust, a -memory-safe systems language that does not rely on a garbage collector. -Userspace applications are run in single-threaded processes that can be written -in any language. A paper describing Tock's goals, design, and implementation was -published at the SOSP'17 conference and is available -[here](https://www.amitlevy.com/papers/tock-sosp2017.pdf). - -In this course, we will look at some of the high-level services provided by Tock. -We will start with an understanding of the OS and its programming environment. -Then we'll look at how a process management application can help afford remote -debugging, diagnosing and fixing a resource-intensive app over the network. -The last part of the tutorial is a bit more free-form, inviting attendees to -further explore the networking and application features of Tock or to dig into -the kernel a bit and explore how to enhance and extend the kernel. - -This course assumes some experience programming embedded devices and fluency in C. -It assumes no knowledge of Rust, although knowing Rust will allow you to be -more creative during the kernel exploration at the end. - -## Preparation - -We will go over setting up a development environment during the course and help -out with possible problems you run into. However, because the WiFi is likely to -be slow, we **strongly urge you to set up the following dependencies ahead of -time, preferably by downloading the provided VM image**. - -First, you will need a laptop running Linux or OS X. Linux in a VM will work -just fine, see below for a pre-made image with all the dependencies. We strongly -recommend you use the pre-made image unless you have set up and tested your -installation before the course. - -### Virtual Machine - -If you're comfortable working inside a Debian virtual machine, you can download -an image with all of the dependencies already installed -[here](http://www.scs.stanford.edu/~alevy/Tock.ova) - - * VirtualBox users: [File → Import Appliance...](https://docs.oracle.com/cd/E26217_01/E26796/html/qs-import-vm.html), - * VMWare users: [File → Open...](https://pubs.vmware.com/workstation-9/index.jsp?topic=%2Fcom.vmware.ws.using.doc%2FGUID-DDCBE9C0-0EC9-4D09-8042-18436DA62F7A.html) - -The VM account is "user" with password "user". Feel free to customize it with -whichever editors, window managers, etc. you like before the training starts. - -> If the Host OS is Linux, you may need to add your user to the `vboxusers` -> group on your machine in order to connect the hardware boards to the virtual -> machine. - -### Manual Installation - -If you would prefer not to use the virtual machine, -[there are directions for manual installation of dependencies](manual_installation.md). - -### Testing - -To test if your environment is working, go to the `tock/boards/imix` directory -and type `make program`. This should compile the kernel for the default board, -Imix, and try to program it over a USB serial connection. It may need to compile -several supporting libraries first (so may take 30 seconds or so the first -time). You should see output like this: - -``` -$ make - Compiling tock-registers v0.2.0 (file:///Users/bradjc/git/tock/libraries/tock-register-interface) - Compiling tock-cells v0.1.0 (file:///Users/bradjc/git/tock/libraries/tock-cells) - Compiling enum_primitive v0.1.0 (file:///Users/bradjc/git/tock/libraries/enum_primitive) - Compiling imix v0.1.0 (file:///Users/bradjc/git/tock/boards/imix) - Compiling kernel v0.1.0 (file:///Users/bradjc/git/tock/kernel) - Compiling cortexm v0.1.0 (file:///Users/bradjc/git/tock/arch/cortex-m) - Compiling capsules v0.1.0 (file:///Users/bradjc/git/tock/capsules) - Compiling cortexm4 v0.1.0 (file:///Users/bradjc/git/tock/arch/cortex-m4) - Compiling sam4l v0.1.0 (file:///Users/bradjc/git/tock/chips/sam4l) - Finished release [optimized + debuginfo] target(s) in 23.89s - text data bss dec hex filename - 148192 5988 34968 189148 2e2dc target/thumbv7em-none-eabi/release/imix -tockloader flash --address 0x10000 target/thumbv7em-none-eabi/release/imix.bin -No device name specified. Using default "tock" -No serial ports found. Is the board connected? - -make: *** [program] Error 1 -``` - -That is, since you don't yet have a board plugged in it can't program it. But -the above output indicates that it can compile correctly and invoke `tockloader` -to program a board. - -## Agenda - -The training is divided into three sections, each starting with a short -presentation to introduce some concepts, followed by a practical exercise. - -1. [Environment Setup](environment.md): Get familiar with the Tock tools - and getting a board setup. - -2. [Userland programming](application.md): write a basic sensing application in C. - -3. [Deliver for the Client](client.md): Help an important client get a - new board setup. - -4. [Free-form Experimentation](freeform.md): Open-ended exploration with - support from the Tock team. - diff --git a/doc/courses/2018-11-SenSys/application.md b/doc/courses/2018-11-SenSys/application.md deleted file mode 100644 index df429ab0c2..0000000000 --- a/doc/courses/2018-11-SenSys/application.md +++ /dev/null @@ -1,206 +0,0 @@ -# Write an environment sensing Bluetooth Low Energy application ---- - -## Course Agenda - -- [Introduction](README.md) -- Part 1: [Getting started with Tock](environment.md) -- **Part 2: [Application Basics](application.md)** -- Part 3: [Client Delivery](client.md) -- Part 4: [Free-form Play](freeform.md) - ---- - -## 1. Presentation: Process overview, relocation model and system call API - -In this section, we're going to learn about processes (a.k.a applications) in -Tock, and build our own applications in C. - -## 2. Check your understanding - -1. How does a process perform a blocking operation? Can you draw the flow of - operations when a process calls `delay_ms(1000)`? - -2. How would you write an IPC service to print to the console? Which functions - would the client need to call? - -## 3. Get a C application running on your board - -You'll find the outline of a C application in the directory -`docs/courses/sensys/exercises/app`. - -Take a look at the code in `main.c`. So far, this application merely prints -"Hello, World!". - -The code uses the standard C library routine `printf` to compose a message -using a format string and print it to the console. Let's break down what the -code layers are here: - -1. `printf` is provided by the C standard library (implemented by - [newlib](https://sourceware.org/newlib/)). It takes the format string and - arguments, and generates an output string from them. To actually write the - string to standard out, `printf` calls `_write`. - -2. `_write` (in `userland/libtock/sys.c`) is a wrapper for actually writing to - output streams (in this case, standard out a.k.a. the console). It calls - the Tock-specific console writing function `putnstr`. - -3. `putnstr`(in `userland/libtock/console.c`) buffers data to be written, calls - `putnstr_async`, and acts as a synchronous wrapper, yielding until the - operation is complete. - -4. `putnstr_async` (in `userland/libtock/console.c`) finally performs the - actual system calls, calling to `allow`, `subscribe`, and `command` to - enable the kernel to access the buffer, request a callback when the write is - complete, and begin the write operation respectively. - - -The application could accomplish all of this by invoking Tock system calls -directly, but using libraries makes for a much cleaner interface and allows -users to not need to know the inner workings of the OS. - - -### Loading an application - -Okay, let's build and load this simple program. - -1. Erase all other applications from the development board: - - $ tockloader erase-apps - -2. Build this application: - - $ make - -3. Load the application (Note: `tockloader install` automatically searches the - current working directory and its subdirectories for Tock binaries.) - - $ tockloader install - -4. Check that it worked: - - $ tockloader listen - -The output should look something like: - -``` -$ tockloader listen -No device name specified. Using default "tock" -Using "/dev/cu.usbserial-c098e5130012 - Hail IoT Module - TockOS" - -Listening for serial output. -Hello, World! -``` - -## 4. Creating your own application - -Now that you've got a basic app working, modify it so that it continuously -prints out `Hello World` twice per second. You'll want to use the user -library's timer facilities to manage this: - -### Timer - -You'll find the interface for timers in `userland/libtock/timer.h`. The -function you'll find useful today is: - -```c -#include -void delay_ms(uint32_t ms); -``` - -This function sleeps until the specified number of milliseconds have passed, and -then returns. So we call this function "synchronous": no further code will run -until the delay is complete. - -## 5. Write an app that periodically samples the on-board sensors - -Now that we have the ability to write applications, let's do something a little -more complex. The development board you are using has several sensors on it. -These sensors include a light sensor, a humidity sensor, and a temperature -sensor. Each sensing medium can be accessed separately via the Tock user -library. We'll just be using the light, temperature, and humidity measurements -for today's tutorial. - -#### Light - -The interface in `userland/libtock/ambient_light.h` is used to measure ambient -light conditions in [lux](https://en.wikipedia.org/wiki/Lux). imix uses the -[ISL29035](https://www.intersil.com/en/products/optoelectronics/ambient-light-sensors/light-to-digital-sensors/ISL29035.html) -sensor, but the userland library is abstracted from the details of particular -sensors. It contains the function: - -```c -#include -int ambient_light_read_intensity_sync(int* lux); -``` - -Note that the light reading is written to the location passed as an -argument, and the function returns non-zero in the case of an error. - -#### Temperature - -The interface in `userland/libtock/temperature.h` is used to measure ambient -temperature in degrees Celsius, times 100. imix uses the -[SI7021](https://www.silabs.com/products/sensors/humidity-sensors/Pages/si7013-20-21.aspx) -sensor. It contains the function: - -```c -#include -int temperature_read_sync(int* temperature); -``` - -Again, this function returns non-zero in the case of an error. - -#### Humidity - -The interface in `userland/libtock/humidity.h` is used to measure the ambient -[relative humidity](https://en.wikipedia.org/wiki/Relative_humidity) in -percent, times 100. It contains the function: - -```c -#include -int humidity_read_sync (unsigned* humi); -``` - -Again, this function returns non-zero in the case of an error. - -### Read sensors in a Tock application - -Using the example program you're working on, write an application that reads -all of the sensors on your development board and reports their readings over -the serial port. - -As a bonus, experiment with toggling an LED when readings are above or below a -certain threshold: - -#### LED - -The interface in `userland/libtock/led.h` is used to control lights on Tock boards. On the Hail -board, there are three LEDs which can be controlled: Red, Blue, and Green. The -functions in the LED module are: - -```c -#include -int led_count(void); -``` - -Which returns the number of LEDs available on the board. - -```c -int led_on(int led_num); -``` - -Which turns an LED on, accessed by its number. - -```c -int led_off(int led_num); -``` - -Which turns an LED off, accessed by its number. - -```c -int led_toggle(int led_num); -``` - -Which toggles the state of an LED, accessed by its number. - diff --git a/doc/courses/2018-11-SenSys/client.md b/doc/courses/2018-11-SenSys/client.md deleted file mode 100644 index ce54a84a27..0000000000 --- a/doc/courses/2018-11-SenSys/client.md +++ /dev/null @@ -1,202 +0,0 @@ -Part 3: Keep the client happy -============================= - ---- - -## Course Agenda - -- [Introduction](README.md) -- Part 1: [Getting started with Tock](environment.md) -- Part 2: [Application Basics](application.md) -- **Part 3: [Client Delivery](client.md)** -- Part 4: [Free-form Play](freeform.md) - ---- - -You, an engineer newly added to a top-secret project in your organization, -have been directed to commission a new imix node for your most important client. -The directions you receive are terse, but helpful: - -``` -On Sunday, Nov 4, 2018, Director Hines wrote: - -Welcome to the team, need you to get started right away. The client needs -an imix setup with their two apps -- ASAP. Make sure it is working, -we need to keep this client happy. - -- DH -``` - -Hmm, ok, not a lot to go on, but luckily in orientation you learned how -to flash a kernel and apps on to the imix board, so you are all set for -your first assignment. - -There were some other apps in `libtock-c/examples` so you go looking there first. -And sure enough there is a folder called "important-client"! And even better, -it has two apps inside of it! "Alright!" you are thinking, "My first day -is shaping up to go pretty smoothly." - -After installing those two apps, which are a little mysterious still, you -decide that it would also be a good idea to install an app you are more -familiar with: the "blink" app. After doing all of that, you run `tockloader -list` and see the following: - -``` -$ tockloader list - -No device name specified. Using default "tock" -Using "/dev/ttyUSB1 - imix IoT Module - TockOS" - -[App 0] - Name: app2 - Enabled: True - Sticky: False - Total Size in Flash: 16384 bytes - - -[App 1] - Name: app1 - Enabled: True - Sticky: False - Total Size in Flash: 8192 bytes - - -[App 2] - Name: blink - Enabled: True - Sticky: False - Total Size in Flash: 2048 bytes - - -Finished in 1.959 seconds -``` - ---- -> ### Checkpoint -> -> Make sure you have these apps installed correctly and `tockloader list` -> produces similar output as shown here. ---- - -Great! Now you check that the LED is blinking, and sure enough, no problems -there. The blink app was just for testing, so you `tockloader uninstall blink` -to remove that. So far, so good, Tock! -But, before you prepare to head home after a -successful day, you start to wonder if maybe this was a little too easy. Also, -if you get this wrong, it's not going to look good as the new person on the team. - -Looking in the folders for the two applications, you notice a brief description -of the apps, and a URL. Ok, maybe you can check if everything is working. -After trying things for a little bit, everything seems to be in order. You -tell the director the board is ready and head home a little early—you did -just successfully complete your first project for a major client after all. - - -## Back at Work the Next Day - -Expecting a more challenging project after how well things went yesterday, you are -instead greeted by this email: - -``` -On Monday, Nov 5, 2018, Director Hines wrote: - -I know you are new, but what did you do?? I've been getting calls all morning -from the client, the imix board you gave them ran out battery already!! Are you -sure you set up the board correctly? Fix it, and get it back to me later today. - -- DH -``` - -Well, that's not good. You already removed the blink app, so it can't be that. -What you need is some way to inspect the board and see if something looks like -it is going awry. You first try: - -``` -$ tockloader listen -``` - -to see if any debugging information is being printed. A little, but nothing -helpful. Before trying to look around the code, you decided to try sending the -board a plea for help: - -``` -help -``` - -and, surprisingly, it responded! - -``` -Welcome to the process console. -Valid commands are: help status list stop start -``` - -Ok! Maybe the process console can help. Try the `status` command: - -``` -Total processes: 2 -Active processes: 2 -Timeslice expirations: 4277 -``` - -It seems this tool is actually able to inspect the current system and the active -processes! But hmmm, it seems there are a lot of "timeslice expirations". From -orientation, you remember that processes are allocated only a certain quantum -of time to execute, and if they exceed that the kernel forces a context switch -back to the kernel. If that is happening a lot, then the board is likely unable -to go to sleep! That could explain why the battery is draining so fast! - -But which process is at fault? Perhaps we should try another command. -Maybe `list`: - -``` - PID Name Quanta Syscalls Dropped Callbacks State - 00 app2 0 336 0 Yielded - 01 app1 8556 1439951 0 Running - - -``` - -Ok! Now we have the status of individual applications. And aha! We can clearly -see the faulty application. From our testing we know that one app detects -button presses and one app is transmitting sensor data. Let's see if we can -disable the faulty app somehow and see which data packets we are still getting. -Going back to the help command, the `stop` command seems promising: - -``` -stop -``` - -Then checking back on the status webpage we now know what functionality we -have to fix. - - -## Time to Fix the App - -After debugging, we now know three things about the issue: - -- The name of the faulty app. -- What that app is supposed to do. -- That it is functionally correct but is for some reason consuming excess CPU cycles. - -Using this information, dig into the the faulty app. - -### A Quick Fix - -To get the director off your back, you should be able to introduce a simple fix -that will reduce wakeups by -[waiting a bit](https://github.com/tock/libtock-c/blob/21234c671eee0ae491faa5d23f35f3762b25c522/libtock/timer.h#L76) -between samples. - -### A Better Way - -While the quick fix will slow the number of wakeups, you know that you can do -better than polling for something like a button press! Tock supports -asynchronous operations allowing user processes to _subscribe_ to interrupts. - -Looking at the [button interface](https://github.com/tock/libtock-c/blob/master/libtock/button.h), -it looks like we'll first have to enable interrupts and then sign up to listen to them. - -Once this energy-optimal patch is in place, it'll be time to kick off a -triumphant e-mail to the director, and then off to celebrate with some -[baijiu](https://en.wikipedia.org/wiki/Baijiu)! - diff --git a/doc/courses/2018-11-SenSys/environment.md b/doc/courses/2018-11-SenSys/environment.md deleted file mode 100644 index b128818d27..0000000000 --- a/doc/courses/2018-11-SenSys/environment.md +++ /dev/null @@ -1,256 +0,0 @@ -# Tock OS Course Part 1: Getting your environment set up - -> While we're getting set up and started, please make sure you have -> completed all of the [tutorial pre-requisites](README.md#preparation). -> You can download a -> [virtual machine image](README.md#virtual-machine) with all the pre-requisites -> already installed. - ---- - -## Course Agenda - -- [Introduction](README.md) -- **Part 1: [Getting started with Tock](environment.md)** -- Part 2: [Application Basics](application.md) -- Part 3: [Client Delivery](client.md) -- Part 4: [Free-form Play](freeform.md) - ---- - -The goal of this part of the course is to make sure you have a working -development environment for Tock. - -During this portion of the course you will: - -- Get a high-level overview of how Tock works. -- Learn how to compile and flash the kernel onto an Imix board. - -## 1. Presentation: Tock's goals, architecture and components - - - -A key contribution of Tock is that it uses Rust's borrow checker as a -language sandbox for isolation and a cooperative scheduling model for -concurrency in the kernel. As a result, for the kernel isolation is -(more or less) free in terms of resource consumption at the expense of -preemptive scheduling (so a malicious component could block the system by, -e.g., spinning in an infinite loop). - -Tock includes three architectural components: - - - A small trusted _core kernel_, written in Rust, that implements a hardware - abstraction layer (HAL), scheduler, and platform-specific configuration. - - _Capsules_, which are compiled with the kernel and use Rust's type and - module systems for safety. - - _Processes_, which use the memory protection unit (MPU) for protection at runtime. - -Read the Tock documentation for more details on its -[design](https://github.com/tock/tock/blob/master/doc/Design.md). - -[_Presentation slides are available here._](presentation/presentation.pdf) - -## 2. Check your understanding - -1. What kinds of binaries exist on a Tock board? Hint: There are three, and - only two can be programmed using `tockloader`. - -2. What are the differences between capsules and processes? What performance - and memory overhead does each entail? Why would you choose to write - something as a process instead of a capsule and vice versa? - -3. Clearly, the core kernel should never enter an infinite loop. But is it - acceptable for a process to spin? What about a capsule? - -## 3. Compile and program the kernel - -### Make sure your Tock repository is up to date - - $ git pull - -### Build the kernel - -To build the kernel, just type make in `boards/imix/`. - - $ cd boards/imix/ - $ make - -If this is the first time you are trying to make the kernel, the build system -will use cargo and rustup to install various Tock dependencies. - -Kernels must be compiled from within the desired board's folder. For example, to -compile for Hail instead you must first run `cd boards/hail/`. - -### Connect to an imix board - -> #### One-Time Fixups -> -> * On Linux, you might need to give your user access to the imix's serial port. -> If you are using the VM, this is already done for you. -> You can do this by adding a udev rule: -> -> $ sudo bash -c "echo 'ATTRS{idVendor}==\"0403\", ATTRS{idProduct}==\"6015\", MODE=\"0666\"' > /etc/udev/rules.d/99-imix" -> -> Afterwards, detach and re-attach the imix to reload the rule. -> -> * With the virtual machine, you might need to attach the USB device to the -> VM. To do so, after plugging in imix, select in the VirtualBox/VMWare menu bar: -> -> Devices -> USB Devices -> imix IoT Module - TockOS -> -> If this generates an error, often unplugging/replugging fixes it. You can also -> create a rule in the VM USB settings which will auto-attach the imix to the VM. - -To connect your development machine to the imix, connect them with a micro-USB -cable. Any cable will do, but notice that there are two USB ports on the imix. -Make sure you connect to the micro-USB port labeled 'debug' on the imix. The -imix should come with `blink` and `c_hello` installed, which will blink the -status LED and print `Hello World` on boot respectively. - -The imix board should appear as a regular serial device (e.g. -`/dev/tty.usbserial-c098e5130006` on my Mac and `/dev/ttyUSB0` on my Linux box). -While you can connect with any standard serial program (set to 115200 baud), -tockloader makes this easier. Tockloader can read attributes from connected -serial devices, and will automatically find your connected imix. Simply run: - - $ tockloader listen - No device name specified. Using default "tock" - Using "/dev/ttyUSB0 - Imix - TockOS" - - Listening for serial output. - Initialization complete. Entering main loop - Hello World! - -### Flash the kernel - -Now that the imix board is connected and you have verified that the kernel -compiles, we can flash the imix board with the latest Tock kernel: - - $ cd boards/imix/ - $ make program - -This command will compile the kernel if needed, and then use `tockloader` to -flash it onto the imix. When the flash command succeeds, the `blink` and -`c_hello` apps should still be running and the LED will be blinking. -You now have the bleeding-edge Tock kernel running on your imix board! - -### Clear out the applications and re-flash the test app. - -Lets check what's on the board right now: - - $ tockloader list - ... - [App 0] - Name: blink - Enabled: True - Sticky: False - Total Size in Flash: 2048 bytes - - [App 1] - Name: c_hello - Enabled: True - Sticky: False - Total Size in Flash: 1024 bytes - - -As you can see, the old apps are still installed on the board. -This also nicely demonstrates that user applications are isolated from the -kernel: it is possible to update one independently of the other. -We can remove apps with the following command: - - $ tockloader uninstall - -Following the prompt, if you remove the `blink` app, the LED will stop -blinking, however the console will still print `Hello World`. - -Now let's try adding a more interesting app: - - $ cd libtock-c/examples/sensors/ - $ make program - -The `sensors` app will automatically discover all available sensors, -sample them once a second, and print the results. - - Listening for serial output. - Starting process console - Initialization complete. Entering main loop - [Sensors] Starting Sensors App. - Hello World! - [Sensors] All available sensors on the platform will be sampled. - ISL29035: Light Intensity: 453 - Temperature: 24 deg C - Humidity: 63% - - ISL29035: Light Intensity: 453 - Temperature: 24 deg C - Humidity: 63% - - -## 4. (Optional) Familiarize yourself with `tockloader` commands -The `tockloader` tool is a useful and versatile tool for managing and installing -applications on Tock. It supports a number of commands, and a more complete -list can be found in the tockloader repository, located at -[github.com/tock/tockloader](https://github.com/tock/tockloader#usage). -Below is a list of the more useful and important commands for programming and -querying a board. - -### `tockloader install` -This is the main tockloader command, used to load Tock applications onto a -board. -By default, `tockloader install` adds the new application, but does not erase -any others, replacing any already existing application with the same name. -Use the `--no-replace` flag to install multiple copies of the same app. -In order to install an app, navigate to the correct directory, make the program, -then issue the install command: - - $ cd libtock-c/examples/blink - $ make - $ tockloader install - -> *Tip:* You can add the `--make` flag to have tockloader automatically -> run make before installing, i.e. `tockloader install --make` - -> *Tip:* You can add the `--erase` flag to have tockloader automatically -> remove other applications when installing a new one. - -### `tockloader uninstall [application name(s)]` -Removes one or more applications from the board by name. - -### `tockloader erase-apps` -Removes all applications from the board. - -### `tockloader list` -Prints basic information about the apps currently loaded onto the board. - -### `tockloader info` -Shows all properties of the board, including information about currently -loaded applications, their sizes and versions, and any set attributes. - -### `tockloader listen` -This command prints output from Tock apps to the terminal. It listens via UART, -and will print out anything written to stdout/stderr from a board. - -> *Tip:* As a long-running command, `listen` interacts with other tockloader -> sessions. You can leave a terminal window open and listening. If another -> tockloader process needs access to the board (e.g. to install an app update), -> tockloader will automatically pause and resume listening. - -### `tockloader flash` -Loads binaries onto hardware platforms that are running a compatible bootloader. -This is used by the Tock Make system when kernel binaries are programmed to the -board with `make program`. - -## 5. (Optional) Explore other Tock example applications - -Other applications can be found in the `libtock-c/examples/` directory. Try -loading them on your imix and then try modifying them. By default, `tockloader -install` adds the new application, but does not erase any others. Be aware, not -all applications will work well together if they need the same resources (Tock -is in active development to add virtualization to all resources to remove this -issue!). - -**Note:** By default, the imix platform is limited to only running four -concurrent processes at once. Tockloader is (currently) unaware of this -limitation, and will allow to you to load additional apps. However the kernel -will only load the first four apps. One option for the free-form section at the -end of the tutorial will be to explore this limitation and allow more apps. diff --git a/doc/courses/2018-11-SenSys/exercises/app/.gitignore b/doc/courses/2018-11-SenSys/exercises/app/.gitignore deleted file mode 100644 index 378eac25d3..0000000000 --- a/doc/courses/2018-11-SenSys/exercises/app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build diff --git a/doc/courses/2018-11-SenSys/exercises/app/Makefile b/doc/courses/2018-11-SenSys/exercises/app/Makefile deleted file mode 100644 index 04eea759fd..0000000000 --- a/doc/courses/2018-11-SenSys/exercises/app/Makefile +++ /dev/null @@ -1,13 +0,0 @@ -# Makefile for user application - -# Specify this directory relative to the current application. -TOCK_USERLAND_BASE_DIR = ../../../../../userland - -# Which files to compile. -C_SRCS := $(wildcard *.c) - -PACKAGE_NAME = tock-course-app - -# Include userland master makefile. Contains rules and flags for actually -# building the application. -include $(TOCK_USERLAND_BASE_DIR)/AppMakefile.mk diff --git a/doc/courses/2018-11-SenSys/exercises/app/main.c b/doc/courses/2018-11-SenSys/exercises/app/main.c deleted file mode 100644 index 2b6c3e030f..0000000000 --- a/doc/courses/2018-11-SenSys/exercises/app/main.c +++ /dev/null @@ -1,8 +0,0 @@ -#include -#include - -int main (void) { - printf("Hello, World!\n"); - - return 0; -} diff --git a/doc/courses/2018-11-SenSys/exercises/app/solutions/ble-ess.c b/doc/courses/2018-11-SenSys/exercises/app/solutions/ble-ess.c deleted file mode 100644 index 709e9cef47..0000000000 --- a/doc/courses/2018-11-SenSys/exercises/app/solutions/ble-ess.c +++ /dev/null @@ -1,80 +0,0 @@ -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -int _svc_num = 0; - -char buf[64] __attribute__((aligned(64))); - -typedef enum { - SENSOR_TEMPERATURE = 0, - SENSOR_IRRADIANCE = 1, - SENSOR_HUMIDITY = 2, -} sensor_type_e; - -typedef struct { - int type; // sensor type - int value; // sensor reading -} sensor_update_t; - -static void ipc_callback(__attribute__ ((unused)) int pid, - __attribute__ ((unused)) int len, - __attribute__ ((unused)) int arg2, - void* ud) { - *(bool*)ud = true; - printf("Updated BLE characteristic.\n"); -} - -int main(void) -{ - _svc_num = ipc_discover("org.tockos.services.ble-ess"); - if (_svc_num < 0) { - printf("No BLE ESS service installed.\n"); - return -1; - } - - printf("Found BLE ESS service (%i)\n", _svc_num); - - delay_ms(1500); - - sensor_update_t *update = (sensor_update_t*) buf; - bool cond; - ipc_register_client_cb(_svc_num, ipc_callback, &cond); - ipc_share(_svc_num, buf, 64); - - while (true) { - int lux; - ambient_light_read_intensity_sync(&lux); - update->type = SENSOR_IRRADIANCE; - update->value = lux; - ipc_notify_svc(_svc_num); - cond = false; - yield_for(&cond); - - int temp; - temperature_read_sync(&temp); - update->type = SENSOR_TEMPERATURE; - update->value = temp; - ipc_notify_svc(_svc_num); - cond = false; - yield_for(&cond); - - unsigned humi; - humidity_read_sync(&humi); - update->type = SENSOR_HUMIDITY; - update->value = humi; - ipc_notify_svc(_svc_num); - cond = false; - yield_for(&cond); - - delay_ms(1000); - } -} - diff --git a/doc/courses/2018-11-SenSys/exercises/app/solutions/repeat-hello.c b/doc/courses/2018-11-SenSys/exercises/app/solutions/repeat-hello.c deleted file mode 100644 index 6eadf8def4..0000000000 --- a/doc/courses/2018-11-SenSys/exercises/app/solutions/repeat-hello.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include - -#include - -int main (void) { - while (true) { - printf("Hello, World!\n"); - delay_ms(500); - } -} - diff --git a/doc/courses/2018-11-SenSys/exercises/app/solutions/sensors.c b/doc/courses/2018-11-SenSys/exercises/app/solutions/sensors.c deleted file mode 100644 index 6975db4f18..0000000000 --- a/doc/courses/2018-11-SenSys/exercises/app/solutions/sensors.c +++ /dev/null @@ -1,36 +0,0 @@ -#include -#include - -#include -#include -#include -#include -#include - -int main (void) { - while (1) { - int lux; - ambient_light_read_intensity_sync(&lux); - printf("Light: %d lux\n", lux); - - /* Turn on the red LED in low light conditions */ - if (lux < 30) { - led_on(0); - } - else { - led_off(0); - } - - int temp; - temperature_read_sync(&temp); - printf("Temperature: %d degrees C\n", temp/100); - - unsigned humi; - humidity_read_sync(&humi); - printf("Relative humidity: %u%%\n", humi/100); - - printf("\n"); - delay_ms(2000); - } -} - diff --git a/doc/courses/2018-11-SenSys/freeform.md b/doc/courses/2018-11-SenSys/freeform.md deleted file mode 100644 index 2452517756..0000000000 --- a/doc/courses/2018-11-SenSys/freeform.md +++ /dev/null @@ -1,45 +0,0 @@ -# Free-form Experimentation ---- - -## Course Agenda - -- [Introduction](README.md) -- Part 1: [Getting started with Tock](environment.md) -- Part 2: [Application Basics](application.md) -- Part 3: [Client Delivery](client.md) -- **Part 4: [Free-form Play](freeform.md)** - ---- - -These are seedling ideas, feel free to try one of these or anything else that -strikes your fancy! - -## Kernel Hacking - - - Understanding board configuration: - - Look into the four-process limitation, where does it come from, can we add more processes, how? - - How are pins mapped and buses configured? Could we do something like tie the LED to be a SPI status indicator? - - - Add a new capsule to the kernel (write it, include it in the crate, - initialize it in a boot sequence). - - Can you send 802.15.4 packets directly from the within the kernel? - -## Userland Hacking - - - What other sensors and interfaces are available? - - Write a thermal alarm app? - - Write a light-change triggering motion detector app? - - Understand how the timer interface works and virtual timers? - - - Execution model - - What happens when there are multiple concurrent interrupts? - - How do blocking calls and callbacks interplay? - -## End-to-End Comprehension - - - What happens when your app returns from main? When will it run again (if at all)? - - - Can you diagram what happens when a process makes a blocking system call - (context switch, interrupt setup, wait_for, interrupt handling, kernel - thread, resuming process)? - diff --git a/doc/courses/2018-11-SenSys/img/architecture.png b/doc/courses/2018-11-SenSys/img/architecture.png deleted file mode 100644 index 5cce5a1cda..0000000000 Binary files a/doc/courses/2018-11-SenSys/img/architecture.png and /dev/null differ diff --git a/doc/courses/2018-11-SenSys/manual_installation.md b/doc/courses/2018-11-SenSys/manual_installation.md deleted file mode 100644 index 4442ae3e3f..0000000000 --- a/doc/courses/2018-11-SenSys/manual_installation.md +++ /dev/null @@ -1,46 +0,0 @@ -## Manual Installation - -If you choose to install manually, you will need the following software: - -1. Command line utilities: curl, make, git - -1. Python 3 and pip3 - -1. A local clone of the Tock repository - - $ git clone https://github.com/tock/tock.git - -1. A local clone of the Tock applications repository (for apps written in C) - - $ git clone https://github.com/tock/libtock-c.git - -1. [rustup](http://rustup.rs/). This tool helps manage installations of the - Rust compiler and related tools. - - $ curl https://sh.rustup.rs -sSf | sh - -1. [arm-none-eabi toolchain](https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads) (version >= 5.2) - - OS-specific installation instructions can be found - [here](https://github.com/tock/tock/blob/master/doc/Getting_Started.md#arm-none-eabi-toolchain) - -1. [tockloader](https://github.com/tock/tockloader) - - $ pip3 install -U --user tockloader - - > Note: On MacOS, you may need to add `tockloader` to your path. If you - > cannot run it after installation, run the following: - - $ export PATH=$HOME/Library/Python/3.6/bin/:$PATH - - > Similarly, on Linux distributions, this will typically install to - > `$HOME/.local/bin`, and you may need to add that to your `$PATH` if not - > already present: - - $ PATH=$HOME/.local/bin:$PATH - - -### Testing - -To verify you have everything installed correctly, -[hop back over to the testing directions in the main README](README.md#testing). diff --git a/doc/courses/2018-11-SenSys/presentation/Makefile b/doc/courses/2018-11-SenSys/presentation/Makefile deleted file mode 100644 index 16011c1365..0000000000 --- a/doc/courses/2018-11-SenSys/presentation/Makefile +++ /dev/null @@ -1,4 +0,0 @@ -all: presentation.pdf - -presentation.pdf: slides.md - pandoc -s --pdf-engine xelatex -t beamer $< -o $@ diff --git a/doc/courses/2018-11-SenSys/presentation/architecture.pdf b/doc/courses/2018-11-SenSys/presentation/architecture.pdf deleted file mode 100644 index ce6aca54cd..0000000000 Binary files a/doc/courses/2018-11-SenSys/presentation/architecture.pdf and /dev/null differ diff --git a/doc/courses/2018-11-SenSys/presentation/execution.pdf b/doc/courses/2018-11-SenSys/presentation/execution.pdf deleted file mode 100644 index 00d66b3c7a..0000000000 Binary files a/doc/courses/2018-11-SenSys/presentation/execution.pdf and /dev/null differ diff --git a/doc/courses/2018-11-SenSys/presentation/imix.png b/doc/courses/2018-11-SenSys/presentation/imix.png deleted file mode 100644 index ef3ca4620e..0000000000 Binary files a/doc/courses/2018-11-SenSys/presentation/imix.png and /dev/null differ diff --git a/doc/courses/2018-11-SenSys/presentation/slides.md b/doc/courses/2018-11-SenSys/presentation/slides.md deleted file mode 100644 index f7e8810f1c..0000000000 --- a/doc/courses/2018-11-SenSys/presentation/slides.md +++ /dev/null @@ -1,349 +0,0 @@ ---- -title: Tock Embedded OS Tutorial -date: SenSys 2018 -header-includes: - - \beamertemplatenavigationsymbolsempty - - \usepackage{pifont} - - \newcommand{\cmark}{\color{green}\ding{51}} - - \newcommand{\xmark}{\color{red}\ding{55}} - - \setbeamerfont{note page}{family*=pplx,size=\large} - - \usefonttheme{professionalfonts} - - \setmainfont{Source Sans Pro} - - \setsansfont{Source Sans Pro} ---- - -## Tock - -A secure operating system for microcontrollers - - * Kernel components in Rust - - * Type-safe API for safe driver development - - * Hardware isolated processes for application code - -## Microcontrollers - -System-on-a-chip with integrated flash, SRAM, CPU and a bunch of hardware -controllers. - -Typically: - - * Communication: UART, SPI, I2C, USB, CAN... - - * External I/O: GPIO, external interrupt, ADC, DAC - - * Timers: RTC, countdown timers - -Maybe... - - * Radio (Bluetooth, 15.4) - - * Cryptographic accelerators - - * Other specialized hardware... - -## Low Resource - - * 10's of µA average power draw - - * 10's of kBs of RAM - - * Moderate clock speeds - -## Use cases - - * Security applications (e.g. authentication keys) - - * Sensor networks - - * Programmable wearables - - * PC/phone peripherals - - * Home/industrial automation - - * Flight control - -## Two types of components: capsules and processes - -![](architecture.pdf) - -## Two types of scheduling: cooperative and preemptive - -![](execution.pdf) - -## Agenda Today - -| | | -|-------------+-------------| -| 09:30-10:40 | Intro to Tock, Development Environment & Hardware | -| 10:40-11:00 | Coffee break | -| 11:00-12:00 | Hardware setup and installing apps | -| 12:00-13:30 | Lunch | -| 13:30-15:20 | Find and fix a real world bug | -| 15:20-15:40 | Coffee break | -| 15:40-17:30 | Choose your own adventure | - -# Part 1: Hardware, tools, and development environment - -* * * - -![](imix.png) - -## imix - - - Atmel SAM4L, Cortex-M4, 64 kB RAM, 256 kB flash - - - Nordic NRF51 Bluetooth SoC - - - 802.15.4 radio (6lowpan) - - - Temperature, humidity, and light sensors - - - 2 USBs (target USB + FTDI serial USB) - - - 2 LEDs, 1 "user" button - -## Binaries on-board in flash - - - `0x00000`: **Bootloader**: Interact with Tockloader; load code - - - `0x10000`: **Kernel** - - - `0x40000`: **Processes**: Packed back-to-back - -## Tools - - * `make` - - * Rust/Cargo (Rust code → Cortex-M) - - * `arm-none-eabi` (C → Cortex-M) - - * `tockloader` to interact with imix and the bootloader - -## Tools: `tockloader` - -Write a binary to a particular address in flash - -```bash -$ tockloader flash --address 0x10000 \ - target/thumbv7em-none-eabi/release/imix.bin -``` - -Program a process in Tock Binary Format[^footnote]: - -```bash -$ tockloader install myapp.tab -``` - -Restart the board and connect to the debug console: - -```bash -$ tockloader listen -``` - -[^footnote]: TBFs are relocatable process binaries prefixed with headers like - the package name. `.tab` is a tarball of TBFs for different architectures as - well as a metadata file for `tockloader`. - -## Check your understanding - -Turn to the person next to you: - - 1. What kinds of binaries exist on a Tock board? - _Hint: There are three, and only two can be programmed using `tockloader`._ - - 2. What steps would you follow to program a process onto imix? What about - to replace the kernel? - -## Answers - - 1. The three binaries are the serial bootloader, the kernel, and a series of - processes. The bootloader can be used to load the kernel and processes, but - cannot replace itself. - - 2. Use `tockloader`: - - * `tockloader install app.tab` - - * `tockloader flash --address 0x10000 imix-kernel.bin` - -## Hands-on: Set-up development environment - - 3. Compile and program the kernel - - 4. (Optional) Familiarize yourself with `tockloader` commands - - * `uninstall` - - * `list` - - * `erase-apps` - - 5. (Optional) Add some other apps from the repo, like `blink` and `sensors` - - - - Head to to get started! - - \tiny ([https://github.com/tock/tock/blob/tutorial-sensys-2018/doc/courses/sensys/environment.md](https://github.com/tock/tock/blob/tutorial-sensys-2018/doc/courses/sensys/environment.md)) - -# Part 2: User space programming - -## System calls - -Tock supports five syscalls that applications use to interact with the kernel. - -| **Call** | **Target** | **Description** | -|:----------|:----------:|----------------------------------| -| command | Capsule | Invoke an operation on a capsule | -| allow | Capsule | Share memory with a capsule | -| subscribe | Capsule | Register an upcall | -| memop | Core | Modify memory break | -| yield | Core | Block until next upcall is ready | - -## C System Calls: `command` & `allow` - -```c -// Start an operation -int command(u32 driver, u32 command, int arg1, int arg2); - -// Share memory with the kernel -int allow(u32 driver, u32 allow, void* ptr, size_t size); -``` - -## C System Calls: `subscribe` - -```c -// Callback function type -typedef void (sub_cb)(int, int, int, void* userdata); - -// Register a callback with the kernel -int subscribe(u32 driver, - u32 subscribe, - sub_cb cb, - void* userdata); -``` - -## C System Calls: `yield` & `yield_for` - -```c -// Block until next callback -void yield(void); - -// Block until a specific callback -void yield_for(bool *cond) { - while (!*cond) { - yield(); - } -} -``` - - - -## Example: printing to the debug console - -```c -#define DRIVER_NUM_CONSOLE 0x0001 - -bool done = false; - -static void putstr_cb(int x, int y, int z, void* ud) { - done = true; -} - -int putnstr(const char *str, size_t len) { - allow(DRIVER_NUM_CONSOLE, 1, str, len); - subscribe(DRIVER_NUM_CONSOLE, 1, putstr_cb, NULL); - command(DRIVER_NUM_CONSOLE, 1, len, 0); - yield_for(&done); - - return Ok(()); -} -``` - -## Hands-on: Write a simple application - - 3. Get an application running on imix - - 4. [Print "Hello World" every second](https://github.com/tock/tock/blob/tutorial-sensys-2018/doc/courses/sensys/exercises/app/solutions/repeat-hello.c) - - 5. [Extend your app to sample on-board sensors](https://github.com/tock/tock/blob/tutorial-sensys-2018/doc/courses/sensys/exercises/app/solutions/sensors.c) - - - Head to to get started! - - \tiny ([https://github.com/tock/tock/blob/tutorial-sensys-2018/doc/courses/sensys/application.md](https://github.com/tock/tock/blob/tutorial-sensys-2018/doc/courses/sensys/application.md#2-check-your-understanding)) - -# Part 3: Deliver for the Client - -## Debugging in a Multi-app Setting - - * Multiprogramming -> multiple things can go wrong - - * Multiprogramming _enables_ better debugging facilities - - - Monitor individual application state - - - Disable only faulty applications - - - Replace only parts of the system - -## The Process Console on imix - -Keeps track of system calls and timeslice expirations for each process. - -Provides basic debugging facilities over UART: - - * `status` - - * `list` - - * `stop [app_name]` - - * `start [app_name]` - -## Our task - - * Fix a "deployed" with two mysteriously named processes: - - `app1` & `app2` - - * Networked over UDP/6LoWPAN - - - One sends button presses - - - Another sends periodic temperature, humidty and light data - - * Functional, but seem to be draining battery! - -_Find and fix the problem!_ - -## Stay in touch! - - - - - - - -\medskip - -\hrule - diff --git a/doc/courses/README.md b/doc/courses/README.md deleted file mode 100644 index e1e8a699a3..0000000000 --- a/doc/courses/README.md +++ /dev/null @@ -1,30 +0,0 @@ -Tock courses -================== - ---- -**NOTE** - -These courses all use Tock version 1 (e.g., 1.2, 1.3). -Tock development is currently on version 2. -The major difference between the two versions is the -system call ABI/API. If you are interested in writing applications on Tock, -the changes appear very small, as userspace libraries hide the differences. -The differences between version 1 and version 2 are most significant if you -want to write system call drivers for kernel services. - -We hope to write a course for version 2 soon. Until then, the -[Tock book](https://book.tockos.org/) is a good place to start. It has -been updated to Tock v2. - ---- - - -Courses are a great way to start learning Tock. They take you to the entire -process of getting started, writing an application, and adding a capsule to the -kernel. There are currently three different courses available, corresponding to -the three conferences we've given workshops at: - -- **[RustConf](rustconf)** - For the user familiar with Rust. -- **[Sensys](2018-11-SenSys)** - For the user familiar with embedded networked sensor systems. -- **[SOSP](sosp)** - For the user familiar with operating systems. - diff --git a/doc/courses/rustconf/README.md b/doc/courses/rustconf/README.md deleted file mode 100644 index 3eaec2fc90..0000000000 --- a/doc/courses/rustconf/README.md +++ /dev/null @@ -1,104 +0,0 @@ ---- -location: Portland, OR, USA -date: August 19th 2017 ---- - -# Tock OS Training @ RustConf 2017 - ---- -**NOTE** - -This course was primarily designed in the summer of 2017. It focuses on -presenting Tock to a Rust (rather than research) audience. It is no longer -updated. For example, this course uses Tock version 1.2, and current Tock -development is on version 2.x. - ---- - - -Put Rust to practice in low-level embedded systems. This training will introduce -cover programming for Tock, a secure embedded operating system for sensor -networks and the Internet of Things, written in Rust. You will learn to write -kernel extensions, the basics of porting Tock to a new platform, and how to -write power- and memory-efficient applications. We will also give an overview of -the system architecture. - -This tutorial assumes basic knowledge of Rust, including ownership, borrowing, -traits, and lifetimes. While not required, it is most appropriate for people who -are familiar with the material covered in the Advanced Rust training, and -attending the morning Intermediate Rust training is highly encouraged. - -## Pre-requisites - -We will go over setting up a development environment during the training. -However, because the WiFi might not provide the fastest Internet connection in -the world, it would be useful to set up the following dependencies ahead of -time: - -1. A laptop running Linux or OS X. Linux in a VM will work just fine, see below - for a pre-made image with all the dependencies. - -2. Command line utilities: wget, make, cmake, git - -4. Python 3 and pip - -5. A local clone of the Tock repository - - $ git clone https://github.com/tock/tock.git - -6. [rustup](http://rustup.rs/). - - $ curl https://sh.rustup.rs -sSf | sh - -8. [arm-none-eabi toolchain](https://developer.arm.com/open-source/gnu-toolchain/gnu-rm/downloads) (version >= 5.2) - - > Note that you can install the version packaged by your Linux distribution, - > but make sure you install the newlib port as well. For instance, on Debian or - > Ubuntu, install both gcc-arm-none-eabi and libnewlib-arm-none-eabi. - -9. [tockloader](https://github.com/tock/tockloader) - - $ pip3 install -U --user tockloader - - > Note: On MacOS, you may need to add `tockloader` to your path. If you - > cannot run it after installation, run the following: - - $ export PATH=$HOME/Library/Python/3.6/bin/:$PATH - - > Similarly, on Linux distributions, this will typically install to - > `$HOME/.local/bin`, and you may need to add that to your `$PATH` if not - > already present: - - $ PATH=$HOME/.local/bin:$PATH - -### Virtual Machine - -If you're comfortable working inside a Debian virtual machine, you can download -an image with all of the dependencies already installed -[here](https://www.dropbox.com/s/5km04herxa9h05w/Tock.ova?dl=0) -(VirtualBox users: -[File → Import Appliance...](https://docs.oracle.com/cd/E26217_01/E26796/html/qs-import-vm.html), -VMWare users: -[File → Open...](https://pubs.vmware.com/workstation-9/index.jsp?topic=%2Fcom.vmware.ws.using.doc%2FGUID-DDCBE9C0-0EC9-4D09-8042-18436DA62F7A.html) -). -The VM account is "user" with password "user". -Feel free to customize it with whichever editors, window managers, etc you like -before the training starts. - -> #### Heads Up! -> There's a small error in the VM configuration. You'll need to manually -> `source ~/.profile` when you open a new terminal to set up all the needed -> paths. - -## Agenda - -The training is divided into three sections, each starting with a short -presentation to introduce some concepts, followed by a practical exercise. - -1. [Getting your environment set up](environment.md) (~1 hour) - -2. [Add a new capsule to the kernel](capsule.md) (~2 hours) - -3. [Write an environment sensing Bluetooth Low Energy - application](application.md) (~1 hour) - diff --git a/doc/courses/rustconf/application.md b/doc/courses/rustconf/application.md deleted file mode 100644 index b10bd3ee69..0000000000 --- a/doc/courses/rustconf/application.md +++ /dev/null @@ -1,353 +0,0 @@ -# Write an environment sensing Bluetooth Low Energy application - -## 1. Presentation: Process overview, relocation model and system call API - -In this section, we're going to learn about processes (a.k.a applications) in -Tock, and build our own applications in Rust. - -## 2. Check your understanding - -1. How does a process perform a blocking operation? Can you draw the flow of - operations when a process calls `delay_ms(1000)`? - -2. What is a Grant? How do processes interact with grants? Hint: Think about - memory exhaustion. - -## 3. Get a Rust application running on Hail - -First, clone the tock-rust-template repository. - - $ git clone https://github.com/tock/tock-rust-template.git - -This is the base for Tock applications written in Rust. Your code goes in the -`src` folder in `main.rs`. The `Cargo` files are Rust build -configurations. The `thumbv7em-tock-eabi.json` and `layout.ld` files are code -compilation configurations. The Makefile uses `cargo` to create ELF files, and -several scripts in `tools/` to build Tock binaries, with all built output going -in the directory `target/thumb7em-tock-eabi/release/`. - -First, lets look at the application code. `main()` is the function called when -the app is started. The base functionality of it creates a Tock console object -and then prints a message through it via the `write!` macro. The -[`alloc`](https://doc.rust-lang.org/beta/alloc/) crate is used to make the -`write_fmt` function inside of `write!` work. Note that `write!` returns a -`Result`, which we call unwrap on to handle. - -We also use the [Tock crate](https://github.com/tock/libtock-rs) -which contains the Rust library for interacting with a Tock kernel. Two pieces -of Tock functionality which we will explain here are the Console and Timer -modules that the Tock crate exports. - -#### Console - -`tock::console::Console` is used to send messages over the USB connection on a -Hail (technically it sends serial data through a UART to an FTDI UART-to-USB -chip, but same difference). Its functions are: - - pub fn new() -> Console - - Creates, initializes, and returns a new Console struct. - - pub fn write(&mut self, string: String) - - Writes a string object to the Console. - -`Console` also implements `fmt::write`, which enables the `write!` macro to -work. We recommend using -[`write!`](https://doc.rust-lang.org/1.5.0/std/macro.write!.html) for this -tutorial, as it allows you to use [format -options](https://doc.rust-lang.org/1.5.0/std/fmt/) while printing. - -#### Timer - -`tock::timer` is used to trigger events at a specific number of seconds in the -future. It has several functions, only one of which will be used today: - - pub fn delay_ms(ms: u32) - - Sleeps until the specified number of milliseconds have passed, at which - point this function will return. Note that this is synchronous, and no - further code will run until the delay is complete. - -### Loading a Rust application - -Now, lets build and load the base template application in `src/main.rs`. - -1. Erase all other applications from the Hail. - - tockloader erase-apps - -2. Build this Rust application. - - make - -3. Load the Rust application. (note: `tockloader install` automatically - searches subdirectories for Tock binaries) - - tockloader install - -4. Check that it worked. - - tockloader listen - -The expected output should look like: - -``` -$ tockloader listen -No device name specified. Using default "tock" -Using "/dev/cu.usbserial-c098e5130012 - Hail IoT Module - TockOS" - -Listening for serial output. -Tock App -``` - -### Creating your own Rust application - -Now that you've got a basic Rust app working, modify it so that it continuously -prints out `Hello World` twice per second. Note the Tock function `delay_ms` as -explained above, as well as the Rust -[loop](https://doc.rust-lang.org/1.6.0/book/loops.html) instruction. - - -## 4. Write an app that periodically samples the on-board sensors - -Now that we have the ability to write Tock applications in Rust, lets do -something a little more complex. The Hail board you are using has several -sensors on it [as shown here](https://github.com/tock/tock/blob/master/boards/hail/media/hail_reva_noheaders_labeled.png). -These sensors include a light sensor, a humidity and temperature sensor, and an -acceleration and magnetic field sensor (marked as accelerometer in the -picture). Each sensing medium can be accessed separately through the Tock -crate, each within the `sensors` module. - -#### Light - -`tock::sensors::AmbientLightSensor` is used to measure ambient light conditions -in [lux](https://en.wikipedia.org/wiki/Lux). Specifically, it uses the sensor -[ISL29035](https://www.intersil.com/en/products/optoelectronics/ambient-light-sensors/light-to-digital-sensors/ISL29035.html). -It has the function: - - pub fn read(&mut self) -> Reading - - Where a Reading in this case is implemented as the type `AmbientLight`, - which is capable of being cast into an `i32` or printed in a message. - -#### Temperature - -`tock::sensors::TemperatureSensor` is used to measure ambient temperature in degrees -Celsius. It uses the [SI7021](https://www.silabs.com/products/sensors/humidity-sensors/Pages/si7013-20-21.aspx) -sensor. It has the function: - - pub fn read(&mut self) -> Reading - - Where a Reading in this case is implemented as the type `Temperature`, which - is capable of being cast into an `i32` or printed in a message. - -#### Humidity - -`tock::sensors::HumiditySensor` is used to measure the ambient -[relative humidity](https://en.wikipedia.org/wiki/Relative_humidity) in -percent. It has the function: - - pub fn read(&mut self) -> Reading - - Where a Reading in this case is implemented as the type `Humdity`, which - is capable of being cast into an `i32` or printed in a message. - -#### Nindedof - -`tock::sensors::Ninedof` is used to read acceleration or magnetic field -strength from the -[FXOS8700CQ](http://www.nxp.com/products/sensors/6-axis-sensors/digital-sensor-3d-accelerometer-2g-4g-8g-plus-3d-magnetometer:FXOS8700CQ). -Note that Hail's hardware implementation of the Ninedof does not include the -traditional rotational sensor. It has the functions: - - pub unsafe fn new() -> Ninedof - - Which creates a new Ninedof struct on which the following functions may be - called. Note that this function is `unsafe` and must be called within an - unsafe block. - - pub fn read_acceleration(&mut self) -> NinedofReading - - Which reads acceleration in [g's](https://en.wikipedia.org/wiki/G-force) in - the x, y, and z orientations. - - pub fn read_magnetometer(&mut self) -> NinedofReading - - Which reads magnetic field strength in - [microTeslas](https://en.wikipedia.org/wiki/Tesla_(unit)) in the x, y, and z - orientations. - -It also has the NinedofReading struct: - - pub struct NinedofReading { - pub x: i32, - pub y: i32, - pub z: i32 - } - - Which has `fmt:Display` implemented for it and thus can be directly printed. - - -### Read sensors in a Tock application - -Using the tock-rust-template, write an application that reads all of the -sensors on Hail and reports their readings over serial. As a bonus, experiment -with turning on/or off an LED when readings are above or below a certain -threshold. - -#### LED - -`tock::led` is used to control lights on Tock boards. On the Hail board, there -are three LEDs: Red, Blue, and Green which can be controlled. The functions in -the LED module are: - - pub fn count() -> isize - - Which returns the number of LEDs available on a board. - - pub fn on(led_num: u32) - - Which turns an LED on, accessed by its number. - - pub fn off(led_num: u32) - - Which turns an LED off, accessed by its number. - - pub fn toggle(led_num: u32) - - Which changes the state of an LED, accessed by its number. - -[Sample Solution](https://gist.github.com/alevy/73d0a1e5c8784df066c86dc5da9d3107). - - -## 5. Extend your app to report through the `ble-env-sense` service - -Finally, lets explore accessing the Bluetooth Low-Energy (BLE) capabilities of -the hardware. The Hail board has an -[nRF51822](https://www.nordicsemi.com/eng/Products/Bluetooth-low-energy/nRF51822) -radio which provides BLE communications. Given that and the sensors available, -we can use Tock to provide the BLE -[Environmental Sensing Service](https://www.bluetooth.com/specifications/assigned-numbers/environmental-sensing-service-characteristics) -(ESS). - -Currently, the Tock libraries for Rust do not support directly -interacting with the BLE radio. However, we can still access BLE by loading an -additional process on the board as a service and sending it commands over -Tock's inter-process communication (IPC) method. - -### Loading the BLE ESS Service - -The BLE ESS service can be found in the main Tock repository under -`userland/examples/services/ble-env-sense`. It accepts commands over IPC and -updates the BLE ESS service, which is broadcasts through the BLE radio. - -Before we load the service though, you should chose modify its name so that -you'll be able to tell your Hail apart from everyone else's (be sure to pick -something short but reasonably unique). On Line 32, change the adv_name to a -string of your choice. For example: - -``` - .adv_name = "AmitHail", -``` - -Once you've changed the name, we can load the service onto the Hail. - -``` -$ tockloader erase-apps -$ cd userland/examples/services/ble-env-sense/ -$ make program -$ tockloader listen -... -[BLE] Environmental Sensing IPC Service -... -``` - -### Using the BLE ESS Service from a Rust application - -Now that we've got the service loaded, we can build our own application to use -it in the tock-rust-template repository. - -**IMPORTANT** - -For this section only, the `layout.ld` file needs to be modified. On Line 18, -the FLASH ORIGIN needs to be changed to `0x00040038`. This places the Rust -application after the BLE ESS service in memory (and will not be necessary soon -when we get Rust applications compiling as position independent code). It -should look like this: - -``` - FLASH (rx) : ORIGIN = 0x00040038, LENGTH = PROG_LENGTH -``` - -#### IPC to the BLE ESS Service - -`tock::ipc::ble_ess` allows for data to be sent to the BLE ESS service via -Tock's inter-process communication mechanism. It has one function: - - pub fn connect() -> Result - - This connects to the BLE ESS service over IPC, returning a - [Result](https://doc.rust-lang.org/std/result/) with a BleEss struct. - -The BleEss struct itself has one function: - - pub fn set_reading(&mut self, sensor: ReadingType, data: I) -> Result<(), ()> - - Which takes a ReadingType and a measurement, and updates it in the - Environmental Sensing Service. - -The `tock::ipc::ble_ess` also has the ReadingType enum: - - pub enum ReadingType { - Temperature = 0, - Humidity = 1, - Light = 2 - } - - Note that the ESS does not accept acceleration or magnetic field strength - measurements. - - -Now that you've got the IPC library, you should be able to write an app that -sends data over BLE. To get you started, here are what the first couple lines -will probably look like: - -``` -#![feature(alloc)] -#![no_std] - -extern crate alloc; -extern crate tock; - -use alloc::fmt::Write; -use tock::console::Console; -use tock::ipc::ble_ess::{self, ReadingType}; -use tock::sensors::{AmbientLightSensor, TemperatureSensor, HumiditySensor, Nindedof}; - -#[inline(never)] -fn main() { - let mut console = Console::new(); - write!(&mut console, "Starting BLE ESS\n").unwrap(); - - let mut ess = match ble_ess::connect() { - Ok(ess) => ess, - _ => { - write!(&mut console, "BLE IPC Service not installed\n").unwrap(); - return - } - }; - write!(&mut console, "Found BLE IPC Service\n").unwrap(); - ... -``` - -To test that everything is working, you can connect to your Hail with a -smartphone. We recommend the nRF Connect app -[[Android](https://play.google.com/store/apps/details?id=no.nordicsemi.android.mcp&hl=en) - | [iOS](https://itunes.apple.com/us/app/nrf-connect/id1054362403?mt=8)]. -The BLE address of the Hail is labeled on its bottom, however iOS devices -cannot access the address of a BLE device. However, you should be able to see -the unique name that you chose earlier. - -[Sample Solution](https://gist.github.com/alevy/a274981a29ffc00230aa16101ee0b89f). - diff --git a/doc/courses/rustconf/architecture.png b/doc/courses/rustconf/architecture.png deleted file mode 100644 index 5cce5a1cda..0000000000 Binary files a/doc/courses/rustconf/architecture.png and /dev/null differ diff --git a/doc/courses/rustconf/capsule.md b/doc/courses/rustconf/capsule.md deleted file mode 100644 index da22382a36..0000000000 --- a/doc/courses/rustconf/capsule.md +++ /dev/null @@ -1,457 +0,0 @@ -### Tock OS Course Part 2: Adding a New Capsule to the Kernel - -The goal of this part of the course is to make you comfortable with the -Tock kernel and writing code for it. By the end of this part, you'll have -written a new capsule that reads a 9DOF (nine degrees of freedom, consisting -of a 3-axis accelerometer, magnetometer, and gyroscope) sensor and outputs -its readings over the serial port. - -During this you will: - -1. Learn how Tock uses Rust's memory safety to provide isolation for free -2. Read the Tock boot sequence, seeing how Tock uses static allocation -3. Learn about Tock's event-driven programming -4. Write a new capsule that reads a 9DOF sensor and prints it over serial - -#### 1. Listen to presentation on Tock's kernel and capsules - -This part of the course will start with a member of the Tock development -team presenting its core software architecture. This will explain how a -Tock platform has a small amount of trusted (can use `unsafe`) code, but -the bulk of the kernel code is in *capsules*, which cannot violate Rust's -safety guarantees. It'll also explain how RAM constraints lead the Tock -kernel to rely on static allocation and use a purely event-driven execution -model. - -This presentation will give you the intellectual framework to understand -why capsules work as they do, and understand what you'll be doing in the rest -of this part of the course. - -#### 2. Check your understanding - -1. What is a `VolatileCell`? Can you find some uses of `VolatileCell`, and do you understand why they are needed? Hint: look inside `chips/sam4l/src`. -2. What is a `TakeCell`? When is a `TakeCell` preferable to a standard `Cell`? - -#### 3. Read the Tock boot sequence (20m) - -Open `boards/hail/src/main.rs` in your favorite editor. This file defines the -Hail platform: how it boots, what capsules it uses, and what system calls it -supports for userland applications. - -##### 3.1 How is everything organized? - -Find the declaration of `struct Hail` (it's pretty early in the file). -This declares the structure representing the platform. It has many fields, -all of which are capsules. These are the capsules that make up the Hail -platform. For the most part, these map directly to hardware peripherals, -but there are exceptions such as `IPC` (inter-process communication). - -Recall the discussion about how everything in the kernel is statically -allocated? We can see that here. Every field in `struct Hail` is a reference to -an object with a static lifetime. - -The capsules themselves take a lifetime as a parameter, which is currently -always `` `static``. The implementations of these capsules, however, do not -rely on this assumption. - -The boot process is primarily the construction of this `Hail` structure. Once -everything is set up, the board will pass the constructed `hail` to -`kernel::main` and we're off to the races. - -##### 3.2 How do things get started? - -The method `reset_handler` is invoked when the chip resets (i.e., boots). -It's pretty long because Hail has a lot of drivers that need to be created -and initialized, and many of them depend on other, lower layer abstractions -that need to be created and initialized as well. - -Take a look at the first few lines of the `reset_handler`. The boot sequence -initializes memory (copies initialized variables into RAM, clears the BSS), -sets up the system clocks, and configures the GPIO pins. - -##### 3.3 How do capsules get created? - -The next lines of `reset_handler` create and initialize the system console, -which is what turns calls to `print!` into bytes sent to the USB serial port: - -```rust -let console = static_init!( - Console, - Console::new(&usart::USART0, - 115200, - &mut console::WRITE_BUF, - kernel::Container::create())); -hil::uart::UART::set_client(&usart::USART0, console); -``` - -Eventually, once all of the capsules have been created, we will populate -a Hail structure with them: - -```rust -let hail = Hail { - console: console, - gpio: gpio, - ... -``` - -The `static_init!` macro is simply an easy way to allocate a static -variable with a call to `new`. The first parameter is the type, the second -is the expression to produce an instance of the type. This call creates -a `Console` that uses serial port 0 (`USART0`) at 115200 bits per second. - -> ##### A brief aside on buffers: -> -> Notice that you have to pass a write buffer to the console for it to use: -> this buffer has to have a `` `static`` lifetime. This is because low-level -> hardware drivers, especially those that use DMA, require `` `static`` buffers. -> Since Tock doesn't promise when a DMA operation will complete, and you -> need to be able to promise that the buffer outlives the operation, the -> one lifetime that is assured to be alive at the end of an operation is -> `` `static``. So that other code which has buffers -> without a `` `static`` lifetime, such as userspace processes, can use the -> `Console`, it copies them into its own internal `` `static`` buffer before -> passing it to the serial port. So the buffer passing architecture looks like -> this: -> -> ![Console/UART buffer lifetimes](console.png) -> -> It's a little weird that Console's `new` method takes in a reference to -> itself. This is an ergonomics tradeoff. The Console needs a mutable static -> buffer to use internally, which the Console capsule declares. However writing -> global statics is unsafe. To avoid the unsafe operation in the Console -> capsule itself, we make it the responsibility of the instantiator to give the -> Console a buffer to use, without burdening the instantiator with sizing the -> buffer. - -The final parameter, the `Container`, is for handling system calls: -you don't need to worry about it for now. - -##### 3.4 Let's make a Hail! - -The code continues on, creating all of the other capsules that are needed -by the hail platform. By the time we get down to around line 360, we've -created all of the capsules we need, and it's time to create the actual -hail platform structure (`let hail = Hail {` ...). - -##### 3.5 Capsule _initialization_ - -Up to this point we have been creating numerous structures and setting some -static configuration options and mappings, but nothing dynamic has occurred -(said another way, all methods invoked by `static_init!` must be `const fn`, -however Tock's `static_init!` macro predates stabilization of `const fn`'s. -A future iteration could possibly leverage these and obviate the need for the -macro). - -Some capsules require _initialization_, some code that must be executed -before they can be used. For example, a few lines after creating the hail -struct, we initialize the console: - -```rust -hail.console.initialize(); -``` - -This method is responsible for actually writing the hardware registers that -configure the associated UART peripheral for use as a text console -(8 data bits, 1 stop bit, no parity bit, no hardware flow control). - -##### 3.6 Inter-capsule dependencies - -Just after initializing the console capsule, we find this line: - -```rust -kernel::debug::assign_console_driver(Some(hail.console), kc); -``` - -This configures the kernel's `debug!` macro to print messages to this console -we've just created. The `debug!` mechanism can be very helpful during -development and testing. Today we're going to use it to print output from the -capsule you create. - -Let's try it out really quick: - -```diff ---- a/boards/hail/src/main.rs -+++ b/boards/hail/src/main.rs -@@ -10,7 +10,7 @@ - extern crate capsules; - extern crate cortexm4; - extern crate compiler_builtins; --#[macro_use(static_init)] -+#[macro_use(debug, static_init)] - extern crate kernel; - extern crate sam4l; - -@@ -388,6 +388,8 @@ pub unsafe fn reset_handler() { - capsules::console::App::default()); - kernel::debug::assign_console_driver(Some(hail.console), kc); - -+ debug!("Testing 1, 2, 3..."); -+ - hail.nrf51822.initialize(); -``` - -Compile and flash the kernel (`make program`) then look at the output -(`tockloader listen`). - - - What happens if you put the `debug!` before `assign_console_driver`? - - What happens if you put `hail.console.initialize()` after - `assign_console_driver`? - -As you can see, sometimes there are dependencies between capsules, and board -authors must take care during initialization to ensure correctness. - -> **Note:** The `debug!` implementation is _asynchronous_. It copies messages -> into a buffer and the console prints them via DMA as the UART peripheral is -> available, interleaved with other console users (i.e. processes). You -> shouldn't need to worry about the mechanics of this for now. - - -##### 3.7 Loading processes - -Once the platform is all set up, the board is responsible for loading processes -into memory: - -```rust -kernel::process::load_processes(&_sapps as *const u8, - &mut APP_MEMORY, - &mut PROCESSES, - FAULT_RESPONSE); -``` - -A Tock process is represented by a `kernel::Process` struct. In principle, a -platform could load processes by any means. In practice, all existing platforms -write an array of Tock Binary Format (TBF) entries to flash. The kernel provides -the `load_processes` helper function that takes in a flash address and begins -iteratively parsing TBF entries and making `Process`es. - -##### 3.8 Starting the kernel - -Finally, the board passes a reference to the current platform, the chip the -platform is built on (used for interrupt and power handling), the processes to -run, and an IPC server instance to the main loop of the kernel: - -```rust -kernel::main(&hail, &mut chip, &mut PROCESSES, &hail.ipc); -``` - -From here, Tock is initialized, the kernel event loop takes over, and the -system enters steady state operation. - -#### 4. Create a "Hello World" capsule - -Now that you've seen how Tock initializes and uses capsules, you're going to -write a new one. At the end of this section, your capsule will sample the -accelerometer from the 9dof sensor once a second and print the results as -serial output. But you'll start with something simpler: printing "Hello World" -to the debug console once on boot. - -To begin, because you're going to be modifying the boot sequence of Hail, -make a branch of the Tock repository. This will keep your master -branch clean. - -```bash -# Possibly undo any changes from exploring debug! above: -$ git reset --hard -$ git checkout -b rustconf -``` - -Next, create a new module in `boards/hail/src` and import it from -`boards/hail/src/main.rs`. In your new module, make a new `struct` for your -capsule (e.g. called `Accelerate`), a `new` function to construct it and a `start` method. - -Eventually, the `start` method will kick off the state machine for periodic -accelerometer readings, but for now, you'll just print "Hello World" to the -debug console and return: - -```rust -debug!("Hello World"); -``` - -Finally, initialize this new capsule in the `main.rs` boot sequence. You'll -want to use the `static_init!` macro to make sure it's initialized in static -memory. `static_init!` is already imported, and has the following signature: - -```rust -static_init!($T:ty, :expr -> $T) -> &'static T -``` - -That is, the first parameter is the type of the thing you want to allocate and -the second parameter is an expression constructing it. The result is a -reference to the constructed value with `'static` lifetime. - -Compile and program your new kernel: - -```bash -$ make program -$ tockloader listen -No device name specified. Using default "tock" Using "/dev/ttyUSB0 - Hail IoT Module - TockOS" -Listening for serial output. -TOCK_DEBUG(0): /home/alevy/hack/helena/rustconf/tock/boards/hail/src/accelerate.rs:18: Hello World -``` - -[Sample Solution](https://gist.github.com/alevy/56b0566e2d1a6ba582b7d4c09968ddc9) - -#### 5. Extend your capsule to print "Hello World" every second - -In order for your capsule to keep track of time, it will need to depend on -another capsule that implements the Alarm interface. We'll have to do something -similar for reading the accelerometer, so this is good practice. - -The Alarm HIL includes several traits, `Alarm`, `Client`, and `Frequency`, -all in the `kernel::hil::time` module. You'll use the `set_alarm` and `now` -methods from the `Alarm` trait to set an alarm for a particular value of the -clock. The `Alarm` trait also has an associated type that implements the -`Frequency` trait which lets us call its `frequency` method to get the clock -frequency. - -Modify your capsule to have a field of the type `&'a Alarm` and to accept an -`&'a Alarm` in the `new` function. - -Your capsule will also need to implement the `Client` trait so it can -receive alarm events. The `Client` trait has a single method: - -```rust -fn fired(&self) -``` - -Your capsule should now set an alarm in the `start` method, print the debug -message and set an alarm again when the alarm fires. - -Finally, you'll need to modify the capsule initialization to pass in an alarm -implementation. Since lots of other capsules use the alarm, you should use a -virtual alarm. You can make a new one like this: - -```rust -let my_virtual_alarm = static_init!( - VirtualMuxAlarm<'static, sam4l::ast::Ast>, - VirtualMuxAlarm::new(mux_alarm)); -``` - -and you have to make sure to set your capsule as the client of the virtual alarm after initializing it: - -```rust -my_virtual_alarm.set_client(my_capsule); -``` - -Compile and program your new kernel: - -```bash -$ make program -$ tockloader listen -No device name specified. Using default "tock" Using "/dev/ttyUSB0 - Hail IoT Module - TockOS" -Listening for serial output. -TOCK_DEBUG(0): /home/alevy/hack/helena/rustconf/tock/boards/hail/src/accelerate.rs:31: Hello World -TOCK_DEBUG(0): /home/alevy/hack/helena/rustconf/tock/boards/hail/src/accelerate.rs:31: Hello World -TOCK_DEBUG(0): /home/alevy/hack/helena/rustconf/tock/boards/hail/src/accelerate.rs:31: Hello World -TOCK_DEBUG(0): /home/alevy/hack/helena/rustconf/tock/boards/hail/src/accelerate.rs:31: Hello World -``` - -[Sample Solution](https://gist.github.com/alevy/73fca7b0dddcb5449088cebcbfc035f1) - -#### 6. Extend your capsule to sample the accelerometer once a second - -The steps for reading an accelerometer from your capsule are similar to using -the alarm. You'll use a capsule that implements the NineDof (nine degrees of -freedom) HIL, which includes the `NineDof` and `NineDofClient` traits, both in -`kernel::hil::sensors`. - -The `NineDof` trait includes the method `read_accelerometer` which initiates an -accelerometer reading. The `NineDofClient` trait has a single method for receiving readings: - -```rust -fn callback(&self, x: usize, y: usize, z: usize); -``` - -However, unlike the alarm, there is no virtualization layer for the `NineDof` -HIL (yet!) and there is already a driver used in Hail that exposes the 9dof -sensor to userland. Fortunately, we can just remove it for our purposes. - -Remove the `ninedof` field from the `Hail` struct definition: - -```rust -ninedof: &'static capsules::ninedof::NineDof<'static>, -``` - -and initialization: - -```rust -ninedof: ninedof, -``` - -Remove `ninedof` from the system call lookup table in `with_driver`: - -```rust -11 => f(Some(self.ninedof)), // Comment this out -``` - -And, finally, remove the initialization of `ninedof` from the boot sequence: - -```rust -let ninedof = static_init!( - capsules::ninedof::NineDof<'static>, - capsules::ninedof::NineDof::new(fxos8700, kernel::Container::create())); -hil::ninedof::NineDof::set_client(fxos8700, ninedof); -``` - -Follow the same steps you did for adding an alarm to your capsule for the 9dof -sensor capsule: - - 1. Add a `&'a NineDof` field. - - 2. Accept one in the `new` function. - - 3. Implement the `NineDofClient` trait. - -Now, modify the Hail boot sequence passing in the `fxos8700` capsule, which -implements the `NineDof` trait, to your capsule (it should already be -initialized). Make sure to set your capsule as its client: - -```rust -{ - use hil::ninedof::NineDof; - fxos8700.set_client(my_capsule); -} -``` - -Finally, implement logic to initiate a accelerometer reading every second and -report the results. - -![Structure of `rustconf` capsule](rustconf.png) - -Compile and program your kernel: - -```bash -$ make program -$ tockloader listen -No device name specified. Using default "tock" Using "/dev/ttyUSB0 - Hail IoT Module - TockOS" -Listening for serial output. -TOCK_DEBUG(0): /home/alevy/hack/helena/rustconf/tock/boards/hail/src/accelerate.rs:31: 982 33 166 -TOCK_DEBUG(0): /home/alevy/hack/helena/rustconf/tock/boards/hail/src/accelerate.rs:31: 988 31 158 -``` - -[Sample solution](https://gist.github.com/alevy/798d11dbfa5409e0aa56d870b4b7afcf) - -#### 7. Some further questions and directions to explore - -Your capsule used the fxos8700 and virtual alarm. Take a look at the -code behind each of these services: - -1. Is the 9DOF sensor on-chip or a separate chip connected over a bus? - -2. What happens if you request two 9DOF sensors (e.g., accelerometer and magnetometer) - - back-to-back? -3. Is there a limit on how many virtual alarms can be created? - -4. How many virtual alarms does the Hail boot sequence create? - -#### 8. **Extra credit**: Write a virtualization capsule for 9dof (∞) - -Remember how you had to remove the userspace facing `ninedof` driver from Hail -in order to use the accelerometer in your capsule? That was a bummer... - -If you have extra time, try writing a virtualization capsule for the `NineDof` -HIL that will allow multiple clients to use it. This is a fairly open ended -task, but you might find inspiration in the `virtua_alarm` and `virtual_i2c` -capsules. - diff --git a/doc/courses/rustconf/console.png b/doc/courses/rustconf/console.png deleted file mode 100644 index 2d26a02e73..0000000000 Binary files a/doc/courses/rustconf/console.png and /dev/null differ diff --git a/doc/courses/rustconf/console.svg b/doc/courses/rustconf/console.svg deleted file mode 100644 index db102e6f59..0000000000 --- a/doc/courses/rustconf/console.svg +++ /dev/null @@ -1,379 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - Application - - - app buffer - - - - Console - UART - - - - static buffer - - - - static buffer - - TakeCell - - copy - - - take - replace - - diff --git a/doc/courses/rustconf/environment.md b/doc/courses/rustconf/environment.md deleted file mode 100644 index f73677830c..0000000000 --- a/doc/courses/rustconf/environment.md +++ /dev/null @@ -1,203 +0,0 @@ -# Tock OS Course Part 1: Getting your environment set up - -> While we're getting set up and started, please make sure you have -> completed all of the [tutorial pre-requisites](./#pre-requisites). -> If you prefer, you can download a -> [virtual machine image](./#virtual-machine) with all the pre-requisites -> already installed. - -The goal of this part of the course is to make sure you have a working -development environment for Tock. - -During this you will: - -- Get a high-level overview of how Tock works. -- Learn how to compile and flash the kernel onto a Hail board. - -## 1. Presentation: Tock's goals, architecture and components - -The key contribution of Tock is that it uses Rust's borrow checker as a -language sandbox for isolation and a cooperative scheduling model for -concurrency in the kernel. As a result, isolation is (more or less) free in -terms of resource consumption at the expense of preemptive scheduling (so a -malicious component could block the system by, e.g., spinning in an infinite -loop). This is accomplished by the following architecture: - -![Tock architecture](architecture.png) - -Tock includes three architectural components: - - - A small trusted kernel, written in Rust, that implements a hardware - abstraction layer (HAL), scheduler, and platform-specific configuration. - - _Capsules_, which are compiled with the kernel and use Rust's type and - module systems for safety. - - _Processes_, which use the MPU for protection at runtime. - -Read the Tock documentation for more details on its -[design](https://www.tockos.org/documentation/design). - -[_Presentation slides are availble here._](presentation/presentation.pdf) - -## 2. Check your understanding - -1. What kinds of binaries exist on a Tock board? Hint: There are three, and - only two can be programmed using `tockloader`. - -2. What are the differences between capsules and processes? What performance - and memory overhead does each entail? Why would you choose to write - something as a process instead of a capsule and vice versa? - -3. Clearly, the kernel should never enter an infinite loop. But is it - acceptable for a process to spin? What about a capsule? - -## 3. Compile and flash the kernel - -### Build the kernel - -To build the kernel, just type make in the root directory, or in boards/hail/. - - $ cd boards/hail/ - $ make - -If this is the first time you are trying to make the kernel, cargo and rustup -will now go ahead and install all the requirements of Tock. - -If you're not in a board folder, -the root Makefile selects a board and architecture to build the kernel for and -routes all calls to that board's specific Makefile. It's set up with -`TOCK_BOARD ?= hail`, so it compiles for the Hail board by default. To change -this default, just change the `TOCK_BOARD` environment variable. For example, -to compile for the imix instead, `export TOCK_BOARD=imix` or `cd boards/imix/`. - -### Connect to a Hail board - -> On Linux, you might need to give your user access to the Hail's serial port. You can do this by adding a udev rule in: -> `/etc/udev/rules.d/99-hail` containing the rule: `ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6015", MODE="0666"`. -> Detaching and re-attaching Hail should do the trick. - -To connect your development machine to the Hail, connect them with a micro-USB -cable. Any cable will do. Hail should come with the Tock kernel and the Hail -test app pre-loaded. When you plug in Hail, the blue LED should blink slowly -(about once per second). Pressing the User Button—just to the right of the USB -plug—should turn on the green LED. - -The Hail board should appear as a regular serial device (e.g. -/dev/tty.usbserial-c098e5130006 on my machine). While you can connect with any -standard serial program (set to 115200 baud), tockloader makes this easier. -Tockloader can read attributes from connected serial devices, and will -automatically find your connected Hail. Simply run: - - $ tockloader listen - No device name specified. Using default "tock" - Using "/dev/ttyUSB0 - Hail IoT Module - TockOS" - - Listening for serial output. - - [Hail] Test App! - [Hail] Samples all sensors. - [Hail] Transmits name over BLE. - [Hail] Button controls LED. - [Hail Sensor Reading] - Temperature: 3174 1/100 degrees C - Humidity: 3915 0.01% - Light: 15 - Acceleration: 987 - ... - -### Flash the kernel - -Now that the Hail board is connected and you have verified that the kernel -compiles, we can flash the Hail board with the latest Tock kernel: - - $ make program - -This command will compile the kernel if needed, and then use `tockloader` to -flash it onto the Hail. When the flash command succeeds, the Hail test app -should no longer be working (i.e. the blue LED will not be blinking). Instead, -the red LED will be blinking furiously, a sign that the kernel has panicked. - -Don't panic! This is because... - -### Clear out the applications and re-flash the test app. - -...the Tock Binary Format (TBF) was recently changed, and is incompatible with the -app pre-loaded on the Hail board. To fix this, clear it out and re-flash it. - - $ tockloader list - ... - [App 0] - Name: hail - Total Size in Flash: 65536 bytes - ... - -As you can see, the old Hail test app is still installed on the board. This -also nicely demonstrates that user applications are nicely isolated from the -kernel: it is possible to update one independently of the other. Remove it with -the following command: - - $ tockloader erase-apps - -The red LED should no longer blink. Compile and re-flash the Hail test app: - - $ cd userland/examples/tests/hail/ - $ make program - -You now have the bleeding-edge Tock kernel running on your Hail board! - -## 4. (Optional) Familiarize yourself with `tockloader` commands -The `tockloader` tool is a useful and versatile tool for managing and installing -applications on Tock. It supports a number of commands, and a more complete -list can be found in the tockloader repository, located at -https://github.com/tock/tockloader. Below is a list of the more useful -and important commands for programming and querying a board. - -### `tockloader install` -This is the main tockloader command, used to load Tock applications onto a -board. Use the `--no-replace` flag to install multiple copies of the same app. -In order to install an app, navigate to the correct directory, make the program, -then issue the install command: - - $ cd tock/userland/examples/blink - $ make - $ tockloader install - -> *Tip:* You can add the `--make` flag to have tockloader automatically -> run make before installing, i.e. `tockloader install --make` - -### `tockloader uninstall [application name(s)]` -Removes one or more applications from the board by name. - -### `tockloader erase-apps` -Removes all applications from the board. - -### `tockloader list` -Prints basic information about the apps currently loaded onto the board. - -### `tockloader info` -Shows all properties of the board, including information about currently -loaded applications, their sizes and versions, and any set attributes. - -### `tockloader listen` -This command prints output from Tock apps to the terminal. It listens via UART, -and will print out anything written to stdout/stderr from a board. - -> *Tip:* As a long-running command, `listen` interacts with other tockloader -> sessions. You can leave a terminal window open and listening. If another -> tockloader process needs access to the board (e.g. to install an app update), -> tockloader will automatically pause and resume listening. - -### `tockloader flash` -Loads binaries onto hardware platforms that are running a compatible bootloader. -This is used by the Tock Make system when kernel binaries are programmed to the -board with `make program`. - -## 5. (Optional) Explore other Tock example applications - -Other applications can be found in the `userland/examples/` directory. Try -loading them on your Hail and then try modifying them. By default, -`tockloader install` adds the new application, but does not erase any others. -Not all applications will work well together if they need the same resources. - -> *Tip:* You can add the `--erase` flag to have tockloader automatically -> remove other applications when installing a new one. - diff --git a/doc/courses/rustconf/goals.txt b/doc/courses/rustconf/goals.txt deleted file mode 100644 index b42d2f205c..0000000000 --- a/doc/courses/rustconf/goals.txt +++ /dev/null @@ -1,18 +0,0 @@ -At the end of the RustConf tutorial, attendees should be able to: - -1) Set up a Hail board, compile Tock, install Tock, compile applications, - and install applications. - -2) Add a new capsule to the kernel (write it, include it in the crate, - initialize it in a boot sequence). - -3) Write a system call interface to a capsule. - -4) Write a new chip driver for a hardware controller. - - Maybe not, but understanding unsafe use here is important. - - If not, maybe _modify_ a controller driver - -5) Write down/diagram what happens when a process makes a blocking - system call (context switch, interrupt setup, wait_for, interrupt - handling, kernel thread, resuming process). - diff --git a/doc/courses/rustconf/presentation/architecture.pdf b/doc/courses/rustconf/presentation/architecture.pdf deleted file mode 100644 index ce6aca54cd..0000000000 Binary files a/doc/courses/rustconf/presentation/architecture.pdf and /dev/null differ diff --git a/doc/courses/rustconf/presentation/execution.pdf b/doc/courses/rustconf/presentation/execution.pdf deleted file mode 100644 index 00d66b3c7a..0000000000 Binary files a/doc/courses/rustconf/presentation/execution.pdf and /dev/null differ diff --git a/doc/courses/rustconf/presentation/hail.png b/doc/courses/rustconf/presentation/hail.png deleted file mode 100644 index 28a460c69f..0000000000 Binary files a/doc/courses/rustconf/presentation/hail.png and /dev/null differ diff --git a/doc/courses/rustconf/presentation/ipc.pdf b/doc/courses/rustconf/presentation/ipc.pdf deleted file mode 100644 index 051a9bf7e9..0000000000 Binary files a/doc/courses/rustconf/presentation/ipc.pdf and /dev/null differ diff --git a/doc/courses/rustconf/presentation/ipc.svg b/doc/courses/rustconf/presentation/ipc.svg deleted file mode 100644 index debff98570..0000000000 --- a/doc/courses/rustconf/presentation/ipc.svg +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - BLE ESSService - - My App - - - allow - - notify - - Kernel - - diff --git a/doc/courses/rustconf/presentation/presentation.pdf b/doc/courses/rustconf/presentation/presentation.pdf deleted file mode 100644 index 85bfe6a5cf..0000000000 Binary files a/doc/courses/rustconf/presentation/presentation.pdf and /dev/null differ diff --git a/doc/courses/rustconf/presentation/process_layout.png b/doc/courses/rustconf/presentation/process_layout.png deleted file mode 100644 index 85932c0c5f..0000000000 Binary files a/doc/courses/rustconf/presentation/process_layout.png and /dev/null differ diff --git a/doc/courses/rustconf/presentation/rng.pdf b/doc/courses/rustconf/presentation/rng.pdf deleted file mode 100644 index fe868b82e3..0000000000 Binary files a/doc/courses/rustconf/presentation/rng.pdf and /dev/null differ diff --git a/doc/courses/rustconf/presentation/rng.svg b/doc/courses/rustconf/presentation/rng.svg deleted file mode 100644 index a90d47a578..0000000000 --- a/doc/courses/rustconf/presentation/rng.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/doc/courses/rustconf/presentation/slides.md b/doc/courses/rustconf/presentation/slides.md deleted file mode 100644 index b33504539b..0000000000 --- a/doc/courses/rustconf/presentation/slides.md +++ /dev/null @@ -1,475 +0,0 @@ ---- -title: Tock Embedded OS Training -date: RustConf 2017 -header-includes: - - \beamertemplatenavigationsymbolsempty - - \usepackage{pifont} - - \newcommand{\cmark}{\color{green}\ding{51}} - - \newcommand{\xmark}{\color{red}\ding{55}} ---- - -> Please make sure you have completed all of the tutorial pre-requisites. If -> you prefer, you can download a virtual machine image with all the -> pre-requisites already installed. - - - - - -## Tock is a... - - 1. Secure - - 2. Embedded - - 3. Operating System - - 4. for Low Resource - - 5. Microcontrollers - -## Secure - -![](snakeoil.jpg) - -## ~~_Secure_~~ Safe - -### Tock has isolation primitives that allow you to build secure systems. - -## Embedded - -### Definition A - -> Kernel, applications and hardware are tightly integrated. - -### Definition B - -> You'll likely be writing the kernel. - -## Operating System - -Tock itself provides services to components in the system: - - * Scheduling - - * Communication - - * Hardware multiplexing - -## Low Resource - - * 10s uA average power draw - - * 10s of kBs of RAM - - * Moderate clock speeds - -## Microcontrollers - -System-on-a-chip with integrated flash, SRAM, CPU and a bunch of hardware -controllers. - -Typically: - - * Communication: UART, SPI, I2C, USB, CAN... - - * External I/O: GPIO, external interrupt, ADC, DAC - - * Timers: RTC, countdown timers - -Maybe... - - * Radio (Bluetooth, 15.4) - - * Cryptographic accelerators - - * Other specialized hardware... - -## Two types of components: capsules and processes - -![](architecture.pdf) - -## Two types of scheduling: cooperative and preemptive - -![](execution.pdf) - -## Agenda Today - - 1. Intro to hardware, tools and development environment - - 2. Add functionality to the Tock kernel - - 3. Write an end-to-end Bluetooth Low Energy environment sensing application. - -# Part 1: Hardware, tools and development environment - -## Hail - -![](hail.png) - -## Binaries on-board - -### Bootloader - -### Kernel - -### Processes - -## Tools - - * `make` (just instrumenting `cargo`) - - * Rust (nightly for `asm!`, compiling `core`, etc) - - * `arm-none-eabi` GCC/LD to link binaries - - * `tockloader` to interact with Hail and the bootloader - -## Tools: `tockloader` - -Write a binary to a particular address in flash - -```bash -$ tockloader flash --address 0x1000 \ - target/thumbv7em-none-eabi/release/hail.bin -``` - -Program a process in Tock Binary Format[^footnote]: - -```bash -$ tockloader install myapp.tab -``` - -Restart the board and connect to the debug console: - -```bash -$ tockloader listen -``` - -[^footnote]: TBFs are relocatable process binaries prefixed with headers like - the package name. `.tab` is a tarball of TBFs for different architectures as - well as a metadata file for `tockloader`. - -## Check your understanding - - 1. What kinds of binaries exist on a Tock board? Hint: There are three, and - only two can be programmed using `tockloader`. - - 2. Can you point to the chip on the Hail that runs the Tock kernel? How about - the processes? - - 3. What steps would you follow to program a processes onto Hail? What about - to replace the kernel? - -## Hands-on: Set-up development environment - - 1. Compile and flash the kernel - - 2. Compile and program `ble-env-sense` service - - 3. (Optional) Add some other apps from the repo, like `blink` and `sensors` - - 4. (Optional) Familiarize yourself with `tockloader` commands - - * `uninstall` - - * `list` - - * `erase-apps` - -# Part 2: The kernel - -## Trusted Computing Base (`unsafe` allowed) - - * Hardware Abstraction Layer - - * Board configuration - - * Event & Process scheduler - - * Rust `core` library - - * Core Tock primitives - -``` -kernel/ -chips/ -``` - -## Capsules (`unsafe` not allowed) - - * Virtualization - - * Peripheral drivers - - * Communication protocols (IP, USB, etc) - - * Application logic - -``` -capsules/ -``` - -## Constraints - -### Small isolation units - -Breaking a monolithic component into smaller ones should have low/no cost - -### Avoid memory exhaustion in the kernel - -No heap. Everything is allocated statically. - -### Low communication overhead - -Communicating between components as cheap as an internal function call. Ideally -inlined. - -## Event-driven execution model - -```rust -pub fn main(platform: &P, chip: &mut C, - processes: &mut [Process]) { - loop { - chip.service_pending_interrupts(); - for (i, p) in processes.iter_mut().enumerate() { - sched::do_process(platform, chip, process); - } - - if !chip.has_pending_interrupts() { - chip.prepare_for_sleep(); - support::wfi(); - } - } -} -``` - -## Event-driven execution model - -```rust -fn service_pending_interrupts(&mut self) { - while let Some(interrupt) = get_interrupt() { - match interrupt { - ASTALARM => ast::AST.handle_interrupt(), - USART0 => usart::USART0.handle_interrupt(), - USART1 => usart::USART1.handle_interrupt(), - USART2 => usart::USART2.handle_interrupt(), - ... - } - } -} -``` - -## Event-driven execution model - -```rust -impl Ast { - pub fn handle_interrupt(&self) { - self.clear_alarm(); - self.callback.get().map(|cb| { cb.fired(); }); - } -} -impl time::Client for MuxAlarm { - fn fired(&self) { - for cur in self.virtual_alarms.iter() { - if cur.should_fire() { - cur.armed.set(false); - self.enabled.set(self.enabled.get() - 1); - cur.fired(); - } - } - } -} -``` - -* * * - -![Capsules reference each other directly, assisting inlining](rng.pdf) - -## The mutable aliases problem - -```rust -enum NumOrPointer { - Num(u32), - Pointer(&mut u32) -} - -// n.b. will not compile -let external : &mut NumOrPointer; -match external { - Pointer(internal) => { - // This would violate safety and - // write to memory at 0xdeadbeef - *external = Num(0xdeadbeef); - *internal = 12345; // Kaboom - }, - ... -} -``` - -## Interior mutability to the rescue - -| Type | Copy-only | Mutual exclusion | Opt. | Mem Opt. | -|----------------|:---------:|:----------------:|:---------:|:--------:| -| `Cell` | \cmark{} | \xmark{} | \cmark{} | \cmark{} | -| `VolatileCell` | \cmark{} | \xmark{} | \xmark{} | \cmark{} | -| `TakeCell` | \xmark{} | \cmark{} | \xmark{} | \cmark{} | -| `MapCell` | \xmark{} | \cmark{} | \cmark{} | \xmark{} | - - -* * * - -```rust -pub struct Fxos8700cq<`a> { - i2c: &`a I2CDevice, - state: Cell, - buffer: TakeCell<`static, [u8]>, - callback: - Cell>, -} - -impl<`a> I2CClient for Fxos8700cq<`a> { - fn cmd_complete(&self, buf: &`static mut [u8]) { ... } -} - -impl<`a> hil::ninedof::NineDof for Fxos8700cq<`a> { - fn read_accelerometer(&self) -> ReturnCode { ... } -} - -pub trait NineDofClient { - fn callback(&self, x: usize, y: usize, z: usize); -} -``` - -## Check your understanding - - 1. What is a `VolatileCell`? Can you find some uses of `VolatileCell`, and do - you understand why they are needed? Hint: look inside `chips/sam4l/src`. - - 2. What is a `TakeCell`? When is a `TakeCell` preferable to a standard - `Cell`? - -## Hands-on: Write and add a capsule to the kernel - - 1. Read the Hail boot sequence in `boards/hail/src/main.rs` - - 2. [Write a new capsule that prints "Hello World" to the debug - console.](https://gist.github.com/alevy/56b0566e2d1a6ba582b7d4c09968ddc9) - - 3. [Extend your capsule to print "Hello World" every second](https://gist.github.com/alevy/798d11dbfa5409e0aa56d870b4b7afcf) - - 4. [Extend your capsule to read and report the accelerometer](https://gist.github.com/alevy/73fca7b0dddcb5449088cebcbfc035f1#file-boot_sequence.rs) - - 5. Extra Credit: Write a 9dof virtualization capsule. - -# Part 3: User space - -## System calls - -| **Call** | **Target** | **Description** | -|:----------|:----------:|----------------------------------| -| command | Capsule | Invoke an operation on a capsule | -| allow | Capsule | Share memory with a capsule | -| subscribe | Capsule | Register an upcall | -| memop | Core | Modify memory break | -| yield | Core | Bloc until next upcall is ready | - -## Rust System calls: `command` & `allow` - -```rust -pub unsafe fn command(major: u32, minor: u32, - arg: isize) -> isize; - -pub unsafe fn allow(major: u32, minor: u32, - slice: &[u8]) -> isize; -``` - -## Rust System calls: `subscribe` - -```rust -type ExternFn = - extern fn (usize, usize, usize, *const usize); - -pub unsafe fn subscribe(major: u32, minor: u32, - cb: ExternFn, ud: *const usize) -> isize { -``` - -## Rust System calls: `yieldk` & `yieldk_for` - -```rust -pub fn yieldk(); - -pub fn yieldk_for bool>(cond: F) { - while !cond() { yieldk(); } -} -``` - -## Example: printing to the debug console - -```rust -pub fn write(string: Box<[u8]>) { - let done: Cell = Cell::new(false); - syscalls::allow(DRIVER_NUM, 1, string); - syscalls::subscribe(DRIVER_NUM, 1, callback, - &done as *const _ as usiz); - syscalls::command(DRIVER_NUM, 1, string.len()); - yieldk_for(|| done.get()) -} - -extern fn callback(_: usize, _: usize, _: usize, - ud: *const usize) { - let done: &Cell = unsafe { - mem::transmute(ud) - }; - done.set(true); -} -``` - -## Inter Process Communication (IPC) - -![](ipc.pdf) - -## Current Rust userland - -### Supported drivers - - * Debug console - - * Timer - - * Sensors: - - * Accelerometer & magnetometer - - * Ambient light - - * Temperature - - * IPC - -### Caveats - - * No global variables allowed! - - * Fixed code offset - - * Blocking library calls only - -## Check your understanding - -1. How does a process perform a blocking operation? Can you draw the flow of - operations when a process calls `delay_ms(1000)`? - -2. What is a Grant? How do processes interact with grants? Hint: Think about - memory exhaustion. - -## Hands-on: Write a BLE environment sensing application - - 1. Get a Rust application running on Hail - - 2. [Periodically sample on-board - sensors](https://gist.github.com/alevy/73d0a1e5c8784df066c86dc5da9d3107) - - 3. [Extend your app to report through the `ble-env-sense` - service](https://gist.github.com/alevy/a274981a29ffc00230aa16101ee0b89f) - diff --git a/doc/courses/rustconf/presentation/snakeoil.jpg b/doc/courses/rustconf/presentation/snakeoil.jpg deleted file mode 100644 index e77953731c..0000000000 Binary files a/doc/courses/rustconf/presentation/snakeoil.jpg and /dev/null differ diff --git a/doc/courses/rustconf/rustconf.png b/doc/courses/rustconf/rustconf.png deleted file mode 100644 index a923dc013e..0000000000 Binary files a/doc/courses/rustconf/rustconf.png and /dev/null differ diff --git a/doc/courses/rustconf/rustconf.svg b/doc/courses/rustconf/rustconf.svg deleted file mode 100644 index 5efe7ffbb4..0000000000 --- a/doc/courses/rustconf/rustconf.svg +++ /dev/null @@ -1,300 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - rustconf - alarm - - fxos8700 - - - - - set - fired - read - callback - - diff --git a/doc/courses/sosp/README.md b/doc/courses/sosp/README.md deleted file mode 100644 index 1575adcf9b..0000000000 --- a/doc/courses/sosp/README.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -location: Shanghai, China -date: October 28th ---- - -# Tock OS Training @ SOSP 2017 - ---- -**NOTE** - -This course was primarily designed in the summer of 2017. It focuses on -presenting Tock to an operating systems research audience. It is no longer -updated. For example, this course uses Tock version 1.2, and current Tock -development is on version 2.x. - ---- - - -This course introduces you to Tock, a secure embedded operating system for sensor -networks and the Internet of Things. Tock is the first operating system to -allow multiple untrusted applications to run concurrently on a microcontroller-based -computer. The Tock kernel is written in Rust, a memory-safe systems language that -does not rely on a garbage collector. Userspace applications are run in -single-threaded processes that can be written in any language. A paper -describing Tock's goals, design, and implementation will be presented at the -conference on Monday and is available [here](https://www.amitlevy.com/papers/tock-sosp2017.pdf). - -This course is based on TockOS as it was in October, 2017. It serves as a useful -introduction to Tock, but it is not updated as Tock evolves. To view this -tutorial in the context it was originally designed for, please checkout the -correct commit hash: - -```bash -$ git checkout 3c12437d23b83db896a5a8e218a3dc14468ce2df -``` - -This commit includes a working version of this tutorial. diff --git a/doc/debugging/VSCode_Debugging.md b/doc/debugging/VSCode_Debugging.md deleted file mode 100644 index f74dbce5c0..0000000000 --- a/doc/debugging/VSCode_Debugging.md +++ /dev/null @@ -1,66 +0,0 @@ -VSCode Debugging -======== - -This is a guide on how to perform remote debugging via JTAG in Tock using VSCode -(at the moment (Feb 2018) nRF51-DK and nRF52-DK are supported). - -## Requirements -1. [VSCode](https://code.visualstudio.com) -2. [VSCode Native Debug Extension](https://github.com/WebFreak001/code-debug) -3. [VSCode Rust Extension](https://github.com/editor-rs/vscode-rust) - -## Installation -1. Install VSCode for your platform -2. Open VSCode -3. Enter the extensions menu by pressing `View/Extensions` -4. Install `Native Debug` and `Rust` in that view by searching for them - -You are now good to run the debugger and the debugging configurations are -already set for you. But, if you want change the configuration, for example to -run some special GDB commands before starting, you can do that -[here](../../.vscode/launch.json). - -## Enabling breakpoints -Let's now test if this works by configuring some breakpoints: - -1. Enter `Explorer mode` by pressing `View/Explorer` -2. Browse and open a file where you want to enable a breakpoint -3. In my case I want to have a breakpoint in the `main` in main.rs -4. Click to the left of the line number to enable a breakpoint. You should see a - red dot now as the figure below: - - ![Enable breakpoint VSCode](figures/vsc_breakpoint.png) - -## Running the debugger -1. You need to start the `GDB Server` before launching a debugging session in - VSCode (check out the instructions for how to do that for your board). -2. Enter `Debug mode` in VSCode by pressing `View/Debug`. You should now see a - debug view somewhere on your screen as in the figure below: - - ![VSCode Debug mode](figures/vsc_debug_view.png) - -3. Choose your board in the scroll bar and then click on the green arrow or - `Debug/Start Debugging`. -4. You should now see that program stopped at the breakpoint as the figure below: - - ![Running](figures/vsc_running.png) - -5. Finally, if want to use specific GDB commands you can use the debug console - in VSCode which is very useful. - - -## Issues -1. Sometimes GDB behaves unpredictably and stops at the wrong source line. For - example, sometimes we have noticed that debugger stops at - `/kernel/src/support/arm.rs` instead of the `main`. If that occurs - just press `step over` and it should hopefully jump to correct location. -2. Rust in `release mode` is optimizing using things such as inlining and - mangling which makes debugging harder and values may not be visible. To - perform more reliable debugging mark the important functions with: - - ``` - #[no_mangle] - #[inline(never)] - ``` -3. Enable `rust-pretty printer` or something similar because viewing variables - is very limited in VSCode. diff --git a/doc/debugging/figures/vsc_breakpoint.png b/doc/debugging/figures/vsc_breakpoint.png deleted file mode 100644 index 67c1985776..0000000000 Binary files a/doc/debugging/figures/vsc_breakpoint.png and /dev/null differ diff --git a/doc/debugging/figures/vsc_debug_view.png b/doc/debugging/figures/vsc_debug_view.png deleted file mode 100644 index d42897c742..0000000000 Binary files a/doc/debugging/figures/vsc_debug_view.png and /dev/null differ diff --git a/doc/debugging/figures/vsc_running.png b/doc/debugging/figures/vsc_running.png deleted file mode 100644 index b5a484622d..0000000000 Binary files a/doc/debugging/figures/vsc_running.png and /dev/null differ diff --git a/doc/process_memory_layout.png b/doc/process_memory_layout.png deleted file mode 100644 index 1a140102d6..0000000000 Binary files a/doc/process_memory_layout.png and /dev/null differ diff --git a/doc/processram.png b/doc/processram.png deleted file mode 100644 index b1c32146b4..0000000000 Binary files a/doc/processram.png and /dev/null differ diff --git a/doc/reference/README.md b/doc/reference/README.md index e6fb603094..b08cbc4701 100644 --- a/doc/reference/README.md +++ b/doc/reference/README.md @@ -8,6 +8,6 @@ Tock Reference Documents - **[TRD 1](trd1-trds.md)**: Tock Reference Documents - **[TRD 102](trd102-adc.md)**: ADC -- **[TRD 103](trd103-adc.md)**: GPIO -- **[TRD 104](trd104-adc.md)**: Syscalls -- **[TRD 105](trd105-adc.md)**: Time +- **[TRD 103](trd103-gpio.md)**: GPIO +- **[TRD 104](trd104-syscalls.md)**: Syscalls +- **[TRD 105](trd105-time.md)**: Time diff --git a/doc/reference/trd-appid.md b/doc/reference/trd-appid.md new file mode 100644 index 0000000000..69933549cf --- /dev/null +++ b/doc/reference/trd-appid.md @@ -0,0 +1,957 @@ +Application IDs (AppID), Credentials, and Process Loading +======================================== + +**TRD:**
+**Working Group:** Kernel
+**Type:** Documentary
+**Status:** Draft
+**Author:** Philip Levis, Johnathan Van Why
+**Draft-Created:** 2021/09/01
+**Draft-Modified:** 2022/10/14
+**Draft-Version:** 10
+**Draft-Discuss:** tock-dev@googlegroups.com
+ +Abstract +------------------------------- + +This document describes the design and implementation of application +identifiers (AppIDs) in the Tock operating system. AppIDs provide a +mechanism to identify the application contained in a userspace binary +that is distinct from a process identifier. AppIDs allow the kernel +to apply security policies to applications as their code evolves and +their binaries change. A board defines how the kernel verifies AppIDs +and which AppIDs the kernel will load. This document describes the +Rust traits and software architecture for AppIDs as well as the +reasoning behind them. This document is in full compliance with +[TRD1][TRD1]. + +1 Introduction +=============================== + +The Tock kernel needs to be able to manage and restrict what userspace +applications can do. Examples include: + - making sure other applications cannot access an application's sensitive + data stored in non-volatile memory, + - restricting certain system calls to be used only by trusted applications, + - run and load only applications that a trusted third party has signed. + +In order to accomplish this, the kernel needs a way to identify an +application and know whether a particular userspace binary belongs to +an application. Multiple binaries can be associated with a single +application. For example, software updates may cause a system to have +more than one version of an application, such that it can roll back to +the old version if there is a problem with the new one. In this case, +there are two different userspace binaries, both associated with the +same application. + +To remain flexible and support many use cases, the Tock kernel makes +minimal assumptions on the structure and form of _application credentials_ +and corresponding _application identifiers_. +Application credentials are arbitrary k-byte sequences that +are stored in a userspace binary's Tock binary format (TBF) +footers. +Before a process is eligible to execute, a Tock board uses an AppID (application +identifier) checker to determine the AppIDs of each userspace binary available +on the board and decide whether to load the binary into a process. + +The Tock kernel ensures that each running process has a unique +application identifier; if two userspace binaries have the same AppID, +the kernel will only permit one of them to run at any time. + +Most of the complications in AppIDs stem from the fact that they are a +general mechanism used for many different use cases. Therefore, the +exact structure and semantics of application credentials can vary +widely. Tock's TBF footer formats, kernel interfaces and mechanisms +must accommodate this wide range. + +The interfaces and standard implementations for AppIDs and AppID +checkers are in the kernel crate, in the module +`process_checker`. There are three main traits: + + * `kernel::process_checker::AppCredentialsPolicy` is responsible + for defining which types of application credentials the kernel + accepts and whether it accepts a particular application credential + for a specific application binary. The kernel only loads userspace + programs that the `AppCredentialsPolicy` accepts. + + * `kernel::process_checker::AppUniqueness` compares the application + identifiers of two processes and reports whether they differ. The + kernel uses this trait to ensure that each running process has a + unique application identifier. + + * `kernel::process_checker::Compress` compresses application + identifiers into short, 32-bit identifiers called + `ShortId`s. `ShortId`s provide a mechanism for fast comparison, + e.g., for an application identifier against an access control list. + +Example implementations can be found in +`kernel::process_checker::basic`. + +In normal use of Tock, a software tool running on a host copies TBF +Objects into an application flash region. When the Tock kernel boots, +it scans this application flash region for TBF Objects. After +inspecting the Userspace Binary, TBF headers, and TBF Footers +in a TBF Object, the kernel assigns it an Application Identifier and +decides whether to run it. + +2 Terminology +=============================== + +This document uses several terms in precise ways. Because these terms +overlap somewhat with general terminology in the Tock kernel, this +section defines them for clarity. The Tock kernel often uses the term +"application" to refer to what this document calls an "Application +Binary." + +**Userspace Binary**: a code image compiled to run in a Tock process, +consisting of text, data, read-only data, and other segments. + +**TBF Object**: a [Tock binary format][TBF] object stored on a Tock +device, containing TBF headers, a Userspace Binary, and TBF footers. +TBF Objects are typically generated from ELF files using the +[`elf2tab`](https://github.com/tock/elf2tab) tool and are the +standard binary format for Tock userspace processes. + +**Application**: userspace software developed and maintained by an +individual, group, corporation, or organization that meets the +requirements of a Tock device use case. An Application can have +multiple Userspace Binaries, e.g., to support versioning. + +**Application Identifier**: a numerical identifier for an application. +Each loaded process has a single Application Identifier. Application +Identifiers are not unique across loaded processes: multiple loaded +processes can share the same application identifier. Application +Identifiers, however, are unique across *running* processes. If +multiple loaded processes share the same Application Identifier, at +most one of them can run at any time. An Application Identifier can be +persistent across boots or restarts of a userspace binary. The Tock +kernel assigns Application Identifiers to processes using a +Identifier Policy. + +**Application Credentials**: metadata that establish integrity of a +Userspace Binary. +Application Credentials are usually stored in [Tock +Binary Format][TBF] footers. A TBF object can have multiple +Application Credentials. + +**Process Checker**: the component of the Tock kernel which is +responsible for validating Application Credentials and determining +which Application Credential (if any) the kernel should apply to a +process. + +**Identifier Policy**: the algorithm that the Process Checker uses to +assign Application Identifiers to processes. An Identifier Policy +defines an Application Identifier space. + +**Credentials Checking Policy**: the algorithm that the Process +Checker uses to decide how Tock responds to particular Application +Credentials. The boot sequence typically passes the Credentials +Checking Policy to the Process +Checker to use when loading processes. + +**Global Application Identifier**: an Application Identifier which, +given an expected combination of Credentials Checking Policy and +Identifier Policy, is both globally consistent across all TBF objects +for a particular Application and unique to that Application. All +instances of the Application loaded with this combination of policies +have this Application Identifier. No instances of other Applications +loaded with this Credentials Checking Policy have this Application +Identifier. One example of a Global Application Identifier is a public +key used to verify the digital signature of every TBF Object of a +single Application. Another example of a Global Application Identifier +is a string name stored in a TBF Object header; in this case the party +installing TBF Objects needs to make sure there are no unintended +collisions between these string names. + +**Locally Unique Application Identifier**: a special kind of +Application Identifier that is by definition unique from all other +Application Identifiers. Locally Unique Application Identifiers do +not have a concrete value that can be examined or stored. All tests +for equality with a Locally Unique Application Identifier return +false. Locally Unique Application Identifiers exist in part to be an +easy way to indicate that a process has no special privileges and its +identity is irrelevant from a security standpoint. + +**Short ID**: a 32-bit compressed representation of an Application +Identifier. Application Identifiers can be large (e.g., an RSA key) or +expensive to compare (a string name); Short IDs exist as a way for an +Identifier Policy to map Application Identifiers to a small identifier +space in order to improve both the space and time costs of checking +identity. + +3 Application Identifiers and Application Credentials +=============================== + +Application Identifiers and Application Credentials are related but +they are not the same thing. An Application Identifier is a numerical +representation of the Application's identity. Application Credentials +are data that, combined with an Identifier Policy, can +cryptographically bind an Application Identifier to a process. + +For example, suppose there are two versions (v1.1 and v1.2) of the +same Application. They have different Userspace Binaries. Each version +has an Application Credentials consisting of a signature over the TBF +headers and Userspace Binary, signed by a known public key. The +Identifier Policy is that the public key defines the Application +Identifier: all versions of this Application have Application +Credentials signed by this key. The two versions have different +Application Credentials, because their hashes differ, but they have +the same Application Identifier. + +3.1 Application Identifiers +------------------------------- + +The key restriction Application Identifiers impose is that the kernel +MUST NOT simultaneously run two processes that have the same +Application Identifier. This restriction is because an Application +Identifier provides an identity for a Userspace Binary. Two processes +with the same Application Identifier are two copies or versions of the +same Application. As Application Identifiers are used to control +access to resources such as storage, this restriction ensures there is +at most one process accessing resources or data belonging to an +Application Identifier, which precludes the need for consistency +mechanisms for concurrent access. + +Application Identifiers can be used for security policy decisions in +the rest of the kernel. For example, a kernel may allow only +Applications whose Application Credentials use a particular trusted +public key to access restricted functionality, but restrict other +applications to use a subset of available system calls. By defining +the Application Identifier of a process to be the public key, the +system can map this key to a Short ID (described below) that gives +access to restricted functionality. + +The Tock kernel assigns each Tock process a unique process identifier, +which can be re-used over time (like POSIX process identifiers). These +process identifiers are separate from and unrelated to Application +Identifiers. An Application Identifier identifies an Application, +while a process identifier identifies a particular execution of a +binary. For example, if a Userspace Binary exits and runs a second +time, the second execution will have the same Application Identifier +but may have a different process identifier. + +3.1.1 Global Application Identifiers +------------------------------- + +Global Application Identifiers are a class of Application Identifiers +that have properties which make them useful for security policies. +For Applications that use Global Application Identifiers, the +combination of the Application Credentials put in TBF Objects, +Credentials Checking Policy, and Identifier Policy establish a +one-to-one mapping between Applications and Global Application +Identifiers. If an Application has a Global Application Identifier, +then every process running that Application has that Global +Application Identifier. Conversely, that Global Application Identifier +is unique to that Application; two Applications do not share a Global +Application Identifier. + +One important implication of this mapping is that Global Application +Identifiers MUST persist across process restarts or reloads. + +Poor management of Global Application Identifiers can lead to +unintended collisions. For example, an Identifier Policy might define +the Global Application Identifier of processes to be the public key of +a key pair to sign an Application Credential. If a developer +accidentally uses the wrong key to sign a Userspace Binary, the Tock +kernel will think that Userspace Binary is a different Application. +Similarly, if the Identifier Policy uses a string name in a TBF Object +header as the Global Application Identifier, then incorrectly giving +two different programs the same name could lead them to sharing data. + +3.1.2 The "Locally Unique" Identifier +------------------------------- + +Some Tock use cases do not require a real notion of Application +identity. In many research or prototype systems, for example, every +Userspace Binary has complete access to the system and there is no +need for persistent storage or identity. Running processes need an +Application Identifier, but in these cases it is not necessary for a +Tock kernel and Application build system to manage Global Application +Identifiers. + +In such use cases, the Identifier Policy can assign a special +Application Identifier called the "Locally Unique Identifier". This +identifier does not have a concrete value: it is simply a value that +is by definition different from all other Application +Identifiers. Because it does not have a concrete value, one cannot +test for equality with Locally Unique Application Identifier. All +comparisons with a Locally Unique Application Identifier return false. + +3.2 Application Credentials +------------------------------- + +Application Credentials are information stored in TBF Footers. The +exact format and information of Application Credentials are described +in the next section. They typically store cryptographic information +that establishes the Application a Userspace Binary belongs to as well +as provide integrity. + +Application Identifiers can, but do not have to be, be derived from +Application Credentials. For example, a Tock system with a permissive +Credentials Checking Policy may allow processes with no Application +Credentials to run, and have an Identifier Policy that defines +Application Identifiers to be the ASCII name stored in a TBF header. + +In cases when a TBF Object does not have any Application Credentials, +the Identifier Policy MAY assign it a Global Application Identifier. +This identifier must follow all of the requirements in Section 3.1.1. + +3.3 Example Use Cases +------------------------------- + +The following five use cases demonstrate different ways in which +Application Policies can assign Application Identifiers, some of which +use Application Credentials: + + 1. **A research system that (memory permitting) runs every Userspace + Binary loaded on it.** The Identifier Policy assigns every Userspace + Binary a Locally Unique Application Identifier and the Credentials + Checking Policy approves TBF Objects independently of their + credentials. + + 1. **A system which runs only a small number of pre-defined + Applications and an Application is defined by a particular public + RSA key.** The Credentials Checking Policy only accepts TBF Objects + with an Application Credentials containing an RSA signature from a + small number of pre-approved keys. The Identifier Policies defines + that the Global Application Identifier of a process is the public + key used to generate the accepted Application Credentials for the + TBF Object. Before verifying a signature in a TBF footer, the + Process Checker decides whether to it accepts the associated public + key using the Credentials Checking Policy. The Identifier Policy + assigns a Global Application Identifier as the public key in the TBF + footer. + + 1. **A system which runs any number of Applications but all + Applications must be signed by a particular RSA key.** The + Credentials Checking Policy only accepts TBF Objects with a + Credentials of an RSA signature from the approved key. The + Identifier Policy defines the Application Identifier as the UTF-8 + encoded package name stored in the TBF Header (or "" if none is + stored). Two Userspace Binaries with the same package name will not + run concurrently. + + 1. **A system that loads the same Userspace Binary in multiple + different processes at the same time.** The Identifier Policy + assigns a Userspace Binary a Locally Unique Identifier. If the + Userspace Binary needs integrity or authenticity then the + Credentials Checking Policy can require signatures. This differs + from the first example in that a single Userspace Binary can be + loaded into multiple processes, instead of loading each Userspace + Binary once. The use cases are different but can (in terms of + identifiers and credentials) implemented the same way. + +As the above examples illustrate, Application Credentials can vary in +size and content. The credentials that a kernel's Credentials Checking +Policy will accept depends on its use case. Certain devices might only +accept Application Credentials which include a particular public key, +while others will accept many. Furthermore, the internal format of +these credentials can vary. Finally, the cryptography used in +credentials can vary, either due to security policies or certification +requirements. + +Because the Identifier Policy is responsible for assigning +Application Identifiers to processes, it is possible for the same +Userspace Binary to have different Application Identifiers on +different Tock systems. For example, suppose a TBF Object has two +Application Credentials TBF footers: one signs with a key A, and the +other with key B. Tock systems using a Credentials Checking Policy +that accepts key A may use A as the Global Application Identifier, +while Tock systems using a different policy that accepts key B may +use B as the Global Application Identifier. + +4 Process Loading +=============================== + +Tock defines its process loading algorithm in order to provide +deterministic behavior in the presence of colliding Application +Identifiers. This algorithm is designed to protect against downgrade +attacks and misconfiguration. + +The process loading operation consists of three stages: + +1. When it boots, the Tock kernel scans for a TBF Object stored in its + application flash region. While parsing the TBF Object, the kernel checks + that the TBF Object is valid and can run on the system (e.g., do not require + a newer kernel version). + +2. After finding a valid and suitable TBF Object, the kernel checks the + credentials of the TBF Object. Using the provided Credentials Checking Policy + (described in Section 6), it decides whether the process has permission to + run. If the TBF Object is allowed to run, the kernel loads the process binary + into a slot in the process binaries array. + +3. Each process in the process binaries array is runnable in terms of its + credentials. However, at any given time it might not be allowed to run + because its Application Identifier or Short ID conflicts with another + process. The kernel scans the array of process binaries and determines + whether to run the process based on its Application Identifier, Short ID, and + the Application Binary version number (stored in the Program Header, + described in Section 5.1). At boot, the kernel starts a process if either of: + + - The process has a unique Application Identifier and Short ID, + - The process has a higher Application Binary version number than + all processes it shares its Application Identifier or Short ID with, + + If two processes which share a Short ID or Application ID have the + same version number, the kernel starts one of them. The one which + starts is the first one discovered in the process binaries array. + + Once a process is determined to be runnable based on credentials and + uniqueness, the process is loaded into a slot in the processes array. At this + point the process will be run. + +Once a Tock system is running, management interfaces may change the set +of running processes from those which the boot sequence +selected. E.g., the process console might terminate a process so that +it can run a different process with the same Short ID and a lower +Userspace Binary version number (rollback). The kernel maintains that +a running process has a unique Application Identifier and a unique +Short ID among running processes. + +5 Credentials and Version in Tock Binary Format Objects +=============================== + +This section describes the format and semantics of Program Headers and +Credentials Footers. + +Application Credentials are usually stored in a TBF Object, along with +the Userspace Binary they are associated with. They are usually stored +as footers (after the TBF header and Userspace Binary) to simplify +computing integrity values such as checksums or hashes. This requires +that TBF Objects +have a TBF header that specifies where the application binary ends and +the footers begin, information which the `TbfHeaderV2Main` header (the +Main Header) does not include. Including Application Credentials in a +TBF Object therefore requires using an alternative +`TbfHeaderV2Program` header (the Program Header), which specifics +where footers begin. + +The Tock process loading algorithm uses version numbers when deciding the +which processes with the same Application Identifier to run. +Version numbers are stored in a TBF Object +in the Version field of a TBF Program Header. + + + + +5.1 Program Header +------------------------------- + +The Program Header is similar to the Main Header, in that it specifies +the offset of the entry function of the executable and memory +parameters. It adds one field, `binary_end_offset`, which indicates +the offset at which the Userspace Binary ends within the TBF +object. The space between this offset and the end of the TBF object is +reserved for footers. + +This is the format of a Program Header: + +``` +0 2 4 6 8 ++-------------+-------------+---------------------------+ +| Type (9) | Length (16) | init_fn_offset | ++-------------+-------------+---------------------------+ +| protected_size | min_ram_size | ++---------------------------+---------------------------+ +| binary_end_offset | version | ++---------------------------+---------------------------+ +``` + +It is represented in the Tock kernel with this Rust structure: + +```rust +pub struct TbfHeaderV2Program { + init_fn_offset: u32, + protected_size: u32, + minimum_ram_size: u32, + binary_end_offset: u32, + version: u32, +} +``` + +A TBF object MUST NOT have more than one Program Header. If a TBF +Object has both a Program Header and a Main Header, the kernel's +policy decides which is used. For example, older kernels that do not +understand a Program Header may use the Main Header, while newer +kernels may choose the Program Header. + +5.2 Credentials Footer +------------------------------- + +To support credentials, the Tock Binary Format has a +`TbfFooterV2Credentials` TLV. This TLV is variable length and has two +fields, a 32-bit value specifying the `format` of the credentials and +a variable length `data` field. The `format` field defines the format +and size of the `data` field. Each value of the `format` field except +`Reserved` MUST have a fixed data size and format. This is the format +of a Credentials Footer: + +``` +0 2 4 8 ++-------------+-------------+---------------------------+ +| Type (128) | Length | format | ++-------------+-------------+---------------------------+ +| data | ++-------------+--------...--+ +``` + +It is represented in the Tock kernel with this structure: + +```rust +pub struct TbfFooterV2Credentials { + format: TbfFooterV2CredentialsType, + data: &[u8], +} +``` + +Which types of credentials a Credentials Checking Policy supports are +kernel-specific. For example, an application that only accepts TBF +Objects signed with a particular 4096-bit RSA key can support only +those credentials, while an open research system might support +no credentials. Because the `length` field specifies the length of a +given credentials, not understanding a particular credentials type +does not prevent parsing others. + +5.3 Integrity Region +------------------------------- + +`TbfFooterV2Credentials` follow the compiled app binary in a TBF +object. If a `TbfFooterV2Credentials` footer includes a cryptographic +hash, signature, or other value to check the integrity of a process +binary, this value MUST be computed over the TBF Header and Userspace +Binary, from the start of the TBF object until +`binary_end_offset`. +This region is called the integrity region. +Computing an integrity value in a Credentials +Footer MUST NOT include the contents of Footers. If new metadata +associated with an application binary needs to be covered by +integrity, it MUST be a Header. If new metadata associated with an +application binary needs to not be covered by integrity, it MUST be a +Footer. + +The integrity region is from the end of the TBF Header to the +location indicated by the `binary_end_offset` field in the Program +Header. The size of the integrity region slice is therefore equal to +`binary_end_offset`. + +6 Credentials Checking Policy: the `AppCredentialsPolicy` trait +=============================== + +The `AppCredentialsPolicy` trait defines the interface that implements +the Credentials Checking Policy of the Process Checker: it accepts, +passes on, or rejects Application Credentials. When a Tock board asks +the kernel to load processes, it passes a reference to a +`AppCredentialsPolicy`, which the kernel uses to check credentials. +An implementer of `AppCredentialsPolicy` sets the security policy of +Userspace Binary loading by deciding which types of credentials, and +which credentials, are acceptable and which are rejected. + +```rust +pub enum CheckResult { + Accept, + Pass, + Reject +} + +pub trait Client<'a> { + fn check_done(&self, + result: Result, + credentials: TbfFooterV2Credentials, + integrity_region: &'a [u8]); +} + +pub trait AppCredentialsPolicy<'a> { + fn set_client(&self, client: &'a dyn Client<'a>); + fn require_credentials(&self) -> bool; + fn check_credentials(&self, + credentials: TbfFooterV2Credentials, + integrity_region: &'a [u8]) -> + Result<(), (ErrorCode, TbfFooterV2Credentials, &'a [u8])>; +} +``` + +If the kernel has been instructed to check credentials of Userspace +Binaries, after it successfully parses a Userspace Binary +it checks the credentials of the process binary. + +To check the integrity of a process, the kernel scans the footers in +order, starting at the beginning of that process's footer region. At +each `TbfFooterV2Credentials` footer it encounters, the kernel calls +`check_credentials` on the provided `AppCredentialsPolicy`. If +`check_credentials` returns `CheckResult::Accept`, the kernel stops processing +credentials and stores the process binary in the process binaries array. +If the +`AppCredentialsPolicy` returns `CheckResult::Reject`, the kernel stops processing +credentials and does not load the process binary. + +If the `AppCredentialsPolicy` returns `CheckResult::Pass`, the kernel tries the +next `TbfFooterV2Credentials`, if there is one. If the kernel reaches +the end of the TBF Footers (or if there is a Main Header and so no +Footers) without encountering a `Reject` or `Accept` result, it calls +`require_credentials` to ask the `AppCredentialsPolicy` what the +default behavior is. If `require_credentials` returns `true`, the +kernel does not load the process binary. +If `require_credentials` returns +`false`, the kernel loads the process binary into the process binaries array. +If a process binary +has no `TbfFooterV2Credentials` footers then there will be no `Accept` +or `Reject` results and `require_credentials` defines whether the +Userspace Binary is runnable. + +The `binary` argument to `check_credentials` is a reference to +the integrity region of +the process binary. + +7 Identifier Policy: the `AppUniqueness` trait +============================== + +The `AppUniqueness` trait defines the API the Process Checker provides +to decide whether two processes have the same Application Identifier +or Short ID. An implementer of `AppUniqueness` implements the +`different_identifier` method, which performs a pairwise comparison of +two processes. + +```rust +trait AppUniqueness { + // Returns true if the two processes have different application + // identifiers. + fn different_identifier(&self, + processA: &ProcessBinary, + processB: &ProcessBinary) -> bool; + + fn different_identifier_process(&self, + processA: &ProcessBinary, + processB: &dyn Process) -> bool; + + fn different_identifier_processes(&self, + processA: &dyn Process, + processB: &dyn Process) -> bool; +} +``` + +This interfaces encapsulate the methods by which a module assigns or +calculates application identifiers. As process binaries must be compared +to both other process binaries and already loaded processes, there are +two version of the `different_identifier` method to support both cases. + +8 Short IDs and the `Compress` trait +=============================== + +While `TbfFooterV2Credentials` often define the identity and +credentials of an application, they are large data structures that are +too large to store in RAM. When parts of the kernel wish to apply +security or access policies based on Application Identifiers, they +need a concise way to represent these identifiers. Requiring policies +to be encoded in terms of raw Application Identifiers can be extremely +costly: a table, for example, that says that only Applications signed +with a particular 4096-bit RSA key can access certain system calls +requires storing the whole 4096-bit key. If there are multiple such +security policies through the kernel, they must each store this +information. + +The `Compress` trait provides a mechanism to map an Application +Identifier to a small (32-bit) integer called a Short ID. Short IDs +can be used throughout the kernel as an identifier of an Application. + +For example, suppose that a device wants to grant access to all +Userspace Binaries signed by a certain 3072-bit RSA key K and has no +other security policies. The Credentials Checking Policy only accepts +3072-bit RSA credentials with key K. The `Compress` trait +implementation assigns a Short ID based on a string match with the +process package name, with certain names receiving particular Short +IDs. Access control systems within the kernel can define their +policies in terms of these identifiers, such that they can check +access by comparing 32-bit integers rather than 384-byte keys. + +Short IDs support the concept of a "Locally Unique" identifier by +having a special `LocallyUnique` value. All tests for equality with +`ShortId::LocallyUnique` return false. + +8.1 Short ID Properties and Examples +------------------------------- + +Given a particular combination of deterministic Identifier Policy and +Credentials Checking Policy, Short IDs have two requirements. They + + 1. MUST be unique across running processes, + 1. MUST be consistent across all running instances of an Application + on Tock systems. + +Short IDs are locally unique for three reasons. First, it simplifies +process management and naming: a particular Short ID uniquely +identifies a running process. Second, it ensures that resources bound +to an application identifier (such as non-volatile storage) do not +have to handle concurrent accesses from multiple processes. Finally, +generally one does not want two copies of the same Application +running: they can create conflicting responses and behaviors. + +These two requirements restrict the set of possible combinations of +Credentials Checking Policy and Identifier Policy. For example, a +Short ID cannot be an incrementing counter; it must be +deterministically derived from the Application Identifier. + +A basic challenge that arises with Short IDs is that they are a form +of compression. In the ideal case, Short IDs would have two +additional properties: + - Different Application Identifiers map to different Short IDs, and + - All Application Identifiers have a concrete Short ID that identifies the Application. + +Unfortunately, it is not possible to satisfy both of these properties +simultaneously. This is because Short IDs potentially compress +Application Identifiers. Consider, for example, a system where the +Application Identifier is the public key in an 4096-bit RSA +credential. Short IDs are 32 bits, but there are more than 2^32 +4096-bit RSA keys. If every RSA key receives a different Short ID, and +that Short ID is always the same, after 2^32 keys the Short ID space +is exhausted. + +Every algorithm to map Application Identifiers to Short IDs therefore +sacrifices one of these two properties: + - **Different Application Identifiers can map to the same Short + ID:** An Identifier Policy with this property is one that uses + string names as Global Application Identifiers and calculates the + Short ID of process to be the checksum (or hash) of the string name. + Two different names can checksum or hash to the same value. These + collisions, however, can be acceptable if a developer is willing to + pick string names that do not collide or change them when they do. + A research or prototyping system might use this Identifier Policy. + - **Some Application Identifiers do not receive concrete Short + IDs:** An Identifier Policy with this property is one that uses + public keys in signature credentials as Application Identifiers and + has a set of public keys it knows and trust. It maps these known + keys to a small set of Short IDs (e.g., 1 through N). The system may + run Userspace Binaries signed by other keys, but assigns them a + Locally Unique Application identifier, which results in a + Locally Unique Short ID. + + +8.2 Example Short ID use cases +------------------------------- + +Here are three example use cases of Short IDs. + +8.2.1 Use Case 1: Anonymous Applications +------------------------------- + +There are many Tock systems that do not particularly care about the +identity of Applications. They do not have security policies, or track +Application Identifiers. A prototyping system whose Credentials +Checking Policy accepts all TBF Objects regardless of Application +Credentials is an example of such a system. At boot, it scans the set +of TBF Objects in application flash, trying to load and run each one +until it runs out of resources (RAM, process slots). Applications +cannot store data they expect to persist across reboots. Because the +Tock kernel does not care about the identity of Applications, it has +no security policies for limiting access to functionality or resources +(e.g., system call filters). + +In this use case, the Credentials Checking Policy accepts all +correctly formatted TBF Objects and the Identifier Policy assigns +every process a Locally Unique Identifier and a Locally Unique Short +ID. + +8.2.2 Use Case 2: U2F Application +------------------------------- + +In this use case, Tock needs to run a Universal 2nd Factor +Authentication (U2F) application. This Application needs to store a +private key in flash. No other Application should be able to access +this key. The Tock kernel also restricts certain system calls to only +the U2F Application, such as invoking cryptographic accelerators. +Finally, the U2F Application needs a consistent identity over reboots +of its Userspace Binary, the kernel, and upgrades of the Application +with new versions (and Userspace Binaries). + +In this use case, the Application Identifier is a Global Identifier. +To establish the authenticity and integrity of the U2F Application, +the Credential Checking Policy requires that an Application has a +valid 4096-bit RSA credential. The system assumes that each Application +has its own public-private key pair. While the system will load and +run any process whose Userspace Binary has a valid 4096-bit RSA +credential, it only gives special permissions and access to the U2F +Application. + +The Identifier Policy defines the Application Identifier of a process +to depend on the public key of its 4096-bit RSA credential. If it is the +key known to belong to the U2F Application, the Application Identifier +is the key. If the key is not recognized, the Application Identifier +is a Locally Unique Identifier. The Short ID of the U2F Application is +1 and the Short ID of all other Applications is Locally Unique. + +8.2.3 Use Case 3: Application Isolation +------------------------------- + +In this use case, Tock needs to support multiple Applications that can +read and write local flash. Each Application has its own flash +storage, and Tock isolates their flash storage from one another. An +Application cannot access the flash of another Application. However, +this is a development system or a system which does not require +confidentiality. While there is storage isolation between +Applications, this is for debuggability, easy of composition, and +simplicity and not to meet security requirements. The Credentials +Checking Policy is permissive and tries to run every properly +formatted TBF Objects. + +In this use case, the Application Identifier is a Global Identifier. +It is the string name of the TBF Object as encoded in a TBF Header. +The Short ID is a one's complement checksum of the string name. + +If a developer installs two TBF Objects with the same string name, the +Tock kernel thinks they are the same Application and only runs one of +them. If a developer accidentally uses two different string names that +have the same checksum (e.g. both "dog" and "mal" checksum to 0x13a), +the Tock kernel also only runs one of them. Some local modifications +to `tockloader` check for these collisions and prevent the developer +from accidentally installing colliding Applications. + +Note that in this case it is possible that the "mal" application could +read data stored by the "dog" application. + + +8.3 Short ID Format +------------------------------- + +The 32-bit value MUST be non-zero. `ShortId` uses `core::num::NonZeroU32` +so that an `ShortId` can be 32 bits in size, with 0 reserved +for `LocallyUnique`. + +```rust +#[derive(Clone, Copy)] +enum ShortId { + LocallyUnique, + Fixed(core::num::NonZeroU32), +} + +pub trait Compress { + fn to_short_id(process: &ProcessBinary) -> ShortId; +} +``` + +Generally, the Process Checker that implements +`AppUniqueness` also implements `Compress`. This allows it to +share copies of public keys or other credentials that it uses to make +decisions, reducing flash space dedicated to these constants. Doing so +also makes it less likely that the two are inconsistent. + +8.4 Short ID Considerations +------------------------------- + +It is RECOMMENDED that the `Fixed` field of `ShortId` be completely +hidden and unknown to modules that use `ShortId` to manage security +policies. They should depend on obtaining `ShortId` values based on +known names or methods. For example, the implementation of an +Identifier Policy can define a method, `privileged_id`, which returns +the Short ID associated with special privileges. Kernel modules which +want to give these processes extra permissions can check whether the +`ShortId` associated with a process matches the `ShortId` returned +from `privileged_id`. Alternatively, when they are initialized, they +can be passed a slice or array of `ShortId`s which are allowed; system +initialization generates this set once and passes it into the module +so it does not need to maintain a reference to the structure +implementing `Compress`. + +The exact `Fixed` values used is an internal implementation decision for +the implementer of `Compress` and the Identifier Policy. Doing so +cleanly decouples modules through APIs and does not leak internal +state. + +9 The `AppIdPolicy` Trait +=============================== + +The `AppIdPolicy` trait is a composite trait that +combines `AppUniqueness` and `Compress` +into a single trait so it can be passed as a single reference. + +```rust +pub trait AppIdPolicy: AppUniqueness + Compress {} +impl AppIdPolicy for T {} +``` + +10 Capsules +=============================== + +Capsules can use AppID to restrict access to only certain processes or to +partition a resource based among processes. By using AppID, this assignment is +persistent across reboots and application updates. + +For example, consider a display that is divided such that different applications +are given access to different regions of the display. These assignments should +be persistent to main continuity for the user looking at the display, even if +applications are added or removed. + +This is a very incomplete example but it shows the general use of ShortId within +a capsule. Note that accessing a ShortId is done using `ProcessId`. + +```rust +pub struct AppScreenRegion { + app_id: kernel::process::ShortId, + frame: Frame, +} + +pub struct ScreenShared<'a, S: hil::screen::Screen<'a>> { + screen: &'a S, + apps: Grant, AllowRoCount<{ ro_allow::COUNT }>, AllowRwCount<0>>, + apps_regions: &'a [AppScreenRegion], +} + +impl<'a, S: hil::screen::Screen<'a>> ScreenShared<'a, S> { + fn get_app_screen_region_frame(&self, process_id: ProcessId) -> Option { + // Check if a process with that short ID has an allocated frame. + let short_id = process_id.short_app_id(); + + for app_screen_region in self.apps_regions { + if short_id == app_screen_region.app_id { + return Some(app_screen_region.frame); + } + } + None + } + + fn write_screen(&self, process_id: ProcessId) { + let screen_region = self.get_app_screen_region_frame(process_id); + self.screen.write(screen_region); + } +} + +impl<'a, S: hil::screen::Screen<'a>> SyscallDriver for ScreenShared<'a, S> { + fn command(&self, command_num: usize, _: usize, _: usize, process_id: ProcessId) -> CommandReturn { + match command_num { + // Driver existence check + 0 => CommandReturn::success(), + + // Write + 1 => { + self.write_screen(process_id); + CommandReturn::success() + } + + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } + } +} +``` + +11 Implementation Considerations +=============================== + +Notes about requirements for application identifier generation/calculation (must be synchronous). + + +12 Authors' Addresses +=============================== +``` +Philip Levis +409 Gates Hall +Stanford University +Stanford, CA 94305 +USA +pal@cs.stanford.edu + +Johnathan Van Why + +Brad Campbell +``` + +[TRD1]: trd1-trds.md "Tock Reference Document (TRD) Structure and Keywords" +[TBF]: ../TockBinaryFormat.md "Tock Binary Format" diff --git a/doc/reference/trd-digest.md b/doc/reference/trd-digest.md new file mode 100644 index 0000000000..777081b711 --- /dev/null +++ b/doc/reference/trd-digest.md @@ -0,0 +1,325 @@ +Digest HIL +======================================== + +**TRD:**
+**Working Group:** Kernel
+**Type:** Documentary
+**Status:** Draft
+**Author:** Alistair Francis, Philip Levis
+**Draft-Created:** June 8, 2022
+**Draft-Modified:** June 8, 2022
+**Draft-Version:** 1
+**Draft-Discuss:** tock-dev@googlegroups.com
+ +Abstract +------------------------------- + +This document describes the hardware independent layer interface (HIL) +for hash functions. A digest is the output of a hash function. It +describes the Rust traits and other definitions for this service as +well as the reasoning behind them. This document is in full compliance +with [TRD1](./trd1-trds.md). The HIL in this document also adheres to +the rules in the [HIL Design Guide](./trd2-hil-design.md), which +requires all callbacks to be asynchronous -- even if they could be +synchronous. + + +1 Introduction +=============================== + +A hash function takes a potentially large input and transforms it into +a fixed-length value. Hash functions have many uses and so there are +many types of hash functions with different properties (computational +speed, memory requirements, output distributions). A *digest* is the +output of a hash function. Generally, hash functions seek to produce +digest values that are uniformly distributed over their space of +possible values, "hashing" the input and mixing it up such that the +distance between the digests from two similar input values seem +randomly distributed. + +*Cryptographic hash functions* are a class of hash functions which +have two properties that make them useful for checking the integrity +of data. First, they have *collision resistance*: it is difficult to find +two messages, m1 and m2, such that hash(m1) = hash(m2). Second, they +have *pre-image resistance*, such that given a digest d, it is difficult +to find a message m such that hash(m) = d. SHA256 and SHA3 are +example cryptographic hash functions that are commonly used today +and believed to provide both collision resistance and pre-image +resistance.[Boneh and Shoup](https://toc.cryptobook.us/). + +*Message authentication codes (MACs)* are a method for providing +integrity when both the generator and checker share a secret. MACs are +a distinct integrity mechanism than digests. They provide both +integrity and authenticity that the message came from a certain sender +(who holds the secret). Some MACs, such as +[HMAC](https://en.wikipedia.org/wiki/HMAC), are built on top of hash +functions. + +This document describes Tock's traits and their semantics for +computing digests in the Tock operating system. These traits can also be +used for generating HMACs. + + +2 Adding Data to a Digest: `DigestData` and `ClientData` +=============================== + +A client adds data to a hash function's input with the `DigestData` trait +and receives callbacks with the `ClientData` trait. + +These traits support both mutable and immutable data. Most HIL traits +in Tock support only mutable data, because it is assumed the data is +in RAM and passing it without the `mut` qualifier in a split-phase +operation can discard its mutability +(see Rule 5 in [TRD2](./trd2-hil-design.md)). Digest supports immutable +data because many services need to compute digests over large, read-only data +in flash. One example of this is the kernel's process loader, which needs +to check that process images are not corrupted. Because digests are +computationally inexpensive, copying the data from flash to RAM in order +to compute a digest is a large overhead. Furthermore, the data input can +be large (tens or hundreds of kilobytes). Therefore `DigestData` and +`ClientData` support both mutable and immutable inputs. + +Clients provide input to `DigestData` through the `SubSlice` +and `SubSliceMut` types. These allow a client to ask a +digest engine to compute a digest over a subset of their data, e.g. to +exclude the area where the digest that will be compared against is stored. +These types have a source slice and maintain an active range over that slice. +The digest will be computed only over the active range, rather than the +entire slice. + +```rust +pub trait DigestData<'a, const L: usize> { + fn set_data_client(&'a self, client: &'a dyn ClientData<'a, L>); + fn add_data(&self, data: SubSlice<'static, u8>) + -> Result<(), (ErrorCode, SubSlice<'static, u8>)>; + fn add_mut_data(&self, data: SubSliceMut<'static, u8>) + -> Result<(), (ErrorCode, SubSliceMut<'static, u8>)>; + fn clear_data(&self); +} +``` + +A successful call to `add_data` or `add_mut_data` will add all of the +data in the active range of the leasable buffer as input to the hash +function. A successful call is one which returns `Ok(())` and whose +completion event passes `Ok(())`. If a client needs to compute a hash over several non-contiguous +regions of a slice, or multiple slices, it can call these methods multiple +times. + +There may only be one outstanding `add_data` or `add_mut_data` operation at +any time. If either `add_data` or `add_mut_data` returns `Ok(())`, then all +subsequent calls to `add_data` or `add_mut_data` MUST return `Err((ErrorCode::BUSY, ...))` +until a completion callback delivered through `ClientData`. + +```rust +pub trait ClientData<'a, const L: usize> { + fn add_data_done(&'a self, result: Result<(), ErrorCode>, data: SubSlice<'static, u8>); + fn add_mut_data_done( + &'a self, + result: Result<(), ErrorCode>, + data: SubSliceMut<'static, u8>, + ); +} +``` + +The `data` parameters of `add_data_done` and `add_mut_data_done` indicate what +data was added and what remains to be added to the digest. If either callback +has a `result` value of `Ok(())`, then the active region of `data` MUST be zero +length and all of the data in the active region passed through the corresponding +call MUST have been added to the digest. + +A call to `DigestData::clear_data()` terminates the current digest computation and +clears out all internal state to start a new one. +If there is an outstanding `add_data` or `add_data_mut` when `clear_data()` is called, +the digest engine MUST issue a corresponding callback with an `Err(ErrorCode::CANCEL)`. + +A digest engine MUST accept multiple calls to `add_data` and `add_mut_data`. Each +call appends to the data over which the digest is computed. + +3 Computing and Verification: `DigestHash`, `DigestVerify`, `ClientHash`, and `ClientVerify` +=============================== + +Once all of the data has been added as the input to a digest, a client +can either compute the digest or ask the digest engine to compare its +computed digest with a known value (verify). These traits have a +generic parameter `L` which defines the length of the digest in bytes. +A SHA256 digest engine, for example, has an `L` of 32. + + +```rust +pub trait DigestHash<'a, const L: usize> { + fn set_hash_client(&'a self, client: &'a dyn ClientHash<'a, L>); + fn run(&'a self, digest: &'static mut [u8; L]) + -> Result<(), (ErrorCode, &'static mut [u8; L])>; +} + +pub trait ClientHash<'a, const L: usize> { + fn hash_done(&'a self, result: Result<(), ErrorCode>, digest: &'static mut [u8; L]); +} + +pub trait DigestVerify<'a, const L: usize> { + fn set_verify_client(&'a self, client: &'a dyn ClientVerify<'a, L>); + fn verify(&'a self, compare: &'static mut [u8; L]) + -> Result<(), (ErrorCode, &'static mut [u8; L])>; +} + +pub trait ClientVerify<'a, const L: usize> { + fn verification_done(&'a self, result: Result, compare: &'static mut [u8; L]); +} +``` + +Calls to `DigestHash::run` and `DigestHash::verify` perform the hash +function on all of the data that has been added with calls to +`add_data` and `add_data_mut`. If there is an outstanding call to +`add_data`, `add_data_mut`, `run`, or `verify` they MUST return +`Err(ErrorCode::BUSY)`. + +The `ClientHash::hash_done` callback returns the computed digest +stored in the `digest` slice. If the `result` argument is `Err((...))`, +the `digest` slice may store any values. If the `result` argument +is `Ok(())` the `digest` slice MUST store the computed digest. + +The `DigestVerity:verify` takes an existing digest as its `compare` +parameter. It triggers the digest engine to compute the digest, then +compares the computed value with what was passed in `compare`. If the +computed and provided values match, then `ClientVerify` passes +`Ok(true)`; if they do not match then it passes `Ok(false)`. An `Err` +result indicates that there was an error in computing the digest. + +Calling either `DigestHash::run` or `DigestVerify::verify` completes +the digest calculation, returning the digest engine to an idle +state for the next computation. + +4 Composite Traits +=============================== + +The Digest HIL provides many composite traits, so that structures +which implement multiple traits can be passed around as a single +reference. The `ClientDataHash` trait is for a client that implements +both `ClientData` and `ClientHash`. The `ClientDataVerify` trait is +for a client that implements both `ClientData` and `ClientVerify`. +The `Client` trait is for a client that implements `ClientData`, +`ClientHash`, and `ClientVerify`. + +```rust +pub trait ClientDataHash<'a, const L: usize>: ClientData<'a, L> + ClientHash<'a, L> {} +pub trait ClientDataVerify<'a, const L: usize>: ClientData<'a, L> + ClientVerify<'a, L> {} +pub trait Client<'a, const L: usize>: + ClientData<'a, L> + ClientHash<'a, L> + ClientVerify<'a, L> {} +``` + + +The `DigestDataHash` trait is for a structure that implements both +`DigestData` and `DataHash`. The `DigestDataVerify` trait is for a +client that implements both `DigestData` and `DigestVerify`. The +`Digest` trait is for a client that implements `DigestData`, +`DigestHash`, and `DigestVerify`. These each add an additional +method, `set_client`, which allows it to store the corresponding +client as a single reference and use it for all of the relevant +client callbacks (e.g., `add_data`, `add_mut_data`, `hash_done`, and +`verification_done`). A digest implementation that implements +`set_client` MAY choose to not implement the individual client set +methods for the different traits (e.g., `DigestData::set_client`); if +it does so, each of these client set methods MUST be marked +`unimplemented!()`. + + +```rust +pub trait DigestDataHash<'a, const L: usize>: DigestData<'a, L> + DigestHash<'a, L> { + /// Set the client instance which will receive `hash_done()` and + /// `add_data_done()` callbacks. + fn set_client(&'a self, client: &'a dyn ClientDataHash); +} + +pub trait DigestDataVerify<'a, const L: usize>: DigestData<'a, L> + DigestVerify<'a, L> { + /// Set the client instance which will receive `verify_done()` and + /// `add_data_done()` callbacks. + fn set_client(&'a self, client: &'a dyn ClientDataVerify); +} + +pub trait Digest<'a, const L: usize>: + DigestData<'a, L> + DigestHash<'a, L> + DigestVerify<'a, L> +{ + /// Set the client instance which will receive `hash_done()`, + /// `add_data_done()` and `verification_done()` callbacks. + fn set_client(&'a self, client: &'a dyn Client<'a, L>); +} +``` + + +5 Configuration +=============================== + +Digest engines can often operate in multiple modes, supporting several +different hash algorithms and digest sizes. Configuring a digest +engine occurs out-of-band from adding data and computing digests, +through separate traits. Each digest algorithm is described by +a separate trait. This allows compile-time checking that a given +digest engine supports the required algorithm. For example, +a digest engine that can compute a SHA512 digest implements +the `Sha512` trait: + +```rust +pub trait Sha512 { + /// Call before Digest::run() to perform Sha512 + fn set_mode_sha512(&self) -> Result<(), ErrorCode>; +} +``` + + +The Digest HIL defines seven standard Digest traits: + - `Sha224` + - `Sha256` + - `Sha384` + - `Sha512` + - `HmacSha256` + - `HmacSha384` + - `HmacSha512` + +The HMAC configuration methods take a secret key, which is +used in the HMAC algorithm. For example, + +```rust +pub trait HmacSha384 { + /// Call before `Digest::run()` to perform HMACSha384 + /// + /// The key used for the HMAC is passed to this function. + fn set_mode_hmacsha384(&self, key: &[u8]) -> Result<(), ErrorCode>; +} +``` +Configuration methods MUST be called before the first call to `add_data` +or `add_data_mut`. + + +6 Capsules +=============================== + +There are 5 standard Tock capsules for digests: + + 1. `capsules::hmac` provides a system call interface to a digest engine that + supports `Digest`, `HmacSha256`, `HmacSha384`, and `HmacSha512`. + 2. `capsules::sha` provides a system call interface to a digest engine that + supports `Digest`, `Sha256`, `Sha384`, and `Sha512`. + 3. `capsules::virtual_hmac` virtualizes an HMAC engine, allowing multiple clients + to share it through queueing. It requires a digest engine that + supports `Digest`, `HmacSha256`, `HmacSha384`, and `HmacSha512`. + 4. `capsules::virtual_sha` virtualizes a SHA engine, allowing multiple clients + to share it through queueing. It requires a digest engine that + supports `Digest`, `Sha256`, `Sha384`, and `Sha512`. + 5. `capsules::virtual_digest` virtualizes a SHA/HMAC engine, + allowing multiple clients to share it through queueing. It requires + a digest engine that supports `Digest`, `HmacSha256`, `HmacSha384`, + and `HmacSha512`, `Sha256`, `Sha384`, and `Sha512` and supports + all of these operations. + +6 Authors' Address +================================= + + Alistair Francis + alistair.francis@wdc.com + + Philip Levis + 409 Gates Hall + Stanford University + Stanford, CA 94305 + USA + pal@cs.stanford.edu diff --git a/doc/reference/trd-public-private-keys.md b/doc/reference/trd-public-private-keys.md new file mode 100644 index 0000000000..b8e92c1351 --- /dev/null +++ b/doc/reference/trd-public-private-keys.md @@ -0,0 +1,383 @@ +Public and Private Encryption Keys +======================================== + +**TRD:** 1
+**Working Group:** Kernel
+**Type:** Documentary
+**Status:** Draft
+**Authors:** Alistair Francis
+**Draft-Created:** 11 Oct, 2021
+**Draft-Modified:** 11 Oct, 2021
+**Draft-Version:** 1
+ +Abstract +------------------------------- + +This document describes the Tock Public Private Key implementation. +This documents the design process and final outcome. This focuses +on the original RSA key support, but applies to all public/private +keys. + +1 Introduction +==================================================================== + +The goal of pub/priv keys in Tock is to allow the kernel and apps to use +pub/priv crypto operations. It is expected that these are used before loading +applications, to check signatures as well as by the kernel and/or apps during +runtime. + +The goal is to support 3 main use cases, for key storage: + + 1. Keys stored on flash. The keys are stored at some address in read only + flash and we want to "import" them and use them in the kernel. + 2. The app specifies a key. A userspace application obtains a key and + passes it to the kernel to use for crypto operations + 3. We generate a key pair while running + +2 Design Considerations +==================================================================== + +The design needs to integrate well with the rest of the Tock kernel and +capsule design. As well as that we want to ensure + +2.1 Low memory overhead +-------------------------------------------------------------------- + +Pub/priv keys can be very large. For example a 4096-bit RSA key is 512 +byes long. That means to store a pub/priv key pair in RAM we need at least +1024 bytes (1K) of memory, just for one key pair. That doesn't take into +account potential post quantum algorithms that can have even larger keys. + +Due to this the design should avoid copying keys into memory where not +required. For example generating a new key pair will need to use memory, +but reading existing keys from flash should avoid copying keys to memory. + +2.2 Mutable and immutable buffers +-------------------------------------------------------------------- + +As the implementation should support importing existing keys from flash or +from userspace the design must allow for both mutable and immutable buffers. + + +3 Possible key structure implementations +==================================================================== + +Below is a list of possible implementations, as well as outcomes of that +design. For consistency all designs below are for a 2048-bit RSA key/pair, +but the designs could apply for any pub/priv operations + +3.1 In memory buffers +-------------------------------------------------------------------- + +Keys would be stored in a memory sturcture, similar to: + +```rust +pub struct RSA2048Keys<'a> { + modulus: [u8; 256], // Also called n + public_exponent: u32, // Also called e + private_exponent: [u8; 256], // Also called d +... +} +``` + +As mentioned in section 2.1 this requires large in memory buffers, even when +using an existing key on flash. Due to that this method will not be used. + +3.2 TakeCell buffers +-------------------------------------------------------------------- + +In order to avoid storing the keys in memory, the design can instead use +`TakeCell`. This way existing keys can pass in a buffer to the key, while +new keys can use a buffer created with `static_init!()` + +```rust +pub struct RSA2048Keys<'a> { + public_key: TakeCell'static, u8>, + private_key: TakeCell'static, u8>, +... +} +``` + +For example, importing a key would look like this: + +```rust +fn import_public_key(&mut self, + public_key: &'static mut [u8], +) -> Result<(), (ErrorCode, &'static mut [u8])> +``` + +The problem with using `TakeCell` is that then the buffer must be mutable. +This won't work with a read-only buffer stored in flash. + +The design also can't use `Cell` and immutable buffers instead, as then the +design doesn't work with mutable buffers, required for genearating keys or +interacting with userspace. + +3.3 Mutable and Immutable buffers +-------------------------------------------------------------------- + +Similar to above, this design uses interior mutability, but adds this enum + +```rust +pub enum MutImutBuffer<'a, T> { + Mutable(&'a mut [T]), + Immutable(&'a [T]), +} +``` + +Then the key structure will look like + +```rust +pub struct RSA2048Keys<'a> { + public_key: OptionalCell>, + private_key: OptionalCell>, +... +} +``` + +This is similar to 3.2, but allows either a mutable or immutable buffer. + +For example to import a key the function would look like: + +```rust +fn import_public_key( + &'a self, + public_key: MutImutBuffer<'static, u8>, +) -> Result<(), (ErrorCode, MutImutBuffer<'static, u8>)>; +``` + +This allows the design to use either a mutable or immutable buffer and doesn't +have a high memory overhead. + +3.4 Read and Read/Write keys +-------------------------------------------------------------------- + +Similar to 3.3 the other option is to have a read only key and a read/write +key and move the enum a level higher. + +For example + +```rust +pub struct RSA2048ReadOnlyKeys<'a> { + public_key: OptionalCell<&'static [u8]>, + private_key: OptionalCell<&'static [u8]>, +... +} + +pub struct RSA2048ReadWriteKeys<'a> { + public_key: TakeCell'static, u8>, + private_key: TakeCell'static, u8>, +... +} + +pub enum RSA2048Keys<'a> { + Mutable(RSA2048ReadWriteKeys<'a>), + Immutable(RSA2048ReadOnlyKeys<'a>), +} +``` + +This has the advantage that it's more obvious if a key is mutable or immutable. +This has a large code duplication downside though. There will be two +implementations, one for `RSA2048ReadOnlyKeys` and one for `RSA2048ReadWriteKeys` +that are almost identical. + +On top of that there also will need to be two HILS, for example: + +```rust +pub trait PubKeyReadWrite<'a> { + fn import_public_key(&self, + public_key: &'static mut [u8], + ) -> Result<(), (ErrorCode, &'static mut [u8])> +} + +pub trait PubKeyReadOnly<'a> { + fn import_public_key(&self, + public_key: &'static [u8], + ) -> Result<(), (ErrorCode, &'static [u8])> +} +``` + +This has a complexity and code size downside compared to section 3.3, but can +avoid confusion where a mutable buffer is required but not supplied. + +4 Possible low level interface APIs +==================================================================== + +On top of the key structure implementation, there will also be a HIL that +hardware implementations inside `chips` will implement. + +This TRD is not trying to describe this API, so let's just assume this is one +of the functions are part of that HIL: + +```rust +/// Calculate the exponent. That is calculate `message` ^ `exponent` +/// +/// On completion the `exponent_done()` upcall will be scheduled. +fn exponent( + &self, + message: &'static mut [u8], + exponent: T, + result: &'static mut [u8], +) -> Result< + (), + ( + ErrorCode, + &'static mut [u8], + T, + &'static mut [u8], + ), +>; +``` + +This function takes the `message` buffer and calculates the exponent from the +public or private key of type `T` and stores it in `result`. + +The below sections describe why type `T` should be. + +4.1 Mutable and Immutable buffers +-------------------------------------------------------------------- + +See section 3.3 for the enum `MutImutBuffer`, which would be used like this: + +```rust +/// Calculate the exponent. That is calculate `message` ^ `exponent` +/// +/// On completion the `exponent_done()` upcall will be scheduled. +fn exponent( + &self, + message: &'static mut [u8], + exponent: (MutImutBuffer<'static, u8>, Range), + result: &'static mut [u8], +) -> Result< + (), + ( + ErrorCode, + &'static mut [u8], + MutImutBuffer<'static, u8>, + &'static mut [u8], + ), +>; +``` + +In this case the underlying API will take a `'static` buffer that is either +mutable or immutable. This is wraped in the `MutImutBuffer` enum. In this case +as well we specify a range of the buffer to be used. + +This has the advantage that the hardware interfacing driver doesn't have to +manage keys, instead it is just passed a buffer (wrapped in an emum). This +is also similar to other Tock HILs. + +The disadvantage is how to get the buffer before calling the above function. + +This implementation requires that the above layer loose access to the buffer, +with something like: + +```rust +fn private_exponent(&'a self) -> Option<(MutImutBuffer<'static, u8>, Range)> { + if self.private_key.is_some() { + let len = PubPrivKey::len(self); + Some((self.private_key.take().unwrap(), 0..len)) + } else { + None + } +} +``` + +Which also requires a way to regain acceess to the buffer on the `exponent()` callback: + +```rust +fn import_private_key( + &self, + private_key: MutImutBuffer<'static, u8>, +) -> Result<(), (ErrorCode, MutImutBuffer<'static, u8>)> { + if private_key.len() != 256 { + return Err((ErrorCode::SIZE, private_key)); + } + + self.private_key.replace(private_key); + + Ok(()) +} +``` + +This option also requires the `MutImutBuffer` enum to work + +4.2 Keys +-------------------------------------------------------------------- + +The other option is to pass the entire key to the low level API, for example +something like: + +```rust +/// Calculate the exponent. That is calculate message ^ exponent +/// +/// On completion the `exponent_done()` upcall will be scheduled. +fn exponent( + &self, + message: &'static mut [u8], + key: &'static mut dyn RsaPrivKey, + result: &'static mut [u8], +) -> Result< + (), + ( + ErrorCode, + &'static mut [u8], + &'static mut dyn RsaPrivKey, + &'static mut [u8], + ), +>; +``` + +Using something like this in the HIL: + +```rust +/// Returns the specified closure over the private exponent, if it exists +/// The exponent is returned MSB (big endian) +/// Returns `Some()` if the key exists and the closure was called, +/// otherwise returns `None`. +fn map_exponent(&self, closure: &dyn Fn(&[u8]) -> ()) -> Option<()>; +``` + +and an implementation similar to: + +```rust +fn map_exponent(&self, closure: &dyn Fn(&[u8]) -> ()) -> Option<()> { + if let Some(private_key) = self.private_key.take() { + match private_key { + MutImutBuffer::Mutable(ref buf) => { + let _ = closure(buf); + } + MutImutBuffer::Immutable(buf) => { + let _ = closure(buf); + } + } + self.private_key.replace(private_key); + Some(()) + } else { + None + } +} +``` + +Then the final implementation can use `map()` with this code: + +```rust +key.map_exponent(&|buf| { + // Do operations of the `buf` array +}); +``` + +This has the advantage that accessing information from keys is not distructive. +It does have the downside that hardware implementations in `chips` needs to +understand the key values to access. + +5 Final implementation +==================================================================== + +TODO once agreed apon + +6 Author's Address +==================================================================== + + Alistair Francis + alistair.francis@wdc.com diff --git a/doc/reference/trd-radio.md b/doc/reference/trd-radio.md index bb5d848b6a..5535f68664 100644 --- a/doc/reference/trd-radio.md +++ b/doc/reference/trd-radio.md @@ -160,8 +160,8 @@ A caller can configure the 16-bit short address, 64-bit full address, PAN (personal area network) identifier, transmit power, and channel. The PAN address and node address are both 16-bit values. Channel is an integer in the range 11-26 (the 802.15.4 channel -numbers). `config_set_channel` MUST return INVAL if passed a channel -not in the range 11-26 and Ok(()) otherwise. +numbers). The channel is encoded in the `radio::RadioChannel` +enum, ensuring the channel value resides in the valid range. fn config_address(&self) -> u16; fn config_address_long(&self) -> [u8;8]; @@ -172,7 +172,7 @@ not in the range 11-26 and Ok(()) otherwise. fn config_set_address_long(&self, addr: [u8;8]); fn config_set_pan(&self, addr: u16); fn config_set_tx_power(&self, power: i8) -> Result<(), ErrorCode>; - fn config_set_channel(&self, chan: u8) -> Result<(), ErrorCode>; + fn config_set_channel(&self, chan: radio::RadioChannel); `config_set_tx_power` takes an signed integer, whose units are dBm. If the specified value is greater than the maximum supported transmit diff --git a/doc/reference/trd-storage-permissions.md b/doc/reference/trd-storage-permissions.md new file mode 100644 index 0000000000..27eece1b10 --- /dev/null +++ b/doc/reference/trd-storage-permissions.md @@ -0,0 +1,354 @@ +Application Persistent Data Storage Permissions +=============================================== + +**TRD:**
+**Working Group:** Kernel
+**Type:** Documentary
+**Status:** Draft
+**Author:** Brad Campbell
+**Draft-Created:** 2024/06/06
+**Draft-Modified:** 2024/06/06
+**Draft-Version:** 1
+**Draft-Discuss:** devel@lists.tockos.org
+ + +Abstract +------------------------------- + +Tock supports storing persistent state for applications, and all persistent +state in Tock is identified based on the application that stored it. Tock +supports permissions for persistent state, allowing for the kernel to restrict +which applications can store state and which applications can read stored state. +This TRD describes the permissions architecture for persistent state in Tock. +This document is in full compliance with [TRD1][TRD1]. + + +1 Introduction +------------------------------- + +Tock applications need to be able to store persistent state. Additionally, +applications need to be able to keep data private from other applications. The +kernel should also be able to allow specific applications to read and modify +state from other applications. + +This requires a method for assigning applications persistent identifiers, +a mechanism for granting storage permissions to specific applications, +and kernel abstractions for implementing storage capsules that respect +the storage permissions. + + +2 Scope +------------------------------- + +This document only describes the permission architecture in Tock for supporting +application persistent storage. This document does not prescribe specific types of +persistent storage (e.g., flash, FRAM, etc.), storage access abstractions +(e.g., block-access, byte-access, etc.), +or storage interfaces (e.g., key-value, filesystems, logs, etc.). + + +3 Stored State Identifiers +------------------------------- + +All shared persistent storage implementations must store a 32 bit identifier +with each stored object to mark the application that created the stored object. + +When applications write data, their [ShortId](trd-appid.md) must be used as the +identifier. When the kernel writes data, the identifier must be 0. + +The security, uniqueness, mapping policy, and other properties of ShortIds are +allowed to vary based on board configuration. For storage use cases which have +specific concerns or constraints around the policies for storage identifiers, +users should consult the [properties of ShortIds afforded by AppId policy](trd-appid.md#8-short-ids-and-the-compress-trait). + +4 Permissions +------------------------------- + +All persistent application data is labeled based on the application which wrote +the data. +Applications can read and modify data with suitable +permissions. + +There are three types of permissions: + +1. **Write**: The application can write data. +1. **Read**: The application can read data. +1. **Modify**: The application can modify existing data. + +Each permission type is independent. For example, an application can be given +read permission for specific data but not be able to write new data itself. + +Write is a boolean permission. An application either has permission to write or +it does not. + +Read and Modify permissions are tuples of `(the permission type, stored state +identifier)`. These permissions only exist as associated with a particular +stored state identifier. That is, a Read permission gives an application +permission to read only stored state marked with the associated stored state +identifier, and a Modify permission gives an application permission to modify +only stored state marked with the associated stored state identifier. + + +5 Requirements +------------------------------- + +The Tock storage model imposes the following requirements: + +1. Applications are given separate write, read, and modify permissions. +2. The label stored with the persistent data when the data are written is the + application's short AppID. +3. Applications without a `ShortId::Fixed` cannot access (i.e., + read/write/modify) any persistent storage. +4. How permissions are mapped to applications must be customizable for different + Tock kernels. + +Additionally, the kernel itself can be given permission to store state. + +### 5.1 ShortId Implications + +As all persistent state written by applications is marked with the writing +application's ShortId, the assignment mechanism for ShortIds is tightly coupled +with the access policies for persistent state. This coupling is intentional as +AppIDs are unique to specific applications. However, as ShortIds are only 32 +bits, it is not possible to assign a globally unique ShortId to all +applications. Therefore, board authors should be intentional with how ShortIds +are assigned when persistent storage is accessible to userspace. + +In particular, two potentially problematic cases can arise: + +1. A ShortId is re-used for different applications. This might happen if one + application is discontinued and a new application is assigned the same + ShortId. The new application would then have unconditional access to any + state the old application stored. +2. A new ShortId is used for the same application. This might happen if the + ShortId assignment algorithm changes. The same application then would lose + access to data it previously stored. + + +6 Kernel Enforcement +------------------------------- + +It is not feasible to implement all persistent storage APIs through the core +kernel (i.e., in trusted code). Instead, the kernel provides an API to retrieve +the storage permissions for a specific process. Capsules then use these +permissions to enforce restrictions on storage access. +The API consists of these functions: + +```rust +/// Check if these storage permissions grant read access to the stored state +/// marked with identifier `stored_id`. +pub fn check_read_permission(&self, stored_id: u32) -> bool; + +/// Check if these storage permissions grant modify access to the stored +/// state marked with identifier `stored_id`. +pub fn check_modify_permission(&self, stored_id: u32) -> bool; + +/// Retrieve the identifier to use when storing state, if the application +/// has permission to write. Returns `None` if the application cannot write. +pub fn get_write_id(&self) -> Option; +``` + +This API is implemented for the `StoragePermissions` object (which is an +`enum`). The `StoragePermissions` type can be stored per-process and passed in +storage APIs to express the storage permissions of the caller of any storage +operations. + +### 6.1 Using Permissions in Capsules + +When writing storage capsules, capsule authors should include APIs which include +`StoragePermissions` as an argument, and should check for permission before +performing any storage operation. + +For example, a filing cabinet abstraction that identifies stored state based on +a record name might have an (asynchronous) API like this: + +```rust +pub trait FilingCabinet { + fn read(&self, record: &str, permissions: &dyn StoragePermissions) -> Result<(), ErrorCode>; + fn write(&self, record: &str, data: &[u8], permissions: &dyn StoragePermissions) -> Result<(), ErrorCode>; +} +``` + +Inside the implementation for any storage abstraction, the implementation must +consider three operations and check for permissions: + +1. The operation is a **read**. If there is no stored state that matches the + read request, the capsule should return `ErrorCode::NOSUPPORT`. If there is + stored state that matches the request, the capsule must call + `StoragePermissions::check_read_permission(stored_id)` with the identifier + associated with the stored record. If `check_read_permission()` returns + false, the capsule should return `ErrorCode::NOSUPPORT`. If + `check_read_permission()` returns true, the capsule should return the read + data. +2. The operation is a **write**, and the write would store **new data**. The + capsule must call `StoragePermissions::get_write_id()`. If `get_write_id()` + returns `None`, the capsule should return `ErrorCode::NOSUPPORT`. If + `get_write_id()` returns `Some()`, the capsule should save the new data and + must use the returned `u32` identifier. It should then return `Ok(())`. +3. The operation is a **write**, and the write would overwrite **existing + data**. The capsule must first retrieve the storage identifier for the + existing state. The the capsule must call + `StoragePermissions::check_modify_permission(stored_id)`. If + `check_modify_permission()` returns false, the capsule should return + `ErrorCode::NOSUPPORT`. If `check_modify_permission()` returns true, the + capsule should overwrite the data while not changing this stored identifier. + The capsule should then return `Ok(())`. + +For example, with the filing cabinet example: + +```rust +pub trait FilingCabinet { + fn read(&self, record: &str, permissions: &dyn StoragePermissions) -> Result<[u8], ErrorCode> { + let obj = self.cabinet.read(record); + match obj { + Some(r) => { + if permissions.check_read_permission(r.id) { + Ok(r.data) + } else { + Err(ErrorCode::NOSUPPORT) + } + } + None => Err(ErrorCode::NOSUPPORT), + } + } + + fn write(&self, record: &str, data: &[u8], permissions: &dyn StoragePermissions) -> Result<(), ErrorCode> { + let obj = self.cabinet.read(record); + match obj { + Some(r) => { + if permissions.check_modify_permission(r.id) { + self.cabinet.write(record, r.id, data); + Ok(()) + } else { + Err(ErrorCode::NOSUPPORT) + } + } + None => { + match permissions.get_write_id() { + Some(id) => { + self.cabinet.write(record, id, data); + Ok(()) + } + None => Err(ErrorCode::NOSUPPORT), + } + } + } + } +} +``` + +### 6.2 `StoragePermissions` Type + +The kernel defines a `StoragePermissions` type which expresses the storage +permissions of an application. This is implemented as a definite type rather +than a trait interface so permissions can be passed in storage APIs without +requiring a static object for every process in the system. + +The `StoragePermissions` type is capable of holding storage permissions in +different formats. In general, the type looks like: + +```rust +pub enum StoragePermissions { + SelfOnly(core::num::NonZeroU32), + FixedSize(FixedSizePermissions), + Listed(ListedPermissions), + Kernel, + Null, +} +``` + +Each variant is a different method for representing and storing storage +permissions. For example, `FixedSize` contains fixed size lists of permissions, +where as `Null` grants no storage permissions. + +The `StoragePermissions` type includes multiple constructors for instantiating +storage permissions. + + +7 Specifying Permissions +------------------------------- + +Different users and different kernels will use different methods for determining +the persistent storage access permissions for different applications (and by +extensions the running process for that application). The following are some +examples of how storage permissions may be specified. + +1. In TBF headers. The `StoragePermissions` TBF header allows a developer to + specify storage permissions when the app is compiled. Using this method + assumes the kernel can trust the application's headers, perhaps because the + kernel only runs apps signed by a trusted party that has verified the TBF + headers. +2. Within the kernel. The kernel can maintain a data structure of permissions + for known applications. This should be coupled with the AppID mechanism to + consistently assign storage permissions to applications based on their + persistent identifier. +3. With a generic policy. The kernel may permit all applications with a fixed + ShortId to use persistent storage. This method can isolate applications by + only permitting read and modify access to state stored by the same + application. + +### 7.1 Assigning Permissions to Processes + +The core kernel allows individual boards to configure how permissions are +assigned to applications. At runtime, the kernel needs to know what permissions +each executing process has. To facilitate this, Tock uses the +`ProcessStandardStoragePermissionsPolicy` process policy. Each process, when created, +will store a `StoragePermissions` object that specifies the storage permissions for +that process. + +```rust +/// Generic trait for implementing a policy on how applications should be +/// assigned storage permissions. +pub trait ProcessStandardStoragePermissionsPolicy { + /// Return the storage permissions for the specified `process`. + fn get_permissions(&self, process: &ProcessStandard) -> StoragePermissions; +} +``` + +This trait is specific to the `ProcessStandard` implementation of `Process` to +enable policies to use TBF headers when assigning permissions. + +Several examples of policies are in the `capsules/system` crate. + + +8 Storage Examples +------------------------------- + +The permissions architecture is generic for storage in Tock, but this section +describes some examples of how this architecture may be used for several storage +abstractions. Note, these are just examples and not descriptions of actual Tock +implementations nor requirements for how various storage abstractions must be +implemented. + +1. Key-Value storage. Each key-value pair is stored as a triple: (key, value, + storage identifier). On `get()`, the storage identifier for the key-value + pair is checked. On `set()`, if the key already exists the modify permission + is used, and if the key does not exist the write permission is used. +2. Logging. Loggers append to a shared log. Loggers can only append to the log + if the have the write permission. Each log entry includes the storage + identifier of the writing logger. Loggers do not have any read permission. + Log analyzers only have read permissions. The analyzers have multiple read + permissions to the log entries they need to analyze. The modify permission is + not used. +3. Per-application nonvolatile storage. Each application is given a region of + nonvolatile storage. Applications only access their own storage region. The + storage implementation still checks and enforces read, write, and modify + permissions, but the expectation is that applications that have the write + permission also have modify and read permissions for their own stored state. + There is no API for accessing other application state, so maintaining lists + of read/modify permissions is not necessary. +4. Global persistent configuration. The storage abstraction maintains a + persistent data store that multiple applications use. Only one application is + expected to have the write permission to initialize the configuration. Other + applications that use the configuration have read permission for the + initializing application's storage identifier, and may have modify permission + if they need to update the configuration. + + +8 Authors' Addresses +=============================== +``` +Brad Campbell +``` + +[TRD1]: trd1-trds.md "Tock Reference Document (TRD) Structure and Keywords" diff --git a/doc/reference/trd-uart.md b/doc/reference/trd-uart.md new file mode 100644 index 0000000000..d9ceddba02 --- /dev/null +++ b/doc/reference/trd-uart.md @@ -0,0 +1,571 @@ +Universal Asynchronous Receiver Transmitter (UART) HIL +======================================== + +**TRD:**
+**Working Group:** Kernel
+**Type:** Documentary
+**Status:** Draft
+**Author:** Philip Levis, Leon Schuermann
+**Draft-Created:** August 5, 2021
+**Draft-Modified:** June 5, 2022
+**Draft-Version:** 5
+**Draft-Discuss:** tock-dev@googlegroups.com
+ +Abstract +------------------------------- + +This document describes the hardware independent layer interface (HIL) +for UARTs (serial ports) in the Tock operating system kernel. It +describes the Rust traits and other definitions for this service as +well as the reasoning behind them. This document is in full compliance +with [TRD1](./trd1-trds.md). The UART HIL in this document also adheres +to the rules in the [HIL Design Guide](./trd2-hil-design.md), which requires +all callbacks to be asynchronous -- even if they could be synchronous. + + +1 Introduction +=============================== + +A serial port (UART) is a basic communication interface that Tock +relies on for debugging and interactive applications. Unlike the SPI +and I2C buses, which have a clock line, UART communication is +asynchronous. This allows it to require only one pin for each +direction of communication, but limits its speed as clock drift +between the two sides can cause bits to be read incorrectly. + +The UART HIL is in the kernel crate, in module `hil::uart`. It provides five +main traits: + + * `kernel::hil::uart::Configuration`: allows a client to query how a + UART is configured. + * `kernel::hil::uart::Configure`: allows a client to configure a UART, + setting its speed, character width, parity, and stop bit configuration. + * `kernel::hil::uart::Transmit`: is for transmitting data. + * `kernel::hil::uart::TransmitClient`: is for handling callbacks + when a data transmission is complete. + * `kernel::hil::uart::Receive`: is for receiving data. + * `kernel::hil::time::ReceiveClient`: handles callbacks when data is + received. + +There are also collections of traits that combine these into more +complete abstractions. For example, the `Uart` trait represents a +complete UART, extending `Transmit`, `Receive`, and `Configure`. + +To provide a level of minimal platform independence, a port of Tock to +a given microcontoller is expected to implement certain instances of +these traits. This allows, for example, debug output and panic dumps +to work across chips and platforms. + +This document describes these traits, their semantics, and the +instances that a Tock chip is expected to implement. It also describes +how the `virtual_uart` capsule allows multiple clients to share a +UART. This document assumes familiarity with serial ports and their +framing: [Wikipedia's article on asynchronous serial +communication](https://en.wikipedia.org/wiki/Asynchronous_serial_communication) +is a good reference. + +2 `Configuration` and `Configure` +=============================== + +The `Configuration` trait allows a client to query how a UART is +configured. The `Configure` trait allows a client to configure a UART, +by setting is baud date, character width, parity, stop bits, and whether +hardware flow control is enabled. + +These two traits are separate because there are cases when clients +need to know the configuration but cannot set it. For example, when a UART +is virtualized across multiple clients (e.g., so multiple sources can +write to the console), individual clients may want to check the baud rate. +However, they cannot set the baud rate, because that is fixed and shared +across all of them. Similarly, some services may need to be able to set +the UART configuration but do not need to check it. + +Most devices using serial ports today use 8-bit data, but some older +devices use more or fewer bits, and hardware implementations support +this. If the character width of a UART is set to less than 8 bits, data is +still partitioned into bytes, and the UART sends the least significant +bits of each byte. Suppose a UART is configured to send 7-bit +words. If a client sends 5 bytes, the UART will send 35 bits, +transmitting the bottom 7 bits of each byte. The most significant bit +of each byte is ignored. While this HIL does support UART transfers +with a character-width of more than 8-bit, such characters cannot be +sent or received using the provided bulk transfer mechanisms. A +configuration with `Width` > `8` will disable the bulk buffer transfer +mechanisms and restrict the device to single-character +operations. Refer to [3 `Transmit` and `TransmitClient` +](#3-transmit-and-transmitclient) and [4 `Receive` and `ReceiveClient` +](#4-receive-and-receiveclient-traits) respectively. + +Any configuration-change must not apply to operations started before +this change. The UART implementation is free to accept a configuration +change and apply it with the next operation, or refuse an otherwise +valid configuration request because of an ongoing operation by +returning `ErrorCode::BUSY`. + +```rust +pub enum StopBits { + One = 1, + Two = 2, +} + +pub enum Parity { + None = 0, + Odd = 1, + Even = 2, +} + +pub enum Width { + Six = 6, + Seven = 7, + Eight = 8, + Nine = 9, +} + +pub struct Parameters { + pub baud_rate: u32, // baud rate in bit/s + pub width: Width, + pub parity: Parity, + pub stop_bits: StopBits, + pub hw_flow_control: bool, +} + +pub trait Configuration { + fn get_baud_rate(&self) -> u32; + fn get_width(&self) -> Width; + fn get_parity(&self) -> Parity; + fn get_stop_bits(&self) -> StopBits; + fn get_hw_flow_control(&self) -> bool; + fn get_configuration(&self) -> Configuration; +} + +pub trait Configure { + fn set_baud_rate(&self, rate: u32) -> Result; + fn set_width(&self, width: Width) -> Result<(), ErrorCode>; + fn set_parity(&self, parity: Parity) -> Result<(), ErrorCode>; + fn set_stop_bits(&self, stop: StopBits) -> Result<(), ErrorCode>; + fn set_hw_flow_control(&self, on: bool) -> Result<(), ErrorCode>; + fn configure(&self, params: Parameters) -> Result<(), ErrorCode>; +} +``` + +Methods in `Configure` can return the following error conditions: + - `OFF`: The underlying hardware is currently not available, perhaps + because it has not been initialized or in the case of a shared + hardware USART controller because it is set up for SPI. + - `INVAL`: Baud rate was set to 0. + - `NOSUPPORT`: The underlying UART cannot satisfy this configuration. + - `BUSY`: The UART is currently busy processing an operation which + would be affected by a change of the respective parameter. + - `FAIL`: Other failure condition. + +`Configuration::get_configuration` can be used to retrieve a copy of +the current UART configuration, which can later be restored using the +`Configure::configure` method. An implementation of the +`Configure::configure` method must ensure that this configuration is +applied atomically: either the configuration described by the passed +`Parameters` is applied in its entirety or the device's configuration +shall remain unchanged, with the respective check's error returned. + +The UART may be unable to set the precise baud rate specified. For +example, the UART may be driven off a fixed clock with integer +prescalar. An implementation of `configure` MUST set the baud rate to +the closest possible value to the `baud_rate` field of the `params` +argument and an an implementation of `set_baud_rate` MUST set the baud +rate to the closest possible value to the `rate` argument. The `Ok` +result of `set_baud_rate` includes the actual rate set, while an +`Err(INVAL)` result means the requested rate is well outside the +operating speed of the UART (e.g., 16MHz). + + +3 `Transmit` and `TransmitClient` +=============================== + +The `Transmit` and `TransmitClient` traits allow a client to transmit +over the UART. + +```rust +enum AbortResult { + Callback(bool), + NoCallback, +} + +pub trait Transmit<'a> { + fn set_transmit_client(&self, client: &'a dyn TransmitClient); + + fn transmit_buffer( + &self, + tx_buffer: &'static mut [u8], + tx_len: usize, + ) -> Result<(), (ErrorCode, &'static mut [u8])>; + + fn transmit_character(&self, character: u32) -> Result<(), ErrorCode>; + fn transmit_abort(&self) -> AbortResult; +} + +pub trait TransmitClient { + fn transmitted_character(&self, rval: Result<(), ErrorCode>) {} + fn transmitted_buffer( + &self, + tx_buffer: &'static mut [u8], + tx_len: usize, + rval: Result<(), ErrorCode>, + ); +} +``` + +The `Transmit` trait has two data paths: `transmit_character` and +`transmit_buffer`. The `transmit_character` method is used in narrow +use cases in which buffer management is not needed or when the client +transmits 9-bit characters. Generally, software should use the +`transmit_buffer` method. Most software implementations use DMA, such +that a call to `transmit_buffer` triggers a single interrupt when the +transfer completes; this saves energy and CPU cycles over per-byte +transfers and also improves transfer speeds because hardware can keep +the UART busy. + +Each `u32` passed to `transmit_character` is a single UART character. +The UART MUST ignore then high order bits of the `u32` that are +outside the current character width. For example, if the UART is +configured to use 9-bit characters, it must ignore bits 31-9: if the +client passes `0xffffffff`, the UART will transmit `0x1ff`. + +Each byte transmitted with `transmit_buffer` is a UART character. If +the UART is using 8-bit characters, each character is a byte. If the +UART is using smaller characters, it MUST ignore the high order bits +of the bytes passed in the buffer. For example, if the UART is using +6-bit characters and is told to transmit `0xff`, it will transmit +`0x3f`, ignoring the first two bits. + +If a client needs to transmit characters larger than 8 bits, it should +use `transmit_character`, as `transmit_buffer` is a buffer of 8-bit +bytes and cannot store 9-bit values. If the UART is configured to use +characters wider than 8-bit, the `transmit_buffer` operation is +disabled and calls to it must return `ErrorCode::INVAL`. + +There can be a single transmit operation ongoing at any +time. Successfully calling either `transmit_buffer` or +`transmit_character` causes the UART to become busy until it issues +the callback corresponding to the outstanding operation. + +3.1 `transmit_buffer` and `transmitted_buffer` +=============================== + +`Transmit::transmit_buffer` sends a buffer of data. The result +returned by `transmit_buffer` indicates whether there will be a +callback in the future. If `transmit_buffer` returns `Ok(())`, +implementation MUST call the `TransmitClient::transmitted_buffer` +callback in the future when the transmission completes or fails. If +`transmit_buffer` returns `Err` it MUST NOT issue a callback in the +future in response to this call. If the error is `BUSY`, this is +because there is an outstanding call to `transmit_buffer` or +`transmit_character`: the implementation will continue to handle +the original call and issue the originally scheduled callback +(as if the call that `Err`'d with `BUSY` never happened). However, + it does not issue a callback for the call to `transmit_buffer` + that returned `Err`. + +The valid error codes for `transmit_buffer` are: + - `OFF`: the underlying hardware is not available, perhaps because it has + not been initialized or has been initialized into a different mode + (e.g., a USART has been configured to be a SPI). + - `BUSY`: the UART is already transmitting and has not made a transmission + complete callback yet. + - `SIZE`: `tx_len` is larger than the passed slice or `tx_len == 0`. + - `INVAL`: the device is configured for data widths larger than 8-bit. + - `FAIL`: some other failure. + +Calling `transmit_buffer` while there is an outstanding +`transmit_buffer` or `transmit_character` operation MUST return `Err(BUSY)`. + +The `TransmitClient::transmitted_buffer` callback indicates completion +of a buffer transmission. The `Result` indicates whether the buffer +was successfully transmitted. The `tx_len` argument specifies how +many characters (defined by `Configure`) were transmitted. If the +`rval` of `transmitted_buffer` is `Ok(())`, `tx_len` MUST be equal to +the size of the transmission started by `transmit_buffer`, defined +above. A call to `transmit_character` or `transmit_buffer` made within +this callback MUST NOT return `Err(BUSY)` unless it is because this is +not the first call to one of these methods in the callback. When this +callback is made, the UART MUST be ready to receive another call. The +valid `ErrorCode` values for `transmitted_buffer` are all of those +returned by `transmit_buffer` plus: + - `CANCEL` if the call to `transmit_buffer` was cancelled by a call + to `abort` and the entire buffer was not transmitted. + - `SIZE` if the buffer could only be partially transmitted. + +3.2 `transmit_character` and `transmitted_character` +=============================== + +The `transmit_character` method transmits a single character of data +asynchronously. The word length is determined by the UART +configuration. A UART implementation MAY choose to not implement +`transmit_character` and `transmitted_character`. There is a default +implementation of `transmitted_character` so clients that do not use +`receive_character` do not have to implement a callback. + +If `transmit_character` returns `Ok(())`, the implementation MUST call the +`transmitted_character` callback in the future. If a call to +`transmit_character` returns `Err`, the implementation MUST NOT issue a +callback for this call, although if the it is `Err(BUSY)` is will +issue a callback for the outstanding operation. Valid `ErrorCode` +results for `transmit_character` are: + - `OFF`: The underlying hardware is not available, perhaps because + it has not been initialized or in the case of a shared + hardware USART controller because it is set up for SPI. + - `BUSY`: the UART is already transmitting and has not made a + transmission callback yet. + - `NOSUPPORT`: the implementation does not support `transmit_character` + operations. + - `FAIL`: some other error. + +The `TransmitClient::transmitted_character` method indicates that a single +word transmission completed. The `Result` indicates whether the word +was successfully transmitted. A call to `transmit_character` or +`transmit_buffer` made within this callback MUST NOT return BUSY +unless it is because this is not the first call to one of these +methods in the callback. When this callback is made, the UART MUST be +ready to receive another call. The valid `ErrorCode` values for +`transmitted_character` are all of those returned by `transmit_character` plus: + - `CANCEL` if the call to `transmit_character` was cancelled by a call to + `abort` and the word was not transmitted. + +3.3 `transmit_abort` +=============================== + +The `transmit_abort` method allows a UART implementation to terminate +an outstanding call to `transmit_character` or `transmit_buffer` early. The +result of `transmit_abort` indicates two things: + + 1. whether a callback will occur (there is an oustanding operation), and + 2. if a callback will occur, whether the operation is cancelled. + +If `transmit_abort` returns `Callback`, there will be be a future +callback for the completion of the outstanding request. If there is +an outstanding `transmit_buffer` or `transmit_character` operation, +`transmit_abort` MUST return `Callback`. If there is no outstanding +`transmit_buffer` or `transmit_abort` operation, `transmit_abort` MUST +return `NoCallback`. + +The three possible values of `AbortResult` have these meanings: + - `Callback(true)`: there was an outstanding operation, which + is now cancelled. A callback will be made for that operation with an + `ErrorCode` of `CANCEL`. + - `Callback(false)`: there was an outstanding operation, which + has not been cancelled. A callback will be made for that operation with + a result other than `Err(CANCEL)`. + - `NoCallback`: there was no outstanding request and there will be no future + callback. + +Note that the semantics of the boolean field in +`AbortResult::Callback` refer to whether the operation is cancelled, +not whether this particular call cancelled it: a `true` result +indicates that there will be an `ErrorCode::CANCEL` in the +callback. Therefore, if a client calls `transmit_abort` twice and the +first call returns `Callback(true)`, the second call's return value of +`Callback(true)` can involve no state transition within the sender, as +it simply reports the curent state (of the call being cancelled). + + +4 `Receive` and `ReceiveClient` traits +=============================== + +The `Receive` and `ReceiveClient` traits are used to receive data from the +UART. They support both single-word and buffer reception. Buffer-based +reception is more efficient, as it allows an MCU to handle only one +interrupt for many characters. However, buffer-based reception only supports +characters of 6, 7, and 8 bits, so clients using 9-bit words need to use +word operations. If the UART is configured to use characters wider +than 8-bit, the `receive_buffer` operation is disabled and calls to +it must return `ErrorCode::INVAL`. + +Each byte received is a character for the UART. If the UART is using +8-bit characters, each character is a byte. If the UART is using +smaller characters, it MUST zero the high order bits of the data +values. For example, if the UART is using 6-bit characters and +receives `0x1f`, it must store `0x1f` in a byte and not set high order +bits. If the UART is using 9-bit words and receives `0x1ea`, it +stores this in a 32-bit value for `receive_character` as `0x000001ea`. + +`Receive` supports a single outstanding receive request. A successful +call to `receive_buffer` or `receive_character` causes UART reception to be +busy until the callback for the outstanding operation is issued. + +If the UART returns `Ok` to a call to `receive_buffer` or +`receive_character`, it MUST return `Err(BUSY)` to subsequent calls to +those methods until it issues the callback corresponding to the +outstanding operation. The first call to `receive_buffer` or +`receive_character` from within a receive callback MUST NOT return +`Err(BUSY)`: when it makes a callback, a UART must be ready to handle +another reception request. + + +```rust +enum AbortResult { + Failure, + Success, +} + +pub trait Receive<'a> { + fn set_receive_client(&self, client: &'a dyn ReceiveClient); + fn receive_buffer( + &self, + rx_buffer: &'static mut [u8], + rx_len: usize, + ) -> Result<(), (ErrorCode, &'static mut [u8])>; + fn receive_character(&self) -> Result<(), ErrorCode>; + fn receive_abort(&self) -> AbortResult; +} + +pub trait ReceiveClient { + fn received_character(&self, _character: u32, _rval: Result<(), ErrorCode>, _error: Error) {} + + fn received_buffer( + &self, + rx_buffer: &'static mut [u8], + rx_len: usize, + rval: Result<(), ErrorCode>, + error: Error, + ); +} +``` + +4.1 `receive_buffer`, `received_buffer` and `receive_abort` +=============================== + +The `receive_buffer` method receives from the UART into the passed +buffer. It receives up to `rx_len` bytes. When `rx_len` bytes has +been received, the implementation MUST call the `received_buffer` +callback to signal reception completion with an `rval` of +`Ok(())`. The implementation MAY call the `received_buffer` callback +before all `rx_len` bytes have been received. If it calls the +`received_buffer` callback before all `rx_len` bytes have been +received, `rval` MUST be `Err`. Valid return values for +`receive_buffer` are: + - `OFF`: the underlying hardware is not available, because it has not + been initialized or is configured in a way that does not allow + UART communication (e.g., a USART is configured to be SPI). + - `BUSY`: the UART is already receiving (a buffer or a word) + and has not made a reception `received` callback yet. + - `SIZE`: `rx_len` is larger than the passed slice or `rx_len == 0`. + - `INVAL`: the device is configured for data widths larger than + 8-bit. + + +The `receive_abort` method can be used to cancel an outstanding buffer +reception call. If there is an outstanding buffer reception, calling +`receive_abort` MUST terminate the reception as early as possible, +possibly completing it before all of the requested bytes have been +read. In this case, the implementation MUST issue a `received_buffer` +callback reporting the number of bytes actually read and with an +`rval` of `Err(CANCEL)`. + +Reception early termination is necessary for UART virtualization. For +example, suppose there are two UART clients. The first issues a read +of 80 bytes. After 20 bytes have been read, the second client issues +a read of 40 bytes. At this point, the virtualizer has to reduce the +length of its outstanding read, from 60 (80-20) to 40 bytes. It needs +to copy the 20 bytes read into the first client's buffer, the next 40 +bytes into both of their buffers, and the last 20 bytes read into the +first client's buffer. It accomplishes this by calling `receive_abort` +to terminate the 100-byte read, copying the bytes read from the +resulting callback, then issuing a `receive_buffer` of 40 bytes. + +The valid return values for `receive_abort` are: + - `Callback(true)`: there was a reception outstanding and + it has been cancelled. A callback with `Err(CANCEL)` will be called. + - `Callback(false)`: there was a reception outstanding but it + was not cancelled. A callback will be called with an `rval` other than + `Err(CANCEL)`. + - `NoCallback`: there was no reception outstanding and the + implementation will not issue a callback. + +If there is no outstanding call to `receive_buffer` or +`receive_character`, `receive_abort` MUST return `NoCallback`. + +4.2 `receive_character` and `received_character` +=============================== + +The `receive_character` method and `received_character` callback allow +a client to perform character operations without buffer +management. They receive a single UART character, where the character width +is defined by the UART configuration and can be wider than 8 +bits. + +A UART implementation MAY choose to not implement `receive_character` and +`received_character`. There is a default implementation of `received_character` +so clients that do not use `receive_character` do not have to implement a +callback. + +If the UART returns `Ok(())` to a call to `receive_character`, it MUST make +a `received_character` callback in the future, when it receives a character +or some error occurs. Valid `Err` values of `receive_character` are: + - `BUSY`: the UART is busy with an outstanding call to + `receive_buffer` or `receive_character`. + - `OFF`: the UART is powered down or in a configuration that does + not allow UART reception (e.g., it is a USART in SPI mode). + - `NOSUPPORT`: `receive_character` operations are not supported. + - `FAIL`: some other error. + +5 Composite Traits +=============================== + +In addition to the 6 basic traits, the UART HIL defines several traits +that use these basic traits as supertraits. These composite traits allow +structures to refer to multiple pieces of UART functionality with a +single reference and ensure that their implementations are coupled. + +```rust +pub trait Uart<'a>: Configure + Configuration + Transmit<'a> + Receive<'a> {} +pub trait UartData<'a>: Transmit<'a> + Receive<'a> {} +pub trait Client: ReceiveClient + TransmitClient {} +``` + +The HIL provides blanket implementations of these four traits: any +structure that implements the supertraits of a composite trait will +automatically implement the composite trait. + +6 Capsules +=============================== + +The Tock kernel provides two standard capsules for UARTs: + + * `capsules::console::Console` provides a userspace abstraction of a console. + It allows userspace to print to and read from a serial port through a + system call API. + * `capsules::virtual_uart` provides a set of abstractions for virtualizing + a single UART into many UARTs. + +The structures in `capsules::virtual_uart` allow multiple clients to +read from and write to a serial port. Write operations are interleaved +at the granularity of `transmit_buffer` calls: each client's +`transmit_buffer` call is printed contiguously, but consecutive calls +to `transmit_buffer` from a single client may have other data inserted +between them. When a client calls `receive_buffer`, it starts reading +data from the serial port at that point in time, for the length of its +request. If multiple clients make `receive_buffer` calls that overlap +with one another, they each receive copies of the received data. + +Suppose, for example, that there are two clients. One of them calls +`receive_buffer` for 8 bytes. A user starts typing "1234567890" at the +console. After the third byte, another client calls `receive_buffer` +for 4 bytes. After the user types "7", the second client will receive +a `received_buffer` callback with a buffer containing "4567". After +the user types "8", the first client will receive a callback with a +buffer containing "12345678". If the second client then calls +`receive_buffer` with a 1-byte buffer, it will receive "9". It never +sees "8", since that has been consumed by the time it makes this +second receive call. + + +7 Authors' Address +================================= +``` +Philip Levis +409 Gates Hall +Stanford University +Stanford, CA 94305 +USA +pal@cs.stanford.edu + +Leon Schuermann +``` diff --git a/doc/reference/trd102-adc.md b/doc/reference/trd102-adc.md index 147711e510..d33e90039f 100644 --- a/doc/reference/trd102-adc.md +++ b/doc/reference/trd102-adc.md @@ -49,7 +49,7 @@ either one-shot or repeatedly. It is implemented by chip drivers to provide ADC functionality. Data is provided through the Client trait. It has four functions and one associated type: -``` +```rust /// Simple interface for reading an ADC sample on any channel. pub trait Adc { /// The chip-dependent type of an ADC channel. @@ -73,6 +73,8 @@ pub trait Adc { /// Can be used to stop any simple or high-speed sampling operation. No /// further callbacks will occur. fn stop_sampling(&self) -> Result<(), ErrorCode>; + + fn set_client(&self, client: &'static dyn Client); } ``` @@ -122,7 +124,7 @@ from by userland applications. The Client trait handles responses from Adc trait sampling commands. It is implemented by capsules to receive chip driver responses. It has one function: -``` +```rust /// Trait for handling callbacks from simple ADC calls. pub trait Client { /// Called when a sample is ready. @@ -145,12 +147,12 @@ The AdcHighSpeed trait is used for sampling data at high frequencies such that receiving individual samples would be untenable. Instead, it provides an interface that returns buffers filled with samples. This trait relies on the Adc trait being implemented as well in order to provide primitives like -`intialize` and `stop_sampling` which are used for ADCs in this mode as well. +`initialize` and `stop_sampling` which are used for ADCs in this mode as well. While we expect many chips to support the Adc trait, we expect the AdcHighSpeed trait to be implemented due to a high-speed sampling need on a platform. The trait has three functions: -``` +```rust /// Interface for continuously sampling at a given frequency on a channel. /// Requires the AdcSimple interface to have been implemented as well. pub trait AdcHighSpeed: Adc { @@ -192,6 +194,8 @@ pub trait AdcHighSpeed: Adc { fn retrieve_buffers(&self) -> (Result<(), ErrorCode>, Option<&'static mut [u16]>, Option<&'static mut [u16]>); + + fn set_highspeed_client(&self, client: &'static dyn HighSpeedClient); } ``` @@ -236,7 +240,7 @@ The HighSpeedClient trait is used to receive samples from a call to `sample_highspeed`. It is implemented by a capsule to receive chip driver responses. It has one function: -``` +```rust /// Trait for handling callbacks from high-speed ADC calls. pub trait HighSpeedClient { /// Called when a buffer is full. @@ -279,7 +283,7 @@ SAM4L implementation creates an AdcChannel struct which contains and enum defining its value. Each possible ADC channel is then statically created. Other chips may want to consider a similar system. -``` +```rust /// Representation of an ADC channel on the SAM4L. pub struct AdcChannel { chan_num: u32, @@ -317,19 +321,16 @@ pub static mut CHANNEL_AD1: AdcChannel = AdcChannel::new(Channel::AD1); pub static mut CHANNEL_REFERENCE_GROUND: AdcChannel = AdcChannel::new(Channel::ReferenceGround); ``` -6.2 Client Type +6.2 Client Handling --------------------------------- -It is difficult in Rust to require a argument that implements two types. -However, it is convenient for the implementation to expect a single client that -implements both the `adc::Client` and `adc::HighSpeedClient` interfaces. It is -possible to do so by defining a new trait that requires each. +As ADC functionality is split between two traits, there are two callback traits. +ADC driver implementations that use both `Adc` and `AdcHighSpeed` need two +clients, which must both be set: -``` -/// Create a trait of both client types to allow a single client reference to -/// act as both -pub trait EverythingClient: hil::adc::Client + hil::adc::HighSpeedClient {} -impl EverythingClient for C {} +```rust +hil::adc::Adc::set_client(&peripherals.adc, adc); +hil::adc::AdcHighSpeed::set_client(&peripherals.adc, adc); ``` 6.3 Clock Initialization diff --git a/doc/reference/trd103-gpio.md b/doc/reference/trd103-gpio.md index 40ae5b3bb4..cf080cf7b8 100644 --- a/doc/reference/trd103-gpio.md +++ b/doc/reference/trd103-gpio.md @@ -348,3 +348,5 @@ phone: +1 650 725 9046 Amit Levy email: Amit Levy ``` + +[TRD1]: trd1-trds.md diff --git a/doc/reference/trd104-syscalls.md b/doc/reference/trd104-syscalls.md index 63cb44c245..85a49df39a 100644 --- a/doc/reference/trd104-syscalls.md +++ b/doc/reference/trd104-syscalls.md @@ -5,10 +5,10 @@ System Calls **Working Group:** Kernel
**Type:** Documentary
**Status:** Draft
-**Author:** Hudson Ayers, Guillaume Endignoux, Jon Flatley, Philip Levis, Amit Levy, Leon Schuermann, Johnathan Van Why
+**Author:** Hudson Ayers, Guillaume Endignoux, Jon Flatley, Philip Levis, Amit Levy, Pat Pannuto, Leon Schuermann, Johnathan Van Why, dcz
**Draft-Created:** August 31, 2020
-**Draft-Modified:** November 23, 2021
-**Draft-Version:** 6
+**Draft-Modified:** June 14, 2024
+**Draft-Version:** 9
**Draft-Discuss:** tock-dev@googlegroups.com
Abstract @@ -42,7 +42,7 @@ in other documents. Three design considerations guide the design of Tock's system call API and ABI. - 1. Tock is currently supported on the ARM CortexM and RISCV architectures. + 1. Tock is currently supported on the ARM CortexM and RISC-V architectures. It may support others in the future. Its ABI must support both architectures and be flexible enough to support future ones. 2. Tock userspace applications can be written in any language. The system @@ -234,6 +234,37 @@ Values greater than 1023 are reserved for userspace library use. Value 1024 (BADRVAL) is for when a system call returns a different failure or success variant than the userspace library expects. +3.4 Returning To Userspace +--------------------------------- + +When the kernel returns to userspace, it only gets to set registers for +one stack frame. In practice, we have two cases: + +### Direct Resume + +Userspace resumes execution directly after the `svc` invocation, so the +assembly that follows the `svc` command can use the values in r0-r3 +as-set by the kernel. + +### Pushed Callback + +Userspace resumes execution at the start of the callback function. + +The values in r0-r3 are consumed by the callback. When the callback +finishes, it will `pop {lr}` (or similar), where the link register in +the callback stack frame has been set by the kernel to the instruction +after the `svc` that relinquished control to the kernel. + +The assembly that invoked the syscall now gets to run. At this point +r0-r3 are unknown as those are caller-save registers (which means the +Upcall callback can clobber them freely). The assembly that invoked the +`svc` cannot make any assumptions about the values in r0-r3, nor can the +kernel use them to pass things "to" the calling assembly. Thus, the +`PushedCallback` case has to use a pointer-based approach for the kernel +to communicate with the assembly that invokes the `svc` (e.g. +`yield-param-A` in `Yield-NoWait`). + + 4 System Call API ================================= @@ -245,7 +276,7 @@ call drivers, which can be added and removed in different kernel builds. The full set of valid system calls a kernel supports therefore depends on what system call drivers it has installed. -The 6 classes are: +The 7 classes are: | Syscall Class | Syscall Class Number | |------------------|----------------------| @@ -293,30 +324,15 @@ result with an error of `NOSUPPORT`. The Yield system call class is how a userspace process handles upcalls, relinquishes the processor to other processes, or waits for one of its long-running calls to complete. The Yield system call -class implements the only blocking system call in Tock that returns, -`yield-wait`. - -When a process calls a Yield system call, the kernel schedules one -pending upcall (if any) to execute on the userspace stack. If there -are multiple pending upcalls, each one requires a separate Yield -call to invoke. The kernel invokes upcalls only in response to Yield -system calls. This form of very limited preemption allows userspace -to manage concurrent access to its variables. - -There are two Yield system calls: - - `yield-wait` - - `yield-no-wait` - -The first call, `yield-wait`, blocks until an upcall executes. It is -commonly used to provide a blocking I/O interface to userspace or to -indicate to the kernel that the process has no work to do so the system -can be put into a sleep state. A userspace library starts a long-running -operation that has an upcall, then calls `yield-wait` to wait for an upcall. -When the `yield-wait` returns, the process checks if the resuming upcall was -the one it was expecting, and if not calls `yield-wait` again. - -The second call, `yield-no-wait`, executes a single upcall if any is pending. -If no upcalls are pending it returns immediately. +class implements the only blocking system calls in Tock that return: +`Yield-Wait` and `Yield-WaitFor`. +The kernel invokes upcalls only in response to Yield +system calls. + +There are three Yield system call variants: + - Yield-Wait + - Yield-NoWait + - Yield-WaitFor The register arguments for Yield system calls are as follows. The registers r0-r3 correspond to r0-r3 on CortexM and a0-a3 on RISC-V. @@ -324,40 +340,99 @@ r0-r3 correspond to r0-r3 on CortexM and a0-a3 on RISC-V. | Argument | Register | |------------------------|----------| | Yield number | r0 | -| No wait field | r1 | -| unused | r2 | -| unused | r3 | +| yield-param-A | r1 | +| yield-param-B | r2 | +| yield-param-C | r3 | - -The yield number specifies which call is invoked. +The Yield number (in r0) specifies which call is invoked: | System call | Yield number value | |-----------------|--------------------| | yield-no-wait | 0 | | yield-wait | 1 | +| yield-wait-for | 2 | + +All other yield number values are reserved. If an invalid yield number +is passed the kernel MUST return immediately and MUST NOT +use `yield-param-A`, `yield-param-B`, or `yield-param-C`. + +The meaning of `yield-param-X` is specific to the yield type. + + +### 4.1.1 Yield-NoWait + +Yield number 0, Yield-NoWait, executes a single upcall if any is +pending. If no upcalls are pending it returns immediately. +There are no return values from Yield-NoWait. This is because if an upcall was +invoked, the kernel pushes that function call onto the stack, such that the +return value may be the return value of the upcall. + +Yield-NoWait will use +`yield-param-A` as the memory address of an 8-bit byte to +write to indicate whether an upcall was invoked. If invoking Yield-NoWait +resulted in an upcall executing, Yield-NoWait writes 1 to the field address. If +invoking Yield-NoWait resulted in no upcall executing, Yield-NoWait writes 0 to the +field address. Userspace SHOULD ensure that `yield-param-A` points to a valid address +in the current process. If userspace does not wish to receive the Yield-NoWait +result, it SHOULD set `yield-param-A` to `0x0`. The kernel SHALL write the Yield-NoWait result +if `yield-param-A` points to any valid process memory and SHALL NOT write the Yield-NoWait +result if it points to an address not in the memory allocated to the +calling process. +Yield-NoWait can use the Yield-NoWait result to allow userspace loops that want +to flush the upcall queue to execute Yield-NoWait until the queue is +empty. -All other yield number values are reserved. If an invalid -yield number is passed the kernel MUST return immediately. +`yield-param-B` and `yield-param-C` are unused and reserved. -The no wait field is only used by `yield-no-wait`. It contains the -memory address of an 8-bit byte that `yield-no-wait` writes to -indicate whether an upcall was invoked. If invoking `yield-no-wait` -resulted in an upcall executing, `yield-no-wait` writes 1 to the -field address. If invoking `yield-no-wait` resulted in no upcall -executing, `yield-no-wait` writes 0 to the field address. This field -allows userspace loops that want to flush the upcall queue to -execute `yield-no-wait` until the queue is empty. -The Yield system call class has no return value. This is because +### 4.1.2 Yield-Wait + +Yield number 1, Yield-Wait, blocks until an upcall executes. It is +commonly used when applications have no other work to do and are waiting +for an event (upcall) to occur to do more work. + +This call will deliver events to the userspace application in the order +they occurred in time in the kernel. If an application has multiple +subscriptions, the userspace upcall handler is responsible for in some way +noting which callback occurred if necessary. + +_Note:_ This will _only_ return after an upcall _executes_. If an event +occurs which would normally generate an upcall, but that upcall is +currently assigned to the Null Upcall, no upcall executes and thus this +syscall will not return. + +Yield-Wait has no return value. This is because invoking an upcall pushes that function call onto the stack, such that the return value of a call to yield system call may be the -return value of the upcall. This is why the no wait field exists, -so that `yield-no-wait` can return a result to the caller. Allowing -the kernel to pass a return value in register back to userspace -would require either re-entering the kernel or expensive -execution architectures (e.g., additional stacks or additional -stack frames) for upcalls. +return value of the upcall. + +`yield-param-A`, `yield-param-B`, and `yield-param-C` are unused and reserved. + + +### 4.1.3 Yield-WaitFor + +The third call, Yield-WaitFor, blocks until one +specific upcall is ready to execute. If +other events arrive that would invoke an upcall on this process, they +are queued by the kernel, and will be delivered +in response to subsequent Yield calls. +Event order in this queue is maintained. + +The specific upcall is identified by a Driver number and a Subscribe number +(which together form an UpcalId). + +- Driver number: `yield-param-A` +- Subscribe number: `yield-param-B` + +This process will resume execution when an event in the kernel generates +an upcall that matches the specified upcall. No userspace callback +function will invoked by the kernel. Instead, the contents of r0-r2 will +be set to the Upcall Arguments provided by the driver when the upcall is +scheduled. + +`yield-param-C` is unused and reserved. + 4.2 Subscribe (Class ID: 1) -------------------------------- @@ -384,6 +459,9 @@ the upcall function. The `application data` argument is a parameter that an application passes in and the kernel passes back in upcalls unmodified. +The `upcall pointer` SHOULD be a valid upcall, i.e., either a +`SubscribeUpcall` or the Null Upcall, as defined in the next section. + If the passed upcall is not valid (is outside process executable memory and is not the Null Upcall described below), the kernel MUST NOT invoke the requested driver and MUST immediately return a failure @@ -496,14 +574,17 @@ failure variant of `Failure`, with an associated error code of handle userspace/kernel mismatches should be able to handle `Failure` in addition to the expected failure variant (if different than `Failure`). + 4.3.1 Command Identifier 0 --------------------------------- +-------------------------- -Every device driver MUST implement command number 0 as the -"exists" command. This command always returns `Success`. This command -allows userspace to determine if a particular system call driver is -installed; if it is, the command returns `Success`. If it is not, the -kernel returns `Failure` with an error code of `NODEVICE`. +Command Identifier 0 provides an existence check for drivers. Command +Identifier 0 MUST return either `Success` or `Failure` with `ENODEVICE`. +`Success` indicates that the driver is present and the userspace process can +issue system calls to it. If the driver is not accessible, Command Identifier 0 +returns `Failure` with an error code of `ENODEVICE`. A driver may be not +accessible because the kernel does not have it, the process does not have the +required permissions to use it, or other reasons. 4.4 Read-Write Allow (Class ID: 3) --------------------------------- @@ -518,10 +599,16 @@ on RISC-V. | Argument | Register | |------------------|----------| | Driver number | r0 | -| Buffer number | r1 | +| Allow number | r1 | | Address | r2 | | Size | r3 | +The *allow number* argument is an ordinal number (index) of the buffer. +When Read-Write Allow is called, the provided buffer +SHALL get assigned to the provided *allow number*, +replacing the previous buffer assigned to that *allow number*, +if there was one. +The supported *allow number*s are defined by the driver. The Tock kernel MUST check that the passed buffer is contained within the calling process's writeable address space. Every byte of a passed @@ -529,8 +616,6 @@ buffer must be readable and writeable by the process. Zero-length buffers may therefore have arbitrary addresses. If the passed buffer is not complete within the calling process's writeable address space, the kernel MUST return a failure result with an error code of `INVALID`. -The buffer number specifies which buffer this is. A driver may -support multiple allowed buffers. The return variants for Read-Write Allow system calls are `Failure with 2 u32` and `Success with 2 u32`. In both cases, `Argument 0` @@ -547,7 +632,7 @@ If the kernel cannot access the grant region for this process, `NOMEM` will be returned. This can be caused by either running out a space in the grant region of RAM for the process, or the grant was never registered with the kernel during capsule creation at board startup. If the specified -buffer number is not supported by the driver, the kernel will return `INVALID`. +allow number is not supported by the driver, the kernel will return `INVALID`. The standard access model for allowed buffers is that userspace does not read or write a buffer that has been allowed: access to the memory @@ -568,7 +653,7 @@ a buffer by calling allow with the same pointer and a longer length and such a call is not required to return an error code of `INVALID`. Similarly, it is possible for userspace to allow the same buffer multiple times to the kernel. This means, in practice, that the kernel -may have multiple writeable references to the same memory and MUST take +may have multiple writeable references to the same memory and MUST take precautions to ensure this does not violate safety within the kernel. @@ -649,11 +734,13 @@ the buffer). --------------------------------- The Read-Only Allow class is very similar to the Read-Write Allow -class. It differs in two ways: +class. It differs in some ways: 1. The buffer it passes to the kernel is read-only, and the process MAY freely read the buffer. 2. The kernel MUST NOT write to a buffer shared with a Read-Only Allow. +3. The *allow number*s in the Read-Only Allow + are independent from those in the Read-Write Allow. The semantics and calling conventions of Read-Only Allow are otherwise identical to Read-Write Allow: a userspace API MUST NOT depend on @@ -788,17 +875,18 @@ If an exit syscall is successful, it does not return. Therefore, the return value of an exit syscall is always `Failure`. `exit-restart` and `exit-terminate` MUST always succeed and so never return. + 5 libtock-c Userspace Library Methods ================================= This section describes the method signatures for system calls and upcalls in C, as an example -of how they appear to application/userspace code.. +of how they appear to application/userspace code. Because C allows a single return value but Tock system calls can return multiple values, they do not easily map to idiomatic C. These low-level APIs are translated into standard C code by the userspace library. The general calling convention is that the complex return types are returned as structs. Since these structs are composite types larger than a single word, the -ARM and RISCV calling conventions pass them on the stack. +ARM and RISC-V calling conventions pass them on the stack. The system calls are implemented as inline assembly. This assembly moves arguments into the correct registers and invokes the system call, and on return copies the returned data into the return type @@ -957,7 +1045,14 @@ Email: pal@cs.stanford.edu Amit Levy +Pat Pannuto + Leon Schuermann Johnathan Van Why ``` + +7 References and Additional Information +======================================= + +- [Design RFC for Command 0 Semantics](../rfcs/2023-08-18--CommandZeroSemantics.md). diff --git a/doc/reference/trd106-completion-codes.md b/doc/reference/trd106-completion-codes.md new file mode 100644 index 0000000000..5522c0df82 --- /dev/null +++ b/doc/reference/trd106-completion-codes.md @@ -0,0 +1,116 @@ +Application Completion Codes +======================================== + +**TRD:** 106
+**Working Group:** Kernel
+**Type:** Documentary
+**Status:** Draft
+**Author:** Alyssa Haroldsen
+**Draft-Created:** December 6, 2021
+**Draft-Modified:** January 25, 2022
+**Draft-Version:** 1
+**Draft-Discuss:** tock-dev@googlegroups.com
+ +Abstract +------------------------------- +This advisory document describes the expected behavior of application completion +codes when terminating via the `exit` syscall, as described in +[TRD 104][exit-syscall]. + +1 Introduction +=============================== +When an application exits via the [`exit` syscall][exit-syscall], it can specify +a **completion code**, an unsigned 32-bit number which indicates status. This +information can be stored in the kernel and used in management or policy +decisions. + +This number is called an "exit status", "exit code", or "result code" on other +platforms. + +2 Design Considerations +=============================== +When possible, Tock applications should follow existing conventions and +terminology from other major platforms. This assists in helping the project be +more understandable to newcomers by following the principle of least +astonishment. + +This advisory document provides guidance for the ecosystem of Tock applications +using the `exit` syscall, and does not define the behavior of the syscall +itself. + +3 Design +=============================== +A completion code of `0` passed to the `exit` syscall MUST indicate normal app +termination. A non-zero completion code SHOULD be used to indicate abnormal +termination. This distinction is useful so that a Tock kernel can handle +success/failure cases differently, e.g. by printing error messages, +and so that kernel extensions (such as process exit handlers defined by a board) +or external tools (such as a tool designed to parse the output from a kernel +with *trace\_syscalls* enabled) can match on these two cases. +This behavior also matches the convention for Unix exit codes, such that it +likely matches the expectations for users coming from that domain. + +A completion code between `1` and `1024` inclusive SHOULD be the +same value as one of the error codes specified in [TRD 104][error-codes]. +This requirement is a SHOULD rather than a MUST because it is useful in the +common case (it allows software to infer something about the cause of an +error that led to an exit, and possibly print a useful message) but also +allows a process to do something else if needed (e.g. for compatibility +with some other standard of exit codes). + +Accordingly, the core kernel MUST NOT assume any semantic meaning for completion +codes or take actions based on their values besides printing error messages +unless + +- there is a specification of a particular application's completion code space + written in a TRD, and + +- the kernel can reliably identify that application and associate it with this + specification. +While there are common and conventional uses of certain values, applications +are not required to follow these and may assign their own semantic meanings +to values. + +| **Completion Code** | **Meaning** | +| ------------------- | --------------------------------------------- | +| 0 | Success | +| 1-1024 | SHOULD be a [TRD 104 error code][error-codes] | +| 1025-`u32::MAX` | Not defined | + +4 Implementation +=============================== +As of writing, libtock [currently implements][termination] this TRD via the +`Termination` trait. + +```rust +pub trait Termination { + fn complete(self) -> !; +} + +impl Termination for () { + fn complete(self) -> ! { + S::exit_terminate(0) + } +} + +impl Termination for Result<(), ErrorCode> { + fn complete(self) -> ! { + let exit_code = match self { + Ok(()) => 0, + Err(ec) => ec as u32, + }; + S::exit_terminate(exit_code); + } +} +``` + +5 Author's Address +=============================== +``` +Alyssa Haroldsen +Hudson Ayers +``` + +[error-codes]: https://github.com/tock/tock/blob/master/doc/reference/trd104-syscalls.md#33-error-codes +[exit-syscall]: https://github.com/tock/tock/blob/master/doc/reference/trd104-syscalls.md#47-exit-class-id-6 +[termination]: https://github.com/tock/libtock-rs/blob/030e5450c9480beb8b62674e1d6795f4e1697b19/platform/src/termination.rs diff --git a/doc/reference/trd2-hil-design.md b/doc/reference/trd2-hil-design.md index 550c8708aa..1a8716e8fc 100644 --- a/doc/reference/trd2-hil-design.md +++ b/doc/reference/trd2-hil-design.md @@ -4,7 +4,8 @@ Design of Kernel Hardware Interface Layers (HILs) **TRD: 2**
**Working Group:** Kernel
**Type:** Best Current Practice
-**Status:** Final
+**Status:** Obsolete
+**Obsoleted By:** 3
**Author:** Brad Campbell, Philip Levis
Abstract diff --git a/doc/reference/trd3-hil-design.md b/doc/reference/trd3-hil-design.md new file mode 100644 index 0000000000..417c9cc934 --- /dev/null +++ b/doc/reference/trd3-hil-design.md @@ -0,0 +1,835 @@ +Design of Kernel Hardware Interface Layers (HILs) +======================================== + +**TRD: 3**
+**Working Group:** Kernel
+**Type:** Best Current Practice
+**Status:** Final
+**Obsoletes:** 2
+**Author:** Brad Campbell, Philip Levis, Hudson Ayers
+ +Abstract +------------------------------- + +This document describes design rules of hardware interface layers (HILs) +in the Tock operating system. HILs are Rust traits that provide a +standard interface to a hardware resource, such as a sensor, a flash +chip, a cryptographic accelerator, a bus, or a radio. Developers +adding new HILs to Tock should read this document and verify they have +followed these guidelines. + +Introduction +=============================== + +In Tock, a hardware interface layer (HIL) is a collection of Rust +traits and types that provide a standardized API to a hardware +resource such as a sensor, flash chip, cryptographic accelerator, bus, +or a radio. + +Capsules use HILs to implement their functionality. For example, a +system call driver capsule that gives processes access to a +temperature sensor relies on having a reference to an implementation +of the `kernel::hil::sensors::TemperatureDriver` trait. This allows +the system call driver capsule to work on top of any implementation of +the `TemperatureDriver` trait, whether it is a local, on-chip sensor, +an analog sensor connected to an ADC, or a digital sensor over a bus. + +Capsules use HILs in many different ways. They can be directly +accessed by kernel services, such as the in-kernel process console +using the UART HIL. They can be exposed to processes with system +driver capsules, such as with GPIO. They can be virtualized to allow +multiple clients to share a single resource, such as with the virtual +timer capsule. + +This variety of use cases places a complex set of requirements on how +a HIL must behave. For example, Tock expects that every HIL is +virtualizable: it is possible to take one instance of the trait and +allow multiple clients to use it simultaneously, such that each one +thinks it has its own, independent instance of the trait. Because +virtualization often means requests can be queued and the Tock kernel +has a single stack, all HILs must be nonblocking and so have a +callback for completion. This has implications to buffer management +and ownership. + +This document describes these requirements and describes a set of +design rules for HILs. They are: + +1. Don't make synchronous callbacks. +2. Split-phase operations return a synchronous `Result` type which + includes an error code in its `Err` value. +3. For split-phase operations, `Ok` means a callback will occur + while `Err` with an error code besides BUSY means one won't. +4. Error results of split-phase operations with a buffer parameter + include a reference to passed buffer. This returns the buffer + to the caller. +5. Split-phase operations with a buffer parameter take a mutable reference + even if their access is read-only. +6. Split-phase completion callbacks include a `Result` parameter whose + `Err` contains an error code; these errors are a superset of the + synchronous errors. +7. Split-phase completion callbacks for an operation with a buffer + parameter return the buffer. +8. Use fine-grained traits that separate out different use cases. +9. Separate control and datapath operations into separate traits. +10. Blocking APIs are not general: use them sparingly, if at all. +11. `initialize()` methods, when needed, should be in a separate + trait and invoked in an instantiating Component. +12. Traits that can trigger callbacks should have a `set_client` + method. +13. Use generic lifetimes where possible, except for buffers used in + split-phase operations, which should be `'static`. + +The rest of this document describes each of these rules and their +reasoning. + +While these are design rules, they are not sacrosanct. There are +reasons or edge cases why a particular HIL might need to break one (or +more) of them. In such cases, be sure to understand the reasoning +behind the rule; if those considerations don't apply in your use case, +then it might be acceptable to break it. But it's important to realize +the exception is true for *all* implementations of the HIL, not just +yours; a HIL is intended to be a general, reusable API, not a specific +implementation. + +The key recurring point in these guidelines is that a HIL should +encapsulate a wide range of possible implementations and use cases. It +might be that the hardware you are using or designing a HIL for has +particular properties or behavior. That does not mean all hardware +does. For example, a software pseudo-random generator can +synchronously return random numbers. However, a hardware-based one +typically cannot (without blocking). If you write a blocking random +number HIL because you are working with a software one, you are +precluding hardware implementations from using your HIL. This means +that a developer must decide to use either the blocking HIL (which in +some cases can't exist) or the non-blocking one, making software less +reusable. + +Rule 1: Don't Make Synchronous Callbacks +=============================== + +Consider the following API for requesting 32 bits of randomness: + +```rust +trait Random { + fn random(&self) -> Result<(), ErrorCode>; + fn set_client(&self, client: &'static Client); +} + +trait Client { + fn random_ready(&self, result: Result); +} +``` + +If `Random` is implemented on top of a hardware random number +generator, the random bits might not be ready until an interrupt +is issued. E.g., if the implementation generates random numbers +by running AES128 in counter mode on a hidden seed[HCG], then +generating random bits may require an interrupt. + +But AES128 computes *4* 32-bit values of randomness. So a smart +implementation will compute 128 bits, and call back with 32 of them. +The next 3 calls to `random` can produce data from the remaining +data. The simple implementation for this algorithm is to call `random_ready` +inside the call to `random` if cached results are ready: the values +are ready, so issue the callback immediately. + +Making the `random_ready` callback from inside `random` is a bad idea for +two reasons: call loops and client code complexity. + +The first issue that arises is it can create call loops. Suppose that +the client wants 1024 bits (so 32 words) or randomness. It needs to +invoke `random` 32 times. The standard call pattern is to call `random`, +then in the `random_ready` callback, store the new random bits and call +`random` again. This repeats 32 times. + +If the implementation uses an interrupt every 4 calls, then this call +pattern isn't terrible: it would result in 8 stack frames. But suppose +that the implementation chooses to generate not 128 bits at a time, but +rather 1024 bits (e.g., runs counter mode on 32 words). Then one could have +up to 64 stack frames. It might be that the compiler inlines this, but it +also might not. Assuming the compiler always does a specific optimization +for you is dangerous: there all sorts of edge cases and heuristics, and +trying to adjust source code to coax it to do what you want (which can +change with each compiler release) is brittle. + +The second, and more dangerously, client logic becomes much more complex. +For example, consider this client code: + +```rust + ... + if self.state.get() == State::Idle { + let result = random.random(); + match result { + Ok(()) => self.state.set(State::Waiting), + Err(e) => self.state.set(State::Error), + } + } + ... + +fn random_ready(&self, bits: u32, result: Result<(), ErrorCode>) { + match result { + Ok(()) => { + // Use the random bits + self.state.set(State::Idle); + } + Err(e) => { + self.state.set(State::Error); + } + } +} +``` + +The result of starting a split-phase call indicates whether +there will be a callback: `Ok` means there will be a callback, while +`Err` means there will not. If the implementation of `Random` issues +a synchronous callback, then the `state` variable of the client will be +in an incorrect state. Before the call to `random` returns, the callback +executes and sets `state` to `State::Idle`. Then, the call to `random` +returns, and sets `state` to `State::Waiting`. If the callback checks +whether it's in the `Waiting` state (e.g., to guard against spurious/buggy +callbacks), this check will fail. The problem is that the callback occurs +*before* the caller even knows that it will occur. + +There are ways to guard against this. The caller can optimistically assume +that `random` will succeed: + +```rust + ... + if self.state.get() == State::Idle { + self.state.set(State::Waiting); + let result = random.random(); + match result { + Err(e) => self.state.set(State::Error), + Ok(()) => {} // Do nothing + } + } + ... + +fn random_ready(&self, bits: u32, result: Result<(), ErrorCode>) { + match result { + Ok(()) => { + // Use the random bits + self.state.set(State::Idle); + } + Err(e) => { + self.state.set(State::Error); + } + } +} +``` + +After the first match (where `random` is called), `self.state` can be in +3 states: + + 1. `State::Waiting`, if the call succeeded but the callback is asynchronous. + 2. `State::Error`, if the call or callback failed. + 3. `State::Idle`, if it received a synchronous callback. + +This progresses up the call stack. The client that invoked this module might +receive a callback invoked from within the `random_ready` callback. + +Expert programmers who are fully prepared for a re-entrant callback +might realize this and program accordingly, but most programmers +aren't. Some of the Tock developers who have been writing event-driven +embedded for code decades have mistakenly handled this case. Having +synchronous callbacks makes all code need to be as carefully written +as interrupt handling code, since from the caller's standpoint the +callback can preempt execution. + +Issuing an asynchronous callback requires that the module be invoked +again later: it needs to return now, and then after that call stack is +popped, invoke the callback. For callbacks that will be triggered by +interrupts, this occurs naturally. However, if, such as in the random +number generation example, the callback is purely from software, the +module needs a way to make itself be invoked later, but as quickly as +possible. The standard mechanism to achieve this in Tock is through +deferred procedure calls. This mechanism allows a module to tell the +Tock scheduler to call again later, from the main scheduling loop. For +example, a caching implementation of `Random` might look like this: + +```rust +impl Random for CachingRNG { + fn random(&self) -> Result<(), ErrorCode> { + if self.busy.get() { + return Err(ErrorCode::BUSY); + } + + self.busy.set(true); + if self.cached_words.get() > 0 { + // This tells the scheduler to issue a deferred procedure call, + self.deferred_call.set(); + } else { + self.request_more_randomness(); + } + } + ... +} + +impl<'a> DeferredCallClient for CachingRNG<'a> { + fn handle_deferred_call(&self) { + let rbits = self.pop_cached_word(); + self.client.random_ready(rbits, Ok(())); + } + + // This function must be called during board initialization. + fn register(&'static self) { + self.deferred_call.register(self); + } +} +``` + +Rule 2: Return Synchronous Errors +=============================== + +Methods that invoke hardware can fail. It could be that the hardware is not +configured as expected, it is powered down, or it has been disabled. Generally +speaking, every HIL operation should return a Rust `Result` type, whose `Err` +variant includes an error code. The Tock kernel provides a standard set of +error codes, oriented towards system calls, in the `kernel::ErrorCode` enum. + +HILs SHOULD return `ErrorCode`. Sometimes, however, these error codes don't +quite fit the use case and so in those cases a HIL may defines its own error +codes. The I2C HIL, for example, defines an `i2c::Error` enumeration for cases +such as address and data negative acknowledgments, which can occur in I2C. +In cases when a HIL returns its own error code type, this error code type +should also be able to represent all of the errors returned in a callback +(see Rule 6 below). + +If a method doesn't return a synchronous error, there is no way for a caller +to know if the operation succeeded. This is especially problematic for +split-phase calls: whether the operation succeeds indicates whether +there will be a callback. + +Rule 3: Split-phase `Result` Values Indicate Whether a Callback Will Occur +=============================== + +Suppose you have a split-phase call, such as for a SPI read/write operation: + +```rust +pub trait SpiMasterClient { + /// Called when a read/write operation finishes + fn read_write_done( + &self, + write_buffer: &'static mut [u8], + read_buffer: Option<&'static mut [u8]>, + len: usize, + ); +} +pub trait SpiMaster { + fn read_write_bytes( + &self, + write_buffer: &'static mut [u8], + read_buffer: Option<&'static mut [u8]>, + len: usize, + ) -> Result<(), ErrorCode>; +} +``` + +One issue that arises is whether a client calling +`SpiMaster::read_write_bytes` should expect a callback invocation of +`SpiMasterClient::read_write_done`. Often, when writing event-driven +code, modules are state machines. If the client is waiting for an +operation to complete, then it shouldn't call `read_write_bytes` +again. Similarly, if it calls `read_write_bytes` and the operation +doesn't start (so there won't be a callback), then it can try to call +`read_write_bytes` again later. + +It's very important to a caller to know whether a callback will be issued. +If there will be a callback, then it knows that it will be invoked again: +it can use this invocation to dequeue a request, issue its own callbacks, +or perform other operations. If there won't be a callback, then it might +never be invoked again, and can be in a stuck state. + +For this reason, the standard calling convention in Tock is that an +`Ok` result means there will be a callback in response to this call, +and an `Err` result means there will not be a callback in response to +*this* call. Note that it is possible for an `Err` result to be +returned yet there will be a callback in the future. This depends on +which `ErrorCode` is passed. A common calling pattern is for a trait +to return `ErrorCode::BUSY` if there is already an operation pending +and a callback will be issued. This error code is unique in this way: +the general rule is that `Ok` means there will be a callback in +response to this call, `Err` with `ErrorCode::BUSY` means this call +did not start a new operation but there will be a callback in response +to a prior call, and any other `Err` means there will not be a +callback. + +Cancellation calls are a slight variation on this approach. A call to +a `cancel` method (such as `uart::Transmit::transmit_abort`) also +returns a `Result` type, for which an `Ok` value means there will be a +callback in the future while an `Err` value means there will not be a +callback. In this way the return type reflects the original call. +The `Ok` value of a cancel method, however, needs to distinguish between +two cases: + 1. There was an outstanding operation, so there will be a callback, but + it was not cancelled. + 2. There was an outstanding operation, so there will be a callback, and + it was cancelled. + + +The `Result::Ok` type for cancel calls therefore often contains +information that signals whether the operation was successfully +cancelled. + +Rule 4: Return Passed Buffers in Error Results +=============================== + +Consider this method: + +```rust +// Anti-pattern: caller cannot regain buf on an error +fn send(&self, buf: &'static mut [u8]) -> Result<(), ErrorCode>; +``` + +This method is for a split-phase call: there is a corresponding +completion callback that passes the buffer back: + +```rust +fn send_done(&self, buf: &'static mut[u8]); +``` + + +The `send` method follows Rule 2: it returns a synchronous error. But +suppose that calling it returns an `Err(ErrorCode)`: what happens to +the buffer? + +Rust's ownership rules mean that the caller can't still hold the +reference: it passed the reference to the implementer of `send`. But +since the operation did not succeed, the caller does not expect a +callback. Forcing the callee to issue a callback on a failed operation +typically forces it to include an alarm or other timer. Following Rule +1 means it can't do so synchronously, so it needs an asynchronous +event to invoke the callback from. This leads to every implementer of +the HIL requiring an alarm or timer, which use RAM, has more complex +logic, and makes initialization more complex. + +As a result, in the above interface, if there is an error on `send`, +the buffer is lost. It's passed into the callee, but the callee has no +way to pass it back. + +If a split-phase operation takes a reference to a buffer as a +parameter, it should return a reference to a buffer in the `Err` case: + +```rust +fn send(&self, buf: &'static mut [u8]) -> Result<(), (ErrorCode, &'static mut [u8])>; +``` + +Before Tock transitioned to using `Result`, this calling pattern was +typically implemented with an `Option`: + + +```rust +fn send(&self, buf: &'static mut [u8]) -> (ReturnCode, Option<&'static mut [u8]>); +``` + +In this approach, when the `ReturnCode` is `SUCCESS`, the `Option` is +always supposed to be `None`; it the `ReturnCode` has an error value, +the `Option` contains the passed buffer. This invariant, however, +cannot be checked. Transitioning to using `Result` both makes Tock +more in line with standard Rust code and enforces the invariant. + + +Rule 5: Always Pass a Mutable Reference to Buffers +=============================== + +Suppose you are designing a trait to write some text to an LCD screen. The trait +takes a buffer of ASCII characters, which it puts on the LCD: + +```rust +// Anti-pattern: caller is forced to discard mutability +trait LcdTextDisplay { + // This is an anti-pattern: the `text` buffer should be `mut`, for reasons explained below + fn display_text(&self, text: &'static [u8]) -> Result<(), ErrorCode>; + fn set_client(&self, client: &'static Client); +} + +trait Client { + fn text_displayed(&self, text: &'static [u8], result: Result<(), ErrorCode>); +} +``` + +Because the text display only needs to read the provided text, the reference +to the buffer is not mutable. + +This is a mistake. + +The issue that arises is that because the caller passes the reference to the +LCD screen, it loses access to it. Suppose that the caller has a mutable +reference to a buffer, which it uses to read in data typed from a user before +displaying it on the screen. Or, more generally, it has a mutable reference +so it can create new text to display to the screen. + +```rust +enum State { + Idle, + Reading, + Writing, +} + +struct TypeToText { + buffer: Option<&'static mut [u8]>, + uart: &'static dyn uart::Receive<'static>, + lcd: &'static dyn LcdTextDisplay, + state: Cell, +} + +impl TypeToText { + fn display_more(&self) -> Result<(), ErrorCode> { + if self.state.get() != State::Idle || self.buffer.is_none() { + return Err(ErrorCode::BUSY); + } + let buffer = self.buffer.take(); + let uresult = uart.receive_buffer(buffer, buffer.len()); + match uresult { + Ok(()) => { + self.state.set(State::Reading); + return Ok(()); + } + Err(e) => return Err(e), + } + } +} + +impl uart::ReceiveClient<'static> for TypeToText { + fn received_buffer(&self, buf: &'static mut [u8]) { + self.lcd.display_text(buf); // discards mut + } +} +``` + +The problem is in this last line. `TypeToText` needs a mutable +reference so it can read into it. But once it passes the reference +to `LcdTextDisplay`, it discards mutability and cannot get it back: +`text_displayed` provides an immutable reference, which then cannot +be put back into the `buffer` field of `TypeToText`. + +For this reason, split phase operations that take references should +generally take mutable references, even if they only need read-only +access. Because the reference will not be returned back until the +callback, the caller cannot rely on the call stack and scoping to +retain mutability. + +Rule 6: Include a `Result` in Completion Callbacks That Includes an Error Code in its `Err` +=============================== + +Any error that can occur synchronously can usually occur asynchronously too. +Therefore, callbacks need to indicate that an error occurred and pass that +back to the caller. Callbacks therefore should include a `Result` type, +whose `Err` variant includes an error code. This error code type SHOULD +be the same type that is returned synchronously, to simplify error +processing and returning errors to userspace when needed. + +The common case for this is virtualization, where a capsule turns one +instance of a trait into a set of instances that can be used by many +clients, each with their own callback. A typical virtualizer queues +requests. When a request comes in, if the underlying resource is idle, +the virtualizer forwards the request and marks itself busy. If the +request on the underlying resource returns an error, the virtualizer +returns this error to the client immediately and marks itself idle +again. + +If the underlying resource is busy, then the virtualizer returns an +`Ok` to the caller and queues the request. Later, when the request is +dequeued, the virtualizer invokes the underlying resource. If this +operation returns an error, then the virtualizer issues a callback to +the client, passing the error. Because virtualizers queue and delay +operations, they also delay errors. If a HIL does not pass a `Result` +in its callback, then there is no way for the virtualizer inform the +client that the operation failed. + +Note that abstractions which can be virtualized concurrently may not +need to pass a `Result` in their callback. `Alarm`, for example, can +be virtualized into many alarms. These alarms, however, are not queued +in a way that implies future failure. A call to `Alarm::set_alarm` +cannot fail, so there is no need to return a `Result` in the callback. + +Rule 7: Always Return the Passed Buffer in a Completion Callback +=============================== + +If a client passes a buffer to a module for an operation, it needs to +be able to reclaim it when the operation completes. Rust ownership +(and the fact that passed references must be mutable, see Rule 5 +above) means that the caller must pass the reference to the HIL +implementation. The HIL needs to pass it back. + +Rule 8: Use Fine-grained Traits That Separate Different Use Cases +=============================== + +Access to a trait gives access to functionality. If several pieces of +functionality are coupled into a single trait, then a client that +needs access to only some of them gets all of them. HILs should +therefore decompose their abstractions into fine-grained traits that +separate different use cases. For clients that need multiple pieces of +functionality, the HIL can also define composite traits, such that a +single reference can provide multiple traits. + +Consider, for example, an early version of the `Alarm` trait: + +```rust +pub trait Alarm: Time { + fn now(&self) -> u32; + fn set_alarm(&self, tics: u32); + fn get_alarm(&self) -> u32; +} +``` + +This trait coupled two operations: setting an alarm for a callback and +being able to get the current time. A module that only needs to be able +to get the current time (e.g., for a timestamp) must also be able to +set an alarm, which implies RAM/state allocation somewhere. + +The modern versions of the traits look like this: + +```rust +pub trait Time { + type Frequency: Frequency; // The number of ticks per second + type Ticks: Ticks; // The width of a time value + fn now(&self) -> Self::Ticks; +} + +pub trait Alarm<'a>: Time { + fn set_alarm_client(&'a self, client: &'a dyn AlarmClient); + fn set_alarm(&self, reference: Self::Ticks, dt: Self::Ticks); + fn get_alarm(&self) -> Self::Ticks; + + fn disarm(&self) -> ReturnCode; + fn is_armed(&self) -> bool; + fn minimum_dt(&self) -> Self::Ticks; +} +``` + +They decouple getting a timestamp (the `Time` trait) from an alarm +that issues callbacks at a particular timestamp (the `Alarm` trait). + +Separating a HIL into fine-grained traits allows Tock to follow +the security principle of least privilege. In the case of GPIO, for +example, being able to read a pin does not mean a client should be +able to reconfigure or write it. Similarly, for a UART, being able +to transmit data does not mean that a client should always also be +able to read data, or reconfigure the UART parameters. + +Rule 9: Separate Control and Datapath Operations into Separate Traits +=============================== + +This rule is a direct corollary for Rule 8, but has some specific +considerations that make it a rather hard and fast rule. Rule 8 +(separate HILs into fine-grained traits) has a lot of flexibility +in design sensibility in terms of what operations *can* be coupled +together. This rule, however, is more precise and strict. + +Many abstractions combine data operations and control operations. +For example, a SPI bus has data operations for sending and receiving +data, but it also has control operations for setting its speed, +polarity, and chip select. An ADC has data operations for sampling a +pin, but also has control operations for setting the bit width of +a sample, the reference voltage, and the sampling clock used. Finally, +a radio has data operations to send and receive packets, but also +control operations for setting transmit power, frequencies, and +local addresses. + +HILs should separate these operations: control and data operations +should (almost) never be in the same trait. The major reason is +security: allowing a capsule to send packets should not also allow +it to set the local node address. The second major reason is virtualization. +For example, a UART virtualizer that allows multiple concurrent readers +cannot allow them to change the speed or UART configuration, as it is +shared among all of them. A capsule that can read a GPIO pin should +not always be able to reconfigure the pin (what if other capsules need +to be able to read it too?). + +For example, returning to the UART example, this is an early version +of the UART trait (v1.3): + +```rust +// Anti-pattern: combining data and control operations makes this +// trait unvirtualizable, as multiple clients cannot configure a +// shared UART. It also requires every client to handle both +// receive and transmit callbacks. +pub trait UART { + fn set_client(&self, client: &'static Client); + fn configure(&self, params: UARTParameters) -> ReturnCode; + fn transmit(&self, tx_data: &'static mut [u8], tx_len: usize); + fn receive(&self, rx_buffer: &'static mut [u8], rx_len: usize); + fn abort_receive(&self); +} +``` + +It breaks both Rule 8 and Rule 9. It couples reception and +transmission (Rule 8). It also couples configuration with data (Rule +9). This HIL was fine when there was only a single user of the +UART. However, once the UART was virtualized, `configure` could not +work for virtualized clients. There were two options: have `configure` +always return an error for virtual clients, or write a new trait for +virtual clients that did not have `configure`. Neither is a good +solution. The first pushes failures to runtime: a capsule that needs +to adjust the configuration of the UART can be connected to a virtual +UART and compile fine, but then fails when it tries to call +`configure`. If that occurs rarely, then it might be a long time until +the problem is discovered. The second solution (a new trait) breaks +the idea of virtualization: a client has to be bound to either a +physical UART or a virtual one, and can't be swapped between them even +if it never calls `configure`. + +The modern UART HIL looks like this: + +```rust +pub trait Configure { + fn configure(&self, params: Parameters) -> ReturnCode; +} +pub trait Transmit<'a> { + fn set_transmit_client(&self, client: &'a dyn TransmitClient); + fn transmit_buffer( + &self, + tx_buffer: &'static mut [u8], + tx_len: usize, + ) -> (ReturnCode, Option<&'static mut [u8]>); + fn transmit_word(&self, word: u32) -> ReturnCode; + fn transmit_abort(&self) -> ReturnCode; +} +pub trait Receive<'a> { + fn set_receive_client(&self, client: &'a dyn ReceiveClient); + fn receive_buffer( + &self, + rx_buffer: &'static mut [u8], + rx_len: usize, + ) -> (ReturnCode, Option<&'static mut [u8]>); + fn receive_word(&self) -> ReturnCode; + fn receive_abort(&self) -> ReturnCode; +} +pub trait Uart<'a>: Configure + Transmit<'a> + Receive<'a> {} +pub trait UartData<'a>: Transmit<'a> + Receive<'a> {} +``` + +Rule 10: Avoid Blocking APIs +=============================== + +The Tock kernel is non-blocking: I/O operations are split-phase and +have a completion callback. If an operation blocks, it blocks the +entire system. + +There are cases when operations are synchronous *sometimes*. The +random number generator in Rule 1 is an example. If random bits are +cached, then a call to request random bits can sometimes return those +bits synchronously. If the random number generator needs to engage +the underlying AES engine, then the random bits have to be +asynchronous. As Rule 1 goes into, even operations that *could* be +synchronous should have a callback that executes asynchronously. + +Having a conditional synchronous operation and an asynchronous backup +is a poor solution. While it might seem to make the synchronous cases +simpler, a caller still needs to handle the asynchronous ones. The +code ends up being more complex and larger/longer, as it is now +conditional: a caller has to handle both cases. + +The more attractive case is when a particular implementation of a HIL +seems like it can always be synchronous, therefore its HIL is +synchronous. For example, writes to flash are typically asynchronous: +the chip issues an interrupt once the bits are written. However, if +the flash chip being written is the same as the one code is fetched +from, then the chip may block reads while the write completes. From +the perspective of the caller, writing to flash is blocking, as the +core stops fetching instructions. A synchronous flash HIL allows +implementations to be simpler, straight-line code. + +Capsules implemented on a synchronous HIL only work for +implementations with synchronous behavior. Such a HIL limits +reuse. For example, a storage system built on top of this synchronous +API can only work on the same flash bank instructions are stored on: +otherwise, the operations will be split-phase. + +There are use cases when splitting HILs in this way is worth it. For +example, straight-line code can often be shorter and simpler than +event-driven systems. By providing a synchronous API for the subset of +devices that can support it, one can reduce code size and produce more +light-weight implementations. For this reason, the rule is to *avoid* +blocking APIs, not to never implement them. They can and should at +times exist, but their uses cases should be narrow and constrained as +they are fundamentally not as reusable. + +Rule 11: `initialize()` methods, when needed, should be in a separate trait and invoked in an instantiating Component +=============================== + +Occasionally, HIL implementations need an `initialize` method to set up +state or configure hardware before their first use. When one-time +initialization is needed, doing it deterministically at boot is preferable +than doing it dynamically on the first operation (e.g., by having a +`is_initialized` field and calling `initialize` if it is false, then setting +it true). Doing at boot has two advantages. First, it is fail-fast: +if the HIL cannot initialize, this will be detected immediately at boot +instead of potentially non-deterministically on the first operation. Second, +it makes operations more deterministic in their execution time, which is +useful for precise applications. + +Because one-time initializations should only be invoked at boot, they +should not be part of standard HIL traits, as those traits are used by +clients and services. Instead, they should either be in a separate +trait or part of a structure's implementation. + +Because forgetting to initialize a module is a common source of errors, +modules that require one-time initialization should, if at all possible, +put this in an instantiable `Component` for that module. The `Component` +can handle all of the setup needed for the module, including invoking +the call to initialize. + +Rule 12: Traits that can trigger callbacks should have a `set_client` method +=============================== + +If a HIL trait can trigger callbacks it should include a method for +setting the client that handles the callbacks. There are two +reasons. First, it is generally important to be able to change +callbacks at runtime, e.g., in response to requests, virtualization, +or other dynamic runtime behavior. Second, a client that can trigger a +callback should also be able to control what method the callback +invokes. This gives the client flexibility if it needs to change +dispatch based on internal state, or perform some form of proxying. It +also allows the client to disable callbacks (by passing an +implementation of the trait that does nothing). + +Rule 13: Use generic lifetimes, except for buffers in split-phase operations, which should be `'static` +=============================== + +HIL implementations should use generic lifetimes whenever possible. This has +two major advantages. First, it leaves open the possibilty that the kernel +might, in the future, support loadable modules which have a finite lifetime. +Second, an explicit `` `static`` lifetime brings safety and borrow-checker +limitations, because mutably accessing `` `static `` variables is generally +considered unsafe. + +If possible, use a single lifetime unless there are compelling reasons or +requirements otherwise. The standard lifetime name in Tock code is `` `a``, +although code can use other ones if they make sense. +In practice today, these `` `a`` lifetimes are all bound to `` `static``. +However, by not using `` `static`` explicitly these HILs can be used with +arbitrary lifetimes. + +Buffers used in split-phase operations are the one exception to generic +lifetimes. In most cases, these buffers will be used by hardware in DMA +or other operations. In that way, their lifetime is not bound to program +execution (i.e., lifetimes or stack frames) in a simple way. For example, +a buffer passed to a DMA engine may be held by the engine indefinitely if +it is never started. For this reason, buffers that touch hardware usually +must be `` `static``. If their lifetime is not `` `static``, then they +must be copied into a static buffer to be used (this is usually what happens +when application `AppSlice` buffers are passed to hardware). To avoid +unnecessary copies, HILs should use `` `static`` lifetimes for buffers +used in split-phase operations. + +Author Addresses +================================= +``` +Philip Levis +414 Gates Hall +Stanford University +Stanford, CA 94305 + +email: Philip Levis +phone: +1 650 725 9046 + +Brad Campbell +Computer Science +241 Olsson +P.O. Box 400336 +Charlottesville, Virginia 22904 + +email: Brad Campbell +``` diff --git a/doc/reference/trd4-legal.md b/doc/reference/trd4-legal.md new file mode 100644 index 0000000000..f14349db9b --- /dev/null +++ b/doc/reference/trd4-legal.md @@ -0,0 +1,188 @@ +Licensing and Copyrights +======================================== + +**TRD: 4**
+**Working Group:** Core
+**Type:** Best Current Practice
+**Status:** Final
+**Author:** Pat Pannuto
+ +Abstract +-------- + +This document describes Tock’s policy on licensing and copyright. It explains +the rationale behind the license selection (dual-license, MIT or Apache2) and +copyright policy (optional, and authors may retain copyright if desired). It +further outlines how licensing and copyright shall be handled throughout +projects under the Tock umbrella. + +Explicitly not discussed in this TRD are issues of trademark, the Tock brand, +logos, or other non-code assets and artifacts. + + +1 Introduction +============== + +Tock’s goal is to provide a safe, secure, and efficient operating system for +microcontroller-class devices. The Tock project further believes it is +important to enable and support widespread adoption of safe, secure, and +efficient software. Tock also seeks to be an open and inclusive project, and +Tock welcomes contributions from any individuals or entities wishing to +improve the safety, security, reliability, efficiency, or usability of Tock +and the Tock ecosystem. + +The intent of these policies is to best satisfy the needs of all stakeholders +in the Tock ecosystem. + + +2 Licensing +=========== + +All software artifacts under the umbrella of the Tock project are +dual-licensed as Apache License, Version 2.0 (LICENSE-APACHE or +http://www.apache.org/licenses/LICENSE-2.0) or MIT license (LICENSE-MIT or +http://opensource.org/licenses/MIT). + +All contributions to the Tock kernel (the code hosted at +https://github.com/tock/tock) MUST be licensed under these terms. + + +3 Copyright +=========== + +Entities contributing resources to open-source projects often require +attribution to recognize their efforts. Copyright notices are a common means to +provide this. For downstream users, the license terms of Tock ensures +unencumbered use. + +Copyrights in Tock projects are retained by their contributors. No +copyright assignment is required to contribute to Tock projects. + +Artifacts in the Tock project MAY include explicit copyright notices. +Substantial updates to an artifact MAY add additional copyright notices to an +artifact. In general, modifications to a file are expected to retain existing +copyright notices. + +For full authorship information, see the version control history. + + +4 Implementation +================ + +Where possible, all textual files that allow comments MUST include a license +notice and copyright notice(s). Files that are not authored by Tock contributors +(such as files copied from other projects) are exempt from this policy. + +Copyright notices SHOULD include a year. Newer copyright notices SHOULD be +placed after existing copyright notices. If non-trivial updates are performed by +an original copyright author, they MAY amend the year(s) indicated on their +existing copyright statement or MAY add an additional copyright line, at their +discretion. + + +4.1 Format +---------- + +License and copyright information SHOULD have at least one (1) blank line +separating it from any other content in the file. + +Text described in this section SHOULD be pre-fixed or post-fixed with +technically necessary characters (i.e. to mark as a comment in source +code) as appropriate. + +The first line of license text SHOULD appear as-follows: + +> Licensed under the Apache License, Version 2.0 or the MIT License. + +The second line of license SHOULD adhere to the [SPDX](https://spdx.dev) +specification for license description. As of this writing, it SHOULD +appear as-follows: + +> SPDX-License-Identifier: Apache-2.0 OR MIT + +The [current (v2.3) normative +rules](https://spdx.github.io/spdx-spec/v2.3/SPDX-license-expressions/) permit +case-insensitive matches of the license identifier but require case-sensitive +matching of the disjunction operator. To simplify enforcement of licensing and +documentation rules, license information SHOULD preserve case as-shown in the +SPDX license list (i.e. as-presented above). + +Copyright lines SHOULD follow this pattern: + +> Copyright {entity} {year}([,year],[-year]). + +The `{entity}` field should reflect the entity wishing to claim copyright. The +`{year}` field SHOULD reflect when the copyright is first established. +Substantial updates in the future MAY indicate renewed copyright, via additional +comma-separated years or via range syntax, at the copyright holder’s discretion. +The initial year SHALL NOT be removed unless it is the express intent of the +copyright holder to relinquish the initial copyright. + + +### 4.1.1 Examples + +The common-case format is: + +```rust +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors . + +//! Module-level documentation... +``` + +placed at the top of the file. + +If you wish to specifically call out the contribution by you or your company, +you may do so by adding another copyright line: + +```rust +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors . +// Copyright . + +//! Module-level documentation... +``` + +A file with a long history and multiple copyrights may look as follows: + +```bash +#!/usr/bin/env bash + +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2014. +# Copyright Pat Pannuto 2014,2016-2018,2021. +# Copyright Amit Levy 2016-2019. +# Copyright Bradford James Campbell 2022. + +set -e +... +``` + +Many additional examples are available throughout the Tock repositories. + + +4.2 Enforcement +--------------- + +To ensure coverage and compliance with these policies, the Core Team +SHALL author and maintain tooling which checks the presence and expected +format of license and copyright information. This SHOULD be automated and +integrated with continuous integration systems. Contributions which do +not satisfy these license and copyright rules MUST NOT be accepted. + +In exceptional situations, consensus from the Core Team MAY circumvent +this policy. Such situations MUST include public explanation and public +record of non-anonymized vote results. This is not expected to ever occur. + + +5 Author’s Address +================== + + Pat Pannuto + 3202 EBU3, Mail Code #0404 + 9500 Gilman Dr + La Jolla, CA 92093, USA + ppannuto@ucsd.edu diff --git a/doc/rfcs/2023-08-18--CommandZeroSemantics.md b/doc/rfcs/2023-08-18--CommandZeroSemantics.md new file mode 100644 index 0000000000..09b67c279e --- /dev/null +++ b/doc/rfcs/2023-08-18--CommandZeroSemantics.md @@ -0,0 +1,239 @@ +Syscalls: Command 0 Semantics +============================== + +- Initial Proposal: 2023-08-18 +- Disposition: Under Review +- RFC PR: https://github.com/tock/tock/pull/3626 + +Summary +------- + +This document captures the proposal(s) brought up by @alevy / @bradjc / others +in https://github.com/tock/tock/issues/3375#issuecomment-1681535604 and +on the August 18, 2023 core team call. + + +Motivation +---------- + +The utility of an "exists" syscall is laid out well in the current TRD104 text: + +> If userspace tries to invoke a system call that the kernel does not +> support, the system call will return a Failure result with an error +> code of `NODEVICE` or `NOSUPPORT` (Section 4). As the Allow and +> Subscribe system call classes have defined Failure types, the kernel can +> produce the expected type with known failure variants. Command, however, +> can return any variant. This means that Commands can appear to have +> two failure variants: the one expected (e.g., Failure with u32) as well +> as Failure. To avoid this ambiguity for `NODEVICE`, userspace can use the +> reserved "exists" command (Command Identifier 0), described in Section +> 4.3.1. If this command returns Success, the driver is installed and will +> not return a Failure with `NODEVICE` for Commands. The driver may still +> return `NOSUPPORT`, however. Because this implies a misunderstanding of +> the system call API by userspace (it is invoking system calls that do not +> exist), userspace is responsible for handling this case. + +However, there is nothing in this rationale that would require _only_ allowing +`Success` as-opposed to any of the success variants. + + +Initial Discussion +------------------ + +During the call, a few design constraints were discussed: + + - **Existence checks should be idempotent and side-effect free.** + - This is not actually enforced by the current TRD text. However, the + motivation is somewhat clear — a generic component polling for + hardware existence / platform capabilities should not trigger + hardware events. + - The secondary implication here is that this implicitly forbids Command + 0 from triggering asynchronous events. Currently, a driver _could_ + generate an Upcall in response to a Command 0 event, which, given + that 'anything' might check existence, probably isn't desirable. + - We could codify these limitations without the more permissive change + here though. + + - **Why not pass this information in a second Command 1 or equivalent for + drivers?** + - Something that cares about hardware count could just call Command 1 + and skip the existence check as [the kernel will automatically return + `NODEVICE` if it's missing + already](https://github.com/tock/tock/blob/aa33bf1bad61ff8f3ba99a36a0760368bc4e6c3f/kernel/src/kernel.rs#L1038). + - However, now drivers have to implement two syscalls (0: exists, 1: + info-about-existence) where one could easily be collapsed into the + other, reducing code overhead. + + - **Having every driver implement an empty Command 0 is silly, we might as + well let them do something with it.** + - Yes.., but really that's an artifact of the legacy drivers that do + something with command 0. Once all of those are gone, the kernel can + take over management of Command 0. + + +Proposals and Prototypes +------------------------ + +The two main approaches are shown here: + - Proposal A: Modify Command 0 to be more permissive. + - Proposal B: Lock down Command 0 and give the kernel clear ownership. + +I included some rough-sketch code of what the two variations might look like +to help discussion. It obviously will not compile as the surface area for +the complete change is huge. + +```diff +diff --git a/doc/reference/trd104-syscalls.md b/doc/reference/trd104-syscalls.md +index b1b4de38e..8f78712f2 100644 +--- a/doc/reference/trd104-syscalls.md ++++ b/doc/reference/trd104-syscalls.md +@@ -496,14 +496,32 @@ failure variant of `Failure`, with an associated error code of + handle userspace/kernel mismatches should be able to handle `Failure` in + addition to the expected failure variant (if different than `Failure`). + +-4.3.1 Command Identifier 0 ++4.3.1 Command Identifier 0 [PROPOSAL A] + -------------------------------- + +-Every device driver MUST implement command number 0 as the +-"exists" command. This command always returns `Success`. This command +-allows userspace to determine if a particular system call driver is +-installed; if it is, the command returns `Success`. If it is not, the +-kernel returns `Failure` with an error code of `NODEVICE`. ++Every device driver MUST implement command number 0 as the "exists" ++command. If the driver is not installed, the kernel will return ++`Failure` with an error code of `NODEVICE`. ++ ++Device drivers MUST return `Success` OR a success variant for command ++number 0. Drivers MAY use success variants to convey additional ++information, e.g. an LED driver might return the number of LEDs ++physically present on the board via `Success with u32`. Command 0 ++implementation MUST NOT have ANY runtime effects. Any code present in ++the `0` match arm MUST be suitable for [constant evaluation by the ++Rust compiler](https://doc.rust-lang.org/reference/const_eval.html). ++ ++ ++4.3.1 Command Identifier 0 [PROPOSAL B] ++------------------------------- ++ ++Command Identifier 0 is implemented by the core kernel and provides an ++existence check for drivers. If a driver is installed, the kernel will ++return `Success` for Command 0. If a driver is not installed, the kernel ++will return `Failure` with and error code of `NODEVICE`. ++ ++Device drivers CANNOT modify the behavior of Command Identifier 0. ++ + + 4.4 Read-Write Allow (Class ID: 3) + --------------------------------- +diff --git a/capsules/core/src/led.rs b/capsules/core/src/led.rs +index c87cd5586..db70042f1 100644 +--- a/capsules/core/src/led.rs ++++ b/capsules/core/src/led.rs +@@ -81,6 +81,11 @@ impl<'a, L: led::Led, const NUM_LEDS: usize> LedDriver<'a, L, NUM_LEDS> { + } + + impl SyscallDriver for LedDriver<'_, L, NUM_LEDS> { ++ #[cfg(AAAAAAAAAAAAAAAA)] ++ const fn commandZero(&self) -> CommandReturn { ++ 0 => CommandReturn::success_u32(NUM_LEDS as u32), ++ } ++ + /// Control the LEDs. + /// + /// ### `command_num` +@@ -93,14 +98,8 @@ impl SyscallDriver for LedDriver<'_, L, NUM_ + /// if the LED index is not valid. + /// - `3`: Toggle the LED at index specified by `data` on or off. Returns + /// `INVAL` if the LED index is not valid. +- fn command(&self, command_num: usize, data: usize, _: usize, _: ProcessId) -> CommandReturn { ++ fn command(&self, command_num: core::num::NonZeroUsize, data: usize, _: usize, _: ProcessId) -> CommandReturn { + match command_num { +- // get number of LEDs +- // TODO(Tock 3.0): TRD104 specifies that Command 0 should return Success, not SuccessU32, +- // but this driver is unchanged since it has been stabilized. It will be brought into +- // compliance as part of the next major release of Tock. +- 0 => CommandReturn::success_u32(NUM_LEDS as u32), +- + // on + 1 => { + if data >= NUM_LEDS { +@@ -131,6 +130,12 @@ impl SyscallDriver for LedDriver<'_, L, NUM_ + } + } + ++ #[cfg(BBBBBBBBBBBBBBBBBBBBBBBBBBB)] ++ // LED count ++ 4 => { ++ CommandReturn::success_u32(NUM_LEDS as u32) ++ } ++ + // default + _ => CommandReturn::failure(ErrorCode::NOSUPPORT), + } +diff --git a/kernel/src/kernel.rs b/kernel/src/kernel.rs +index d5121144b..811f8db36 100644 +--- a/kernel/src/kernel.rs ++++ b/kernel/src/kernel.rs +@@ -1034,7 +1034,19 @@ impl Kernel { + arg1, + } => { + let cres = match driver { +- Some(d) => d.command(subdriver_number, arg0, arg1, process.processid()), ++ Some(d) => { ++ if subdriver_number == 0 { ++ #[cfg(AAAAAAAAAAAAAAAAAAAA)] ++ d.commandZero() ++ #[cfg(BBBBBBBBBBBBBBBBBBBB)] ++ CommandReturn::success() ++ } else { ++ let sd_arg = unsafe { ++ core::num::NonZeroUsize::new_unchecked(subdriver_number) ++ }; ++ d.command(sd_arg, arg0, arg1, process.processid()) ++ } ++ }, + None => CommandReturn::failure(ErrorCode::NODEVICE), + }; + +diff --git a/kernel/src/syscall_driver.rs b/kernel/src/syscall_driver.rs +index 562e48dc3..dbf5a8a9b 100644 +--- a/kernel/src/syscall_driver.rs ++++ b/kernel/src/syscall_driver.rs +@@ -189,6 +189,13 @@ impl From for CommandReturn { + /// corresponding function for capsules to implement. + #[allow(unused_variables)] + pub trait SyscallDriver { ++ // n.b. This requires new nightly features that are still a bit in flux: ++ // https://github.com/rust-lang/rust/issues/67792 ++ #[cfg(AAAAAAAAAAAAAAAAA)] ++ const fn commandZero(&self) -> CommandReturn { ++ CommandReturn::failure(ErrorCode::NODEVICE) ++ } ++ + /// System call for a process to perform a short synchronous operation + /// or start a long-running split-phase operation (whose completion + /// is signaled with an upcall). Command 0 is a reserved command to +@@ -196,7 +203,7 @@ pub trait SyscallDriver { + /// always return a CommandReturn::Success. + fn command( + &self, +- command_num: usize, ++ command_num: core::num::NonZeroUsize, + r2: usize, + r3: usize, + process_id: ProcessId, +``` + + +Consensus +--------- + +Proposal B. It is easier to teach, can be centralized, is easier to implement +in userspace, and promotes better design patterns for drivers. + +There _may_ be a rationale in the future to support a more flexible Command 0. +If we run into compelling use cases where changing command 0 provides +non-trivial code size or application complexity benefits, we can revisit +TRD 104, most likely in a highly backwards compatible way. + diff --git a/doc/syscalls/00000_alarm.md b/doc/syscalls/00000_alarm.md index 1f8d826ce0..32fc76487a 100644 --- a/doc/syscalls/00000_alarm.md +++ b/doc/syscalls/00000_alarm.md @@ -7,7 +7,7 @@ driver number: 0x00000 ## Overview The alarm driver exposes a wrapping hardware counter to processes. An alarm can -report the current tic value and notify via a callback when the counter reaches +report the current tick value and notify via a callback when the counter reaches a certain value. The alarm's frequency is platform-specific, but must be _at least_ 1kHz. @@ -22,8 +22,7 @@ The alarm's frequency is platform-specific, but must be _at least_ 1kHz. **Argument 2**: unused - **Returns**: The number of concurrent notifications supported per process, - 0 if unbounded, otherwise NODEVICE + **Returns**: Success if the driver exists, otherwise NODEVICE. * ### Command number: `1` @@ -37,13 +36,13 @@ The alarm's frequency is platform-specific, but must be _at least_ 1kHz. * ### Command number: `2` - **Description**: Read the current counter tic value. + **Description**: Read the current counter tick value. **Argument 1**: unused **Argument 2**: unused - **Returns**: The counter value in tics. + **Returns**: The counter value in ticks. * ### Command number: `3` @@ -54,31 +53,30 @@ The alarm's frequency is platform-specific, but must be _at least_ 1kHz. **Argument 2**: unused **Returns**: INVAL if the notification identifier is invalid, ALREADY if - the notification is already disabled, or Ok(()). + the notification is already disabled, or success. - * ### Command number: `4` + * ### Command number: `5` - **Description**: Set an alarm notification for a counter value. + **Description**: Set an alarm notification for a counter value relative to the current value. Notification invokes the callback set with subscribe. - **Argument 1**: The counter tic value to notify. + **Argument 1**: The relative counter tick value to notify. **Argument 2**: unused - **Returns**: INVAL if the notification identifier is invalid, ALREADY if - the notification is already disabled, or Ok(()). + **Returns**: Tick value when the callback will be called. - * ### Command number: `5` (experimental) + * ### Command number: `6` - **Description**: Set an alarm notification for a counter value relative to the current value. + **Description**: Set an alarm notification for an absolute counter value. Notification invokes the callback set with subscribe. - **Argument 1**: The relative counter tic value to notify. + **Argument 1**: The reference point tick value. - **Argument 2**: unused + **Argument 2**: Absolute tick value. Callback will be issued + when it matches current tick value. - **Returns**: INVAL if the notification identifier is invalid, ALREADY if - the notification is already disabled, or Ok(()). + **Returns**: Tick value when the callback will be called. ## Subscribe @@ -87,8 +85,8 @@ The alarm's frequency is platform-specific, but must be _at least_ 1kHz. **Description**: Subscribe to alarm notifications. **Callback signature**: The callback recieves two arguments: the counter - tic value when the alarm notifiation expired and the notification - identifier returned from command 4. The value of the remaining argument is + tick value when the alarm notification expired and the reference + tick with which it was registered. The value of the remaining argument is undefined. **Returns**: Ok(()) if the subscribe was successful or NOMEM if the diff --git a/doc/syscalls/00001_console.md b/doc/syscalls/00001_console.md index b44c0665bb..b91a0d44c7 100644 --- a/doc/syscalls/00001_console.md +++ b/doc/syscalls/00001_console.md @@ -11,10 +11,6 @@ write a buffer, a process must share the buffer using `allow` then initiate the write using a `command` call. It may also using `subscribe` to receive a callback when the write has completed. -Once the write has completed, the buffer shared with the driver is released, so -can be deallocated by the process. This also means that it is necessary to -share a buffer for every write transaction, even if it's the same buffer. - ## Command * ### Command number: `0` @@ -33,7 +29,10 @@ share a buffer for every write transaction, even if it's the same buffer. At the end of the transaction, a callback will be delivered if the process has `subscribed`. - **Argument 1**: The maximum number of bytes to write. + **Argument 1**: The maximum number of bytes to write. If this argument is + greater than or equal to the buffer's size, the entire buffer will be + written. Otherwise, the first N bytes of the buffer will be written, where N + is the value of this argument. **Argument 2**: unused @@ -41,13 +40,18 @@ share a buffer for every write transaction, even if it's the same buffer. shared, or NOMEM if the driver failed to allocate memory for the transaction. + **Additional notes:** A process may call this command with a write size of + `0` to cancel a write transaction, if one is ongoing. Unless an error + occurs, this will generate a write transaction completed event, regardless + of whether or not a write transaction was already in progress. + * ### Command number: `2` **Description**: Initiate a read transaction into a buffer shared using `allow`. At the end of the transaction, a callback will be delivered if the process has `subscribed` to read events using `subscribe number` 2. - **Argument 1**: The maximum number of bytes to write. + **Argument 1**: The maximum number of bytes to read. **Argument 2**: unused @@ -96,7 +100,7 @@ share a buffer for every write transaction, even if it's the same buffer. **Returns**: Ok(()) if the subscribe was successful or NOMEM if the driver failed to allocate memory for the transaction. -## Allow +## Read-Only Allow * ### Allow number: `1` @@ -104,20 +108,19 @@ share a buffer for every write transaction, even if it's the same buffer. the next write transaction. A shared buffer is released if it is replaced by a subsequent call and after a write transaction is completed. Replacing the buffer after beginning a write transaction but before receiving a - completion callback is undefined (most likely either the original buffer or - new buffer will be written in its entirety but not both). + completion callback is undefined. **Returns**: Ok(()) if the subscribe was successful or NOMEM if the driver failed to allocate memory for the transaction. - * ### Allow number: `2` +## Read-Write Allow + * ### Allow number: `1` **Description**: Sets a shared buffer to be read into by the next read transaction. A shared buffer is released in two cases: if it is replaced by a subsequent call or after a read transaction is completed. Replacing the buffer after beginning a read transaction but before receiving a completion - callback is undefined (most likely either the original buffer or new buffer - will be sent in its entirety but not both). + callback is undefined. **Returns**: Ok(()) if the subscribe was successful or NOMEM if the driver failed to allocate memory for the transaction. diff --git a/doc/syscalls/00004_gpio.md b/doc/syscalls/00004_gpio.md index 413fbbe94e..4b08782408 100644 --- a/doc/syscalls/00004_gpio.md +++ b/doc/syscalls/00004_gpio.md @@ -22,17 +22,13 @@ actual hardware pins. This mapping is currently subject to change. * ### Command number: `0` - **Description**: Whether GPIO pins are exported by this board. + **Description**: Does the driver exist? **Argument 1**: unused **Argument 2**: unused - **Returns**: _Unstable:_ Most boards return the number of GPIO pins - available, however users should consult their board for details of - this return value. - `NODEVICE` if this driver is not present on the board. - + **Returns**: Success if it exists, otherwise `NODEVICE` * ### Command number: `1` @@ -44,7 +40,7 @@ actual hardware pins. This mapping is currently subject to change. **Argument 2**: unused - **Returns**: Ok(()) if the command was successful, and `INVAL` if the + **Returns**: `Ok(())` if the command was successful, and `INVAL` if the argument refers to a non-existent pin. * ### Command number: `2` @@ -125,6 +121,18 @@ actual hardware pins. This mapping is currently subject to change. configuration field of the argument. If any error is returned, no state will be changed. + * ### Command number: `10` + + **Description**: Whether GPIO pins are exported by this board. + + **Argument 1**: unused + + **Argument 2**: unused + + **Returns**: _Unstable:_ Most boards return the number of GPIO pins + available, however users should consult their board for details of + this return value. + ## Subscribe * ### Subscribe number: `0` diff --git a/doc/syscalls/00007_analog_comparator.md b/doc/syscalls/00007_analog_comparator.md index 9cad310b58..8900583551 100644 --- a/doc/syscalls/00007_analog_comparator.md +++ b/doc/syscalls/00007_analog_comparator.md @@ -55,7 +55,7 @@ the index of the last AC module. **Returns**: `Ok(())` if starting interrupts was succesful. -* ### Command number: `4` +* ### Command number: `3` **Description**: Stop interrupts on an analog comparator. @@ -65,3 +65,13 @@ the index of the last AC module. **Argument 2**: unused **Returns**: `Ok(())` if stopping interrupts was succesful. + + * ### Command number: `4` + + **Description**: Get number of channels + + **Argument 1**: unused + + **Argument 2**: unused + + **Returns**: Number of channels \ No newline at end of file diff --git a/doc/syscalls/00009_ros.md b/doc/syscalls/00009_ros.md index 53bf48b9ae..823b7f974b 100644 --- a/doc/syscalls/00009_ros.md +++ b/doc/syscalls/00009_ros.md @@ -83,9 +83,17 @@ valid userspace readable buffer. ## Command - * Description: command() is used to query the version: + * ### Command number: `0` - * ### Command Number: 0 + **Description**: Existence check. + + **Argument 1**: Unused + + **Argument 2**: Unused + + **Returns**: Success, or `NODEVICE` if this driver is not present on the board. + + * ### Command Number: 1 **Description**: Get Version. diff --git a/doc/syscalls/00010_pwm.md b/doc/syscalls/00010_pwm.md new file mode 100644 index 0000000000..f6f6ea3971 --- /dev/null +++ b/doc/syscalls/00010_pwm.md @@ -0,0 +1,71 @@ +--- +driver number: 0x00010 +--- + +# PWM + +## Overview + +The PWM driver provides userspace to control a specific PWM pin. The pin's frequency and duty cycle can be changed. + +The PWM pins are indexed in the array starting at 0. The order of the pins and the mapping between indexes and the actual pins is set by the kernel in the board's main file. + +## Command + + * ### Command number: `0` + + **Description**: Existence check. + + **Argument 1**: Unused + + **Argument 2**: Unused + + **Returns**: Success, or `NODEVICE` if this driver is not present on the board. + + * ### Command number: `1` + + **Description**: Start the PWM output. + + **Argument 1**: First 16 bits represent the duty cycle, as a percentage with 2 decimals (100% being the maximum possible duty cycle), and the last 16 bits represent the PWM pin to be controlled. This format was chosen because there are only 2 parameters in the command function that can be used for storing values, but in this case, 3 values are needed (pin, frequency, duty cycle), so data1 stores two of these values, which can be represented using only 16 bits. + + **Argument 2**: The frequency in hertz. + + **Returns**: `Ok(())` if the start attempt was successful, `INVAL` if the pin is invalid, `RESERVE` if the app doesn't have permission to use this pin at this time. + + * ### Command number: `2` + + **Description**: Stop the PWM output. + + **Argument 1**: The PWM pin to be stopped. + + **Argument 2**: unused + + **Returns**: `Ok(())` if the stop attempt was successful, `INVAL` if the pin is invalid, `RESERVE` if the app doesn't have permission to use this pin at this time, `OFF` if the requested pin is already off. + + * ### Command number: `3` + + **Description**: Get the maximum frequency of the pin. + + **Argument 1**: The PWM pin to get the maximum frequency from. + + **Argument 2**: unused + + **Returns**: The maximum frequency of the pin, `INVAL` if the pin is invalid. + + * ### Command number: `4` + + **Description**: How many PWM pins are supported on this board. + + **Argument 1**: unused + + **Argument 2**: unused + + **Returns**: The number of PWM pins on this board. + +## Subscribe + +Unused for the PWM driver. Will always return `ENOSUPPORT`. + +## Allow + +Unused for the PWM driver. Will always return `ENOSUPPORT`. \ No newline at end of file diff --git a/doc/syscalls/20007_can.md b/doc/syscalls/20007_can.md new file mode 100644 index 0000000000..f6c085deeb --- /dev/null +++ b/doc/syscalls/20007_can.md @@ -0,0 +1,279 @@ +--- +driver number: 0x20007 +--- + +# CAN + +## Overview +The CAN capsule allows the user to send and receive asynchronous messages on the CAN bus. +The user must set the bitrate and operation mode of the peripheral before turning it on. +After the device was enabled, the communication parameters cannot be modified without +turning it off beforehand. The capsule can be controlled by the userspace using 10 +different commands. + +The userspace will be notified by the capsule when a message is sent and received and +when the device was enabled and disabled. For the send command, there is a read-only +shared buffer, and for the receive command, the kernel communicates with the userspace +using a read-write buffer. + +## Command + + * ### Command number: `0` + + **Description**: Does the driver exist? + + **Argument 1**: unused + + **Argument 2**: unused + + **Returns**: Success if it exists, otherwise NODEVICE + + * ### Command number: `1` + + **Description**: Set the bitrate for the CAN peripheral. This will calculate all the + timing parameters for the device. This command must be sent before enabling the device. + + **Argument 1**: The bitrate value for the CAN communication. + + **Argument 2**: unused + + **Returns**: Ok(()) if the bitrate value is correct, otherwise INVAL if the value is_enabled + incorrect or if the timing parameters could not be correctly calculated or BUSY if the device + was previously enabled and is running. + + * ### Command number: `2` + + **Description**: Set the operation mode of the CAN peripheral. This command must be + sent before enabling the device. + + **Argument 1**: The operation mode that can be Loopback, Monitoring, Freeze or Normal. + + **Argument 2**: unused + + **Returns**: Ok(()) if the operaton value is correct, otherwise BUSY if the device + was previously enabled and is running. + + * ### Command number: `3` + + **Description**: Enable the device. Previously, the bitrate and the operation mode + must pe set. + + **Argument 1**: unused + + **Argument 2**: unused + + **Returns**: Ok(()) if the enable process of the device worked, otherwise ALREADY if the device + was previously enabled and is running, RESERVE if there is another application that is + using the capsule or FAIL if the peripheral is in an error state/ + + **Additional notes:** After this command, the userspace must wait after 2 callbacks: the `state_changed` that sends + to the capsule the state of the device, and the `enabled` callback that confirms that the enable + process was successful. + + * ### Command number: `4` + + **Description**: Disable the device. Previously, the device must be enabled. + + **Argument 1**: unused + + **Argument 2**: unused + + **Returns**: Ok(()) if the disable process of the device worked, otherwise BUSY if the device + was already disabled or RESERVE if there is another application that is using the capsule. + + **Additional notes:** After this command, the userspace must wait after 2 callbacks: the `state_changed` that sends + to the capsule the state of the device, and the `disable` callback that confirms that the disable + process was successful. + + * ### Command number: `5` + + **Description**: Send a message with a standard identifier. Previously, the device must be enabled. + + **Argument 1**: the 16-bit identifier for the transmission. + + **Argument 2**: the length of the message. + + **Returns**: Ok(()) if the message could be sent, otherwise NOMEM if the message could not be + accessed, RESERVE if there is another application that is using the capsule or OFF is the device + is not enabled. + + **Additional notes:** After this command, the userspace must wait after the `transmit_complete` callback that returns + to the capsule the buffer used for the data transfer between the driver and the capsule. + + * ### Command number: `6` + + **Description**: Send a message with an extended identifier. Previously, the device must be enabled. + + **Argument 1**: the 32-bit identifier for the transmission. + + **Argument 2**: the length of the message. + + **Returns**: Ok(()) if the message could be sent, otherwise NOMEM if the message could not be + accessed, RESERVE if there is another application that is using the capsule or OFF is the device + is not enabled. + + **Additional notes:** After this command, the userspace must wait after the `transmit_complete` callback that returns + to the capsule the buffer used for the data transfer between the driver and the capsule. + + * ### Command number: `7` + + **Description**: Sends to the driver the command to start listening for messages on the CAN + bus. This will also configure filters so that any message can be received. Previously, the + device must be enabled. + + **Argument 1**: unused + + **Argument 2**: unused + + **Returns**: Ok(()) if the device is ready to receive messages, otherwise OFF is the device + is not enabled, NOMEM if the buffer in which data should be saved cannot be accessed, SIZE + if the buffer in which data should be saved cannot store more than 2 messages. + + **Additional notes:** After this command, the userspace must wait after the `message_received` callback that returns + to the capsule a reference of the buffer used for the data transfer between the driver and the capsule. + The capsule must make a copy of the data because is does not own the buffer. + + * ### Command number: `8` + + **Description**: Sends to the driver the command to stop listening for messages on the CAN + bus. This will also disable the filters that were previously enabled. Previously, the + device must be enabled. + + **Argument 1**: unused + + **Argument 2**: unused + + **Returns**: Ok(()) if the device was stopped from receiving messages, otherwise OFF is the device + is not enabled, and FAIL if the buffer that was used to store messages cannot be owned by the + capsule after begin owned by the driver. + + **Additional notes:** After this command, the userspace must wait after the `stopped` callback that returns + to the capsule the buffer used for the data transfer between the driver and the capsule. + The capsule owns now the buffer. + + * ### Command number: `9` + + **Description**: Set the timing parameters for the CAN peripheral. This command must be + sent before enabling the device. + + **Argument 1**: An integer that has on each byte one timing parameter (as the parameters + can be represented using 8 bits): time_segment1, time_segment2, synchronization_jump_width + and the baud_rate_prescaler. + + **Argument 1 format**: + + ``` + 0 8 16 24 32 + +--------------+---------------+-----------------+---------------------+ + |time_segment1 | time_segment2 | sync_jump_width | baud_rate_prescaler | + +--------------+---------------+-----------------+---------------------+ + ``` + + **Argument 2**: An integer that represents the propagation value for the communication. + + **Returns**: Ok(()) if the parameters are correct, otherwise BUSY if the device + was previously enabled and is running. + + +## Allow ReadWrite + + * ### Allow number: `0` + + **Description**: Buffer to write data from the peripheral to the user. + + **Buffer format**: + + ``` + 0 1 2 3 4 5 6 7 10 11 ... + +---------+-----------+---------+--------+-----------+-----------+------------+------+-----------+ ... + | counter(u32) |buf0[0](u8)|buf0[1](u8)|buf0[2](u8) | .... |buf1[0](u8)| ... + +---------+-----------+---------+--------+-----------+-----------+------------+------+-----------+ ... + | Message 0 | Message 1 ... + ``` + +## Allow ReadOnly + + * ### Allow number: `0` + + **Description**: Buffer to send data from the user to the peripheral. The length of the buffer is + 8 bytes. + + **Buffer format**: + + ``` + 0 1 2 3 7 8 + +----------+----------+----------+--------+-----------+ + |buf[0](u8)|buf[1](u8)|buf[2](u8)| .... |buf[7](u8) | + +----------+----------+----------+--------+-----------+ + | Message | + ``` + +## Subscribe + * ### Subscribe Number: `0` + + **Description**: Enable callback for when the device was enabled. + + **Argument 1**: 0 if success, otherwise the error number if there is + any error in the enable process + + **Argument 2**: unused + + **Argument 3**: unused + + * ### Subscribe Number: `1` + + **Description**: Disable callback for when the device was disabled. + + **Argument 1**: 0 if success, otherwise the error number if there is + any error in the disable process + + **Argument 2**: unused + + **Argument 3**: unused + + * ### Subscribe Number: `2` + + **Description**: Callback that the last message was sent. + + **Argument 1**: 0 if success + + **Argument 2**: unused + + **Argument 3**: unused + + * ### Subscribe Number: `3` + + **Description**: Callback that a new message was received. + + **Argument 1**: 0 if success + + **Argument 2**: unused + + **Argument 3**: unused + + * ### Subscribe Number: `4` + + **Description**: Callback that the receive process was stopped. + + **Argument 1**: 0 if success + + **Argument 2**: unused + + **Argument 3**: unused + + * ### Subscribe Number: `5` + + **Description**: Callback that signals an error during the transmission or + receiving of a message or that a `state_changed` callback was called from + the driver without a previously sent enable or disable request or that the + state of the peripheral is different than the state the capsule expected it + to be. + + **Argument 1**: the error code, that can be kernel errors or custom capsule + errors: ERROR_TX or ERROR_RX + + **Argument 2**: the kernel error code, if the first argument is a custom capsule + error. + + **Argument 3**: unused + \ No newline at end of file diff --git a/doc/syscalls/50003_key_value.md b/doc/syscalls/50003_key_value.md new file mode 100644 index 0000000000..8c9a4fe373 --- /dev/null +++ b/doc/syscalls/50003_key_value.md @@ -0,0 +1,219 @@ +--- +driver number: 0x50003 +--- + +# Key-Value Storage + +This Driver provides access to a shared key-value store. + +Note: use of this interface is protected by `StoragePermissions`, so +applications will need permissions in the TBF headers to use this interface. + +## Command + +- ### Command number: `0` + + Does the driver exist? + + #### Arguments + + - **1**: unused + - **2**: unused + + #### Returns + + `SUCCESS` if it exists, otherwise `NODEVICE`. + +- ### Command number: `1` + + **GET**. Retrieve a value from the store based on the provided key. + + Use RO allow 0 to set the key. + + #### Arguments + + - **1**: unused + - **2**: unused + + #### Returns + + `SUCCESS` if the get command was accepted. On error, returns: + + - `NOMEM`: Already a pending request for this application. + - `RESERVE`: Error in the driver, requesting process not set. + - `SIZE`: Key too long. + - `INVAL`: Incorrect permissions for the app. + +- ### Command number: `2` + + **SET**. Set a key-value pair in the storage. If the key already exists the + existing value will be overwritten. + + Use RO allow 0 to set the key and RO allow 1 to set the value. + + #### Arguments + + - **1**: unused + - **2**: unused + + #### Returns + + `SUCCESS` if the set command was accepted. On error, returns: + + - `NOMEM`: Already a pending request for this application or no room + in internal buffers to store value. + - `RESERVE`: Error in the driver, requesting process not set or value allow + buffer not set. + - `SIZE`: Key too long or value too long. + - `INVAL`: Incorrect permissions for the app. + +- ### Command number: `3` + + **DELETE**. Delete a key-value pair from the database. + + Use RO allow 0 to set the key. + + #### Arguments + + - **1**: unused + - **2**: unused + + #### Returns + + `SUCCESS` if the delete command was accepted. On error, returns: + + - `NOMEM`: Already a pending request for this application. + - `RESERVE`: Error in the driver, requesting process not set. + - `SIZE`: Key too long. + - `INVAL`: Incorrect permissions for the app. + +- ### Command number: `4` + + **ADD**. Add a key-value pair to the storage. The key must not already exist + in the database. If the key already exists the operation will ultimately fail + with an error in the upcall. + + Use RO allow 0 to set the key and RO allow 1 to set the value. + + #### Arguments + + - **1**: unused + - **2**: unused + + #### Returns + + `SUCCESS` if the add command was accepted. On error, returns: + + - `NOMEM`: Already a pending request for this application or no room + in internal buffers to store value. + - `RESERVE`: Error in the driver, requesting process not set or value allow + buffer not set. + - `SIZE`: Key too long or value too long. + - `INVAL`: Incorrect permissions for the app. + +- ### Command number: `5` + + **UPDATE**. Modify a value belonging to the specified key in the storage. The + key must already exist in the database. If the key does not already exist the + operation will ultimately fail with an error in the upcall. + + Use RO allow 0 to set the key and RO allow 1 to set the value. + + #### Arguments + + - **1**: unused + - **2**: unused + + #### Returns + + `SUCCESS` if the update command was accepted. On error, returns: + + - `NOMEM`: Already a pending request for this application or no room + in internal buffers to store value. + - `RESERVE`: Error in the driver, requesting process not set or value allow + buffer not set. + - `SIZE`: Key too long or value too long. + - `INVAL`: Incorrect permissions for the app. + +## Subscribe + +- ### Subscribe number: `0` + + Subscribe to operation completion upcalls. All K-V operations will trigger + this upcall when complete. + + #### Upcall Signature + + The upcall signature looks like: + + ```rust + fn upcall(s: Statuscode, value_length: usize, unused: usize); + ``` + + If the requested operation was set/add/update/delete, the other fields are + always 0. + + If the requested operation was a GET, `value_length` will be set to the length + of the value in bytes. If the value was longer than what fit in the RW allowed + buffer, `s` will be a `SIZE` error. If a different error occurred + `value_length` will be set to 0. + + The third argument `unused` is always 0. + + ##### `Statuscode` Values + + If the operation succeeded `s` will be `SUCCESS`. + + On failure, the following errors will be returned: + + - For GET: + - `SIZE`: The value is longer than the provided buffer. + - `NOSUPPORT`: The key could not be found or the app does not have + permission to read this key. + - `FAIL`: An internal error occurred. + - For SET: + - `NOSUPPORT`: The app does not have permission to store this key. + - For ADD: + - `NOSUPPORT`: The key already exists and cannot be added or the app does + not have permission to add this key. + - For UPDATE: + - `NOSUPPORT`: The key does not already exist and cannot be modified or the + app does not have permission to modify this key. + - For SET/ADD/UPDATE: + - `NOMEM`: The key could not be updated because the KV store is full. + - `SIZE`: The key or value is too many bytes. + - `FAIL`: An internal error occurred. + - For DELETE: + - `NOSUPPORT`: The key does not exist or the app does not have permission to + delete this key. + - `FAIL`: An internal error occurred. + +## Read-Only Allow + +- ### RO Allow number: `0` + + The key to use for the intended operation. The length of the allowed buffer + must match the length of the key. + + +- ### RO Allow number: `1` + + The value to use for the intended write operation. The length of the allowed + buffer must match the length of the value. + + This is only used for set/add/update operations. + +## Read-Write Allow + +- ### RW Allow number: `0` + + Storage for the value after a GET operation. The kernel will write the value + read from the database here. + + If the read value is longer than the size of the allowed buffer the driver + will provide the portion of the value that does fit. The `value_length` in the + callback will still be set to the full size of the original value. + + As the kernel must be able to write the buffer to provide userspace the value + this must be a read-write allow, and separate from the RO allow for setting + the value. diff --git a/doc/syscalls/60000_ambient_temperature.md b/doc/syscalls/60000_ambient_temperature.md index 420ca7f889..0bfe5d60ee 100644 --- a/doc/syscalls/60000_ambient_temperature.md +++ b/doc/syscalls/60000_ambient_temperature.md @@ -20,7 +20,7 @@ hundredths of degrees **Argument 2**: unused - **Returns**: Ok(()) if it exists, otherwise NODEVICE + **Returns**: Success if it exists, otherwise NODEVICE * ### Command number: `1` diff --git a/doc/syscalls/60001_humidity.md b/doc/syscalls/60001_humidity.md index 976b5ad9d1..4920934472 100644 --- a/doc/syscalls/60001_humidity.md +++ b/doc/syscalls/60001_humidity.md @@ -20,7 +20,7 @@ hundredths of a percent. **Argument 2**: unused - **Returns**: Ok(()) if it exists, otherwise NODEVICE + **Returns**: Success if it exists, otherwise NODEVICE * ### Command number: `1` diff --git a/doc/syscalls/60002_luminance.md b/doc/syscalls/60002_luminance.md index f870d90900..c2e95675e5 100644 --- a/doc/syscalls/60002_luminance.md +++ b/doc/syscalls/60002_luminance.md @@ -19,7 +19,7 @@ from a sensor. Luminance is reported in lux (lx). **Argument 2**: unused - **Returns**: Ok(()) if it exists, otherwise NODEVICE + **Returns**: Success if it exists, otherwise NODEVICE * ### Command number: `1` diff --git a/doc/syscalls/70005_l3gd20.md b/doc/syscalls/70005_l3gd20.md index bf08d7a9cf..74ac53190f 100644 --- a/doc/syscalls/70005_l3gd20.md +++ b/doc/syscalls/70005_l3gd20.md @@ -14,7 +14,7 @@ Three axis gyroscope and temperature sensor. * ### Command number: `0` - **Description**: Returns Ok(()) + **Description**: Existence check. **Argument 1**: unused diff --git a/doc/syscalls/70006_lsm303dlhc.md b/doc/syscalls/70006_lsm303dlhc.md index 11211bca48..0ad69e61c0 100644 --- a/doc/syscalls/70006_lsm303dlhc.md +++ b/doc/syscalls/70006_lsm303dlhc.md @@ -14,7 +14,7 @@ Three axis accelerometer, magnetometer and temperature sensor. * ### Command number: `0` - **Description**: Returns Ok(()) + **Description**: Existence check. **Argument 1**: unused diff --git a/doc/syscalls/90001_screen.md b/doc/syscalls/90001_screen.md index e29bd3783b..d05ce7ef81 100644 --- a/doc/syscalls/90001_screen.md +++ b/doc/syscalls/90001_screen.md @@ -8,6 +8,17 @@ driver number: 0x90001 The screen driver allows the process to write data to a framebuffer of a screen. +This syscall driver is designed for single-client usage, +for the sake of simplicity, and because a single client covers +a significant portion of display use cases. +While several clients may use this interface simultaneously, +each may independently modify parameters defining the usage of the display, +like resolution, or power. Those changes will not be broadcast +to every client, requiring external synchronization. +This syscall interface does not expose the current power state of the display, +so each usage should start by calling the "Set power" syscall. +All commands except "Does the driver exist?" and "Set power" +may return OFF when power is not enabled (see screen HIL for details). ## Command * ### Command number: `0` @@ -18,7 +29,7 @@ The screen driver allows the process to write data to a framebuffer of a screen. **Argument 2**: unused - **Returns**: Ok(()) if it exists, otherwise NODEVICE + **Returns**: Success if it exists, otherwise NODEVICE * ### Command number: `1` @@ -30,17 +41,35 @@ The screen driver allows the process to write data to a framebuffer of a screen. **Returns**: SUCCESS_U32 with 1 if yes, and 0 if no + * ### Command number: `2` + + **Description**: Set power + + **Argument 1**: 0 if off, nonzero if on. + + **Argument 2**: unused + + **Returns**: Ok(()) followed by the ready callback if the command was successful, + BUSY if another command is in progress. + + The callback will carry 1 as an argument if the display was turned on, + but configuration not fully applied. Otherwise, the argument is 0. + * ### Command number: `3` **Description**: Set brightness - **Argument 1**: Percent of brightness, 0% should turn off the screen, greater than 0% should turn it on. + **Argument 1**: Lightness value, relative to minimum and maximum supported. + 0 should turn off the light if available, greater than 0 should set it to minimum. + 65535 and above turn lightness to the maximum supported. + Intermediate values approximate intermediate lightness levels. + May take effect only after power is set (e.g. for LED displays). **Argument 2**: unused **Returns**: Ok(()) if the command was successful, BUSY if another command is in progress. - * ### Command number: `4` + * ### Command number: `4` (deprecated) **Description**: Turn on invert colors mode @@ -50,7 +79,7 @@ The screen driver allows the process to write data to a framebuffer of a screen. **Returns**: Ok(()) if the command was successful, BUSY if another command is in progress. - * ### Command number: `5` + * ### Command number: `5` (deprecated) **Description**: Turn off invert colors mode @@ -59,6 +88,18 @@ The screen driver allows the process to write data to a framebuffer of a screen. **Argument 2**: unused **Returns**: Ok(()) if the command was successful, BUSY if another command is in progress. + + * ### Command number: `6` + + **Description**: Control invert colors mode. + Color inversion will affect all pixels already submitted, and submitted in the future. + It may get reset when in case of switching pixel formats. + + **Argument 1**: 0 if off, nonzero if on. + + **Argument 2**: unused + + **Returns**: Ok(()) if the command was successful, BUSY if another command is in progress. * ### Command number: `11` @@ -68,7 +109,7 @@ The screen driver allows the process to write data to a framebuffer of a screen. **Argument 2**: unused - **Returns**: SUCCESS_U32 with a u32 being the number of supported resolutions (minimum 1), BUSY if another command is in progress. + **Returns**: SUCCESS_U32 with a u32 being the number of supported resolutions (minimum 1). * ### Command number: `12` @@ -78,43 +119,49 @@ The screen driver allows the process to write data to a framebuffer of a screen. **Argument 2**: unused - **Returns**: Ok(()) followed by a callback with the resolution, BUSY if another command is in progress. + **Returns**: A pair of u32 values: width, height. * ### Command number: `13` - **Description**: Get the number of supported color depth (Setup API) + **Description**: Get the number of supported pixel formats (Setup API) **Argument 1**: unused **Argument 2**: unused - **Returns**: SUCCESS_U32 with a u32 being the number of supported color depth (minimum 1), BUSY if another command is in progress. + **Returns**: SUCCESS_U32 with a u32 being the number of supported pixel formats (minimum 1). * ### Command number: `14` - **Description**: Get the type of a supported color depth (Setup API) + **Description**: Get the type of a supported pixel format (Setup API) - **Argument 1**: index of the color depth (0, return of command 13) + **Argument 1**: index of the pixel formats (0, return of command 13) **Argument 2**: unused - **Returns**: Ok(()) followed by a callback with the resolution, BUSY if another command is in progress. + **Returns**: SUCCESS_U32 with the pixel format value, INVAL if index out of bounds. * ### Command number: `21` - **Description**: Get the screen's rotation + **Description**: Get the screen's rotation. **Argument 1**: unused **Argument 2**: unused - **Returns**: Ok(()) followed by a callback with the rotation value, BUSY if another command is in progress. + **Returns**: SUCCESS_U32 with the rotation value: + Normal = 0, + Rotated90 = 1, + Rotated180 = 2, + Rotated270 = 3. + + Rotation is measured counterclockwise. * ### Command number: `22` **Description**: Set the screen's rotation (Setup API) - **Argument 1**: rotation value (0 - normal, 1 - 90deg, 2 - 180deg, 3 - 270deg) + **Argument 1**: rotation value, counterclockwise (0 - normal, 1 - 90deg, 2 - 180deg, 3 - 270deg) **Argument 2**: unused @@ -128,7 +175,7 @@ The screen driver allows the process to write data to a framebuffer of a screen. **Argument 2**: unused - **Returns**: Ok(()) followed by a callback with the rotation value, BUSY if another command is in progress. + **Returns**: A pair of u32 values: width, height. * ### Command number: `24` @@ -142,19 +189,24 @@ The screen driver allows the process to write data to a framebuffer of a screen. * ### Command number: `25` - **Description**: Get the screen's color depth + **Description**: Get the screen's pixel format **Argument 1**: unused **Argument 2**: unused - **Returns**: Ok(()) followed by a callback with the rotation value, BUSY if another command is in progress. + **Returns**: A single u32 value. + 0: 8 pixels per byte monochromatic, pixels more to the left are more significant bits. 1 is light, 0 is dark. + 1: RGB_233, 2-bit red channel, 3-bit green channel, 3-bit blue channel. + 2: RGB_565, 5-bit red channel, 6-bit green channel, 5-bit blue channel. + 3: RGB_888 + 4: ARGB_8888 (RGB with transparency) * ### Command number: `26` - **Description**: Set the screen's color depth (Setup API) + **Description**: Set the screen's pixel format (Setup API) - **Argument 1**: color depth + **Argument 1**: pixel format specifier (see above) **Argument 2**: unused @@ -170,7 +222,7 @@ The screen driver allows the process to write data to a framebuffer of a screen. **Returns**: Ok(()) followed by a callback when it is done, BUSY if another command is in progress. - * ### Command number: `101` + * ### Command number: `200` **Description**: Initiate a write transaction of a buffer shared using `allow_readonly`. At the end of the transaction, a callback will be delivered if the process @@ -182,7 +234,7 @@ The screen driver allows the process to write data to a framebuffer of a screen. **Returns**: Ok(()) followed by a callback when it is done, BUSY if another command is in progress. - * ### Command number: `102` + * ### Command number: `300` **Description**: Initiate a fill transaction of a buffer shared using `allow_readonly`. This will fill the write frame with the first pixel in thhe buffer. At the end of the transaction, a callback will be delivered if the process @@ -217,4 +269,3 @@ The screen driver allows the process to write data to a framebuffer of a screen. new buffer will be written in its entirety but not both). **Returns**: Ok(()) if the subscribe was successful. - diff --git a/doc/syscalls/90002_touch.md b/doc/syscalls/90002_touch.md index 2166689d1c..ef2e96e9c4 100644 --- a/doc/syscalls/90002_touch.md +++ b/doc/syscalls/90002_touch.md @@ -18,7 +18,7 @@ The touch driver allows the process to interract with a touch panel. **Argument 2**: unused - **Returns**: Ok(()) if it exists, otherwise NODEVICE + **Returns**: Success if it exists, otherwise NODEVICE * ### Command number: `1` diff --git a/doc/syscalls/90003_text_screen.md b/doc/syscalls/90003_text_screen.md index 55ba7440c0..fba6b9d771 100644 --- a/doc/syscalls/90003_text_screen.md +++ b/doc/syscalls/90003_text_screen.md @@ -19,7 +19,7 @@ screen like an LCD display. **Argument 2**: unused - **Returns**: Ok(()) if it exists, otherwise NODEVICE + **Returns**: Success if it exists, otherwise NODEVICE * ### Command number: `1` diff --git a/doc/syscalls/README.md b/doc/syscalls/README.md index 1f69f2fc02..964ee5fd8a 100644 --- a/doc/syscalls/README.md +++ b/doc/syscalls/README.md @@ -5,7 +5,7 @@ This folder contains the detailed documentation for the interfaces between userspace and the kernel. It includes details of the ABI interface, the kernel provided syscalls, and the driver specific interfaces (using `allow`, `schedule`, and `command`). For more information on the general syscalls, see -[here](../Syscalls.md). +[here](https://book.tockos.org/doc/syscalls). @@ -15,12 +15,13 @@ provided syscalls, and the driver specific interfaces (using `allow`, * [Base](#base) * [Kernel](#kernel) * [Hardware Access](#hardware-access) - * [Radio](#radio) + * [Networking](#networking) * [Cryptography](#cryptography) * [Storage](#storage) * [Sensors](#sensors) * [Sensor ICs](#sensor-ics) * [Other ICs](#other-ics) + * [Display](#display) * [Miscellaneous](#miscellaneous) @@ -47,16 +48,13 @@ stabilized or not (a "✓" indicates stability) in the Tock 2.0 release. | ✓ | 0x00001 | [Console](00001_console.md) | UART console | | ✓ | 0x00002 | [LED](00002_leds.md) | Control LEDs on board | | ✓ | 0x00003 | [Button](00003_buttons.md) | Get interrupts from buttons on the board | -| ✓ | 0x00005 | [ADC](00005_adc.md) | Sample analog-to-digital converter pins | -| | 0x00006 | DAC | Digital to analog converter | -| | 0x00007 | [AnalogComparator](00007_analog_comparator.md) | Analog Comparator | | | 0x00008 | [Low-Level Debug](00008_low_level_debug.md) | Low-level debugging tools | -| | 0x00009 | [ROS](00009_ros.md) | Read Only State, access system information | ### Kernel |2.0| Driver Number | Driver | Description | |---|---------------|------------------|--------------------------------------------| +| | 0x00009 | [ROS](00009_ros.md) | Read Only State, access system information | | | 0x10000 | IPC | Inter-process communication | ### Hardware Access @@ -64,16 +62,21 @@ stabilized or not (a "✓" indicates stability) in the Tock 2.0 release. |2.0| Driver Number | Driver | Description | |---|---------------|------------------|--------------------------------------------| | | 0x00004 | [GPIO](00004_gpio.md) | Set and read GPIO pins | +| ✓ | 0x00005 | [ADC](00005_adc.md)| Sample analog-to-digital converter pins | +| | 0x00006 | DAC | Digital to analog converter | +| | 0x00007 | [AnalogComparator](00007_analog_comparator.md) | Analog Comparator | +| | 0x00010 | [PWM](00010_pwm.md)| Control PWM pins | | | 0x20000 | UART | UART | | | 0x20001 | SPI | Raw SPI Master interface | | | 0x20002 | SPI Slave | Raw SPI slave interface | | | 0x20003 | I2C Master | Raw I2C Master interface | | | 0x20004 | I2C Slave | Raw I2C Slave interface | | | 0x20005 | USB | Universal Serial Bus interface | +| | 0x20007 | [CAN](20007_can.md)| Controller Area Network interface | _Note:_ GPIO is slated for re-numbering in Tock 2.0. -### Radio +### Networking |2.0| Driver Number | Driver | Description | |---|---------------|------------------|--------------------------------------------| @@ -96,6 +99,7 @@ _Note:_ GPIO is slated for re-numbering in Tock 2.0. | | 0x50000 | App Flash | Allow apps to write their own flash | | | 0x50001 | Nonvolatile Storage | Generic interface for persistent storage | | | 0x50002 | SDCard | Raw block access to an SD card | +| | 0x50003 | [Key-Value](50003_key_value.md) | Access to a key-value storage database | ### Sensors @@ -104,10 +108,11 @@ _Note:_ GPIO is slated for re-numbering in Tock 2.0. | ✓ | 0x60000 | [Ambient Temp.](60000_ambient_temperature.md) | Ambient temperature (centigrate) | | ✓ | 0x60001 | [Humidity](60001_humidity.md) | Humidity Sensor (percent) | | ✓ | 0x60002 | [Luminance](60002_luminance.md) | Ambient Light Sensor (lumens) | -| | 0x60003 | Pressure | Pressure sensor | -| | 0x60004 | Ninedof | Virtualized accelerometer/magnetometer/gyroscope | -| | 0x60005 | Proximity | Proximity Sensor | -| | 0x60006 | SoundPressure | Sound Pressure Sensor | +| | 0x60003 | Pressure | Pressure sensor | +| | 0x60004 | Ninedof | Virtualized accelerometer/magnetometer/gyroscope | +| | 0x60005 | Proximity | Proximity Sensor | +| | 0x60006 | SoundPressure | Sound Pressure Sensor | +| | 0x90002 | [Touch](90002_touch.md) | Multi Touch Panel | ### Sensor ICs @@ -129,11 +134,15 @@ _Note:_ GPIO is slated for re-numbering in Tock 2.0. | | 0x80003 | GPIO Async | Asynchronous GPIO pins | | | 0x80004 | nRF51822 | nRF serialization link to nRF51822 BLE SoC | -### Miscellaneous +### Display |2.0| Driver Number | Driver | Description | |---|---------------|-----------------------------------------|--------------------------------------------| -| | 0x90000 | Buzzer | Buzzer | | | 0x90001 | [Screen](90001_screen.md) | Graphic Screen | -| | 0x90002 | [Touch](90002_touch.md) | Multi Touch Panel | | | 0x90003 | [Text Screen](90003_text_screen.md) | Text Screen | + +### Miscellaneous + +|2.0| Driver Number | Driver | Description | +|---|---------------|-----------------------------------------|--------------------------------------------| +| | 0x90000 | Buzzer | Buzzer | diff --git a/doc/threat_model/Application_Loader.md b/doc/threat_model/Application_Loader.md deleted file mode 100644 index 7d75789e08..0000000000 --- a/doc/threat_model/Application_Loader.md +++ /dev/null @@ -1,87 +0,0 @@ -Application Loader -================== - -## What is an Application Loader? - -The term "application loader" refers to the mechanism used to add Tock -applications to a Tock system. It can take several forms; here are a few -examples: - -1. [Tockloader](https://www.github.com/tock/tockloader) is an application loader - that runs on a host system. It uses various host-to-board interfaces (e.g. - JTAG, UART bootloader, etc) to manipulate application binaries on the Tock - system's nonvolatile storage. - -1. Some build systems combine the kernel and apps at build time into a single, - monolithic image. This monolithic image is then deployed using a programming - tool. - -1. A kernel-assisted installer may be a Tock capsule that receives application - binaries over USB and writes them into flash. - -## Why Must We Trust It? - -The application loader has the ability to read and modify application binaries. -As a result, the application loader must be trusted to provide confidentiality -and sometimes integrity guarantees to applications. For example, the application -loader must not modify or exfiltrate applications other than the application(s) -it was asked to operate on. - -Tock kernels that require all application binaries to be signed do not need to -trust the application loader for application integrity, as that is done by -validating the signature instead. Tock kernels that do not require signed -application binaries must trust the application loader to not maliciously modify -applications. - -To protect the kernel's confidentiality, integrity, and availability the -application loader must not modify, erase, or exfiltrate kernel data. On most -boards, the application loader must be trusted to not modify, erase, or -exfiltrate kernel data. However, Tock boards may use other mechanisms to protect -the kernel without trusting the application loader. For example, a board with -access-control hardware between its flash storage and the application loader may -use that hardware to protect the kernel's data without trusting the application -loader. - -## Tock Binary Format (TBF) Total Size Verification Requirement - -The application loader is required to confirm that the TBF header's -`total_size` field is correct for the specified format version (as specified in -the [Tock Binary Format](../TockBinaryFormat.md#tbf-header-base)) before -deploying an application binary. This is to prevent the newly-deployed -application from executing the following attacks: - -1. Specifying a too-large `total_size` that includes the subsequent - application(s) binary, allowing the malicious application to read the binary - (impacting confidentiality). - -1. Specifying a too-small `total_size` and making the kernel parse the end of - its image as the subsequent application binary's TBF headers (impacting - integrity). - -## Trusted Compute Base in the Application Loader - -The application loader may be broken into multiple pieces, only some of which -need to be trusted. The resulting threat model depends on the form the -application loader takes. For example: - -1. Tockloader has the access it needs to directly delete, corrupt, and - exfiltrate the kernel. As a result, Tockloader must be trusted for Tock's - confidentiality, integrity, and availability guarantees. - -1. A build system that combines apps into a single image must be trusted to - correctly compile and merge the apps and kernel. The build system must be - trusted to provide confidentiality, integrity, and availability guarantees. - The firmware deployment mechanism must be trusted for confidentiality and - availability guarantees. If the resulting image is signed (and the signature - verified by a bootloader), then the firmware deployment mechanism need not be - trusted for integrity. If there is no signature verification in the - bootloader then the firmware deployment mechanism must be trusted for - integrity as well. - -1. An application loader that performs the nonvolatile storage write from within - Tock's kernel may make its confidentiality, integrity, and availability - guarantees in the Tock kernel. Such a loader would need to perform the - `total_size` field verification within the kernel. In that case, the kernel - code is the only code that needs to be trusted, even if there are other - components to the application loader (such as a host binary that transmits - the application over USB). diff --git a/doc/threat_model/Capsule_Isolation.md b/doc/threat_model/Capsule_Isolation.md deleted file mode 100644 index 46f7f0c820..0000000000 --- a/doc/threat_model/Capsule_Isolation.md +++ /dev/null @@ -1,28 +0,0 @@ -Capsule Isolation -=========================== - -## Isolation Mechanism - -Capsules are limited to what they can access within Rust's type system without -using `unsafe`. That isolation is implemented by banning `unsafe` from use in -capsule code and by banning the use of unaudited libraries (except those that -ship with Rust's toolchain) in kernel code. This isolation is vulnerable to code -that exploits compiler bugs or bugs in `unsafe` code in toolchain libraries. -When a board integrator chooses to use a capsule, they are responsible for -auditing the code of the capsule to confirm the policies are followed and to -detect potentially malicious behavior. The use of Rust's type system as a -security isolation mechanism relies in part on Rust's resistance to underhanded -programming techniques (stealthy obfuscation), and is a weaker form of isolation -than the hardware-backed isolation used to isolate the kernel (and other -processes) from processes. - -Capsules are scheduled cooperatively with the rest of the kernel, and as such -they can deny service to the rest of the system. - -## Impact on Kernel API Design - -Kernel APIs should be designed to limit the data that capsules have access to. -Trusted kernel code should use capabilities as necessary in its API to limit the -access that capsule code has. For example, an API that allows its clients to -access data that is not owned by either the API or caller should require a -"trusted" capability. diff --git a/doc/threat_model/Code_Review.md b/doc/threat_model/Code_Review.md deleted file mode 100644 index 8b0b902c45..0000000000 --- a/doc/threat_model/Code_Review.md +++ /dev/null @@ -1,31 +0,0 @@ -Code Review -=========== - -## Kernel Code Review - -Changes to the Tock OS kernel (in the kernel/ directory of the repository) are -reviewed by the Tock core working group. However, not all ports of Tock (which -include chip crates, board crates, and hardware-specific capsules) are -maintained by the Tock core working group. - -The Tock repository must document which working group (if any) is responsible -for each hardware-specific crate or capsule. - -## Third-Party Dependencies - -Tock OS repositories permit third party dependencies for critical components -that are impractical to author directly. Each repository containing embedded -code (including [tock](https://www.github.com/tock/tock), -[libtock-c](https://www.github.com/tock/libtock-c), and -[libtock-rs](https://www.github.com/tock/libtock-rs)) must have a written policy -documenting: - -1. All unaudited required dependencies. For example, Tock depends on Rust's - [libcore](https://doc.rust-lang.org/core/index.html), and does not audit - `libcore`'s source. - -1. How to avoid pulling in unaudited optional dependencies. - -A dependency may be audited by vendoring it into the repository and putting it -through code review. This policy does not currently apply to host-side tools, -such as elf2tab and tockloader, but may be extended in the future. diff --git a/doc/threat_model/README.md b/doc/threat_model/README.md deleted file mode 100644 index cd18b14199..0000000000 --- a/doc/threat_model/README.md +++ /dev/null @@ -1,173 +0,0 @@ -Tock Threat Model -================= - - -**Note: This threat model is not descriptive of Tock's current implementation. -It describes how we intend Tock to work as of some future release, perhaps -2.0.** - -## Overview - -Tock provides hardware-based isolation between processes as well as -language-based isolation between kernel capsules. - -Tock supports a variety of hardware, including boards defined in the Tock -repository and boards defined "out of tree" in a separate repository. -Additionally, Tock's installation model may vary between different use cases -even when those use cases are based on the same hardware. As a result of Tock's -flexibility, the mechanisms it uses to provide isolation — and the strength of -that isolation — vary from deployment to deployment. - -This threat model describes the isolation provided by Tock as well as the trust -model that Tock uses to implement that isolation. Users of Tock, which include -board integrators and application developers, should use this threat model to -understand what isolation Tock provides to them (and what isolation it may not -provide). Tock developers should use this threat model as a guide for how to -provide Tock's isolation guarantees. - -## Definitions - -These definitions are shared between the documents in this directory. - -A **process** is a runtime instantiation of an application binary. When an -application binary "restarts", its process is terminated and a new process is -started using the same binary. Note that the kernel is not considered a process, -although it is a thread of execution. - -**Process data** includes a process' binary in non-volatile storage, its memory -footprint in RAM, and any data that conceptually belongs to the process that is -held by the kernel or other processes. For example, if a process is reading from -a UART then the data in the UART buffer is considered the process' data, even -when it is stored in a location in RAM only readable by the kernel. - -**Kernel data** includes the kernel's image in non-volatile storage as well as -data in RAM that does not conceptually belong to processes. For example, the -scheduler's data structures are kernel data. - -**Capsule data** is data that is associated with a particular kernel capsule. -This data can be either kernel data or process data, depending on its -conceptual owner. For example, an ADC driver's configuration is kernel data, -while samples an ADC driver takes on behalf of a process are process data. - -**Tock's users** refers to entities that make use of Tock OS. In the context of -threat modelling, this typically refers to board integrators (entities that -combine Tock components into an OS to run on a specific piece of hardware) and -application developers (who consume Tock's APIs and rely on the OS' guarantees). - -## Isolation Provided to Processes - -**Confidentiality:** A process' data may not be accessed by other processes or -by capsules, unless explicitly permitted by the process. Note that Tock does not -generally provide defense against side channel attacks; see the [Side Channel -Defense](#side-channel-defense) heading below for more details. Additionally, -[Virtualization](Virtualization.md) describes some limitations on isolation for -shared resources. - -**Integrity:** Process data may not be modified by other processes or by -capsules, except when allowed by the process. - -**Availability:** Processes may not deny service to each other at runtime. As an -exception to this rule, some finite resources may be allocated on a -first-come-first-served basis. This exception is described in detail in -[Virtualization](Virtualization.md). - -## Isolation Provided to Kernel Code - -**Confidentiality:** Kernel data may not be accessed by processes, except where -explicitly permitted by the owning component. Kernel data may not be accessed by -capsules, except where explicitly permitted by the owning component. The -limitations about [side channel defense](#side-channel-defense) and -[Virtualization](Virtualization.md) that apply to process data also apply to -kernel data. - -**Integrity:** Processes and capsules may not modify kernel data except through -APIs intentionally exposed by the owning code. - -**Availability:** Processes cannot starve the kernel of resources or otherwise -perform denial-of-service attacks against the kernel. This does not extend to -capsule code; capsule code may deny service to trusted kernel code. As described -in [Virtualization](Virtualization.md), kernel APIs should be designed to -prevent starvation. - -## Isolation that Tock does NOT Provide - -There are practical limits to the isolation that Tock can provide; this section -describes some of those limits. - -### Side Channel Defense - -In general, Tock's users should assume that Tock does NOT provide side channel -mitigations except where Tock's documentation indicates side channel mitigations -exist. - -Tock's answer to "should code X mitigate side channel Y" is generally "no". Many -side channels that Tock can mitigate in theory are too expensive for Tock to -mitigate in practice. As a result, Tock does not mitigate side channels by -default. However, specific Tock components may provide and document their own -side channel mitigation. For instance, Tock may provide a cryptography API that -implements constant-time operations, and may document the side channel defense -in the cryptography API's documentation. - -In deciding whether to mitigate a side channel, Tock developers should consider -both the cost of mitigating the side channel as well as the value provided by -mitigating that side channel. For example: - -1. Tock does not hide a process' CPU usage from other processes. Hiding CPU - utilization generally requires making significant performance tradeoffs, and - CPU utilization is not a particularly sensitive signal. - -1. Although Tock protects a process' data from unauthorized access, Tock does - not hide the size of a process' data regions. Without virtual memory - hardware, it is very difficult to hide a process' size, and that size is not - particularly sensitive. - -1. It is often practical to build constant-time cryptographic API - implementations, and protecting the secrecy of plaintext is valuable. As - such, it may make sense for a Tock board to expose a cryptographic API with - some side channel defenses. - -### Guaranteed Launching of Binaries - -Tock does not guarantee that binaries it finds are launched as processes. For -example, if there is not enough RAM available to launch every binary then the -kernel will skip some binaries. - -This parallels the "first-come, first-served" resource reservation process -described in [Virtualization](Virtualization.md#availability). - -## Components Trusted to Provide Isolation - -The Tock kernel depends on several components (including hardware and software) -in order to implement the above isolation guarantees. Some of these components, -such as the application loader, may vary depending on Tock's use case. The -following documents describe the trust model that exists between the Tock kernel -and its security-relevant dependencies: - -- [Capsule Isolation](Capsule_Isolation.md) describes the coding practices used - to isolate capsules from the remainder of the kernel. - -- [Application Loader](Application_Loader.md) describes the trust placed in the - application deployment mechanism. - -- [TBF Headers](TBF_Headers.md) describes the trust model associated with the - [Tock Binary Format](../TockBinaryFormat.md) headers. - -- [Code Review](Code_Review.md) describes code review practices used to ensure - the trustworthiness of Tock's codebase. - -## What is an "Application"? - -Tock does not currently have a precise definition of "application", although -there is consensus on the following: - -- Unlike a process, an application persists across reboots and updates. For - example, an application binary can be updated without becoming a new - application but the update will create a new process. - -- An application consists of at least one application binary (in the Tock Binary - Format), although it is unclear whether multiple application binaries can - collectively be considered a single application (e.g. if they implement a - single piece of functionality). - -This section will be updated when we have a more precise definition of -"application". diff --git a/doc/threat_model/TBF_Headers.md b/doc/threat_model/TBF_Headers.md deleted file mode 100644 index f5764e0cb3..0000000000 --- a/doc/threat_model/TBF_Headers.md +++ /dev/null @@ -1,20 +0,0 @@ -TBF Headers -=========== - -TBF is the [Tock Binary Format](../TockBinaryFormat.md). It is the format of -application binaries in a Tock system's flash storage. - -TBF headers are considered part of an application, and are mostly untrusted. -As such, TBF header parsing must be robust against malicious inputs (e.g. -pointers must be checked to confirm they are in-bounds for the binary). - -However, because the kernel relies on the TBF's `total_size` field to load the -binaries, the application loader is responsible for verifying the `total_size` -field at install time. The kernel trusts the `total_size` field for -confidentiality and integrity. - -When possible, [TLV types](../TockBinaryFormat.md#tlv-types) should be designed -so that the kernel does not need to trust their correctness. When a TLV type is -defined that the kernel must trust, then the threat model must be updated to -indicate that application loaders are responsible for verifying the value of -that TLV type. diff --git a/doc/threat_model/Virtualization.md b/doc/threat_model/Virtualization.md deleted file mode 100644 index bfe9934ff8..0000000000 --- a/doc/threat_model/Virtualization.md +++ /dev/null @@ -1,74 +0,0 @@ -Virtualization -============== - -Tock components that share resources between multiple clients (which may be -kernel components, processes, or a mix of both) are responsible for providing -confidentiality and availability guarantees to those clients. - -## Data Sharing (Confidentiality) - -In general, kernel components with multiple clients should not share data -between their clients. Furthermore, data from a client should not end up in a -capsule the client is unaware of. - -When a capsule with multiple clients is given a buffer by one of those clients, -it must do one of the following: - -1. Avoid sharing the buffer with any other kernel code. Return the buffer to the - same client. - -1. Only share the buffer downwards, to lower-level components. For example, a - capsule providing virtualized access to a piece of hardware may pass the - buffer to the driver for that hardware. - -1. Wipe the buffer before sharing it with another client. - -Kernel components with multiple clients that retrieve data on behalf of those -clients must implement isolation commensurate with their functionality. When -possible, components reading from shared buses should mux data transferred over -those buses. For example: - -1. A UDP API can provide a mechanism for clients (processes and/or capsules) to - gain exclusive access to a port. The UDP API should then prevent clients from - reading messages sent to other clients or impersonating other clients. - -1. A UART API with multiple clients should implement a protocol that allows the - UART API to determine which client a received packet belongs to and route it - accordingly (in other words, it should implement some form of muxing). - -1. Analog-to-Digital Converter (ADC) hardware does not have a concept of which - process "owns" data, nor is there a way to implement such a concept. As such, - an ADC API that allows clients to take samples upon request does not need to - take separate samples for different clients. An ADC API that receives - simultaneous requests to sample the same source may take a single reading and - distribute it to multiple clients. - -## Fairness (Availability) - -Tock components do not need to guarantee fairness between clients. For example, -a UART virtualization layer may allow capsules/processes using large buffers to -see higher throughputs than capsules/processes using small buffers. However, -components should prevent starvation when the semantics of the operation allow -it. For the UART example, this means using round-robin scheduling rather than -preferring lower-numbered clients. - -When it is not possible to prevent starvation — such as shared resources that -may be locked for indefinite amounts of time — then components have two -options: - -1. Allow resource reservations on a first-come, first-served basis. This is - essentially equivalent to allowing clients to take out unreturnable locks on - the resources. - -1. Restrict access to the API using a kernel capability (only possible for - internal kernel APIs). - -An example of an API that would allow first-come-first-served reservations is -crypto hardware with a finite number of non-sharable registers. In this case, -different processes can use different registers, but if the registers are -overcommitted then later/slower processes will be unable to reserve resources. - -An example of an API that would be protected via a kernel capability is -indefinite continuous ADC sampling that blocks other ADC requests. In this case, -first-come-first-served reservations do not make sense because only one client -can be supported anyway. diff --git a/doc/tock-stack.png b/doc/tock-stack.png deleted file mode 100644 index 6523d86d68..0000000000 Binary files a/doc/tock-stack.png and /dev/null differ diff --git a/doc/wg/README.md b/doc/wg/README.md index 017a632c10..09b2a318b5 100644 --- a/doc/wg/README.md +++ b/doc/wg/README.md @@ -1,11 +1,93 @@ Tock Working Groups =================== -Working groups (wg) are focused groups to organize development around a +Working groups are focused groups to organize development around a particular aspect of Tock. +## Motivation + +Tock encompasses a large and varied set of subsystems, architectures, +focus areas, libraries, ancillary tools, and documentation. Most +contributors have expertise and stake in a subset of these. Moreover, +it is impractical for any maintainer to keep up with the discussions +and direction of each part of the project. Finally, different parts of +the project should be able to move at different paces, with different +levels of scrutiny. For example, a soundness bug or performance +regression in the kernel crate can catastrophically impact all users +and should be avoided if at all possible, while a suboptimal design +decision in an experimental user-space library is not a big deal. + +To facilitate this, working groups take on responsibility for specific +sub-areas of the project. Members of a working group become experts in +that sub-area and are best able to determine appropriate scrutiny for +accepting contributions, frequency and mode of design discussions, +etc. + +## Structure And Responsibilities + +Tock development organizes around a core working group as well as +additional area-specific working groups. The core working group +oversees the project holistically, defining high-level design goals +and project direction, establishing working groups, and facilitating +work that spans multiple working groups. Other working groups +facilitate contributions to specific sub-areas of the project, with +devolved decision-making responsibility for accepting contributions, +design, and direction. + +While working groups *oversee* development, working group members are +not expected to be the primary source of contributions. Instead, +working groups establish code review standards, define and communicate +specific design direction for their purview, and ensure relevant +contributors are both supported and effective. + +### Working Group Organizational Guidelines + +Each working group has a Lead who assembles the working group +membership and is responsible for its operation. + +Each working group should include at least one member from the Core +Working Group to ensure that the Core Working Group is regularly +updated on the activities and motivations of the working group. The +Core Working Group member need not be the working group Lead. + +Each working group, including the Core Working Group, establishes its +own rules and procedures for accepting contributions, communicating, +meeting, and making decisions. In absence of such rules, working +groups make decisions by consensus, have weekly voice calls, make +meeting notes available publicly, and communicate asynchronously via a +mailing list. + +However, working groups are encouraged to establish appropriate +decision-making rules, meeting frequency and communication mode for +their needs and membership. + +## Core Working Group + +The Core Working Group shepherds and oversees the Tock OS and related +tools and libraries. Importantly, it serves as a backstop for managing +contributions that fall outside the purview of existing working groups +and for resolving conflicts about contributions that fall under the +purview of multiple working groups. It also establishes new working +groups to handle such contributions, and dissolves or re-organizes +existing working groups. + +Formally, the Core Working Group controls who can directly commit to +all Tock project repositories and devolves the ability to commit to +specific repositories or components of repositories to other working +groups. The Core Working Group, like other working groups, establishes +its own rules for deciding how to accept contributions as well as how +to establish and disband working groups. + Existing Working Groups ----------------------- - [Core](core/README.md) - [OpenTitan](opentitan/README.md) +- [Network](network/README.md) +- [Documentation](documentation/README.md) + + +Retired Working Groups +---------------------- + +- [Legacy](legacy/README.md) diff --git a/doc/wg/core/README.md b/doc/wg/core/README.md index e58a20f9dd..f69e9b8f38 100644 --- a/doc/wg/core/README.md +++ b/doc/wg/core/README.md @@ -3,30 +3,48 @@ Tock Core Working Group (core) - Working Group Charter - Adopted 3/31/2020 +- Amended 3/01/2024 ## Goals The core team manages and guides the development of Tock. Its responsibilities -are to: +are: -- Design, implement, and maintain the Tock kernel and its interfaces, including - system calls, hardware interface layers, and internal kernel APIs, -- Decide when to update the Rust compiler toolchain that Tock uses, -- Decide on the formation, scope, and lifetime of other Tock working groups, -- Decide on, articulate, and promote core principles of Tock kernel software and - its development, -- Support any code in the Tock repository that is not supported by another - working group. + +- Managing and overseeing code, documentation, testing, and releases + for the Tock project. + +- Defining and communicating overall project goals and direction. + +- Establishing and delegating responsibility over components and + subprojects to working groups. + +- Ensuring that working groups have the people and resources needed to + accomplish their work. + +- Ensuring working groups are accountable to their delegated + responsibilities and the project as a whole. + +- Coordinating decisions including (but not limited to) code, + documentation, testing, and releases that affect purviews delegated + to more than one working group. + +- Facilitating communication channels and consensus among working + groups. + +- Coordinating project-wide changes to teams, structures, or + processes. ## Members -- Hudson Ayers, [hudson-ayers](https://github.com/hudson-ayers), Stanford +- Hudson Ayers, [hudson-ayers](https://github.com/hudson-ayers), Cruise LLC - Brad Campbell, [bradjc](https://github.com/bradjc), UVA - Branden Ghena, [brghena](https://github.com/brghena), Northwestern - Philip Levis, [phil-levis](https://github.com/phil-levis), Stanford -- Amit Levy (chair), [alevy](https://github.com/alevy), Princeton University +- Amit Levy (Lead), [alevy](https://github.com/alevy), Princeton University - Pat Pannuto, [ppannuto](https://github.com/ppannuto), UCSD -- Leon Schuermann, [lschuermann](https://github.com/lschuermann), University of Stuttgart +- Alexandru Radovici [alexandruradovici](https://github.com/alexandruradovici), Politehnica Bucharest & OxidOS +- Leon Schuermann, [lschuermann](https://github.com/lschuermann), Princeton University - Johnathan Van Why, [jrvanwhy](https://github.com/jrvanwhy), Google ## Membership @@ -70,14 +88,3 @@ within a week of a call. This delay is to give participants an opportunity to correct any errors or better explain points that came up. They are intended to be a communication mechanism of the group, its discussions, the technical issues, and decisions, not a literal transcription of what is said. - -## Code Purview - -The core working group is in charge of (responsible for reviewing, approving, -and merging pull requests for) all code directories that are not under the -purview of another working group. The following directories are expected to -remain under the sole purview of the core working group: - -- `capsules`, although other working groups may have subdirectories -- `kernel` -- `libraries` diff --git a/doc/wg/core/notes/core-2020-02-21.txt b/doc/wg/core/notes/core-notes-2020-02-21.txt similarity index 100% rename from doc/wg/core/notes/core-2020-02-21.txt rename to doc/wg/core/notes/core-notes-2020-02-21.txt diff --git a/doc/wg/core/notes/core-2020-02-28.txt b/doc/wg/core/notes/core-notes-2020-02-28.txt similarity index 100% rename from doc/wg/core/notes/core-2020-02-28.txt rename to doc/wg/core/notes/core-notes-2020-02-28.txt diff --git a/doc/wg/core/notes/core-2020-03-06.txt b/doc/wg/core/notes/core-notes-2020-03-06.txt similarity index 100% rename from doc/wg/core/notes/core-2020-03-06.txt rename to doc/wg/core/notes/core-notes-2020-03-06.txt diff --git a/doc/wg/core/notes/core-2020-03-13.txt b/doc/wg/core/notes/core-notes-2020-03-13.txt similarity index 100% rename from doc/wg/core/notes/core-2020-03-13.txt rename to doc/wg/core/notes/core-notes-2020-03-13.txt diff --git a/doc/wg/core/notes/core-2020-06-12.md b/doc/wg/core/notes/core-notes-2020-06-12.md similarity index 100% rename from doc/wg/core/notes/core-2020-06-12.md rename to doc/wg/core/notes/core-notes-2020-06-12.md diff --git a/doc/wg/core/notes/core-notes-2021-12-10.md b/doc/wg/core/notes/core-notes-2021-12-10.md new file mode 100644 index 0000000000..ee96bad390 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2021-12-10.md @@ -0,0 +1,106 @@ +# Tock Core Notes 12/10/2021 + +Attendees + * Branden Ghena + * Leon Schuermann + * Alexandru Radovici + * Johnathan Van Why + * Phil Levis + * Jett Rink + * Pat Pannuto + * Hudson Ayers + * Amit Levy + * Brad Campbell + * Vadim Sukhomlinov + + +# Updates + * Phil: Two updates. 1) there was some discussion about use cases for things like OTBN and accelerators. As background, OTBN has a data memory and code memory space. When you want to do a big number operation, you load code and data and trigger the action. In some cases you load code once at boot and it's static. But there's a question whether that's the expected use case or if code will be dynamically loaded at runtime. I check in with Jade and Arun and they thing dynamic is an important use case. The number of instructions to possibly accelerate is larger than memory. Important for abstractions. + * Phil: 2) We're good with the UART TRD, so I'm going to work on that over the holidays. + * Alexandru: Regarding WiFi, we managed to scan networks. It works on the RPI board and we pushed a PR we want feedback on. We renamed the HIL to be chip-specific for now, since there's just one chip supported. For now it just scans networks. https://github.com/tock/tock/pull/2625 + * Jett: Moving allow read only and allow read/write into core kernel. PR is failing some CI tests, but we're looking into why. We had to submit a change to libtock-c, which we did. But I think that will break CI for everything right now. It's the LiteX simulation. So FYI, that might be unreliable until my PR lands. https://github.com/tock/tock/pull/2906 + * Leon: I think this is a proof of the value of LiteX, that we had a breaking change and it's signaling that it's broken. + * Hudson: To give some background, the CI uses a pinned version of libtock-c, so it won't break everything. I updated your PR to move CI to a new pinned commit. It still fails, so that might indeed be a real bug. Definitely needs looking into. + + +# Userspace Readable Allow + * Jett: Continuation of my update. With the PR getting close to landing, there are some cleanups. We can remove the default impl for the process buffers and we can remove an option that the process points to and make it a straight process ID. They're blocked because of the userspace allowable buffer. It's effectively the same as the allowed read/write buffer and it's type aliased to it. So, I only moved the allow read/write and read-only, not the userspace one. So I can't make these cleanups as the code is. Wondering if this is something we can simplify. + * Jett: Right now, this looks like an API contract thing. When the userspace shares a buffer, it can't assume that it can access the buffer anymore. The kernel can't assume that the userspace isn't looking at it. That's still the contract though. The userspace share explicitly allows the userspace process to access the buffer while shared, but they're treated the same on the kernel side. + * Jett: Looking at the way things are implemented, I don't know if the kernel will ever be able to take advantages of the differences here. The kernel has to assume that things could be modified, even if the application promises not to. The only exception would be if there was hardware protection, in which case the kernel could relax this. But all of our hardware will never have this advance MPU stuff. So the lowest common denominator would be assuming that the process could touch the memory, since we can't rely on that hardware everywhere. + * Jett: It seems like this is something we could potentially drop and make the API documentation for read/write allow. If we can't just drop it, then maybe we could do something for the read/write allow and use the top bit of the length to determine if it's a shared userspace readable buffer. So this concept is now something different that we need to discuss. Multiple ways forward are 1) drop the concept and change the docs or 2) can we encode the concept in a different way? Then in either case, dropping it allows a big process cleanup. + * Phil: I didn't 100% follow everything. You're asking if we can unify read/write allow with userspace readable allow? + * Jett: Yeah, can we drop the separate userspace version of it? We could say in docs, that processes could access the data inside a read/write allow. + * Phil: The intention had been that the kernel could revoke access to something you allowed, say through an MPU. It doesn't today and isn't feasible on platforms, but it could and the userspace should assume that it does. + * Jett: But every Tock isn't going to have that feature, so the kernel MUST assume that it doesn't have the protection. + * Phil: That's true, but the next step of your argument is that because the kernel MUST assume this, we should say that all buffers are readable? + * Jett: I think everything you pass to the kernel with a read/write allow, you should be able to read. + * Phil: That's not the semantics today. + * Jett: Right. I'm saying we merge the two semantics and change the docs to say this is how read/write allow works. + * Phil: But it would mean that kernel could NEVER protect those buffers. + * Jett: It could protect against write, but not read. + * Phil: Is the motivation implementation simplicity? + * Jett: One aspect is cleaning up code. But it's also good to simplify the API. + * Phil: Barring extreme cases, it should be semantics first and not implementation first. + * Phil: And you're right that it simplifies the API, but in making a more general syscall that has more options. + * Jett: I think removing the third option of allowing makes things less confusing. The userspace doesn't have to use new functionality or change their code, but can use new features if it wants. + * Phil: But there will no longer be the possibility of a syscall where you pass a buffer and then lose access to it. + * Jett: Do we need that feature? + * Phil: It's the general assumed behavior. When you pass a buffer you lose ownership until it's returned to you. You can't touch the buffer while the call is outstanding. + * Jett: Indeed libtock-rs wouldn't be able to take advantage of this bonus semantic. But other languages could. + * Johnathan: With the current design, it can do both. Other designs wouldn't do both under the same system call number though. + * Phil: It seems to me that the idea of changing semantics of memory sharing to simplify some implementation is bad. + * Jett: I think the backstory isn't the only point here. The API view is also meaningful. I didn't see big use cases for this option, although maybe it will be usable in the future. + * Phil: The userspace readable buffer was the special case. That's what we thought of as a narrow case. Generalizing it to everything seems weird. + * Jett: Will that restriction be useful though? + * Hudson: It seems to me that actual implementations are unlikely to ever diverge. Until every platform Tock supports can protect the buffers, we can't take advantage of it. And if we had that feature, we might change many other API things too. So just having two calls with different docs to support a hypothetical future doesn't seem useful. + * Leon: But some platform might have the capability. And if we change the call, then we lose that capability. + * Brad: I want to push back on the idea that we can't implement this. We're hamstrung by IPC right now, which limits our MPU. If we change IPC, then we get enough MPU regions that we could implement faults on access to allowed buffers. + * Leon: And FPGA implementations could trivially have 64 MPU regions. + * Jett: Okay, so it sounds like we want to keep the concept. We could still change how we express the concept, where it's a flag instead of a syscall number. That might make the API more complicated, but would allow some kernel cleanup. + * Phil: Lots of system calls isn't necessarily more complex if they're simple. + * Jett: This PR moved stuff into the core kernel so the grant manages it. There's machinery in code to handle it. If we wanted to make the userspace call, we would have to also make that code handle that as well. That could potentially lead to another usize usage, even if you don't use this concept. We'd have to expand it for everyone. So all three calls are similar. Or we can leave it how it is and have it be separate and be this thing that's in your "type T" interface and still be on the syscall driver trait. It seems that all three should be handled similarly though, since they're similar concepts. Maybe that's not something that we want to do. + * Leon: It sounds to me like we're trying to force three concepts into the same system call. + * Jett: No read-only allow would be its own system call, for sure. + * Brad: I'm sympathetic and agree that the current implementation is problematic because for every grant region we have to store the number of upcalls, the number of read-only allows, number of read-write allows, number of readable allows, even if they're all just zero. But we're never going to have 4 billion allows. Should we just divvy up the number of allows into different bit widths and just have one value? + * Phil: 256 seems like a great number + * Jett: I thought that could be a good optimization. I think that could work + * Hudson: It does mean that every capsule has to specify the number of userspace readable allows it has. It felt, when we added it, that it was zero cost. Now it's starting to feel like a cost even though only one upstream platform uses it. + * Phil: I'm less concerned about the grant region allocation than I am about code size. We can compress those numbers. Kernel code size, especially for specific use cases, matters. + * Brad: The code size might go down if we improve read-only and read-write allows and not having to duplicate machinery to support userspace readable allow. + * Jett: It sounds like we want to keep the concept. So I'll think other changes. + + +# TRD for App Completion + * Hudson: Brad had a decent comment I wanted to discuss here https://github.com/tock/tock/pull/2914 + * Brad: This would be a new TRD that would have some context and structure for app completion codes: a 32-bit number the process provides to the exit code that the kernel will store on behalf of the process. Right now we have no description about what the code means. So the proposal reserves regions for certain semantically meaningful completion codes. + * Phil: One question I have is "why SHOULD". + * Brad: I think the intention is for this TRD to be advisory in nature. It's not that the kernel will necessarily act on these values, which it could if they were MUST. The kernel wouldn't change control flow or act on the value based on its value. Just hold it. + * Phil: You're saying the kernel shouldn't act upon it, leave it flexible to apps. + * Brad: That's my understanding + * Phil: Seems reasonable to me. + * Brad: My comment was that this was about specifying process behavior, not kernel behavior. So should this doc be in the kernel at all? Especially since we don't say anything about how the kernel uses it. + * Brad: Also the exit call already has a flag about exit terminate versus exit restart. And this doesn't say how it interacts with that. + * Branden: Where else would you put it? + * Brad: It could go in the libtock-rs or libtock-c communities. Constants in the code. + * Pat: But I'm hesitant in encouraging differentiation between runtimes. Ecosystem should be faithful across languages. + * Phil: But that's not what happens in posix. What matters is what it's codes are, not the language. + * Leon: So the hesitation is that it's not the kernel, just a central authority. So this would define how applications act with respond to each other. + * Phil: I like that this is a SHOULD. If you have no idea what to return, here's a default. This is a good approach. But if you've got your own ideas, you're welcome to. It's good to give guidance, as long as the kernel makes no assumptions. + * Brad: Yeah. That's a good argument. Then in my opinion this TRD needs to go further and give more options. I could see that being useful. + * Phil: More than just saying you "should do this"? + * Brad: I think your example of writing a library and crashing is good. So you need to give a number. Not zero, but what? + * Phil: It's tricky, because any level of the stack could exit, and somehow they need to align. If main uses some values and libtock-c uses others, how do you reconcile that? + * Brad: I think we're agreeing! The TRD, as-is, doesn't actually provide a number though. Which the implementer needs rather than range. + * Branden: On the documentation standpoint, I think ecosystem goes in Tock repo, even if it's not binding on the kernel. + * Leon: I'm worried that the kernel will eventually make assumptions on these and make use of them. So the isolation isn't as clear. Fault handlers want to give info. + * Phil: Right, so will the kernel try to infer a string from this? + * Brad: I think that's problematic, but we just have to be rigorous about things the kernel relies on being part of the exit docs, not this advisory doc. + * Phil: We could say "if the error condition aligns with an error code, then it should be a TRD104 error code, otherwise it should be something ELSE" + * Leon: But the kernel might do something specific for some of these. + * Phil: We can be rigorous about it being a SHOULD. + * Leon: So it only makes sense in that other applications can use it? I'm worried that implicitly we'll do something in the kernel based on it. + * Phil: So don't do that :) + * Amit: So the proposed TRD seems to me to do as good of a job as it can to avoid that + * Brad: I think it's missing a little that we discussed here. Motivation and expectations. But then it would be good. + + diff --git a/doc/wg/core/notes/core-notes-2021-12-17.md b/doc/wg/core/notes/core-notes-2021-12-17.md new file mode 100644 index 0000000000..907584e97b --- /dev/null +++ b/doc/wg/core/notes/core-notes-2021-12-17.md @@ -0,0 +1,229 @@ +## Attendees + - Alexandru Radovici + - Leon Schuermann + - Philip Levis + - Hudson Ayers + - Johnathan Van Why + - Branden Ghena + - Alyssa Haroldsen + +## Updates + + - Johnathan: Libtock-rs update. I've got enough of it merged that it should be usable. I don't have example apps or documentation or integration testing. So you have to work with me to understand it until I get those written, but it should be usable. Ti50 is moving over to use it. + + - Phil: HURRAY! + + - Hudson: Pretty exciting. Once there's documentation and such you'll be ready for people to port drivers? + + - Johnathan: Yeah, I'll start with console and debug. One document I want to write is "how to port a driver". + + - Leon: Testing: integrating allows into the grant region, IPC breakage, Jett has showed us how CI is so valuable. I'm looking forward to incorporating this into the LiteX tests. + + - Johnathan: Were you running it in Verilator, or an FPGA? + + - Leon: Verilator, also works on FPGAs. That's eoomething we could also talk with Pat and the hardware CI folks. We could talk about an uniform way to run these tests. + + - Alyssa: Comcast seems to not like this meeting on Friday mornings. Calling in. + + - Hudson: We've been doing updates. If anyone else has some more. + + - Alyssa: I can provide an updated on the zero sized type (ZST) for ProcessBuffer. I sent a message to the unsafe code guidelines people about it. I have an example for it. I was talking to unsafe code guidelines. It passes under Miri with default flags, but not with unsafe pointer tags enabled. This means it *might* be unsound, but it's not clear. So we should convert to raw pointers, since the enum doesn't bring any benefits without big runtime posts. So it sounds like it is not sound now, but might be sound once extern types are standardized. + + - Leon: This is good. Based on this, it sounds like we should move to raw pointers. We've been trying to avoid big breaking changes to capsules. + + - Alyssa: That's why I've been avoiding trying to break the APIs, trying to avoid raw pointers, wanting to keep the API the same. + + - Leon: Yeah, thank you for the investigation. We definitely need to find a solution. What we are doing isn't sound. + + - Alyssa: There isn't any easy solution except raw pointers. I have some other ideas which might make the ZST idea work for the purposes we need, but I won't stand by them until I talk with people who know pointer provenance better than I do. + + - Hudson: Any more updates before the first agenda item? + + - Hudson: Well, in that case, Pat isn't here yet, but Leon are you equipped to talk about the item you added? + + - Leon: Yes, if there are unresolved questions we can answer it over email. + + - Leon: Around when we released 2.0, we talked about splitting out the Tock registers crate as that seemed to have some advantages. With all of the improvements, and more generic trait-based interface we have under the library. It's useful to a wide variety of crates across the Rust ecosystem. So the question is whether it's desirable by us to split it out. It's already on Crates.io. It's a schroedinger-crate. Kind of independent, but not, tracking our Rust toolchain. + + - Leon: Are there any opinions on that issue? + + - Hudson: If it's moved, any time we want to update the Rust version, this library would have to be updated to use the new Rust version. + + - Leon: We have seen some breakages when updated Rust versions. The library is in the state that it requires only one nightly feature. It's very likely to be compatible with stable in the near future. Potentially provide features on the crate that make it compatible with a wide variety of nightly versions. Then try to get it to stable as soon as we can. Then we don't have to worry about compatibility with the Tock Rust toolchain version at any time? + + - Hudson: This would be forked in the Tock organization? + + - Leon: The primary issue isn't splitting it out, but dealing with external dependencies. We've just vendored them in. I'm wondering if long-run that's a stable solution. Or we can pin different revisions. That's the larger issue at hand. + + - Hudson: For the specific case of register, if we can have dependencies within the Tock organization, that seems pretty tame. Broader question, changing our overarching policy that is a tough conversation to have without Amit and Brad here. + + - Leon: I think the idea that it's OK to pin things inside the project is OK. But there are also technical questions, of how we pin, for example versions in Crates.io, vendoring it into submodules. I wanted to bring this up, because there have been comments from Google folks, depending on which method we use, it might break internal build processes. + + - Phil: I like the limited view of dependencies within the Tock organization. Is stable 2 months or 2 years? Tension between good for us and good for the community. + + - Leon: Probably not 2 months but also not 2 years. I think separating this out will help a lot. I agree about good for us and good for the community. The way people do this now is hard for them. + + - Phil: I just worry about the tension: a lot of work from us for a tiny benefit is not good, but a small amount of work from us for a great benefit to the community is good. + + - Johnathan: I don't think there should be an issue from the Google side. + + - Leon: You mentioned sometjhing about issues with Crates.io, itn would be much harder for you to do downstream? + + - Johnathan: Crates.io is actually easier, I don't think this is the case. Ti50 is already handling crates from Crates.io. + + - Leon: That's great to know. I'd rather use standard was in the Rust ecosystem, rather than building some custom hacks. If we could just pull from Crates.io, like everyone else, that would be a great advantage. + + - Johnathan: That should be fine. + + - Leon: I agree, we should not make any decisions with Brad and Amit, because they have a lot of thoughts on external dependencies. + + - Leon: We want Tock to be reproducible. So when we build things, we want to be able to include a Cargo.toml file. Sorry, Cargo.lock. I wanted to know if this would have any issues. Rust says that for artifacts, binaries, to be reproducible, you should check in Cargo.lock to be reproducible. + + - Hudson: We used to, and it was a huge pain for regular developers. Lots of conflicts, didn't give much benefit. + + - Alyssa: Are you thinking for all crates, or examples, or the kernel, or what? + + - Leon: I think in terms of workspaces. + + - Alyssa: If we did that, I would change the Ti50 build process to remove the Cargo.lock file. No, wait, we would ignore it since it wouldn't be in our workspace. + + - Leon: If you're using it as a library, it would ignore it. + + - Alyssa: You can always try, and it's causing problems, and if so, take it out. + + - Hudson: We did, and it did have problems, and so we took it out. + + - Leon: I don't suppose you have any specifics on what these were, besides merge conflicts? + + - Hudson: Yeah, any change to any file required also all of the Cargo lock file, and so every change has merge conflicts. + + - Leon: Ah, it locks the individual crates in our workspace. + + - Alyssa: We weren't using a cargo worksspace, that might have made things more broken. + + - Hudson: Yeah, I think upstream wasn't using a workspace either. + + - Leon: Interesting issue, I will look into modern Cargo's behavior as it reflects on workspaces. We want binary reproducibility in the kernel, especially when we pull in external dependencies. + + - Hudson: We have it now, but once we move things into external dependencies, we'll lose it? + + - Leon: Yes, and so if we do, we'll lose it. I was hoping that Cargo.lock files would be a solution to this issue. If we do include them, we would have binary reproducibility. We would have a build failure if the upstream dependency changed. + + - Hudson: If we pin by a git commit hash, that would give us equivalent to what we have now. + + - Alyssa: Except for external dependencies without a Cargo.lock. + + - Hudson: Right, I am assuming are external dependencies don't have dependencies. + + - Alyssa: If Tock is tied to a specific git commit, but there is no Cargo.lock to reference, the build isn't reproducible. Even if you specify all of yours, and transitive dependencies could have different versions, so it's not sufficient to use a specific git commit to create a reproducible build. + + - Leon: Yeah, I think we were not thinking of external dependencies that have external dependencies, so as long as we do that we won't have this issue. + + - Alex: How do you tie to a git commit using Crates.io? + + - Leon: You can't -- we need to decide whether to use git and commits, or Crates.io and Cargo.lock. + + - Alex: What about including the Cargo.lock only in the commit that would be a release, then remove it inbetween. + + - Hudson: Having a Cargo.lock file in these dependencies, is much less of a pain than in the main Tock repository. + + - Leon: In library crates, you are not expected to check in a Cargo.lock file. We are only concerned about what kinds of headaches checking in this file will have for developers. It seems OK if it behaves with workspaces, if it only updates with external dependencies, not things internal to the repository? + + - Alyssa, Hudson: Yes. + + - Leon: Let's put this as a low priority item for next time, so Amit and Brad can discuss. + + - Hudson: Next time will be 3 weeks from today. + + - Leon: That's fine, Pat's on board with this change. I'll play around with this. We might create a Tock-registers repository within the Tock organization, but it won't be official. It's just for testing. That's everything from my side. + + - Hudson: Alyssa, did you want to talk about your RFC for exit codes? [https://github.com/tock/tock/pull/2914] + + - Alyssa: Yes, what's blocking it? + + - Leon: We want to have implicit behavior of the kernel based on return codes. There might not be an explicit statement, but if the kernel does make an implicit assumption, we want to avoid it in case an application wants to do something different. + + - Alyssa: Primary intention is to allow for the ability to automatically determine if an application exited successfully or not, and based on that take an action. I would like to, within a capsule, take an action based on a completion code. I'm looking for the possibility for the kernel to do something in the future. + + - Leon: I think we want to make sure the kernel doesn't make assumptions. + + - Alyssa: Agreed, but if it's just zero or non-zero, that's a huge error space. + + - Alex: How about adding another parameter? + + - Alyssa: This is why I say that a non-zero error code may not be an error. Except 1-1024, which is TRD104. + + - Alex: What if you just want to know if it's an error or not. + + - Hudson: Why would adding another parameter be any different than specifying some as success and some as failure. + + - Leon: Primary pushback is that the kernel should not... the error code is primarily about other apps. + + - Johnathan: Or capsules? + + - Leon: Right, or capsules? + + - Alyssa: It's a convention. Even for programs like test, where it's part of the expected life cycle. I wanted to be a little bit more lawyeristic about this. A non-zero SHOULD does not imply that all non-zero are error codes. + + - Phil: What are our requirements? + + - Alyssa: I would like a convnetion, that if an app completed with completion code 0, this means it was success, not an abnormal app completion. + + - Leon: I'm fine with this, we can be like POSIX. But we aren't quite Linux. But say, when an application returns 14, it doesn't say that it succeeds, or it failed, does that bring us any real benefits. + + - Alyssa: It's mostly that it can print out text for the TRD104 error code. Or "App returned 6, likely INVALID." If they're like, why is it returning this, why is this specific error message, then don't use a TRD 104 error code. + + - Leon: So this is for diagnostic purposes. Because we can't use this for any other reasons. + + - Alyssa: Mayne the kernel can use it. + + - Leon: If we do want the kernel to do something, then we want a more rigid specification. + + - Alyssa: I think it is mostly for diagnostic purposes. + + - Alex: This is why I am in favor of an extra parameter. Because then the exit code would be. + + - Alyssa: Always the option of whether the top bit is set. + + - Alex: That would work too. + + - Hudson: Top bit advantage is we dont want to change the syscalls. + + - Phil: TRD104 is really for function return values, bubbling it up to main you lose location. + + - Alyssa: At that point you need better inspection, use syscall tracing. + + - Alyssa: I have some thoughts on open EMs? I've noticed a few places that we have a new type wrapping an int, where we treat it like an enum, where it can have any value. When I have that kind of pattern, I defined all of the constants as associated constants, not constants within the type. So this way it has the same syntax as enum variants. So it becomes identical in semantics to a C++ scoped enum. I was wondering on the possibility of changing some of the libtock-rs structures to follow that pattern. + + - Johnathan: Sounds great to me! I didn't realize you could this without a trait. + + - Alyssa: They did it a few years ago, it's how uint32::MAX works. + + - Johnathan: Oh, cool! I use that all the time! + + - Alyssa: Could I talk for a couple of minutes on porting Ti50 to Tock 2.0. + + - Alyssa: I see we're supposed to be getting the result type, and manually getting the types out of a return. I've been working on an into that's generic over the success and failure types. And if it's the correct success variant, it's OK. If it's the correct failure, it's Err. If it's the incorrect value, if it's failure it preserves the error code, if it's the wrong success, it turns into Err with error code fail. Interest in upstreaming? + + - Johnathan: Yes, but I want to know what the upstream code implications are. There's probably a design for the command system call that involves generics that avoids code bloat issues. I think there's a better way to do a design than I did. But it'll be more than 3 minutes we have. + + - Alyssa: Can you explain code bloat? + + - Johnathan: If command returns a 3-type result, then you end up with command has its own internal match, the API implementation has its own match call, those are expensive to compile, unless it's inlined the compiler can't merge them. + + - Alyssa: This only returns Ok or Error. + + - Johnathan: I'm going to want to see code size. + + - Alyssa: I'll send you what I'm working on. Thank you. + + + + + + + + + + + diff --git a/doc/wg/core/notes/core-notes-2022-01-07.md b/doc/wg/core/notes/core-notes-2022-01-07.md new file mode 100644 index 0000000000..48a7aa038c --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-01-07.md @@ -0,0 +1,129 @@ +# Tock Core Notes 2022-01-07 + +Attendees: +- Hudson Ayers +- Branden Ghena +- Philip Levis +- Amit Levy +- Alexandru Radovici +- Jett Rink +- Leon Schuermann +- Vadim Sukhomlinov +- Johnathan Van Why + +## Updates + +### Asynchronous Process Loading + +- Phil: continuing to work on asynchronous process loading for + signature verification. Made good progress. Starting the + implementation with credentials which are just SHA hashes, given the + AES interface is not finalized yet. + + Will result in 3 coupled PRs to elf2tab, Tockloader, Tock. + +- Leon: Do you need software implementations of SHA or AES? Have them + lying around, would need to update them to the new interfaces. + +- Phil: Don't think so. + + Hope to have it done by end of the month. + +### Book "Getting Started with Secure Embedded Systems" + +- Alexandru: Our Tock book titled "Getting Started with Secure + Embedded Systems" is released. Made a PR to add it to the main + README. Not sure where it should go, but people should be able to + find it. + + https://link.springer.com/book/10.1007/978-1-4842-7789-8#toc + +- Hudson: This is great. We want others to be able to find it. + +### Code Size Analysis + +- Hudson: Working on a paper about some of the code size issues when + working with Tock on Ti50. Planning to submit it to LCTES. It has + some interesting insights, will share when it is ready. + +## libtock-c PR #274 + +- Hudson: it is possible that one could call `delay_ms` and the kernel + could fail to set the alarm. This would cause the app to yield + indefinitely. Terrible to debug. + + Proposed solution is to check the places where it could fail and + print errors. + + Don't like the solution: some people would prefer to rather not + print things. Also, it makes small apps significantly larger (blink + app goes from ~1400 bytes to ~2400 bytes), as for sleeping the + entire console machinery would need to be included. + + Fixed I could imagine: + 1. some mechanism for making the printing configurable by apps to be + able to opt-out. + 2. errors are returned when possible, when not possible the app + would simply not yield. + 3. user-provided function pointer for user-defined error handling + when setting an alarm fails. + 4. have `delay_ms` exit the app when failing to set an alarm. + + Complicating this, the libtock-c alarm driver has a virtualization + layer such that a single app can set multiple alarms + simultaneously. When an alarm fires, in the callback for that alarm, + it will check if there are future alarms and then set those. When an + alarm is requested it is enqueued in this structure. If an alarm + fails later in the queue, it's not trivial to propagate this error + to the original caller. + +- Branden: what is causing the kernel to return an error when setting + the alarm? + +- Hudson: don't actually know. Apparently this has been happening on + OpenTitan. However, given that it is technically possible for alarm + to return an error, we should probably handle those. + +- Phil: second Branden's question. Are we paying a cost for an + exceedingly rare edge case? + +- Branden: it appears that only the first time a timer is set it could + fail? Because if a second timer will be set, this is in response to + the first timer, which then must've already worked. + +- Hudson: not impossible, but it would be pretty unusual and involve + a bug in the kernel alarm driver. + +- Phil: another approach could be that, if this is of concern, a + developer should roll their own solution. + +- Alexandru: looking at the kernel, it does not seem like `set_alarm` + returns anything which could indicate an error. + +- Hudson: essentially it could fail for the same reason any call can + fail, which is not being able to enter the Grant. + +- Branden: presumably this is the first time a timer is set? + +- Hudson: yes. + +- Alexandru: `delay_ms` does not guarantee that it sleeps the exact + amount of time, but it might sleep for longer. We could define it to + just not sleep at all if it errors. + +- Phil: the alarm interface is designed to never fail, as it has + wrapping semantics. We need to find out what is generating this + failure. + +- Hudson: it seems the only reasons for failure is if deprecated + command 4 is called, or the Grant region cannot be allocated. + +- Branden: I don't hate that they are paying attention to this issue, + so returning an error seems like a good option. + + It would not hurt backwards compatibility, and it would only every + occur on the first call (if this is related to Grant allocation + failure). + +- Phil: the hypothesis is that this is a Grant problem, we should + verify that. diff --git a/doc/wg/core/notes/core-notes-2022-01-14.md b/doc/wg/core/notes/core-notes-2022-01-14.md new file mode 100644 index 0000000000..914c057956 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-01-14.md @@ -0,0 +1,80 @@ +# Tock Core Notes 01/14/2022 + +## Attendees + * Branden Ghena + * Hudson Ayers + * Phil Levis + * Jett Rink + * Pat Pannuto + * Johnathan Van Why + * Vadim Sukhomlinov + * Leon Schuermann + * Alexandru Radovici + + +## Updates + * Phil: From Alistair, he's been working for a while on getting cryptographic traits into kernel HILs. One that's really important is #2839 for RSA. https://github.com/tock/tock/pull/2839 Sometimes RSA keys are big and in Flash, but sometimes they're in RAM. Because passing buffers in asynchronous buffers need to be mut so you don't cast away mutability, this means RSA has two pairs of traits, one for mutable and one for immutable. We tried this a few ways and decided that mut/immut buffers shouldn't be in HILs. Passing down a mutable buffer and getting immutable back is a bad failure case. + * Phil: So, we should have people take a look at https://github.com/tock/tock/pull/2839 as it's important. + * Hudson: Small update, working with Alyssa on runtime/linker scripts for libtock-rs. Internally we're trying to port over to libtock-rs 2.0 APIs. Stuff is mostly working with help from Johnathan. Hopefully we'll be able to start porting drivers over soon. + + +## Crypto and Mutability + * Phil: Same challenge from RSA keys. The idea of some crypto data coming from Flash is going to be recurring in crypto traits. + * Phil: Issue I'm running into is verified process loading. Checking the integrity of an application image. The challenge is that there are lots of ways to do this, but a cryptographic hash over the text and metadata of binary is often the case. But the API for digest, the HIL for this, requires a mutable buffer. But code in Flash is immutable. https://github.com/tock/tock/blob/master/kernel/src/hil/digest.rs So this means we need parallel APIs, one for mutable and one for immutable. + * Phil: Things that use lots of data particularly have this problem. AES is only small blocks and you can copy into RAM. But for things like hashes and public keys, it matters. + * Phil: Right now, the best idea we could come up with is having these parallel traits. Other options seem to require using unsafe and panic if things go wrong. + * Jett: Potentially ignorant question. Why can't we just expose the immutable interface? Is there a time we need the references to be mutable? + * Phil: So if I pass a mutable buffer into this interface. If I want to pass it in immutably, I can't have a mutable reference too. + * Jett: So this is within the kernel. Nothing to do with apps. + * Phil: Right. So eventually I get the buffer back and it's immutable, but I can't get the mutable version back. + * Jett: Yeah, it seems like an abstraction for what type it _used_ to be seems useful. + * Phil: This is exactly what the mut_immut_buffer was for. So I can act on something like an immutable buffer, but I can pull the mutable out at the end if it really is that. So this could be the solution. OpenTitan group we talked about this and tried using this. The challenge is that moment when you've gotten the mut_immut_buffer back as the client, and want it to be the mutable buffer again, if there was a bug in the lower level implementation where it gave you the wrong thing back, what do you do? + * Jett: We've had issues where you pass in a buffer, some slicing happens, and you only get part of the buffer back. It's a similar issue if there are bugs. + * Phil: Yes. There's a bug in some low-level implementation that your client is being forced to handle. Slicing is a good analogy. You can split slices but you can't put them back together. So instead we said we'd have parallel traits. And the implementation actually uses mut_immut_buffer, but it can detect the error right where it happens, rather than kicking the can to the client. + * Jett: I would think that the capsule that gave the buffer that gave the buffer, it tries to cast right when it gets it and notices. Whereas if the underlying thing handles it, it might just never give a callback if it's the wrong type. Potentially. + * Phil: So the question is whether the capsule can respond to the error. Our model is that this is a serious memory leak problem. + * Jett: Right, like slicing. + * Hudson: I remember reading a blog post about a network stack handling this. I think this is it: https://lab.whitequark.org/notes/2016-12-13/abstracting-over-mutability-in-rust/ The technique they use is that the trait takes a generic type, either mutable or immutable buffers. So if we're willing to assume that for a given implementation we'll either pass mutable or immutable, but never both, this is an approach we could take maybe. + * Phil: For clients, yes. But the underlying implementation needs to do both. + * Hudson: Could the lower-level implementation be generic too, so it doesn't care and always returns what you gave it? + * Phil: You'll have code replication for the two types though, right? + * Hudson: If you use both at compile time. But if an implementation only hashes either RAM or Flash, that should be fine. And you don't have to duplicate source code. + * Jett: If you need one instantiation that needs to manage hardware, there's a problem there. + * Hudson: Oh, I see what you mean. You can't monomorphize the lowest level object. + * Branden: Maybe I didn't understand, but why can't we use mut/immut? Just because there could be a bug? + * Phil: If you pass mut/immuts around, it's pretty easy for the underlying implementation to mix up the buffers because they all look the same. In contrast, with the parallel tracks, you quickly realize when there's an issue. So it's easier for implementers to write bugs, which is why we avoided it. And we found the parallel tracks didn't greatly increase code size, so they were a better solution. + * Branden: That makes sense. + * Jett: We could treat slicing and mutability as one problem that we have one solution for. We could have a "passable" buffer we send in, and make it so that it can't be changed and guarantees to be the same thing you gave. + * Phil: But couldn't it send you a different "passable buffer" back? Leasable buffer gets at this. + * Jett: It could mix up its clients, yeah. + * Phil: With process loading, it's synchronous right now and it has slices and splits them, but when you pop back on the stack you get the original thing back. You slice RAM and Flash for processes. When this becomes asynchronous, this gets trickier. Right now you back out after allocation, but for asynch you can't split the slices until you're SURE you want them, because you can't undo it. + * Phil: Let me explain again. With process loading, you have a big chunk of RAM. Loading processes carves pieces off of that slice. So it might give 8 KB to a process and hang on to 32 kB for the rest, if it starts with 40. If it fails, you can just pop out of the function, and you're back to code that still holds the 40 KB. If it's asynchronous and split into 8 and 32, there's no way to put them back together, because there's no way to return to the point where we had 40 kB. + * Branden: And there's no way in Rust to recombine? + * Phil: There are crates, but with lots of unsafe. So we wanted to avoid that. + * Phil: And for processes, we tear off chunks of _mutable_ RAM. So we can't undo the slice. + * Phil: So, I wanted to share to get people thinking about the idea in case anyone has an interesting solution. + * Phil: For now, I'm going to split Digest into parallel tracks for immutable and mutable. + * Jett: And you said there's not that much code size overhead? + * Phil: No, it's a pretty thin wrapper. And the code to decide is something the client would need anyways. + * Jett: That's a good trade then to make the check happen at compile time. + + +## Kernel Releases + * Jett: What's the release schedule? When would we release 2.1? + * Hudson: In the pre-2.0 days, it was whenever someone got motivated for a new release. We didn't have a particularly regular schedule. + * Pat: We tried time-based releases, but it didn't work so well given that Tock moves in bursts. So instead whenever, we decide to do a release we do. So you could push for one, if you want one. + * Jett: We're not in a rush. But also the longer we wait the more validation work it is. + * Pat: Here's the official release policy: https://github.com/tock/tock/blob/master/doc/Maintenance.md#preparing-a-release + + +## Libtock-RS Releases + + * Alexandru: For libtock-rs, that will never be compatible with 2.0, right? It'll need 2.1 because allows don't work with 2.0. + * Johnathan: It should work fine for 2.0 + * Alexandru: It shouldn't work because we moved allows from capsules into the kernel. + * Johnathan: Oh, I see what you mean. Yeah, it won't be compatible. So we could probably consider it released as of 2.1 + * Hudson: That's as good a motivation as any. + * Johnathan: I do want to remove the 1.0 stuff at some point. Although I'm not sure when that will happen. Need agreement with others, including Alistair. + * Hudson: Right, at this point there's no development that's relevant to the 1.0 crates. + + diff --git a/doc/wg/core/notes/core-notes-2022-01-21.md b/doc/wg/core/notes/core-notes-2022-01-21.md new file mode 100644 index 0000000000..71705f79ea --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-01-21.md @@ -0,0 +1,92 @@ +# Tock Core Notes 2022-01-21 + +## Attendees + - Amit Levy + - Alexandru Radovici + - Brad Campbell + - Leon Schuermann + - Jett Rink + - Philip Levis + - Johnathan Van Why + - Hudson Ayers + - Vadim Sukhomlinov + - Pat Pannuto + - Branden Ghena + - Alyssa Haroldsen + + +## Updates +- JVW: libtock-rs. New script for running elf2tab and running binaries via QEMU + and tockloader. Useful for testing with 2.0. +- Alyssa: libtock-rs. Updates to build system for assembler and archive to work + with specific addressing requirements. + - Could we just use LLVM tools? + - Might be missing assembler. +- Ran into assembler bug with comments on move instructions. + + +## ufmt experience + +- Hudson: considered vendoring ufmt into libtock-rs to replace core formatting + libraries to reduce binary size and increase performance in some cases. +- Ported ti50 applications to use ufmt on libtock-rs. +- Experimented with using it in the kernel as well. +- Findings + - Doesn't support all of the same format specifiers. Supports only normal, + debug, and :#. + - No support for hex numbers. + - No support for many number formatting and padding tools. + - Difficult to write independent code that works with both core and ufmt. + Would have to limit to just the very limited interface of ufmt. + - For ti50 apps, saved ~13 kB! +- Now working on ufmt-extended that adds some important features, see what code + size effects are. +- Is this viable for apps? +- Looking to support most formatting for hex numbers. +- Seems like it could work for most apps. +- Issue: if library doesn't use it, might have to include both corefmt and ufmt. +- ufmt is not "pay for what you use", the entire binary size is added even if + you don't use certain features. +- If you use a feature that doesn't exist, does it fail at compile time? + - Yes. +- Any attempt to do the formatting in the kernel? + - Debug hard to do since the type is complex. + - For simple types, would be feasible, but would result in more syscalls. + - Could overlap with console syscall? + - Still might require many more syscalls. + - Might want to use formatting more generally. + - Would need to pass the structure of the thing to be printed. + - Macro implementation, how much could be done at macro invocation time? + - Not clear yet. + +## AppID + +- Phil: state machine in place for loading multiple processes with credentials and without. +- Dummy implementations for async checks (placeholders until RSA implementations exist). +- Three changes to process: + - New queue init task fn + - Two credentials functions: mark as pass, mark as fail +- What does toolchain look like for working with credentials? + - Brad: Adding to tockloader would be straightforward. + - elf2tab can add a header specifying the footer, or tockloader can insert that. +- Hudson: if someone does not want to use this, what do they pay for in code size? + - Goal is code is structured so all unused features are elided. + +## Process Slice + +- Jett: remove ProcessBuffer layer in exchange for a check on access that makes +sure no aliasing. +- Any invalid mutable access to a process slide would fail. +- Checks would be at runtime. +- Phil: we thought about this at allow time, not necessarily use time. Doing at + use time could lead to unusual failures in certain cases. Checking at + allow-time adds some overhead (100s of cycles). + +## Testing for Tock + +- Integration testing? Unit testing for capsules? +- Litex simulation in use. + https://github.com/tock/tock/blob/master/.github/workflows/litex_sim.yml +- QEMU in use. +- libtock-rs with 2.0 support coming. +- Hardware CI in progress. diff --git a/doc/wg/core/notes/core-notes-2022-01-28.md b/doc/wg/core/notes/core-notes-2022-01-28.md new file mode 100644 index 0000000000..e776fe12d0 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-01-28.md @@ -0,0 +1,146 @@ +# Tock Core Notes 2022-01-28 + +Attendees: +- Hudson Ayers +- Branden Ghena +- Philip Levis +- Leon Schuermann +- Pat Pannuto +- Vadim Sukhomlinov +- Johnathan Van Why +- Alyssa Haroldsen +- Brad Campbell +- Alexandru Radovici +- Amit Levy +- Jett Rink + +## Updates + +### ufmt library + * Hudson: On uFmt stuff. I've completely implemented width specifiers, so you can pad numbers with zeros or any type with spaces, at least on the right side of the type. Left spacing for non-numeric types would take a re-architecture. I've gotten the size overhead down to like 1.5 KB out of 13 KB for the applications I'm looking at. So now they only save 11.5 KB by changing to this uFmt solution. Still a pretty big win. + * Alyssa: Does ufmt support lowercase x or uppercase X or both? Would only one save a non-trivial amount of space? + * Hudson: It only supports lowercase, but I think uppercase would be a trivial cost. + +### Hardware CI + * Pat: Hardware CI pull request finally showed up. Seems to work in the base case so people should start taking a look. + * Phil: Now all we need is to duplicate on many systems. + * Hudson: What's the base case again? One platform (nRF52840) and some base tests (UART, GPIO, and not SPI). + +### Trustzone and Tock + * Phil: A student rotating with me has picked up the M33 code for Tock. So trustzone-m on the nRF53. Some interesting thoughts on how trustzone could better secure tock. Particularly against capsules that could abuse safety issues in libraries. So basically you put some part of code in trustzone so a malicious capsule can't corrupt, say, core crypto. + * Vadim: How do you determine which memory accesses are allowed and which are not? + * Phil: When it boots, the code in trustzone says which memory isn't accessible. + * Vadim: But it's not dynamic, right? + * Phil: It's decided at compile-time, but happens at run-time. + * Vadim: A long time ago I was working on sandboxing individual functions. I'm very interested in applying it to Tock. + * Phil: The whole port is up to Tock 2.0 now. And more work is on-going. + +### Libtock-RS 2.0 and Result + * Alyssa: I was proposing adding must-use to command return. It's easy for a command to return and not be used. + * Hudson: For background, this is similar to forcing results to be used. I support this. + * Phil: There are some narrow cases where you don't care. But those are rare and you can be explicit about ignoring + * Alyssa: I've got some code for turning command returns into actual results. + * Phil: I'd love to hear about that. One of the goals of the way the systemcall ABI was designed was towards compatibility with result. + * Johnathan: Oh! If I had known that, I would have had some comments on it. There are three cases now, counting "unintended result". + * Leon: We could always represent unintended result as a failure, but it's also possible that we didn't design it right based on what the userspace might need + * Johnathan: If it's a failure with a u32, what do you stuff in result? (Maybe just zeros) + * Leon: The idea was to collapse into an error when passing up to higher level code. + * Alyssa: The logic right now is generic over all success and error types. When you call into result, it checks if it's an unexpected variant, and it uses errorcode::fail if it needs to, filling in zeros for the other stuff. We could use the bad return value error code that's in TRD104 instead. It's not in the enum right now because the kernel can't return it, but the libtock-rs API might still want it. + * Phil: Agreed + * Leon: I think we expected it to be added to the enum for userspace. Just not in kernel-space. So we should update the userspace enum + * Johnathan: It might also be a separate enum that has that type, rather than the errorcode enum. We could return a different type instead. + * Alyssa: I'm not sure whether having the restricted enum in userspace is beneficial. Would it be easier to just have errorcode be a struct wrapper over u16. + * Johnathan: It would be easier. Just loses efficiency in some use cases, but we likely don't care. + * Alyssa: There's already one niche case with non-zero u16. + * Johnathan: And there are many special cases the way it handles it now. + * Phil: How would userspace respond to this anyways? If it returns a different type, you can't recover. + * Alyssa: Right, it means your library is out-of-step with the capsule (out of date or a bug) + * Johnathan: In a lot of cases, within the userspace API, I think it will be obvious what to do. But the generic command return path doesn't have anything obvious to do. + * Alyssa: I think the answer is going to be panic or propagate. + * Hudson: Propagate. If you app wants to be super reliable, then it can try to soldier on. + * Alyssa: And I want to avoid errorcode::fail, because it's unclear what it means + * Johnathan: Maybe add a new errorcode for it that handles "extreme failure" + * Phil: Libtock-c does propagate badrval. It's part of the enum there. + * Alyssa: Okay, I think I'm going to add it to libtock-rs + * Alyssa: Generally, I'm thinking about how to structure into result to save code size too. I'll experiment and see what works best. + +## Final review of App Completion Codes + * https://github.com/tock/tock/pull/2914 + * Hudson: I pushed a commit handling things from last time we talked about this. Want to make sure everyone is fine with the text as-is now. + * Vadim: How should the kernel handle codes that are nonzero. One use case is that the kernel may try to restart an application or may not. How should this be indicated what action it should take for this app? In most cases you would expect either restart or non-restart. + * Hudson: Right now in Tock, boards get to choose the restart policy. I wanted this doc to say it would be allowable for a board to choose to only restart apps with non-zero exit codes. But any board can choose any policy. + * Vadim: Would it be reasonable to have an error code that indicates that there was an unplanned panic in the application? + * Hudson: Currently, we report panics in libtock-rs via the low-level debug. C doesn't have an analog to that. TI50 has a custom error handler for apps. But libtock-rs still plans to support various panic handlers that apps can choose from. + * Phil: Two comments. 1) in the table say "reserved" rather than "not defined". 2) in section four, we should document the Termination trait here. Since this is a documentary TRD, section four, which says to use a trait, implies that this document should _also_ document that trait. + * Alyssa: I disagree. I think the trait is an example that implements the design described. + * Phil: So what happens if the source code changes and the trait goes away? + * Alyssa: It would be a broken link like any out-of-date documentation + * Hudson: Well, it wouldn't break. You linked to a specific blob of code. + * Phil: So, I think we should describe the trait here and the implementation. I think this doc should be self-contained, even if github stops existing in the future. + * Alyssa: I think there's strong value of connecting the design doc and a concrete implementation. I find it very helpful when trying to learn the code base. So, I think removing it is bad. + * Amit: Maybe what Phil is saying is that you can both keep the link and also import things from the link so the documentation is standalone. + * Phil: Yeah, exactly. Self-contained. + * Alyssa: Would this be a binding promise if we include it in TRD106 here? + * Phil: There's a process for writing a new doc and deprecating an old one. Things can change and evolve. + * Leon: I think this is a major concern though. It kind of seems like a big choice to say that the kernel must never do anything. So maybe we should say that if there's a contract between the kernel and the application, then it might apply semantic meaning to the completion codes. We definitely want to avoid misinterpretations of codes, because if there's no agreement by both parties than there should be no action. + * Phil: Generally what I was getting at was that it would be good to have the termination trait here. + * Alyssa: Okay, as long as we can have both the trait and the link, I'll do that. + * Alyssa: Going back, should we really do not defined instead of reserved? + * Phil: In my mind, reserved means that in the future we might specify. + * Leon: TRD104 says some areas are reserved and others will never be used. So maybe reserved has the wrong meaning here. + * Phil: Okay, I buy that. + * Brad: With return code zero being a MUST. What happens if a process doesn't do that? Why is it a MUST? + * Alyssa: My thought there is that any engineer who sees success printed by the kernel will be very confused why there are errors. Maybe the MUST is too strong of language, but I wanted to make it clear that returning error code zero on a panic is a bad choice. But it isn't breaking any invariants on the kernel, so maybe it should be a SHOULD. + * Leon: I like having a specific number for it. There is a much more contained space for success than the possibility of errors. The app could still make multiple other success variants on other numbers, if it wanted to distinguish. So zero would always mean that the application succeeded. + * Brad: That does make sense too. + * Phil: Okay, I think this doc is good as-is now. + + +## Implications of App Credentials + * Phil: One of the requirements of AppID is that two processes running at one time can't map to the same short ID. So an implication of that, is that two applications with the same Application ID (decided however you want) can't run at the same time. So this means that when we want to run a process, we have to check that no process has the same Application ID. Currently all the checks are in the process trait. So every implementation would need to handle this correctly. + * Phil: So, I added a function to the kernel that is "submit a process to run". It checks if it is valid, and then marks it as runnable. This state transition would require a capability which only the kernel would have. + * Phil: The way it's written, a ton of functionality is within process, but properties of processes for security reasons mean that some of that logic should move into the kernel. We don't want kernel guarantees reliant on specific process implementations. + * Brad: I think it's a great point. We've always relied on a little of this, because the kernel loop uses internal process state to determine which things it can run and when. We've always relied on those states being valid and not crashing the kernel. I would definitely be supportive of policy decisions being extracted outside of any implementation. + * Phil: It does mean more Capabilities. We also discussed in the past when to use a capability versus a trait. So we could have a trait that only the kernel has access to. The two things on process right now protected by capabilities are the method that says that the process integrity is verified (passes credential checks into the "checked state"), and then the second is whether a "check" process can be run. + * Brad: I think using traits with process just _doesn't_ work with rust the way we set things up. So capabilities might be our only hope, just for compilation reasons. + * Hudson: Yeah, we sure ran into a lot of issues when trying to do this for process printing. So long as we can keep generics off of the process type itself, we're okay. + * Amit: Phil, can you clarify more what the question we're asking is? + * Phil: I don't know that there is a question. I just wanted to let everyone know and check if anyone saw a major/minor issue with doing this. I wanted incremental updates so I didn't make a whole system and only at the end find out it's messed up. In this case, it's the refactoring of responsibilities between process and kernel and who can transition process states I wanted to check in on. + * Amit: In that case, this totally makes sense to me. + * Phil: Cool. I'll keep going, but people feel free to speak up if you realize an issue. I haven't fully done the short IDs part yet, but the system works in the sense that two processes with the same application ID returned by the application identifier trait, the kernel won't do it. + * Hudson: I'll second that this sounds good. What I see in the changes looks good so far, but I'll keep skimming as you go and will speak up if I see an issue. + +## ProcessBuffer & Raw Pointers + * Leon: I've been looking into how to change the ProcessSlice API into one that uses raw pointers underneath. Sounds terrifying due to lots of unsafe code, but appears to be the only sound solution for the model we chose. + * Leon: Here's a small writeup: https://gist.github.com/lschuermann/a51cbcf65f6315609361c0608452bd7e + * Leon: We currently sort of abuse slicing in rust in that we use a slice of cells. A ReadableProcessSlice is a transparent wrapper around a slice and not a slice reference. ReadableProcessByte is a wrapper around a cell, and that's where the unsafety is because a cell would allow mutating memory and we get into issues of pointer prevalence. This structure we've chosen provides the Index trait which must return a reference to something whenever we have an indexing operation using square brackets. When we switched it over to the struct illustrated in the link, we have an actual instance of the struct lying on the stack. This makes it fundamentally incompatible with implementing the Index trait in Rust because it requires returning a reference to something. So, this makes our APIs rather inconvenient. + * Leon: I've summarized what I think could be a solution in the "Proposed API Changes". We must implement three different methods because we can't overload them based on argument type. Slice, slice_from, slice_to, and finally an index method to get access to a ReadableProcessByte type, which is essentially the wrapper around a single byte we can access. That makes for a bit unwieldy of an API to be honest, and I'd rather not make these changes, were it not for soundness issues. + * Leon: It gets tricky when you think about panicking/non-panicking APIs. Previously we were able to just provide non-panicking APIs next to the indexing operations, which DO panic if you specify out-of-bounds indices. But when we try to have panicking and non-panicking APIs on the same struct we have a collision of the method names. One thought I had was whether it is a good idea to provide panicking APIs at all. I asked Brad since he has experience. The point I'm trying to get at is that if we provided only non-panicking APIs, we'd shift all the panics that could happen explicitly into capsule code. + * Leon: I have an example of what this looks like, which looks scary with all the unwraps. We might be able to optimize some of these cases where we essentially use the buffers shared with userspace as a configuration vector and index in at different offsets. A Macro could make these more elegant, but the macro_rules! are insufficient for implementing this. I could try to make this API nicer and look into procedural macros, but if that's still a no-go for us, then I'd rather not waste the time. + * Alyssa: Why not have it do the same thing as index, where .get() can take a range, range-from, usize by having a trait bound. Then if there's concern about overloading of the term, you can just change ProcessByte get into ProcessByte read. + * Leon: I hadn't though of that. We should be able to bind that method on a trait and implement it. + * Alyssa: That would simplify the API to look really similar to normal indexing. + * Leon: Still the same issues with regards to panicking. + * Alyssa: That's generally true. So get would be for the non-panicking API. In most cases where I'm dealing with it, I'm just going to put a question mark after each operation, so it's a little less unwieldy. + * Leon: I have an implementation of the raw pointers lying here, but I haven't ported to all capsules yet. I could try with only the non-panicking APIs and see how messy it gets. And then report back on whether we want to invest in this and use macros to make it more comfortable. + * Alyssa: It would make it really explicit where panicking can occur. So there is an advantage there. + * Leon: Yes. Traditionally we've always been a bit concerned about processes exposing arbitrary data of arbitrary length to the kernel. This has always been a friction point of implicit panics in the kernel because we assume buffer sizes. So the code might look scarier, but now it's explicit unwraps. + * Jett: I like the idea of only exposing the non-panicking versions. We don't want implicit panics based on what user code is sharing if we forget to check things. So we want that interaction really explicit and really checked. + * Alyssa: A lot of time it can save a significant amount of code size to use the question mark operator and then panic in the outer layer as well. From what I've seen. + * Leon: I'm just checking if people are generally open to this APIs, for now. + * Alyssa: I like it + * Hudson & Phil agree + * Phil: We do want to see the implications before making a decision + * Leon: The current capsules will be a bit ugly since I'm not refactoring. But new capsules it might be more elegant. + * Alyssa: Can you also make sure you're using non-null and not just a raw pointer. Then you'll be able to have the same niche behavior as references. There won't be any ABI changes. + * Leon: Makes sense + * Phil: If the direct translation is kind of ugly, we don't necessarily have to do it for every capsule. Some are less common than others. But core capsules should be made clean. Timer, UART, GPIO, etc. + * Leon: Do you want to do this in the step of introducing core APIs, or different PRs after the fact? + * Phil: We should at least do one right away as a demonstration. So we can make a good decision. + * Jett: We probably also want ok_or() rather than unwrap(). For general best practice + * Hudson: The main problem with some of the existing capsules is that you have lots of functions where the only operation is accessing a processbuffer, which is just a panic operation doing it the easy way in the past. Refactoring for returning a result and propagating is, I'm guessing, what Leon is referring to about a re-org. + * Leon: Right. I would like that more complex transform, but that's weeks of work. + * Jett: We probably want to return no-mem, if you try to access something you were expecting. Maybe the next step, if we're doing this is a function that returns a Tock result, so you don't have to do ok_or(), and you can have a function just for that. + * Leon: We've been trying to separate ABI results from internal tock kernel, because it would otherwise lead you to just always hand errors up to userspace. So an option would be a better solution. + * Jett: Sounds good to me too + diff --git a/doc/wg/core/notes/core-notes-2022-02-11.md b/doc/wg/core/notes/core-notes-2022-02-11.md new file mode 100644 index 0000000000..185bf82745 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-02-11.md @@ -0,0 +1,280 @@ +# Tock Core Notes 2022-02-11 + +## Attending + +- Hudson Ayers +- Brad Campbell +- Branden Ghena +- Philip Levis +- Amit Levy +- Alexandru Raduvici +- Jett Rink +- Leon Schuermann +- Vadim Sukhomlinov +- Johnathan Van Why + +## Updates: + +### libtock-rs Rust stable + +- Johnathan: using libtock-rs does not require any unstable features anymore. + + Migrated away from external assembly on RISC-V. On ARM, it generated + substantially different instructions from the external assembly. I'll create a + PR that does this transition and pass it over to someone more comfortable with + ARM assembly. + +- Hudson: I'll be happy to test it on one of my boards. + +### Tockloader / elf2tab issue with libtock-rs + +- Hudson: It'll also allow me to look into the Tockloader issue. Issue is with + the cargo runner that Johnathan built. + +- Alex: Yes, this is because the name of the TAB file is incorrect because it + names the TBF files not by the architecture but for the name of the app. + +- Johnathan: not sure whether elf2tab actually knows the CPU architecture. + +- Brad: Why does it not know the architecture name? + +- Alex: within the TAB file there used to be multiple TBF files with the name of + the architecture. Right now, the TAB file has only one TBF, which is named by + the app. + +- Amit: Why would it be the case that elf2tab does not know the architecture it + is translating for? + +- Johnathan: Architecture is something like "cortex-m0", is that encoded in the + ELF file? + +- Alex: It just names the TBF after the name of the ELF file. + +- Brad: Right, so if the ELF name is changed, it generates a different TAB. + +- Amit: Why not have the cargo runner name the ELF file the way we expect it to, + or rename the TBF file after running it through elf2tab. We can add a `-o` + argument for the output file name. + +- Brad: it needs this for `n` inputs. + +- Leon: maybe add a modifier for each of the input files. This would avoid us + having to rely on filenames. + +- Amit: Yes, add essentially two options for every input. + +- Leon: Right, I've built such an interface before, I can have a look at it. + +- Alex: I can also try to implement an interface that uses an architecture + specification through a comma-separated string. + +## Stable Rust progress and planning + +- Brad: relevant issue: + + There are developments in the Rust compiler which help us reach our goal to + target stable Rust. The `asm!` macro is now stable. There are extensions we + use which are not yet stabilized. Because they are related, there is a push to + get that done. + + Other one is `naked_functions` (at least a preliminary version will be + stabilized). + + Furthermore, `const_fn`, or more specifically `const_fn_trait_bound`, which + tock-registers uses. + + This is looking really good for our path to work on stable Rust. + +- Phil: could we work around the extensions to `asm!`? + +- Hudson: not sure about `asm_const!`. libtock-rs works around it by just + matching on the const and having a duplicate of that value in the assembly. + + Tricky part of this feature is that it is blocked by some other inline-const + features. + +- Leon: `const_fn_trait_bound` being stablized means that all required features + of `tock-registers` would then be stable. With the prospect of eventually + splitting this out into a separate repository, this would guarantee + compatibility with Rust nightlies and stable versions chosen by Tock. + +- Hudson: Great thing about `const_fn_trait_bound` is that Rust realized that + the lack of this feature inspired various workarounds made possible through + mistakes by the Rust developers. They decided to rather stabilize the feature, + than to provide backwards compatibility for people relying on the workarounds. + +- Brad: Okay, why bring this up now? If this is the turning point, and if we + want to go through with using stable in the short term, it does mean that + dealing with "we use this unstable thing because it's going to be some time + because we have to be on nightly anyways". Few things stand out: + + - intrinsics used by OpenTitan + + Amit: what are these used for? + + Brad: around atomic functions, atomic support is not provided by the ISA but + by LLVM somehow. Used by the deferred calls in the kernel. + + Amit: presumably the LLVM intrinsics have some implementation for + implementing the atomics with methods provided by the ISA and in principle + we ought to be able to implement them ourselves in raw assembly. + + Hudson: not sure that we can necessarily emulate LLVMs behavior using + assembly. A key part of the core intrinsics are going to be memory barriers + at the LLVM IR level. + + Amit: we can 100% do that. LLVM has to be able to know about raw memory + barriers. It is either going to be conservative or it has to look at the + assembly. + + Hudson: maybe it is possible then. I remember in the last discussion we were + talking about whether we actually need atomics for the deferred calls. + + Amit: right. Are we only using atomics because atomic semantics mean that + they are allowed to be in shared globals? LLVM might be potentially just + compiling that to regular atomic operations. + + Brad: first part, yes. Second part, not sure. + + Amit: if atomics are only used to have a cross-platform way of implementing + this, we can look at solving it with assembly. Only tricky thing is if we + were using this in the core kernel, shared across platforms. + + Phil: the comments say that this is for ARM. + + Brad: hard to say. Some of this code has been revived with OpenTitan was + added. + + - `custom_test_frameworks`, which we've experimented with for testing. + + Leon: can we hide this behind a feature / only when we actually run tests? + Might be reasonable to have a specific nightly just for the tests. + + Hudson: a little weird to test on a different compiler than we use to build + the release binaries. + + Leon: Agreed. However, these tests are really concerned with testing + semantic logic and implementations of capsules / algorithms across the + kernel. We'd still be able to do other tests and validation on stable. + + Brad: Right. It's not all of our testing, it's just a recent subset of it. + + Hudson: is this used in the LiteX tests? + + Leon: no, LiteX just runs a regular Tock kernel and interacts with it + through the console. + + Amit: is it the case that a stable compiler of Rust is essentially minted + from a nightly version? If that is the case, we could use the nightly + version corresponding to that stable version. + + Johnathan: not exactly going to have the same commit. + + Amit: seems like a reasonable trade off. There are additional tests which we + can't run on stable. It is not negating all the tests that we can run using + stable. This would give us more confidence in writing code, as we are not + taking advantage of behavior differing between stable and nightly + versions. A particular platform can always decide to use a different + compiler versions, if they decide the benefits of the tests outweigh the + differences between compilers. + + Leon: skimmed over Rust's release train model. We might be able to + automatically derive the nightly version from which a particular stable has + been branched off. There might be additional commits in there, but should be + a good start. + + - builing core library ourselves. + + Hudson: this is just a size optimization. Upstream boards could simply not + do this, although downstream boards can. + + Amit: Presumably, platform caring most about size optimizations is + OpenTitan? Is it also the case that OpenTitan cares most about using stable + Rust? + + Hudson: Likely, Ti50 will care more about size optimizations than + OpenTitan. Believe that it uses are custom managed toolchain, which is a + stable toolchain with some flags flipped. + + Amit: Downstream users don't necessarily care about upstream being stable, + but using a version of Rust which is reasonably close to stable to make + changes to. + + Johnathan: For OpenTitan, there is a good chance that Tock is going to be + built with bazel. Building the core library is a `cargo` flag, not a `rustc` + flag. In this case, this isn't even relevant to us. + + Amit: why does compiling `core` require a nightly `cargo`? Is this an + arbitrary choice? Does `core` use unstable features? + + Johnathan: might be that building the core library is not a popular feature. + + Brad: according to the `cargo` documentation, this is in the very early + stages of development. Perhaps they don't want to make promises about the + feature. + + +## Code review policies for libtock-rs + +- Johnathan: several months ago we introduced the temporary change in the Code + Review document for Tock 2.0. There is no hurry to get libtock-rs to Tock 2.0 + anymore, so we might want to revert that again. + + PRs are divided: + - upkeep pull requests + - significant pull requests: + + Defined as _every PR which adds a new API (e.g. new function on a struct) + that is public_. + + These have a full week of delay, which has been brought to me as a pain + point. + + I propose to make the distinction at how much churn and turn it would be to + revert a given change. If it is trival to take a PR out later, it's + upkeep. However, when it would require significant reengineering it's a + significant PR. + +- Phil: I would trust your judgement on this. If you think that something is + significant enough that is should wait for a week, that it'll wait for a week. + +- Amit: I agree with that. + +- Johnathan: You mentioned "you" a lot. A policy cannot privilege one of us over + anyone else, if only for the fact that I might not be available always. + +- Amit: we historically applied the same standards for this in Tock and + libtock-rs. In Tock, generally a PR is considered "significant" if it touches + security-critical code or works on major parts of the kernel or interfaces. + + It might be reasonable to choose a very different policy for libtock-rs, as it + is used in a very different way. + +- Johnathan: also, we might want to reduce the list of owners to the Tock Core + Working Group and Alistair. The others on this list have not contributed in + over a year. I feel like Hudson, Alistair and I have a good feeling as to when + we review things. + +- Amit: the list of contributors should be revised periodically to actually + reflect the people showing up, including valid reviews, etc. + + Another option would be to state that if e.g. two people of this group + approved a PR, then it does not have to wait any longer. + +- Hudson: two people seems to be the magic number for libtock-rs because of the + number of reviews on current PRs. + +- Amit: An important difference between Tock and libtock-rs: for a user of + libtock-rs it is entirely voluntary to update the library, versus for a kernel + which promises to offer a given API. Applications cannot generally choose the + kernel they are running on. + + It is much more reasonable to be liberal about breaking applications in + intermediate commits. Every release should be explicit about what parts of the + API have changed. + +- Johnathan: that is true. A different discussion is how to actually version the + libtock-rs crates. + + I am going to create a PR to shift the document to classify more PRs as + "upkeep". diff --git a/doc/wg/core/notes/core-notes-2022-02-18.md b/doc/wg/core/notes/core-notes-2022-02-18.md new file mode 100644 index 0000000000..a797034bc2 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-02-18.md @@ -0,0 +1,440 @@ +# Tock Core Notes 2022-02-18 + +Attendees: +- Alexandru Radovici +- Alyssa Haroldsen +- Amit Levy +- Brad Campbell +- Branden Ghena +- Johnathan Van Why +- Leon Schuermann +- Pat Pannuto +- Philip Levias +- Vadim Sukhomlinov + +## Updates + + * Brad: Just a quick update on stable Rust with Tock. There's not been a ton of + progress, but things are moving in the right direction. Some new + stabilization PRs have popped up, and the Rust procedure for getting approval + to merge at least one of them (the `const_fn` one). We remove the one use of + `option_result_contains`, so that's one feature we've removed. The major + limiting thing is `const` `mut` refs, which we might have to dig into a bit + more. Some progress, things are looking good from the Rust point of view. + * Amit: Seems like bit-by-bit progress but promising. + + +## `libtock-rs` API design + * Amit: If I understand the discussion topic correctly, the question is "what + should, generally speaking, userspace drivers -- libraries for `libtock-rs` + interacting with kernel drivers -- should their APIs generally look like?". + * Alexandru: I started writing userspace drivers for `libtock-rs` and faced a + dilemma on how to write them. I tried to get inspiration from `libtock-rs` 1 + but they can't be done exactly the same. There's no `async` in `libtock-rs` + 2. Basically, I could see two ways of doing this. Either write drivers as + super low level, mimicking what the capsule does. The second is to write + higher-level drivers which would be meaningful, and the best example is the + GPIO driver. We could either go with functions like `set_pin`, `clear_pin`, + `set_input`, `read_input`, or we could have a higher-level driver which + exports structures like `Pin`, `InputPin`, `OutputPin`. Or we could have a + mixture of the two -- low level drivers which mimic the capsules and build on + top of them optional libraries that use memory allocation and stuff that + requires more memory. + * Amit: Could you expand on the differenc between having a low and high level + API and having a low level API plus additional high-level APIs in presumably + separate crates. + * Alexandru: I'll take for instance the GPIO. What we could do for the GPIO is + like the LEDs -- simply offer static functions in the driver meaning `set` + which gets the pin number and `clear` which gets the pin number. For + interrupts, we register a single callback. Whenever there's an interrupt for + the pin, the same callback would be called always. The callback would have a + parameter for the number of the pin and the state of the pin. The + higher-level API would be to have a structure called `Pin`, from which we can + get an `InputPin` and an `OutputPin`. You can't read from an `OutputPin` as + the function would not be there. Whenever we want to set a callback, you + would set the callback on the pin, like `pin.on_interrupt(callback)`. That + would mean the high-level driver would need to register one single callback, + have a dynamically-allocated array of pins -- we don't know at compilation + time how many pins we have -- and send the event to those pins. It would + probably require memory allocation -- I'd be happy to talk about how to avoid + that -- and probably more memory. Some applications would need that, some + would not. If this is one single driver, then you can't opt out. If you split + them into two, the users would have a choice not to include this upper level. + The GPIO driver is written in a strange way right now -- it has some basic + functionlity (global callback) and some structures for pins. I don't like it + very much. + * Amit: Johnathan, it seems like you have one perspective on this, so maybe you + can share what you said in the comment. + * Johnathan: My view is kind of that when you design high-level APIs you end up + making tradeoffs, and making correct decisions there is essentially + impossible unless you understand the use case for those APIs. If we try to + build high-level APIs now without using the APIs as we're building them, + we're just going to end up with APIs that no-one uses because they don't fit + their use case. The low-level APIs will work for any use case the syscall API + will work for. Building high-level APIs now is doing a bunch of work that may + never get used. My take is to focus on low-level APIs now and if somebody + wants the high-level API for their application, they can design and + contribute that API. + * Amit: I was going to say it seems like one similar way to view this is "what + do we want the role of `libtock-rs` to be and what should be done elsewhere". + A reasonable model to me is how Rust divides the `core` library and the `std` + library. The `core` library contains the stuff -- it's not exactly 1:1 + because the `core` library doesn't contain system calls -- but nonetheless, + the `core` library contains the stuff that any Rust application needs. + Similarly, maybe `libtock-rs` should contain the bare-bones glue that + effectively should apply to any Rust application for Tock. The `std` library + for Rust contains not only mapping to drivers, but layers of abstraction like + the sockets API, which is higher level than the C socket API, some + portability, etc, at the expense of not being appropriate for all + applications. It's reasonable to have multiple standard libraries, like the + `async` ecosystem has a few alternative standard libraries. Maybe we don't + have to go as far as having a separate crate for each high-level driver. + Maybe it's reasonable to have a `libtock-rs-std` -- maybe let's not call it + that -- that is one point in the design space for how a Rust application + would be written against a common set of high-level drivers. In fact, maybe + that should focus on relative ease of use and good abstractions for common + applications + prototyping, and doesn't need to be concerned about absolutely + minimizing footprint or performance or whatever. Critical applications, like + OpenTitan, might not use it, but many of us would use it for applications + that are less sensitive. + * Johnathan: You talked about a division between a core part of `libtock-rs` + used by all applications, and a separate part where some of these APIs + belong. We already have a division there, where `libtock_platform` contains + stuff used by all `libtock-rs` applications, and GPIO and LEDs are in their + own crates. We already have a division there, but maybe the lines aren't + drawn in the right place. Like `libtock_platform` is suitable for all users + and these APIs could just all be high-level APIs, and anyone who needs a + low-level API could write their own. + * Amit: At least to me it does seem like it's worth having a division in + project too -- for example, the kind of scrutiny and process for merging PRs + for `libtock-rs` might have a different bar than a library that is focused on + providing usable abstractions. The same way that the bar for merging stuff + into the kernel is different than for merging stuff into userspace. + * Alexandru: For me it's not clear. Johnathan, you said the base would be the + platform and then everything else would be opt-in, but the platform does not + provide any drivers. It's just access to system calls. + * Johnathan: That's correct. It sounded like Amit didn't realize the drivers + were already in their own crate, so I wanted to point out that we do kind of + have a division there. That's not the same as low-level APIs versus + high-level APIs. + * Alexandru: For instance, for me I don't like how the GPIO crate is right now, + because it has some things that are more or less low level like the + interrupts and some things like pins that are high level. I'm seeing this as + a mixture and I'm not sure it's the right way to go. For LEDs it was clear, + set on/off, two functions on and off, and for buttons it was just read the + button and set callbacks. For GPIO it's more complicated, I'm not sure if + this is the right approach. It adds some code size -- very simple + applications may want to just use the system calls. Those structures like + `Pin`s add some code size. I'm not sure if it's significant, but it does add + some code size. + * Phil: Can you talk about how it mixes high level and low level stuff? You + can't have an input without pull up. + * Alexandru: Let's say you want to set a pin to `1`. The simplest way you could + do it is `Gpio::set(pin_number)`. If the pin is valid and it was previously + set to output, it would work, otherwise it would fail. If it is an input pin, + it's now allowed to be called like that. The way it is implemented now, you + have `Gpio::get_pin(pin_number)`, which returns a `Pin` structure that has no + useful methods, except for `make_input` and `make_output`. `make_output` will + give you back an `Output` structure, on which you can `set` and `clear`. + * Phil: So using the type system instead of runtime checks. + * Alexandru: Exactly, but it adds some code size. On the input, it's a + templated method with a generic, so depending on the data type you state on + the generic, you have to specify the type of pull you want. + * Phil: I understand the idea that what we'd like is a bunch of simple methods + to minimize code size, but I could also imagine cases where you want the type + checking rather than having to handle those runtime errors. The flip side is + that when you set the pin, you have to check the return value. I think both + cases are useful. For your particular case, A may be better than B, but for + others B is better than A. + * Alexandru: The problem I have is that due to this more complex API, the + driver has the simpler functions, but they're private, not public. In the + back, those structures will call the same function on the driver. My idea is + we only call the low-level one and provide the higher-level one in a separate + library, someone could build another library which is better suited for their + job. + * Leon: One thing to consider is that when you have these high-level APIs, one + can always make the argument for extending the functionality covered by these + APIs. For instance, when I say `make_output` but I have another process that + shares that PIN, I could make the argument that now the API also needs to + check whether the pin is still an output since I've created it. I think that + exposing these lower-level methods could be a good common ground to build + upon, where everyone can agree on a flat and clean API surface, and then + develop it in these different directions with regards to how much + functionality the high-level APIs actually cover. There's much more space for + exploration there. + * Alyssa: I don't like the whole high-level/low-level distinction, it's about + statically-checked versus unchecked. + * Phil: I agree, it's about where is the checking occurring. Is it occurring + within the library using types, or is it required by the caller. + * Alexandru: I think there is a problem that Leon exposed very well. We do the + type checking, and if there's one single app that uses the pin, it's find -- + kind of, because you can call `get_pin` twice. There's no way to stop that + without using allocation. At least I couldn't find a way, probably somebody + more experienced than me might find a way. The problem gets trickier once you + have two apps using the same pin. In one app you set it to an output and + start using it, and the other app might set it as an input and the first app + wouldn't know about that. Unless the GPIO driver allocates pins to the apps, + and then we'd need to modify the GPIO driver in the kernel. + * Amit: I lost the thread on where we stand in this discussion. One question + is, is there an objection to doing this stuff in a separate + library/crate/repository? + * Alyssa: I think we should push people towards the static-checking versions + while providing the unchecked versions. I think that only providing the + unchecked versions is not the way to go. + * Amit: Can you clarify, when you say the unchecked versions, is that the stuff + that's currently in `libtock-rs`, or the stuff that Alex is proposing? + * Alyssa: I believe the proposal, but I would need to see the transcript of + this conversation to be sure. + * Alexandru: The problem is users will always be able to directly call the + system call, so the low-level things will always be available. + * Alyssa: It should require `unsafe` to do so, does it not? + * Johnathan: No, `command` is a perfectly-safe API. + * Alexandru: I think the GPIO problem is deeper in the kernel because the + driver does not allocate pins to apps, but that's another discussion. My + vision was that we could build the low-level API which is not statically + checked, and advise users to use higher-level libraries which are statically + checked. If some user has some really constrained app, or wants to build a + library differently, they could. If we only provide the statically-checked + ones, the user will not be able to optimize their library, but will always be + able to use Command and go around the driver. + * Amit: To give another comparison, `libc` in Unix is offered as the + recommended way to interact with the kernel through a set of abstractions, + and sometimes people choose to go around that. Like the Go runtime doesn't + link against `libc` and interacts directly with system calls. Now again, the + division might be a little bit different, but to me it sounds reasonable to + say "the recommended way is to use this crate with safe abstractions, but + applications that are more constrained are free to use the core `libtock-rs` + interface with little abstraction". + * Alyssa: I would call it a "raw" interface to match with `RawSyscalls`. + * Leon: I agree with the Alex's sentiment because of another reason: we + currently have a perfectly-fine way to standardize the ABI between the kernel + and userspace at the system call layer. When we expose these raw APIs as part + of `libtock-rs`' public API surface, there is no ambiguity. + * Alyssa: I like exposing a raw interface but pushing people towards the + statically-checked one. + * Johnathan: I was initially opposed based on the high-level versus low-level + framing, but looking at it as statically-checked versus unchecked, I'm + totally fine with exposing a statically-checked interface, as long as it + doesn't require dynamic memory allocation. I do think that + dynamically-checked via dynamic memory allocation is a separate thing -- if + you expose that, you also need to expose an API that doesn't rely on dynamic + memory allocation. As for the low-level raw version, that could just be + defining the constants for the different Subscribe/Allow/Command numbers, and + telling the users "hey, if you don't want to use the high-level APIs, you got + to go read the syscall documentation". Ultimately, that sort of low-level API + ends up being a bunch of functions that just call Subscribe/Allow/Command + directly, and that ends up being pure boilerplate. Providing the constants + and saying "don't use these unless you really need them" seems like a pretty + reasonable way to go. It's not going to be much harder for the users. + * Alyssa: I think if we put it in a submodule called `raw` and make it clear + that these are the unchecked interfaces, and have functions that are really + thin wrappers over the syscalls, so you don't have to do a ton of research to + figure out how to do a raw output on a pin. + * Alexandru: I think the functions can be inlined there, it will be transparend + in the code size. + * Johnathan: I don't think we need to bother maintaining the functions. + * Alyssa: I think it would be good for us to provide somewhere in between the + statically-checked API and just doing syscalls. Something a little bit more + friendly. + * Alexandru: I see this as the difference between the standard C library and + POSIX. Of course, the standard C library works on several platforms, but if + you use the POSIX API you're not directly using system calls but it's very + close. If you use the standard library, it has more functions but takes more + space. + * Alyssa: I mean, unused functions in `libtock-rs` are basically free. + * Johnathan: Yeah, I think it should be possible to avoid code bloat. The main + thing is the statically-checked version will probably not support every + possible usage pattern -- it will probably be a bit over-restrictive. + * Alexandru: At least for pins, I cannot put an interrupt on a pin structure + unless I can allocate a vector of pins. If there is any other way of doing + this, and not having static things and `unsafe`, I would be really interested + to talk about it. + * Alyssa: I don't think that should be necessary. + * Johnathan: I take back what I said about extra cost. You can totally do that, + but it would involve like a linked list, which is going to be larger than the + raw API. + * Amit: I think this discussion should be another vote for doing this in a + separate library. Folks in this call have a history of designing good APIs. + One way to make progress on this sort of question is to go explore and maybe + design an API that makes sense for a particular kind of application, and when + there is some usable library -- maybe covers more than just GPIO and LEDs + because that's not necessarily the totality of drivers than an application + would use -- then we could have a look at it. We could then look it it and + tweak it to cover more applications or decide it is application-specific and + we need different abstractions for other applications. That would give us a + sense of whether we want an additional library or to tweak this one. + * Johnathan: That might be useful from a maintainership perspective. I've + wanted to review all the code in `libtock-rs`, but I honestly didn't want to + review the high-level APIs also. + * Alexandru: We can build an additional repository with libraries for + `libtock-rs` and push there. + * Johnathan: I don't think it should be an additional repository. + * Alyssa: Why not draft one under `apis/`? + * Alexandru: Okay + * Johnathan: We currently have one crate per API. That's probably an + unnecessary amount of division. We can probably combine all the low-level + interfaces into a single crate and all the high-level interfaces into a + different crate or something instead. + * Alyssa: As long as those don't require any statically-allocated memory. I + know that Console might require that. + * Johnathan: I honestly wouldn't expect that because it will not work well in + the unit test environment. + * Alyssa: Static memory? + * Johnathan: Yeah. A mutable static is going to be a challenge in the unit test + environment -- that's a thread-safety issue. An immutable static, either way, + reachability analysis will not compile it in if that API is not used. + * Alyssa: Hopefully. + * Johanthan: Actually, the bigger concern is HMAC or CTAP support, which + doesn't currently exist but exists in libtock 1, and depends on large + external crates. That's a big dependency tree thing. We could handle that + through the use of features, or perhaps that should be a separate crate. + * Amit: One reason I might advocate this being in a separate repository is it + would be a shame if Alex ends up blocking on discussions about particular + drivers while there's a design exploration process going on. If there ends up + being a whole discussion about whether this way of doing things is optimal + for every driver, or it might be the case that a few drivers work together a + bit better, then maybe it's not worth having that in the same workflow as + more mature `libtock-rs` work. + * Amit: Alex, was this discussion useful enough to move forward, or is there + still a remaining open question about how and what to do. + * Alexandru: It's not really clear how I should move forward. The next drivers + would be Alarm probably, and the simple drivers which are really easy to + implement, but I'm not sure how to implement them. For instance, for Alarm, I + could go with structures and something similar to `libtock-c`, where part of + the alarms are set in userspace. The driver only accepts one alarm per + process, and in `libtock-c` you can have several. Or simply go with a simple + raw interface as Alyssa said. I'm not really sure how to continue and I'm not + really sure if I should change the GPIO driver. + * Amit: I'm curious what other people who haven't chimed in think about this. + My sense is that if we want to move towards building applications in Rust by + default, then we ought to have a user-level library layer that is very + convenient for writing applications against. I think it's clear that + `libtock-rs`' primary goal should not be to do that, that we should have an + advocated-for but optional separate library for exposing a porcelain API that + prioritizes convenience and ease of development over performance or memory + efficiency. Obviously there's a tradeoff and we need to find a reasonable + design point, but yes. For alarms, I think it should be similar to + `libtock-c` -- it makes sense for apps to have multiple alarms, and managing + that on your own is tricky, and that logic shouldn't be in `libtock-rs`. I + think it should be in at least a separate crate or separate repository that + takes much more freedom than `libtock-rs` should about implementing + heavyweight abstractions. + * Johnathan: I don't think it should be a separate repository, as there are some + blurred things between there, like does dynamic memory allocation belong in + `libtock-rs` or does it belong in that crate? That has much more tie-in with + the runtime than it is optional in `libtock-rs`. I'm not sure if a separate + repository is right, a separate crate seems good thought. + * Alyssa: From the Ti50 perspective side, if `libtock-rs` doesn't implement + something that makes sense, we'll implement our own, and I'd rather not + duplicate that work. + * Amit: It's quite likely that Ti50 will have different requirements of a + runtime library than non-Ti50 applications. + * Alyssa: I suppose, but I think designing for alloc-less first is a good idea. + Have that be the default, and have some nice-to-haves that require + allocation. + * Alexandru: In the pin library, my thought was to add allocation as a feature, + so if you don't use allocation you can't set interrupts on the pin structure. + If you use allocation, you can set interrupts directly on the pin structure. + * Alyssa: I feel like a lot of the time, `Box` can be replaced by a static + reference. + * Alexandru: If you have time we can chat about that, I would be interested. If + you have any idea how to do that without allocation, you have more experience + than me in Rust, I would be happy to talk to you about that. + * Amit: I suspect that at high level the answer is the library doesn't know how + many timers the application will need but the application will, so the + application can statically allocate a structure for storing timers and pass + that to the library. Then you don't need to dynamically allocate on the heap, + you have the application allocate statically and pass to the library. + * Alyssa: Just have it all be borrowed. + * Alexandru: I see what you mean. + * Amit: Leon, Brandon, Pat, Brad, and Vadim, and Phil to some degree -- Phil + talked a little bit -- thoughts on this? What's the library that you would + want to use? + * Phil: One thing I'm worried about is the type checking that's provided by the + higher-level library is only valid in the context of kernel drivers that + provide exclusive access. Like I might get an output pin struct, but if + another process makes it an input it's no longer valid. We can't necessarily + avoid runtime checks within the application. That suggests to me that the raw + interface is what we want, but doing something that leverages Rust's type + checking might require changes to the syscall API. That's a bigger can of + worms. + * Leon: I'm personally not fundamentally convinced that the static type + checking approach will work, but I agree with the general sentiment of having + the raw API. I think that is important to establish as a ground truth from + which you build higher abstractions. What Alyssa said about building from + something that does not require allocation is a good idea, and it plays + nicely with whath Rust's `core` and `std` libraries do, and the division + seems very natural to me. + * Vadim: Yeah. I would say I prefer to have both raw interface and type-checked + interface and let basically developers choose and see how it works, what are + the use cases for the raw interface, maybe we can generalize. I usually like + don't want to force people to do some specific way, so I prefer to have both + ways of doing that and see adoption dynamics and see what new ideas may pop + up. + * Amit: My summary here is that since Alex, you are kind of the one doing the + work for these drivers and hopefully you have some use in mind, I know you + have applications you are building, essentially it is up to you and the rest + of us can choose -- I suppose -- to offer feedback and use it or not. + * Alexandru: My take of this would be that LEDs and Buttons are written exactly + like that. I would modify the GPIO interface to have a fully-raw interface + and split the libraries somehow out. Johnathan, do you prefer it in some + place in `libtock-rs`? Are the APIs library, or how do you see these being + organized? + * Johnathan: No strong opinions, other than that I don't want two crates for + every syscall API. I would personally prefer to merge the raw drivers + together and all the high-level -- well, I don't know. + * Amit: I generally agree with that, for what it's worth. We don't need a + million different crates, we need crates that offer different entry points. + * Alexandru: Do you want me to rearrange the raw interfaces into one crate and + submit a PR for that? Maybe we can merge the button PR which seems to be + ready, and then I can rebase the GPIO PR and split them. + * Johnathan: I think merging this into a raw crate -- I'm just questioning, + maybe this belongs in `libtock_platform`. + * Alyssa: Maybe the raw APIs should be a submodule of the higher-level APIs + that currently exist. Like there's the `apis/leds` crate and maybe there + should be a `apis/leds::raw` module. + * Alexandru: I understand. So basicallly if you don't use the higher level, + then you don't pay the cost of it, it won't be compiled into your app, so you + can always use the raw ones. + * Alyssa: Yes, I believe so. + * Alexandru: But it would be something use `apis/leds` raw. + * Alyssa: I guess it's up to the group whether you want a single raw crate or + have a raw module in every API crate. + * Amit: I personally prefer a separate raw crate or in `libtock_platform` or + something. + * Leon: If we determine that we want to have single crates for each syscall + driver, then this takes me back to the question I have a few minutes ago + about feature flags. Would the feature flags be disabling an entire module, + or is that going to just disable a few functions? I think feature flags are + the wrong tool to disable a few functions. + * Alexandru: Probably a few functions, but it was just an idea. I'm not if you + want to use that. + * Leon: I'm scared that we will have -- depending on the combination of feature + flags that are enabled -- we will have a very incoherent API. + * Alexandru: I was referring to a single feature, alloc, if allocation is + available or not. + * Alyssa: That's entirely reasonable. + * Leon: I understood that you'd essentially have two ways to achieve one goal + rather than two APIs. I was worried you'd have multiple functions to do the + same thing, one richer than the other, and then we have to deal with the + implications of people using two functions interchangably. Whereas when we + have two very separate and encapsulated APIs, where one is the raw one and + one is the more rich one which perhaps uses allocation, then we can think + about all the interactions of these calls to these APIs. + * Alexandru: I like Alyssa's ideas to have a raw submodule in the API because + that API will use that raw module. + * Alyssa: Yeah, exactly, + * Amit: Great, that sounds like some sort of way forward. In my mind, if that's + what Alex prefers, that's what we should do. It seems coherent. + * Alexandru: Okay. I can restructure the GPIO driver like that and send another + push. Maybe a different PR, just drop PR and people can actually state some + opinions on that. + * Amit: I think we're overtime. If I can offer some predictive advice about + this, I think it is worth leaving ourselves open to changing these sorts of + decisions once we get to more complex drivers APIs. I strongly suspect that + for example, without commenting about the specific choices so far, doing + alarm and user-level networking library, maybe LCD or screen stuff, will + elucidate more challenges. Those are more complex and involved APIs than + things like GPIO and LEDs. We should be open to revisiting decisions like + these once we get to those APIs. + * Alexandru: Yeah, makes sense. diff --git a/doc/wg/core/notes/core-notes-2022-02-25.md b/doc/wg/core/notes/core-notes-2022-02-25.md new file mode 100644 index 0000000000..e1455f85b6 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-02-25.md @@ -0,0 +1,58 @@ +# Tock Core Notes 2022-02-25 + +Attendees: + - Branden Ghena + - Amit Levy + - Leon Schuermann + - Johnathan Van Why + - Alexandru Radovici + - Pat Pannuto + - Vadim Sukhomlinov + - Jett Rink + - Brad Campbell + - Alyssa Haroldsen + + +## Updates + +### Stable Tock + * Johnathan: Rust update means that some assembly features are now stable + * Alyssa: What are the blockers for stable Tock? + * Brad: There are still some issues. There's an issue with them. https://github.com/tock/tock/issues/1654 Several have good signs, but unlikely to have changed in just a week or two. + * Brad: I did submit a PR to update to a new nightly. We should be able to move ahead and remove the ASM feature from Tock. + +### App Completion Codes + * Alyssa: Draft TRD for app completion codes. Should I apply the suggestions there and commit? + * Leon: Yes, I think that it's a good summary of the points we had in mind. + +### Process Slices + * Leon: Got the PR for transitioning process slices to raw pointers. https://github.com/tock/tock/pull/2977 Thanks to Alyssa for recognizing that we had unsound code. + * Leon: This PR introduces many unsafe blocks into sensitive areas of the kernel, and will require a lot of manual checking. Hopefully some automatic checking as well if I can get that working. + * Leon: I transitioned from square brackets to unwrap calls, so I want to get rid of the panics, where possible. + * Alyssa: That shouldn't change the panic behavior right? + * Leon: Yes. But because we needed to rework the interface anyways, that it might be a good idea to remove implicit panics, since it's a friction point in Tock. Indexing into untrusted data could make the kernel panic, so we want to avoid that altogether. + * Alyssa: Providing panicking functions and see the .unwrap() is best. But providing some kind of unwrap_or_else() would be good too. + * Leon: Currently we provide only non-panicking functions and use unwrap() in capsule code. + * Alyssa: I think that's the right thing to do. + * Leon: The other thing this PR does, and I couldn't not make this change, was that we had some safety invariants to adhere to, like maximum buffer sizes and non-null pointers. There's careful reading to make sure it's not messed up. + * Alyssa: Do you mention this in the PR? + * Leon: Yes, there should be info in the PR. + * Alyssa: Okay, I'll take a look and add comments. + * Alyssa: One thing I just noticed was that there are lots of explicit lifetimes, that I think aren't needed. + * Leon: We do want to not leak a buffer that outlives the memory. I'm aware that this is a choice, but I think that being explicit about lifetimes with annotations is useful. People may disagree and I'd be open to changes. + * Alyssa: I see. It can be subtle. Overall bounding lifetimes is good. + * Leon: So summary - take a look if you have the time. This interface really needs to be sound and correct + * Brad: When I looked at the console code in the PR, you think about what capsules are likely to do when sharing a buffer, it's mostly just copying bytes from userspace to a kernel buffer. Could we add helper functions that copy data from one buffer to another in a way that doesn't panic? + * Leon: We have that. We have the usual copy-from-slice and a copy-to-slice. The functions haven't been used very much, and have even been re-implemented with for loops, but the functions are there. + * Brad: Good to know. We should think about which cases should be changed over to that, which could get rid of the unwrap in places. + * Leon: Yes. I have a local commit with some of those changes. + * Brad: Cool + * Alyssa: So the question is whether we want to add more unsafe operations to remove the .unwraps, or hope that optimization will elide many of them? + * Leon: I would like to think that panics are avoidable, for instance on ARM they can be turned into conditional execution. We definitely want to remove panics and return proper errors instead. So we don't want to add more panics. + * Alyssa: Sorry, I wasn't proposing more panics. I was asking if an unchecked version should exist for cases where it's easy to prove that it won't panic. + * Leon: Well, an unchecked version would have to be unsafe, and unsafe isn't allowed in capsules. So I think practically, that would not be a useful feature. + * Alyssa: I do definitely support returning useful error codes instead of panicking. + * Leon: What we do now is return an option, which is converted into a result, which makes you think about what type of error should be returned and what the API is. + * Alyssa: I mean the functions calling .get, not .get itself. + * Leon: That makes sense, sure. + diff --git a/doc/wg/core/notes/core-notes-2022-03-04.md b/doc/wg/core/notes/core-notes-2022-03-04.md new file mode 100644 index 0000000000..65c28df938 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-03-04.md @@ -0,0 +1,298 @@ +# Tock Core Notes 2022-03-04 + +Attendees: +- Alyssa Haroldsen +- Brad Campbell +- Branden Ghena +- Hudson Ayers +- Johnathan Van Why +- Leon Schuermann +- Pat Pannuto +- Philip Levis +- Vadim Sukhomlinov + +## Updates + +* Hudson: Ti50 is trying to use a currently-unmerged LLVM patch that adds linker + relaxation for RISC-V. Saves about 7% on code size, over 26 kilobytes. + Suggests that not having relaxation for RISC-V is a major issue. +* Phil: What is linker relaxation? +* Hudson: It's something done by the linker that allows for collapsing certain + instructions based on addressing modes. Currently, if you look at RISC-V Tock + binaries, you see a lot of `auipc` instructions followed by `jalr`. That loads + an address and then jumps to it, rather than just jumping to it. One of the + optimizations that linker relaxation enables is replacing those two + instructions with a single direct jump instruction. +* Phil: So basically link time optimization when you can bound values to certain + ranges. +* Hudson: It was Vadim who initially noticed that a lot of these optimizations + were missing. +* Alyssa: I'm planning to open an issue about it, but I think I found an + unsoundness error in `register_bitfields!`. You can essentially create a + reference to MMIO memory, which is generally not a great thing to do. Or more + specifically, read from a reference. +* Hudson: Do we currently do this in the Tock kernel, or is it just possible? +* Alyssa: I haven't looked to see if we do this in existing Tock code. We + essentially need to enforce that a trait bound that a type is MMIO-safe. Ti50 + does have code -- it creates a buffer in MMIO space and right now we're doing + `.copy_from_slice()` which, from my understanding, is not guaranteed to work + because of the way volatile memory works with MMIO. +* Hudson: That's interesting, I look forward to seeing the issue. +* Phil: I've definitely seen weird things with MMIO on ARM architecture. +* Alyssa: The key thing is to use volatile reads and writes. +* Phil: Yes, nice catch. +* Johnathan: There is known unsoundness, because if I remember right, the + `tock_registers` crate creates a reference to a type that has the MMIO memory + inside of it and LLVM can insert arbitrary dereferences to it. +* Leon: That's exactly right. We do need to address this in the kernel, but I + would address it by dropping the Rust reference and using regular raw pointer + operations. If that is insufficient, I may need to rework the design I have + now. + +## `usize`-sized syscall return values (#2981) + +* Hudson: Does anyone else have any context about the want for using + `usize`-sized return values? Jett posted issue #2981 -- he wants to have + `FailureUsize` and `SuccessUsize` return variants. +* Alyssa: I can give a bit of background on this. Essentially, it is because of + host emulation, where `usize` is 64 bits. You can't really return pointers + from Command anymore. [Editor's note: Alyssa's last sentence was hard to hear] +* Hudson: Alyssa said that for Ti50's host emulation code, which is 64-bit code, + they want to return pointers in response to commands, but there's currently + not a `FailureUsize` of any sort that allows doing so. That makes it hard to + write code that works on both the 64-bit host emulation platform and a 32-bit + platform that returns pointers. +* Phil: I put this in a comment, but if it has to be 64-bit, we can find a way + around it, but it feels like the tail wagging the dog. We have an emulation + platform that needs 64 bits, therefore our operating needs to support 64 bits. + One challenge is there can be bugs that appear in a 32-bit setting that don't + appear in a 64-bit setting. Why not run the emulation in 32-bit mode? If that + ship has sailed then it has sailed, but that's one thing I didn't quite + understand. +* Johnathan: So Jett's question is about things returned from Command, not + Subscribe and Allow? +* Leon: Yes. For Subscribe and Allow we already took that into account. We + fundamentally have designed the syscall return enum to use proper pointer + types for those return values. I suppose that a part of Jett's question is how + they should in general be returned to userspace -- what a proper 64-bit ABI + would look like -- and the second is how that translates to Command return + values. +* Leon: To give some background on the current design, when we thought about it, + we acknowledge the fact that we would need to transfer pointers as part of + Subscribe and Allow, but for Command we decided that we really want to fix the + size of parameters passed to Command and return values from Command to make + syscall drivers portable. As soon as you add variable-width returns or + parameters, you start having issues where you write a driver on a 64-bit + system and it doesn't work on a 32-bit system. I think that this discussion + should still be interesting because fundamentally -- I suppose -- in the + future we would be interested in having a 64-bit ABI. How to integrate that + into the concept of a driver that fundamentally only has 32-bit values for + Command could be very interesting. +* Alyssa: I think the easiest way to do this is to have the `usize` variant + always be the `u64` variant, and on 32-bit systems just drop the top half. +* Leon: I'd be strongly against that. It feels like a hack, and you still have + issues where this could affect portability. +* Alyssa: Having only 32-bit responses is directly affecting portability right + now because we cannot run on 64-bit systems. +* Leon: Maybe the better term should have been consistency. We want to have + consistency in what can be returned on different platforms through the driver + trait. +* Phil: Why not run 32-bit emulation? +* Alyssa: I would like to be able to run it on Linux native without a dependency + on 32-bit runtime libraries. +* Phil: It seems a little weird -- Tock was designed for 32-bit systems, and to + change the kernel architecture to be able to emulate it on 64-bit processors + seems weird. +* Alyssa: Was it designed from the beginning to be 32-bit? I wasn't aware of + this. +* Johnathan: Definitely yes +* Phil: Yeah. That doesn't mean we couldn't -- someday we may want to run this + on 64-bit platforms as well, but that will involve a new ABI like Linux has a + different ABI on 64-bit systems. Those are different ABIs, and trying to make + them transparent is a way to run into tricky edge cases. +* Leon: If we were to want to support a physical chip with a 64-bit RISC-V core, + would that be more motivation to work on this issue? +* Phil: I think so, especially because whatever we do we want to ensure that we + can just run on 64-bit platforms. That means we need to figure out a 64-bit + ABI. +* Johnathan: I will point out that `libtock-rs` has encountered a similar issue + which I resolved without noticing it. `libtock-rs`' unit test environment is + portable and runs on 64-bit systems. The advantage we have is number 1, + Command doesn't return pointers -- simply don't support that in `libtock-rs`. + The other syscall types use like Success with u32, consistent with TRD 104, + and shove pointer-sized values into the registers anyway. It ends up all kind + of working because `libtock_unittest` and `libtock_platform` are part of the + same project and have their own ABI between them. The ABI is identical to TRD + 104 in the 32-bit case but a little bit different in the 64-bit case. I didn't + even recognize that when I wrote it. +* Phil: Does RISC-V say anything about memory maps in its specification? If you + never have more than 4 GB on your embedded system, then would that ever be + more than 32 bits? On an emulated system that makes sense, as sure you can + have more than 4 GB. +* Leon: I think once you start having a 64-bit RISC-V architectures, then your + pointers ought to be 64 bits wide. I think that RISC-V does not make any + guarantees regarding physical memory maps, as fundamentally RISC-V is an + instruction set, not a microarchitecture. +* Phil: The ARM ISA says things about memory maps. +* Phil: If this is the beginning of the push to support 64 bits, then we should + just support a full 64 bit ABI. +* Johnathan: Something that's been bothering me with this whole conversation is + the potential miscommunication. My understanding is the Tock core developers + -- even before I joined -- none of you wanted pointers to be returned from + Command, and Ti50 is returning pointers from Command. I feel like there was a + documentation weakness or a lack of communication that lead to that design. + It's nagging at me because that feels like the root cause -- they wrote a + bunch of code based on an expectation that disagreed with our expectations in + an undocumented contract. I'm not really sure what to do with that observation + but I think it's sort of the fundamental issue here. +* Leon: I think returning pointers is actually very reasonable. For instance, if + you have a shared buffer between the kernel and userspace and you want to + point to arbitrary data in the buffer. You would like to use actual + pointer-sized values for the indexing operations and not rely on the fact the + buffer is limited to 32 bits. +* Phil: I disagree, the TRD is quite clear. It says "Command calls should never + pass pointers". It doesn't say anything about return values, so we should + check what the return value specification says, but it's very clear you should + never pass pointers. +* Leon: That's very interesting, because how would you implement a structure + like a ring buffer shared between userspace and the kernel? +* Phil: You don't then. +* Alyssa: You would need to use indexes rather than pointers. I've suggested we + do that right now in host emulation -- use a 32-bit offset from a base + address, but that's been difficult. I wish Krzysztof were here right now + because he could better explain the use cases. +* Hudson: Hopefully Jett or Krzysztof or someone will chime in with more details + on the use case in the issue that already exists. +* Johnathan: We may want to shelve this until next week, as we're having a + conversation about an unexpected use case and the people with that use case + aren't here. +* Hudson: I don't think that's the worst idea. +* Phil: We had some discussion, but obviously we should not reach any + conclusion here because we want to hear from the folks are encountering this + and understand the issues in play. The writeup in the TRD says you never pass + pointers, in retrospect it probably should've said you don't return pointers, + but it doesn't. We can talk through that and sort that out. +* Johnathan: Yeah, part of this is that I think the problematic code predated + Tock 2.0. +* Johnathan: Oh, next Friday is a company-wide holiday, so it might be shelved + for two weeks. +* Hudson: We can continue discussion on the issue. + +[Editor's note: Phil had to leave the meeting at this point] + +## Updating the Rust toolchain + +* Brad: Can we merge the update to the new nightly? +* Hudson: I was not clear on if we were going to wait for the + `const_fn_trait_bound` stuff to work itself out, but it's been long enough at + this point that I guess would should merge now. We can update again if we want + to. +* Brad: That PR becomes unmergable every two days, then it takes a few days for + someone to fix it, perpetually. +* Alyssa: Is there a major update on `const_fn_trait_bound`? +* Hudson: The update is they accidentally stabilized a workaround that allows + you to do it, and then everyone decided that instead of dealing with the fact + they stabilized a workaround, they'd go ahead and stabilized the feature. +* Johnathan: But they haven't merged the stabilization yet? +* Hudson: Correct +* Johnathan: I'm subscribed to the stabilization issue now. +* Alyssa: This is one of the only things our codebase actually depends on, so + I'm excited for us to be able to use in on stable. +* Hudson: It feels like we're really close to stable Rust, as there's also been + progress on naked functions and `asm_sym`. +* Alyssa: I think the only thing we'll end up depending on is a couple of + nightly-only intrinsics. +* Hudson: We've talked about getting rid of the intrinsics in the core kernel, + but haven't pulled the trigger yet. +* Brad: That const mut refs though, I don't know. +* Hudson: Where is that used again? +* Brad: That is used in a lot of the library code -- a lot of the cell code -- + and propagates through the capsules. +* Leon: We also used it when we had to use `const` functions in the chip crates, + I think for peripherals? +* Hudson: Yeah that sounds right. +* Brad: And that one there was like some progress, then there wasn't process, + and I think it could take another year. +* Alyssa: I couldn't even figure out how to implement it for my + `impl From> for const T`. +* Hudson: I `bors r+`'d the PR, Brad. +* Brad: Fantastic. +* Brad: I think the summary is that yes, there is optimism, but const mut refs + seems like it will be a real issue. +* Hudson: Yeah +* Alyssa: Can we have a separate initialization pass for `const` contexts that + doesn't use mut refs? +* Hudson: The problem is we have a lot of cells, like `OptionalCell`, and you + can contain a reference in an `OptionalCell` or a `TakeCell`. You may want to + initialize it as empty, and use a `const` constructor so you can initialize + something as a static mut global, then later the type still needs to be a + mutable reference as it is a global you may change it to a non-`None` value. I + believe it is less of an issue now because we got rid of a lot of the `static + mut` peripherals, but I'm guessing there's still a couple `static mut` things + that require `const` constructors and also contain + `OptionalCell`/`TakeCell`/`MapCell`, which therefore need to have `const` + constructors. It's maybe possible there's something we can do here, but I + don't think it will be very straightforward. + +## ProcessSlice raw pointers PR (#2977) +* Leon: Brad, you said you wanted to port over some more code, otherwise I would + spend the weekend looking at some capsules. How should we progress on that? +* Brad: I just looked at the first capsule, which is the app flash or something, + which looked like it had a straightforward "copy between two buffers using the + indexes" and I said "I can change that to use `.copy_from_slice()`". The + problem is that the Rust `.copy_from_slice()` API is that the slices must be + the same length, and in a lot of cases we don't want that. We want to copy as + much as we can, and then tell me how much you copied, and we'll iterate + anyways because it's event-driven. I tried to add a new API that will handle + all that internally for you. I got a little ways into that and got distracted + or something. I can share what I have or find some time today to try to get + that working, but that's as far as I got. +* Leon: I think the usual approach is to calculate the minimum of the two slices + lengths, and use that for subslicing, then use `.copy_from_slice()` or + `.copy_to_slice()`. I've also been using Rust's iterators, which are + implemented by the ProcessSlice types, which you can combine using `zip()`. + That may be an option to avoid re-implementing the API. +* Brad: I'm remembering why I got stuck. If you don't want to use the `[]` + operator, then you have to use `Result`. +* Leon: If we manage to use `zip`, then it's automatically bounded to the + minimum of the length of the two slices lengths. +* Brad: Great, that sounds like that is encapsulating that sort of + `unsafe get_unchecked`, which would be great. If you've got a prototype of + that or something, that sounds great. +* Leon: I'm assuming it works, I'm not sure if it works on two different types + of iterators, so I'll try. + +## App completion codes (#2914) +* Brad: Were there any outstanding issues on app completion codes? +* Alyssa: I thought that was merged after an adjustment was made. Did it not? + Otherwise I can get it merged. +* Brad: It's not merged. +* Alyssa: I don't think there's anything else. I'm okay with the new language, + and it sounds like everybody else is. +* Hudson: It seems like we should merge that. +* Brad: I'll do that. +* Brad: We can always edit it. +* Alyssa: I did have one question about the wording. It says that the + specifications and exceptions must be written in a TRD. Can people just write + TRDs and not share them publicly, as long as they stay on a specific version + that works for them? Can Ti50, if we say we're going to stick on a particular + Tock version can we write our own TRD that says the kernel can assume semantic + meaning for completions because we wrote our own specification for our + application and our own branch of the kernel. Would that be violating this TRD + to do that, out of curiosity? +* Leon: As far as I interpret it, I think the TRDs only mandate things for + upstream changes, so what you do downstream is not necessarily compatible with + how Tock development continues. I don't think TRDs are meant to limit what + downstream users can do. I'm not an authority on this, though. +* Brad: I think this would be a question for Phil. +* Alyssa: It seem Phil has the strongest opinions for the legal language here. +* Brad: I would concur. +* Pat: And just the most experienced, but I do agree with Leon's assessment. +* Alyssa: It's basically contract law. + +## Storing kernel-managed grant values in one `usize` (#2958) +* Hudson: I'm planning to come back and think through some of those soundness + issues after a conference deadline. +* Brad: Sounds good. I would like to try to measure it -- to measure what the + size change looks like. As long as there's no other major updates to `Grant` + in the meantime it should be okay. diff --git a/doc/wg/core/notes/core-notes-2022-03-11.md b/doc/wg/core/notes/core-notes-2022-03-11.md new file mode 100644 index 0000000000..c541fb07a0 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-03-11.md @@ -0,0 +1,33 @@ +# Tock Core Notes 2022-03-11 + +## Attending + +- Hudson Ayers +- Brad Campbell +- Branden Ghena +- Philip Levis +- Leon Schuermann +- Vadim Sukhomlinov +- Johnathan Van Why +- Alexandru Radovici +- Pat Pannuto + +## Updates: + * Hudson: Missing a few key people for the active discussions, so just updates for today. + +### libtock-rs + * Johnathan: New contributor to libtock-rs! Someone who stumbled across it. Good time for us to notice weaknesses in documentation and improve + +### Stable Tock + * Brad: Another new feature was stabilized, so there should be a nightly soon that allows us to remove that feature. The feature was const-function-trait-bound. + * Leon: This should also let us move Tock-registers to stable. There were some people who are using it who want it to be on stable. So we should probably do a sub-release to add that. + * Hudson: My paper deadline got moved back a week, so I had to delay my work on that. + * (General digression about how English doesn't make sense) + +### Tock publication + * Hudson: An old writeup about Tock that we revived just got accepted at EuroSec, a workshop at Eurosys. So I'll be presenting that there + +### General Tock PR State + * Brad: Probably time to look at Pull Requests. We've been over two pages for a while now, which isn't good for a project of our size. No one ever looks at the second page and we don't want things to linger. We should either move forward or decline and maybe move to an issue. + * Brad: No immediate action is needed, but generally take a look with a mind towards cleaning things up + diff --git a/doc/wg/core/notes/core-notes-2022-03-18.md b/doc/wg/core/notes/core-notes-2022-03-18.md new file mode 100644 index 0000000000..7ea4c94c7f --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-03-18.md @@ -0,0 +1,353 @@ +# Tock Core Notes 2022-03-18 + +Attendees: +- Alyssa Haroldsen +- Amit Levy +- Brad Campbell +- Branden Ghena +- Hudson Ayers +- Jett Rink +- Johnathan Van Why +- Pat Pannuto +- Vadim Sukhomlinov + +## Unscheduled chat about RLS/`rust-analyzer` + +* Brad links [Rust's nightly component + history](https://rust-lang.github.io/rustup-components-history/) in chat. +* Hudson: Brad, that's an interesting chart there. I'm surprised there has been + no RLS support since February 23rd. I wonder if it was officially deprecated + in favor of `rust-analyzer`? Is `rust-analyzer` the official option? I'm not + sure. +* Brad: That's what makes keeping track of this so hard. +* Johnathan: I see a `rust-analyzer-preview` which suggests that it is not yet + the official option, but I can see motivation for maintaining RLS drop over + time. +* Alyssa: Has anyone figured out how to make `rust-analyzer` descriptors for + non-`cargo` projects? Can I run Clippy using the same config? That's what I + want. +* Hudson: I've definitely never used `rust-analyzer` on a non-`cargo` project. + +## Updates (which became a discussion about #2958) + +* Hudson: I looked back at #2958 (compressed grant resource counters). My issue + with the original PR is not that it did anything unsound but it exposes + functions that if called in different places could create unsoundness. I'm + trying to figure out what to mark `unsafe` to regain the safety invariants + that Rust needs. Will hopefully have a commit out today. +* Brad: I'm curious to see what you will come up with. I don't think I agree + that the current approach distributes this unsafety in a lot of places because + we already have this guarantee that you can only enter a grant once. That + provides a lot of uniqueness properties, and we're already checking it so we + can leverage it. My changes centralize all of the tricky work that if you did + it wrong would be very bad. It makes it less distributed across the `grant.rs` + file, and the APIs are easier to use. That doesn't mean there isn't a + different way to achieve those same goals. +* Hudson: I think the current state still has issues where there are a lot of + places where we construct the `KernelManagedLayout` type that don't require + entering the grant before constructing it. For instance the subscribe, allow + RO, and alow RO functions that are called by the kernel, you don't have to + enter the grant to call those. +* Brad: No, but those enter the grant. +* Hudson: Yes, but right now it's possible to rewrite the + kernel without using `unsafe` to both enter the grant and call one of those + functions. +* Brad: Yeah, that's fine. +* Hudson: We don't currently do that, but I could call Subscribe from within an + entered grant and create unsoundness before the panic would occur. +* Brad: How would you do that? +* Hudson: I would pass a closure to some call to `Grant::enter` from within the + kernel and call the grant subscribe method from that closure. +* Brad: So if you've entered the grant, and you've called subscribe on an upcall + that is in that same grant, then when the subscribe function tries to run +* Hudson: I'm calling the subscribe method defined in `grant.rs`, not the + subscribe method on `Upcall`. +* Brad: Correct. I'm not sure how easy that is to get -- I guess I could pull up + the link. The first thing that subscribe function has to do is to enter the + grant. +* Hudson: Does it not have to create the kernel-managed layout first? I thought + it did. +* Brad: No, because it doesn't know the pointer to use until it has entered the + grant. +* Hudson: Maybe I misread this and what you have now is fine then. I'll look + back through that. + +## Pointer-width syscalls (#2981) + +* Jett: I came across this while implementing host emulation stuff which runs on + 64-bit Linux. It's not upstreamed, but we have a custom kernel-owned buffer + that is passed back to applications which uses pointers. We ran into a problem + where we are trying to return a pointer from Command and we don't want to have + to fork in our code and return a different success type on 32-bit and 64-bit + architectures. That made me think "what is the plan for a 64-bit ABI", in + general, as there is 64-bit RISC-V variant. We could add a "success with + pointer" return type which would give us what we need without having to add + `usize` to the ABI. It would only be for a pointer so it could be + architecture-independent. Would keep the property that each syscall returns + one success variant. +* Hudson: The person who most strongly pushed back against this is Phil, who is + not here. I suppose you probably saw his comment on the issue about having a + separate 64-bit ABI. That doesn't solve the issue of writing capsules that + want to pass pointers, because then they would need to switch depending on the + ABI. +* Jett: Exactly. It's weird to write capsule code that is chip-independent -- + therefore architecture-independent -- that has to fork based on what it is + compiled against. Subscribe already returns pointer-sized values. I think + there should be something for Command which gives the ability to pass back a + pointer. I think we should make that first class. +* Hudson: The issue is the 2.0 syscall TRD says that Command should never pass + pointers, so we're discussing changing that wording. +* Jett: Editing that text and allowing this thing solves that 32-bit/64-bit ABI + thing. +* Amit: What again is the expected scenario where we would have a 64-bit + architecture. +* Jett: We have host emulation, which uses the kernel and applications and runs + on a Linux system. I believe in the tree there is a 64-bit RISC-V port -- not + fully fleshed out, but it exists. +* Johnathan: Is the question "what is host emulation?" I'm not sure we've ever + really explained that. It is spinning up a Tock kernel in a standard Linux + process and spinning up Tock processes as separate Linux processes and making + them communicate and pass syscalls between each other as a way to simulate a + Tock system. +* Alyssa: Essentially a "Linux HIL". +* Amit: And this is for testing? +* Jett: Yes, this is for testing in our CI. Running tests in our application + with our kernel and make sure it works without deploying to hardware. +* Amit: To channel my sense of Phil's pushback, do we want to make architectural + changes to the system to make testing more convenient? +* Jett: I think this testing is bringing up an issue that would exist in the + future with 64-bit RISC-V or something else. +* Amit: I think there's skepticism from Phil and others on this call that we + will realistically see 64-bit embedded chips. +* Hudson: It seems very possible that we'll see 64-bit embedded chip, but it + doesn't seem like Tock will be a good fit. +* Pat: We fundamentally wrote a 32-bit syscall ABI, and to support a 64-bit + platform then we should write a 64-bit ABI rather than shove 64 bits into our + 32-bit ABI. +* Jett: That makes sense, but it still doesn't solve the problem of capsules + having to know about the architecture. We can have a 64-bit ABI and I think + that makes sense, but we still run into the problem that there needs to be a + Success variant that is specifically called Success with Pointer or Success + with `usize` for it to have a corollary on both ABIs. +* Pat: Leon might know better because he worked with Phil a lot more on the + syscall ABI surface. I don't think we intended to be able to pass pointers + across the ABI, did we? +* Branden: Leon is not here. +* Brad: I think we need to separate these two issues. There's a 64-bit syscall + ABI, and there's transferring pointers. If we did have a 64-bit syscall ABI, + then all the widths would be 64 bits, and you could put a pointer in the + normal "success with u64" field. This probably really wouldn't have come up in + the same way, as it would be pretty easy to work around. It wouldn't be as + elegant, but it wouldn't be as much of a blocker. +* Jett: Suppose a 64-bit ABI exists. What does capsule code specify for a + success pointer? It has too choose between success with u32 and success with + u64 depending on the architecture. Agree that it is related, but even if we + have a 64-bit ABI it doesn't solve the problem of what a capsule should use + for a pointer-sized variant. +* Alyssa: I'm thinking the capsule could switch which variant it does based on + the size of usize. In kernelspace, when you ask for a `usize`, it treats it as + the variant that is pointer-sized for this architecture. Handle it at API + level rather than ABI level. +* Jett: That's how you'd have to do it now -- have to do some forking in your + capsule code or the success variant class has to do the forking based off + architecture as well. Is that actually breaking the "one success variant per + return" rule? +* Alyssa: Then it would be one success return variant per syscall per platform. +* Jett: Is forking logic the expected way, or do we want to have a unified path? +* Alyssa: Are you asking "should we have a `usize` variant"? +* Jett: Yes. +* Branden: Another question is should Command ever return pointers. If yes, then + we need a `usize` variant. Another workaround here -- and maybe it's not + possible because of the way the documentation is written -- but you could use + Subscribe to return a pointer, which already supports `usize`. +* Jett: I was having trouble making that work. +* Pat: Is this a kernel pointer that is being returned or a userspace pointer + that is being returned? +* Jett: It is a kernel pointer, into a kernel-owned buffer that is leased to the + application. +* Pat: I'm trying to make an analogue to Linux, where you get an index into a + kernel-owned data structure. Can you do the same thing and avoid leaking + kernel pointers? +* Jett: When the application needs to read it, it is reading from memory. On + Unix processes we open up a memory-mapped file and pass pointers back and + forth. This emulates how it happens on an embedded device. +* Branden: So the analogy Pat mentioned breaks because you don't execute a + system call to do the reads and writes. +* Pat: The fundamental issue is you're giving applications access to kernel + memory rather than the kernel access to application memory, which doesn't + match with Tock's original design where the applications own all memory they + access. +* Jett: Right, and the kernel fundamentally owns the memory, so it can detect if + an app dies and revoke it to reuse it. +* Amit: To ground this, are you doing this as a testing thing, or is there a + production-use argument for having userspace use kernel memory? +* Jett: We have to receive 1K-sized I2C messages, and they are passed to + multiple applications. If we were to have application-based buffers, each + application would have to have its own 1K buffer in RAM. I2C only sends and + processes one packet at a time, so we have the kernel own the buffer and + dispatch it to an app for processing. +* Amit: The most similar thing I can think of that exists in Tock is IPC. It's + kinda like IPC, except one of the services lives in the kernel. +* Jett: Yeah, I see your analogy and I agree there are similarities. +* Amit: I wonder if the right solution to this, rather than bending + Subscribe/Command/Allow or whatever, is to acknowledge that this is a + different kind of pattern. Maybe if we had a decent IPC mechanism, I would + recommend we would add this here. We don't, so maybe we should come up with + something separate. Sharing kernel memory via an additional API that is + specifically about sharing pointers (or a pointer + a length). Then Command + could return something like file descriptions -- e.g. return "I'm referring to + buffer 3" or something like that. +* Jett: To make sure I'm understanding: you're proposing creating a new class of + syscall that just deals with kernel-owned buffers. +* Amit: Yes. Not Command or Subscribe, something like "kernel allow". +* Alyssa: What about when we want processes to share memory with each other? +* Amit: That was the intent of the IPC capsule, which was trying to fit that + into Tock's existing APIs. Maybe we need to acknowledge that this is a + legitimate enough use case to have ABI support for it, and a set of system + calls that cleanly support sharing memory from the kernel with processes and + between processes. +* Jett: I think that is a good way to go about this to make it first-class. That + would also solve some problems -- there have been other PRs where we want to + add and remove MPU accesses that was hard to put into the kernel. +* Amit: The origin of not using `usize` for `CommandReturn` and other things is + that in the common case, Command is returning a value, not a pointer. If it's + a pointer-length value, then capsules or applications can't rely on the width + being something. It's harder to assume that e.g. the capsule will always + return something 32-bits, and know it won't be truncated by hardware. It's a + different enough use case that maybe it warrants a different system call API. + We want IPC and know IPC has reasonable use cases, and the existing IPC + mechanism sucks. We should probably treat IPC in a more specialized way than + glomming it onto the existing syscall API. +* Jett: Yeah, that seems like a reasonable way to go. I'm curious what other + people think -- if we were to write this up, would you support this idea? +* Hudson: I think it's worth noting that before Amit voiced this idea, Branden + put the same idea in the chat (which Amit can't see because of how he's signed + in to the call). +* Branden: I also think this is a reasonable idea. Maybe extending subscribe + will work, bet perhaps a new system call is the way to do that. I will + devils-advocate this for a second, and say that if extending Command is + sufficient for everything that is needed, then that is way easier. Just add a + new return variant. +* Jett: Yeah, so I agree we can solve this by adding one variant for pointers, + or we can add all of this architecture for this first-class concept and + dispatcher. +* Pat: I'm hesitant to say it's "just that simple", because it will have the + contract that the kernel is providing access for some time, and it's not clear + what that time is. I think tackling the memory sharing problem directly is a + more robust way to do it. +* Alyssa: We need to do thing that the base Tock OS doesn't support so we need + to add functionality. It would be nice to have something that's officially + offered by Tock, but for just the ability to pass pointers back and forth for + people who are hand-rolling their own IPC, I think it makes sense. +* Jett: Another way to say this is I don't think the solutions are mutually + exclusive. We can implement both. +* Branden: I think it may be worth doing the simple thing and playing around + with it for a while to see what it would take to have first-class support for + IPC. +* Amit: I'm generally empathetic to that kind of approach. It seems that we have + this use case that we want to do in the future and also it would be nice to + have something like this now so we could do something really hacking for + testing. We could make changes that we mark as experimental and have that be + concurrent with figuring out what we need to be more robust rather than + blocking progress on a months-long design project. +* Jett: I think having a pointer-sized Success variant can stand on its own. It + has its own use cases. It's not hacky. +* Hudson: I think the one thing that's a little strange about adding a success + pointer variant is there's no reason it's useful unless you have a capsule + that can directly manipulate the MPU, which most cannot. +* Jett: I don't think that's true. +* Amit: I could imagine a capsule that returns information about a process' + address space. +* Hudson: Yeah okay, makes sense. +* Jett: I think this is a nice piece of functionality that is well-defined that + we could add that would unblock us. I do like making it a first-class thing + where you have kernel-owned memory that can be shared with userspace, but it + would take months to get that working. We'd help design it and implement it, + then switch to using it. +* Amit: I'm convinced, are there other concerns? Does anyone want to try and + channel Phil? +* Hudson: Before Brad left, he pasted in the chat "I have to go, but: I think + this is generally an interesting idea and worth allowing Tock to explore. + However, the two leads on Tockv2 (Phil and Leon) are not on the call, and I + think we need their thoughts." I agree and am also personally convinced, but + we should ask them. Maybe we can send a PR and start the discussion with Phil + and Leon there. +* Pat: I think Phil would want a specification PR -- a PR of semantics against + TRD 104, which explains what guarantees are made around the pointers etc, + which is where a lot of the challenge and risks come from. +* Jett: I think there are no guarantees with the pointer -- it will be a driver + or capsule-specific guarantee on what the pointers are. +* Pat: That would be a place to start -- write down exactly that. Just say that + "this is what the syscall would do, here are guarantees it doesn't make" and + start the discussion with that. +* Amit: I would go farther, and say the changes to the TRD should say that this + variant is experimental, and capsules should not rely on it existing. +* Jett: I don't think it needs to be experimental as it is well-defined. +* Hudson: I think if we introduce a superior first-class approach in the future + then we'll want to get rid of this one. +* Jett: Yeah, but I think there are still scenarious where you could return a + pointer that isn't just dispatching a first class thing. +* Amit: Experimental doesn't mean we won't stabilize it. Experimental means we + don't need to convince Phil and Leon now that the API will be permanent. +* Jett: I would rather start with stable and negotiate down to experimental. +* Amit: I think that will make the PR review take longer, but that's okay. +* Alyssa: I think it's intended to mirror the Rust intention behind experimental + and stable, where you do things as experimental so you don't have to commit to + supporting all of the specifics for the forever future. +* Amit: There's a chicken-and-egg problem we need to solve. Have to answer the + question "what are the use cases", but can't answer that because it isn't in + place. Adding it as an experimental feature allows us (the royal us) to + demonstrate use cases. +* Jett: I'm okay with it being experimental. With the guarantees being "there + are no guarantees", then I don't think there is a legacy support burden. +* Amit: I totally get it. This was just a suggestion from me -- I would be okay + with not marking it experimental. +* Jett: I'm out for most of next week, but after that I'll try to send a PR. It + sounds like we have rough alignment, so the next step is to put it into TRD + words. + +## Console API change/bug fix (#2996) + +* Johnathan: I mostly want to make sure people saw the PR, because it rides the + line between a breaking change and a bugfix, and that's not obvious from the + PR's title. This was a result of a `libtock-rs` code review, where a new + contributor read the console documentation and believed that console could + write less of the buffer than the process had asked it to. I.e. they + interpreted that if you pass a 5 byte buffer to the kernel and told the kernel + to write 5 bytes, it could write 2 bytes, and that is acceptable. That is now + how I read the console docs, but it wasn't explicitly written in the + documentation. I read the console source code to verify I read the docs + correctly, assuming it would always write the full 5 bytes, unless the process + told it to print fewer bytes. If the process passed a larger buffer and told + console to print a smaller number of bytes, I assumed console would write the + first bytes, but when I read console's source it prints the last bytes. I + changed the wording to clarify that the console will always write the number + of bytes you told it to write or the buffer size, whichever is smaller, which + is its current behavior. I also adjusted the console capsule so that it prints + the first bytes, rather than the last bytes, of the buffer. That is + technically a breaking change, sort of. If anybody ever depended on the + behavior of printing the last bytes, this change would break them. But the + documentation never said what it should do, and it's kind of a question of how + you read the documentation before whether this is a breaking change or a bug + fix (making console behave the way it was specified to). +* Amit: It seems like there's no way we shouldn't do what you're suggesting. + Whether we call it a breaking change or not is incidental. This is also like + the console -- this is affecting `println`s basically, and as you're + suggesting it is difficult to imagine that anybody relied on that behavior. + Must of the people who have used Tock are on this call. While the community of + users is relatively small, take advantage of that and fix things that are + clearly bugs even if technically they are a breaking change to what some + theoretical user might have relied on. +* Jett: I second that, for sure. +* Alyssa: Just document it in the release notes, that's all I care about. +* Hudson: Yeah, I agree that that's clearly just a bug fix. +* Johnathan: Okay, so I can probably just merge it as is, or should I ping Phil + and Leon on the PR and ask them to review? +* Amit: Ping me, if I'm not already on it, I will look at it and will approve it + quickly. +* Johnathan: Sure, I'll do that. +* Hudson: I think the discussion that started at the end of the PR about + removing the bytes written field -- if somebody is particularly concerned + about that can just be opened as a separate issue or PR and not hold up this + bug fix. +* Johnathan: Okay, well I think it's settled. diff --git a/doc/wg/core/notes/core-notes-2022-03-25.md b/doc/wg/core/notes/core-notes-2022-03-25.md new file mode 100644 index 0000000000..ca2d78463f --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-03-25.md @@ -0,0 +1,199 @@ +# Attendees +- Amit Levy +- Hudson Ayers +- Philip Levis +- Alexandru Radovici +- Johnathan Van Why +- Pat Pannuto +- Alyssa Haroldsen +- Branden Ghena +- Vadim Sukhomlinov + + +## Updates +- Phil: I am continuing my work on Process loading and signing. One thing that is tricky is that + there is not the same hardware crypto available on different platforms, and + we don't have software implementations. I am implementing a software SHA256 so + we can test across platforms. I want to avoid incorporating licenses if + possible. + +## Uninit in static buffers +- Alyssa: we have storage::read() syscall which reads from storage + if you have a 1k buffer you want to read into, we find we have to 0 it out + in userspace before we write to it, because Rust expects a [u8] slice and + not a `[MaybeUninit]`. This wastes cycles. +- Alyssa: The in-progress switch of Readable and Writable ProcessBuffers to use raw + pointers should make this not an issue on the kernel side. +- Alyssa: Problem with working with uninit data is that it is not guaranteed to be + fixed, this is a unique property of uninitialized memory. However it seems + that on all our platforms integer data should be frozen once passed across the + system call boundary. +- Alyssa: Another complicating factor is that rules around uninitialized byte + buffers are unclear -- is it UB to create + an uninit integer, or just to read from it? +- Phil: Why do we want this buffer to be uninitialized? +- Alyssa: It is wasteful to zero large structures which we are going to + immediately write over. +- Amit: So to clarify. I am in rust userspace, I want to read 1k from flash. I + don't want to waste 1000 cycles initializing a large vector in the process + stack because that would be slow since we have to 0 it out and doing so is a + waste since once we pass the buffer to the kernel this is going to be + overwritten. The problem is that the system call interface takes an + initialized slice, and so there would need to be some casting, and the + semantics of that cast are undefined. +- Johnathan: So we are talking about using ReadWriteAllow, which takes `&mut + [u8]`, to pass these uninit buffers +- Alyssa: yes +- Johnathan: I was going to suggest just adding an API to libtock-rs that + takes MaybeUninit instead, but one threat model concern is that we + might be sharing secrets that were previously in RAM with a capsule +- Alyssa: Yes, and for this reason I think that it might be smart to have a + write-only allow. +- Amit: That doesn't sound like a bad idea on its face +- Phil: How would you make a type capable of this in the kernel? +- Alyssa: MaybeUninit is an example of a type that does this -- it takes + unsafe to read from it +- Phil: How does that work for an array where you might only write to part of + it? +- Alyssa: Functions that take in a WriteOnlySlice and output + ReadableWritableSlice +- Hudson: How does that cover the "write only part" portion of Phil's Q? +- Alyssa: It depends how important that is. Rust std library does have a + nightly-only structure that provides some support for this by tracking + which portions of a buffer have been initialized. Alternatively we + can just give it an API where you have to write an entire buffer to write + this write-only slice +- Amit: A slice of MaybeUninits might cover this +- Alyssa: Could have a partial write function that returns a WriteOnlySlice + and a ReadableWritableSlice split across the portion that you wrote +- Phil: Can we talk about the use case a bit? This sounds like 256 wasted + cycles? Is this worth rearchitecting chunks of the kernel for this? +- Alyssa: This is multiplied across every flash read. And there is code for + these mem clears. +- Alyssa: Also we are using the zerocopy library, where we take a structure created + using `new_zeroed()` and then pass an `as_mut()` slice to the kernel. so we have + monomorphization based on the size we are loading in memory. +- Johnathan: It does seem that these kernel changes could be larger, size wise + than the size in userspace from all of this. +- Alyssa: It just in general seems like this is something that should be + possible +- Hudson: One note, we do this in libtock-c all the time +- Alyssa: Yeah, that's allowed +- Johnathan: Well when we do that in libtock-c that is a threat model concern. +- Phil: If you cared about it in your app you would 0 it +- Alyssa: Our storage controller being able to read uninitialized data is not + part of our (Ti50)'s threat model +- Johnathan: I think Ti50 has a different threat model. +- Amit: The high level goal is achievable by zeroing out the memory, so this + is just a question of performance trade-offs (between cycles and size it + seems) +- Alyssa: another option could be a libtock-rs option for sharing uninit + memory without using a new syscall. That would require an agreement with + the capsule that it will only write to the memory or that you don't care if + it reads from uninit memory. There is also a fourth option which is uninit + data in the u8 directly, that passes in miri now but it is unclear if it is + unsound. +- Amit: Even the last option requires the same security concept with capsules +- Johnathan: I think it would be reasonable to change the threat model to say + that a process can choose to trust a capsule with arbitrary data, and then + add an API to libtock-rs that does this. +- Phil: I agree with Amit's characterization here about this being an + empirical question of performance trade-offs. My intuition is that the kernel + costs would be greater, but the only way to know is to do this empirically +- Amit: If I understand Alyssa and Johnathan then you are happy to trust the + capsule, and this is more about trying to stick to the letter of the current + threat model? +- Alyssa/Johnathan: We do trust this capsule to do what it says. + The primary blocker now + is the lack of a libtock-rs API that takes uninit memory. +- Amit: My thought then is that if you are cool with having unsafe code in the + app to handle the uninitialized memory in order to avoid this overhead of + zeroing out the memory, that is fine. If that is not cool or needs to be in + libtock-rs, there is a question of whether this generally is applicable to + other applications because of the threat model concerns. +- Amit: Basically it is a reasonable threat model for apps to trust particular + capsules, but we don't want to generalize that to all applications. + Basically we don't want a safe operation in libtock-rs that does this if it would + only work for certain capsules. +- Alyssa: This would be checked at the type level +- Amit: So this is not a general interface we would expose to any application + using allow for any purpose? +- Alyssa: For that we would need to have a raw pointer allow that is exposed + (to some degree). The issue here is what is a specialized userspace driver + going to do without that? Directly talk to the kernel via asm or use + `libtock_syscalls`? +- Hudson: It seems like we might want an `unsafe` API for this to indicate it + it not generally okay to use with arbitrary capsules from a security + perspective. +- Amit: If the only way to actually allow this in the kernel is to convert + this uninitialized memory to a slice that can be read, doesn't this break + type safety? If I allocate on the stack some type with private fields and + then that gets deallocated and I end up sharing it through a safe interface + and it gets shared in a readable way we have broken the encapsulation that is + part of type safety +- Alyssa: Well from the uninit memory you can't actually get the original + type, just the underlying representation +- Amit: Well at some level there is something performing an unsafe operation to + convert this to a slice +- Alyssa: Not necessarily +- Amit: but it seems like inevitably we are at some point going to make it so + the kernel can read this uninitialized memory without using unsafe, because + some unsafe in libtock-rs was encapsulated in a safe function +- Alyssa: what use of unsafe? +- Hudson: I imagine the use of unsafe would be just to call the raw syscall +- Alyssa: Yeah but none of the memory transformation is unsafe. It is sound to + go from MaybeUninit to raw pointer and length +- Amit: But on the kernel side, we are transforming that pointer and size into + a slice of u8's that can be read +- Alyssa: Once we convert to raw pointers in the kernel for ProcessBuffers it + wont actually be a slice. It remains true we are trusting the capsule in + that we are ok with it reading from uninit memory. This sounds like UB, + but whether it actually is depends on whether the buffer is frozen when it + crosses the syscall boundary +- Hudson: Once the kernel receives a buffer via allow, isn't that buffer + always going to be frozen? +- Alyssa: Yes, though I believe that is hardware specific, there could be + weird things with virtual memory managers +- Johnathan: That would be entirely under the kernel's control though +- Alyssa: Yeah, so it should be frozen. +- Amit: I still feel the function that enables this in userspace should be + marked unsafe. It could enable some library to wrap some stack pointer that + contains data that it did not create to get wrapped and passed to some + untrusted capsule that could leak that data. +- Alyssa: I don't think that is a memory safety thing, its a security thing. + That is not part of the ti50 threat model though. +- Amit: I think I would be uncomfortable with libtock-rs as a core library + exposing this operation as safe because of what it hides and because it + breaks encapsulation +- Alyssa: Is there anywhere else we use unsafe to mark security concerns + instead of memory safety? +- Amit: In the kernel, everywhere. Maybe not in libtock-rs +- Alex: If you pass MaybeUninit data to the kernel, and then unallow the data, + how can you know that the kernel wrote that data? +- Alyssa: Once you pass this across the system call boundary the optimizer + does not know what could happen +- Johnathan: The asm for system calls informs the rust compiler that the data + could be written to. +- Hudson: what will unallow() look like for this new API? Will it return an + initialized slice? +- Alyssa: Well.. it could return a MaybeUninit, and then based on the success + of the operation it could call `assume_init()`. +- Alex: How do you return success? +- Alyssa: Not sure +- Phil: The low level driver gets an upcall indicating the read completed and + was successful. Based on that, the userspace library knows whether the data + was initialized or not +- Amit: This all seems reasonable but all this conversion stuff should be + happening in capsule-specific drivers not being a low-level system call API. +- Alyssa: I think there should be a well-lit path for sharing uninit data in + libtock-rs, with security disclaimers. +- Phil: I am still interested in an empirical performance evaluation +- Hudson: I think that evaluation will be very dependent on the particular + use of uninitialized data being considered so we may not get clear-cut + results. +- Alyssa: A lot of this is motivated by C firmware engineers being generally + frustrated with the fact they have to 0 memory to work with it at all +- Phil: Regardless, it seems clear that if we measured this for an example it would shine + some insight into which approach is better for many use cases. If the kernel + cost of a new system call is high enough we would simply never consider it. + Not saying that will happen but we want to know the constant factors here. diff --git a/doc/wg/core/notes/core-notes-2022-04-01.md b/doc/wg/core/notes/core-notes-2022-04-01.md new file mode 100644 index 0000000000..ade7ac1e29 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-04-01.md @@ -0,0 +1,131 @@ +# Core Working Group Meeting, April 1st 2022 + +## Attendees +- Hudson Ayers +- Philip Levis +- Alexandru Radovici +- Leon Schuermann +- Johnathan Van Why +- Brad Campbell +- Alyssa Haroldsen +- Branden Ghena +- Vadim Sukhomlinov +- Jett Rink + + +## Updates + * Brad: No progress on stability issues by Rust community + * Hudson: Going to EuroSec next week. Presenting a paper on Tock there. + + +## Ergonomic Static Apps + * https://github.com/tock/tock/pull/3001 + * Hudson: Wondering if this is something we should be doing in the Tock kernel, or should be pushing to Makefiles for apps? Code in the kernel has a cost for everyone, even apps that don't need it. Alternatively, it seems like apps could choose addresses while building. + * Brad: While you're developing the kernel, the address where RAM for apps starts can change. That can make it limiting about where you can put apps and how many you can put. So more flexibility in the kernel is probably convenient, because it's hard to know which addresses you might want, and it takes time to link apps for many different possible addresses. + * Hudson: Right. So at build time you don't know where the memory region for the kernel ends. + * Phil: I'm not sure how this would work. Doesn't the Tock kernel already handle this and it's a Tockloader issue? + * Brad: No. Right now what the kernel does is that it looks at the loaded app, checks if it needs a fixed address, and then tries to start the apps region wherever it requested. The issue, as I understand, is that this only works if the address is well-aligned for the MPU. If it's not, creating the MPU region will fail and the process won't load. + * Phil: I thought that when loading, it was walking forward through RAM. When loading an app, it jumps forward to a memory address if the app needs. So the issue is that if the statically assigned address doesn't align with the MPU, then allocation can fail? + * Brad: yes + * Leon: But won't you know the MPU alignment at app build time, for the given platform? Why would you link an application without a well-aligned address + * Brad: Not sure + * Hudson: I think it's a great question. I think this is coming from libtock-rs. + * Phil: I'm trying to parse this comment in the PR. https://github.com/tock/tock/pull/3001#issuecomment-1079168845 Why doesn't 0x20004000 just work? I'll respond to the PR. + * Hudson: Maybe when we're building apps for some fixed memory address, the fixed address we're building for we're specifying the end and the start is determined by the size? I'm pretty confused here too. + * Brad: If the app needs more RAM, i.e., if you increase the stack. If it becomes longer than the MPU alignment, then the app might no longer be well-aligned. So if the memory requirements increase, you could go from an app alignment that works to an app alignment that doesn't work. + * Brad: So say your app needs 2 bytes of memory. You could put it at address 2 which is aligned. But if you increase to 4 bytes of memory (for more stack size), it's still at the fixed address of 2, but that doesn't work anymore. + * Branden: How are we picking that address anyways? How are we choosing 2? + * Brad: It's hard-coded in the linker when the app is compiled. + * Alexandru: Like a constant, or based on how much memory is needed? + * Brad: I don't know libtock-rs, but it's fixed in libtock-c + * Johnathan: It's hardcoded in libtock-rs too. + * Leon: Okay, I think I get it. But how does this PR solve it then? Seems like a problem we can't without multiple regions. + * Brad: Yes, so back to my original example. If we start at address 2, but need 5 bytes of memory. We start the MPU region at address 0, the app can start at 2, AND it can still have 5 bytes. + * Leon: Doesn't this increase complexity a lot because we might have to check backwards for collisions? + * Brad: That's correct. I think this is mitigated because the MPU interface is based on the start of memory that's available, which is already after the previous app. So I think that's going to work. Although we should double-check it. + * Leon: We could also fix this with better checks while linking, right? + * Hudson: We don't know where the kernel region ends though. + * Leon: I think we might. We have to know where RAM is for our apps since they're fixed. + * Brad: That can change, and it's theoretically fine to choose a later address and waste memory, that way if the kernel does increase you don't have to recompile your app. + * Leon: This sounds like we would end up with a pretty suboptimal result. We could propose adding more checks in the linking process. + * Brad: Right. The question is "why not just choose a better starting address"? That's not clear to me. + * Branden: I'm a little worried that changing the linking process would be pretty difficult actually. Whereas this does seem to solve the problem as-is. + * Hudson: This takes 200 bytes or so. I try to push back on these things because I don't want to waste kernel space on things that are very special-purpose and could be done somewhere else. + * Phil: It would be nice that if you're just given binaries and can't recompile them, that you could still load them. + * Brad: An issue is that Tockloader doesn't know where the start of application RAM would be. If you compiled an app for, say, 100 different RAM locations, you don't know how to choose. If you have one app for address 2 and one for address 16, Tockloader doesn't know which one to choose. + * Phil: Right. In the presence of dynamic loading along with static loading, it can't know. + * Brad: Even with only static, Tockloader would need to know MPU, and kernel memory bounds, and app memory size. Then it could pick. + * Leon: If the kernel could tell Tockloader things, it could make more intelligent decisions. + * Brad: Yup. But then we're adding even more code size. + * Phil: So is this use case important? That you're given a binary, you can't recompile it, it's statically linked, and Tockloader needs to determine where to place it? + * Alexandru: A question is how far away are we from relocatable apps? Do the GCC for Rust efforts solve this problem? + * Brad: It seems like we need to better understand the use case. Since existing apps haven't seemed to run into this issue. + * Johnathan: You have TI50 which is doing it's own thing and combining kernel and apps. + * Johnathan: Upstream, libtock-rs is linked against a fixed kernel. Occasionally we have to update that and adjust the linker a little. There is some confidence that in the future we'll be able to do relocatable apps on RISC-V. But on ARM, we still won't be able to with LLVM, so we'd need GCC for Rust too. + * Brad: Okay, so right now we have this logic that looks forward in memory and advances to meet the application requirement. If we just change that to hit the nearest MPU region, that seems fine. + * Branden: The nearest MPU region for what size of memory? + * Brad: The app size, plus the gap until you start memory, and the gap until the app wants its memory. + * Branden: But you're saying that you have this info in the kernel and can make that choice. + * Hudson: The linker script should know memory sizes, once? + * Brad: Conceptually yes. Practically that's only known by elf2tab. + * Jett: It's weird to me that the memory size and the alignment address are separate in the linker stuff. + * Hudson: Okay, so we don't need a full resolution here. But I wanted to bring it up and think through what's going on with this PR. Specifically whether it should be done in the kernel at all. I'll looks some more at this on the libtock-rs side after the call and think about whether the linker could get all the right info. If that looks really hard, we could see if we can just optimize the kernel side a little. + * Johnathan: Since the linker script is generated by libtock-rs, push comes to shove we could generate a linker script dynamically with the right size. + * Phil: There is a future issue where, since we have signed apps, we won't be able to touch the binary but still want to load them. + * Johnathan: But they'd be signed after the app is compiled, so if it choose the right location before then, everything's fine. + * Phil: But I could imagine not moving it, but increasing the size it says it needs to make it work. It's not a prominent use case right now, but I just want people to keep signed apps in mind. + * Hudson: Presumably, we're signing the TAB not the ELF. So the parameters are there and protected. + +## Mixing Fixed Position and PIC apps + * https://github.com/tock/tockloader/issues/82 + * Hudson: This seems like a good use case to support, and the author put a lot of work and thought into this. I wanted to raise it to get people to take a look and provide feedback. + * Brad: I thought this would be a simple change. Just place the fixed position apps first, then the relocatable ones after. Not sure why it needs to be more complicated than that. + * Hudson: The SAT solver is to optimize placement. It's probably okay to be suboptimal since this is an unusual case. Not sure why it needs refactoring of TBF handling and App classes though. + * Brad: I can follow up and see if we can figure out what's really needed or not. It seems like a stretch to me that we're going to have so many apps in this case that we'll run out of flash before running out of RAM. + * Hudson: Right. It does depend on the platform though. + * Hudson: I guess the changes for the TBF handling code, we might assume that once we see one app that is fixed all future ones are fixed as well. + * Brad: That was a bug and is now fixed. But TBF doesn't really have stuff for mixed, so might need changes. + * Hudson: My thought on dependencies is that we probably don't care for Tockloader. + * Brad: Only worried about complexity. Not actually a problem to have dependencies. + * Hudson: Cases that would be really worried about auditing already don't really use Tockloader. + * Brad: We just want to avoid other requirements or dependencies that make it hard to run on some platforms. Pure python dependencies are fine. + * Brad: This SAT solver makes me nervous. I think there's a lot of native stuff here that might make Tockloader not work on some platforms. + + +## Command Result Type for Pointers + * https://github.com/tock/tock/pull/3003 + * Jett: I made a proposal for a new success variant for Command, "success with pointer". Different semantics from u32 or u64. There are no guarantees about validity or accessibility of the pointer from the kernel. + * Phil: I thought we were going to return an offset into an existing pointer instead? + * Jett: Two use cases: returning a pointer into a kernel-owned buffer where the app doesn't know the address. Another hypothetical use case is some kind of system manager info where an app might want to know something about its own address space, which would involve giving it a pointer. + * Phil: What's an example of wanting to receiving a pointer into the kernel? + * Jett: We have a kernel-owned buffer that's leased to applications through some driver, with contents. The application can access it, and the kernel has to MPU share it first making it exclusive to that app. The application reads, modifies, and lets the kernel know when it's done. + * Phil: Right, this is the case where we have one kernel buffer rather than a buffer in each app. So we move access to it around rather than have each app take up space making their own. + * Jett: Yes. + * Jett: So, this is a first-class way to return a pointer. Drivers still need an API where it makes sense. But this is just the method. Gives a name for the thing that we need. Assume we have a pointer, this return is for it, and will make it work in a platform-independent way. + * Phil: Question, for memop, it returns a bunch of pointers already? + * Jett: Probably. Allow takes a pointer already. There are other system calls that receive or return pointers. This allows capsules to have a return-variant for command that they can return, that's well-defined and platform independent for when returning a pointer makes sense. + * Leon: One concern, in Tock 2.0 we have a set of system call classes which have pointers because they must interact with pointers, like allow or memop. Then commands which, conceptually, should work exactly the same on whatever platform you're running on. So, I'm fine with a new return variant that would be able to return a pointer for these cases. I wouldn't want to increase u32 to usize, because something developed on a 64-bit platform could break on a 32-bit platform. So the driver trait should be limited in the arguments to accept 32-bit values rather than staying the same across platforms. So how are we going to reflect this change in the arguments of commands? + * Jett: I did the least-amount-of-change approach. I stayed with the restriction. We didn't need to pass in pointers as inputs, so command only takes the arguments as it has been. The change is that pointers can be returned from the kernel with command, but apps passing pointers down should go through allow or memop. And then command could return offsets like Phil said earlier. I think command inputs don't ever need to be usize, I think we can stay with u32. + * Leon: Okay, I think that makes sense to me. + * Phil: The concern with 64-bit is that you're in a 64-bit platform for testing, right? + * Jett: Yes. That was the impetus of this discussion. But I think it's an important design point even apart from host-emulation. + * Phil: So how does memop work? + * Jett: Allow and Memop use `usize`. That's part of their definition. It's just that there's not command variant for capsule code that works with pointers. So this introduces that new variant. + * Phil: But with memop, there's a series of calls you make to get your start addresses, flash addresses, etc. That's all success-with-u32. So it's fundamental that the whole ABI works for u32 architectures. + * Jett: But that memop stuff happens in the board/arch sections of the kernel. So that is all architecture-dependent and handles it all appropriately. It's not defined for 64-bit systems how memop should work. I think it's clear what you ought to do, even if it's not really defined for 64-bit. I think it's okay to have `usize` for architecture-dependent code, like memop. But capsule is very much not platform-dependent. So that's where this is sticking. + * Phil: I'm still trying to understand. It can be that the structure definitions versus actual bits that are passed. Technically, the return type for memop is success-with-u32 (https://github.com/tock/tock/blob/master/doc/reference/trd104-syscalls.md#46-memop-class-id-5). So the fact that the Rust structure holds it as a usize is okay. But ultimately userspace is supposed to assume that it's a 32-bit value. Is the host emulation actually passing a 64-bit value and getting away with it? How does that work? + * Jett: Good question. I'll have to look at that. + * Phil: So I think the question I asked last time, is there a reason you don't just run the host emulation in 32-bit mode? + * Jett: We run it natively on our machines. + * Alyssa: I don't want to revamp our toolchain just for that. + * Phil: Well, you are asking to revamp the kernel for this. + * Alyssa: I think it's a bug to not have a usize return type. In my opinion, you should allow some variable-width data in a return type like this. + * Leon: There are two issues here, actually. The memop stuff could be addressed with a 64-bit ABI, but we would still be having this discussion about drivers and command. + * Jett: I agree. Related, but two separate issues. + * Jett: So, here we're focusing on the issue of adding the new success variant with pointer. That will help when we think about a 64-bit ABI. It's still well-defined for 32-bit ABIs too. + * Phil: I think this is a thorny issue. There's also that there's a command returning a pointer into kernel memory. I don't see any significant issue with that, but it's different from Tock's traditional memory model. + * Jett: Giving capsules the ability to return a pointer seems useful. Maybe it's a kernel pointer. Maybe it's an application pointer. But it's a valid return type that I think upstream should allow. Again, no guarantees on accessibility. This is just a value, but it's a usize-width value. + * Hudson: I think it would be good to look over the prior call notes too. We talked about this two weeks ago on a call that Phil missed, so I want to make sure we're not re-hashing issues too much. + * Phil: Oh! Definitely. Jett and I could talk about this offline too, grabbing other interested parties. I want to put some thought into this issue, for sure. + * Jett: Sounds good + diff --git a/doc/wg/core/notes/core-notes-2022-04-08.md b/doc/wg/core/notes/core-notes-2022-04-08.md new file mode 100644 index 0000000000..85d3a5d648 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-04-08.md @@ -0,0 +1,44 @@ +# Attendees +- Alexandru Radovici +- Alyssa Haroldsen +- Amit Levy +- Brad Campbell +- Branden Ghena +- Jett Rink +- Johnathan Van Why +- Leon Schuermann +- Pat Pannuto +- Vadim Sukhomlinov + +## Updates + +- Pat: Hudson is presenting the threat model paper at EuroSys, correct? +- Amit: He already presented, is flying back. +- Pat: I'd like an update on how that was received when possible. +- Amit: I can relay what he said to me. It went pretty well, I think. EuroSys is + not a super embedded-familiar audience, so had to answer a few questions about + why we don't use ASLR etc.. We also got some good questions from people who + thought the challenge of having to trust liveness is interesting. Also got + some questions after the talk about how we compare to Redleaf and whether + Redleaf's fault-recovery mechanisms could work for our purposes. Some + questions about whether there is interest in developing some public collection + of safe-Rust-only crates with other systems like Redleaf and Theseus and + working with them on requiring safe-Rust-only app code, which is an + interesting idea. +- Amit: Are people familiar with Redleaf and Theseus? These are two research + operating systems for not-microcontrollers also requiring Rust. +- Alyssa: I have not heard of them. +- Branden: [Linked https://mars-research.github.io/projects/redleaf/ in chat.] +- Amit: Expect to hear from Hudson next week. I think generally the topic of an + ecosystem of safe-only core crates is a pretty interesting one. There are + questions on if systems are similar enough to rely on that. My very limited + experience on looking into that is that the differences between Tock and other + things are not just in relying on unsafe, but also in not doing general + dynamic allocation in the kernel. Probably a lot of things that would be + reasonable dependencies would look pretty different if you can or cannot use + the heap. Even if that is the case, maybe there is a set of related stuff that + is relevant to the Rust userspace that has similar safety requirements, + generally, but is allowed to do heap allocation and looks similar to what the + other Rust kernels are doing. + +[Editor's note: The meeting agenda was empty, and there was no topical discussion] diff --git a/doc/wg/core/notes/core-notes-2022-04-15.md b/doc/wg/core/notes/core-notes-2022-04-15.md new file mode 100644 index 0000000000..3880906244 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-04-15.md @@ -0,0 +1,84 @@ +# Core Working Group Meeting, April 15th 2022 + +## Attendees + * Branden Ghena + * Leon Schuermann + * Phil Levis + * Pat Pannuto + * Hudson Ayers + * Alexandru Radovici + * Johnathan Van Why + * Vadmim Sukhomlinov + * Alyssa Haroldsen + + +## Updates + * Alyssa: I'm going to be a semi-official liason between TI50 and Tock. + * Phil: It might be good to start planning the next Tock world. An in-person meeting would be great to do. I don't know when a good time is, but it would be very good to organize another in-person Tock world. Maybe end-of-summer. I'll bug Amit about it. + * Hudson: It would be great to do another one! + * Phil: Hudson and I are trying to push forward the UART TRD and a redesign of the UART API. We've got some undergrads learning Tock at Stanford who are learning Tock and porting the SAM4L and nRF52. We'll see how that goes and then expand efforts to other chips. + + +## SHA Draft PR + * https://github.com/tock/tock/pull/3010 + * Phil: The draft software SHA implementation PR is up. Part of the AppID effort so we have some way of checking integrity. I thought it would make sense to have a software implementation so it works across platforms with various hardware. SHA-256 is the implementation. There's a few edge cases, but I have it working. I'll transistion from draft to real PR as I clean up the code. + * Phil: Generally, I think it's nice to have software-only implementations of some things so they are really cross-platform. + * Alyssa: Are there any pre-certified libraries we could use instead of rolling our own? + * Phil: Yeah, I don't want to do this for anything that involves secrecy, for that reason. AES for instance, is a bad idea to implement ourselves. For integrity stuff though, this seemed reasonable. Where this gets tricky is RSA. For SHA I felt comfortable because there are simple test cases and it's just an integrity question. Public key or really any secrecy questions, I would not feel comfortable implementing. + * Phil: Notably, this is a super brain-dead implementation. It's not fast or optimized. It's just nice for platforms with no hardware support at all. + * Leon: Interesting argument. This might be nice for CI use cases too, where there is no hardware support for stuff. I think it might be hard to find existing code for other things because Tock handles buffers differently. I do actually have an AES implementation lying around, but I'm not sure where the line is where Tock would accept something. Say that we know it has side channels, but it's nice for testing. + * Phil: I'd say anything in the main repository shouldn't be rolled on our own. Even if it's only for testing, someone will use it for real just because it's there. + * Alyssa: I'd also like to mention that there needs to be a stable interface for SHA. For platforms with their own crypto hardware, we want to switch out for that. + * Phil: Absolutely. This is just a capsule that meets the Digest interface. + * Phil: The current plan is to use this for AppID stuff to check integrity of app images. + + +## Tock Registers Alignment + * Branden: I wanted to check if anyone saw: https://github.com/tock/tock/issues/3019 Looked scary. + * Leon: I looked at this briefly and don't have enough time to look in-depth for now. We do make assumptions about the register structs and that they're matching the repr(c) rules. I think our logic doesn't take alignment into account right now. + * Leon: I think this issue might not be one that we can really resolve. We only know at run-time what address is being loaded at. + * Branden: I'll follow up on the issue. I'm also confused _how_ they ended up with an unaligned address anyways. + + +## Tock Registers Soundness + * Alyssa: I'm also concerned about references to volatile memory in Tock registers. + * Leon: Yeah, we did look into this. We believed that our current interface is valid as-is. There was a discussion here: https://github.com/tock/tock/issues/2593 + * Pat: Definitely take a look at that. And if you have real concerns still, please let us know. We're planning to release a 1.0 at some point relatively too. + * Alyssa: Mostly, I think instead of individual field access, there should be accessors which return wrappers around a raw pointer. So there's never a reference to volatile memory and it remains a raw pointer the whole time? + * Pat: Does that add an extra function call to each access at runtime? + * Alyssa: No. + * Leon: How does that work? Right now we use a struct that overlays memory and is the exact same size. This discussion sounds very much like what we talked about for userspace memory sharing. Fundamentally, this is the same issue, right? + * Alyssa: You'd have your outer structure be a wrapper around a raw pointer. And you'd have functions that get individual fields within the struct. + * Leon: I think I'd need a written-form version of this to get to think through it. It gets pretty complicated to think about it. + * Alyssa: The concern is that LLVM can insert a read at any point if it sees a pointer that's dereferencable. Which is problematic with volatile memory. + * Pat: So you think there could be spurious reads? + * Alyssa: It's _possible_. Why would LLVM ever do this? I think it's unlikely. But it is a semantic correctness thing. + * Pat: This is something that messed with C/C++ compilers for a while. I think there's almost no way to correctly represent memory-mapped I/O per the letter of the law. LLVM doesn't have a representation for it. + * Alyssa: C represents volatile as a property of memory, not of an access. LLVM and Rust treat it as a propery of an access. But if you make a pointer to a volatile int. LLVM doesn't know for certain that it can dereference this pointer. + * Johnathan: I think that's true in all cases for C/C++. But for Rust all references are dereferencable. + * Alyssa: I thought C++ was. Maybe there's something tricky going on. + * Leon: So you think the boiled down problem is that volatile cell as a concept is incorrect. + * Alyssa: Yeah. + * Leon: So I don't know the solution for laying out this struct on memory where there are arbitrary offsets into it. + * Alyssa: Maybe I don't know enough about the Tock register interface. How do you access things dynamically? + * Leon: I think it's all compile-time. It is arbitrary but constrained by byte-aligned at compile time. + * Alyssa: Right now we're doing a repr(c) struct to do mmio offsets. Instead of exposing publicly accessible fields, you expose methods and the methods take a pointer to the wrapper struct. Each one of these methods take a pointer and offset it by a known static value generated by the macro and return the offset pointer. + * Pat: So you don't need to change the interface at all, right? + * Alyssa: Well, instead of fields you need methods. + * Leon: I think the traits we have that provide methods allow us to change the representation underneath. So defined offsets with constant methods to determine offsets. Then we would return different types with the same interfaces as today. It would use the container pointer values of these types to return the appropriate data. + * Leon: So we should change the way the registers are defined in the first place and the wrapper underneath. + * Pat: I _think_ we can change this without changing anything for users of the library. + * Alyssa: If you're accessing fields, you'll need to change. So calling the macro doesn't necessarily have to change. Just the using of the output of it. + * Pat: I'm nervous because there's a lot of work that went into this and a huge volume of code that's using it. So I want a concrete statement that this is necessary because of a real functional issue and that the change is sanctioned and correct and won't have to change again. + * Alyssa: Is there an issue open for this? The volatile cell issues? + * Pat: #2593 mentions some things, but you'll need to open a new one. + * Leon: This is separate from that issue. No issue exists for this yet. + * Hudson: So this is the same problem that exists for the volatile registers crate and various other MMIO crates. It seems likely that if LLVM started inserting reads, a lot of stuff would start to break, not just Tock registers. + * Johnathan: There's this one too: https://github.com/japaric/vcell/issues/10 I do recall the embedded working group making changes because of this concern. + * Alyssa: There are also some minor soundness issues. You can create a register field that's just an exposed u8 buffer, which isn't great because it should be in an unsafe cell. It shouldn't be possible to just put a block of memory there. So some way to present a slice in mmio is necessary. + * Leon: I think I agree. Though technically, the unsafe part there happens when you transmute the pointer to a reference of this struct. So it's more of a user mistake. So that's sort-of outside of the scope of what Tock registers defines. But we do it all the time so we should think about it. + * Alyssa: We should probably have a constructor function that makes this instead of people converting themselves. + * Leon: If we are going to have to change the interface, this sounds like a good idea. We first really need to document these issues so we can seriously consider them. + * Alyssa: I can send a simple playground of what it might look like. + * Leon: That would be great. + diff --git a/doc/wg/core/notes/core-notes-2022-04-22.md b/doc/wg/core/notes/core-notes-2022-04-22.md new file mode 100644 index 0000000000..8441927b43 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-04-22.md @@ -0,0 +1,257 @@ +# Tock Core Notes 2022-04-22 + +Attendees: +- Hudson Ayers +- Branden Ghena +- Alyssa Haroldsen +- Philip Levis +- Amit Levy +- Leon Schuermann +- Vadim Sukhomlinov +- Johnathan Van Why + +## Updates + +### Porting implementations to new UART HIL + +- Hudson: students at Stanford porting over the UART code to the HIL have + gotten basic functionality implemented on the SAM4L chips. + + Found one case on a board where there are upcalls being issued + within a downcall. Hopefully should have some fixes there shortly. + +- Alyssa: What exactly is a downcall? + +- Hudson: Just a regular call issued from an application, versus a + call from the kernel in response to an interrupt. + +### Additional CPU registers in app switching + +- Leon: Been looking at the RISC-V specifications and noticed they + specify a `uscratch` CSR as part of an optional extension. It turns + out that this is not supported on any of the boards in-tree, but it + made me worried about different ISA variants potentially exposing + further registers to userspace which we should store & restore in + response to a syscall / switch apps. If we don't watch out there we + might introduce covert channels. + +- Amit: Good question. Wonder if there is something like this for ARM + chips with TrustZone-M. + +- Phil: Student of mine got to know TrustZone really well. Might be + worth to reach out to him. + +### Embedded code size paper + +- Hudson: Paper on embedded Rust code size was accepted at LCTES + (co-located with PLDI). It uses Tock for many of its examples. + +- Amit: Saw a draft of that. Can you send that around? + +- Hudson: Camera-ready is May 6th, sending it around afterwards. + +- Phil: Two other papers at PLDI on Rust. One of them is from Will + Crichton about using ownership for information flow control. + +## Converting ProcessBuffer to use raw pointers + +- Hudson: Leon, do you want to give an update on where PR + [#2977](https://github.com/tock/tock/pull/2977) stands? + +- Leon: Was busy writing my thesis, hence did slack off a bit + w.r.t. this PR. However, it seems we agreed on the general sentiment + and approach proposed there. + + This PR does introduce a lot of new unsafe code in the core kernel + which demands careful review. This is hard to get right. + + The capsules should at least all be compiling. These changes + introduce new calls to `unwrap`, which makes the index-operator + panics explicit (so doesn't introduce new panics). We want to reduce + calls to `unwrap` and eventually entirely get rid of panics in + response to accessing userspace memory. We can use iterators and + proper error handling for that. People should look at capsules, + happy to get new commits improving the code quality there. + +- Alyssa: I will take a look at this. + +- Leon: Thanks! + +- Hudson: Is there a list of the capsules that have been done? + +- Leon: In a sense all have been done. Did a mechanical replacement of + square-bracket index operators `[$IDX]` to `.get($IDX).unwrap()` + calls. To remove these panics, people can look at the diff and see + where `unwrap` calls have been introduced. + +- Amit: Ideal thing would be to avoid doing this and just use entirely + non-panicing accesses. + +- Leon: That's right. Ideal thing would be to handle the various + errors properly to compose a coherent capsule ABI. Even if we were + to just return a generic error, still better than panicing. + +- Hudson: We wanted to identify a capsule which serves as a good + reference for what this porting process can look like. Did `console` + end up being that? + +- Leon: Good question, not sure. Essentially any capsule containing a + panic-free way to access a sequence of elements using iterators as + well as accesses to known offsets will serve as a good example. + +- Alyssa: Yes. there are a lot of `unwraps`, but many of them are in + functions that return `Result`. We could use + `.ok_or(ErrorCode::$ERR)?`. + + Do you want to delay this transition to fallible operations? Or + preserve semantics. + +- Leon: For capsules where we do have a specified ABI (perhaps just + `console`) we want to preserve semantics. For all others, we can + just do this transition as part of this PR. + +- Alyssa: Turning a panic into a return code is always ABI safe. + +- Hudson: Well, the console API specifies what `ErrorCode` will be + returned when. If we start returning arbitrary error codes in + response to various internal error cases, we would be misleading + users regarding the precise cause of the error. + +- Leon: re whether we should do this now or later. Once this PR is + merged, people will forget and delay these transformations. As long + as this PR is open, at the risk of it getting rather large, it might + be good to do these transformations all in one go. + + The reason why this new ABI does not return a `Result` but rather an + `Option` is that we want to avoid blindly throwing up + kernel-internal errors to userspace. We need to be careful of the + errors we return and deliberately choose them individually, as this + will compose our offered ABI. + +- Alyssa: Once again, the alternative is panic. + +- Hudson: Agreed that panics are even less debuggable than improper + returned errors, but we should adhere to our ABI contracts. If + something is specified to return `EINVAL` and we return `ESIZE`, + that's not good. + +- Leon: In terms of ABI compliance, panicing is technically better + than returning an incorrect error code, as the app will never be + exposed to that. Doesn't help us though and we don't want to panic. + +- Alyssa: Many of these panics should be optimized out anyways. + +- Hudson: Yes, but it's still a lot easier to grep for calls to + `unwrap`, `expect` or `panic` than it is to analyze the resulting + binary. We can't really rely on this being optimized. + + (...after the below discussion around error codes...) + + It sounds like for this PR, we want to get this in, possibly with + some more transformations of capsules. Secondly, we want to look + into introducing a new internal error code and possibly some others. + +- Leon: Want to emphasize that people should review `processbuffer.rs` + and the unsafe code there. Going to reach out to Alyssa for ways to + automatically assure that the pointer transformations there don't + end up in invalid memory. + +- Alyssa: Like a Miri test? Can help with that. + +### Side discussion: ErrorCode extension + +- Alyssa: I think an internal error code which never needs to be + documented would be valuable. It would be good to not panic, but + still preserve ABI compatibility. + + I'm generally a fan of canonical error codes and + [Abseil](https://abseil.io/docs/cpp/guides/status-codes). + + An internal error code would specifically be an error indicating + that something is broken within the kernel, i.e. worthy of a bug or + outage report. If a user runs into an `EINTERNAL`, they should + report that. + +- Phil: historically used `EFAIL` for that. + + Agree that _fail_ encompasses other things as well. Making that + distinction would be valuable. + +- Leon: Generally agree with the sentiment that an internal error can + be useful, but is separate from the ProcessBuffer PR. This would + require an update of TRD 104. + +- Hudson: Happy to work on a PR that adds this. + +- Alyssa: while we're at it, _deadline exceeded_ is also a really + useful error case. + +- Leon: it might be worth keeping in mind that for specific error + cases, or when the predefined `ErrorCode`s aren't sufficient, a + capsule can always use `FailureWithU32` to introduce custom error + cases in its ABI. + +- Phil: the error codes are directly inherited from TinyOS. There's + clearly more things which come up. Agree with Leon that we should + think about this and introduce additional error codes in a + systematic approach instead of adding them one-by-one. + +- Alyssa: Now that I am looking at this, there's also + _unauthenticated_ and _unimplemented_. Also four different variants + of _unavailable_. + +- Phil: Don't think _unimplemented_ should be included, as it should + be caught at compile time. + +- Alyssa: Useful during development when using partial implementations + of components. + +- Amit: Useful for development, but presumably not wanted in a shipped + kernel. + +- Alyssa: Many kernels do ship with unimplemented + functionality. E.g. RSA signing only supported for 2048 and 4096 bit + keys, not 3072 bit. + +- Phil: for system calls, we can use `NOSUPPORT` for that. My argument + was for kernel space. + +- Alyssa: oh, missed that! + + How do we feel about overloading `ALREADY`? Is defined as an + operation is already ongoing. Been using it for _already exists_. + +- Leon: I have been doing the same (e.g. port listener already + registered). + +- Alyssa: Might want to modify the meaning to include this. + + Also, something indicating violation of security invariants. + +- Phil: We don't have an `ENOACCESS`, as in TinyOS this was not a + concept. Everything would be determined at compile time. + +- Amit: Might be good to share particular use cases for error + codes. On the one hand, we have enough space to introduce more error + codes. On the other hand, there is an overhead in dealing with and + interpreting error codes. + + For something like `ENOACCESS`, the decision we made at the time was + to return `ENOSUPPORT` or `ENODEVICE`. It does not leak information + about the device. Also, then applications don't need to handle these + cases separately: for an application, it does not make a difference + to distinguish between it not being allowed to access the device, or + it not being present. + +- Phil: Agreed, we should come up with examples for new error codes. A + new error code should much better articulate an error case than the + existing error codes. + +- Leon: We should also embrace the fact that the new ABI allows us to + associate additional discriminators with returned errors. Might be + worth documenting how this can be done, or provide a canonical + example. For instance, this is useful when two error conditions were + to collapse onto the same error code. + +- Alyssa: It could be nice to have a better mechanism for custom error + codes in the future. diff --git a/doc/wg/core/notes/core-notes-2022-05-06.md b/doc/wg/core/notes/core-notes-2022-05-06.md new file mode 100644 index 0000000000..8ad619bb62 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-05-06.md @@ -0,0 +1,101 @@ +# Tock Core Notes 2022-05-06 + +Attendees: +- Hudson Ayers +- Brad Campbell +- Branden Ghena +- Alyssa Haroldsen +- Philip Levis +- Amit Levy +- Pat Pannuto +- Alexandru Radovici +- Leon Schuermann +- Vadim Sukhomlinov +- Johnathan Van Why + +## Updates + +- Leon: Rebased the Rust toolchain update PR + ([#2988](https://github.com/tock/tock/pull/2988), as the previous + attempt at merging failed CI and has been waiting for some weeks. We + should get that in reasonably quickly, to avoid collisions with + other merged PRs. + +- Hudson: If I recall correctly, those changes are pretty innocuous. + +- Leon: Already had some approvals, shouldn't be a big deal. + +## elf2tab / Tockloader / cargo Pipeline Application Credentials Integration + +- Phil: *moving this to an email discussion with Brad* + +## TockWorld + +- Hudson: Talk about doing a TockWorld this year. People are somewhat + able to travel again, can plan doing an in-person TockWorld at some + university in the summer. We can talk about scheduling and location. + +- Amit: We might want to clarify what a TockWorld is for people who + have not attended the previous ones. + + It's been a while since the last one. It comprises the people on + this call and perhaps a few more, such as ones starting to be + engaged. + + We have used it as a focal point for some important discussions. For + instance, last time we have used it to focus on having a Tock 2.0 + and which constraints should be integrated into the API. + + This time, there might be some different goals. The primary goal + might be, given that Tock is growing, how to grow it in an organized + way, not just shaped by those putting in the cycles in an ad-hoc + way. Specifically how we can grow open source communities grown out + of an academic project. + + Many people have not met in person, which is a change from previous + time. We might want to invite interesting stakeholders which have + not necessarily been actively engaged in Tock. + + A significant goal might be to develop a shared understanding in + which directions the project should move, and as a result, what + types of activities we should do as a community. Also, what type of + governance would make sense going forward. + + We might have some more constraints this time given potentially two + contributors would be arriving from Europe, and perhaps one from + Australia. + +- Phil: I thought that the last TockWorld was really successful in + part because we had some clear agenda items regarding the major + questions and challenges to sort out. Meeting in person is a good + high-bandwidth way to reach a consensus. + +*Personal details excluded from the meeting notes, location / date / +topic planning notes sent via Email.* + +## tock-dev Google Group Mailing List + +- Hudson: An issue ([#60](https://github.com/tock/tock-www/issues/60)) + was created on the `tock-www` repo stating that joining the + `tock-dev` mailing list is not possible unless one provides a phone + number to Google. This used to work, but it seems it no longer does. + +- Brad: Did anyone tell them about Slack? + +- Leon: I don't think Slack is any better than Google groups in terms + of giving up information. + + Also, Google groups are pretty picky regarding spam filters, which + is not great when you operate your own mail server. + +- Amit: It might be that we can solve this issue for this particular + person, but this raises some reasonable concerns. Google Groups is + easy to setup and manage, and it has a nice interface generally, but + there are alternatives. + + Will respond to that issue. + +- Branden: It appears we can add individual people through their email + address. + +- Leon: Will reach out to the person in question. diff --git a/doc/wg/core/notes/core-notes-2022-05-13.md b/doc/wg/core/notes/core-notes-2022-05-13.md new file mode 100644 index 0000000000..a21267c2f4 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-05-13.md @@ -0,0 +1,63 @@ +# Core Working Group Meeting, May 13th 2022 + +## Attendees + * Branden Ghena + * Leon Schuermann + * Phil Levis + * Hudson Ayers + * Alexandru Radovici + * Johnathan Van Why + * Brad Campbell + * Alyssa Haroldsen + * Jett Rink + * Pat Pannuto + * Vadmim Sukhomlinov + + +## Updates + +- None + +## TockWorld 5 + +- July 19-20 (Tue & Wed) rising to the top of good dates. +- Deciding on a location. + - UVA + - UCSD + - NW <- winner +- Ranked choice vote. +- Invite list: + - Cycle group. + +## PR 3041 + +- Two versions of digest trait: one `mut`, one not `mut`. +- Useful to support non `mut` for data stored in flash. +- PR changes `LeasableBuffer` to `LeasableMutableBuffer` for mutable data. Adds + `LeasableBuffer` for immutable data. +- Decision: expose mut/imut in interface, or handle internally? + - Current idea is better to have this exposed in API for error handling. +- First attempt: wrap in `MutImutBuffer`. What happens if start with imut, but + get mut data back? Can't do anything, basically have to panic. +- Need to separate mut and imut to match Rust's ownership model and asynchronous + programming. + - If you have a mut buffer, and pass it to imut interface, you lose the + ability to ever mutate that buffer again. + - Otherwise you need to do a copy to imut buffers. +- Why not combine mut and imut functions in same trait? Why two traits? Is there + ever a case where you wouldn't be able to support both? + - Need separate callbacks for sure. Could separate callback trait. + +- Handle asynchronicity with suspend points in the kernel. + - Would require multiple kernel stacks. + - Async scheduler managing multiple kernel stacks. + - Complexity and code size issues + +- Users of Digest HIL want to be able use imut data + - Also, nice to not have to implement for both imut and mut buffers. +- Should other HILs also use this method? + - Not many HILs need to use large data. +- Reality is this solution is not great, but not clear there is a better + solution. +- Need to work on PR for potential alternatives. + diff --git a/doc/wg/core/notes/core-notes-2022-05-20.md b/doc/wg/core/notes/core-notes-2022-05-20.md new file mode 100644 index 0000000000..abcef67108 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-05-20.md @@ -0,0 +1,88 @@ +# Tock Core Notes 2022-05-20 + +Attendees: +- Branden Ghena +- Johnathan Van Why +- Jett Rink +- Pat Pannuto +- Philip Levis +- Hudson Ayers +- Leon Schuermann +- Vadim Sukhomlinov +- Alyssa Haroldsen + +## Updates + * None + + +## Tockworld Planning + * Hudson: I sent out email requesting additional invite ideas for Tockworld. People should reply with other people they want invited. + * Johnathan: I'm speaking with people and will have an answer soon. + * Phil: I'm speaking with Dom too. So we should coordinate + * Alyssa: Who's already invited? + * Hudson: Everyone on the email chain. I added five people to the list, including Jett and some of Alex's students. Feel free to send other people though. + * Alyssa: I have a friend in OpenTitan who might want to visit. + * Leon: I'm at a conference and meeting with Dorota. They can't attend Tockworld, but I'm taking notes of the issues they ran into so we discuss in more depth. + * Hudson: So the reminder is to invite additional people to Tockworld and send out to the email list. + +## Mut & Unmut trait problem + * https://github.com/tock/tock/pull/3041 + * Hudson: We talked about this some last week, and Alyssa among others had concerns. I spent some time thinking about this. + * Hudson: Leon and I spent some time thinking about digest traits and mut/unmut problems and combined it with the DMA problems to see if we could solve both. + * Leon: I'm working on a prototype implementation of the proposal. So the basic concept: with this new model we wouldn't pass down the actual buffer to the driver and have it passed back through a callback. Instead we'd pass a reference to a container which holds the buffer. Then peripherals would lock this container when using it, allowing synchronous or DMA operations. Afterwards the peripheral will unlock the buffer, which allows the capsule to access it again. I'm trying it in the core kernel and console capsule first all the way down to the UART for some chip. + * Phil: As a bit of framing, the issues Alyssa raised last meeting, everyone agrees with. Multiple traits felt not great. We're in a "what is the least evil" situation. I think no solution will be perfect, there will just be tradeoffs: duplication, locking, etc. + * Leon: I think this solution too will indeed add complexity, although I hope it won't be too much. + * Leon: In the bigger picture, we currently have unsoundness in mutable DMA operations. And all of our HILs are built without using LeasableBuffer, so we can't pass a window into a slice. This approach does attempt to solve all of those at once. Which is pretty elegant. + * Phil: Going back to something Brad brought up. When I was thinking about this and prototyping, I didn't consider something like this. I did consider something where we would have a type where a mutable or immutable buffer goes in, mut is transformed into immutable, and we can later reconstitute the mutable buffer out of it. So it would transform and then later transform back. I couldn't think of a safe way to do that without a lot of machinery. + * Phil: So, my concern from Brad is how can we do this without using unsafe? Think particularly about virtualization. There are multiple handles that are passed around. The virtualizer returns a handle and also needs to call to the lower layer that would itself return a handle. We would have two handles that would both have a reference to the buffer. + * Leon: Naive approach would be dynamic dispatch to convert these concrete types into their dynamic trait representation as a reference, and then pass that down to the hardware. This would be the simple solution to make virtualization work, because we would just for the peripheral lose the information as to whether it's a mutable or immutable buffer. But the capsule would still have that information. (Signal issues, hard to hear) + * Phil: (repeating the question) A handle structure holds a reference to a buffer. And we have a runtime lock that lets us release that. If I'm a virtualizer, and someone calls a method, I need to return a buffer. But I also need to hold sufficient state so I can reconstitute that type to pass it down to get a handle back. So there are two references to the buffer. + * Hudson: This is like TakeCell, I think. Multiple things can hold it, but only one can use it at a time. + * Phil: But we have two separate handles here. Because the virtualizer needed to return a handle and the lower layer had to return a handle. + * Hudson: It can't be. We have to just pass references to a single handle around. So there will be multiple references to a single handle. Whoever owns the actual buffer holds on to the container. But they can pass down immutable static references. + * Leon: And interior mutability will allow us to represent the locking method. That's why we can change these to trait objects. So the lower layers never know what's inside, they just know it's a buffer that is readable. + * Phil: Hmm. Hard to conceive how the call paths work here. Maybe I just need to see it. + * Leon: A quick pointer is to forget about the upcalls. Nothing gets passed back anymore. It's just passing down an immutable reference, of which you can create infinite copies. + * Leon: Playground example (warning, pretty dirty still): https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=376c9d2d22a477402d054354206207f8 + * Hudson: So the idea is that if you're a capsule that would normally be handed a big static mutable buffer, you would be handed instead a statically allocated ImmutableDMABuffer reference. It's constructed by static init in the component. This wrapper type gets passed. + * Phil: Okay, so the part that's different is that you pass static handles around. + * Hudson: Yeah, that's a key difference from my original post. + * Hudson: Still an open question about how much complexity this adds. It could make virtualization ugly. But the two traits approach could be tricky too. + * Phil: I have tried it. + * Leon: I'm going to make a PR soon with a small example. That will let us discuss and explore whether this is a better or worse approach in terms of complexity. + * Hudson: One thing you mentioned is that we could have LeasableBuffer implement these traits too. So we could have many types abstracted this way. Then presumably unlock at the end of the operation could also restore the buffer back to its full length from a slice. + * Leon: Yes. Although just a thought experiment. I haven't verified that it works yet. + * Phil: I admit that I get skittish about this. We have a narrow problem that is digest, and this is a very sophisticated solution. In terms of Rust elegance, that's great. But it could be a challenging learning curve. So I'm worried that innovative ways to use the language keep raising the bar to understand things. + * Leon: I do fundamentally agree. One note: this would have to be an unsafe trait. So that means that every further implementation of this would have to go through a lot of review and be implemented in the kernel. So I'm hopeful that the complexity doesn't spread out through the OS. So we could limit the use to the narrow use cases. + * Phil: I'm worried less about the lines of code and more about the complexity of HILs. + * Alyssa: I'm also worried about when to use this, whether to copy the pattern into my own drivers. + * Phil: To be clear, I think we should see this through and compare. Side-by-side is the best. Just that my intuition is to be wary. + * Hudson: I am interested in whether Alyssa thinks this approach seems closer to what you imagined. + * Alyssa: Closer, yes. Part of my concern is that static mut references are hard to work with. You can reborrow them, but you can't treat them as static immutable reference while still being totally sound. So I like the asref + static, but I want to think if there's a better lifetime to put here. Needing static seems to be the crux of the problem. + * Phil: I'm kind of the opinion that DMA is a special case. It's a common thing on all hardware platforms, but maybe DMA drivers should have a bit of unsafe to turn a slice into pointer and length and later just reconstitute the slice. + * Alyssa: You still have a lot of the same pointer provenance rules, just the language isn't checking it. + * Hudson: It still doesn't solve the problem of immut/mut and which to reconstitute as. + * Phil: I'm just talking about DMA, not immut/mut + * Hudson: Oh, yes. We are still thinking that DMA drivers will need unsafe. + * Leon: And I do have some basic code doing what Phil says, which we could investigate further if we want to keep the two-traits approach Phil has. + * Phil: On copies versus generalization. If it's just two, copies are fine. But once the set is unbounded, or at least more than two, generalization is needed. + * Alyssa: We could put it under a field trait for byte slices, but then we wouldn't be able to transfer other info. + * Phil: I meant that for a long time we passed around slices rather than AsRef. Transitioning would clarify some things. But I think we should think more broadly about whether we want to. + * Alyssa: If we don't generalize though, we require future drivers to either duplicate the API or not use both immut and mut. + * Phil: Anything we choose will add complexity, for sure. + * Alyssa: I'd say we definitely shouldn't require mutable buffers. But we don't want to require a copy either. Pretty big cost for read-only operations. + * Phil: But if you hit DMA, your memory has to be static. Unless we can reason about lifetime of the reference and the physical time of DMA, it's tricky. + * Hudson: For most drivers, I think it's fine for us to just have copies. Digest is kind of unique. + * Alyssa: But if it's a static mut that you pass in, then you have to allocate it. + * Hudson: Well, the capsule can just pass a short-lived buffer down, and in the kernel there's a copy into the kernel-owned buffer. That works for many HILs. But digest works with really big buffers where that becomes too expensive. + * Phil: That is another approach. Here's we're hashing entire process images. We could maybe chunk it, 1 KB at a time, which is not a _huge_ amount of RAM. But a challenge there is that the memory copy will be a significant overhead compared to the operation. + * Alyssa: Yeah, I got rid of a double endianness swap and it drastically increased the hash operation. I do understand the problem here. I would _like_ if we could take advantage of Rust reasoning about memory to avoid copies. I do like Hudson's design, although I don't think I totally understand the difference yet. + * Hudson: Hopefully we'll have a better example for next week. + * Alyssa: I do like the generic approach. + * Hudson: I like unifying over LeasableBuffer. + * Leon: Dorota mentioned that LeasableBuffer was important for them, but they had few examples of it. Really important for what they were doing. + * Hudson: Okay, I think we have a clear path forward. + * Phil: My last comment is that we should also keep in mind Butler Lampson's paper on principles of system design. Generalization is good, but not _always_ good. It's very easy to forget the tradeoffs there. (SOSP 1983, https://dl.acm.org/doi/10.1145/773379.806614) + * Alyssa: Anyone can learn to use a complex API with enough guidance. But when you throw people at it, it gets hard. + + diff --git a/doc/wg/core/notes/core-notes-2022-05-27.md b/doc/wg/core/notes/core-notes-2022-05-27.md new file mode 100644 index 0000000000..23b5cee132 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-05-27.md @@ -0,0 +1,370 @@ +# Tock Core Notes 2022-05-27 + +Attendees: +- Hudson Ayers +- Brad Campbell +- Branden Ghena +- Alyssa Haroldsen +- Philip Levis +- Amit Levy +- Pat Pannuto +- Alexandru Radovici +- Jett Rink +- Leon Schuermann +- Johnathan Van Why + +# Updates + +## Unsoundness in `VolatileCell` + +* Alyssa: Discovered some unsoundness in `VolatileCell`. It gets + allocated read-only when used as a static field and is placed into + the `rodata` section in the final binary; writing to it causes a + fault. + + The undefined behavior occurs because it can be modified with just + an immutable reference to it, and it does not have interior + mutability. + + The only reason why we haven't seen a lot of this UB is because + we're using volatile reads and writes, so these are not going to be + reordered or elided, even if Rust thinks that the memory has not + changed. + +* Leon: the fix for this would be to just have it wrap an + `UnsafeCell`? + +* Alyssa: yes. And technically it should be made unsafe in the way + it's current used, or creating it should be made unsafe. + +* Hudson: right. You can create a `VolatileCell` with any type inside, + and `get` and `set` are just safe wrappers around the unsafe + `volatile_{read,write}` internally. There's nothing stopping you + from using this on something other than MMIO registers. + +* Amit: what specifically does `UnsafeCell` do in this particular case? + +* Alyssa: this is the only way to tell Rust that some memory may + change behind a shared reference. You cannot mutate behind a shared + reference, unless its interior mutable. + +* Leon: it still does not take care of volatility, just that the + memory is allowed to change. + +* Phil: and volatile makes sure that they can't be elided. + + Also, code seem originally taken from + https://github.com/hackndev/zinc/tree/master/volatile_cell. Might be + worth informing them. It looks like their implementation changed a + lot. + +* Alyssa: `VolatileCell` is usually not what you want; what you + actually want is a pointer to volatile memory. + +* Leon: talked about this as part of the discussion around + tock-registers, and why what we're doing there is unsound currently. + + Rust is technically allowed to insert arbitrary reads to references, + even if the value isn't accessed explicitly. + +* Alyssa: yes, LLVM is technically allowed to insert spurious + reads/writes to references, but I cannot conceive why it should. + +* Amit: so the problem is that a spurious read over MMIO can have side + effects? + +* Alyssa: references in Rust are declared as dereferenceable in the + LLVM-IR. A requirement of that is that reads have no side effects + and the accesses have to behave like regular memory. + + This also holds true for `VolatileCell`, for which we technically + violate this requirement. + + However, in our codebase all reads and writes that _should_ happen + are declared as volatile, so in practice this (spurious reads) might + not lead to issues for us. + +* Leon: does sound like an important issue though. In the previous + discussion about tock-registers we concluded that we'd like to + redesign its API and we should fix these issues. + +* Phil: should be a full agenda item. + +* Leon: or good discussion for the mailing list. + +* Hudson: we should mark the constructor `unsafe` and have it wrap + `UnsafeCell` in the short term. For the more intricate issues, I + would rank this as less important than some of the other soundness + issues we have currently. + +## WiFi trait & driver + +- Alex: Alex (student) has been working on WiFi interfaces and a + driver for Tock. We are able to send and receive UDP packets. It's + not complete yet, but working towards a fully functional + driver. Question: what should the data channel look like? It works + on the Arduino RP2040. + +* Amit: what's the WiFi hardware for that? + +* Alex: WiFiNINA by U-Blox, probably an ESP32 underneath. + +* Hudson: were you able to reuse the IPv6 UDP stack, or was that too + tightly coupled to 6LoWPAN? + +* Alex: no, that is too tightly coupled. Also, currently we can only + use one UDP socket at a time. + +* Amit: does the device implement the IP/UDP stack itself? Can you + send raw IP packets? + +* Alex: yes, it's a network coprocessor. Not sure whether we can send + IP packets directly, we had to reverse-engineer the interface from + the Arduino source code. + + Project for another student is to use an STM32 with a userspace + network stack, which is going to be more tightly integrated with + Tock. + + Most of the chips around have a fully integrated IP stack. I have + been struggling to find a chip which has just a bare-bones WiFi + radio inside, not a full processor. + +* Leon: Problem with these devices is that the implementation will get + significantly more complex. You will have to provide an entire WPA + supplicant, etc. + +* Alex: Tried it on the RISC-V ESP. The WiFi hardware is not + documented, and the IDF provided by Espressif uses a binary + blob. Tried to integrate this with Tock, was not able to call more + than one function and have it crash. + +# TockWorld 5 Invites + +* Hudson: The dates are set and hopefully everyone who wanted to + recommend people to invite has sent these recommendations over. Amit + and I talked about who would be reasonable to invite. + + *[list omitted for privacy]* + +* Branden: is anyone going to invite more students? + +* Brad: thinking about it. + +* Pat: probably not. The student working on hardware CI is doing an + internship during this time. + +* Branden: from my perspective, adding a couple more people is not + going to hurt. + +* Hudson: I assume there is no concern we are going to have a strict + (e.g. < 25) limit on the room capacity? + +* Branden: don't think so. + +* Johnathan: according to Hudson's email, the plan is for the first + day to have everyone involved, and the second day be just for the + core team. Is that final? + +* Hudson: that seems like a reasonable approach for it. It allows + people who are not super involved to more easily book travel on the + second day. + + It makes sense to keep project governance to just the core team, and + this is currently planned to be on the second half-day. + +* Phil: if we choose to do that, it seems very important to hear the + people who are not in the core team what their thoughts are on + project governance (e.g. from lowRISC, OpenTitan at Google, + etc.). We can have an extended discussion within just the core team + after that. + +* Amit: I agree. Differentiating between two days makes it easier for + people who are not on the core team, but other people should be + allowed to attend still. + +* Phil: agreed, it's not secret. + +* Leon: so that would lead to essentially a differentiation between + organizational and technical discussions over the two days. + +* Pat: we have not talked about whether we want to support remote or + hybrid participants. + +* Amit: we should support hybrid if it were the case that key people + cannot attend in person, but that does not seem to be the case. If + we're supporting people attending remotely, it might make the + experience worse for everyone else. + +* Leon: could be on a per-meeting granularity? E.g. dzc-self had some + very interesting project-inspired feedback on Tock. It would make + sense to hear this feedback and incorporate it into the + discussions. dcz-self cannot attend in-person, unfortunately. + +* Phil: benefit of having occasional in-person meetings is to create a + social fabric, as well as it being a form of high-bandwidth + communication. Developing the security model at the previous + TockWorld worked really well. Other organizations (IETF) made bad + experiences with a hybrid model. + + Having virtual events to talk with users would be great as well, + just not hybrid. + +* Pat: agree with all of this, just want a finite answer to this + question. + +* Brad: would be open to having someone give a remote talk. Trying to + engage in a hybrid model over the entire day seems unreasonable. + +*settled on enabling remote presentations, but not hybrid in general* + +* Alyssa: hybrid is a lot easier for people to participate (just + live-streaming). + +* Braden: in this case it seems more about deep discussions from the + people who can make it, instead of lots of people attending. + +* Amit: if there are talks permissible to share widely, it seems + absolutely reasonable to live-stream these. Live-streaming + discussions is tougher to do technically and may have an influence + on the discussion culture. It is probably a good idea to have an + additional virtual-only event. + +* Branden: we can get a classroom which is set up with the appropriate + equipment for live-streaming (using a tool we run, e.g. Zoom or + Google Meet). + +## Mutable & Immutable Buffers for Digest and other HILs + +* Hudson: Leon and I have an update for our approach to implementing + mutable and immutable buffer passing with the Digest trait, and Phil + wants to give an update to his approach as well. + +* Phil: update relates to the current state of [PR + #3041](https://github.com/tock/tock/pull/3041). Digest needs to take + both immutable and mutable slices: mutable for data in RAM, + immutable for data in flash such as an application binary. + + Prior approach was designed to use two traits for both mutable and + immutable buffers respectively. Went through with this approach and + decided that it is not workable. For RSA this worked well, but + Digest is more complex. + + Current approach uses two functions and two callbacks for immutable + and mutable buffers respectively. When calling the mutable function, + the client receives a mutable callback, restoring the buffer + reference. + + This seems strictly better than the two traits approach as well: for + instance, the mutable function can refuse an operation when the + immutable operation is currently processing a request. With two + traits, each interface must either cache requests for them to be + multiplexed onto a single underlying hardware, or refuse operations + because a different unrelated interface is busy. + + With the single trait, it is also possible to compute data which is + located partly in RAM and flash (does not have to be contiguous). + +- Leon: Hudson and I have been experimenting with a different approach + described in [the comments on that same + PR](https://github.com/tock/tock/pull/3041#issuecomment-1130655228). + Implementing a variant of this proposal immediately revealed some + issues with it. To recap: the proposed approach defines two new + types, one for holding a mutable and one for holding an immutable + `'static` slice. We would only pass down a `'static` immutable + reference to this container to the hardware, which locks the + container to prevent the buffer from being replaced or accessed by + other layers. When the requested operation completes, the container + is unlocked prior to invoking the client callback. + + One issue with that is that capsules have been written in a way + relying on checks whether a buffer is present in a capsule-held + container (e.g. `TakeCell`). However, with the proposed approach a + buffer would always be present in this container, and the lock + indication might not be immediately set when requesting an + operation, for instance when it is queued by a mux. + + We transitioned to a second approach, still using two containers + holding an immutable and mutable buffer respectively. However, when + passing down a buffer to lower layers, both of these types lock + themselves internally and synthesize a buffer handle, with a type + independent of the original buffer's mutability. This handle only + allows immutable access to the underlying memory (something which is + sound for both mutable and immutable slices), for as long as this + handle is in scope. To restore access to the original buffer, the + handle needs to be consumed by the buffer container again. + + This infrastructure is analog to concepts already used around Tock: + the containers (`MutableDMABuffer` and `ImmutableDMABuffer`) replace + `TakeCell`s maintained in the capsules, whereas the + `ReadableDMABufferHandle` takes the place of `&'static mut [u8]` + slices passed down to peripherals and back up in + callbacks. Therefore, it does not change any semantics in existing + capsules and can be integrated easily. + +* Amit: how could this relate to `LeasableBuffer`? + +* Leon: the concept of `LeasableBuffer` can be trivially integrated + into this solution. Instead of handing out + `ReadableDMABufferHandle`s over the entire slice held by the + container, we can simply create a `ReadableDMABufferHandle` over a + window in that slice. + +* Hudson: the current interface does not support this. We would add a + function similar to `borrow_readadble`, which returns a handle over + a subslice. + +* Leon: we can even go ahead and define a function akin to + `return_readable`, which would move the borrowed window forward in + the buffer. This would be particularly useful for chunked + operations. + +* Amit: that seems like it could make a lot of our internal interfaces + much simpler. Rather than being a reasonably complex type + infrastructure to solve a narrow problem, it seems this has the + potential to become a generally useful (and similarly complex) + buffer type to pass around the kernel. + +* Leon: if we were to integrate `LeasableBuffer` into this natively, + given that we only need LeasableBuffer to maintain a subsliced + region on a `&'static mut [u8]` buffer across call stack + invocations, we can get rid of that infrastructure entirely. + + There is one major drawback with this approach: given that we coerce + both `MutableDMABuffer`s and `ImmutableDMABuffer`s into a single + `ReadableDMABufferHandle` type, we need to keep track of the + container which created a given handle, and to only unlock locked + containers with matching handles. For this we use a static counter + incremented once for container allocation and panic when that + wraps. Should not be a problem, given we have only a constant number + of containers allocated at board initialization. + +* Branden: for the nRF52, DMA only works from RAM. All peripherals + have their own DMA engine, but all of these are limited to RAM. + +* Leon: excellent question. If we were to change these functions + changed across our HILs, it might well happen that the nRF52 is + exposed to buffers over flash memory. Not sure whether we want to + incorporate these restrictions into the type system, but we should + return a runtime error in such cases. + + Plan is to extend this implementation to the `Digest` HIL to have a + direct comparison, extend it and write tests. + +* Hudson: should be noted that this is currently only for operations + which don't mutate memory. We might want to extend this to work for + mutable operations as well. + +* Phil: it sounds like these approaches are solving different + issues. Leon's and Hudson's approach looks down at the DMA level, + whereas my issue is much narrower, just for the Digest + implementation. + + For me the Digest is blocking for end-to-end verification of + processes. + +* Leon: the two-methods approach seems like a good short-term solution + just for `Digest`, whereas our approach might be something we'd like + to integrate in the long term. + +* Phil: Agreed. diff --git a/doc/wg/core/notes/core-notes-2022-06-03.md b/doc/wg/core/notes/core-notes-2022-06-03.md new file mode 100644 index 0000000000..532364256f --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-06-03.md @@ -0,0 +1,228 @@ +# Tock Core Notes 2022-06-03 + +Attendees: +- Alyssa Haroldsen +- Brad Campbell +- Branden Ghena +- Hudson Ayers +- Johnathan Van Why +- Leon Schuermann +- Pat Pannuto +- Philip Levis +- Vadim Sukhomlinov + +## Updates + * Leon: Small update from my side. I've been working on the DMA buffer story + and the process buffer story. Trying to reconcile the two. Writing Miri tests + to make sure the solutions are sound in the Rust sense. Implementing the + `LeasableBuffer` abstraction we spoke about last week as part of the DMA + buffer. Hopefully there will be more news and actual code output next week. + * Hudson: Amit is not here but he submitted a PR this past week that addressed + some of the concerns with `VolatileCell` that Alyssa had brought up. Alyssa, + I hope you're happy with how that ended up? + * Alyssa: Yes, it's much better. + +## TockWorld + * Johnathan: I wanted to check in and make sure we're ready to invite people. + * Hudson: Yes, we are ready to send out invites. I'll send out an RSVP form to + share. Were you planning to personally invite some of the people you asked to + invite? + * Johnathan: Yes, that was my plan. + * Hudson: Okay, that makes sense. + * Johnathan: It sounds like I should wait for the form then I can send invites. + * Hudson: I'll paste it into chat. + * Phil: It sounds like we are more than just ready to send out invitations -- + we can send out invitations. [ed: some emphasis was lost when transcribing to + text] + * Hudson: Yes + * Hudson: Johnathan, so we don't duplicate invites, who are you planning to + send invites to? + * Johnathan: I was planning to invite all the invitees I suggested. + * Hudson: Alyssa or Vadim, would you like to send invites to the other Ti50 + folks? + * Alyssa: Yeah, I can. What do I need to get to them, logistically? + * Hudson: There's an RSVP form, and you can add some descripton. + * Alyssa: Can I send an open invite to the team, so that anyone who wants to + come can come? + * Hudson: We definitely had a desire to not have too many total people. There + was a limit above which Google people would be unable to give talks. I don't + want to say no, don't do that, but we didn't really talk about it. + * [Some discussion omitted for privacy. Conclusion: Johnathan will invite the + people he recommended inviting, Alyssa will invite Ti50 team members] + * Hudson: Alyssa, if you want to invite more people from Ti50 that's probably + fine but I think Amit should have final say on that and he's not here at the + moment. + * Alyssa: Okay + * Branden: Logistically, we have more space, but I'm concerned that if there + are a bunch of random people who are not really interested in Tock it may not + be interesting to them. + * Phil: If it isn't useful, they might note come. Another concern as somebody + who sits on both sides, I think it's important to avoid having too many + people from any one side. So for example, a couple more people from Ti50 -- + especially if they are working on different parts of it -- is fine, but we + wouldn't want 50 people from Stanford to show up and drown out the + discussion. I'm not saying a couple more people is an issue, but we don't + want the meeting to be dominated. + +## UART HIL TRD PR (#3046) + * Hudson: A group of students at Stanford spent some time this quarter to port + the sam4l and nrf52 chips over to the new UART HIL that Phil proposed a few + months back. This PR is currently in draft, because it cannot pass CI until + all the other chips and capsules are ported to use the new HIL. Only two of + the four capsules that use UART have been ported, and there's a healthy + collection of chips that need to be ported over. I believe that yesterday the + TRD for the new HIL was merged. It can still change if people run into + problems with implementations. Have not run into problem yet so that's a good + sign. I wanted to bring this up as a call in case anyone has one of these + chips. It would be great to get help porting them over, as unfortunately this + has to be an atomic update porting all these over at once. + * Phil: We do have people who are in charge of each chip, right? + * Hudson: Yes, with the exception of the ones that are maintained by the core + team blob. + * Leon: How do we propose updates? Do we just push to the branch, or send PRs + onto the branch? + * Hudson: I asked Colin to to give us write access to the PR, so we can push + directly. If you don't have permission, you can send a PR to the branch. + * Hudson: Try not to be too intimidated by the amount of changes in the sam4l + and the nrf, because those are two of the more complex UART implementations. + For a lot of these other chips, there's no support for receive or aborting + transmissions etc so the changes will be very simple. Most of the changes are + around how to correctly handle aborts. For chips where aborts aren't already + supported, changing to the new HIL should be pretty mechanical. + * Leon: On perhaps this is a chance to properly support aborts. + * Hudson: That's true. Given this has to be atomic, and touches enough files + that there is significant maintenance burden, I think there is incentive to + get a minimum change in soon. If that additional work takes a lot longer, it + creates a lot of maintenance work for Colin or myself to do the rebasing. + * Leon: That makes sense. + * Phil: Hudson, can I make a suggestion? Can you -- by each chip -- put, with a + question mark, who is nominally in charge of that chip. A question mark + because we want to check with them. Then I can reach out to everybody and let + them know that we want to do this sooner rather than later. If somebody says + I can't do that, we can remove them. + * Hudson: Sure, that's a good idea. + +## `LeasableBuffer` + * Phil: To give some context, it has to do with the digest API and how it + interacts with `LeasableBuffer`. I'd like to propose a change. + * Phil: *Pastes + https://github.com/tock/tock/blob/35cfc73e2b70023a91bbd550b6416366c3ca4911/kernel/src/hil/digest.rs#L74 + into chat*. + * Phil: For `add_data`. To give some background. Digest has 3 traits. There's + adding data to something you're going to digest -- a mechanism for taking a + large piece of data and turning it into a small piece of data with status and + integrity guarantees. It takes a `LeasableBuffer`. The idea is you could add + some big thing like a process image, and use the `LeasableBuffer` to limit + the size of stuff you're digesting over without messing with slices. As + Hudson pointed out, technically the implementation can use the whole slice, + so it doesn't provide privacy, but it's an easy way to couple the three + values down: slice, start, and end. + * Phil: The question I have is the return type of a success/OK. What the API + does is if you get an `Ok`, you get a value back of how much is going to be + digested. When you get a completion callback, + * Phil: *Pastes + https://github.com/tock/tock/blob/35cfc73e2b70023a91bbd550b6416366c3ca4911/kernel/src/hil/digest.rs#L14 + into chat*. + * Phil: it doesn't give you a `LeasableBuffer` back, it gives you the actual + slice. My thought is that one of these two things should be true: either + `add_data` should operate on the entire active region of the `LeasableBuffer` + or it's an error, so `Ok` should be a unit type, or `add_data_done` should + pass back the `LeasableBuffer` with the active region being the data + remaining. + * Phil: This idea that I call it and need to track how much it was done and + also track where I was in the `LeasableBuffer` is weird -- tracking + `LeasableBuffer` state and replicating it repeatedly. + * Leon: It makes total sense. I've encountered this essentially every time I + try to use `LeasableBuffer`. The first time I saw similar code, I was + surprised to see you were calling `reset` or transmutting it back into a + mutable slice in the peripheral driver, while I was passing the + `LeasableBuffer` back. That is definitely a part which is underspecified in + the current API. I don't have an idea for what I think a sensible API would + look like. Closing in on the discussion we had last week on a potential DMA + buffer, we may want to extend this API to cover the functionality of + `LeasableBuffer`, as it is solving a lot of these issues by its design. I can + go into more details if you'd like, but we could continue discussing the + current `LeasableBuffer` too. + * Phil: Yes, it sound like you and I should chat. I was trying to find a + surgical change to make this API more consistent as opposed to a more + significant change. + * Phil: Do people see what the issue is? + * Hudson: I think there are two issues. One issue is it's not clear when you + should be calling `reset` on the buffer and our HILs don't set a clear + precedent for how they should be `reset`. The second problem is that often + when you're using `LeasableBuffer`, you also want to be tracking how much of + the buffer that was initially shared has been consumed, and it's not clear + whether `LeasableBuffer` should be updated to track the amount of the buffer + that has been consumed or whether `LeasableBuffer` is just for the higher + layer to specify the subset of the buffer it wants to share. + * Leon: I think that's a precise description of what's happening here. I always + viewed `LeasableBuffer` as a mechanism just for the client to limit what + region of the buffer is designated for the peripheral to operate on. I fear + that if we are also using it as a mechanism to return feedback on what the + peripheral has actually done, it will get more confusing because you have + influences from both sides, which are fundamentally different aspects of the + implementation. On one hand, you want to pass a buffer which is just an + implementation detail of us using static mutable slices which we cannot slice + easily, and on the other we want to pass some feedback on what data the + operation has operated on. I don't feel comfortable mixing these two + concepts. It also has the disadvantage of losing track of what you originally + passed to the peripheral. + * Phil: I would disagree on that point. I think it's a type. Just as we can + pass a bunch of integers, which mean different things depending on their use. + One answer is that the call to `add_data` doesn't return a `usize`, and an + `Ok` then as well as a callback which indicates a success means that the + entire active region of the `LeasableBuffer` as passed was added. You can't + do like a partial add and return `Ok`, which is what happens now. That way + it's then fine to pass back the slice or `LeasableBuffer`, that's less + important because from the caller's standpoint you can do either one from + either. + * Phil: I think the idea that I call and get an `Ok` and get a type, and when I + get the callback, the `LeasableBuffer` tells me how much I have left, that is + valid. This data structure indicates how much is remaining. I kind of prefer + the first approach -- a successful operation means the entire active region + was added -- because it simplifies the client code. It forces a state machine + at the bottom for hardware that can only digest a certain number of things in + a particular interrupt, for instance OpenTitan, but it means that you don't + have to have that state machine a layer above. + * Leon: I agree. I think we're still trying to solve two different problems + here. We have the problem of hardware potentially not supporting arbitrary + buffer sizes in a single operation, and the problem of us using mutable + slices which we can't reslice using Rust slicing operations. If we wanted to + ignore that latter restriction and only focus on hardware not being able to + operate on arbitrary buffers in a single operation, it becomes immediately + obvious as to what the solution would be. Of course we need to pass back + additional information on what was operated on or refuse operations with too + much data to handle at once and pass back feedback on an appropriate buffer + size. I think it's still fine to incorporate both use cases into a single + type, but they must be explicitly designed into the API. Could use a type + state construction which passes in one type which describes the region which + is active, and would convert that into another one which says "I retain this + designated region", but additionally pass back information on what I'm + actually working on that. Having that implicit in resizing the buffer seems + weird to me. + * Phil: I view it as if I pass a list. Pretend we're not in Tock land and have + `malloc`. I pass in a list, and it gives me a list back, which is what it + didn't do. That seems like a valid use of the list. Regardless, an easy way + to dodge this without adding types is to say that an `Ok` means that it did + the whole active region of the `LeasableBuffer`, and the low level driver + just needs a state machine. + * Leon: I think I prefer that. For exceptional cases where that's unreasonable, + we can still pass back some form of feedback about how much was actually + processed. + * Phil: You mean in an error? So if you do `add_data` and it gives `Ok` but it + can't process the whole buffer, when you signal the error case in the + callback you tell it how much was processed? + * Leon: Not what I was going for, but a good idea. What I was going for was if + we have a peripheral where processing the whole buffer at once isn't + reasonable, we can add feedback to the callback to say "I'm giving you the + original slice back, but I only took N bytes of that". + * Phil: That seems like a really strange edge case. You can always do things in + software. Fundamentally, digest operations should not be limited in how much + data they can cover. In that case, you don't use the digest trait. + * Leon: I was just trying to make sure that even if we were to go with the + route of saying an operation is either okay or error with an entire slice + processed, then that doesn't prevent us from ever processing partial slices + in the future. + * Phil: I see. + * Hudson: I'm w/ Leon, that sounds right to me. + * Phil: Lets talk more about this next week. That's it for me. diff --git a/doc/wg/core/notes/core-notes-2022-06-10.md b/doc/wg/core/notes/core-notes-2022-06-10.md new file mode 100644 index 0000000000..b8aba7150b --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-06-10.md @@ -0,0 +1,187 @@ +# Tock Core Notes 2022-06-10 + +Attendees: +- Hudson Ayers +- Alyssa Haroldsen +- Amit Levy +- Alexandru Radovici +- Vadim Sukhomlinov +- Johnathan Van Why + +# Updates + +- Leon: Been in contact with a researcher of Tampere University in Finland. [The + group](https://sochub.fi/) is building a custom ASIC with various subsystems + on there (e.g. DSP, AI accelerator, Ethernet) and have a few RISC-V cores in + there. They would like to use Tock as an orchestration and general purpose OS + on there. Use Rust excessively, so Tock is really attractive. Their CPU is the + Ibex in the rv32e variant (16 registers). + + They have an initial tapeout and perform the hardware bringup now. Tock's + requirements may influence the second hardware revision, for instance to + include an MPU with the processor, etc. I'm chatting with them regarding + Tock's requirements and how we can help with the bringup. + +- Alexandru: Started unifying the text screen and graphics screen into a single + HIL in response to discussions with dcz. Still a draft, feedback is welcome! + Used SPI TRD as an inspiration. + +- Leon: Have the Jazda devboard, can test potentially. + +# VolatileCell Licensing Issue + +- Johnathan: [posts into chat] + + ``` + Commit that added VolatileCell to Tock: + https://github.com/tock/tock/commit/852b757ba5e3617c39204bcca0db26ae617e9186 + + Source that VolatileCell was copied from: + https://github.com/hackndev/zinc/blob/master/volatile_cell/lib.rs + + Alternative VolatileCell that Amit shared with me: + https://github.com/japaric/vcell + ``` + + The source code of `volatile_cell.rs` includes a link to the original source, + which is Apache-2.0, but not also MIT-licensed. We copied it without the + license header. + + Amit found another `VolatileCell`, which is not stemming from the same + codebase, dual-licensed under Apache-2.0 and MIT. We could swap it out to be + in the clear. + +- Leon: Our `VolatileCell` does not have many LOC, the implementation is + trivial. Doubt that licensing would be a proper legal issue. Arguably there is + no other way to implement such a construct at all. + +- Alyssa: There are other ways to implement volatile accesses generally, but for + the specific `VolatileCell` there is no other way the basic `set`/`get` + functionality could be implemented. We at most copied these methods, the + documentation etc. are not copied. + +- Amit: The old repo is archived, a reasonable approach might be to reach out to + the original author, explain the situation and get a written exception that + this is acceptable use. + +- Johnathan: Seems to be more than one author. Would need to get everyone's + approval. Some of them touch irrelevant parts / whitespace, so need to count + exactly how many to contact. + +- Amit: Need to look at the code from when we've copied it. + +- Johnathan: Copied after the last modification to the original. + + There seems to be only one change which affects the type after the initial + commit and we've undone that change within our codebase. + +- Alyssa: What would change when we'd use japaric's variant? + +- Leon: seems almost identical, I don't think it'd require any significant + changes. + + We wanted to eliminate usage of `VolatileCell` within tock-registers + anyways. Might just push forward on that issue and then remove `VolatileCell`? + +- Amit: I will email the author. I am nearly certain that this code was not + copied, but I just reimplemented this very small block of code. I don't know + how meaningful it is to copy it over from some new repository. The more + pressing issue seems to legal certainty for when the code is used in actual + products. + +- Alyssa: Would be shocked if these lines are legally enforceable. + +- Leon: Agree with the sentiment that we should give proper attribution wherever + possible. Contacting the author might be an elegant solution. + +- Hudson: The history of the `VolatileCell` file in the Zinc repository is not + complete because it was moved several times. The pre-move unfortunately goes + back to the initial commit of the entire repository. + +## TockWorld 5 Remote Presentation & Recording + +- Johnathan: Chris Frantz might want to give a remote presentation at TockWorld + but knows only in early July. + +- {Amit,Hudson}: That seems fine. + +- Johnathan: Question from Luis Marques as to whether there are going to be + recording or transcripts of the presentations at TockWorld. + +- Leon: We did agree on live-streaming presentations already, would be easy to + just record using the tool we use to stream if the presenter is okay with + that. + +- Hudson: Yes. Probably would not want to record the discussion. + +- Amit: Branden also seemed to believe the rooms are set up with AV equipment + anyways. + +## HMAC and Digest HIL - Mutable/Immutable Buffer Approach + +- Hudson: Phil wanted to mention that he has published an updated HMAC HIL and + within that PR a new TRD for Digest. This represents Phil's approach to + calculating digests over that located in flash, memory, or partially in both + regions. This is important to check the integrity of processes which have + their code stored in flash, to avoid copying the entire process. + + This might be at odds with the approach Leon and I have been pursuing. Phil's + reasoning is likely that agreeing on his approach would unblock the Digest and + HMAC HILs. But perhaps Leon's codebase is in a state where we can also talk + about using that instead. + +- Leon: Been working on Miri tests on the new approach, testing it in a way + which resembles typical usage in Tock. Have not noticed any unsoundness + yet. Needs more work. + + In a previous call, at the very end, we talked about a good compromise: Phil's + approach is really good at resolving this single issue in the Digest / HMAC + scenario. Hudson's and my approach seeks to solve more issues, such as the + current DMA buffer unsoundness, LeasableBuffer integration, etc., all in a + single solution and type infrastructure. + + Our approach still seems viable and nice, but it needs more time. It seems + trivial to migrate Phil's approach to ours at the given time. I'd be happy + with merging Phil's approach first and then migrating later. + +- Hudson: I agree. I still prefer our approach, but I am sympathetic that Phil + is blocked on this. It does not seem like merging his version now is going to + cause trouble when we want to migrate it to our approach. + +- Leon: Perfect is the enemy of good. Once Phil's approach is in, the first step + to supporting mutable & immutable buffers in a single API is done. Our + approach will be an improvement on that interface, if it works. Just having + Phil's approach in, though, I going to provide justification to continue + working on this, and extending to existing subsystems. + +- Alyssa: Also comes with an example on how the existing API is used in + practice, which is helpful. + +## Proposed UART HIL TRD Changes (draft 5) + +- Leon: Proposed some changes to the just merged draft 4 of the new UART HIL + TRD. Noticed a few oddities while implementing the new HIL's interface for the + `sifive` chip. I realize many of my proposed changes are subjective in nature, + however for further chips it's important to have a common ground to design + compliant implementations. + +- Hudson: I'm fine with these changes. Likely Phil just needs to do a pass over + this. + +- Leon: Will shoot him an email, thanks! + +## Ti50 Team Tock Survey + +- Alyssa: was thinking about sending out a survey to the Ti50 team before + TockWorld about the painpoints and things they like about Tock. (There is one + major painpoint w.r.t. to size and fixed size increase per app added.) + + Would that survey be worth discussing a TockWorld? What do people think? + +- {Amit,Hudson,Leon}: Sounds great! + +- Alyssa: More in the form of presentation or discussion? + +- Amit: Likely depends on the results of that survey, and how much time that + would take to present. I'd be very happy about a presentation focusing on the + painpoints of the Ti50 team. diff --git a/doc/wg/core/notes/core-notes-2022-06-24.md b/doc/wg/core/notes/core-notes-2022-06-24.md new file mode 100644 index 0000000000..07d2395f13 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-06-24.md @@ -0,0 +1,276 @@ +# Tock Core Notes 2022-06-24 + +Attendees: +- Brad Campbell +- Branden Ghena +- Alyssa Haroldsen +- Philip Levis +- Amit Levy +- Alexandru Radovici +- Leon Schuermann +- Vadim Sukhomlinov +- Johnathan Van Why + +## Updates + +### Digest HIL and Software SHA-256 Implementation + +- Phil: Digest HIL updates and software SHA-256 implementation are now + merged. There was some discussion last week about the way + `LeasableBuffer` is to be used. Opinion was that it's okay the way + it's implemented now, but might be revisited in the future. + + Fought this for over a week because of Tock OpenTitan instructions + had all types of errors in them, hope to iron this out. + +### UART HIL Chip Implementations PR: + +- Phil: Also, there is the draft PR for update to UART HIL: + https://github.com/tock/tock/pull/3046. + + Students at Stanford started porting this HIL to some. <...> + +- Leon: Ported the `sifive` chip, was reasonably straightforward. + Couldn't really test whether my port works, given there seems to be + some other crucial infrastructure missing (IIRC DebugWriter). Should + we work on that first, so that people porting chips can test their + changes immediately? + +- Phil: Absolutely. You might want to explicitly write down what the + issues are on the PR, so we can get them fixed. + +### Bootstrapping USB on the RP2040 + +- Alex: Trying to figure out how to bootstrap USB on RP2040 chip. Does + someone have experience with this and could join a call? + + Registers seem to setup correctly, if we boot nothing works. If we + boot the RP2040 with the official SDK, and then booting Tock without + power cycling, USB CDC works. + +- Phil: I have some experience working with that on OpenTitan. Let's + chat after the call. + +- Amit: Also happy to help. + +- Alex: Enumeration works, the communication is jammed when we need to + properly send data. We send some bytes but just get zeros. + +## Potential System Call Encoding Abstractions / Refactoring + +- Amit: Vadim wanted to talk about refactoring of the system call + handling and integration to improve performance. + +- Vadim: Currently focusing on code size rather than performance, but + this could ultimately also aid in performance. + + For instance, to perform cryptographic operations, we need to pass + different kinds of buffers to the kernel, execute an operation, and + subsequently unallow these buffers to be able to reuse them. + + Commonly, we need to provide 5 different buffers. This will then + translate to 5 `allow` operations, one `subscribe`, one `command`, + one `yield`, and then 5 `allow`s again. This results in almost 500 + bytes of just system call overhead in the binary. + + It would be possible to change the system call ABI to a more + efficient one, when it would be easier to swap out the current + system call handling implementation in the kernel. We cannot + currently provide our own syscall enum, given there are + interdependencies in the kernel for various system call variants in + the enum. + + Maybe it's worth looking into redesigning the kernel to just provide + functions for handling system calls and allow the system call class + to be sourced from different location. So let the kernel provide all + infrastructure for handling system calls as a library and allow a + custom system call class to be sourced from outside the kernel. Our + own implementations would use the exposed kernel infrastructure, but + we can change how system call information is transferred. + + This would allow us to easily run experiments to see what type of + encoding would work best for us. + +- Leon: Remember well working on that particular part of the kernel + during the Tock 2.0 system call interface redesign, which + significantly changed the way system calls are handled throughout + the kernel. + + We did try to make it as flexible as possible to introduce new + system call encodings. By defining the `Syscall` enum in the first + place, we have a standard interface to inject system calls into the + kernel regardless of their encoding. However, this infrastructure is + insufficient if you want to introduce entirely new classes of system + calls without modifying the core kernel code. + + Question are: + - can we make it as efficient to have an additional layer of + indirection to route system calls through the kernel _without_ a + fixed enum defining the different types of system call variants in + existence. + - do we want to allow unbounded flexibility to introduce new system + call classes externally (somehow) in the first place? + +- Vadim: Routing should be up to the system call implementation rather + than the system call structure, but would be a significant change + throughout the kernel. I will need to change the ABI, to come up + with more efficient ones specifically for my target platform. From + the application POV, this would be hidden by the usual system call + abstraction. On the kernel side, it should be up to the system call + implementation what to do with a specific set of registers. + + Instead of having the kernel handle the system calls, have the + architecture call functions within the kernel in response to system + calls. This can then also decide on how to route system calls. + +- Leon: What you are describing is that you want to define your own + ABI. This is deliberately supported in the kernel as of today's + implementation. If you look at `syscall.rs`, it contains some + reference functions to create the `Syscall`-enum variants based on + passed register values. However, these functions are not mandatory + to be used, and they are called from the architecture itself. You + can define a different architecture, and just decide not to call the + kernel-provided marshaling and unmarshaling functions for encoding + and decoding system call and return parameters. + +- Vadium: if you look at the `Syscall`-enum, the ABI is hard coded + within this infrastructure. If you look at `process.rs`, I cannot + change something there as it is intertwined with the kernel. + +- Alyssa: I think we'd like a concrete example of what it might look + like to implement custom syscalls. I think it is possible in the + current design. + +- Leon: When you do not want to introduce proper new system call + classed, but just have a more efficient representation of system + calls in registers, or represent multiple system calls being passed + as a batch, this is all possible within the current infrastructure. + + Our `arch`s are essentially scheduling a process, and upon returning + from the process in response to receiving a system call use a + kernel-provided helper function to decode this system call, and then + finally call into the kernel with the constructed `Syscall` enum + variant. By not using this kernel-provided helper function, you can + define an arbitrary encoding of system calls and even schedule + multiple kernel-syscalls (e.g. `allow`, `subscribe`, `command`, + `memop`, etc.) in response to a single hardware system call. + +- Alyssa: Would this require us to carry patches against the Tock + kernel? + +- Leon: No, because you'd choose to just not call these functions. All + these functions do is they get passed a few registers, and they + return you a variant of the `Syscall` enum. + +- Vadim: That is what I want to change. + + For instance, `process.rs` mandates that a command system call gets + passed parameters one and two. I want to also pass parameters three + and four. + +- Leon: There is a distinction to make between the kernel-concept of + system calls, of which there is a defined set with a fixed number of + arguments, and hardware system calls which can encode one or + multiple of these kernel system calls. + + To change the set of kernel system calls itself is going to be much + harder, as this is the one interface we carry around the kernel, up + to the capsules as system call driver implementations. + +- Alyssa: What helper function are you referring to, Leon? + +- Leon: `syscall.rs`, there are the methods `from_register_arguments` + and `encode_syscall_return`. These just parse registers to form a + `Syscall` instance or encode a `SyscallReturn` into a set of + registers respectively. + +- Phil: This all seems like a technically subtle discussion. It might + be best to have an implementation to talk about. + +## `static mut` Globals in CI + +- Phil: Currently, Alistair's OpenTitan code has standard Rust + test. However, Rust's test cannot take any arguments. Thus anything + you want to run a CI test on needs to be passed through a `static + mut` global. However, this is something we have transitioned away + from. Do we want to have boards which are being tested in CI have a + global `static mut`s? + +- Alyssa: Need to understand the use case better. There's other ways + to pass data into tests, e.g. through context parameters and type + parameters. + +- Phil: How do you allocate the memory for these objects and where are + they allocated? How would type parameters solve this? + +- Alyssa: Easiest way to allocate memory returning a `&'static mut` + reference without allocating a `static mut` global is to leak a + `Box`. + +- Phil: Use case is for tests to build on each other. For instance, we + can have one test which tests initialization of the console, the + second test uses this initialized console to print something. + +- Amit: Generally, requiring these boards to have `static mut`s seems + undesirable. If there is a way to move away from that, we should + pursue this. + +## Linked List Interface Redesign + +- Leon: We had an issue at some point where people built linked lists + in Tock, but accidentally inserted an element twice which resulted + in a cyclic list. + + Linked lists are built in Tock as a list of references using + traits. These traits can be used to define an implementation of the + `next`-method, yielding the next list element, but this + implementation is user-defined. This makes it impossible to, for + instance, check whether a list would be cyclic if an element is + inserted, given that the `next`-method can rely on arbitrary + internal state (is not pure functional). + + This PR defines a generic list interface as a replacement for the + current infrastructure, where list nodes are still built using + traits, as well as a simple list with list nodes built through + predefined types, giving more strict guarantees. + +- Amit: Are there downsides to this new downsides? + +- Leon: There might be more cognitive overhead in understanding the + provided infrastructure, but there are also additional guarantees + introduced for usages of the simpler list interface. + +- Amit: Seems good generally. Probably needs just review, might ask + Leon to walk me through the code. + +- Phil: There is this comment in the PR: "However, it also makes + implementing basic consistency checks as proposed in #2773 + impossible. For example, because the list is entirely dynamic and + implementation dependent, and because nodes are not managed by the + list itself, it's impossible to tell whether a list will, during + runtime, result in a loop. This makes handling lists difficult, + especially for simple cases where all of this flexibility is not + needed." + + Can you explain this? + +- Leon: If I recall correctly, the current interface returns list + nodes using a trait, which then provide access to the underlying + element. This is talking about this original structure, and is + further relevant to the generic list implementation retained with + this proposal. For instance, in the implementation of the `next` + method, it is feasible to return a reference to `self`. To + illustrate, this might be useful to represent a sequence of numbers + through a list interface, without actually allocating individual + list elements for each number, by returning an internal counter and + incrementing it in for each call to `next`. This makes it impossible + to implement basic sanity checks. + +- Phil: The worry is that if people are implementing crazy lists, they + will introduce bugs? + +- Leon: Right, that's exactly what this PR should protect against. + +- Amit: Okay, will go through this with Leon. One of those PRs where + the diff is not very helpful, but comparing the two versions + in-depth is. diff --git a/doc/wg/core/notes/core-notes-2022-07-01.md b/doc/wg/core/notes/core-notes-2022-07-01.md new file mode 100644 index 0000000000..5fe67896a2 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-07-01.md @@ -0,0 +1,182 @@ +# Tock Core Notes 2022-06-10 + +Attendees: +- Hudson Ayers +- Alyssa Haroldsen +- Alexandru Radovici +- Vadim Sukhomlinov +- Johnathan Van Why +- Brad Campbell +- Leon Schuermann + +# Updates + +- Alyssa: Found a bug that generated 3k of size savings for us! All we had to do + was change the representation of ErrorCode to be u32 instead of u16. + +- Alyssa: The problem is that the Rust ABI for risc-v makes the callee + responsible for ensuring the top bits of a register are cleared. So the + compiler inserts assembly instructions to set the top bits of a u16 to be 0 in + any function that accepts a type which is repr(u16). + +- Johnathan: I also wrote some inefficient code for converting error codes in + libtock platform. + +- Alyssa: I think the problem I described is the larger one. Here is a godbolt + sample: https://godbolt.org/z/rxKqjqWfd + +- Alyssa: This is theoretically a breaking change for libtock-rs but it should + lead to compiler errors for any users. + +- Johnathan: I think that change should be fine to submit upstream for the size + savings. Libtock-rs is not technically stable. I understand some theoretical + concern about unsafe uses of the type but in practice I do not expect + problems. But keep in mind that no Tock kernel version has been released which + libtock-rs targets + +- Alyssa: Does that mean Ti50 will have trouble updating libtock-rs? + +- Johnathan: Only if you are not on a kernel after the PR that added the allow + swapping protections. + +- Hudson: That is a good reason that we should attempt to release 2.1 sooner + rather than later, since libtock-rs is pretty usable now. + +- Brad: I have a student who is making progress on dynamic app loading. Current + status is he has an app which has a binary of the blink tbf inside it, the app + passes it to the kernel and the kernel loads it into the process array and + runs it without resetting the kernel. Hope is a PR soon. + +- Leon: That is awesome, I know Dorota would love to use that. + +- Hudson: Recommend people book hotels for TockWorld. + +- Brad: Alistair has booked his trip for TockWorld! + +# AppID PRs + +- Hudson: I think we should all take a look at Phil's AppID PRs to the Tock + kernel and elf2tab / tockloader. Let's take 10 minutes and read through the PR + description and look at the general layout of the code and see if we have any + high level comments for Phil. I think he would love detailed reviews when + people get the chance. + +- all: 10 minutes to read + +- Hudson: One thing I concerned about is this breaking change for updating the + value of the `init_fn_offset` field to reflect what the TBF documentation says + it should mean. I don't think we have a great way to support that type of + breaking change in a way that is not gonna be pretty poor from a user + experience perspective. I was wondering whether we could just change the + documentation instead. + +- Alyssa: Most of my concerns surround code size / RAM overhead. Specifically + the impact per-app. We verify our entire firmware image at once so for us + validating apps dynamically is not a business need. Wondering whether this + could be an optional feature. + +- Johnathan: Well this does not require any cryptographic implementation, it is + optional for the kernel + +- Johnathan: Either way you need an ID mechanism for apps for security, even if + you verify images externally. How would you do IPC securely? + +- Alyssa: Well we use a capsule for that where apps subscribe to dispatcher + channels, and name targets to send to. + +- Leon: As far as I know if you do not include footers and do not supply a + verifier this should be pretty minimal overhead. This mechanism is optional! + +- Alyssa: The Process struct adds fields, it is over 1kB in size already, + per-process. + +- Leon: This adds just two words to the struct. Those are references to the + location in memory of those footers. + +- Alyssa: Oh so those fields are just raw pointers? + +- Leon: They are actually references but I think they should be pointers looking + from a safety perspective. + +- Alyssa: Ok so long as it is not a significant code size or RAM impact I am + happy but I think we should have a test bed set of applications to test things + like code size impact. Do we have that? + +- Hudson: Not completely -- for most boards the kernel is built with an + assumption of 4 processes and we have github workflows that track the size + impact of a given change. And for this particular change I don't think there + should be any change to the size of apps themselves if they choose not to add + these footers. + +- Alyssa: So what are the results of that workflow for this PR? + +- Leon: It looks like Phil has updated all boards to actually use this mechanism + now, so we could not easily see what the overhead is for someone opting out. + +- Alyssa: How do you turn this on/off? Is it a cargo feature? + +- Leon: You use `load_processes` instead of `load_and_check_processes`. + +- Hudson: I think this is a fair concern, we definitely need to benchmark this + before we merge it, and we should probably have some of the example boards + upstream not use the feature so we can ensure that option continues to work. + +- Hudson: It looks like this feature adds about 2.5 kB for all of the upstream + boards now, so that should be an upper bound on the overhead. That is true for + both Hail which has 20 apps and Imix which has 4. + +- Brad: Why would the number of processes in the process array matter? + +- Hudson: I guess it shouldn't + +- Alyssa: It can, in isolated cases. + +- Johnathan: 2600 bytes is a pretty large increase. Does this include a crypto + implementation? + +- Hudson: Yes, I think so + +- Johnathan: Oh, then that makes sense. + +- Hudson: Yeah we need to be sure if you don't use this you don't pay the cost + of the SHA implementation. + +- Brad: Can we turn off the feature for a board real quick and see the overhead? + +- Hudson: (tried to do this, did not have luck as it is a little more involved + than I thought). + +- Leon: I think we need to get rid of some dynamic dispatch in this + implementation in favor of generics so that dead code elimination can work + properly for this code. + +- Alyssa: Maybe I should try to upstream something like our current dispatcher + implementation -- it does provide pretty simple and efficient IPC + +- Hudson: We are definitely interested in improved IPC + +- Brad: Seconded + +- Leon: Improved and safe IPC! + +- Alyssa: I do not think ours even includes any unsafe + +- Johnathan: If we want to add IPC that does not use AppID we will need to + change the threat model. So that apps cannot impersonate each other. TicKV + also is not compliant with this threat model because there is no verification + that apps are who they say they are. + +- Alyssa: Yeah I see. If you want to establish trust on top of IPC I understand + that. You could have a list in the kernel of what channels apps can subscribe + to. + +- Johnathan: I think we should meet one-on-one and discuss this. + +- Alyssa: yeah + +- Leon: I think the purpose of this PR is a one-stop-shop for identifying and + authenticating an app within the Tock ecosystem. Ideally this would be a + unified thing. + +- Johnathan: Well, storage is not included, but we decided that intentionally. + At least for dynamically loaded apps. diff --git a/doc/wg/core/notes/core-notes-2022-07-08.md b/doc/wg/core/notes/core-notes-2022-07-08.md new file mode 100644 index 0000000000..bbcc602dfb --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-07-08.md @@ -0,0 +1,73 @@ +# Tock Core Notes 2022-07-08 + +Attendees: +* Branden Ghena +* Amit Levy +* Alexandru Radovici +* Johnathan Van Why +* Brad Campbell +* Alyssa Haroldsen +* Pat Pannuto + + +## Updates + * Brad: https://github.com/tock/tock/pull/2958 + * Brad: Hudson and I met to talk about grants. Goal to save space by refactoring grant layouts. Tried to clean up structure of grant layout as well. + * Brad: Also we need to make sure there are no references to a grant after its left. We came up with a solution that marks "leave_grant" as unsafe, which puts the onus on the caller to make sure there are no lingering references. That PR is ready for final review. + * Alyssa: What was the circumstance that would violate safety? + * Brad: The kernel would have to be implemented poorly. Something in `grant.rs` where this type is created and it's maintained after the file calls `leave_grant()`. It's not something where a malicious capsule could exploit it, but instead we want the grants structured so it's really hard to make this mistake. + + * Alexandru: https://github.com/tock/tock/pull/3077 + * Alexandru: Vadim had a concern about wanting to pack multiple system calls into a single context switch. I submitted a PR that follows Leon's idea for how to do so. It's still a work in progress. + * Branden: A technical question: a context switch causes a single system call to happen. For packed system calls, how do we "loop" back to cause the next one to occur? + * Alexandru: When the special system call occurs, it tracks how many system calls are packed. When `switch_to_process()` is called, it checks and just executes the next system call if there is one. + + +## Tock History + * Johnathan: Part of an internal history document. I was curious about the interactions between Helena, Secure Internet of Things Project (SITP), and Tock. + * Johnathan: To start, I think SITP funded some of early Tock? + * Amit: Only Phil would be exactly sure about that answer. Various people were working on it, and were funded in multiple ways. So it's not always clear exactly what funds were funding which person. + * Pat: I know I had some paper that acknowledged SITP funding. + * Johnathan: What year did Tock begin? It looked like 2014? + * Amit: The repository that we have now started later. There was an earlier repo, which was just a prototype. So late 2014. + * Johnathan: And Amit interned on OpenTitan once in 2016? + * Amit: Sounds right. + * Johnathan: Do you remember when Phil was brought on to the project at Google? + * Amit: I don't know. Sometime after that. + * Johnathan: One question I was answering was when Google became involved in funding it. Either SITP or Amit's internship. + + +## TockWorld Agenda + * Amit: https://www.tockos.org/tockworld22/agenda (may have been modified since the discussion) + * Amit: I sent out a proposed agenda for TockWorld. I was hoping we could use some time today to get feedback. In particular, if there are other talks that aren't included, possible remote presentations, etc. + * Amit: To start, does this make sense? + * Branden: One question is how talk time and discussion time splits up? + * Amit: For 30 minute slots, I was expecting 20 for presentation and 10 for discussion. My expectation was that the presentations would partially inform later-on discussions. We have plenty of time to discuss later. So if the talks get long, we can come back to the discussion. + * Johnathan: A comment on the OpenTitan talk. I think I can give it, but I'm not sure yet. And the other people are out of office this week. + * Brad: We could ask Alistair to give one. He's been leading development of Tock on OpenTitan, visibly at least. We could have multiple talks on OpenTitan from different perspectives. + * Pat: State of RISC-V would also be really good. What's needed to get the rest of that fully functional and usable. + * Brad: Yeah, something spanning both maybe? What support they need, what the features are, roadmap. + * Branden: I could see two OpenTitan talks: one on the goals of OpenTitan generally and one about Tock and OpenTitan together. + * Johnathan: One concern is time to put something together. I'm limited on preparation time. We can definitely talk at a high level about it. I'm not sure how substantive it can be. + * Alyssa: I've got similar concerns about Ti50. + * Amit: We can defer this a little bit, sorry to put you on the spot. In both of these cases, I think there is value in the rest of us knowing a bit more, or hear again, about the projects even if at a high level. + * Alyssa: I think we can discuss individual technical problems and some general stuff. + * Amit: Yeah. I'm wondering if it's reasonable to talk about "What is Ti50" and "What is OpenTitan". + * Alyssa: Yes. We do have security requirements that aren't entirely Tock's goals. + * Amit: Okay. Also Alexandru, you have some students coming. Is there a talk outside of teaching that you or your students would like to give? + * Alexandru: Teaching is fine. A little bit of research is fine. We also have a commercial project in mind, but I don't know if I can talk about it yet. + * Brad: One comment I have. I think the discussions section on day one could be broadened to have one and three-year vision. Those have been really useful discussions. + * Amit: That's great. Do you see that as a separate section or just opening up one of them? + * Brad: I'd say rather than focusing on pain points, we could focus on where we want to go, which should hopefully address the pain points too. + * Branden: FYI, there's also breakfast in the morning before we start. And there is lunch on day two. + * Alyssa: What do you want to hear about Ti50? + * Amit: What it is and how it's going for sure. Hopes and dreams and important goals for the project. + * Branden: And where Tock is or isn't doing well at supporting the project. + * Alyssa: Yeah. It's complicated since it's in flux and pretty agile. It's a very different development paradigm from Tock. + * Amit: That would be great too. It doesn't need to be limited to technical stuff, and this is true for everything. As we're looking to make the community project more formalized and sustainable, there are many definitions of working and not working: documentation, outreach, governance, etc. + + +## Next week's call + * Amit: We should skip next week's call since it's so close to TockWorld. + + diff --git a/doc/wg/core/notes/core-notes-2022-07-29.md b/doc/wg/core/notes/core-notes-2022-07-29.md new file mode 100644 index 0000000000..fca29a3faf --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-07-29.md @@ -0,0 +1,408 @@ +# Tock Core Notes 2022-07-29 + +Attendees: +* Alexandru Radovici +* Alyssa Haroldsen +* Arun Thomas +* Brad Campbell +* Branden Ghena +* Chris Frantz +* Hudson Ayers +* Johnathan Van Why +* Leon Schuermann +* Pat Pannuto +* Philip Levis + +## Updates + * Leon: Working on pushing Ethernet support upstream. Have a sort-of-finished + PR for the QEMU(?) board. Have a pretty-polished PR that's still in draft + because there are some things with respect to memory safety in VirtIO network + support. An issue open for supporting physical ethernet chips. I picked up + work on an SPI-based chip we can attach to any Tock chip with a SPI master + controller. Looking pretty good so far. + * Phil: Not an update, but as mentioned at TockWorld I want to talk about + assigning PRs to people so they don't languish and reviewing outstanding PRs + during calls. + * Hudson: Yeah, that's a good point, I remember both of things came up. For the + first one, I think the ideal thing would be if we could find something like + the Bors bot that runs on the upstream Rust repository that will make a guess + at who is the best reviewer for a given PR and assign them. If they're not + the best reviewer, they can assign someone else. Ideally that would evenly + distribute the load. For doing PRs during the core call, we could use the + time at the end of the call for that, or we could schedule a few -- or 20 -- + minutes at the beginning. + * Phil: Amit used to have a script that would go through all the pull requests + and autogenerate the list and stuff like that. + * Leon: If you find a PR that you would be suited to review, you can assign + yourself. For those with no reviewer assigned it should be easy to go through + those and have them done in one or two minutes. Not sure if automated efforts + to assign people would work out. + * Phil: That's a good idea Leon -- spending the beginning of each call + assigning them. Maybe we can start doing that next week. + * Pat: Maybe have the unassigned ones in the agenda, then people can grab a few + and start before the meeting. + * Hudson: That's a great idea. If people can assign themselves to a few PRs + this week, especially any people have basically been owning, then next week I + can get a list of all that have not been assigned and we can assign them. + * Branden: One more item that's not an update but doesn't fit anywhere else. + It's a request. Alyssa, can your push your example unit test code that you + started, maybe as a draft PR? I'd love to start playing around with that. + * Alyssa: Yes + * Brad: On the over-the-air updates side, we're trying to figure out what it + looks like to restrict that API to only certain applications. Trying + different options to see how they work -- that'll be the next changes to the + pull request. + * Hudson: Has starting to work on the app ID stuff given you thoughts on how + they might interact? + * Brad: Conceptually, yes + * Hudson: Do you expect it to work okay? + * Brad: From a code point of view, I don't know. Conceptually, yes. Don't know + if the code will be seamless. We're on the right path. + * Phil: I'm going to guess it won't be very difficult. + * Brad: Hope so, it comes down to Rust type stuff. + * Hudson: Since Phil's stuff makes process loading asynchronous, that + simplifies stuff anyway. + * Leon: I've been working with Johnathan and Chris Frantz on `tock-registers`' + unsoundness with respect to having MMIO memory exposed no Rust references. + Johnathan has been looking into options there, I've been trying to reconcile + this to a generics-based approach to also allow testing of register + peripherals. I was going to ask if on the next call we could present + preliminary results and have a more focused discussion on it. + * Hudson: I'm excited to see what you come up with. + +## App ID + * Phil: I wasn't here for the last call where I think it was discussed. I know + one of the things that came up is the code size impact. I wanted to get to + the bottom of where that is coming from -- how much is unavoidable, how much + is avoidable. In doing so I improved the code size tools a bunch. + * Phil: Verifying the process credentials are correct is an asynchronous + operation, because it may depend on a crypto hardware accelerator. Loading + processes into memory and checking their values can still by synchronous, but + making the transition from loaded into runnable is asynchronous. This adds + complexity to the kernel, as we have to have new state machines and we have + new process states. We have to make sure that something whose credentials + didn't pass doesn't have a workaround to still get it to run. When I trim + down stuff and compile it for the CW310, it adds about 1200 bytes of code. + 400 bytes is in the kernel itself -- things like additional process states. + 400 bytes is in the Tock TBF library. About 250 are in the boot sequence -- + parsing processes and their footers. There's another 100 or so in assorted + functions -- e.g. new methods on processes. This 1200 bytes is if you do not + do any checking -- load every process and mark them all runnable. There's + some other data in there like strings which I will look at, but this is the + basic cost. + * Hudson: Have you looked at the cost with virtual function elimination + enabled? That may get rid of some of the unused methods on `Process`. + * Phil: I suspect that will get less savings than you think because the + additional methods are like "this thing has been loaded, mark it runnable" + which you have to do anyway. I can try. I guess it will not give us very much + -- guess like 50 bytes or something. The basic challenge is the ability to do + this requires changing the process state machine and therefore the kernel + state machine for loading processes. + * Hudson: Alyssa, I know when we talked about this on the last call, this was + something you had a lot of feedback on. Do you have any comments on this? It + sounds like the overhead is less than when we last spoke. + * Alyssa: Are the numbers on GitHub so I can check them out? + * Phil: Not currently. I will put them on the PR. + * Alyssa: Did you investigate whether you can control this via a feature? + * Phil: I put in a method where you don't do any checking, which pulls out a + lot of the state machine, and that's how you get the 1.2k. To fully excise + the feature would be a pretty invasive change to a lot of methods on + `Process`. There are certain things you can do today which you can't do if + you have credentials -- like marking processes runnable requires a capability + now. We have been very leery of using features. + * Alyssa: I'm just considering how for every feature, unless it's well + controlled, it costs extra space. I'm thinking more like the Linux model + where at the beginning you tell it what features you want and it does it. + That's how you build a tiny Linux that can fit on a floppy drop. + * Phil: Right, so if we wanted to go down this path then the way to do this + would be to have two different versions of `Process`. One with checkable + credentials, one which doesn't. + * Alyssa: I need to see the PR a little better to see why it needs to be so + invasive and not a little bit more modular. + * Phil: If the answer is we can never add anything because it adds code space, + that's a tricky situation to be in. + * Alyssa: There is a clear answer to that and the clear answer is features. + * Phil: We've had many discussions about that and that is not an answer we are + comfortable with. + * Alyssa: Over the long term, I don't see how we will be able to continue to + add features if we don't have some way to control the explosion of the kernel + size. + * Leon: I think this particular example is bad in the way that this has such + deep integration in the process loading state machine, and we have to keep + much more information in the process than other features. I'm having a hard + time coming up with examples, but I know in the fast we have used generics to + entirely omit extra information from the binary. So this is an outlier we are + looking at here. + * Alyssa: Okay. I still need to take a closer look at this. + * Phil: Let me put the numbers together and + * Alyssa: I don't love the code size increase and I don't love that tracking + upstream Tock means now we have less room to do our things. That's where I'm + coming from. I have to keep track of upstream but also have to keep track of + our code size. + * Phil: Understood. I think Leon's point is this isn't just adding a feature, + it is a foundational change about the security model the kernel can support + that has been pending for a very long time. I'm not happy with the 1.2k and + would like it to get smaller and am happy to keep working on it, but at some + point we have to figure out how big is too big. If it was 4 bytes we wouldn't + have an issue here. + * Leon: With the disclaimer of only having looked at this for an hour or so, + I'd say there is some unavoidable impact, but I could imagine that if we + tried hard we could make Tock TBF to be configurable, etc. The question is if + we got it down to 1k or 800 bytes, would we still be having the same + discussion. + * Alyssa: I can't give you an exact number, but something like 500. As long as + this is not a regular trend where we will be adding 1.2k unavoidable features + often, I don't think it will be a big problem. If I am able to construct a + way to modularize that, would you be open to such a PR? + * Phil: Yes. The way that this would be modularized would be having different + implementations of `Process`. A lot of the implementation would be shared, so + how do you factor that out? A `ProcessStandard` and a `ProcessUnchecked`, or + something like that. + * Alyssa: I do need to understand the security model for app IDs better to do + this effectively, but I'm imagining what Linux does -- basically `cfg-if`. + * Brad: That's a much more difficult conversation and pull request. We've had + difficult experiences in the past where it becomes very difficult to maintain + so many different code paths. Easier to maintain if things are expressed + through the Rust code itself. + * Alyssa: I think an all-or-nothing approaching is going to hamper the project + long-term. We will eventually -- without a doubt -- need a way to fence off + code features of the kernel. + * Leon: For many things, we have that. I think there is a difficulty in + terminology. On one hand, you have features of an operating system, and on + the other hand you have Cargo features which you can use for conditional + compilation. I don't think that Cargo features are the right tool for us, as + we have to test many combinations as part of the compilation target. So to + have good coverage + * Alyssa: That's entirely normal. Every major kernel I know does it + * Leon: For instance, for the process fault handler, we use these traits we + define and we have very lightweight implementations for when you don't want + the additional complexity added. That would amount to the same overhead as a + top-level feature tag without the overhead of having top-level cargo + features, and we keep Rust type safety. We do want to support features in the + sense of making the kernel configurable, but not through Cargo features if we + can avoid that. + * Alyssa: I don't think this is tenable long-term. You're going to have to have + config flags someday. It doesn't have to be Cargo features specifically, but + some way to do conditional compilation to enable or disable features. + * Hudson: Which we do have -- `kernel/src/config.rs` -- and there are multiple + things we turn on and off that way. I think this discussion is getting + derailed with conflating configuration with specifically cargo features. + Alyssa, if you want to take a look at the PR and give concrete suggestions + for how you would shave some of this overhead off that would be a more useful + way for us to frame any discussion around this. + * Alyssa: I will probably upload a version that does it exactly how I see it + all the time in the Linux kernel. I will also try a mechanism that takes + advantage of generics. How do you feel about two `Process` implementations + but a config-controlled type alias? + * Leon: The code idea of the `Process` trait is you can add either a second + upstream or a downstream implementation without any issues. Could help test + whether the API works out. + * Phil: Just because there are ways people have dealt with struggles in + particular languages -- we have concerns about cases where the code that is + running is not also code but also impacted by how you compiled it. There is + state in its construction that is not embodied in its code. A `#define` + passed on the command line is an example of that. This led us to the config + structure we have in the kernel. Sometimes we do want to use features for the + hardware file, but we are very limited and constrained in what those can do. + It would be a discussion whether something like a type alias could fit in + that bin. + * Phil: It definitely seems like something we should explore and figure out. + This is a good test of process. + * Alyssa: I don't think we'll be able to avoid conditional compilation forever. + I guess we can try. + * Hudson: We do have conditional compilation in the kernel -- there are + multiple config flags there now -- they are just contained in a particular + way. + * Alyssa: From my memory, don't they only control individual constants, and the + optimizer would remove unused code based on those constants? + * Hudson: Correct + * Alyssa: That's quite a bit different + * Leon: That still makes the compiler check whether type safety will work out + for any configuration. + * Alyssa: I think a config flag on a struct field is perfectly reasonable. + Perhaps that's just me. + * Hudson: The biggest problem is that when we went down that path in the past, + we didn't have a way to use our CI to confirm that every combination of + config flags would compile. Contiki went this way, and if you've ever tried + to pick arbitrary configuration like 50% don't compile, and that's something + we wanted to avoid for Tock. Easier to avoid for Linux where you have a + billion users. + * Pat: Linux famously has `make randomconfig` and has farms testing build + configs + * Hudson: Maybe something like that would make it more pallatable. + * Alyssa: This seems like an infrastructure problem + * Leon: It isn't just going to be one config flag. If we open the gate to + enabling this, saying something like "oh this won't be used for features that + can be implemented otherwise", and we'll end up with exponential complexity + and it will be a real issue. We still can't *test* test -- in the sense of + runtime tests -- every configuration, but compilation tests have been a major + contributor to our code's stability and finding bugs. Every with 6, 8, 9 + interoperable features, it will be hard to test. + * Alyssa: You don't need to test every single combination. If you keep new + configs controlled -- don't add them very often -- and you have a set of + configurations that should be tested, then with 7 features you probably only + need 10 tests. + * Alexandru: I think the question that Leon tries to put here is "where do you + draw the line?" How many features do you add? + * Phil: I think that the fact that Linux has farms testing random + configurations doesn't make it desirable -- it is a lesser of two evils + option. + * Phil: This is the first feature of this kind in Tock. It isn't something that + happens very option -- it's touching on the security model and relationship + of processes with the kernel. + * Alyssa: What do you mean by "of this kind"? + * Phil: Adding a substantial feature to the kernel, really changing the + relationship of how things work and increasing kernel size by this much. + * Hudson: Typically features are added in capsules. E.g. we added the process + console, and if you don't want it you don't add it to your board. This is + unique because of the extent to which it has to be integrated into the core + kernel. + * Alyssa: Okay + * Phil: We've had things like the system call ABI, where we decided to redesign + it, but not where a new feature is added. Can somebody else think of + something like this? + * Hudson: You could argue a lot of the stuff that extends TBF headers, but + that's a much smaller overhead. + * Leon: And those are things that we can remove without touching core parts of + the kernel, low-hanging fruits. When we're at that level of trying to + optimize things, we can say that for some target audience we should have an + entirely system call interface and buffer sharing, and at that point it is no + longer Tock. Where do we draw the line? + * Alexandru: It's a slippery slope here -- as soon as we start adding features, + we will find arguments for adding new features. + * Alyssa: The argument will probably be that it is adding code size a ton and + we can't keep doing that. If it's in a capsule then you don't need to add a + feature, but for these sorts of things that change the core security model of + Tock -- that as far as I can tell Ti50 doesn't need -- are the sort of things + that are appropriate to gate behind features. The things that change + fundamentals for what security the kernel provides and its associated costs. + * Brad: On that point, I don't think this changes, I think this is realizing + what we always thought the kernel would do and haven't implemented. Like + maybe the restart process and not just having processes crash and panic the + kernel. These are the changes that we wish were on day one, but of course + can't be. We're really realizing what we set out to do when we said we wanted + to have a security-focused kernel. + * Alyssa: I understand. It's difficult balancing + * Alexandru: At least for our use case in automotive, this feature is + paramount. Were it not for Phil, we would've implemented it. + * Phil: I think Ti50 has a really important counterpoint -- you know, rather + than having per-process credentials, we will just sign the whole thing + together and we will verify the processes when we verify the kernel as part + of the boot process. You can couple than, and check the whole thing. + * Alyssa: The cost of dynamicism, essentially. + * Phil: What I hear from this is that right now we're at 1200, and if you're + not using this then we want to minimize the cost. For Tock TBF, we may be + able to pull that out so certain types aren't parsed and yeah. But also, your + approach of having a `ProcessStandard` and a `ProcessUnchecked` would also be + another approach. + * Leon: I don't know whether this approach will stay in the long term and be + worth maintaining, but it is also a goal to have two `Process` + implementations. May want to have two process types with different security + models. + * Phil: I think there will be some shared core functionality because we don't + want code duplication, and can factor aspects like security models that are + different. + * Leon: We'd essentially have a diamond structure -- one trait, two + implementations, and a common backend. + * Alyssa: I like the idea. My thought is to take the existing `Process` code + and the PR for app IDs, consolidate them into a base process, and have them + delegate as much as possible to share code. + * Hudson: I don't hate the idea -- if we're going to have multiple process + types -- of having a base process others rely on. I'd be interested to see + how clean an implementation of that ends up being. + * Phil: I think it is necessary -- you don't want to have two lines of code + that do the same thing. + * Leon: There are valid reasons to have entirely differently `Process`es that + work completely differently but this is not one of them. + * Phil: Agreed + * Hudson: We're excited to see what you'll propose, and then we can have a + concrete discussion of the tradeoffs. + * Alyssa: I also need to understand what your hangups are on configs so I know + what to avoid + * Hudson: The primary hangups are the testing concern and concerns about + fundamentally changing kernel design. + * [Brad pastes + https://github.com/tock/tock/blob/master/doc/Design.md#ease-of-use-and-understanding + into chat] + * Phil: I think this is coming from the embedded side of things. You can't just + store the configuration in the kernel because there isn't a way to get it + out. + * Hudson: Brad, did you want to take some time to talk about the state of + things for the changes you've made to `tockloader` and `elf2tab` for app ID? + * Brad: Phil really kicked this off, I've been playing around with `elf2tab` + and `tockloader`. I haven't done much testing with boards and deploying the + kernel + apps and with the kernel PR. The current state is `elf2tab` can add + signatures and reserve space for future signatures, and `tockloader` + understands them now. It can parse them, add and remove them, and check they + match the rest of the TBF application. Hopefully can also flash them onto + boards. Support is preliminarily robust in what it can do -- there are + probably edge cases that need to be tested. Provides the same experience as + tockloader provides to other tasks, just to the new footer. I'm sure there + are additional features that we'd want, but for the initial implementation + the features should be pretty good at this point. + * Hudson: It seems most of the PRs can be merged now. Are you waiting for more + reviews there, or is the hope that some of that will go through in the next + couple days? + * Brad: I would like to have hardware in front of me, and double-check that if + we do update `elf2tab` and `tockloader` that nothing breaks. Don't expect + that, but I'd like to confirm. Do have some time because we don't expect + people to download the latest-and-greatest of the tools. I do think we should + get them in there so people interested in working on this can use the tools + without having to manage the PRs. The feature should be backwards-compatible + -- we want to support people who don't update their kernel right away. + * Hudson: That's a good point. Where did we end up on that one portion of the + original PR that was going to require an elf2tab update? + * Phil: We decided not to do that. There was a difference between documentation + and implementation on an offset, and we decided to update the documentation + to match. + * Hudson: That makes the `elf2tab` stuff easier. + * Leon: I was going to do a shameless plug -- the QEMU board and flash loader + support should help test. + * Hudson: It seems the LiteX CI has found a bug that currently exists in the + PR. + * Leon: Not necessarily a bug, just an incompatibility. If there's any issue I + can look at specifically, please let me know, as I know it is difficult to + get working on anything but my setup. + * Hudson: I managed to get LiteX to run and toggled stuff in the kernel. The + kernel seems to be running fine -- no panic -- but the process is being found + at the wrong location. Probably related to these changes. + * Leon: That shouldn't be LiteX-specific, should be RISC-V in general. + * Hudson: Or maybe something about the `elf2tab` stuff. + * Phil: I have been able to run in Verilator on Earlgray. Definitely will keep + on working on this. Have been able to test on Imix, would greatly appreciate + help testing. + * Brad: So others are aware, on Cortex-M platforms where we pad to a power of + two, pre-this-change, we did the padding by adding zeroes to the application + binary. Now elf2tab tries to opportunistically insert a reserved footer + credential section. The idea is you can reserve space such that if you want + to add a new credential later, you don't have to modify a part of the + application covered by another credential. So on Cortex-M, even if you don't + use any credential stuff, you will still end up with one, which should be + fine. An old kernel should ignore it. If you look at `tockloader` you may see + the credential show up. + * Hudson: I wanted to ask before Phil had to leave and didn't, but I do wonder + if we want to consider trying to quickly tag a minor release before we merge + the app ID PR. It's a major change, and we have over a year of changes since + Tock 2.0 that are meaningful improvements but not fundamental changes. Would + be good to have a tested release with all the new post-2.0 stuff. + * Leon: I think this would put us in the same situation as we were after 2.0, + where we immediately changed how Allow works then didn't do a release for a + long time. So we should probably do a release afterwards. I also want to fix + unsoundness w.r.t. userspace buffers shared with devices, and maybe want to + push that into the first release as well. + * Hudson: I think you're right we should also try to do a release relatively + soon after this. I don't think we should have the mindset of releasing every + year, and we should try to get back to more frequent releases when we have + big items. I think that you're right that it's a big issue that we haven't + done a release since Allow -- that's been blocking `libtock-rs` development, + which is unfortunate. Also easier to issue a release if you recently issued a + release, because stuff's more tested and you find fewer bugs. + * Alexandru: I agree and think a release before app ID would be good. I would + like to incorporate Leon's Allow soundness work. + * Brad: This is a bigger discussion: if we did want to do a release, what + should we include? + * Hudson: Maybe that should be the first item for next week. There's one minute + left, and a couple people have had to drop already. + * Leon: I didn't want to decide on this now, just raise the question, thought + I'd have direction. + * Hudson: We can talk more next week. diff --git a/doc/wg/core/notes/core-notes-2022-08-05.md b/doc/wg/core/notes/core-notes-2022-08-05.md new file mode 100644 index 0000000000..3d1285b414 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-08-05.md @@ -0,0 +1,122 @@ +# Tock Core Notes 2022-08-05 + +Attendees: + * Leon Schuermann + * Arun Thomas + * Branden Ghena + * Vadim Sukhomlinov + * Philip Levis + * Chris Frantz + * Johnathan Van Why + * Hudson Ayers + * Alyssa Haroldsen + * Pat Pannuto + * Brad Campbell + +## Updates + - Phil: AppID: More code size reduction; 900 bytes w/out checking. 300 in TBF, 300 kernel, 300 in strings (ish). Will look into strings to see if we can cut those, but getting to the point where it won't be much smaller. + - Hudson: 900 much smaller than when started; much more palatable + - Hudson: New approaches to deferrred call. First, tighter kernel integration, no globals, atomics. Then tried new pair of PRs that would eliminate dynamic deferred calls (capsules use same infra as chips) at cost of rough syntax; other PRR keeps globals + atomic usize, one more instantiation of this becomes available to capsules. Suspect that approach two will improve code size a lot, esp for those using dynamic deferred calls. + - Phil: Deferred calls have always been a tricky wart; nice to see improvements here. + - Hudson: +1, always strange to have both dynamic and static + - Alyssa: About to push a draft unit testing example. Not perfect, but want folks to see what the necessary pieces are to get unit testing working. + +## PR Review + - Phil: We brought this up at TockWorld and other recent meetings. Idea is to keep folks up to date with big PRs that have been recently merged and/or are open/active. + +### Merged: + - https://github.com/tock/tock/pull/3108 - kernel: hil: sensors: AirQualityDriver improvements + - https://github.com/tock/tock/pull/3113, https://github.com/tock/tock/pull/3096, https://github.com/tock/tock/pull/3087 - Series of improvements to print_tock_memory_usage + - Phil: Symbol tables from llvm have changed some, so attribution wasn't so great; now fixed. + - https://github.com/tock/tock/pull/3106 - Seven Segment Display Capsule + +### Open: + - https://github.com/tock/tock/pull/3092 - capsules: Add support for AES GCM + - Phil: Discussed at OT meeting. GCM is a form of authenticated encryption, concurrent encryption and MAC computation. Some complex layering, e.g. current GCM layers on CBC, so what does virtualizers do? + - Phil: Historically, we have the strongly anti-third party policy. But crypto is the one place where this really probably shouldn't be the case. + - Johnathan: If we're talking about the external policy, this is also probably a factor of why Tock and the greater embedded rust universe are somewhat distinct. + - Phil: Yes. But for the immediate, if we're going to have software AES, we should probably use a third-party. + - https://github.com/tock/tock/pull/3067, https://github.com/tock/tock/pull/3055, https://github.com/tock/tock/pull/3011 - Display updates + - Brad: I can give a very limited update. Best info is probably in tracking issue that was recently created, but it's still a bit hard to keep track of the priorities and ordering of steps ( https://github.com/tock/tock/issues/3079 ). Does seem like step 1 is largely HIL consolidation, consistency, and cleanup. + - Phil: Often we think only in framebuffers. Here, we actually have to think about the display as well (how pixels are sent, etc); makes this a bigger task. + - https://github.com/tock/tock/pull/2993 - RFC: hil: Add generic block device HIL + - Phil: Trying to be the simplest view, but still running into the challenges are page granularity, erasing, etc. Something to watch. + +## Discussion of 2.1 Release + - Hudson: Brad made a tracking issue ( https://github.com/tock/tock/issues/3116 ). + - Hudson: Brad and I are advocating to do a release in the very short term. i.e. I plan to try to do release tests on imix today. If no issues, then kick out the platform testing process. Some folks wanted to wait for a few others, wanted to chat quick + - Leon: I had wanted to get some of the open unsoundness issues merged, but given that there are no visible issues from unsoundness yet, okay to go forward now + - Hudson: Yes, and 2.2 should follow 2.1 pretty quickly, i.e. roughly post appid + - Phil: Yes, because of libtock and soundness and such, doing 2.1 w/out AppID make sense. Less concerned about how quick a release with AppID comes out, more concerned about how quick AppID gets merged, as there's a lot of maintenance work to keep it up as + - Leon: Can we merge the trivial PRs right now, and then do a freeze now? + - Hudson: That sounds reasonable, I'll share screen and we can walk through these. + - Leon: Quick question, what's our platform deprecation timeframe, if people don't step up to test? + - Hudson: Maybe two weeks after the initial round of testing and tagging people; if we haven't heard anything (even 'need more time to test'), can pull it for the release + - Brad: There is also some judgement here; something 'near' a platform we support a lot can stay more, something that's less used can be more aggressively pruned + - Hudson: That makes sense, I'll give please reply in two weeks message, but won't threaten removal. We can do judgement after that + +### PR Triage + - https://github.com/tock/tock/pull/3127, https://github.com/tock/tock/pull/3125, https://github.com/tock/tock/pull/3124, https://github.com/tock/tock/pull/3123 - Not ready yet + - https://github.com/tock/tock/pull/3122 - waiting on OT bitstream update + - https://github.com/tock/tock/pull/3120 - Hudson: could go? Phil: I'll look and merge. + - https://github.com/tock/tock/pull/3119 - Hudson: Already approved; merge. + - https://github.com/tock/tock/pull/3118 - Has changes requested (just spelling?). Not urgent. Don't block for this. + - https://github.com/tock/tock/pull/3117 - Might be needed for tests to pass for this platform + - https://github.com/tock/tock/pull/3114 - Not ready, 'could be months of back and forth; hoping not...' + - https://github.com/tock/tock/pull/3112 - Not a blocker. + - https://github.com/tock/tock/pull/3110 - Not ready + - https://github.com/tock/tock/pull/3095 - Leon/Pat will discuss after call + - https://github.com/tock/tock/pull/3092 - Blocked on OT discussion + - https://github.com/tock/tock/pull/3086 - WIP + - https://github.com/tock/tock/pull/3085, https://github.com/tock/tock/pull/3056 - Not working? Still some PMP issues. Chris: Would prefer to delay bitstream until OT solidifies formal release process. This one is mine, can just close, I will open a new one when ready. + - https://github.com/tock/tock/pull/3084 - Not blocking. + - https://github.com/tock/tock/pull/3077, https://github.com/tock/tock/pull/3068 - WIP. + - https://github.com/tock/tock/pull/3067 - RFC not blocker. + +... At this point, all older enough to not likely be blockers; any folks want to highlight? + + - Leon: https://github.com/tock/tock/pull/2516 should be ready to go today; Leon will rebase, Hudson will review + - Phil: https://github.com/tock/tock/pull/3045, lowrisc autogen register definitions? Chris: In a similar vein as the OT discussion, want to close this until the OT release process stable. You want the version of these files tied to the release tag you're going to support; these are an arbitrary day. Short term: convert to draft, indicate waiting for OT release process. + + +## Discussion of approaches to removing `DynamicDeferredCall` + + - Pre-meeting notes from Hudson: + - https://github.com/tock/tock/pull/3123 is the first approach, and builds off of my older PR that removed the need for atomics in deferred_call.rs, helping us remove an unstable feature. However, if you look at the changes in capsules/src/ieee802154/driver.rs, you will see that the syntax required by this change is messier, and makes adding a deferred call to an existing capsule more of a chore. + - https://github.com/tock/tock/pull/3127 also removes DynamicDeferredCall in favor of pushing capsules to a statically defined approach, but still keeps the general approach of using global (atomic) variables in the kernel. This does not help us move off Rust nightly, but lets the syntax in capsules be much nicer and requires less refactoring elsewhere. It also seems to produce smaller code, though I have not extensively tested this. + - Hudson: Really want to do compare/contrast of these approaches. + - Hudson: I think it makes sense to get rid of DynamicDeferredCall. In all the upstream boards/chips/capsules, we don't take advantage of the fact that at runtime you can change what structure gets calls. Things are effectively set up statically at boot. Really we have this dynamic thing because it's hard for capsules to set things up given the type limitations for the existing deferred call type (as capsules can't depend on specific chip crates, and chip crates don't depend on capsules) - so no way to create a list of deferred calls for capsules and chips. + - Hudson: The https://github.com/tock/tock/pull/3123 approach has a generic deferred task that chips and capsules implement over. The board main.rs defines a mapper (`fn handle_deferred_call`) for what handles deferred calls. This also handles all the associated type definitions. Previously, this logic was contained in `chips`, now it's all surfaced to the board main. + - One thing worth considering; look at the radio capsule example ported: Previously had to allocate, now take in the manager reference, which adds a generic over deferred call manager parameter that could propagate in a challenging ergonomic way. + - Other thing: The mapping of deferred calls, and what comes back to capsule is in trusted main.rs code; but the triggering of deferred calls currently trusts capsules to call the right one. + - Leon: Q how does this relate to abstraction layers over trait objects? Given that all the users must be generic over deferred call mapper, will this work okay with external uses, will this leak through all later types? + - Hudson: Is Q what happens if radio driver needs to be object-safe? + - Leon: It's really does this generic type propagate through trait objects? + - Hudson: Yeah, actually think it might not be object safe in general + - Leon: what happens when components from other crates, not components or capsules? i.e. downstream? + - Hudson: Out-of-tree capsules crates generally seem to depend on our capsules crate, e.g. to get virtualizers or other needed pieces. Can define their own Task enum that is a superset, and pass that as the capsule task type in main.rs + - Leon: Would that be compatible with the upstream use of the directly named type? + - Hudson: No... + - Phil: Skittish about this namespace. Not checked or protect; collisions are possible as well. + - Hudson: Going to need to develop something to protect ids for different users and use sets + - Hudson: Leon, the point is good that this harder with types for downstream + - Hudson: The https://github.com/tock/tock/pull/3127 approach: Doesn't get rid of globals, atomics; instead adds capsule-deferred. Basically a copy in capsules of what we did in chips today. Changes of use much smaller. Still have the same namespacing problem, but the generics issues go away. There are some small changes for generics in chips.rs; basically assume two kinds of deferred call. Still have a mapping in main, mostly the mapping is defined in e.g. capsules/src/driver.rs, just instantiated in board main.rs now. Still doesn't have a great story for downstream extension. + - Leon: Maybe we should look into Rust type ids? Could map to integers and guarantee uniqueness? + - Phil: Compile-time count is good for array sizing etc; key thing is conflict, duplication issue + - Leon: Would assume most capsules would break when given a spurious deferred call + - Phil: Shouldn't be able to call a method on something if you don't have a reference to it + - Hudson: This approach shouldn't have the spurious call problem as they can't get reference, but still no good solution to the downstream extension + - Hudson: Downstream extensibility wasn't really considered yet, seems like that's a big issue I need to / will think about next + - Leon: Thank you for doing this. Lots of work. When writing first capsules struggled with dynamic + - Hudson: Call for ideas for task registry with out-of-tree capsules, now or later. Today, it relies on an enum in the capsules crate that we can update upstream as things change, but doesn't really allow extensibility. + - Alyssa: Wrapper around integer with associated constant? + - Leon: isolation? Can craft integers, while enums give type restrictions + - Leon: This might be where we can use rust type ids, as they are guaranteed to be unique and hash to unique integers + - Alyssa: yes, but type ids will require vtables + - Hudson: Even with unique types, not clear can be generic over unknown types + - Hudson: Will try to create a more minimal example, discuss in small working group (roughly alyssa, leon?) + +## Closing Comments + + - Alyssa: PR up for example test. Think we'll need some structures to help support test infra, esp static_init... + - Hudson: Lots of discussion about how to make static_init safer; still open issue I think, will send along diff --git a/doc/wg/core/notes/core-notes-2022-08-12.md b/doc/wg/core/notes/core-notes-2022-08-12.md new file mode 100644 index 0000000000..eaf6e29a04 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-08-12.md @@ -0,0 +1,141 @@ +# Tock Core Notes 2022-08-12 + +Attendees: +* Branden Ghena +* Johnathan Van Why +* Leon Schuermann +* Arun Thomas +* Alexandru Radovici +* Chris Frantz +* Pat Pannuto +* Phil Levis +* Alyssa Haroldson +* Jett Rink + +## Updates + * Branden: I have been working with capsule unit tests based on Alyssa's draft PR. https://github.com/tock/tock/tree/capsule-unit-test I'm making GPIO unit tests right now and so far things are working. Thinking about how to add helpers for unsafe kernel resources we need to use within capsules while unit testing. + * Alyssa: Google has documentation on how to make better unit tests and helpers. I've got lots of thoughts on the issue. + * Branden: Awesome. I think we can discuss and think about how to do unit tests best. I'll likely make another PR/Issue to discuss it. + * Branden: The issue right now isn't so much the unit tests as the infrastructure. Having kernel and capsules in different crates makes testing harder than it might otherwise be, since the tests config option doesn't cross crate boundaries. There's an open Rust issue on it. + * Alyssa: Yeah. There are some options you could use a feature or have a dedicated crate. Using a feature gets all the trait elements, which is what my draft PR does. + + +## PR Overview + - Merged (non-trivial): + - https://github.com/tock/tock/pull/2516 QEMU riscv32 "virt" board + - https://github.com/tock/tock/pull/3117 Apollo3 IOM fixes + - https://github.com/tock/tock/pull/3118 AirQuality capsule + - https://github.com/tock/tock/pull/3119 NotEnoughFlash TBF parsing errors + - https://github.com/tock/tock/pull/3120 Tickv panics to errors + - https://github.com/tock/tock/pull/3131 Catch errors in size CI tool + - Opened (non-trivial): + - https://github.com/tock/tock/pull/3129 (DRAFT) Capsule unit test + - https://github.com/tock/tock/pull/3134 (Release Blocker) SAM4L UART deferred calls + - https://github.com/tock/tock/pull/3136 Alarm implementation bug + - https://github.com/tock/tock/pull/3137 Scheduler infinite loop + - https://github.com/tock/tock/pull/3139 (Release Blocker) Redboard Artemis hardfault + - https://github.com/tock/tock/pull/3140 (Release Blocker) Reboard Artemis app loading + + * Branden: Note that some of these are release blockers for Tock 2.1 + + +## Infinite loop in scheduler + * Branden: Jett found a scheduler infinite loop in https://github.com/tock/tock/pull/3137 that's had a lot of comments and back and forth. + * Jett: We had some discussion and are pulling more general design questions into a separate issue: https://github.com/tock/tock/issues/3138 + * Jett: I think taking a step back and looking at the incoming work and processed blocked machinery is a good idea here. It's probably the better solution long-term because I don't think that the current optimization to not iterate processes is really needed. + * Jett: I suspect that just iterating all N processes is fine because N is very small. + * Branden: This code is fairly legacy so I think we'd at least be open to discussing all kinds of changes. + * Pat: As memory serves, the optimization comes from a concern of energy savings. Don't want to waste energy checking all processes. + * Pat: I think we should measure how large the costs actually are, and it's not worth saving unless the measurements support it. + * Jett: Do we have any low-power apps for guidance? + * Pat: Mostly on the research side. Energy-harvesting applications like Brad has. + * Phil: It's not just about energy, but also CPU cycles. How long this takes constrains the maximum interrupt rate. + * Phil: I'm not saying this optimization is the right choice. However, there are some weird things without it. If interrupts come in really quickly, they just chain and work fine. If they come in just a bit slower, we could get caught in the "scanning processes" loop and not make any progress. So there would be a weird step delay as the rate decreases. + * Phil: So the question is, how many cycles does it take to do this scan. If it's microseconds, we're likely just fine with it. If it's milliseconds, that could be an issue. + * Alexandru: We ran some tests at one point and found that Tock stopped responding when given an external interrupt at around 10 kHz. We didn't look into the exact reasoning at the time. + * Leon: It would be great to reproduce that. I tested once and found much larger numbers, with this still reasonably workable. It would be good to look into this. + * Alexandru: We will try it, probably next week or so. + + +## Tock 2.1 Status + * https://github.com/tock/tock/issues/3116 + * Branden: 2.1 release candidate is out and there is an issue tracking testing of boards. Everyone should look and see if they're responsible for testing and check their box if so. + * Alexandru: What's the deadline? Is next week okay? (general consensus that it's fine) + * Leon: I think the policy was two weeks without hearing back from anyone is a problem. + * Leon: Also, I was collecting LiteX feedback since the last release and am going to make updates to board/readme/bitstream to make it easier to use, possibly with a Docker image. Is this okay as part of 2.1? + * Branden: I think it's probably fine. A lot more palatable than kernel or capsule changes. + * Pat: I agree. Sounds like patching a bug to me. + * Alyssa: Do we have a list of API breaking changes since 2.0? That would be quite useful. + * Jett: I have one to add to it. + * Leon: Generally, the release notes include things like that. Let's make a separate tracking issue and put them there. + + +## Ti50 Frustration - Printing Flush and Synchronization + * Alyssa: One theme we've run across is that printing is frustrating from code size. Performs worse than printf. + * Alyssa: Flush doesn't actually flush either. Getting a better idea of why there are multiple queues would be useful. And we want a flush operation that always validly flushes. + * Leon: Are we talking about code size or speed (code size). + * Phil: And by flush, you mean a synchronous wait until stuff is printed out. + * Alyssa: yeah. I'm trying to understand why it's not today. + * Phil: There are no synchronous calls in the kernel. So it's not the same semantics as a POSIX flush. + * Alyssa: So there's that in the kernel. Or some way to wait until the print is finished. This is in apps too. Definitely want the app to wait until things are really printed and finished. + * Branden: I think these are two issues. The app one is somewhat segmented from the kernel one. In the kernel you can't have a synchronous interface and need a callback. You could pause the state machine and not move forward until the callback. + * Phil: We do not ever ever want to use a synchronous flush in the kernel for normal code since the whole kernel will halt. But for tests it may make sense. + * Alyssa: Or log-based debugging which was the case here. + * Phil: But if you flush and the kernel spins for a while, all kinds of things could go drastically wrong. + * Alyssa: I'm curious about what could go wrong? + * Branden: Generally introducing arbitrary timing delays like that screws up all kinds of external interactions with hardware. It's the same reason that you sometimes can't attach GDB to a running microcontroller. It'll get you to a point, pause, and let you introspect variables. But continuing running afterwards sometimes doesn't work at all. + * Leon: For testing, do you mean tests that run on actual hardware? + * Alyssa: Not tests. It's debugging. So we want to make sure that prints are consistent and ordered between the app and the kernel. I want to have ordering constrained. + * Phil: Can you describe the case of a userspace process and calling print? What's the timing concern there? + * Alyssa: For debugging, say I'm debugging and app and a capsule it communicates with. So the app prints. Then after that the app syscalls into the capsule. Then the kernel capsule does another print. There's no guarantee that the kernel print occurs before or after the app print. + * Phil: This is helpful. So this is a very specific, and important, case. The issue here is that the virtualizer for the console doesn't operate in a FIFO order. So the app can print and memory gets copied into a buffer. The virtualizer doesn't guarantee which buffer gets copied into the low-level buffer first. It's somewhat arbitrary based on the order of virtual requests in the linked list. We have discussed having FIFO ordering. + * Alyssa: That would help. Flush is also an issue for some things. Particularly it would be great to be able to print a massive amount of stuff and have the debug buffer not overflow, which is a big issue for us now. + * Alyssa: The other frustration is moving between apps and kernel.. The happens-before relationship doesn't happen, which breaks expectations. + * Leon: I was thinking that printing in the kernel is used seldomly in production. So maybe we only need to support the debugging use case rather than the general one. + * Phil: Well, ordering is still useful. The reason we don't have FIFO ordering is mostly accidental. The happens-before relationship would be important to maintain. + * Phil: The challenge here is that if you just do a simple queueing approach, you can get weird behavior. An app does a print which gets enqueued first. The capsule does a print which gets queued second. Then the application does another print. The second application print is just appended at the end of the application buffer. So you might get both application prints before the kernel because we don't keep track of individual requests, just a buffer. + * Alyssa: I think we should consider changing that. + * Leon: Let's say we did have total ordering and ticketing for individual reservations. You'd have a different problem where one app could denial-of-service another app by printing a very long string. + * Phil: Well, there are limited sized buffers. So there's already a switching mechanism there. + * Leon: Is the chunking on the driver level? Is it exposed to applications? + * Phil: No, it's not exposed. + * Leon: So from an app's perspective, you couldn't be certain that a long message you are printing would print in its entirety with total ordering. + * Phil: I see, so if the app does a huge dump of information that would starve the other applications. To be fair, POSIX doesn't ensure atomicity. + * Leon: I was going to ask how POSIX does this. Is there ordering or is it unspecified? + * Phil: It's tricky, because things in POSIX are blocking. So there's fundamentally some ordering. When I call print and print blocks until the print is complete that fundamentally creates an ordering. Because if I then send a message to another process, I know that message is after the print. + * Leon: But if you have two applications printing to the same virtual terminal at the same time, those can be intertwined. + * Phil: Right, but that's not a happens-before relationship. Independent processes could be running at whatever speed on different processors. + * Leon: Okay, so applications can synchronize themselves due to blocking. That maps to our applications. If we had a way to specify back to the application that the print did totally occur to real hardware, then we could block the application until we get that notification. + * Phil: Within the kernel we do this in hardfault handlers: synchronous writes with blocking. But if you do this in the kernel and halt things for many milliseconds there's no guarantee that things still work afterwards. You'll get weird issues afterwards. + * Leon: I'm arguing that synchronizing between two apps and app/kernel are similar and we expect both or neither to work. + * Alyssa: Especially with IPC I expect the ordering to be needed. + * Phil: I think this is a virtualizer ordering issue, but that sounds doable. Big flushes in the kernel are harder. + * Alexandru: Would a dedicated events capsule help here? It could keep track of causality for events. + * Alyssa: Sometimes I want to dump, say, the SHA hash or the contents of memory. And it would be nice to guarantee that. Every method I try just gives me "debug buffer full". + * Pat: Is this something you continue afterwards? Or you just want information and then to stop? + * Alyssa: I want to continue. It would be useful to do a print, do some work, do another print. They're on the order of 20 kB prints. + * Pat: I mentioned because the hardfault handler tears down the world and prints things synchronously. That would work if you don't want to continue, but doesn't work if you do want to continue. + * Phil: Here's the line that has ordering for the virtualizer process console: https://github.com/tock/tock/blob/1e3f8c1757a4582cf870ef20e9445cb5ed949e9e/capsules/src/console.rs#L330 When you finish a write from an app, it just search all apps linearly. So changing that to a FIFO with ordering would help, with the caveat that it's possible multiple application writes are combined. We don't track write state, just data. + * Alyssa: You could add some ordering tag in the app to do this. + * Phil: If you get the callback when it's actually written over the UART, that will serialize things. + * Leon: I think this is the right way to think about this. It would make apps capable of synchronization but not enforce costs. + * Alexandru: I think maybe a dedicated capsule with a large buffer might be more useful. Or did you just try making the debug buffer really big? + * Leon: I think a general synchronization primitive makes sense here for printing. I doubt we could make a truly general primitive even if the idea applies. + * Alyssa: I still see the easiest primitive on the app end is a synchronous flush. + * Phil: Waiting for the callback is the flush. I'm trying to see when the upcall is triggered. + * Alyssa: That's when it finishes copying to an internal buffer, not when it's written out. + * Phil: So that plus the lack of ordering is the issue. We would like the semantic to be a notification when it's actually written back. + * Leon: It can sometimes be useful for applications to write and not flush. + * Phil: Printf is sort of like that, but write doesn't do that. + * Alyssa: Write is often line buffered. + * Leon: If we could change semantics for only synchronous writes that would be great. I just think changing the semantics for both operations would be a mistake. + * Alyssa: When we receive back from the print and it's done a flush to the internal queue, that's still useful. + * Alexandru: So your problem is actually that you print large buffers from apps, but the issue is that they might sit in the console buffer? + * Alyssa: That was an issue where increasing the buffer size in the app didn't fix things because there are multiple buffers. + * Alexandru: I think right now when it copies it overflows. You could check and just do the copy piecemeal, but then you lose the event. + * Alyssa: I think a flush with an overflowing buffer should write what it can and copy the rest. That might be hard though. + * Alexandru: We could change the console to copy things and if it can't copy everything to delay the print. But you'd lose the order. + * Alyssa: What I care about the most is that flush actually flushes. The team discovering that flush didn't actually flush was frustrating. + * Phil: It shouldn't have been named that since it's not really a flush. + + diff --git a/doc/wg/core/notes/core-notes-2022-08-19.md b/doc/wg/core/notes/core-notes-2022-08-19.md new file mode 100644 index 0000000000..107f178e16 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-08-19.md @@ -0,0 +1,168 @@ +# Tock Core Notes of 2022-08-19 + +Attendees: +- Hudson Ayers +- Brad Campbell +- Branden Ghena +- Alyssa Haroldsen +- Philip Levis +- Amit Levy +- Pat Pannuto +- Alexandru Radovici +- Jett Rink +- Leon Schuermann +- Johnathan Van Why + +## Dialpad Meeting Automatic Recording + +* Hudson: Any reason to not turn on automatic recording for Tock + meetings? Room used for other things? + +* Amit: Very rarely, can still turn it off. + +* Johnathan: OpenTitan WG uses it, but recording should not be an + issue. Will ask next OT meeting. + +## Updates + +### Console Debug Print Ordering + +* Phil: In response to ordering of console prints. Looked at how + queuing works in the kernel. The only non excessively complicated + method to provide ordering of console prints is to use a single + output buffer. The best solution to this problem seems to be a + second system call, which would essentially invoke debug and copy + writes from userspace into the global kernel debug buffer. + +* Alyssa: I think this should work for our use cases. + +* Phil: These operations would be non-blocking; no flush can be + implemented because we'd only be able to run top-half interrupt + handlers. If it cannot run any bottom-halves, it cannot invoke the + UART peripheral's interrupt handler to continue printing. + + Debug buffers should be sufficiently large to work around that. + +* Alyssa: Would like a userspace `flush` operation, which blocks the + issuing app. + +* Phil: That should work. + +### `tock-registers` Stable Release + +* Leon: User commented on issue #2982 that `tock-registers` still does + not have a release usable on stable Rust. It works on stable Rust + internally for a while now. We might want to do another `v0.Y.0` + minor release with what's in the repo currently. + +* Hudson: If external people are blocked on a release, just do a minor + release. + +* Pat: Johnathan, what is the latest update on the register interface + PR? Is that expected to land in the short term or requires some more + time? + +* Johnathan: It is going to be a couple of weeks still. Also, it would + manifest in major non-backwards compatible changes to the API + surface. Would not be a great release for users who just want to use + it on stable. + + Also, we should wait a while with these changes in to polish it. + +* Leon: Also, these changes might be rather controversial. Revamping + the entire register structs infrastructure should require some + elaborate discussions. + +* Pat: Preparing the release now. + +## State of Tock 2.1 + +* Hudson: Current state seems that some boards have been tested, but + there is more testing to do. + + Discovered failures in the log tests during `imix` kernel tests; + interestingly not the linear log tests. One of the bytes written is + not read back correctly on `imix`. Looking into changes since the + last release. + + Some of the 6LoWPAN tests fail. Specifically ones which combine + in-kernel capsules using 6LoWPAN along with multiple + applications. Failures are strange, just at the application level + and only occur with multiple applications. Order of app operations + seem to matter, and applications have time-quantum expirations. + + Alex has a potentially unfair number of boards assigned to him. Can + take over the Nano 33. + +* Phil: There is still one release blocker (PR #3139, Redboard Artemis + Hardfault Exception). + +* Branden: Tock kernel issue or just for specific board? + +* Phil: Just for this board. Brief summary is that it gives a + hardfault exception, because the stack is corrupted and the saved + link register ends up as `0`. It then tries to jump to address `0`, + which does not contain a Thumb instruction and hence it gives a + hardfault. + + Inlining a function "fixes" this issue. The root of the problem + seems to be that, to perform some FPU configuration checking, you + have to trigger an `svc` handler and the code was not triggering + this handler correctly. + + Solveable, but requires a very deep understanding of ARM exception + handling. + +* Hudson: Default bootloader of this board enables the FPU, so it is + entirely board-specific. + + We have not gotten to the bottom of this in a week. Do we merge + Alistair's fix, which is not actually solving the underlying + problem? + +* Phil: Answer has to be no, this fix is coincidental with the stack + corruption (which is happening) does not then also corrupt return. + +* Hudson: Defer 2.1 until we have a fix for this, or do we release + with a known-broken board. + +* Phil: Release with a fix or pull Redboard Artemis from the release. + +* Alyssa: Can we list this under known issues? + +* Phil: Hardfaults on boot, unusable. Just exclude it from the current + release, but it back in later. + +* Hudson: Hope for this release was to tag it during the next meeting + (2022-08-26). Not sure whether that seems realistic. We could at + least have an attempt at testing for the individual boards by then. + + `weact_f401ccu6` board was assigned to `@yusefkarim`, have not heard + back from that person. We might need to consider deprecating + it. Will put a post on the issue stating that, if we have not heard + back until a release is tagged, it will be deprecated. + + (Note from chat: `weact_f401ccu6` seem no longer current and/or + available, so deprecating seems like a reasonable outcome) + +## PR Review + +- Hudson: + + - Merged (non-trivial): + - https://github.com/tock/tock/pull/3140 + boards/redboard_artemis_nano: Fixup app loading + - https://github.com/tock/tock/pull/3136 Fix secondary alarm from + firing immediately during alarm callback + - https://github.com/tock/tock/pull/3134 use deferred calls to + report aborted reception in sam4l uart + - Opened (non-trivial): + - https://github.com/tock/tock/pull/3149 boards/litex: update + pinned tock-litex release to 2022081701 + - https://github.com/tock/tock/pull/3148 + boards/esp32-c3-devkitM-1: Prepare for the 2.1 release + - https://github.com/tock/tock/pull/3139 boards/redboard_artemis: + Fixup Hard Fault exception + + Hope would be that each goes in before the release, except for + perhaps the last one. diff --git a/doc/wg/core/notes/core-notes-2022-08-26.md b/doc/wg/core/notes/core-notes-2022-08-26.md new file mode 100644 index 0000000000..8ede9a3d1d --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-08-26.md @@ -0,0 +1,61 @@ +# Tock Core Notes 2022-08-26 + +Attendees: +- Amit Levy +- Branden Ghena +- Brad Campbell +- Johnathan Van Why +- Arun Thomas +- Alexandru Radovici +- Chris Frantz +- Pat Pannuto +- Alyssa Haroldson +- Jett Rink +- Leon Schuermann +- Hudson Ayers + +## Updates + +- Alex: oxidos.io funding round secured to use Tock in automotive space. +- Need a blog post! + +## Tock 2.1 Testing + +- https://github.com/tock/tock/issues/3116#issuecomment-1209792251 +- Brad: We've done a lot of testing, I say we go forward with the release soon. +- Alex: STM almost finished. RPi tomorrow. Will be ready. +- Other Alex will test i.mx board. +- Branden: Let's make a deadline to finish testing and move on with release. +- Pat: redboard problem. +- Brad: should create 2.2 issue now. Goals: update cortex-m syscall handling and + AppID. +- Branden: Other PRs on 2.1 to discuss? They seem pretty straightforward. +- Brad: Need release notes, possibly changelog.md. +- Leon: Happy to start that, would like to see if anything is missed. +- Jett: Mark major breaking change in changelog.md. +- Branden/Amit/Brad: September 1, 2022 release goal. + +### redboard Problem + +- https://github.com/tock/tock/pull/3139 +- Pat: Technical debt from context switching in cortex-m. Assumptions that + things would happened one at a time. Nested events and floating point violate + these assumptions. +- We are checking link register, should be checking status register. +- Current summary: we need to re-write cortex-m bottom half handling, should fix + this issue. +- Amit: Been talking about this re-write for a while, makes sense to do this + change. +- Probably best thing to do is go ahead with 2.1, focus on re-factoring this for + next release, and let this board be broken for this release. +- Pat: could apply PR for 2.1 which is a workaround hack. This PR still leads to + stack corruption but that corruption doesn't matter. +- Brad: agree with merging PR. Should open revert PR now. +- Alyssa: add comment indicating hack needs to be reverted. +- Hudson + Amit: agree + +## Tock Foundation + +- Amit: 501(c)3 very difficult. +- Alternatives, just as 501(c)6 should be fine. +- Technically, could accept money now through Open Collective. diff --git a/doc/wg/core/notes/core-notes-2022-09-02.md b/doc/wg/core/notes/core-notes-2022-09-02.md new file mode 100644 index 0000000000..f04c9d320c --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-09-02.md @@ -0,0 +1,207 @@ +# Tock Core Notes of 2022-09-02 + +Attendees: +- Brad Campbell +- Chris Frantz +- Branden Ghena +- Alyssa Haroldsen +- Amit Levy +- Pat Pannuto +- Alexandru Radovici +- Jett Rink +- Leon Schuermann +- Vadim Sukhomlinov +- Johnathan Van Why + +## Updates + +### Machine-Readable Tockloader Output + +- Brad: revisited an old Tockloader issue of adding machine-readable + output. Now some commands output JSON, for use in scripts. + + Broader goal was to use Tockloader in testing, e.g. checking the + current state of a board. Could be useful in a CI setting. + +- Alyssa: Commitments to stability of output and/or tests? Might we + worth a tracking issue. + +- Brad: Stability of output is a concern, no good solution yet. Maybe + we should get somebody using it first? Just recently introduced. + +- Johnathan: In the long run might make sense to rewrite in Rust, + given as of now it's the only reason for a Tock contributor to learn + Python. Maybe it should be architected as a library? + +### Progress on Capsule Testing + +- Branden: Working on capsule testing. Commands worked with Alyssa's + code. Trying to get allows and subscribes working; they require + Process and Grant space to work. Made a whole mock implementation of + Process. Next step is figuring out how to allocate Grant + space. Dynamic allocation / big array? + +## Tock 2.1 + +- Leon: Catching up on release tests. Remaining board is the newly + introduced QEMU RISC-V 32bit "virt" platform, to support VirtIO + devices. Tested it with apps before the PR was merged, no longer + works. Will take the weekend to figure this out, if we don't tag the + release. + +- Alex: All STMs have been tested (all boards that I have). STM-F3 + reboots continuously on a fault. Tried to debug this with Hudson, + have not found the issue yet. In a panic print, reaches the process + memory table and reboots immediately then. + + Faulting due to memory issues was related to incorrect memory sizes + in the linker script. + + On the RP2040 IPC faults in an unexpected place. It seems to fault + randomly if the IPC callback function has any code in it. Other than + that, all works. + +- Branden: Do we find these kinds of failures acceptable in a release? + +- Brad: IPC one would be good to solve, but should not hold up the + release because of that. ARM Cortex M0 support has not been tested + extensively. Other issues seem acceptable. + +- Pat: There once was a Tier-{1,2,3} board support list. + +- Leon: Recurring theme, established multiple times that this might be + good to have again. + +- Branden: Is there a place to document issues with "second-class" + boards then? + +- Leon: GitHub issue [#3141](https://github.com/tock/tock/issues/3141) + collects breaking changes and generally content release notes. Might + be a appropriate place to document this. + +### PR #3175 (syscall: Fix SuccessU32U64 format) + +- Leon: As part of Tock 2.0 multiple system call return variants have + been introduced, documented in TRD 104. By this definition, they are + part of our ABI contract. For the `SuccessU32U64` variant, it has + been implemented as to return a 32-bit value in the first, followed + by a 64-bit value in the following registers, whereas the TRD + specifies those values to be passed the other way around. + + PR #3175 changed the kernel implementation to match the + TRD. `libtock-rs` implemented the specification as documented in the + TRD. Even though we are not using this particular variant upstream, + it is exposed in the kernel crate's API surface and could have been + used by downstream users of Tock. + + Issue is that this technically breaks our ABI compatibility. How + closely are we following semantic versioning, what's our plan here? + +- Alyssa: Was this API actually used? + +- Brad: We do not know for sure, but likely not. + + Strong proponent to sticking to stability guarantees. We want to + guarantee that the low-level ABI is backwards compatible. That said, + the documentation is as intended. `libtock-rs` was written against + that documentation. We have not evidence of it ever being used. We + should update our code. It is not a breaking change, in the sense + that it would not break anything in practice. + +- Leon: Supposedly the TRD 104 has been written (and actually adjusted + during development) to reflect the target platform's calling + conventions, so adhering to this might actually be more + efficient. Looked into the generated assembly, but no results yet. + +- Amit: We made the same mistake twice, once in the kernel and once in + `libtock-c`? If we hadn't made the mistake in `libtock-c`, this + would clearly be just a bugfix. + +- Johnathan: Did not actually make the mistake in `libtock-c`, as it + does not have any code to decode that particular return variant. + +- Leon: Touches on the root of my question: at what layer would we + like to guarantee stability? ABI aka. register interface or + userspace library ABI as provided with `libtock-c` and `libtock-rs`? + +- Amit: If an application is compiled (binary artifact with userspace + library included in that) for 2.0, it should work on 2.1. + +- Leon: That would make our guarantees decoupled from the exact + userspace library used in compiling applications. + +- Johathan: If this breaks someone, it is code that has been written + against the kernel implementation in code that we cannot see. + +- Amit: Seems like this is a bugfix. The API is defined through the + TRD. If there is someone out there who wrote their own system call + library, it was probably someone who would have written it against + the TRD and noticed this bug. + +- Leon: Seems fair. If we conclude to view the TRD as the ultimate + governing document and define stability in its terms, we should be + consistent in that. Otherwise this is going to be arbitrary. + +- Alyssa: Natural for decisions to feel arbitrary in the beginning of + a project (at the current stage). + +- Branden: Thought our argument would be more along what Amit said: if + an app worked on 2.0, it should work on 2.1. We are breaking that + here. Maybe this app does not exist, so we are fine now. But it + seems we did have a decision and we are deciding against it in this + case. + +- Alyssa: Even Rust stable has made decisions which go against its + theoretical guarantees of stability, for bug fixes or security + fixes. + +- Amit: We always have to make some sort of judgment call about what + is 2.0 in that particular case. Is it the implementation which has a + bug, and perhaps someone has written an application to rely on + that. Or is it the idea / specification of it, which a reasonable + app has been written against. + +- Brad: It is the code, as this is what people compile against. + However, this is a good example of one of the limits of that + stance. Certain that there is some ambiguity in our documentation, + and we cannot use that as an argument to vouch for changing the + implementation. + +- Alyssa: Documentation should be the final decider. In the case of + ambiguity, the code is what determines the interpretation. + +- Branden: If we had used this extensively, would we decide the other + way around and change the TRD? + +- Leon: Third option: new release. Breaking changes are not + necessarily bad, we just have to communicate them properly. + +- Pat: If we would have used this in `libtock-c`, we would have caught + it, because it would not have worked with the way the TRD is + currently defined. In this specific instance, it is very unlikely + that any code has relied on that; some level of pragmatism. + +- Amit: For other kinds of mistakes, we can also deprecate a + implementation and introduce a new one which implements the desired + approach. + + Does not make sense in this case, because there is inconsistency and + someone's code would break anyways. + +- Brad: Final decision? Leaving #3175 in? + +- Branden: Seems like the decision. Anyone opposed? + +### Remaining TODOs + +- Brad: Waiting on release notes, RP2040 SPI busy issue and last of + Leon's testing. + +- Leon: I've tested all platforms which are actually in use. The + `qemu_rv32_virt` seems broken, but it's a newly introduced platform + and not particularly useful without VirtIO support yet, so we can + leave it out if I don't get it to work before the other issues have + been resolved. + +- Brad: Might do a corresponding Tockloader and elf2tab release as + well. diff --git a/doc/wg/core/notes/core-notes-2022-09-09.md b/doc/wg/core/notes/core-notes-2022-09-09.md new file mode 100644 index 0000000000..eff8c59cf8 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-09-09.md @@ -0,0 +1,128 @@ +# Tock Core Notes 2022-09-09 + +Attendees: +- Branden Ghena +- Brad Campbell +- Alistair +- Leon Schuermann +- Hudson Ayers +- Vadim Sukhomlinov +- Chris Frantz +- Phil Levis +- Pat Pannuto +- Johnathan Van Why +- Alexandru Radovici +- Alyssa Haroldson +- Amit Levy + +## Updates + + * Phil: With 2.1 being out, I pull the AppID up to the current master. So it's synced up, but we have stuff to talk about today so it's not ready to go. + * Hudson: I talked with Alyssa about deferred call series of PRs. It's a pretty tricky problem to find a solution that works well for both dynamic and normal deferred calls. Some creative use of unsafe could lead us to a solution, but still in progress. + * Phil: This seems to recur in Tock, where we find a hard thing, talk to Rust experts for a while, and hopefully there's a solution. + +## PR Review + * We merged a TON of PRs that were waiting on the 2.1 release, and we have a lot to talk about today, so we're jumping past this. + +## AppID - KernelResources Structure + * https://github.com/tock/tock/pull/3124 + * Phil: A major question right now for App ID is in #3124. + * Phil: Brief overview, when you create a kernel you pass in a verifier. It looks at credentials and decides if they are good or bad. Brad has taken the position, which I mostly agree with, that we don't want to pass arguments to the kernel. Instead, KernelResources specifies all kinds of stuff, and this should become a field in KernelResources. This has some nice side effects about lifetimes. + * Phil: My response is that this could happen, but KernelResources is this organically grown grab-bag of stuff. So we should probably clean it up and decide what belongs and what doesn't. Example, if you look at the structure there's stuff related to hardware and security policies and it would be good to structure that better. Timers, watchdogs, filter, etc. We should probably decompose into multiple things. + * https://github.com/tock/tock/blob/abfe6c3a6cb6c5f4e6ae074c2e3a667624a985e4/kernel/src/platform/platform.rs + * Phil: So, that's the first discussion about AppID. + * Hudson: What do you imagine this split would look like? Multiple traits, or organization within this trait, or hierarchy? + * Phil: I think it makes sense to break into 2-3 traits. One for security, one for kernel configuration, and one for hardware. The second two _could_ be merged, not sure. But this is the structure for how you configure the kernel. And it now has 8 types in it. So it's time for more structure. + * Hudson: The main reason for all these associated types is that it reduces the number of generic parameters we have to store. + * Alyssa: We explicitly use this pattern on Ti50 in places. Reducing generic parameters by passing in a single type that holds multiple types. We sometimes use zero-sized types instead of holding the resources themselves as well. Helps maintaining things. + * Phil: I think the technique is fine. But I still think we should decompose into classes of things. + * Leon: I thought we wanted KernelResources to reduce generic types complexity. I always thought of it more as a technicality. + * Alexandru: I thought Phil wanted to split it into traits, not add generics to it. + * Leon: Right, but the primary utility wasn't usability for people who configure boards, but rather for making code in the kernel simpler by reducing types. + * Alyssa: You could still have both with a hierarchy and subtraits. Personally, I'd want to see a specific use case for splitting things up before doing it. Not only for the breaking change, but I'm not sure we've reached the complexity where it's impossible to understand the entire thing. + * Alexandru: I agree with Phil. The code would be better organized if split. KernelResources is large and handles a lot of things. + * Phil: For example, I might have a kernel with particular security policies. But I might change which drivers it supports or compile it for different architectures. Right now, those two things are coupled. There's no way to split out the security stuff from the architecture stuff. We could continue this way for now, but it seems like it will be an issue going forward. + * Hudson: It being a breaking change for out-of-tree boards is important. We do that all the time, but we _should_ minimize it. We could have a hierarchy, but it feels a bit like adding layers for layers sake. Is the concern that because there's so much someone will ignore something and not set it right? + * Phil: From a least-privilege perspective, when I want to allocate a grant, there's a kernel resources for that. Which means that the allocate grant code _could_ touch the verifier or the scheduler. + * Brad: That's not quite right. The kernel has access to all the configuration options, but you wouldn't pass a KernelResources to the grant call. + * Phil: Well, there is this: https://github.com/tock/tock/blob/50550987a73dd596bf0384adc132d20bf722ea28/kernel/src/kernel.rs#L99 + * Brad: Sure, but that's still in the kernel. It wouldn't go deeper. + * Alyssa: In those cases, we could just take one or two items by trait, passing in specific things instead of KernelResources. + * Brad: One question is where does the split happen. Does the split need to be exposed to the board author, or internal to the kernel trait? + * Alyssa: Where do you want to isolation to occur? + * Phil: So, backing up, that this is a breaking change means we shouldn't do it lightly. And we definitely don't want to iterate a whole bunch of times. So I'd say we should table this for now, but keep it as a possibility for later. + * Hudson: I agree. AppID doesn't need to block on this. + * Brad: We should open an issue for this. (agreed) + * Alyssa: We have multiple traits with overlaps like you're talking about. When we refer to an inner one, it builds a new implementation of internal stuff with a zero-sized type. That'll let anything that implements KernelResources implement, say, SecurityResources. So you can still pass in the raw thing, but lock down what the inner function gets to access. + * Phil: That makes a lot of sense as an approach. I'll make an issue with some of this discussion and link in the PR. + +## AppID - Role of Verifier + * https://github.com/tock/tock/pull/2809#discussion_r937380785 + * Phil: Second major item is role of verifier and short IDs with security policies in the kernel. + * Phil: Background: if you recall we have TBF headers specifying which system calls you can issue and which storage IDs you can access. These headers would be covered by integrity and authenticity in AppID. I believe Alistair has been using these successfully. So, in AppID you can take an app and make a short 32-bit ID and use it for security to determine which APIs can be accessed. So there's the notion that the kernel can impose security policies based on short IDs. This is different from a process/TBF-object stating what its security permissions are, potentially covered by integrity. + * Phil: So, a signed TBF-object can specify things and kernel trusts whoever signed it. But with AppID, the kernel can also impose its own policies and not have to trust the TBF-object. So there's this interesting case of whether the policy is part of the TBF-object or part of the kernel. + * Alistair: Missing one thing. If you're doing the enforcement through the TBF object, it's decided at compile time. But you can also add that enforcement to the kernel. If you have some key signing on an app, no matter what the TBF says, we won't give it the permission. So it's not one or the other, it's both. In case someone makes a mistake in the TBF object, for example. The checker would have a list of public keys that it allows on the board and what each key is allowed to do. + * Johnathan: The short ID mechanism that Phil implemented is a compression version of that list. So we don't have to store everything and duplicate long AppIDs everywhere. + * Phil: So that's right. The kernel could check, but it would have to compare the whole 4 kb key. You just want to do a word check at runtime. + * Alistair: You'd just do it the first time. + * Phil: Where do you store that state? Are you saying there's extra information attached to the process? That's what short IDs were for. + * Alistair: There's a listing and you take the intersection to decide if it's good or not. + * Phil: So in your model, it's required that the application specify in the TBF header all the things it can do. At loading the verifier also has a list of all the things that certain signed things could do. And you reject the app if they don't match? + * Alistair: Yes. But many verifiers won't care and will just trust TBF headers. + * Phil: So this really changes process loading. There's this additional check. + * Alistair: Yes. But on each syscall, you don't have to check against the AppID. You still need syscall filtering on a little list for the process. But it's previously been verified so you never have to look up short IDs anymore. + * Phil: Looking up the short ID isn't a big overhead. But every TBF object now, if you want a security policy on system calls, requires all processes to completely list their set of operations. You have to have syscall permissions headers. + * Alistair: If the app doesn't list any, and the verifier has a smaller subset, the verifier could return that smaller subset and keep it in RAM somewhere. + * Phil: But the verifier is just about checking, not imposing security policies on top. + * Alistair: I'd argue that integrity is part of the header, but also whether it matches what the kernel expects it to be able to do + * Phil: But that's a separate thing + * Johnathan: Generally, you want to look at all the crypto stuff first, then do all the other verification separately. You don't want bugs from other verification to mix in with your crypto checking code leading to bugs. + * Alistair: Fair. You could still do the whole crypto check first. + * Johnathan: This just doesn't seem like the verifier's responsibility. + * Phil: I agree with Johnathan. Verification is just integrity and authenticity. We could still have a separate mechanism for applying security policies based on headers of the app. It's just a separate question. + * Alistair: It could be separate. Just adds a bigger state machine. + * Alyssa: It can still use a lot of the same mechanisms though. + * Alistair: I think so. It's fine if they're separate, but I think it should exist. + * Phil: You're saying it's useful to be able to have a thing that decides whether to load processes based on TBF headers? (yes) + * Branden: So it sounds like Alistair is asking for a separate Kernel feature, separate from AppID stuff. + * Alistair: Yeah, I think a second place in the app-loading process that can check the headers. + * Brad: I'm not seeing the benefit of combining them versus keeping them separate. + * Alistair: Yeah, so two states before a process goes to runnable. + * Phil: Currently a checker looks at a TBF object and checks if the credentials exist and are valid, meaning that it approves loading the process. So in the future, we could require a signature from one of N keys in order to load a process. It's just about authenticity and integrity. And you need that for all kinds of truncation attacks and things like that. + * Phil: I think if we want to extend the method of loading processes to add features, we can. I'm a little wary in that, even doing this one thing has taken a year. I don't want to just throw in a bonus thing that looks at headers. It seems like a whole new thing to design. + * Alistair: I'd say that just passing the headers into a function isn't a new design. We're already passing in the footers. + * Brad: The part that's a new design is changing the intent of the implementation. So an implementer would have to choose which types of checking it wants to use and order and stuff. That all gets pushed to the board author. That's what would change the design. + * Phil: And as Johnathan pointed out, you absolutely don't want to look at headers until you have verified their integrity. + * Alyssa: What kind of "not look at it". Don't parse it because it could be malicious? + * Johnathan: Headers aren't trusted, but in general yes. You really don't want to do any parsing at all if they could be invalid. This depends on the trust model. + * Alistair: We are parsing the headers a little before going into the checker + * Phil: Yeah, if you assume a potentially malicious header there's a big expansion in checks. The stuff we do look at first has a TON of checks in there now, to check size and alignment and what not. If you added each type of header, that could be a lot of code. + * Alyssa: I'm not sure. What area of code are you looking at? + * https://github.com/tock/tock/blob/eb3f7ce59434b7ac1b77ef1ab7dd2afad1a62ac5/kernel/src/process_standard.rs#L1236 + * Phil: In create of process standard. There are a lot of checks that occur. We might have to discuss more offline. + * Alyssa: There are a lot of checks, but if you're dealing with data you need resiliency. Regardless of whether it's signed and verified. + * Johnathan: Agreed, but it's a defense in depth approach. + * Brad: I agree and wish we had better support from Rust to do that. Rust buffer parsing code is a lot like writing C where you hope it's correct and it panics. We think we're doing it right now so it can't panic, but you have to very carefully review every PR to that area to ensure that it can't panic. + * Alyssa: The Linux kernel has been asking for this specifically. Some way to ensure that it can't panic. + * Brad: That would be a big step and useful. + * Branden: So stepping back, I think a summary is that Alistair wants to modify how app verification works to add a step where TBF headers are checked. And the thinking from the rest of the group is that should be a separate step from the existing crypto verification of integrity and authenticity that occurs. + * Phil: Yeah, I think we need to verify integrity first before looking at headers and such. If we're going to change the semantics about what a checker can do and what it can consider, that's a bunch of steps backwards. + * Alistair: That's not a great argument. Better to go back than to merge something that has issues. + * Phil: You have a specific use case you'd like to support. I think it's an interesting idea, but it's a new idea to affect process loading based on syscall filters or other headers. Should we go back to the design stage? + * Alistair: This was brought up at design. But to me, I don't think it's weird that we might look at headers in the verification process. + * Phil: But there's a lot of subtle stuff there that's at the core of the security model. + * Alistair: I agree. But I don't think that means it can't or shouldn't be done. + * Phil: The question is do we merge this and look into that later. Or does that need to be part of this right now? + * Brad: I don't honestly see us halting at this point. Keeping this PR outstanding requires a huge amount of effort to keep it in sync. It seems like we have to move forward. I do NOT think that we can't change these traits in the future. If we are missing something it's possible to add things and this isn't the final design. We need people to try using this and add implementations and I don't want that to go on hold. + * Hudson: With the size of the PR and how long it's been open, it's a huge priority to get it in. It's a large amount of work not just for Phil to rebase, but for reviewers to keep track of the changes. + * Phil: And after it's merged, I think a further iteration on the process loading algorithm seems reasonable. We can do that and design and implement and test. Seems reasonable to me. + * Branden: How about Alistair? Would doing that harm the ideas that you're working towards? + * Alistair: I'm on the halt train. However, I'm fine if everyone disagrees. Especially, if we can make changes in the future. Sometimes its hard to tell if the community will accept changes to things after they go in. + * Brad: Upfront, we certainly don't want to change things. But if we need to make a change due to a meaningful use case or performance, we're willing to do that + +## AppID - External Dependencies + * Hudson: We're out of time. Anything you want people to look at Phil, before next week? + * Phil: The key thing is "Allowing external dependencies for cipher implementations the recurring issue of flash HILs, notably: https://github.com/tock/tock/pull/2993, https://github.com/tock/tock/pull/2248" At some point if we want crypto in the kernel, we're going to have to have external dependences that implement ciphers. We do NOT want to do them ourselves. So that's something to think about having a carveout for. + * Brad: That seems like a good issue + * Alistair: There's an AES GCM PR open about this too. + diff --git a/doc/wg/core/notes/core-notes-2022-09-16.md b/doc/wg/core/notes/core-notes-2022-09-16.md new file mode 100644 index 0000000000..c348b37959 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-09-16.md @@ -0,0 +1,270 @@ +# Tock Core Notes of 2022-09-02 + +Attendees: +- Brad Campbell +- Alyssa Haroldsen +- Pat Pannuto +- Alexandru Radovici +- Leon Schuermann +- Vadim Sukhomlinov +- Johnathan Van Why +- Philip Levis + +## Updates + +Phil: AppID + +Brad: Support for parsing, adding, and removing credentials in tockloader +is in the master branch. There might be bugs, but it's working. There's +also progress in elf2tab, making it so you can build TABs with credentials +and footers. We want to make it possible for poeple to start using this +AppID ecosystem as soon as possible. + +Hudson: That will be 0.11 for elf2tab? + +Brad: No, I think 0.11 will be for 2.2. There are still some unknowns +on the exact formats of credentials. When those settle out and are +stable, if that's before 2.2, I don't see any reason not to do a 0.11 +release. + +Hudson: So people will have to pull and work out of git. + +## PR Review + +Hudson: There's an update to static macros, such that if the macro +is called twice, it panics rather than fail silently. Now that it calls +panic, I don't think static_buf needs to be unsafe, which means that +static_init doesn't need to be unsafe, so that is something to look at. +One issue is the extra code size for this additional function call is +1500 bytes for imix, which is significant. We could have a "safe" static +buf, which is checked, and an "unsafe" static buf, which doesn't include +this check. So that's + +Hudson: Another is 3221, converting ShortID to be an enum rather than an +Option. Also 3216, which fixes UART MUX to be able to create multiple +MUXes, which inspired the changes to static_buf. Currently the UART MUX +component, because it called static_init locally, in finalize, a second MUX +would use the same memory which is of course unsound. + +Hudson: THere's a README update for OpenTitan, which lets people use a +much newer version of OpenTitan. This is important because OT has moved to +a new build system, Bazel. This is part of a longer-term effort to update +Tock's support for OpenTitan. + +Hudson: Three merged: 3191, board tiers, 3184, which defines the buzzer +HIL, and 3196, which added support for the particle boron board. + +Hudson: What are the TODOs for AppID, blockers? + +Brad: So one question that we had on Wednesday was is if two processes +have the same application ID do they necessarily have the same short ID? + +Phil: They do not necessarily have the same short ID. + +Brad: Well, I would put that the other way and say, well if they're using the +short ID locally unique, then they're using the app ID locally unique. + +Phil: Okay, yeah, I think I think I buy it right that so basically what this means is +that yes there is that this notion of just locally unique IDs. + +Brad: Yeah, so, I guess. Okay. So there is this lingering question of if you write in +app ID to short ID compressor, are you responsible for ensuring that if the app IDs +are different, the short IDs are different. And I would say yes, you are responsible for +that and so therefore, we can just check the short IDs for uniqueness. + +Phil: Yes. + +Johnathan: Implication doesn't feel that way. If you have two apps AppIDs then they'll +have different short IDs but that means you can have two apps with the same long ID. + +Brad: No, that requirement is also fixed if you have the same app ID, you must have the +same short ID and if you have different app IDs you must have different Short ID. + +Phil: Also, I have two versions of the same binary version one version two. They +might have different app IDs but they have the same short ID. + +Leon: For them what, when this isn't bizarre behavior. How would I write a +compression function which takes for instance, a signature over give them a +binary and tries to do just to show that from that or is that just like an +entirely unsupported use case and you should use locally unique IDs which already pulled up. +Because what this kind of tells me is that users of the boards or developers always +responsible for ensuring that the show that the assignment reflect some properties they +would have liked to have in the system with respect to for instant access control until +that. What kind of make me feel like it is better to have apple IDs be assigned for +instance statically through us for a tool to just talk to order and then store the +headers instead of them being derived from some other. + +Leon: I think I was probably just confused by the terminology of a compression. +I remember in the sense that this is really not compressing along to a short one, +but it's extracting a short that ID from a longer one. + +Brad: There is the rule that the short ID has to be unique when running. So if you +want to have a family of applications that work together they have to have separate short +IDs. So you don't get the benefit of having the same access. + +Phil: You would have to write it in the access rules that all of those Short IDs +have these permissions. For example, let's suppose that you have three processes +that you want to all be able to work together. In some way they need to have unique +short IDs cause they are unique applications. If you, for example wanted to make it +that they all the same access permissions then you can structure the short ID space: +they all have a most significant bit set to one. We talked about having the notion of +families of identifiers and eventually said, a better way to do this is to do it this way +and to allow the implementation to decide how it wants to structure the identifiers. + +Alyssa: I'm worried a little bit about this confusing authorization and authentication. + +Leon: Well, I think that it's still a well, a separate in the sense that the sort of +these are still just identifiers, right? + +Leon: I have an application and I wanna create multiple instances of it like from +that exact same binary. How would I come up with a mapping which assigns each of +these instances of that exact same binary and individual engine and locally in +each application I do Short ID. + +Phil: We're so, I guess what I would I to see, can you write down this use case exactly +what's needed cause that's part of like it's very easy to come up with. You got the +spaces of what we might need to do is huge. + +Phil: I'd like to move on, this is much more than 2 minutes. And I know that Alyssa had +something she wants to talk about, which I think is probably more useful. This +discussion could go on for a very long time ago and so maybe it's something to push to the PR. + +Hudon: Ok, yeah, Alyssa how did you want to talk about this? + +Alyssa: I wanted to talk about the best way to structure static_init. I +really want niche optimization to be possible, but I can't figure out a +way to do it. I can't figure out a way that the Option isn't all 0s. + +Hudson: The most recent commit doesn't use an option. + +Alyssa: On 3219? I want niche optimization to work. There's no way to +ensure that the niche is 0. There are ways to make it 0, but that disables +the optimization. Two issues: guaranteeing the safety of this, should be +able to handle thread-safety scenarios, so you need some kind of +"once mut" that keeps track. Or you need an atomic bool, on platforms that +support it. + +Hudson: How do we implement this on platfors with atomic bool, we check +this once, while ones that don't have it we know it's single threaded. + +Alyssa: There are ways to communicate this with a config flag. Or I believe +they added that you can check if atomics exist. + +Hudson: True, but some of our single-threaded platforms have atomics. + +Aylssa: We could configure this in a central location. We could, say, have +one implementation or antoher based on whether you are single or +double threaded. + +Hudson: Main motivation for multithreaded environments is for testing? + +Alyssa: Yes, testing in general. So you're not writing something that +isn't actually safe. + +Hudson: So some arbitrary Rust user can use it safely. + +Alyssa: Whole bunch of, multiple scenarios. There needs to be some way +to communicate whether the system is thread-safe. We should not be +bundling the bool and the buffer together. due to alignmenty requirements. +We want to put the bools together, without padding on the buffers. + +Hudson: I like that. The second thing is inoffensive, no problem there. The +first thing, is trying to find something that isn't non-ergonomic. Or +we could just leave it as unsafe, and if we want to transition to safe +cross that bridge when we get to it. + +Alyssa: We should keep a static unchecked around. + +Hudson: Keeping them makes sense. The problem for a user who doesn't want +to pay for the code size is that at some point components will have to +make a decision. + +Alyssa: All of our static_init occurs in our kernel setup function. + +Hudson: Some of the helpers do it. There are some examples when there +are calls to static_init in the finalize method. + +Alexandru: There are more problems than just UART. + +Alyssa: Component helpers exist due to unsafe? + +Hudson: Component helpers exist to make it possible for components to +be reusable without requiring everything to be initialized in main. +It's a way to take all of this code that can't be safely contained +within a function. + +Alyssa: Yeah I would just make it an unsafe function. + +Brad: Short answer: allows components to work across different +chips. + +Hudson: Component methods are already unsafe. It's not a safety issue. +I don't recall all of the details. + +Alyssa: It's a pattern I've been trying to unpack. + +Hudson: General dissatisfaction with components. + +Phil: They're always getting better and are never good enough. + +Brad: One of the issues around components is that we've never made +clear guarantees on what they are, what they can do, what they can't +do. So different people have different ideas on what is possible, and +that leads to issues. So we need to have clear answers for the requirements. + +Hudson: Alyssa, one of the reasons why we didn't do the component is +a function that you can only call once, is that we want them to be +used multiple times. + +Alyssa: Why we don't have a more egonomic API for static initialization. +Just calling static_init directly might more ergonomic. + +Phil: Only call once has issues with encapsulation. + +Alyssa: Could we make that a concept, a function that can only run once, +that could give you zero cost at initialization. + +Hudson: Zero size cost at least. I would be interested to see code +that might achieve that. + +Alyssa: I've been hammering my head at this problem for a while. +A function that returns a singleton is not a great design. + +Hudson: Component functions also include calling set_client, passing +default parameters, buffer sizes, etc. + +Brad: Helper macros for components are something I added because it was +the only way I could figure out how to do it to share components across +chips. But I am not a Rust expert, and Rust has come a long way. There +might be a much better way. It's not that I said "This macro is what +we really want." + +Alyssa: Primary thing is initialize static inputs, I was going to suggest +we just do that, but statically creating tuples is less efficient +than individual statics. I might make it a procedural macro that's on +the component impl, which did not exist at the time. + +Hudson: It may be the case, I remember compiler errors on generic +parameters in outer scope, we went to this macro approach instead, it +might be something that's totally fixable. + +Brad: Other part of static init is how components can be used multiple +times in the same board. + +Hudson: Right the issue is people put static_init in finalize and then +it's not reusable. + +Alexandru: I think this is not documented anywhere, that you should not +use it in finalize. + +Brad: We never said "we intend components to be used multiple times on +the same board" and so it was never a design goal. 3219 will be a big +improvement. + + + + + + + + diff --git a/doc/wg/core/notes/core-notes-2022-09-23.md b/doc/wg/core/notes/core-notes-2022-09-23.md new file mode 100644 index 0000000000..1eac6ff882 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-09-23.md @@ -0,0 +1,218 @@ +# Tock Core Notes 2022-09-23 + +Attendees: +- Adithya Anand +- Brad Campbell +- Chris Frantz +- Branden Ghena +- Alyssa Haroldsen +- Amit Levy +- Pat Pannuto +- Alexandru Radovici +- Jett Rink +- Leon Schuermann +- Vadium Sukhomlinov +- Johnathan Van Why + +## Significant Pull Requests + +* Amit: Brad submitted a substantial amount of pull requests for + component updates porting to new static format. + + Hudson submitted PR [#3239](https://github.com/tock/tock/pull/3239) + to remove `StaticUninitializedBuffer` and `UninitializedBuffer` in + favor of `MaybeUninit`. + + Furthermore: + - [#3225](https://github.com/tock/tock/pull/3225) + BBC HiFive Inventor board ported to Tock + - [#3218](https://github.com/tock/tock/pull/3218) + ESP32-c3 Build Fixes, add docs for flashing apps + +## Dependabot and `tock-teensy` PR #18 + +* Branden: Are we still using the [tock-teensy + repository](https://github.com/tock/tock-teensy)? + Dependabot noticed a vulnerability and submitted [a pull + request](https://github.com/tock/tock-teensy/pull/18). + + Seems like no one with knowledge about this repository is on the + call, might punt on it for next week. + +* Leon: Teensy 3.6 is already significantly outdated. Not sure whether + makes sense to continue support. + +* Branden: Suspect we need to wait on Phil and Hudson. Suspect they'd + vouch to archive it. + +### Dependabot Alert on `elf2tab` for `structopt` + +* Branden: Dependabot also sent an alert for our usage of + `structopt`. Migrate to `clap`. Opened issue + [#53](https://github.com/tock/elf2tab/issues/53). + +## `static_init` Updates + +* Brad: `static_init` has been around for a long time in Tock. A few + years ago, to make components work for the kernel, we needed a + version of it which would separate the actual declaration of static + memory from initializing this memory. This led to creating + `static_init_half` macro, which separated creating the static buffer + from initializing it with the object type. The idea was that + creating the buffer could be in the boards main, whereas the + initialization happens in the component itself. About 2 years ago, + we updated `static_init` to make it more sound and safe to use. + + PR [#3239](https://github.com/tock/tock/pull/3239) adds a few new + things: + 1. Components can generally only be used once today. This is because + some of the components use `static_init` + internally. Instantiating these components multiple times would + then alias the underlying buffer. + 2. The infrastructure currently has grown organically, with + different parts of it being in various states. + 3. Rust types for uninitialized memory have been improved. + + This PR updates the `static_init` and `static_init_half` + implementations to check whether one is aliasing the underlying + memory. When this happens, it will issue a panic. + + Furthermore, we are getting rid of a lot of machinery which we added + to Tock, in favor of the core library's `MaybeUninit`. + + Finally, we are introducing a standard practice way to write + components, which we can check against in PR reviews. We should + disallow `static_init` to be called in components, which makes them + reusable. + + This PR should be a good step forward for `static_init` and + components in Tock. It does not, however, enforce the fact that we + cannot use `static_init` in components explicitly through types, but + just by convention. + +* Alyssa: We definitely need to keep the unchecked, raw version + around, which just writes without any checks. This is for places + where we want to use unsafe to eliminate the cost of these + checks. We cannot afford this `Option` cost at this exact moment. + + Also, it does need to be sound on `std` systems, e.g. in unit + tests. This means that, if it is running on a system with threads, + it must perform synchronization. If it is not performing + synchronization, it is unsound. + +* Brad: We do not have any systems with multiple threads. + +* Alyssa: Unit tests do run on systems with multiple threads, and we + want to unit test. Also have host emulation running on x86-64. If it + is `unsafe`, then it's fine to not synchronize. However, if it is + safe and does not perform synchronization, it's unsound. + +* Amit: Is there a way to perform synchronization on these things in + test environments, but is not expensive on our single-threaded + target platforms. + +* Alyssa: I would go with the safest option, which is to enable + synchronization by default and turn it off using a flag. This can be + implemented using `build.rs`, for instance. + +* Amit: Basically, this translates to `#ifdef`s which would incur + `Mutex`es in these macros on platforms where it would be required + for safety. + + Is there a major benefit from having these new macros not be marked + as `unsafe`? These are all used in the board definitions, where we + are working with a global unsafe on the function definition. + +* Brad: Right, this is because we are not changing the component + trait. If we were to mark `static_buf` as unsafe, things would + continue to work. + +* Amit: It seems that the vast majority of this PR is a clear + improvement. A version of this PR which does not remove the `unsafe` + seems uncontroversial enough. + +* Alyssa: I have been thinking more about initialization. Given that + initialization happens all at once during initialization, it would + be nice for us to transition to a function or a macro which + generates a function, which is guaranteed to only be executed + once. Instead of tracking the initialization of each single object, + you are tracking the initialization of a group of objects. + +* Amit: Seems like a good idea. Suspect that synchronization on + multi-threaded systems would still be an issue. Anything which + enforces the invariant that the reset handler is only called once + seems like an improvement. + +* Brad: Board initialization is 90% unsafe. Hence using unsafe as a + marker of caution is not working for us; just have 100s of lines of + code of unsafe. Making `static_init` would help us to specifically + tag distinguished part of the initialization as unsafe. + +* Alyssa: The benefit is only truly there if it is actually safe + though. It is not fine for us to make it safe it it isn't actually + safe to use in every context. + +* Amit: On the target systems for the OS, what synchronization would + look like is a no-op, right? + +* Alyssa: Would also need to disable interrupt. + +* Amit: Even if the interrupt is not reading / writing it? + +* Alyssa: No, only a problem if the interrupt can read / write it. + +* Amit: In the context we're in, we are exposing things which are only + safe because of the environment we are working in and assumptions + made (e.g. hardware specifics, memory layout, etc.), but not + necessarily in the larger Rust context. + + How strongly are we willing to keep never allowing this type of Tock + code to be used from running in a violating environment. It seems + like a reasonable discussion whether this is a case in which we want + to make weaker assumptions about the underlying system (e.g. to + safely run on multi-threaded unit tests), or say that developers + must pay attention to emulate specifics of the particular system one + is working on. + +* Alyssa: If a given piece of code is safe, and is only sound given + certain properties of the surrounding system which are not checked, + then it is unsound. It must not compile on systems where it is + marked as safe and is unsound. + +* Alex: How is this different from accessing a buffer from within a + capsule? An interrupt can fire and conceivably, on a different + system, modify the buffer contents. This is not using unsafe to + access the buffer. + +* Johnathan: It would not be safe to call into a capsule from within + an interrupt context, as capsules are not `Sync`. In the board's + initialization function, there is nothing preventing it from being + accessed from multiple threads. + + The synchronization primitive used in `static_init` can be modeled + through dependency injection. + +* Amit: To summarize: we do not all have to agree on the statement as + to whether it would be _only_ permissible to have `static_init` be + safe when it is sound in a multi-threaded (i.e., non Tock board) + context as well. We can certainly agree that this is desirable. + +* Jett: Inside `static_buf` macro, we currently dereference a + buffer. If we remove that and move it out of the function, we'd use + unsafe in all the call location. This might remove the controversy + around this. + +* Brad: Moving the unsafety up does have a cost. This is not related + to the academic notion of safety, but the software engineering + notion of safety and what it means for users to develop for this + system. + +* Alyssa: We are deferring the decision and figuring out a solution + for later. We are introducing new checks which inform users when + they have violated the assumptions of these macros, even if it does + not make them entirely safe. This itself is a massive improvement. + +* Amit: Defer the discussion of the context switch code to next + week. Also move remainder of this discussion to the pull request and + an issue about removing the `unsafe` marker now or in the future. + diff --git a/doc/wg/core/notes/core-notes-2022-09-30.md b/doc/wg/core/notes/core-notes-2022-09-30.md new file mode 100644 index 0000000000..f613f9d46f --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-09-30.md @@ -0,0 +1,293 @@ +# Tock Core Notes 2022-09-30 + +Attendees: +- Alexandru Radovici +- Amit Levy +- Brad Campbell +- Chris Frantz +- Jett Rink +- Johnathan Van Why +- Leon Schuermann +- Pat Pannuto +- Phil Levis + +## Updates + * Phil: In the OpenTitan working group, we had a long discussion about app IDs + and short IDs. Brad and I converged, and explained the mechanisms to + Alistair. Need to make a few tweaks, e.g. at boot it does not check the + uniqueness of short IDs, and we need to modify the process ID trait so you + can get short IDs from process IDs. + * Jett: We just found an issue in the timer driver when you have + quickly-scheduled timer events. We will open up an issue or PR about it soon. + * Phil: Is the issue when you have multiple userspace timers multiplexed on the + same alarm? + * Jett: No, it's on the kernel side. If the loop that looks through the linked + list takes long enough that a timer has already expired, it will miss an + event. + * Phil: If I recall correctly, there should be something in the code to handle + this case. Something like "if this will fire soon, then fire it now". + * Jett: Yes, but there's still a race. We've seen it, and fixed it locally. + * Pat: There's an issue that's come in about the HiFive1's stack usage. Gabe is + going through some work to figure out what happened and how to address it. + We'll want to see if any of the initialization component stuff is affecting + our stack usage. + * Amit: What's the stack usage now? + * Pat: On HiFive1, it was limited to `0x900` and is now blowing up to + `0x1200`. Hudson did some work to make sure that `static_init!` wasn't pulled + onto the stack, and that didn't make it to HiFive1. Also a lot of the TBF + parsing code might live on the stack for longer than it needs to. + * Amit: How much memory does that chip have? + * Pat: 16k, which is why we didn't do much with it. + * Amit: For reference, `0x1200` is something like 5k, so small but not that + small. + +## Dependabot and tock-teensy + * Amit: I see Phil has responded, it looks like we can archive tock-teensy. + * Phil: I don't know anyone using and it's out of date. Yes. + +## Updating Cortex-M context switch code (#3109) + * Brad: I think this is something we should update for Tock 2.2. Subtle, so + needs eyes on it, want a robust solution. Want more eyes to look at it. Can't + reliably test it because symptoms could go away randomly due to unrelated + changes. Pat and Amit, you've thought about this -- I think we should + consolidate what we know so we're not repeating steps we've already done. Can + we sketch out what should be happening then translate that into assembly, or + how do we move forward? + * Pat: I thought I commented on the PR but just checked and don't see it. + There's a book that describes how to properly do syscalls on ARM and why, as + well as how to correctly read vector control status registers. I will find + that and post it on the PR this afternoon. + * Amit: We should use PendSV to switch to user code. Looking at early history, + we were doing that, but now we're abusing `svc` to go both directions which + is a bit of a mess. There is a more well-established way of doing this on + ARM. + * Pat: The other comment I'll bring up on reproducibility -- we could replicate + our interrupt interleaving by throwing some busy loops into interrupt + handlers. + * Leon: Is there a way we can reproduce this in a well-defined manner? Maybe + emulation with an instruction trace? Want to be able to reproduce the edge + case after the fix and verify it works. + * Pat: I think the answer is probably yes. + * Amit: Research idea I've been playing around with that is applicable to this. + I worry that one issue is that because this is happening on ARM, we don't + have the same kind of tools as we do for RISC-V to do that stuff. + * Leon: What I'm getting at is if there is a sufficiently-accurate emulator, I + would be happy to look into this and try to reproduce it. + * Amit: Maybe with some permutation of QEMU. + * Leon: I guess so + * Amit: There's also an undergraduate starting her senior thesis looking at + bottom halves, kind of similar to this issue. + * Amit: An issue with PendSV is that it is an asynchronous instruction. All it + does is set the bit for the interrupt to occur, and that's not necessarily + unworkable but it doesn't work well with our current schedulers. That doesn't + mean it's the wrong thing to do, but it's a heavier lift than just changing + the assembly. + * Pat: I'll have to think more + * Brad: Are these separate issues? Can we work on the register-stacking + separate from the context switch mechanism? + * Amit: Yeah, probably. It seems like there's enough motivation for several + people to form an informal working group to try to figure this out on the + side. + * Brad: I think this is really important, yeah. + * Pat: Yeah, that seems fine. + * Amit: Slack channel being created as we speak. + +## Notifying drivers about Subscribe/Allow syscalls (#3252) + * Alexandru: The problem we are facing is the drivers have no idea if a buffer + is swapped underneath them. This was not a problem before Tock 2.0, but now + it is. The best example in upstream Tock is the touch driver. If you have + multiple touches, the driver receives a stream of touches and writes them + into the buffer. The application would read the data, then send a command to + acknowledge it to the driver. Now the application needs to swap out the + buffer, access the data, then swap the buffer back again. This requires an + extra command. With my proposal, when the application swaps a buffer, the + driver gets a notification. The other use case we have internally is the CAN + driver, where we need to swap buffers fast. There's currently no way to tell + if an application has finished with a buffer and swapped it. We need to know + when the application has accessed a buffer and placed a new one there. + * Phil: Can you walk through those examples again? In the CAN bus you're + streaming data into the buffer -- can you walk through it again? + * Alexandru: In the touch driver, I have a buffer, the driver receives + notifications of multiple buffers, and fills the buffer. When the driver + receives more events, it can either overwrite the buffer or drop events. + Before Tock 2.0, it would fill the buffer then drop every event until the + application sends a command acknowledging it has consumed the data in the + buffer. Without this, the application could be in the middle of consuming + data as it is erased. In Tock 2.0, the application cannot read the buffer + while it is being shared. Now, the application needs to swap the buffer to + read it. The driver has no way of knowing this, so it needs an extra "hey + I've consumed the buffer command". The optimization here is for the driver to + assume the process has consumed the buffer. + * Phil: Basically you have to do a system call to swap in the new buffer, which + could tell the kernel that it can begin writing to the buffer, but that + notification is currently absent. + * Alexandru: In the streaming driver we use multiple buffers. We can tell + whether one is shared but not whether it was consumed. Currently have an + extra Command call. + * Phil: So the basic use case is streaming data. + * Alexandru: I am pretty sure this will appear for any streaming use case. + * Phil: The driver needs to know where the tail of the stream is. + * Alexandru: Exactly + * Phil: It's not that you are concerned it's been swapped, you just need to be + notified that there's a buffer so you can reset your index to zero. + * Alexandru: Exactly + * Leon: I've looked at this before, and I am sympathetic to this change. Tock + 2.0 requires the application to swap buffers. On the other hand, I think we + should still document that the notification is not sufficient to ensure + buffers don't change. I fear that developing this notification will lead + developers to rely on the assumption that this notification is required for + the buffer to not change. + * Alexandru: It doesn't change anything in the guarantees. + * Leon: I'm trying to think in terms of developer expectation management. + * Alexandru: So add a comment stating that this does not guarantee that the + buffer changes. + * Leon: Not a criticism, I just want to reiterate that we don't change those + guarantees. + * Leon: I would also be interested in the runtime overhead and code size + impact. + * Alexandru: I can check runtime overhead, but on ARM I always get exactly the + same size for the kernel text segment. I think I'm encountering some + rounding. + * Amit: Yeah, it may be allocating or counting in chunks of 4kB or something. + * Brad: What's the issue with using Command? + * Alexandru: It generates several additional system calls. + * Brad: Is that too much? + * Alexandru: Probably if it's high speed. + * Phil: I'm less concerned about the speed cost, mostly concerned about the + semantics. Can we imagine a case where userspace wants to change the buffer + but wants state the driver is keeping not to change? For example, I swap the + buffer, but the kernel still writes to the location it was before. It seems + like a nice clean thing for the driver to automatically know to update its + state. + * Leon: That depends on whether the driver is incorporating such functionality. + If the driver wants to support functionality which says "a packet must not be + located at the start of the buffer but can be located elsewhere", then if the + driver wants to give these additional options it still can opt for the + solution with the explicit command. In terms of network drivers, there's very + seldom the use case where you want to mutate a buffer but not send the packet + at the start. Could be an efficient streaming mechanism for network + interfaces. + * Alexandru: That is exactly our use case. + * Brad: One of the advantages to internalizing the Allow implementation is that + capsules don't get a callback when buffers are shared. To me, that is + intentional, because we had early capsules where that made sense. "I got that + buffer, lets do something". By removing this, we made it impossible for + capsule authors to do that, and all actions must be a result of Command. By + adding this callback, we are re-introducing this capability. Do we have a + definition of what this callback is allowed to do? + * Phil: That's a good point. We had some early capsules that did that, and we + realized that it is not a way to do things. However, that is not why we moved + Allow to the kernel, and that was a side effect. + * Brad: I think this is a very nice side effect -- it makes code review easier. + * Phil: Think about the state drivers are keeping -- indexes, sizes. + * Brad: I'm not saying we shouldn't consider it. I'm saying that if we don't + talk about this, then we're re-opening an issue that we had previously + closed. We should discuss what we will approve, because it shouldn't be + something like issuing an I2C write. + * Phil: Any definition we can come up with, I think we can come up with a + counterexample. May be more clear from userspace, in the context of streaming + operations. One limit is you can only get upcalls in response to Commands. + Swapping a buffer should not start an operation, but in a case like streaming + it could involve continuing the operation. + * Leon: I'm taking issue with the statement that upcalls should only happen in + response to Commands. That's not currently true; a lot of mechanisms can + schedule an upcall. + * Phil: This is not a statement of "anything we state is true has to be + statically verified". + * Leon: Are we enforcing that? I haven't heard of that paradigm before. + * Alexandru: The touch driver currently sends upcalls without needing a + Command. + * Phil: So it sends when you register -- there's no command to say "I want to + receive events"? + * Alexandru: You just register. + * Phil: So it's automatically activated? + * Alexandru: If you have activated the receipt of events but don't subscribe, + but then subscribe, events will start coming in. + * Phil: That's the point. Of course if you activate it without having an + upcall, then you won't receive upcalls until you subscribe. If you do a + subscribe without doing a command, will you receive an upcall? + * Leon: Oh, I thought you were saying that one upcall has to correlate with one + preceding Command call. + * Phil: No, e.g. GPIO. It shouldn't be that I Allow a buffer and I receive an + upcall that the buffer was transmitted. + * Leon: This is kinda pedantic, but we can emulate that for now. A driver could + iterate over all buffers for every app. + * Alexandru: We could change this PR by adding a bitstream within the grant. + When an application swaps a buffer, set it to clean, and when a capsule + accesses the buffer, set it to dirty. This will add some space requirement to + the grant. This will be enough -- you can enter the buffer and see if it is + dirty or not. If clean, you can just reset the index. In this way, the driver + would never receive a callback, but would still know if it is a different + buffer than it saw last time. I think it would work. + * Leon: I would be hesitant because it makes things so much more implicit. + Behavior would become much less predictable. A notification mechanism is easy + to understand. + * Alexandru: This is why I chose the callback instead of this. + * Brad: I do kind of like that idea but probably not enough -- the clearing + would be very implicit and potentially confusing. The general idea that this + isn't a closure-style callback where you can do anything arbitrary but is + instead a notification channel is nice. It scopes it down to what it is + intended to do, and doesn't increase capsule-writing complexity. + * Alexandru: Okay, but it will take more space in the grant. + * Brad: Sure, yeah. I'm not saying this is the implementation we need to go + with. I am advocating for avoiding designing a generic mechanism; rather say + "this is what it is" and the implementation is the current implementation. + Could change the implementation in the future if we find a better one. Want + to reserve the ability to change the implementation. Are we setting the + parameters where we want them to be? We should be explicit: is this new + functionality that we want people to experiment with, or is this meant for a + specific use case and may change in the future? + * Leon: This mechanism prevents the one use case of avoiding another Commnd + call. I'm fine with that, but it would make this impossible. + * Brad: In #3252, it does not look like any event is triggered. Is that because + it is not the same case in the pull request? It just sets a flag, it does not + trigger an event. + * Alexandru: It just sets a flag, the ACK flag. + * Brad: So it's not triggering an event? + * Alexandru: No. The previous driver had an extra Command which was + acknowledging because before 2.0 this was not a problem. + * Brad: I'm saying to Leon, is this PR not capturing what you're talking about. + * Leon: Yes, so I was specifically referring to the motivation that Alex was + talking about at the beginning of the discussion. I got the impression he was + talking about streaming data, where the transmission of data was initiated by + this transaction. + * Alexandru: That was not my intent. You start reading from the network, you + have a big buffer, application will swap it out, don't know when to reset. + Position 100, application just swapped it, have no idea to know should reset + to 0. Problem is I'm getting out the buffer -- it's a race. + * Leon: I get that. It was just a misunderstanding on my side -- I thought you + were thinking of the case of starting a transmission on Allow. + * Alexandru: Command is not sufficient, it is not atomic. I can swap out the + buffer, and before I issue the Command the driver can continue to write to + the buffer. + * Brad: Can we scope this to only the grant? The use case is we want to modify + our grant state. + * Alexandru: The only way I see to do it is to add extra bits to the buffer. + The counter Leon proposed would add a lot of space. I think one bit would be + enough -- indicate "swapped since last use by the capsule". + * Leon: Regarding scoping, the immediate problem is we don't know the type of + the grant in the handling logic. Would need to do dynamic dispatch in the + driver to invoke a method on that type. + * Brad: That is definitely a challenge. I think we've converged and can + continue this discussion on the pull request. + * Alexandru: Sure. + * Leon: Sounds good to me. + +## `static_init!` update + * Brad: Now that the `static_init!` PR has been merged, I have been updating + capsules. I've done X. Don't know how to do it, as we have about 60 + components. Do you want one big PR, maybe split some out if they're not rote? + * Amit: Maybe all the rote ones in one big PR, and do harder ones in separate + PRs. + * Phil: Does it need to be atomic? + * Brad: No, independent changes. + * Phil: I'm of the opinion of preferring larger PRs, with notes saying what is + rote and what to pay attention to. I'm happy to not require that because I + know I differ. Unless you don't want to have to do all of them. + * Brad: I will open a pull request with the straightforward ones. Can take a + look and see if you agree with my changes. Some of them are "we need to do + this" and some are for consistency. + * Pat: I thought we were letting those sit because you and Hudson were + discussing a change that was getting made to components which would bubble up + through all your updates. Are you done with that? + * Brad: Yes, it was all bundled into last week's PR. diff --git a/doc/wg/core/notes/core-notes-2022-10-07.md b/doc/wg/core/notes/core-notes-2022-10-07.md new file mode 100644 index 0000000000..dfff70193e --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-10-07.md @@ -0,0 +1,391 @@ +# Tock Core Notes 2022-10-07 + +Attendees: +- Adithya Anand +- Alexandru Radovici +- Alyssa Haroldsen +- Brad Campbell +- Chris Frantz +- Jett Rink +- Johnathan Van Why +- Leon Schuermann +- Pat Pannuto +- Phil Levis +- Vadim Sukhomlinov + +## Updates + * Phil: My one update is that this is my first week at Google -- I'm on leave + at Stanford -- so I need to figure out what that means for my participation + in decision-making processes. + * Pat: Looking at PRs merged in the past week, there was an OpenTitan one, and + a lot from Brad in a big clean-up effort. I think we'll talk a bit more about + that here. We have a pretty deep backlog of stuff that is ready to go. + +## App ID + * Phil: I think app ID is in the final stretch and has an approval from Brad. I + want to go through documentation one more time and clean up. There is one + change since last time, a structural change. There is "app uniqueness", which + the way that a particular identifier policy can tell the kernel "are the app + identifiers for this policy different". It allows you to make it pluggable + what exactly an app ID is. It used to be that the trait also had a method + `has_different_identifiers`, which given a process and an array of processes, + will tell you whether they're unique. There was a default implementation of + this in the trait and the intention was that nobody would ever reimplement + it, in part because it checks the short IDs, and Hudson suggested that it + should be moved into the kernel crate so nobody can accidentally violate + kernel guarantees. That method has been pulled out of the trait and made a + kernel function. + * Pat: I guess the biggest point of action is to ask "what stands between us + and merging this in"? Should we all review over the next week? + * Brad: I mean, it's been open for a year and a half, so we're had time to + look. In my mind, we're just waiting on Phil. If you want to look at it, you + have until Phil gets these last nits worked out. + * Pat: Okay, I'm not going to fight that. I've reviewed most of it and am happy + with it. + * Phil: After the merge we will have time with it in the tree to iron out + kinks. + * Brad: Since you're looking at documentation, would you mind making a note in + the Imix board about how to get an application that will work. + * Phil: Okay. + * Pat: I think it's basically ready to go, lets see what happens and how it + shakes out. + * Phil: I'll go over the documentation today to make sure everything is + consistent. + +## #3252 and #3258 (Allow notifications) + * Alexandru: Recap of last time: the problem we faced is it is impossible for a + capsule to determine if a buffer was swapped beneath it, which prevents + streaming use cases. After a process swaps the buffer, the capsule doesn't + know, so it will write to the incorrect index. We could have the process + remove the buffer, send a command, then return the buffer, which risks losing + packets. I submitted two PRs. One lets capsules be notified of swaps, which + has the disadvantage of allowing capsules to take action on Allow, and adds 2 + kB to the kernel. The second used the MSB of the buffer's length field to + indicate whether the buffer was already accessed by the capsule. Downside is + it limits the length of the buffer to half of the possible RAM, which is + probably not an issue for Tock. While looking at the size increase, I think I + changed how the kernel searches for drivers, which decreased the kernel size. + I sent that as a separate PR (#3276), which reduces the size by about 800 + bytes. Compared to #3276, the notification PR adds about 260 bytes. + * Pat: All of that length manipulation is in the kernel, none of it is in + userspace, correct? + * Alexandru: Userspace shares a buffer. The kernel, before allowing the Allow, + is checking whether the first bit is set. If the first bit is set, it rejects + the Allow due to size. Instead of using the length in the kernel, I used a + local register. The length is not the length anymore, it has 31 bits for the + length and 1 to indicate if it has been accessed. In the capsule, it can + access a public property of the structure that is set to true or false. + * Pat: This *is* technically a change to our syscall API because you can't send + a length with the top bit set. The reason I was thinking about this is, if we + are going to make a change like that, should we reserve the top X bits for + future use? + * Alexandru: That works. In preparation for this I reserved only 1 bit, but the + structure that reserves the attribute is a non-exhaustive one. + * Jett: Can the capsule prevent an un-Allow? + * Alexandru: No, the capsule can know whether the buffer has been swapped since + its last access. If the application does it right you would know. + * Leon: It is important to that when we say "new buffer", we don't refer to the + contents, but to the location and size. + * Phil: On one hand, I like it because it's a different way of doing it and + it's nice to have multiple. However, a capsule needs to check whether the + buffer was changed on each access. + * Alexandru: No, capsules don't need to check if they don't care. + * Phil: Yes, but it puts the onus on the capsule to insert these checks + whenever they care. + * Alexandru: Yes. The second approach prevents the capsule from taking action + in response to a buffer change. + * Brad: The first pull request, all that did in response to the notification is + set a flag, right? + * Alexandru: It allows the capsule to do something. + * Brad: What does the actual implementation do? Was it not representative of a + real implementation? + * Alexandru: It just calls a function in the `SyscallDriver` trait. + * Brad: It, in this case, is the kernel. I'm asking about the implementation of + that function in the touch capsule. + * Alexandru: It was just setting a flag. + * Brad: If we were to add that notification function, is that they way it would + generally be done? + * Alexandru: Before that, the application had to acknowledge it read the buffer + and has a new one. + * Brad: What's confusing me is it seems like that implementation required the + capsule to check if the buffer was acknowledged. The same check as in the + other implementation. + * Alexandru: Yes. There's no difference there. There's a bool in each case. In + the case of say a network driver, there would probably be an index that would + be reset to zero. + * Brad: That I think is the key. Phil, when you're describing the difference, + you're imagining that the notification function would reset the variable, so + there wouldn't be a check. + * Phil: Exactly, in that particular use case. To me, the tradeoff here is that + from an ergonomics standpoint the notification is much better, but it has the + drawbacks of allowing people to use it for things other than what's intended. + Can prevent that in the mainline repository, but not elsewhere. I imagine the + notification will result in far fewer bugs. + * Leon: If an application issues an Allow call with the same buffer, should + that be counted as a new buffer? + * Alexandru: If the application does one single Allow and shares the same + pointer, it would be set. I could add a check to not reset it for the same + address. If the application would replace it with a null buffer and reset it + that would count as new. + * Leon: I recall in the early 2.0 syscall discussions, we discussed the fact + that the new buffer types still expose the address of the buffer as well as + the length of that buffer. Currently, it is technically possible for an + capsule to distinguish the cases where the buffer remained the same or + changed, but it is not sufficient for the capsule to know whether any Allow + call was issued in the meantime. Is that an issue for you? + * Alexandru: Probably, yes. I mean, if the application was super fast, and I + never got a buffer before. Imagine this: I filled up a buffer, set it to the + application, didn't receive any new packets, the application did an Allow, + didn't swap it -- just read it, then swapped it back, I would never reset the + state. + * Leon: That makes sense for that kind of application. I was trying to figure + out if we could try to reduce the number of applications of this mechanism to + just care about actual changes of buffers in memory. But yeah, if that's the + kind of use case we want to support then I realize that this is not + sufficient. + * Alexandru: The pointer is a private field. + * Johnathan: Maybe I missed something, but in the case you are trying to stream + data into an application and the network, couldn't the application swap + between two buffers atomically and the capsule can check the address to see + if they've been swapped? + * Alexandru: That would work, but do I get the address in the capsule? I might + be missing something, but that was a private field in `ProcessBuffer`. + * Johnathan: I think you get the address in the capsule, yes. + * Leon: It's a private field, but you can dereference and get its address. We + cannot prevent that. I think what you said about the fact you can re-Allow an + identical buffer whose contents have been changed is a compelling argument + that this is not sufficient. + * Alexandru: The application could swap it with a new one, do some processing, + then swap it back, exactly the same. The next packet I get, I will get the + same address. So I won't know the application went through a cycle of Allow + Null, re-Allow the buffer. We will submit a CAN example with double buffering + which uses this. Indeed we have two buffers. But for instance for the touch + driver it could have only one if it expects a lot of touches. + * Johnathan: It seems like the double buffering mechanism works for every use + case unless performance is an issue, and if performance is an issue we can't + decide between these approaches without benchmarking anyway. It seems like + we're adding a controversial mechanism to make a rare use case a lot more + elegant. I'm not really sure what that means for the PRs, though. + * Phil: I feel a little differently. The idea that after you change a buffer + you may have to issue a command, is leaking information about the capsule's + implementation. In that way, that's not a great approach, which pushes us + towards something that happens automatically in the kernel. Which mechanism + is a separate question. Even outside high-performance implementations, just + knowing that it is a different buffer is important. + * Alyssa: I guess I'm trying to understand why this couldn't be a data + structure that capsules keep alongside their other data, store the pointers + with the last range. + * Alexandru: Imagine the following scenario. You have a buffer, and fill it up + to some point, and the application doesn't have another buffer. It swaps it + with null, no events come in, the application reads the buffer, then the + application swaps it back in. Then the capsule cannot detect that the buffer + was read to reset. + * Pat: This is only a problem because there's no coordination between the + capsule and the userland about the state of the buffer. The two could + coordinate, yes. + * Alexandru: Through Command, yes. + * Pat: If there are head/tail pointers in the buffer, then userland could + indicate what was read. + * Alexandru: That would require much more code than simply having a + notification. + * Pat: That goes back to Johnathan's question of "how common is this?". Is this + an idiom the kernel needs to support, or a specialized case? I don't have a + good feel for it. + * Alexandru: For instance, if I want to do aligned reads I need to lose four + bytes from the buffer. + * Alyssa: 0-3 bytes. + * Pat: I guess the other thing is that the way this is designed right now, + everyone who doesn't care is paying the overhead of maintaining the extra + state. I'm probing at the design space here. + * Leon: I think Pat's comment is great as to the impact of this on capsules + which don't care. The design challenge we're facing is the fact that on one + hand we have this generic grant type w/ capsule-specific functionality, but + on the other we don't have that information in the syscall handler. Would + need to encode that information in the grant structure. Can't make it + zero-cost. + * Pat: Could maybe set it as a property of the process. + * Leon: Yeah. + * *[33 second silence]* + * Pat: Alright, so there's a lot of kind of dead air of uncertainty here. Boils + down to a non-trivial ergonomics improvement for some use cases with some + overhead for all use cases. Where do we sit on that tradeoff. + * Alexandru: For #3252 I counted something like 260 bytes extra, after kernel + refactorings. I'm sure it's super correct, and I sent a PR with only that + change. For the second, I haven't sampled the code size yet, but I'll do + that. The penalty should be minimal -- checking and resetting one bit. If the + capsule doesn't care the capsule code shouldn't change. + * Jett: I hadn't seen the PR yet. Can we add this to the `KernelResources` + trait or some other mechanism to make it customizable, or is it too + integrated for that? + * Alexandru: At least for the attribute it would be strange, as it wouldn't be + clear what the longest length you could have for the buffer. + * Pat: I suspect for the attribute case the impact will be no more than 100 + bytes. + * Jett: That amount of space doesn't seem worth trying to customize out. The 2k + number is quite a lot to pay. + * Phil: Yeah 2k is a non-starter. + * Alexandru: If you compile it now it's less than `master`. + * Phil: You're talking about #3258 + * Alexandru: I'm talking about notifications. If you submit a driver number to + an Allow/Subscribe that is invalid, you get invalid rather than nodevice. The + only one that returns correctly is Allow Userspace-Readable. Every function + was searching for the driver, which costs 260 bytes. I moved the driver + search before the syscall dispatch. Reduces by 800 bytes on ARM and 400 bytes + on RISC-V compared to `master`. + * Leon: Regardless of which approach we take we could take advantage of that + optimization, right? + * Alexandru: Yes. That is why I sent the third PR. + * Johnathan: Does this change the error returned by kernel if userspace tries + to call a capsule that doesn't exist? + * Alexandru: It returns `NODEVICE` as it would before automatizing these ones. + It should return `NODEVICE` if the driver does not exist. + * Phil: That's right. + * Johnathan: This might've been a spot where I coded `libtock-rs` against the + implementation of the kernel. I do recall finding something -- although I + thought it was a Tock 2.0 thing not a 2.1 thing -- but I found something + where TRD 104 did not match the implementation. I forget if I updated TRD 104 + to match but it was after stabilization so I didn't think it could be + changed, I'll have to see. + * [Ed. note: see the below "chat conversation on the side" transcript, as + Johnathan's point above was continued in chat alongside the call] + * Alexandru: I have the error there, I can put `INVAL`. Technically the TRD + would not be correct -- Command would return `NODEVICE` but Subscribe would + return `INVAL` for the same incorrect driver number. + * Phil: Yeah it should return `NODEVICE`. + * Alexandru: If my understanding of the kernel was correct it was not returning + `NODEVICE`. The kernel would fail to allocate the grant which would result in + `INVAL` or `NOMEM`. + * Phil: I think we're not looking at a 2kB code increase. I really do lean + towards the notification approach with the caveat that Brad's point is right + -- we want to make sure this is narrowly crafted so it is used for what it is + meant for. + * Pat: So is that a way to move forward from here. One, lets look over #3276 + relatively quickly -- it looks like a good change. Get that in, then + Alexandru can revamp the notification one to get size numbers, and explore + how to change that interface to limit the scope of what it could do. + * Phil: To Brad, because you're the one who raised the concerns about what this + interface can do. What are your expectations and thoughts? + * Brad: Where I am at is Rust always seems to have these magic ways to do + things that I would never have been able to come up with on my own, and + sometimes it doesn't. I think the intent is that this callback should only + modify state in the grant region, that covers all use cases. Ideally, you + would get some sort of closure-like thing where all you have access to is the + grant region. I don't know how to do that. Make it not be a generic + notification like Subscribe used to be, make it more narrow. If there's not a + realistic way to do that, we need to look at documentation approaches to make + it clear. Easy to check during code review if a function only modifies the + grant region. + * Leon: I would like to think that we could have it execute something that is + only passed a mutable reference to the grant region. That seems conceivable, + but maybe not -- perhaps talk offline. + * Alexandru: The problem is if you enter the grant it will pay a higher penalty + than calling an empty function. I'm not sure, need to think about it. + * Johnathan: [Brings up the error code mismatch from chat] + * Pat: We should probably just put this in an issue, because this is an issue. + * Johnathan: Yeah + * Phil: Definitely that looks like a bug. + * Johnathan: This gets back to the conversation of "how do we handle + implementation/doc mismatches". At the time, I didn't even think it was a + question of possibly changing the implementation to match the documentation, + but we've had more discussion since. + * Alexandru: From the [missed] point of view, I'm most probably sure the user + will expect a `NODEVICE` if there's not a device. At least for debugging, + this will be a nightmare if somebody messes up a driver number. + * Pat: This is probably a longer discussion we should discuss on the issue, but + from the perspective of semantic versioning this is a bug and a bugfix would + be a minor point fix, even though it is changing what the ABI does. Gets a + little messy. A longer conversation than we're prepared to have now. + * Pat: With that in mind, I think we have a few action items moving forward. + Want more precision on scoping for what notification does, and Alexandru can + explore some code and see what happens there, as well as trying to isolate + the performance and size impacts. This issue we've surfaced in chat, + hopefully Johnathan you can open an issue and we can discuss it offline then + it will be a chunk of next week's issue. + * Alexandru: That's okay for me. + * Johnathan: Okay, I'll post it. + +### Chat conversation on the side +This conversation happened in chat alongside the above conversation: + + * Johnathan: This is what I was referring to in libtock-rs: + https://github.com/tock/libtock-rs/blob/1d785a043a95d83b410f6a099a6121fc101ca3b7/unittest/src/fake/syscalls/subscribe_impl.rs#L76. + * Johnathan: Subscribe returns NOMEM if it is called with a non-existent driver + number + * Pat: + https://github.com/tock/tock/blob/master/doc/reference/trd104-syscalls.md#42-subscribe-class-id-1. + Reading very quickly, it looks like the TRD specifies what is done about + invalid upcall, but not invalid driver number? + * Pat: though maybe that's earlier.. + * Johnathan: IDK what TRD 104 means, but that's what was stabilized in Tock 2.0 + * Pat: If userspace invokes a system call on a peripheral driver that is not + installed in the kernel, the kernel MUST return a Failure result with an + error of NODEVICE. If userspace invokes an unrecognized system call on a + peripheral driver, the peripheral driver MUST return a Failure result with an + error of NOSUPPORT. + * Pat: That's in §4.0 of TRD104 + * Pat: seems like we do have an implementation / documentation mismatch? :/ + * Johnathan: Yes + +## Migrate `ProcessSlices` towards raw pointers #2977 + * Leon: I've been looking at an ancient PR of mine, #2977, which tries to + migrate `ProcessSlices` towards raw pointers underneath. I think it's an + important issue to solve but tons of things have come up and I haven't had + time to get to it. `ProcessSlices` are currently built using `[Cell<>]` which + is unsound in terms of Rust's aliasing rules, and the only sound solution + seems to be to change the types to use raw pointers and use Rust's methods + for working on raw pointers. I think there is general agreement this is + useful and the API seems to be good. As part of the PR, we wanted to + transition as many capsules away from panicing on userspace buffer accesses + and perform more sensible error handling. I've spent a day rebasing it, and + reworked tons of capsules to refactor them. The lesson from that is we should + punt on converting capsules away from panicing accesses to a separate PR, + because it is hard to do that without changing their behavior on edge cases. + E.g. when a process asks a capsule to perform an operation that goes beyond + the bounds of the buffer it has shared. That's kind of the state of that, I + think the only thing missing is Miri tests, which I'm currently writing, to + have some confidence that what we're doing is sound. Does that make sense to + everybody? + * Pat: I think so + * Phil: It makes sense to me + * Alexandru: Yes it does + * Leon: The takeaway is I'm working on it again, I didn't forget it, it's just + a lot of work and as soon as we get the tests I propose to try to get it in. + Then work through capsules one-by-one to change them to sensible error + handling. + * Pat: It's a longstanding pain point so I'm onboard with getting this to + happen. + +## Component update PRs + * Pat: On the subject of large mechanical changes, Brad has a small army of + component PRs. I reviewed a bunch, tagged them last night. We'll probably + merge them now, unless someone has a reason not to, we should probably get + those in. + * Phil: I `bors r+`-ed the basic one. + +## Re-entrant interrupts + * Jett: Does Tock OS ever have plans for or against having re-entrant + interrupts? Like re-entrant M mode interrupts? + * Leon: You should talk to Amit about that, he has some plans there. + * Jett: Alright + * Phil: It's useful to note that Tock's interrupt handlers are minimal -- they + wake the system and let the bottom half in the kernel loop do most of the + work. That's mostly because interrupts and Rust's safety do not play well + with each other. Are you referring to reentrant interrupts directly being + handled by kernel code, or do you mean in the current interrupt handler? + * Jett: I do mean the top half ones. We have two scenarios where we have to use + top half handlers for performance reasons. We have a requirement where we + have to act within 10 microseconds, and one of the two takes longer, so we + would like to give the other a higher priority, it would interrupt it and + preform its small task. + * Phil: If we were to wind back to the beginnings when we were first sketching + this out, I think the statement would be that all of that is outside Rust and + the core kernel, and it was always intended that you could do stuff like + that. You would be in assembly land, and all bets are off -- you have to do + it right. + * Jett: Sure, and I definitely agree with you that you have to be careful with + Rust and all that stuff in a fast interrupt handler. We're taking care of + that, we just want the reentrant part. It looks close, but there are some + things like `mret` that we don't save. I tried a few things, and couldn't get + it to work, wanted to see what the general feel ways. Sounds like I could + talk to Amit about it. + * Phil: Sounds right diff --git a/doc/wg/core/notes/core-notes-2022-10-14.md b/doc/wg/core/notes/core-notes-2022-10-14.md new file mode 100644 index 0000000000..3749152689 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-10-14.md @@ -0,0 +1,254 @@ +# Tock Core Notes 2022-10-14 + +Attendees: +- Adithya Anand +- Hudson Ayers +- Chris Franzt +- Alyssa Haroldsen +- Philip Levis +- Amit Levy +- Alexandru Radovici +- Leon Schuermann +- Vadim Sukhomlinov +- Johnathan Van Why + +## Updates + +* Hudson, Phil: App ID PR merged. (*woohoo*) + +* Hudson: Brad has started cortex-m exception handling rewrite, + getting close to component revamp. `finalize` will no longer be an + unsafe method. After that, revisit making `static_init!` unsafe + debate and how to make it safe for unit tests. + +* Hudson: OpenTitan version bump appears to be close to ready. This + will also unblock automatically generating peripheral register + definitions. + +* Chris: Yes, although for the automatic register definitions + generation, some more work is required in addition to that. + + We might generate the definitions once per release through a manual + invocation of the script. Alternatively, we can integrate this into + the OpenTitan release pipeline. + +### Text and graphic displays: couple of PRs open, varying states. + +* Phil: Would like to talk about the various text & graphic display + PRs. + + - Alex: Planning to work on this over the next weeks. Unify the + bitmap, text & 7-segment displays. Was hoping to get some more + input from @dzc-self. Displays vary significantly, blocked at + finding a reasonable abstraction over these types of displays. + + - Phil: Unifying 7-segment with text / bitmap seems tricky. Perhaps + unifying the control path. + + - Alex: Need to sketch this out. Will send you an email. + +### Packed System Calls + +* Alex: Working on the packed system calls. Struggling to find an + implementation which does not significantly increase kernel text + size. + +### Virtual Alarm Issue - PR #3277 + +* Hudson: Jett submitted a fix for virtual alarm (PR + [#3277](https://github.com/tock/tock/pull/3277)), such that in + certain scenarios we avoid missing alarms. There appear to be some + tradeoffs. Assigned Phil to that. + +* Phil: Thought we had a solution to that. We keep on having these + point solutions to subtle problems, there may be a larger underlying + issue with our testing methodology. Will take a look today. + +## Documentation/implementation mismatch for an unknown driver_num (#3278) + +* Johnathan: TRD 104 has a general clause saying that "if a process + calls a system call and passes a driver number that the kernel + doesn't have, the kernel should return `ENODEVICE`". If I remember + correctly, this is behind some error checks, such as the in-bounds + check for upcall numbers. + + In neither Tock 2.0 nor 2.1 this is done for all system + calls. `subscribe` 2.0 we returned `NOMEM` instead. In 2.1, `allow` + and `subscribe` return `NOMEM` instead. We are not aware of any code + relying on that. `libtock-rs` does not actually interpret the error + codes, just forwards them. `libtock-rs`'s fake kernel used them. + + How to fix this? Are we just going to change it to return `NODEVICE` + in every case? + +* Hudson: Brad believes that we should consider this a bugfix and do a + minor version bump. + +* Phil: Agreed. + +* Hudson: Anyone opposed to that? (*no*) + +* Alexandru: PR [#3276](https://github.com/tock/tock/pull/3276) which + reduces the kernel size already contains a fix for that. + +* Hudson: Is it the case that it is difficult to split out this change + separately from the PR? + +* Alex: No, will split it out. + +* Leon: It's good for our release notes to be able to link to a + specific PR for breaking changes, where these changes are isolated + in that PR. + +* Johnathan: Adding a comment on the original issue. + +## Guidelines for when external dependencies *can* be allowed + +* Phil: postpone to next week with Brad on the call. Can explain what + he said on the OpenTitan call. + + Generally, using external dependencies seem necessary in some cases, + but we have to be very careful about the chain of dependencies + pulled in. + + One thought was to only allow leaf-dependencies. + +## Implicit Support of Conditional Compilation in Register Definitions (Issue #3257) + +* Leon: People have been using tock-registers with Rust's conditional + compilation support to include or exclude the generation of certain + fields in `tock-registers`'s `register_structs!` macro. + + This used to work through some tricks employing zero-sized reserved + fields to automatically insert padding in these definitons. + + The errors which now prevent this from working probably have been + errors for some time now, however they would be issued to the user + as part of Rust `#[test]` tests which would need to be explicitly + run (through e.g., `cargo test`). In the latest `tock-registers` + release, these sanity checks have been converted from runtime tests + to compile time errors. + + On the one hand, it seems to me that these usage patterns would + still be good to support through `tock-registers`. On the other + hand, we perhaps should think about whether supporting this is worth + the effort and whether we want to make guarantees about supporting + these use cases in the future. + +* Phil: Strongly against using this within Tock itself. That being + said, if external users want to do this, no one is going to stop + them. + +* Johnathan: When working on the unit-testing support and fixing + `tock-register`'s soundness issues, it became obvious that I needed + to make backwards-incompatible changes. + + There is going to be a similar discussion: do we want to keep both + versions around, tagging one as `1.0` and the other as + `2.0`. Maintain both in parallel? Keep both versions around in the + repository? This issue is another one which feeds into this issue. + + Conditional compilation is going get really difficult with unit test + support. Configs are going to get very fine-grained, switching off + certain parts of traits. + + It seems to me the answer is going to be: merging in my new + functionality, we release the old version as `v1.0` and then release + `v2.0` with the new interface. + +* Leon: One thing this issue makes really clear: guaranteeing API + stability is already hard, but doing it for a `macro_rules!` API is + so much harder. + + It seems very important that we have a solution for users of + `tock-registers` right now --- which may well be saying that this is + now unsupported (we're still pre-release). However, this is still an + important discussion going forward: our new interface will + presumably still be exporting macros. How are we going to manage + user expectations and API stability? + + Specifically: we're exposing a macro infrastructure to users. Not + using procedural macros, by Tock's principles. Because we're + operating on a syntax-tree abstraction, manipulating tokens, I would + go as far as to say that any slight changes to this provided macro + infrastructure has the potential to break external usages in subtle + and unexpected ways. + +* Johnathan: Not entirely convinced that we're not using procedural + macros. + +* Leon: Not necessarily opposed to this, but it'll require an entirely + new discussion around introducing any kind of procedural macro into + Tock. We have been refraining from that. + +* Alyssa: `syn` is a lot more rigorous in what it would parse compared + to `macro_rules!`. It would make things more complicated, but also + it would force us to think about a lot of these weirder scenarios + up-front. + + If valid input that was previously accepted is now rejected in a + future version, that is a breaking change. For the next version of + `tock-registers` to avoid this from happening, we should extremely + narrow and defensive of the things we support. + +* Leon: Remaining question is -- for the currently released version, + we're still in the initial development phase, version `0.x.y`. From + a SemVer perspective, it would be fine to make these breaking + changes. Do we want to take a stance on this and say that this is + now unsupported, or invest effort and try to fix it? + +* Alyssa: Support that mode of usage explicitly, unless we have a + really good reason not to do that. + +* Hudson: A reason not to support it would mean that we'd invest + effort in fixing this, which would be made completely redundant and + incompatible with Johnathan's rewrite. + +* Alyssa: If it accepts attributes to propagate them, it should do so + correctly. + +* Johnathan: The challenge with this is doc-comments. It interprets + them as attributes. If you want your registers to be documented such + that this shows up in Rustdoc, you have to allow passing arguments. + +* Alyssa: Don't have to allow all attributes. Can limit to doc + comments. Really easy in procedural macros. Should be doable in + `macro_rules!`. + +* Hudson: Hadn't promised the library was stable. Wouldn't be breaking + anything by just not supporting this. + +* Alyssa: Issue with all code generation -- have to predict how it's + going to be used. + +* Leon: Circling back to the original issue -- there seem to be three + possible solutions: + + 1. Can't reproduce the issue locally. This may just be a + misunderstanding, and so perhaps resolving it is fine. + + 2. If this turns out to be breaking, we could either take the stance + that this specific use-case is just no longer supported, + + 3. or we could introduce a flag to turn these compile-time checks + off. + +* Alyssa: If we were stable, I'd say revert the breaking changes. On a + version 0, it's fine. + +* Hudson: There doesn't seem to be a lot of motivation to stabilize + `tock-registers` at this point. We've got some pretty big changes + coming up, so there does not seem to be a point in making any + guarantees to downstream users. + +* Alyssa: Obviously, we should provide some best-effort stability such + that things that look like they should work do work. Ultimately, + we're always going to be encountering really obscure uses of + provided interfaces that break. Whether these deserve fixing should + probably be decided on a case-by-case basis, depending on just how + obscure they are. + +* Leon: Takeaway from this: try to get the issue resolved by attemting + to support this usecase, but if it ultimately does not work with our + current release, we prefer to keep the static assertions. This is + not a dealbreaker for downstream code. diff --git a/doc/wg/core/notes/core-notes-2022-11-04.md b/doc/wg/core/notes/core-notes-2022-11-04.md new file mode 100644 index 0000000000..a2f9529f5f --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-11-04.md @@ -0,0 +1,325 @@ +# Tock Core Notes 2022-11-04 + +Attendees: +- Alexandru Radovici +- Amit Levy +- Brad Campbell +- Hudson Ayers +- Jett Rink +- Johnathan Van Why +- Leon Schuermann +- Pat Pannuto +- Phil Levis +- Vadim Sukhomlinov + +## Updates + +### App ID PR #3307: Correct Handling of Userspace Binary Version Numbers + +* Phil: Created a PR ([#3307](https://github.com/tock/tock/pull/3307)) to update + AppId. There is an issue with the current version in tree: it did not obey + version numbers. If there are two binaries which have the same ID, the system + should boot the app which has the higher version number. Going to optimize + code size today. + + Gotcha: to make the loading algorithm simple, there are cases in which there + can be transitive blocking in loading applications: binary A is blocked by B + (e.g., it has a higher version number), B is in turn blocked by C, but C does + not block on A, then it is possible that A does not boot. Seems like a + reasonable limitation, appreciate feedback. + +* Hudson: Tried looking at code size impact of this PR, but looks like benchmark + CI did not run. + +* Hudson: Brad is trying to port Apache NimBLE Bluetooth in userspace. Inspired + partially by the fact that Rubble has been moved into maintenance mode. Also + looked into Zephyr's bluetooth code, relying to much on Zephyr in general. + +### Ethernet on Tock + +* Leon: Created a tracking issue for Ethernet support in Tock + ([#3308](https://github.com/tock/tock/issues/3308)). Rebased a lot of my + previous downstream work, cleaned that up and pushed it to the `tock-ethernet` + branches on the `tock` and `libtock-c` repositories respectively. It should be + in a state where all LiteX boards can run a Webserver (using LwIP) out of the + box on this brach. Now shifting efforts to get the ENC28J60 SPI-connected + Ethernet chip, as well as the QEMU VirtIO network card support finalized. + +* Amit: How are you doing TCP? + +* Leon: Currently all done in userspace through LwIP. The kernel just contains a + "TAP driver", similar to Linux or BSD TAP devices. Ultimate goal: use this as + a basis to get applications connected, iteratively move parts of the stack + into the kernel (perhaps in an intermediate stage calling back-and-forth from + userspace to kernel to multiple times). It's really hard to design an + efficient, adaptable, performant, ... network stack from nothing, so a good + approach seems to approach this step-by-step. + +* Alexandru: I have a Bachelor's student who is going to write an Ethernet + driver for one of the STM boards, to also support a physical Ethernet + interface. + +### Interrrupt & Context Switch Code Investigation / Redesign + +* Amit: There is an undergraduate student looking into the scheduling and + interrupt issues that we have encountered. She is starting to look at how we + could restructure our interrupt handling to hopefully make it more efficient + and also address the complexity of the current solution, which makes it hard + to track down the issues we encounter from time to time. + +* Alexandru: Can you connect us with her? We have some people working on similar + things as well. + +* Brad: Does this include tracking down the issue we've experiencing on this one + build for `imix`? + +* Amit: Yes and no. She has looked into that, as part of that also to understand + the current implementation. It is plausible that the proposal will be + different enough to make fixing the particular bug we're experiencing now + largely redundant. + +* Brad: It seems like we're always going to wonder what the root cause of this + bug was. Definitely curious on what she finds out. It seems like what we have + now is not so bad. + +* Amit: At a high level, the task is to look into alternatives (such as + PENDSV). The outcome is either a proposal or implementation, or a well + thought-out explanation for why the way we're doing things right now is + necessary. The flow of how interrupts get handled in the kernel is a solution + derived from constraints that we have, it's not clear whether what we have is + the right solution. + +* Hudson: If we want to find out what the original bug is, if anyone on the call + owns a J-Trace, that might be the easiest way to get to the bottom of + this. It's pretty expensive. + +* Leon: It seems not entirely clear whether this would be compatible with the + SAM4L and/or whether `imix` has all of the required pins available. + +* Pat: We can probably get more information with the J-Trace still. + +* Leon: From what I've read, SAM4L has a custom mechanism to access serial trace + data on a pin which may or may not be available. + +* Pat: Perhaps we can also use a Saleae Logic for that. We could write a custom + protocol debugger. The hypothesis is that the trace is output on a single pin, + producing a serial stream. The Saleae should be able to sample at a + high-enough frequency. + +## Significant Pull Requests + +* Hudson: A few new PRs: + + - [#3303](https://github.com/tock/tock/pull/3303): Update Nightly Oct 2022 and + remove asm_sym feature + - [#3263](https://github.com/tock/tock/pull/3263): Use Cargo workspace package + table + - [#3276](https://github.com/tock/tock/pull/3276): Fixup return values for + NODEVICE in master + + Open PR: + - [#3312](https://github.com/tock/tock/pull/3312): doc: Add + ExternalDependencies.md + - [#3310](https://github.com/tock/tock/pull/3310): USB implementation for + Raspberry Pi Pico + + +## License Headers Discussion + +* Hudson: [PR #3301](https://github.com/tock/tock/pull/3301) by Alex's company + introduces some license headers into the repository. + +* Leon: Brought up this issue as I remembered that we had a related discussion a + few years back. This was around the issue of adding license headers to files + in the Tock repository as a potential requirement by Google. This PR would add + a license header and copyright notice. Given that we did not really reach any + conclusion after the previous discussion, it seems good to at least have a + consistent line of argument w.r.t. whether we'd allow license headers or + copyright statements in files and what they must look like, before we + implicitly commit to something by merging this PR. + + The discussion last week did also not bring any conclusions. We established + that removing the license text and retaining only copyright statements may be + more problematic than having both or none. + +* Johnathan: The reason why this makes me uncomfortable is that it may seem as + if these files were then subject to the default copyright (where _default + copyright_ is not a well-defined term, varies internationally). Someone might + see these copyright statements and assume it is not licensed under MIT or + Apache 2.0. + +* Leon: Would be fine with adding license texts and copyright notices to files + (where copyright can be attributed to invidiuals or legal entities, depending + on the contributor's legislation). Main worry is that it is going to be hard + to manually enforce that there is a consistent license assignment on all files + (i.e. making sure the license text is identical). This seems rather + important. We may want to then commit to using automated tools to ensure that + all added license texts are consistent. + +* Phil: What is the concern with putting licenses in every file? + +* Pat: Code churning overhead. Also, if we are having licenses in each file, we + have to have someone build a tool to enforce that at PR time. + +* Johnathan: If you all come up with the license header, I can forward this to + the OpenTitan legal committee. + +* Amit: If we added a single copyright header to all files (something like + "Copyright Tock Developers"), that seems fine. What about copyright + assignments to individuals? + +* Leon: I presume this is fine. In many countries, it is actually not possible + to "transfer" copyright. As long as we have each file and each contribution + consistently licensed (under MIT and Apache-2.0), commiters can retain their + copyright but give a non-revocable, non-exclusive usage agreement as defined + by these licenses. + +* Johnathan: Alex, would you be okay with a generic copyright statement such as + "Copyright Tock Contributors"? + +* Alex: Needs to be proof of authorship as mandated by investors, prefer to have + the name included. + +* Amit: At some point, who is the author of a file? For a new contribution that + is clear. What happens if files change over time? + +* Pat: This comes down to collaborative copyright. It really is the licensing + which matters in the end. + +* Leon: In many jurisdictions, whether we have explicit copyright statements or + not is actually irrelevant. What is important that that we are granted the + same irrevocable and non-exclusive rights from every contribution, which is + what we are relying on currently through the presence of the LICENSE + file. Despite not having explicit copyright assignments the files currently + are still subject to copyright. + + I'm advocating for a consistent license assignment in each file, if we do + include headers in files. + +* Phil: Agreed with the fact that it is the license that matters and not the + copyright, for a variety of reasons. + +* Amit: Perhaps it would help to clarify what Rust does and what we're + advocating for. It seems like this position entails that generally it is fine + to add license / copyright headers, but they are not required. Contributors + can add a header indicating our license and add a copyright assignment along + with their name and/or organization. + +* Hudson: Generally seem to be three options: + + - Don't support having license and copyright headers in files and refuse to + accept PRs which add them. Does not seem like a good option. + + - By default insert a license header in every file. In the general case it + assigns copyright to the Tock contributors. Individual contributors can add + their name to it. We would have a tool to check that the license text is + identical in every file. + + - We a notice [similar to Rust's in its LICENSE + file](https://github.com/rust-lang/rust/commit/69b1ccb44e76bc5c3eb815bec852e13becdb96f4), + indicating that "[c]opyrights in the Rust project are retained by their + contributors. No copyright assignment is required to contribute to the Rust + project. [...] Some files include explicit copyright notices and/or license + notices." + + One issue with this is that we don't explicitly place license headers in + each file (while copyright notices may well be in these files). + +* Pat: Looking at the [OpenTitan license + header](https://github.com/lowRISC/opentitan/blob/5af4ad37777f38efc31c579efce649dddaa2541b/sw/device/silicon_creator/rom/e2e/empty_test.c), + we could reduce ourselves to the lower two lines and include it in every + file. Do not even need to explicitly include "Copyright Tock + contributors". It's fine for individual contributors to add copyright notices + if they want. + +* Phil: OpenTitan's strategy views files in the context of their + repository. However, reusable components can be copied to other repositories, + and then the `LICENSE` file pointer does not apply any more. + +* Pat: There's still the SPDX license identifier, and there is even a syntax for + dual-licensing. + + I feel like I have a good enough understanding of the various constraints to + draft this up in a TRD. + +* Phil: Remaining issue - suppose we have a file contributed by developer A, but + then developer B makes significant changes and wants to add a copyright line, + but contributor A does not want that. + +* Leon: Fine from a legal standpoint. By developer A granting us the original + file under MIT and Apache-2.0, the we are in the right to make these changes. + +* Phil: This is not a legal argument, just what if a developer asks us not + to. We wouldn't want to make developer A upset, so this would have to involve + a conversation. + +* Amit: In these cases, we can do some mediation if there is a problem. + +* Phil: In TinyOS, the copyrights were held by the original contributors + (e.g. schools / invidual developers). It was nice to see which people and + organizations contributed to files over time. + +* Amit: To be clear, it seem fine in this context for individuals and + organizations to still add their individual copyright lines. + +* Johnathan: I can offer to write the CI enforcement tool for this. And once Pat + has a writeup and it has received a significant consensus in the project, I + can present it to the OpenTitan legal committee. + +# PR #3312: Document Requirements for Adding External Crates + +* Phil: Concern raised by Brad - if we are pulling in a crate, what is the full + set of dependencies we are adding. `cargo tree` gives us these insights. This + seems like a middle-ground to determine a set of dependencies which a given + Tock release has. + + [First draft shared by + Alistair](https://github.com/tock/tock/blob/1ed33ef32424bee2c7e4b2a2106a4a7b41f99944/doc/ExternalDependencies.md). + Not perfectly in line with what we've talked about, but a good start. + +* Hudson: We probably do not want to start out as broad and permissive as the + text suggests. It also does not address the issue of our safety argument for + capsules: given we're saying that capsules are safe because they cannot use + `unsafe`, capsules using `unsafe` are in conflict with that. This would be + expanding the trust boundary for our memory safety argument. + +* Phil: Response to that is that we use the `core` library, which is unsafe + internally. + + Our reason for allowing external dependencies for use-cases such as + cryptography implementations is that our own implementations of these + libraries could and would likely reduce the security of Tock. + +* Amit: Disagree. It's different security concerns completely. One of them is + linking against untrusted code, having buffer overflows, accessing raw memory + and doing an unbounded number of other bad things. The other is related to the + correctness and security of the actual cryptographic primitive, e.g., + protecting against side-channels. + +* Phil: Generally, the following statement seems to be below the bar for usage + of external dependencies we want to maintain: "The external crate must provide + important functionality that couldn't easily or realistically be provided by + the Tock developers." + +* Hudson: We would most likely want to use these libraries within `capsules`. + +* Amit: Does not have to be in `capsules`. For instance, for a cryptographic + library there could be many different implementations of these primitives + through a common interface (including in hardware). Capsules are only + dependent on the definition of this interface. If a board chooses to expand + its TCB to include trusting some hardware component or an external library, + that's a reasonable and informed choice. + +* Hudson: This adds the upside that external dependencies using `build.rs` or + procedural macros will not force people to run this code on their host just + for using capsules. + +* Phil: Like the idea of pulling in external dependencies not in `capsules`, but + only through the board or chip implementation. + +* Amit: I think it might be fine for `capsules` to, in principle, depend on + external crates if we can ensure that these crates abide to the same + restrictions and rules that capsules otherwise have to. + + For the kernel that is more tricky, as it is part of the TCB. What code does + there matters more. diff --git a/doc/wg/core/notes/core-notes-2022-11-11.md b/doc/wg/core/notes/core-notes-2022-11-11.md new file mode 100644 index 0000000000..2af8e0463e --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-11-11.md @@ -0,0 +1,353 @@ +# Tock Core Notes 2022-11-11 + +Attendees: +- Alexandru Radovici +- Alyssa Haroldsen +- Amit Levy +- Chris Frantz +- Hudson Ayers +- Johnathan Van Why +- Leon Schuermann +- Vadim Sukhomlinov + +# Updates + * Hudson: One updated from Pat, who is not on the call, is he submitted a + [PR](https://github.com/tock/tock/pull/3318) for a license policy for Tock, + but that's the first item on the agenda so I'll wait on it. + * Alyssa: There's some unsoundness in `MapCell`. Discussed internally, I'll see + if we can share it with the Tock Slack. + * Johnathan: He sent [the PR](https://github.com/tock/tock/pull/3325) already, + it's public now. + * Hudson: That seems like a big problem, thanks. + +# FlashController HIL (#3319) + * Alexandru: The problem we have is in the flash HIL. + * [Alexandru, in chat: + https://github.com/tock/tock/issues/3319#issuecomment-1311203404] + * Alexandru: The flash HIL in tock defines the associated type `Page` which + needs to be `AsMut<[u8]> + Default`. In TickFs, Alistair assumed page sizes + are 2KiB. His flash controller for TicKV uses our flash controller from Tock. + The flash page doesn't have a fixed size, but his flash controller needs a + size, so it can compute addresses and erase pages. Basically, there is no + checking here. + * [Alexandru, in chat: + https://github.com/tock/tock/blob/0f7fe7d9355002e4e7065cc3fb940a0d121e8d21/capsules/src/tickv.rs#L74 + https://github.com/tock/tock/blob/0f7fe7d9355002e4e7065cc3fb940a0d121e8d21/kernel/src/hil/flash.rs + https://github.com/tock/tock/blob/0f7fe7d9355002e4e7065cc3fb940a0d121e8d21/kernel/src/hil/flash.rs#L114] + * Alexandru: The page size in TicKV needs to be the same size as the associated + type `Page` from our flash controller. Our suggestion was to implement it as + in the issue, but then we realized that `Page` could be anything that gives + us a slice, which does not have a size at compile time. + * Hudson: I'm not super familiar with TicKV, but it seems like the real fix is + for TicKV to also be generic in the same way as the flash controller with + regards to buffer sizing. + * Alexandru: It needs to know how to erase pages, so it needs to know the size + of the page. TicKV is generic on two sizes -- the write size, the minimal + item we can write, and the erase size, the minimal item we can erase. It has + a W and an E size. But the flash controller it Tock only allows reading one + page at a time, writing one at a time, or erasing one at a time. This might + be fine with controllers with a 2 KiB page, but the STM we use has a 16 KiB + page. The TickFs implementation Alistair did was coded to a 2 KiB page. We + can fix that, but I don't know how to connect the length to Tock's flash HIL, + because it does not have a constant on the page size. That may be a problem + because the flash HIL has erase page with a page number, but all the other + functions -- read and write -- can take an arbitrary-length buffer. + * Leon: That's an interesting observation given the associated type requires + every implementer to have an `AsMut<[u8]>` implementation. It allows every + consumer to potentially reference the flash page to a mutable slice. I'm + wondering whether that's a wrong level of abstraction we're using here + because when we want to have something dereferenceable to a mutable slice of + bytes, we force the implementation to provide an in-memory view anyways. I'm + wondering whether it would hurt any of our use cases to make the associated + type an associated constant over the size of the flash page instead. + * Alexandru: The idea would be that somebody would need to provide a buffer of + that size when writing or reading? + * Leon: It's a good question. I mean, potentially we could also add an + associated constant that says the flash page has to be a particular size but + use an unsized slice. It would cause some inconsistencies in the API. I'm not + sure I have a good solution. + * Alexandru: Reading and writing is fine, erasing is a problem because it needs + to happen at a page level. Reading and writing is a strong limitation. We + have 16 KiB pages but we never read or write 16 KiB. + * Chris: That's exactly correct. Typically, reads and writes have fewer + constraints. Erases have to be aligned and be a full sector size. It's not + uncommon for flash implementations to even allow you to re-write the same + word multiple times -- you can start with the word full of ones and clear a + bit at the time, but that's typically dangerous. For some flash + implementations, you can damage the flash if you re-write the same word too + many times without an intervening erase. I think maybe we should look at how + the flash control driver is modelling the flash and have the requirements + flow outward from there. + * Alexandru: I think TicKV is relying on the fact it can write multiple times. + I think it needs to be able to write at least twice to mark + garbage-collectable keys. + * Alexandru: Right now I have a chip that has some 16 KiB pages and other pages + of 128 KiB. We're currently limiting ourselves to the 16 KiB pages. With the + HIL we need to read and write 16 KiB at a time. TicKV doesn't require it, the + flash HIL today does. + * Alyssa: Ti50 has its own flash HIL for that reason. + * Alexandru: We could do that but we would like to have something consistent + with upstream Tock. + * Alyssa: I think that an associated constant for the flash size would be good, + but maybe we could have attributes that can be set on the flash that enable + or disable single-word writes or reads. Something that can describe the + properties of the flash and its restrictions. + * Alexandru: I'm not sure how. Something that could be read by an upper driver + layer? + * Alyssa: You could have some functions that are only enabled on flash chips + with certain properties. You could define a "can write single word" property + as an associated constant and only enable the write-single-word function if + the constant is true. + * [Hudson in chat: https://github.com/tock/tock/pull/2248 + https://github.com/tock/tock/pull/2993] + * Alexandru: PR 2248 looks interesting but it's very old and not merged. + * Hudson: The description of that PR sounds very similar to the issues that + you're raising, so I think it's worth looking through the discussion on that + PR and figuring out why it was not merged. + * Alexandru: I would be happy to continue the discussion there and be able to + merge it. We could write our own HIL but want to do something aligned with + upstream Tock. + * Alyssa: Tock basically needs to enable more configuration to be able to + handle these diverse systems. + * Hudson: I think that's what PR #2248 does. + * Hudson: My takeaway is that we've known for a long time that the existing + flash HIL is bad, people keep running into some of the same issues. #2248 + seems to be the minimal PR that gets close to addressing these. The reason it + hasn't made it past the finish line is because everytime somebody puts in + effort to rewrite the HIL, others come up with cases where the new HIL + doesn't fully meet their needs, a debate happens, and people lose interest in + pushing the PR forwards. If #2248 looks like it would largely address your + concerns, Alex, it would be great if you were willing to take a look at the + discussion on that PR and try to push it across the finish line. I get the + feeling it had gotten close when Alistair was still working on it. + * Alexandru: We'll do that, maybe continue the discussion next week. I can't + push directly to that branch. I could open a pull request to his branch, or + open a draft PR and Alistair can pull the changes. + * Hudson: That would be fine. Alistair may start looking at it again. + * Alexandru: Some boards don't have equally-sized pages. We have a board with a + few pages of 16K, one page of 64K, and a few more of 128K. This is the reason + tockloader doesn't work on STMs, as tockloader assumes a 4K page, which is + not the case. Using tockloader on STMs breaks immediately. + * Hudson: That's not related to this flash HIL. + * Alexandru: The only way of flashing apps on these boards is re-bundling + completely. + * Hudson: Your observation is that this assumption we've made that flash pages + are a fixed size is not only wrong here but also in tockloader. + * Alexandru: I don't know other boards that well to be able to generalize. NRFs + have equally-sized pages. + * Hudson: Right, as does SAM4L. SAM4L and NRF are the main chips that inspired + a lot of Tock HILs. It's not surprising we got that wrong. + * Alexandru: I don't know if this is the exception or the common case. + * Hudson: Do you feel like you have a path forward on this? + * Alexandru: Yes. Looking at #2248 and coming up with feedback and hopefully we + can push it forward into merging it. + +# License and Copyright Policy (#3318) + * Hudson: Pat is not here, but I still want to at least bring it up because it + seems like something we don't want to leave outstanding. Amit has a PR + implementing this policy. I want to get a feel where everyone stands on this, + especially Johnathan and Amit. + * Amit: I can get mine out of the way. I think I have agreed with most of what + Johnathan suggested. Otherwise I don't have a particularly strong opinion on + it. I would love to just see this get merged as soon as we feel comfortable. + I think Johnathan is thinking a bit more like a lawyer and I don't know. What + Pat wrote seems like it's in the spirit of being right to me, I don't have a + strong opinion about the specific text, except for agreeing with Johnathan's + points. + * Johnathan: I left some comments there that haven't been addressed yet; I + assume the status is we're waiting for Pat to address them. The biggest one + is my comment about the format section. When we get really picky about + license headers, that makes it difficult or impossible for us to use code + from other projects. If their license headers don't follow our format or are + in the middle of the file, then that doesn't meet our requirements. If I + codify that into a CI tool that will fail in any PR that pulls that in. In + one of the files, Amit pointed out we could move the license header and that + is true but that is not true in every case. For the most part, we cannot mess + with other peoples' headers. In that case, we could move the notice to the + top of the file because we can converted it from C to Rust and we put the + lines above the license header. It would be nice to be open enough that we + can grab files from other Apache 2.0 OR MIT projects and use them in our + codebase. + * Amit: Broadly, if I am reading the tone of your comments correctly, it is + that the first draft was basically overspecified... + * Johnathan: Yes + * Amit: and that we should say less if we don't absolutely need to be specific + about it. I assume Pat's perspective was that he was like "oh I'm going to + sit down and write this" and he just wrote something up and got carried away + with details. Again I'm projecting -- I'm assuming he doesn't disagree with + you. We should just prune it of details that are not necessary. We have + nothing right now, so anything we do will be more specific than what we have. + Can leave ourselves more freedom. + * Johnathan: The other thing I'll state is I want to see this policy converge + and get more agreement, then I can reach out to the OpenTitan legal + committee. I'll see if they approve of the header from the perspective of the + OpenTitan project contributing code to Tock and also using Tock. + * Amit: Do you think we should do that before we hit merge? + * Johnathan: Yes. But I do think it should be somewhat stabilized. + * Hudson: Johnathan, do you have a concrete recommendation for what we should + do with regards to specifying where a copyright should go or how it should be + formatted? It seems your recommendation is to not specify where it should go + or how it should be formatted, but we do need to specify that it exists. How + are you going to write a tool that works around such a loos specification? + * Johnathan: Yeah, I think the answer is we can specify things about projects + made by Tock contributors. I don't want us to be too exact about all license + headers in the code. We kind of need to divide -- if we pull code from + another project, like the sip hash, I think we should be more open about what + those license headers look like -- and we can have a tool that only checks + for the Tock project's license header. + * Alyssa: Why should we be more open instead of reformatting the copyright when + we do the port. + * Johnathan: Because we have a limited ability to reformat a copyright. In many + cases we just can't touch it and we have to leave it in place. + * Alyssa: An Apache/MIT one? Are there any cases where you can't put at the top + "hey this is licensed under Apache/MIT and here are the original copyright + declarations"? + * Johnathan: That's a question for a lawyer not me. + * Leon: There might still be a case where we import software that is e.g. + released to the public domain, or just Apache or just MIT, which should be + fine given our terms. I think it would make the most sense is for us to keep + the description in the TRD vague but have a tool that may be overly picky, + and we can maintain and extend it over time. + * Johnathan: I don't think it's necessarily correct that we can take code that + is only licensed for one of the licenses out of another project and put in + ours and distribute it under both licenses. + * Leon: I should've prefaced this with the disclaimer that it gets fuzzy and + requires a separate discussion. I'm saying it's not always as easy as taking + code from another project and reformatting the header because it's the same + license. + * Alyssa: For the limited cases that we do have, if we see that it says MIT + license and Apache 2.0 license, I don't think there's any harm if we don't + change what's there, we can always add a license declaration to the top as + long as it matches the license that was listed previously. It's only adding + the same information, it's not changing the semantics if it matches. + * Amit: I agree that we can do that, and we probably should when we can. Do we + need to specify that in the TRD? Suppose we're wrong, or it causes some + conflict. Do we really care about there being a file with a license in a + different place. + * Johnathan: Let me give a concrete example. Lines [100 to + 10](https://github.com/tock/tock/blob/e2f9b0bf902e80d895b750bd29080a7438f566b7/doc/reference/trd-legal.md?plain=1#L100) + state license information MUST come before copyright information. If you look + at the OpenTitan project, we put our copyright line above the license + information. And so that alone, if Tock were to try and take a file intact + from the OpenTitan project -- which it'd already have to remain under the + Apache 2.0 license -- but say we wanted to do that, the Tock project can't + then change the license header on the OpenTitan file. + * Alyssa: I think this TRD is overspecified. I think the only thing we should + absolutely require is the SPDX license identifier because that is designed to + be programmatic. + * Johnathan: I agree with SPDX. I think we'll have to see what the OpenTitan + legal committee if it's okay to omit the copyright statement. + * Alyssa: As long as have some knowledge of the source, it's always fine to + add. + * Chris: Would it be acceptable to allow files in that have the SPDX header and + maintain a list of exceptions? So, if you want to import something that + doesn't have that and for whatever reason you can't change the sources to + include it we can have a file that says "these files are an exception to the + rule". + * Johnathan: I was anticipating that when I build the CI tool I'd have to + develop that. + * Leon: Definitely seems feasible, especially for putting in external + dependencies. We may want to add a check to make sure that the source doesn't + change without noticing that and change the license terms, so something like + including the hash may be a good idea. + * Alyssa: That sounds like it is pretty fragile, would change very option, I + guess by intention. + * Chris: There's a tool within Google that does something like this. It tracks + the licenses of third-party opensource libraries. It's more policy-based. + It's not "did it change", but "whatever the license is, is it acceptable". If + you build something that uses these opensource third-party libraries, you can + run a check over your dependencies and make sure that everything you're + pulling in fits some constraint. I don't think we need to build something + quite that involved but if we have a list of acceptable licenses and make + sure everything we have falls in that last, I would suggest it doesn't matter + if it changes. If we have something under one acceptable license and it + changes to a different acceptable licenes, that's fine, but if it changes to + an unrecognized license then there's an issue. + * Alyssa: I think that mechanism should be SPDX. + * Chris: I agree + * Johnathan: There's a distinction between tools that check that your + dependencies have compatible license -- I think `cargo audit` might do this. + There are a different set of tools that make sure your project's license + headers match your project's standards. When Google releases an open source + project and make it public for the first time, we make sure our own license + headers are correct using a tool. I think Chris was talking about one type of + tool and I'm talking about the other type. I'm talking about one that just + checks the format of our license headers rather than checking the licenses of + our dependencies. + * Leon: Presumably, given our current policy on external dependencies, we won't + have a flurry of external dependencies to track. It seems fine for that to be + a manual process. + * Alyssa: Why couldn't we edit or require all third-party dependencies to + include an SPDX? It's a really reasonable request. + * Leon: Because fundamentally we might want to pull the dependencies into a git + subtree or submodule, and it creates an insane overhead to maintain a + sightly-deviating version of the dependency. + * Alyssa: I'm thinking of the current third-party dependency policy. + * Johnathan: Well, consider `libtock-rs` which is not under the Tock kernel's + third-party dependency policy and pulls in a lot more external crates. + * Alyssa: In that case, either SPDX or cargo license metadata. + * Amit: Can I step back, I got a little bit lost. Are we discussing things that + should be included in the TRD as, like, MUST; things that should be included + in the TRD as SHOULD; or things that shouldn't necessarily be included in the + TRD but should do as a matter of practice. + * Johnathan: I think it is a separate topic. + * Amit: Okay, so for the TRD how about we either catch Pat on Slack and he has + some cycles or I will try and take a pass later today to basically tone down + the requirements. + * Amit: How about we try to get thumbs-up approvals from people, then we can + get feedback from external folks, then we can go from there. + * Johnathan: Sounds good to me. + * Hudson: That sounds good. + * Alexandru: Considering there's an agreement that copyright information could + be in the file, is there any way we can merge the CAN PR in the meantime? + * Hudson: My feelings is it probably doesn't make sense to merge anything until + we actually finalize this. + * Amit: In that case, I think the ball is in my court right now, but can we + kind of step on trying to merge this. Johnathan, how long do you think you'll + need to get feedback from people once we approve? + * Johnathan: I don't know for sure. I think under a week, but I can't make a + promise. + * Alexandru: That's fine, then I have no problem. + * Amit: I'll try to get it ready for approval soon, and then lets try and get + feedback and hand it over to Johnathan relatively quickly. + * Hudson: I think this is something we should try to get through as quickly as + possible. + +# RasPi Pico USB + * Alexandru: We have a working USB stack for the Raspberry Pi Pico. I think the + PR is mostly done. Can someone with more experience look at it and maybe we + can merge it? It's critical for the Pico because right now the rest of my + Pico cannot be used without an exterier seriel converter. That would let us + use it in the classroom. + * Amit: I'm interested. What's the PR? + * Alexandru: https://github.com/tock/tock/pull/3310 + * Alexandru: We just duplicated whatever was on the NRF. It works in and out -- + you can read and write data, and the process console seems to work. + * Amit: This seems limited to boards and chips, seems like it should be easy to + merge fairly liberally in my opinion. + * Alexandru: It should be limited to Pico and the RP40 chip. As soon as this + gets merged we will add it to some other boards with the same chip. + * Amit: Great + * Hudson: One thing I noticed when looking at that PR is you have this empty + implementation for reset bootloader enter function + * Alexandru: I have no idea how to do that yet, we're still looking into it. + * Amit: Is that similar on the NRF? + * Alexandru: No, the NRF reset. I was able to load apps on the clue. On the + RasPi we have an idea but it's still under exploration. We'll probably follow + up w/ another PR. + * Hudson: The problem is not that you can't reset the chip, it's that you need + a way to call the function? + * Alexandru: No, the problem is we need to keep something in a register the + bootloader reads. I have a pretty good idea how to reset the Pi but I don't + have an idea how to keep data in a register while it restarts. + * Amit: Yeah, that's exactly right. The whole implementation is a hack for the + bootloader. + * Alexandru: It has to do something. We can delete that function, it is still a + TODO. + * Hudson: I think it is fine, just something I noticed. Overall this looks + pretty inoffensive and is confined to the board and chips so. + * Alexandru: The parts I am particularly interested in are in the USB stack. + The NRF have some comments with TODOs. We do this on DMA, but to do this is + not something we can do, and these are the parts where another set of eyes + that has done a USB stack before would be super useful. + * Hudson: I think I'm going to go ahead and stamp this. diff --git a/doc/wg/core/notes/core-notes-2022-11-18.md b/doc/wg/core/notes/core-notes-2022-11-18.md new file mode 100644 index 0000000000..e598947fd7 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-11-18.md @@ -0,0 +1,223 @@ +# Tock Core Notes 2022-11-11 + +Attendees: +- Amit Levy +- Chris Frantz +- Hudson Ayers +- Brad Campbell +- Johnathan Van Why +- Philip Levis +- Vadim Sukhomlinov +- Leon Schuermann + +# Updates + * Phil: submitted a PR that addresses some concerns that Alyssa had about how + userspace and kernel writes to the console can be interleaved in an order + that is different than the order those debug statements were made. + * Chris: I sent in a PR that details how Google's Titan chip is used in + Google's datacenters, and describes how some of the features and planned + features of Tock might be used in this context. I have a question about how + this sort of document, which is not formal Tock documentation but more + intended to illicit discussion, should be submitted + * Brad/Amit: The place you put it (in the OT working group folder) seems + appropriate for now, we could always move it eventually + * Amit: The document is really great + +# Significant Pull Request Rundown + * Brad: I don't have a full list in front of me, we just had Phil and Chris + mention two newly opened ones, and there have been some new PRs on the OT + side of things, but not sure there is anything to discuss there. + +# [PR #3318](https://github.com/tock/tock/pull/3318) Continued Discussion + * Johnathan: Looking at the PR, there are currently three approvals out of the + working group and a lot of unaddressed comments. I am not sure if it is too + soon to reach out to the lowRISC legal committee, but I think I may do that today + * Hudson (chat): What I had in mind when I added this to the agenda was going + through all of the unresolved comments and coming to a consensus among the + people on this call how we should resolve them. + * Amit: I think many of the unaddressed comments are more a reflection of Pat + being busy, and anyone else can go through and apply some of them. I can go + through the comments though. + * Johnathan: (Read one of his PR comments)... we can make the tool explicit + about license headers + added by Tock contributors, and ignore any copyrights / license notices + already existing in files that we pull in. Pat was trying to be explicit to + make the tool more simple, but I think it is more important that we make it + easy to pull in code with existing licenses/copyright notices. + * Amit: I think it is important to note that it is OK for the tool to be less + restrictive than what the TRD specifies, the tool is just helping us to + enforce our best practices + * Johnathan: I think to the extent possible the TRD should document what the + tool will enforce. But the TRD can have some ambiguity to make the tool + work, such as in the case of being vague about exactly how near the top of a + file a license header needs to be. + * Phil: The TRD specifies the bounds of what you can do according to the + project, but individuals can take a more restrictive approach. We just have + to accept contributions that may not be as restrictive as, say, what we + normally do, so long as those contributions meet the requirements of the + TRD. + * Amit: This can be tricky with a vague specification -- what if someone + submits a contribution with a license header further down than we normally + do, we ask them to change, and they are resistant. We can't clearly prove + that the contribution violates the best common practice. + * Leon: I think it is unrealistic for the first version of the tool to handle + all the cases we will need to handle long term anyway + * Phil: My one significant comment on the documet: I read this, and does this + tell me what a file should look like, or what we have to do (e.g. we should + not remove a copyright). I am not sure about the latter one, because it + means the TRD is making a statement not about the documents themselves, but + about actions people take. + * Amit: Can you clarify that distinction? + * Phil: I think there is a difference between saying "Copyrights should be at + the top" and "you should not remove a copyright". The first is about what a + compliant document looks like (the artifact), the second is about the + actions that can be taken by an individual. + * Amit: What is the takeaway here? + * Phil: I am unsure whether this document should do the latter. + * Leon: I consider the latter as arguably more important. + * Johnathan: My concern with the latter is getting too close to giving legal + advice + * Phil: I think that is a good articulation of my concern. It is fair to say + that we do not expect developers not to remove copyrights, but that is + different from saying `SHOULD NOT`. + * Amit: I am personally swayed by that argument + * Leon: I think the current document is written using explicit RFC style + language to perform or not perform any actions. I am indifferent as to + whether we should be doing that. + * Phil (chat): SHOULD NOT: This phrase, or the phrase "NOT RECOMMENDED" mean + that there may exist valid reasons in particular circumstances when the + particular behavior is acceptable or even useful, but the full implications + should be understood and the case carefully weighed before implementing any + behavior described with this label. + * Leon: It is best for this document to not establish some new legal framework + and instead just state best practices + * Amit: I agree, this should be a statement of our expectations, nothing more. + * Amit: OK, lets quickly go through the outstanding comments on the PR like + Hudson suggested + * Phil: Yeah I think hammering through these seems productive. + * Amit: Should we update from "exactly one" blank line after license/copyright + to "at least one"? + * consensus: yes + * Amit: Johnathan separated notices into two distinct groups, Tock project + added notices and 3rd party notices. Do we agree that we should limit our + formatting requirements and automated enforcement to the former group? I + believe we do? + * Leon: How are we going to enforce this in practice? Via an allowlist? + * Johnathan: The tool will look for a Tock project notice at the top of the + file in a particular format. If it does not find it it complains, if it does + find it, it stops looking. + * Johnathan: The document should be clear that these requirements only apply + to Tock project notices. + * Amit: I will make a note of that to add as a comment at the end + * Phil: So the resolution here is that the formatting expectations are about + the Tock project license headers/copyright notices. + * Leon: That is nice because it means this document does not cover copyright + enforcement + * Johnathan: My next comments says that if another project's license header + is similar we should be willing to accept theirs rather than nearly + duplicating it at the top of the file + * Amit + Phil: I basically agree with that + * Johnathan: I think it is fair for the document to acknowledge that in cases + when files are pulled in from other projects, the format of those notices + might deviate slightly. + * Amit: So we can modify license headers to make them compatible, but we do + not need to if it is close enough? + * Johnathan: I don't think we can modify them. + * Leon: How will we specify which files are "Tock project files" to avoid + adding our license headers to other project files + * Leon: For example, if we pulled in a git submodule. Would we have a mechanism + to make our tool aware that this might not follow our guidelines? + * Johnathan: Google's practice is to have a directory called `third\_party` + that covers all code that might be under different licenses. we could copy + that practice, and the tool could ignore it. We might be able to get away + with skipping submodules. + * Leon: If we have any such mechanism, does this comment still apply? + * Johnathan: Yes, because we might copy in other files directly into the main + source e.g. driver files or vendoring code into libraries, and in those + examples we want to preserve original headers but also have a copyright that + reflects the possibility for continued additions by Tock contributors. + * Amit: OK, our conclusion is that if they are different enough we would want + to modify we should create a new license, but the tool can be liberal about + accepting license that are close to our expectations to avoid needless + additional headers. + * Amit: What about files that do not support comments, e.g. markdown? The + current distinction of code vs. not code might be ambiguous. + * Amit: I proposed any file type that allows comments should have a license + header + * Hudson: How precise a category is that? Does a text file "allow" comments? + * Johnathan: We obviously should not require people to add metadata in PNGs -- + are those comments? + * Phil: The statement says code artifacts + * Johnathan: That is vague + * Johnathan: I think license headers in all textual files that allow comments + should suffice + * Amit: Sounds good *adds that comment to the PR on his shared screen* + * Amit: I don't think that the license that applies to input that affects a + binary should carry the same license as documentation. Does it even make + sense to licenses for a README? + * Johnathan: We didn't specify that our documentation is under a different license, + so it is under the same license. Using a different license for our documentation + would be weird because we copy code between our documentation and implementation. + * Amit: *updates comments on PR to reflect earlier discussion of not using RFC + language to specify actions of individuals* + * Leon: My next comment is that our current suggested approach feels weird + because it is different than what many other projects do with regards to + putting license headers in parent-module doc comments. + * Amit: I agree with Johnathan we should pick one or the other + * Leon: Sure, if so I think we should be careful the ambiguity of the text + does not allow either approach. + * Amit: Yeah, with a 1-3 line license header it seems that should go first. + This isn't a problem for the compiler right? + * Leon: Yeah, Rust allows comments above module documentation (but nothing + else) + * Phil: Sounds like Rust thought this through! + * Amit: I am gonna say that this is redundant since we are going to be more + liberal with code imported from third-parties. + * Johnathan: It sounds like we still want to follow a normal practice of + putting the header above module documentation: + * Amit: I will add that to the comment + * Amit: What do we do about tools? Two questions -- first, other repos that + are not the kernel, second, what about tools in the kernel repo (under + `tools/`). What if someone contributes a tool with a different license + header. + * Amit: A tricky bit is something that is AGPL, which is fine to use as a + tool, if it is not linked against. + * Johnathan: AGPL stands out as one license that Google's lawyers really do + not like, so they are very conservative about it. + * Amit: An example is like a stack-analysis tool. We could pull it in using a + shell script and run it, is that different than including it in the + repository? + * Phil: I think this is a lot easier if stuff within a particular repository + has a very clear license. + * Johnathan: I am really annoyed at picolibc because they removed all the + GPL/LGPL code, but added some GPL/AGPL binaries, which still makes it hard + to follow company policies with automated license headers. We (Google) might + fork it and delete those 3 files. + * Amit: Conclusion: It is easier if everything in a given repository is under + the same/compatible license. I think this document, for now, should just + cover Tock/tock, and we should update the language to reflect that. Is that + OK with other people? + * Johnathan: I will want to apply this tool to libtock-rs as well. Where + should it live? + * Amit: Seems fine to gradually expand the scope of this doc and tool. + * Leon: We want to make it specific that the license must apply to these + files, but not the formatting guidelines. + * Amit: Agreed. + * Hudson: I vote that the tool live in tock/tock/tools + * Johnathan: That means the CI for other repos will have to reference the + kernel repo, but that is not really a big deal. + * Brad: Everyone take a look at the PR for the agenda item we did not get to. + We will not meet next week because of Thanksgiving. + * Johnathan: If anyone knows of any other alternatives to newlib besides + picolibc, please let me know. + * Vadim: Do you have a list of functions that you need from newlib? I am + working on a bare metal C library for Ti50 to remove newlib dependency. + * Johnathan: No I do not. + * Phil: But that is definitely something of interest! I will try to find a + list of what we need from newlib. + * Amit: Regarding other options, MUSL/bionic do not work, they are too linux specific + * Phil: We need printf. + * Vadim: But how does it print? + * Phil/Amit: we implement write/read/putstr, printf calls those + implementations that we define. + * Phil: I will look at the symbol tables to try to get a complete list of what we need from newlib diff --git a/doc/wg/core/notes/core-notes-2022-12-02.md b/doc/wg/core/notes/core-notes-2022-12-02.md new file mode 100644 index 0000000000..d95fe5fbb4 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-12-02.md @@ -0,0 +1,303 @@ +# Tock Core Notes 2022-12-02 + +Attendees: +- Hudson Ayers +- Brad Campbell +- Chris Frantz +- Philip Levis +- Amit Levy +- Pat Pannuto +- Alexandru Radovici +- Jett Rink +- Leon Schuermann +- Vadim Sukhomlinov +- Johnathan Van Why + +## Updates + +* Phil: In touch with another group of Google. They have some upcoming + PRs w.r.t. MapCell unsafety and some new use-cases which might + motivate changes to grants and Tock processes. We are spinning up a + detailed technical discussion about it. Hopefully we have something + up by end of month. + +* Johnathan: Started on the license header checking tool. + +* Leon: Pushed along [the PR](https://github.com/tock/tock/pull/3110) + to make VirtIO devices work on the QEMU board. It's an old PR, so + it's no longer on the front page. But if people are able to take a + look, I'd appreciate that. + + This is one of the key components towards establishing Ethernet + support on Tock. This establishes support for a virtual RNG which we + can use to test this part of Tock's infrastructure in CI. It also + adds a virtual network card, similar to how Linux in QEMU + communicates over the network. One of the few devices we'd use to + define an Ethernet HIL ultimately. + +* Hudson: Opened [a PR](https://github.com/tock/tock/pull/3336) that + fixes that `tock-registers`' `matches_any` does not work for + multi-bit fields. The existing interface does not actually make it + possible to implement it that way, as it works by adding up many + individual field values, thus losing information of which fields + were combined to create this value. Created a new function with a + slightly different interface, and renamed the old function to + `any_matching_bits_set` (as it's more efficient for the case where + we match onto a series of bits). + +* Brad: Working on an `elf2tab` update basically rewriting its + logic. The last change tries to get rid of all the custom logic we + were doing, and instead parse an ELF properly. + +* Phil: Poke + [on Alistair's PR](https://github.com/tock/tock/pull/3312) on + external dependencies. + + *added to call agenda* + +* Hudson: Wanted to mention the copyright PR. Came to some good + conclusions, just need to incorporate the feedback. + +## Policy on External Dependencies (PR #3312) + +* Hudson: Let's talk about the external dependencies [PR + #3312](https://github.com/tock/tock/pull/3312). + +* Brad: Talked about this on the OpenTitan call. Want to have external + dependencies in some controlled way. Where the PR currently stands + is to start with a narrow use-case. Essentially limit to + cryptography dependencies at first, and explain the process for + review, as well as precisely specify which kind of cryptography + libraries we'd like to support at first. + + The goal is not to over-specify things, but rather clearly indicate + to readers our intentions. + + Another thing to specify is how we'd like external dependencies to + be included in Tock. My proposal is: we have a single global crate + in the Tock repository. That crate is the only crate which is + allowed to have external dependencies (with the exception of board + crates). Each crate which would like to use external dependencies + would depend on this crate. The reason for this is that it provides + us an explicit namespace indicating where things are coming. + + This is motivated by changes in the new Rust edition, which no + longer requires the `extern crate` specifiers, so it may not + necessarily be clear where a module, e.g. `ghash`, originates + from. With an explicit crate, we could have our external imports be + called e.g., `tock_extern::ghash`. + +* Hudson: Sounds good. One outstanding idea: may capsules depend on + directly on external dependencies? + + Alistair's PR adds `ghash` not for app signing, but for the AES + capsule. Because its a dependency of the capsules crate, every board + has to fetch and compile all of the `ghash` crate and all of its + transitive dependencies. + + Curious whether users of Tock may have a problem with that in terms + of having to audit that code, because it gets compiled along with + the capsules crate, but not necessarily used? Specifically + concerning procedural macros or `build.rs` scripts. + +* Amit: The task of auditing is made more complex. If there aren't any + external dependencies, then it is pretty clear that none need to be + audited. If some dependencies are pulled in but not used, that makes + it significantly harder to identify whether a dependency requires + auditing: for example, a capsule could not use that dependency, but + rely on another capsule which uses it, or re-export it under a less + obvious name. + +* Leon: Is this relating back to the discussion of whether capsules + should be a single crate? I believe in previous iterations on this + we didn't come to a conclusion given we didn't have any obvious + benefits to it, but now there clearly would be some? + +* Hudson: Had been advocating for a new external dependencies capsules + folder, and any capsules that wanted to use external dependencies + would live in that folder, which would be a separate crate. + + Leon, you're right, having different capsules be separate crates + would also solve this issue. And anybody wanting to use only a given + capsule would only need to include it and its explicit dependencies + in their boards. + +* Hudson: Can you think of any reason that would make it challenging + to have every capsule be in a separate crate? + +* Leon: Potentially recursive dependencies. Also, it's hard to define + what a single capsule is. I think it'd be fair to do a bit of + grouping. + +* Amit: Yes, the network stuff should probably be one crate, or the + core capsules including console and time. We could then also apply + different standards for different crates (e.g., allow external + dependencies only for some, never for the core crates). + +* Leon: Related, is the current state of the document still specifying + that the core kernel can only utilize external dependencies through + traits which we define, or can it now directly use external + dependencies. + +* Hudson: Document has moved away from that, can now directly + depend. There are some software-engineering challenges related to + that. + + For example, tried to do this for just the `ghash` crate. You can + define a trait which is exactly the interface of this crate. Also + kind of tricky, as this crate uses its custom types, which you then + need to pass back in to other functions. Calling macros is also not + really possible. + +* Amit: This doesn't really seem like the right approach. We don't + want to expose `ghash` for `ghash`'s sake, but to use it for some + purpose, e.g. validating app header signatures. It seems like that + should be the functionality we should abstract over. + + Maybe there's some boards which can do that in hardware, other don't + want to do it, and some want to use a software implementation, which + internally uses `ghash`. + + We'd be asking Alistair to rewrite a lot of his code then. In + general, there is an intended way to use these dependencies, and + we'd be creating this whole additional layer to be able to use + them. There's a non-zero cost here. + +* Amit: Also a non-zero cost with including dependencies in the kernel + crate. + +* Brad: Voicing support for not having traits, which is essentially + doing HILs for external dependencies. It's not the right place to + put an abstraction. + +* Amit: Not HILs for external dependencies, but for functionality that + may or may not be exposed by hardware on different platforms. Re + AES: we probably only ever want to use an AES implementation through + a common abstraction. + +* Brad: That's correct, but that's specific to dependencies which + implement algorithms such as AES, can't generalize this to all + motivations to use external dependencies. + +* Amit: Question is -- do we have a use-case for which this model does + not fit the model of Alistair's PR? + +* Brad: Perhaps app signing? + +* Leon: Alistair's PR seems very close to how we'd like to integrate + external dependencies, by having it back an interface we already + defined. Totally different question when app signing is blocked on + calling out to specific external dependencies or just having some + external crypto library supported, on boards which don't implement + this in hardware. + +* Hudson: Including `ghash` and putting it behind our current `digest` + HIL seems tricky. Would likely require us to change that HIL + substantially. + +* Leon: Right, but this still seems pretty close to ideal. We're + providing some interface to other parts of the kernel and userspace + which is backed in part by our custom code and in part by + `ghash`. From the user's perspective it doesn't matter what + implementation we use underneath, as long as our provided interface + remains compatible. In Alistair's PR, we can very well replace the + usage of `ghash` by implementing AES-GCM by simply using another + library or writing the code ourselves, there's no hard in-kernel + dependence on `ghash`. + +* Hudson: Agreed, if it's only used for AES-GCM. But say I had a board + with a hardware hash implementation, there is no way to build an AES + GCM on top of that hardware hash implementation. + +* Leon: There is, we'd just need to rewrite large parts of the AES-GCM + implementation, specifically the parts which use `ghash` right + now. The fact is that we aren't going to be in the situation where + we can't remove `ghash` without rewriting parts of the kernel crate, + as it doesn't directly depend on it. It's not easy or elegant, but + doable. + +* Brad: But the document in the current form would allow the kernel to + directly depend on a namespaced external crate. + +* Leon: Yes, makes me uneasy. The PR I'm seeing right doesn't motivate + this, and so I don't think we have sufficient proof that this is + indeed necessary. + +* Brad: Which crate then is allowed to use external dependencies? + +* Amit: If we have one capsules crate, then only boards and chips. If + we have many capsules crates, then I'd be fine with relaxing this + for some crates, e.g., crypto-related capsules as they require + implementations of algorithms that are not prevalent in + hardware. Never depend on external crates for core capsules, which + are things that virtually every board has to depend on, and + virtualizer capsules. + +* Hudson: I'm in favor of what Amit described. This seems like the + best route forward here and resolves the majority of my concerns + still on the document. + +* Amit: This is morally equivalent to external dependencies in + specific board crates, except that these components may be useful to + multiple boards and are hence located in a shared crate. + +* Leon: Really like the division of core capsules (without which no + Tock board can really be useful) along with virtualizers, and then + other crates which we can partition in arbitrary ways and which may + well pull in external dependencies. + +* Hudson: Seems as good a reason as any to split up capsules. + +* Brad: Proposal seems to be that we add a list of crates which are + allowed to have external dependencies? + +* Amit: Rather a list of crates which aren't allowed. There's still a + case-by-case evaluation per external dependencies, where some would + probably have more push-back than others. If there's some external + crate useful for debugging added to a debugging capsules crate, only + used on boards which need it while debugging, that seems totally + fine. + +* Brad: Are these crates then directly including external + dependencies? + +* Amit: When we're breaking up capsules into multiple crates, it's + better to have them be depended on directly. There's less reasons + for this additional indirection, and there's a difference between + pulling in _an_ external dependency compared to many. + +* Brad: Makes sense. By having the core kernel crate not be allowed to + add any external dependencies, one of the major needs for + namespacing is no longer there. + +* *all* list of crates disallowed to have any external dependencies + (at least initially): + - `kernel` + - core capsules (including virtualizers) + - libraries + - arch + +* Hudson: refactoring of capsules crate should go in before this + document is added. Possibly could take a stab at this. + + Reasonable to have a standalone crate for every capsule except for + core, and when there's direct dependencies between capsules? + +* Brad: That seems not great. That's a lot of extra crates to add to + each board and a bunch of extra files. + +* Amit: This would be one additional directory hierarchy. My intuition + is to have ~10 categories of capsules, such as `core`, `net`, + `crypto`, `com`, etc. + + We can also start with pulling out the one capsule that wants an + external dependency. + +* Leon: Minimum structure we'd want to have is probably the `core` + capsules crate, and then a large `contrib` capsules for everything + else which does not introduce external dependencies, and then start + breaking up gradually per dependency added. + +* Hudson: Yes, would probably try to implement this structure + first. This seems to let us move forward on the external + dependencies issue the quickest. diff --git a/doc/wg/core/notes/core-notes-2022-12-09.md b/doc/wg/core/notes/core-notes-2022-12-09.md new file mode 100644 index 0000000000..f8afaec923 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-12-09.md @@ -0,0 +1,132 @@ +# Tock Core Notes 2022-12-09 + +Attendees: + - Branden Ghena + - Pat Pannuto + - Alexandru Radovici + - Leon Schuermann + - Brad Campbell + - Phil Levis + - Hudson Ayers + - Johnathan Van Why + - Chris Frantz + - Alyssa Haroldsen + - Vadim Sukhomlinov + + +# Updates + * None + + +# 64-bit timers for userspace + * https://github.com/tock/tock/pull/3343 + * Phil: Alistair noted that RISC-V has 64-bit timers and it would be good for userspace to be able to access them. So he made the PR. + * Phil: I think there are some issues here, where different architectures get different-sized timers. But the better discussion is whether 64-bit time should be part of the system call API and a basic primitive. + * Hudson: What's the alternative? Separate system calls? No support? + * Phil: If we want 64-bit time to be accessible to userspace, I think the right way to do it is to add a new device. You could just add a command to the existing capsule, which would require processing properly. + * Phil: We could instead just have 64-bit time to be the status quo. + * Alyssa: Sounds expensive for some cases. + * Phil: That's right. The cost is that for a 32-bit architecture, we'd need to properly handle overflow and a multi-word time value. You can do it and the code isn't big, but it's extremely sensitive and tricky to get right. The low 32-bits are free-running, while the upper 32-bits are updated by interrupts. You could end up having the two out-of-sync because you need two separate reads. Fussy. But it can be done. + * Alyssa: I'm worried about the static code size impact. + * Phil: I think that if done right the code size wouldn't be large at all. 100-200 bytes at most. It's very little code. + * Phil: The current TRD105 https://github.com/tock/tock/blob/master/doc/reference/trd105-time.md#8-required-modules does say that you MUST provide 64-bit time in the kernel. It's not finalized, so we can still change our minds on it. + * Hudson: I thought Ti50 tried to export 64-bit time to userspace somehow. I remember some issues around the update. If you already have some hack to expose it, that might be helpful. + * Alyssa: I'd have to go look. Not sure. + * Phil: For systems with a native 64-bit timer, there would be no code-size implications. Only for 32-bit wide timer limitations. + * Alyssa: If I remember correctly, we have a 64-bit timer. + * Phil: I think RISC-V mtimer is 64-bit. + * Chris: For my part, I would like to see 64-bit time supported at _least_ as an option people could turn on if they want. For ms/us time, 32-bit timers can really hit a limit. And synthesizing something yourself isn't great. So the kernel doing "the right thing" for you would be really helpful. + * Alyssa: Sounds reasonable to me, as long as the code size impact isn't huge (~1 kB). + * Chris: I also like being able to turn it on only if you want it. So if you know you don't, then just don't include it. But 32-bit is too small. About one hour for microsecond tick. About 49 days for millisecond. Both are reachable. + * Phil: The way the alarm/timer system currently works is that rollover is fine as long as you use unsigned operations/values. But because the increments are 32-bits, then you can't set something further in the future than half of that window. So half an hour and 25 days. + * Chris: So having a facility for the 64-bit representation would be good. + * Branden: That sounds to me like a separate capsule + * Johnathan: I would argue that we should only have 64-bit time and not waste code size exposing 32-bit time, since you're just asking for wraparound bugs. That could still mean a separate capsule, and not offering the 32-bit capsule. + * Hudson: My understanding is that the implementation would track overflow at the lowest level to track overflow. + * Phil: Not necessarily. You could put a thin layer on top. That wouldn't be a big deal. + * Hudson: I ask because maybe a lot of the complexity in the time capsules could go away if they only deal with 64-bit timers. + * Phil: Unfortunately no. Most of the complexity isn't about overflow, but rather about when a request comes in what happens if the request was generated in the past and the time for it to fire has already happened. The ability to realize that time is passing while processing is happening, that's the vast majority of the complexity and edgecases. + * Hudson: I thought the only reason we needed to pass in the current time was for overflow. + * Phil: Well, all timers overflow + * Johnathan: The whole point of a 64-bit timer is that it doesn't overflow. You have to start it at zero and you have to cap it at some reasonable frequency (like 10 Ghz) but those are reasonable for Tock to do. 10 GHz still gets you 50 years. + * Alyssa: Ti50 isn't concerned about 64-bit timers overflowing. + * Phil: Linux wasn't concerned either. + * Alyssa: We do actively consider 32-bit timers. Part of what we do is have two timers: a higher precision likely to roll over and a lower precision that won't. It depends what we are trying to do. + * Hudson: The reason I asked all of this is because I wanted to avoid two capsules. I thought it would be easier to implement all of this at the chip level, so we wouldn't need a 32-bit capsule. It also seems that if you have one timer that is 32-bit and one that is 64-bit, then you end up with apps targeting one or the other. Since lots of userspace libraries build on it (like UDP) you end up with non-portable apps based on which one is targeted. + * Alyssa: I definitely don't want to split the world. You could theoretically have two with interchangable interfaces. + * Hudson: Not if one has 32-bit values and one has 64-bit values. If only some boards would have the one interface, then we'd need to use that for all userspace libraries that want to be portable. + * Phil: Yeah. It depends on what we think of as a "standard kernel" + * Hudson: If the cost is only 100 bytes, then it's not worth the maintenance burden of having two capsules. + * Alyssa: Agreed. I was thinking having a 32-bit capsule and a super-set of functionality for 64-bit capsules. + * Phil: Right. When I said it was 100 bytes, it would the cost for a 32-bit platform to just let userspace get the time in 64-bits. It doesn't allow setting alarms in the context of 64-bits. + * Phil: I think the outcome is that I should poke around and see what the code size implications should be. It sounds like there is a lot of interest in 64-bit access for userspace. + + +# Use case for new allow syscall + * Phil: Idea from chats at Google we wanted to make people aware of. + * Phil: Use case is some security-oriented microcontroller that can access an external RAM bank. It'll have to do operations on that DRAM, like compute a hash over a block of RAM that's not inside the process image. I've got a big block of say, 16 MB and the process wants to ask the kernel to compute a SHA over it. The memory isn't part of the process address space and outlives it. And we definitely don't want to copy it into a user space buffer. So the question is whether there is a new allow which could provide static buffers to the kernel, so it could pass them to DMA without a copy. + * Branden: Why not treat the external memory as a device and pass block numbers to the driver? Then you wouldn't have to touch allow + * Phil: At some point we need an address to pass into DMA. There could be a translation somewhere in the kernel. Some trusted code to change block number into a static address. + * Branden: Yes. The problem definitely still exists either way, but it seemed shunting it into the kernel lets you avoid syscall changes, which is a plus. + * Phil: We were thinking it would be neat if this didn't require a totally different mechanism. So the same SHA calls could work on either type of memory. + * Branden: That makes sense and is desirable. + * Hudson: What's tricky is that processes shouldn't pass memory from their reserved region as "static". Because they could be restarted. Would this memory be owned by the process that's passing it? + * Phil: I do think there's a separate question of where these addresses come from and how the kernel can be sure the process isn't making something up. Let's pretend there is some way of doing that. Then how does this propagate as something like an allow on the system. Assume the checks already occurred and the memory is okay. How do we do it? + * Phil: I wasn't looking for an answer yet. I just wanted to get people thinking about it. + * Hudson: It does seem like it would be challenging for this to use the same SHA system call. The code right now assumes there's a shared buffer. I guess this could dovetail with the having a enum for buffer type and work on any. + + +# TBF Parsing Crate + * https://github.com/tock/elf2tab/pull/62 + * Alex: One of my students sent in this PR. We want to port tockloader to Rust for two reasons. 1) installing with pip and sudo or not is a pain. Very frustrating for students. 2) We could also reuse code better if everything is in one language. + * Alex: A question is where we put this. Some mono-repo? Or something just for tools? + * Phil: I love this idea. When working on AppID, there were three separate places where TBFs were parsed. I understand why everything evovled this way, but it was a challenging aspect. + * Alex: I would be happy to begin working on it. We have some students and bandwidth. + * Hudson: Brad did start one once: https://github.com/tock/tockloader-proto-rs But it's different from what you were thinking. Brad would know more + * Brad: So, this is the implementation of the bootloader communication protocol for the board. So tock bootloader uses this. It's another case where we have the board side in Rust and the controller/host side is in python for Tockloader. Another duplication problem. + * Hudson: And it would be a lot easier to test these against themselves if it was all Rust. + * Brad: Maybe. The background here is that there was a tool called stormloader that was written in python. And Tockloader grew from that. Tockloader is very complicated. It's 2-3 orders more complicated than elf2tab in my mind. So I'm torn here. I don't think installing python is that hard, and it lets us iterate pretty quickly. If there was a port in Rust, that would be good to build off of. But I'm not excited about two versions where each have partial functionality. Where like the Rust version supports _some_ things and the python supports _other_ things. + * Chris: What is the scope of the python tockloader? Does it search out FTDI chips and stuff like that to do firmware loading? + * Brad: Three interfaces: OpenOCD, Jlink Tools, and Serial ports. It tries to intelligently identify serial ports by name. It can autodetect boards of the two JTAG interfaces to figure out what is connected. + * Chris: In the OpenTitan repo we have opentitantool which allows us to interact with it. It's in Rust with structopt/clap as the CLI. We have a limited set of hardware things to interact with. I found writing the code in Rust to be nice and convenient, but I think there's a big advantage of Python in how easy it is to interact with other parts of the system. Where Rust would have to reinvent some of that. For example, opentitantool can interact with 3-4 hardware interfaces now and has abstractions for them: SPI, GPIO, UARTs and some custom add-ons for FPGA boards for loading a bitstream. All I'm saying is that it will be a LOT of work to reinvent Tockloader in Rust. I found, at least for our tooling, that the low-level Rust tooling for interacting with FTDI chips, for example, were somewhat wonky. I really like that you can make a rich command hierarchy with structopt/clap and that Rust gives you a lot of power in representing abstractions, but it's a not insignificant amount of code in open titan for opentitantool, and it's just one chip we're talking too. That's my experience from building such a tool. I don't want to discourage you, but I want to be clear that it's a BUNCH of work and to first consider the value of the existing stuff. Could instead clean up nasty corners and clean up stuff in the existing implementation. + * Alyssa: Rewriting in Rust means the same API for enums and things like that. + * Chris: That is a good counterargument. A nice unified API if everything is in one language. + * Alyssa: It lowers the bar to changing the TBF implementation too. So that would speed up evolution of TBF stuff. But python is great at generally evolving code. + * Phil: I think you touched on the tension. Python is great if we want to evolve the Tockloader interfaces. But changes to the TBF format that's in multiple tools would be better all in Rust. So the question is where the pain-point is. + * Chris: Another part of my experience is that opentitantool is a library of functions for interacting with the chip. So writing a purpose-built test program to interact with a chip, no matter what it is or its interface is, that's all abstracted away by the library. So you can have a rust-based test program on host that loads code onto a target and interacts with it over interfaces. Very convenient to have. This is one of the things that we get out of the tool being in Rust. You can write some really good custom test flows + * Alex: We were thinking of building on top of probe.rs https://probe.rs/ Still need to look into this more. + * Pat: I was looking at Tockloader. It's current 6000 source lines of python. There are a LOT of features that would need to be brought up. So taking something like this on is good. But I think you'd want feature parity before switching over. And that's gonna be a lot. + * Alex: My roadmap would be end-of-summer. Just experimental until it hits parity. I wouldn't push for switching before then. + * Pat: That seems low-risk from our perspective. + * Phil: To be clear, an important thing to tell Alex is if we thought that we must be in python, even if the Rust version hit feature parity + * Brad: It's a good question. I think there are definitely benefits to being in Rust. A question is how hard it is to implement this functionality. If it's really awkward and challenging and there aren't good interfaces, then maybe we need some compromise. + * Alyssa: Is there a Tockloader test suite? + * Brad: No + * Alex: My final question is whether it's worth splitting out the crate or if we should wait on that. + * Brad: Is there a reason not to include it in the TBF crate we already have? + * Alex: We have a TBF parsing crate? Is there a reason elf2tab wasn't using it? + * Brad: Well, elf2tab _creates_ TABs, but the kernel parses them. So there isn't as much overlap as you'd think. + * Brad: Here is the crate: https://github.com/tock/tock/tree/master/libraries/tock-tbf + * Brad: To answer your question, I see no reason to make a separate repo, since Rust is so good at splitting things into crates + * Alex: Makes sense + + +# Update on Licensing PRs + * CI PR - https://github.com/tock/tock/pull/3345 + * Hudson: Johnathan made a CI PR for licensing, but it's blocked on more discussion on the original PR + * Original PR - https://github.com/tock/tock/pull/3318 + * Pat: I'll pick that up soon, sorry + * Johnathan: Is there a reason we do the two lines (copyright and license) in the order we picked? I thought the other order was more common + * Pat: I thought the order mattered for some reason. It was some implicit top-down thing. It felt arbitrary to me and I don't feel strongly about it. + * Johnathan: The boilerplate for both MIT and Apache put the copyright first. + * Pat: I'll read a few more things, and if it doesn't make sense to buck the trend, I'll make sure I follow it. + +# Capsule reorganization + * https://github.com/tock/tock/issues/3346 + * Hudson: Out of time, but I wanted to drop a link to the capsule split like we discussed last week. It's gonna be a huge pain to keep re-doing this. So I want to discuss and come to some conclusion before making more changes. + * Hudson: I don't necessarily feel strongly about the split as is. Please drop comments on the PR if you have thoughts. + +# Future meetings + * Phil: We'll meet next week, December 16th but not on the 23rd or 30th of December due to holidays. Does that sound right? + * General agreement + diff --git a/doc/wg/core/notes/core-notes-2022-12-16.md b/doc/wg/core/notes/core-notes-2022-12-16.md new file mode 100644 index 0000000000..de819dec26 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2022-12-16.md @@ -0,0 +1,212 @@ +# Tock Core Notes 2022-12-16 + +Attendees: +- Alexandru Radovici +- Alyssa Haroldsen +- Amit Levy +- Brad Campbell +- Branden Ghena +- Johnathan Van Why +- Philip Levis +- Vadim Sukhomlinov + +# Updates +- Branden: I had a student who was digging into the microbit and particularly + working on libtock-c this quarter. It turns out every sound application was + doing value times 3 to play a note. We spent time tracking it down and it + seems to be an arbitrary constant. He will send a PR to make those + non-arbitrary. +- Alexandru: I have the answer. I copied the Arduino app and it wouldn't work. +- Branden: It's a minimum frequency. Those notes won't play because they're + below what the speaker can do. +- Alexandru: I multiplied by 3 and saw it works but forgot to go back. +- Phil: Discussed 64-bit time with Alistair. His PR is primarily about a 64-bit + timestamp. Given that TRD 105 says that platforms should provide the API, I'm + going to start making sure things follow it. The current proposal is to add a + new Command to retrieve 64-bit time to the time capsule. The difference from + Alistair's suggestion is this adds a new command rather than changing the + existing command to 64-bits. I'll start looking at that. +- Alexandru: I started working on the display trait and TRD. Hopefully I have + more time next week to finish it. Phil, if your offer to discuss it is still + valid I would be happy to do so. +- Phil: Happy to talk. +- Alexandru: I'll send an email. + +# Ordering userspace print statements with kernel debug statements (#3327) +- Phil: This PR from me came in response to Alyssa's comment that it is + frustrating that writes to the console can interleave, particularly + user/kernel space stuff. We don't see causal ordering in the output of + Console. This PR introduces a new capsule that makes userspace printfs append + to the debug stream rather than going through a virtualized writer. So it's + all synchronous and in order. The tradeoff is that because it uses a fixed + size buffer it is possible that your messages are truncated. The question that + came up in this PR is we now have an asynchronous console driver and a + different driver which is synchronous -- what do we want to do? Print log does + not allow you to read like console, should that be changed? Do we want to just + have both, have one, should they be separate drivers, separate userspace APIs? + How do we want to manage these two different semantics? I'd love to get + people's thoughts. +- *[47 second silence]* +- Phil: Okay, any people have questions? +- Brad: How does the print log capsule tie into the debug buffer? +- Phil: It literally calls `debug!()` +- Alyssa: Is it possible to write to the same buffer as debug without being + synchronous? +- Phil: The point is it doesn't block, in the sense of that being synchronous, + but we write into the buffer at exactly that moment. That's why if you reach + the end of the buffer it doesn't block, it just truncates. +- Alyssa: We could have the userspace make sure it doesn't send too much at a + time. +- Phil: Hudson raised some questions about the size of the debug buffer, but I + think that's trivial stuff we can sort out. This is really the question of: we + want two implementations; one that allows arbitrary length writes from + userspace but can interleave, and one that may drop writes but is synchronous. + The question is whether they should be the same system call API with two + implementations, or two different system call APIs? If there are two different + system calls, then there can be two different userspace APIs representing + them? If there are two implementations of the same system calls then there can + be a configuration option and printf goes to both of them. +- Amit: The way it is currently structured -- same interface but different + implementations -- seems better for the stated use (debugging) and a good + thing to have. Arguably, part of the point of Tock is for it to be extensible, + and part of that is we should have places where different platforms have + different implementations with different tradeoffs. To a degree, I can imagine + issues that can crop up -- what if one syscall API evolves and there's a + mismatch -- but that almost seems like a good thing to have surface so we have + to solve it. I think this design is good for both this use case and for the + kernel to have a main capsule with this feature. +- Phil: My one software engineering concern to that approach is that while the + write paths are very different, the new syscall doesn't implement receive. I + would like to avoid having the synchronous API copy all the receiver code -- + would want to refactor the implementation to deduplicate the code. +- Brad: I agree with Amit, and there's a part of me that thinks it is strange to + take something as fundamental as console and implement it on top of `debug!`. + I would find that difficult to parse and understand as a user. The other part + is -- I think there's still an idea out there that we want to have a more + robust channel between a host and a device. By having two console + implementations we are not making the stand that we always want sequential + behavior to happen. +- Phil: The name of `debug!()` is historical. We could add an alias + `synchronous_write!`. +- Alyssa: Yeah, we have three aliases, `console_info`, `warning`, and `error`. +- Brad: I think it's more than a name, but I could see some argument that + `debug!` could be rewritten as an integral part of the kernel. `debug.rs` has + things in it that are not okay for non-debug code. +- Phil: Like what? +- Brad: Like the unsafe hacks that make everything easy to use and nice. +- Alyssa: Which unsafe hacks? +- Brad: How the different buffers are connected -- off the top of my head I'm + not sure. Definitely to get the user interface to work the macro required a + bit of "okay, yeah, let's just get this to work because it's just for + debugging". +- Phil: That's true, `debug.rs` has more than just the debug writer, it has + debug I/O and the panic handler. +- Alyssa: To me, it matters what you're trying to use it for. The previous + design is better to have a reliable data stream. This would have fewer of + those properties, so it seems to be a tradeoff between easy-to-understand + behavior or resilience against apps attacking each other. +- Phil: I agree with that framing, which is why my initial thought is having + separate system calls. It is the case that if the kernel can provide either + implementation, the userspace API must reflect that data given to write may + not be written. That would push that logic to userspace, so even if you were + sitting on top of console you would have logic to complete a write that was + not completed, though console would always complete it. +- Alyssa: Console could just write up to a line. +- Phil: True, but that's not what it does. One other side of saying they're two + implementations of one system call is you can't have both -- something that + can write arbitrary-length stuff from userspace and something that can write + synchronously. +- Alyssa: If we can give them the same API with different guarantees, that seems + ideal. I think they should be exclusive -- one or the other. +- Brad: I agree, I worry about complicating this for userspace and userspace + examples. This seems like an advanced thing that most users don't need to deal + with. I wouldn't want to see different forms of printf calls in different + examples, as that's a barrier for new users. I would prefer unifying them in + the syscall interface and making this board-specific. +- Alyssa: We could make the console capsule take this choice as a generic + parameter. Users would have to choose between one or the other, but we + wouldn't have conditional compilation. +- Brad: That's what we have now but with two different capsules. +- Alyssa: I want to make it obvious these perform the same purpose but you have + to choose between them. I think most people expect console to be + temporally-ordered, so if we have to have a default I think it should be + temporally-ordered. +- Phil: It sounds like there's pretty good consensus that option one, + alternative implementations of the same device is the way to go. +- Brad: Phil, did you look at putting a layer between the bottom of console and + the UART or debug? +- Phil: That's totally something to dig into. I would be wary of making calls to + `debug!` look like UART, especially if that is the default one. +- Brad: Yeah, because the issue is the virtualization is at the "wrong" layer. + The virtualization needs to be above the central pool, not below it. +- Phil: There are other issues that come up. Not just interleaving, but also + userspace being able to take a lock. Once the client is operating on the UART, + it gets to keep on writing until it has nothing left. I worked out some cases + where clients can starve others. +- Brad: That seems like a different issue, a console issue. +- Phil: This is at the UART virtualization layer. +- Brad: Oh I see. +- Phil: I think that would probably be the right way to do this. There will have + to be some refactoring for the receive path. +- Phil: Are there any arguments for having separate syscall APIs? +- Alyssa: I think that would fragment the community. +- Phil: That sounds like an argument against, I'm looking for an argument for. + +# Fixing MapCell safety (#3325) +- Alyssa: Looks like it may double drop, I'll add some comments. +- Brad: The issue with `MapCell` is you can put something in the `MapCell`, take + it out, and take it out a second time, ending up with two references. This + pull request tracks that state, preventing you from taking something out a + second time. The tricky question is "what do we do with `replace`"? `replace` + should put something in the cell and return the old thing, but what if you + call it while you've taken something out? There's nothing to return, so it's + invalid, so this PR panics. You know I'm not the biggest fan of adding panics. +- Phil: You and people who care about code size. +- Brad: True. Maybe the mistake was having `replace` and we should just get rid + of it. +- Branden: Why can't `replace` just return `None`? +- Brad: Good question. +- Branden: You don't have to answer. +- Amit: I see four options. Return `None`. Return a `Result, Error>`, + it could panic, or we could remove `replace`. Do we use `replace` anywhere? +- Brad: It looks like we do use `replace` but we never do anything with it, so + we can just use `put`. +- Amit: I think that a fix is necessary, that panicing is not a good option. If + we don't need `replace`, then lets get rid of it. If we really need it we can + resolve the API issue. Basically what Brad said, right? +- Phil: `MapCell` is not used in that many places, right? +- Alyssa: I may be wrong but I don't see `replace` update `self.occupied` when + inserting a new item. +- Amit: That seems right, so it's maybe just a bug. +- Alyssa: I'm not comfortable changing `MapCell` at all until there's rigorous + unit tests. +- Amit: That seems like a reasonable ask for the PR. +- Branden: I'm a little confused about the `self.occupied.set` problem, it + wasn't there before. +- Alyssa: It did it via `self.put`, which sets `self.occupied`. +- Phil: Alyssa, can you comment that on the PR? +- Alyssa: Yes. It's also doing a double-drop on values. +- Branden: I think that's two more votes for removing `replace` as we've managed + to put two more bugs into it. + +# Rasberry Pi Pico USB (#3310) +- Alexandru: Are we ready to merge #3310? +- Phil: Brad, can you take a look at #3310? +- Brad: Yup. +- Brad: So we have `make program` that doesn't program. +- Alexandru: It's a bit tricky with the board, because you need to use a UF2 + file. At the moment you cannot program it with OpenOCD unless you have a + patched version of OpenOCD and another Raspberry Pi (either Pico or the big + one) and we want to avoid this. So we just use the UF2 now. We couldn't use + this because it has no serial so it needed the USB to print something. +- Brad: So the copy command copies the UF2? +- Alexandru: To a fake USB drive. +- Brad: That ends up programming it? +- Alexandru: Exactly. It has a bootloader in its ROM which cannot be + overwritten. +- Brad: Okay. Doesn't have to be OpenOCD as long as it does something, great. +- Alexandru: I think we still left the comment in the README file. If you really + need OpenOCD and want to debug it, you can. I think in the Makefile it's the + UF2. As soon as we understand how to program the flash on the Pi we'll port + the Tock bootloader to it, but for now that's a flash-less chip so it's a + little bit tricky. diff --git a/doc/wg/core/notes/core-notes-2023-01-13.md b/doc/wg/core/notes/core-notes-2023-01-13.md new file mode 100644 index 0000000000..b739ced191 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-01-13.md @@ -0,0 +1,184 @@ +# Tock Core Notes 2023-01-13 + +Attendees: +- Leon Schuermann +- Hudson Ayers +- Chris Frantz +- Philip Levis +- Johnathan Van Why +- Branden Ghena +- Brad Campbell +- Alexandru Radovici +- Alyssa Haroldsen + +## Updates + +* None. + +## Open / Merged PRs + +* Hudson: Merged 3 minor PRs + + - Fix Imix to not require process credential checking by default + ([#3376](https://github.com/tock/tock/pull/3376)) + - Enable virtual function elimination by default for OpenTitan + ([#3358](https://github.com/tock/tock/pull/3358)) + - boards/opentitan: bump to latest rtl sha + ([#3359](https://github.com/tock/tock/pull/3359)) + + Opened PRs: + + - `debug_writer_component`: make debug buffer size configurable; double + default debug buffer size ([#3369](https://github.com/tock/tock/pull/3369)) + - `boards/qemu_rv32_virt`: set minimum reqd QEMU version to 7.2.0, fix + Makefile rules, add documentation + ([#3370](https://github.com/tock/tock/pull/3370)) + - Implementation of PWM functionality for RP2040 ([#3372](https://github.com/tock/tock/pull/3372)) + - Make the set_clocks functions of the RP2040 drivers public to crates only + ([#3373](https://github.com/tock/tock/pull/3373)) + +* Branden: Why VFE for OpenTitan but not for others. + +* Hudson: Generally very useful, but still has soundness issues. OpenTitan + really cares about the code size, and the way they are using Tock seems to not + be affected by the soundness bugs. + + One of the bugs is that, if you use it, but you don't rebuild the standard + library, that can lead to soundness issues. There was another issue where it + could be too eager in removing functions and removed some which were meant to + be called. + +* Johnathan: The people working on VFE are working together with the OpenTitan + team, so it's kind of using what they are building. + +## Approach to Propose Changes to Tock + +* Alyssa: What is the best format to propose changes to or ideas for Tock? + Presentation, Issue? + +* Johnathan: If it's more concrete a GH Issue might be a good place, otherwise + perhaps a presentation is better suited. + +* Phil: Lean towards presentation, mostly to put the emphasis on discussion. + +# License PR (#3318) + +* Leon: The PR which initially sparked this discussion (implementation + of the CAN infrastructure, + [#3301](https://github.com/tock/tock/pull/3301)) has been waiting + for a long time now. Perhaps we can try to get this in, even before + we finalize the last few formalities on the licensing TRD PR + ([#3318](https://github.com/tock/tock/pull/3301)). + +* Hudson: That seems like the main motivator for us to get the TRD in. Not + opposed to merging the CAN PR, if there is general agreement that what's in + the license PR is what we are going to end up at. + +* Pat (chat message): License PR kept falling off my list. Will try to work on + it as soon as possible. Don't feel like you need my approval at this point, + just get things in. + +* Hudson: Thanks for taking this on in the first place! + + Does everyone feel comfortable with merging the CAN PR before formally merging + the TRD? + +* Johnathan: We were considering having "Copyright Tock Contributors" as a + standard line, which this PR does not do yet. + +* Hudson: Merging this PR even without this statement seems fine. Whatever we + arrive at is not going to forbid the use of that. To be on the safe side would + mean including this copyright assignment. + +* Johnathan: Is there any other open discussion item? We might be able to + resolve this today. + +*(people going through the PR)* + +* Hudson: it seems like this is the only unresolved issue. Main people involved + in the discussion seem to be Leon, Johnathan and myself. We're missing Amit, + but could try to come to a conclusion now. Leon, do you want to summarize your + standing on that. + +* Leon: Have some concerns with requiring that line. Even if not relevant from a + legal perspective, from an open-source community aspect, it would be weird for + us to assign copyright to an e.g., vendored file, to the general "Tock + Contributors". + + This is especially problematic for vendored files where adding this line would + be our only modification. However, even when it's a file written by a Tock + contributor, it seems inappropriate to force-assign copyright also to the + wider "Tock Contributors". Having this file seems like appropriation of the + author's work. + + It seems more elegant to me to have that line be standard, but if a developer + chooses to not include it, or we happen to vendor an outside file, that's okay + too. If someone makes substantial contributions to a file, and this person + happens to be also contributing to Tock, they are in their right to add that + line back. + +* Hudson: My argument is that the history of Tock has shown that this mostly + won't happen. Basically every file for which "Copyright Tock Contributors" is + not added initially, people will make edits to it but won't add further + copyright lines. + +* Johnathan: I agree. I think we should have it be in the examples in the TRD + and ususally add it, but not require it. + +* Hudson: I agree with Leon that we should not require this line on e.g., + vendored files. Is there an allow-list in the license checker? Then only those + files would not be required to have this line. + +* Johnathan: It is a bit tricky, but we already have a list of files which the + checker does not look at, which we could reuse. + + I am leaning towards not enforcing this in software. + +* Leon: I am not opposed to enforcing this through software in practice. I am + just opposed to a formal document (TRD) stating this requirement. It's much + easier to change the enforcement software to adhere to the reference, than the + other way around. + +* Chris: Generally agree with the philosophy on vendored files. If we aren't + changing it, we shouldn't touch it. + +* Hudson: I think that is how the document is currently written. + +* Leon: Two lines which worry me: "All textual files that allow comments must + include license and copyright notices." + + This is further ambiguous as to whether it implicitly enforces these files to + follow our formatting guidelines. + +* Johnathan: Yes, this sentence seems a little too strong. + +* Phil: Can we just qualify this with "When possible, "? We can also provide + particular examples of the exceptions we are thinking of. + +* Branden: Impression of this dicussion is that everyone agrees and there is no + real point of contention, except that we do not want to bind ourselves + accidentally. + +* Hudson: The one concrete change we'd need to make is on line 76-77, loosening + the statement about where license and copyright headers are required. + +* Johnathan: I will type up a commit after the call, to address both the + exceptions around vendored files and to add "Copyright Tock Contributors" to + the example. + +* Hudson: Going back to the CAN PR. Alex, I think it is safer to add the + "Copyright Tock Contributors" line, although from this discussion it seems + like we are not going to require it. Would you be okay with adding that? + +* Alex: No problem with that. + +* Leon: The CAN PR has been open for a few months, had multiple approving + reviews and a `last-call` label attached. Would we comfortable merging it + immediately once the change has been made? + +* Hudson: I think so. If anyone wants to speak out against it they can do so + now. + +* Johnathan: License checker PR can also be merged before the TRD, as it is + effectively not turned on (except for the files added in its PR). + diff --git a/doc/wg/core/notes/core-notes-2023-01-20.md b/doc/wg/core/notes/core-notes-2023-01-20.md new file mode 100644 index 0000000000..42ef7cf46b --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-01-20.md @@ -0,0 +1,99 @@ +# Tock Core Notes 2023-01-20 + +Attendees: + - Branden Ghena + - Phil Levis + - Leon Schuermann + - Hudson Ayers + - Jett Rink + - Alyssa Haroldsen + - Vadim Sukhomlinov + - Brad Campbell + +## Updates + * Hudson: Amit went through and approved and merged the two license PRs: the checker tool and policy doc. He also submitted some updates to the PR that adds license headers to files. Johnathan found a few small issues with that PR, but we're very close to being done with it. + +## Significant PRs + * License PRs! + * CAN HIL was merged thanks to Alex & team for the code and patience + + +## Deferred Calls + * Hudson: Leon and I have been working on this + * Hudson: As a refresher, today in Tock there are two types of deferred call. + * Hudson: The normal "deferred call" that's only used in chip peripheral crates. For example, the SAM4L uses it for the USART and Flash controller. The implementation supports only up to 32 deferred calls, but it's a very clean interface since it relies on global variables in each chip peripheral and in the kernel. Not a lot of boilerplate, just make a handler. Then the chip.rs file maps deferred calls to peripherals. + * Hudson: The other type is "dynamic deferred call" which was just in capsules, but is now also in the kernel app-checking infrastructure. Twelve total places, but capsules that are used by many boards like virtual UART. It uses dynamic dispatch from the scheduler to select the appropriate handler, which has vtables everywhere so some size overhead and less good API. + * Hudson: I have a few very incomplete slides on this that may improve in the future: https://docs.google.com/presentation/d/1Y9bT053LlU0097AmE4MmVwDxmF09VgUXJ1wHRVb3ndE/edit#slide=id.p + * Hudson: One downside of the "deferred call" implementation is that there needs to be an enum listing all tasks. So downstream changes would require forking the entire chip crate, instead of depending on it. Some other downsides are that it uses our own implementation of AtomicUSize, which keeps us on nightly rust for core_intrinsics. + * Hudson: Downsides for the "dynamic deferred call". It has about 100 bytes per client extra code size. Plus 20 byte vtable per use of it in RAM. Plus some runtime overhead since it can't be inlined. Also it's more annoying to initialize and use; much less clean. Capsules that use it have to accept an object in their new functions and store a handle in an option. Finally, it panics at runtime if there are not enough slots allocated. People have to remember to add this in main.rs. AND the panic happens before the board setup finishes, so the panic doesn't print anything nice out, and just looks like a silent hang. Terrible to debug. + * Hudson: So here's what Leon and I have been working on to fix things. https://github.com/tock/tock/compare/master...hudson-ayers:tock:defcall4 + * Phil: Which of these problems does this solution solve? Or all of them? + * Hudson: This is a lighter weight dynamic deferred call with a simpler interface. It solves the problem where you get a panic before the board setup finishes. Now it's after the kernel loop starts. Not registering is also a panic now. This approach has lower code size and RAM overhead from "dynamic deferred call". It also replaces both types, so there's just one type to think about and use. Easier to explain hopefully. It is more expensive than the old "deferred call", but it's less expensive than "dynamic deferred call", so I expect the total change for most boards to be a net reduction. + * Leon: It's also worth talking about tradeoffs. We thought about a lot of approaches, and the only major downside of this approach is that it makes it less obvious how deferred calls work under the hood and requires an understanding of how stuff is routed in Tock. We're just registering stuff in capsules now, but it can be hard to find where routing happens since there are some globals. It's a little less obvious. + * Leon: Still, I think it's our best solution. We did think about other possibilities. + * Phil: I think that's not too bad. Documentation can solve that issue. + * Phil: It's also worth saying that this is a long-standing problem in systems like this. + * Hudson: One other key limitation is that since this still uses a bitmask to track state, it has a maximum of 32 deferred calls. It's straightforward to increase that to 64 or 128, or could even use an array. + * Phil: Why use a bitmask? + * Hudson: More efficient lookups instead of iterating + * Phil: Why would you need to iterate? + * Hudson: The bitmask makes it really easy to set a bit when it wants to schedule itself + * Leon: A ring buffer would mean we'd have to check the existing bits to see if a call is already scheduled + * Alyssa: A ring buffer doesn't have to have a check and a branch for when it would overflow, you can mod the index + * Phil: I think they're saying that knowing if a particular deferred call is already in the ring buffer is iteration. You can't have any more than once, or else it could overflow since it only reserves space for what exists + * Alyssa: This is still a work in progress? + * Hudson: Yes. Any particular issues? + * Alyssa: I see some usize instead of u32. And more doc comments would be great. + * Hudson: Definitely. I'm using the bones of the code you suggested for lower-cost dynamic deferred calls, although I made some slight changes when storing the callback. + * Alyssa: You're avoiding doing an unsafe cast of the function pointer. + * Hudson: Leon was concerned that there wasn't a guarantee that the ABI isn't the same, but the closure should go away at runtime. + * Alyssa: That should be a guarantee + * Hudson: We workshopped this with Rust people on the subreddit (main Rust subreddit), and they proposed this. It compiles to the same assembly + * Alyssa: Great. With no cost, I have no concern. + * Hudson: Moving to the 15.4 driver, which used "dynamic deferred calls". + * Hudson: We used to store a handle and a call. Now it's just a call. You also don't have to pass in from main and can just make it yourself. There is a register method, which is a part of the client trait along with handling calls. Finally, you can just "set()" a call now, no unwrapping necessary. So I think this is a lot cleaner. Less in main and less in each component, although components MUST still remember to call register. But if it doesn't, you'll get a panic in the kernel loop, so it's easy to spot. + * Hudson: That works by the kernel loop verifying all deferred calls the first time it starts (both count and registration) + * Hudson: So this is where we're at for now. I've been trying capsules to start. + * Hudson: One interesting one. The LPM screen capsule. It had a single call, but multiple handles for different types of callbacks. To implement that, it did a match on the handle type that was returned from the deferred call. So this code would check which of the handles it owned matched the returned handle. With our new mechanism you can't do that anymore. Deferred calls don't return anything to you. Every call could take in and return an ID, but I'm not sure it's worth it. No other capsule does anything like this. Each call would be larger. + * Leon: Yeah, I'm against adding an ID. It's easy enough for capsules to have their own state that tracks which deferred call is active. I thought based on the design that everyone would use this idea, but we only added it to the docs recently. So for the one or two places that use it, I think we can work around it. + * Hudson: Yeah, so that's an overview of what we've worked on and where it's headed. We're going to finish porting this for the capsules, then I need to port over app verification in the kernel. Then we'll take some measurements once we finished removing "dynamic deferred call". Then we'll port over the chips, add docs, make a PR. + * Phil: I have a question about macros mostly. This challenge was one of the major challenges that TinyOS encountered, and handled with language extensions in TinyOS 2. The way we eventually solved it is that every deferred caller is given a unique number starting at zero, and somewhere else can get that count to initialize the structure that holds them. So every caller has a fixed index into the array. Makes most operations constant. + * Phil: I'm wondering if we could use that same idea with Rust macros. We can make it so each trait gets a number, but we also need to get at compile time a sum of all those (the total). + * Leon: We have been thinking about this approach. There is a mechanism in rust call TypeID. Which can give unique numbers to any given type. It doesn't have the property that they are necessarily sequential. We could use the numbers to allocate an array and track which capsule. But we have no way to count them. A fixed number of deferred calls and using a counter, we still can't really with the current state of Rust be generic over that number. The type system isn't there. We did think about a deferred call scheduler which is generic over the number of calls it have. I tried implementing it, but the generics just aren't there with reasonable code quality. + * Alyssa: Rustc is allergic to global analysis. + * Phil: So we can separate into two things. 1) can I assign a unique identifier, and the answer seems to be yes. If they can be dense and start at zero, they can also be used for an array-based queue. And 2) can you know how many there are so you can make the queue large enough. Number 2 there is less important, as we could just allocate RAM space for it, as the N here is quite small. + * Phil: Two links: https://stackoverflow.com/questions/51577597/how-to-automatically-generate-incrementing-number-identifiers-for-each-implement and https://github.com/tinyos/tinyos-main/blob/master/tos/system/SchedulerBasicP.nc + * Hudson: How did you store a pointer to the actual function that needed to be called? + * Phil: In TinyOS you didn't have to. In Tock it would have to be stored somewhere. + * Hudson: Let's say that call 3 was set, how did TinyOS change to that call? + * Phil: What TinyOS would do, and we can't, is parameterize a function by a constant. So there's not just one but N of them. And you can say "execute version 47 of this function". So the compiler makes the switch statement. In Tock, when you register your deferred call, you'd stick the handler in the array of function pointers. And the ID thing would just be for maintaining the queue. + * Phil: I have long thought we CAN'T do this in Rust. I just wanted to bring it up again as some things are getting better. + * Alyssa: I'm not sure why it would require constant folding of const generic, instead of inlining regular arguments. + * Hudson: I think the challenge Leon was describing was that you would want some const generic parameter use to parameterize the global array by, if we had some way to count the number. You'd also parameterize the calls by that constant. + * Alyssa: I think you can hack this with linker scripts, but I really think it's best to avoid global analysis. + * Hudson: This would possibly cause havoc for incremental compilation. + * Alyssa: It would also be static and not dynamic. + * Hudson: We actually are fine with static. We didn't want dynamic, it was just an implementation choice. It wasn't like you could change the target. + * Leon: We did try having a switch in the board state, but that didn't work well + * Alyssa: I think calling a function pointer is more elegant than a switch statement anyways. I like the idea of automatically setting up the magical switch statement, but I suspect that runtime function pointers is more elegant. + * Phil: Yeah. I wasn't exactly suggesting that. I just wanted to explain how TinyOS did it. In Tock, there shouldn't be a switch statement. The good thing would be the automatic maintenance of the structure such that it will never overflow, will easily maintain the ordering, and will allocate only exactly as much memory as you need. + * Alyssa: What are the blockers for that kind of scheme? That's what I didn't understand. Did it require full knowledge of the number of tasks? + * Hudson: Yes. + * Alyssa: What about a maximum number of tasks? + * Hudson: That's this now. + * Alyssa: We could use a ring buffer instead though + * Phil: Ring buffer would keep ordering, which would be nice + * Hudson: That's true. Existing code, I don't think has FIFO ordering. + * Leon: Neither "deferred call" for sure, or "dynamic deferred call" which we maybe just didn't implement. It did technically allow chips to assign priority. + * Phil: Yes. Interrupts are done in the same way where we scan the bitmasks. Starvation is problematic. + * Phil: So if you can do the counting, then you can maintain the ordering. And we could just oversize it, since it's really not big. + * Hudson: Confirmed that "dynamic deferred call" isn't FIFO today. + * Alyssa: Okay, so if FIFO wasn't expected, it could remain unexpected. + * Hudson: It wouldn't be a huge lift to go from bitmask to fifo. + * Leon: Wouldn't take more space. Seems possible. + * Hudson: Alright. So no final decisions yet or anything. But thanks for the thoughts and feedback. + * Alyssa: Could we make it so that the trait for deferred calls can't be made into a trait object by giving it `Sized`? Make it clear the intention is to NOT use it as a trait object. It would stop you from making a `dyn` object out of it. + * Leon: So anything that is not sized, can no longer implement the trait. + * Alyssa: It also stops implementing it on a slice, but I don't think that matters. + + diff --git a/doc/wg/core/notes/core-notes-2023-01-27.md b/doc/wg/core/notes/core-notes-2023-01-27.md new file mode 100644 index 0000000000..f9383da717 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-01-27.md @@ -0,0 +1,206 @@ +# Tock Core Notes 2022-01-27 + +Attendees: +- Alexandru Radovici +- Alyssa Haroldsen +- Amit Levy +- Branden Ghena +- Jett Rink +- Johnathan Van Why +- Pat Pannuto +- Philip Levis +- Vadim Sukhomlinov + +# Updates +* [No updates] + +# PR #3384 Fixed i2c buffer len +* Branden: Working with a screen, want to send it many bytes. Ran into HIL + limits. Had a discussion about whether the size should be a `usize` or something like `u16`. +* Phil: Initially we decided we should use `u32` everywhere and not `usize`, + then we discovered Rust gets mad at us for that. +* Vadim: This is a challenge for host emulation as well. +* Alexandru: I suggested leaving `usize` because that's what Rust uses for array + length and indices. +* Phil: I was a strong proponent of `u32` but the realities of Rust make that + impractical. +* Alyssa: Does `usize` implement `Into` on systems where that works? +* Johnathan: It impls `TryFrom`, not `From`. +* Phil: I think we should do `usize` for consistency. +* Branden: I was sold on consistency before there was a Rust reason. I think we + can be done. So `usize` it is. +* Alyssa: Both `usize` and `u16` are `TryFrom` each other, even when they're + compatible. + +# Issue #3383: Interpretation of "blank line"? +* Alyssa: I think having it in a comment is entirely reasonable. +* Branden: I strongly don't care about this issue. +* Amit: Likewise +* Alexandru: I don't care. +* Pat: Don't care. +* Johnathan: Hudson prefers it to mean an entirely blank line. +* Johnathan: If we need a tie-breaker then I'll vote for allowing a blank + comment. +* Phil: Be generous in what you accept and precise in what you send, that argues + for accepting comments. +* Johnathan: I'll send a PR that changes the license checker to allow it. + +# Ti50 Tock Wants +* [If you want the slides, you can contact Alyssa] +* Alyssa: A quick overview of some discussions I've had over the last week. The + biggest want I've seen is a new blocking command syscall. We've already + implemented it locally and have gotten solid code size savings. Allows us to + do some operations soundly that would otherwise be quite difficult. Common + pattern: + - Command + - Subscribe + - Yield-loop + - Unsubscribe +* Alyssa: We're done this enough that we realized we want to have a blocking + command. During a blocking command, all upcalls that are scheduled are queued + and not invoked. Allows userspace to use global buffers without adding + synchronization with upcalls. I think it is useful and deserves its own + syscall class -- what are your thoughts? +* Phil: I'm pretty positive on this. In particular, I think it is important that + calls are queued for similar reasons to what you mentioned. One of the reasons + the system call API is fully asynchronous was for the original low-power use + cases, where you want a lot of parallelism. In Ti50's use case, you don't care + about doing a lot of operations in parallel. +* Alyssa: We're doing an operation that we know takes some time and don't have + anything else to do. +* Branden: You don't Allow memory during this? +* Alyssa: We often do Allow as part of this sequence too. For example, our + console print is Allow memory, blocking Command, un-Allow memory. +* Jett: The Allow/un-Allow part is just about flash size and runtime savings. +* Alyssa: I think blocking Command is the fundamental piece, we can build other + things on top of it. Upcalls being queued is the special part. +* Jett: I agree. I think blocking Command is less controversial, but taking it + to the next step is worth looking at. +* Alyssa: I know we discussed combined syscalls, what was the problem? +* Alexandru: The prototype works, but I haven't had time to implement it. +* Phil: There were issues with Yield. +* Alexandru: Yield had to be the last command. +* Jett: This works better with blocking Command because Yield is not involved. +* Phil: A couple of issues come up with doing batches of syscalls. What happens + if you do a batch of 5 allows and allow 3 fails, what's the error handling? +* Alyssa: On the app side or kernel side? +* Phil: On the kernel side. What's returned if you do 5 Allows and the third + fails. You can return that a couple succeeded and exit, or keep going. +* Alyssa: You could also have it unwind the sequence. +* Phil: That doesn't always work. +* Johnathan: We're optimizing for a common case. In more rare cases with more + complex error handling, the app can do that itself. +* Phil: What if there's a non-blocking Command in the sequence? Can't roll that + back. There was research on batching in NFS, and the conclusion was you stop + immediately. Rolling back state changes is hard. +* Jett: Allow/Subscribe are special as they are kind of a `try`/`finally` thing. +* Phil: I'm skittish because we're talking about a specific use case, and to + build a general mechanism to solve a specific problem -- we want to be careful + there. +* Alyssa: There are different levels of complexity. We could define setup, + execute, and teardown sections. If I do Allow, blocking Command, un-Allow and + the blocking Command fails, I would expect to still do the un-Allow. +* Alexandru: Can't the library in userspace do the rollback? +* Alyssa: Yeah, but why do the combined syscalls? +* Alexandru: It's a bit faster. +* Alyssa: I'm mostly concerned about code size. +* Phil: What if we made something narrower, like batched Allows? That makes the + error handling simpler. +* Alyssa: If we wanted to have a combined Allow, blocking Command, un-Allow? +* Phil: That would be 3 system calls. +* Alyssa: If you're always doing Allow, operation, un-Allow, it makes sense to + just declare I want an Allow wrapping this. If it's just for Allows, it would + be combine this syscall with some number of Allows. If it fails, the kernel + rolls back. +* Alyssa: Sounds like blocking Command would be well-received? +* [Thumbs-up appeared in emotes] +* Phil: I think the semantics makes sense, devil is in the details. +* Alyssa: Should we send an implementation PR or a TRD first? +* Amit: I would find it easier with an implementation but either is probably + okay. +* Alyssa: Moving on, one team member wants a completely redesigned console + capsule, with: + - Order printing of strings in apps and capsules + - Line buffering in the kernel, with app printing to a kernel buffer rather + than an app-side buffer. + - Single console output buffer for all modes. Each USB/UART/etc has its own + head in and common head is min() of all heads -- moves w/ slowest. + - Micro-optimization: a print that always appends `\n` to reduce string + literals. +* Alyssa: Does anyone have thoughts on this so far? +* Branden: The last one seems easy. For line buffering, if you move it into the + kernel, you have to do a system call for each partial line. That has a speed + implication, though it seems like you care more about memory than speed. +* Alyssa: Yes. Personally, I want to keep line buffering in maps, but I'm + expressing a want of my team member's. +* Phil: Can you walk me through that? You do a series of writes from userspace + that are buffered until I send a newline? +* Alyssa: Right now our writes are buffered in userspace. In theory, the capsule + could do that instead. It would be more syscalls but less code and simpler + memory management. +* Phil: Because the kernel does it? +* Alyssa: Yes. +* Alyssa: That optimization using blocking Command saved 4kB by the way. +* Phil: If two processes are writing partial strings, the order will depend on + the order of the syscalls that triggers newlines? +* Alyssa: Yes. +* Alyssa: I should add another ask from the team. For our automated tests we + occasionally look at the console output, but if we end up having an interrupt + in the middle of a series of line prints the test fails. We have a couple of + possible solutions. I wanted to bring it up, because there was an idea of + being able to lock the console (with a max timeout) for a particular app. My + primary issue would be DoS. +* Amit: All of these seem like reasonable things, one concern I have about where + this is going is the variety of needs we are trying to serve. For multiple + apps, do we want a single shared console, or per-app virtual consoles? Maybe + printing arbitrary-length strings separated by lines is not the ideal + interface. I think all of these are reasonable, but I worry the existing + console is the wrong starting point for it. +* Alyssa: Yeah, essentially it would be an entire redesign. +* Phil: One of the challenges is many of these look good on their own but I'm + not sure their interactions are good. +* Alyssa: I'd want to make these requests more concrete. +* Johnathan: While designing app IDs I envisioned something that looks more like + IRC, as a way to separate app messages without having completely virtual + consoles. +* Amit: That would require line buffering. +* Phil: Not interleaving code helps in a test case, but avoiding interleaving + may make debugging harder, if the interleaved prints indicate unexpected + ordering. How do we do this in a general way? Doing a point fix for one + problem causes issues for other problems. +* Phil: Number one seems kinda obvious. +* Alyssa: I personally don't want it but see some advantages. +* Alyssa: We also have some muxing -- Ti50's console outputs to UART and USB and + they are not the same speed. What if prints run at a speed between that of the + two devices? +* Alyssa: Also want code sharing between apps, I know it's hard. Has there been + progress? +* Johnathan: No +* Phil: It's a linking problem, right? +* Alyssa: Yes. You can't share statics between processes but do want to share + code. +* Johnathan: FDPIC +* Alyssa: We want FDPIC but statically located. +* Johnathan: Right. That doesn't exist, and Ti50 is alone in wanting it. +* Alyssa: ufmt in the kernel, has Tock considered taking over its maintenance? +* Johnathan: Pending an evaluation, maybe. Ti50's experience with ufmt can help + here. +* Alyssa: I don't have numbers yet, kinda tricky to get because this was going + parallel to other work. Seems to be quite a bit more efficient. Needs more + TLC. There are specific use cases where I noticed it could be more efficient. + If Tock owns it I would like to help improve it. I don't want to work on ufmt + if I will upload a PR and it will be ignored. +* Johnathan: My main concern with ufmt was whether it was fit for our use, not + code size. I assumed it would help with size. I also think having it + maintained -- whether by Tock or another group -- is a necessity. +* Alyssa: There is want for more flexibility in syscalls -- how can we change + the ABI or add syscalls? We also have a team member who wants a fully + preemptive kernel with a simpler API. +* Phil: How does that interact with Rust? +* Alyssa: This person is not a Rust expert, I don't think they've thought it + out. +* Phil: When we first designed Tock we decided that interrupts would always be + unsafe. We'd have mechanisms to do interrupts but all bets are off. +* Alyssa: I think being fully preemptive in the kernel is desired. The safety + questions aren't easy to answer. +* Alyssa: End of presentation questions? diff --git a/doc/wg/core/notes/core-notes-2023-02-03.md b/doc/wg/core/notes/core-notes-2023-02-03.md new file mode 100644 index 0000000000..7330902ab9 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-02-03.md @@ -0,0 +1,60 @@ +# Tock Core Notes 2023-02-03 + +Attendees: + - Branden Ghena + - Philip Levis + - Hudson Ayers + - Jett Rink + - Alyssa Haroldsen + - Vadim Sukhomlinov + - Brad Campbell + - Johnathan Van Why + - Alexandru Radovici + +## Updates + * Phil: I just got off a 45 minute conversation with Lawrence Esswood, the person who has been looking into new kinds of allow based on some interesting memory requirements and he has come up with some very cool and promising ideas. Number one is the whole question of "can I actually allow DMA on memory from a process, with the necessitating implementation of this being that the zombie can become a zombie process"?. In the process of working on this he banged his head against grants a lot and came up with an alternative to the Grant type which I think is very interesting, and he is interested in presenting that to the core group next week. + * Phil: What he sees is us moving away from Grant::enter() and to his new idea which he calls "PRef". His new implementation is significantly simpler because we don't have to worry about grant reentry and stuff like that, and also brings some significant code size reductions, so I said people would be excited to hear about that. + * Hudson: Do you have a 10 second summary of what is different about PRef? + * Phil: The idea is that having a Pref is something like having a grant, in the sense that it is something which a process has given me which might no longer be live. Then you can convert a Pref to a Pref-live and a Pref-live is constrained in lifetime to a particular scope. It's basically then the case that as long as a kernel path is executing, I can continue to use Pref-live but then when this kernel path completes it is no longer usable and would need to be regenerated from a Pref. This has a nice property that you are no longer constrained by scope in the same way, so now Grants can reference each other (via non-live references). + * Hudson: And then a chip peripheral itself could actually hold onto a Pref (unlike a grant reference) + * Phil: Exactly + * Hudson: My update from the past two weeks is that I finally submitted the new deferred call PR that Leon and I have been working on. I am pretty excited about it and I think it is a much better interface, reduces LOC in the kernel and saves some size, it is also really nice that we don't have two deferred call types which I think is something that people always kinda tripped on. If you go look at the PR it is going to seem like a massive thing to review because it is like 2500 lines changed, but if you are going to review it I recommend just looking at the kernel changes, and then looking at a couple chip peripherals and a couple capsules. Leon and I have both gone through the two of us and looked at the full extent of the changes, so I don't think that we necessarily need a third set of eyes to do that. I am definitely interested in any feedback on the high-level new design and interface and stuff. Alyssa, I did tag you on the PR but a soundness review would definitely be appreciated for this. There are a couple of places where we are using `static mut` Cells. + * Alyssa: Oh, that's...fun + * Hudson: Yeah, so basically this is something we were doing in the old DynamicDeferredCall implementation that I carried over to the new one but I suspect we might not have done it that way on the old implementation if a pair of more discerning eyes had been on it you know 4 years ago or whenever it was. We could get around this by using like atomics with some code size cost, but the idea behind what we have now is that we have these static mut Cells, but they are not visible outside of the file, and every time they are accessed they drop their mutability immediately. + * Alyssa: So I have a SyncCell internally that we might want to port here + * Hudson: I assume that would do basically the same thing but enforce it at the boundaries of the Cell rather than having to check every place that the static mut gets accessed in the file? + * Alyssa: It essentially has two implementations, a thread unsafe and thread safe version, and which is used gets changed based on what system you are on. + * Hudson: I think we would be interested in having something like that because there are a lot of places where we are kind of using static mut incorrectly and that might be preferable. + * Alyssa: OK yeah, I think I agree there. The question is how do you determine whether you are in an environment without threading, can we just have like a flag? If you are running unit tests or host emulation that could be in a multi-threaded environment so you should keep that in mind. + * Hudson: Yeah, for upstream Tock we don't have host emulation and have been operating under the assumption that the kernel will always be run single threaded. Even QEMU or anything still runs the kernel single threaded. + * Alyssa: Yeah it is a bit of a weird thing with host emulation because any code could spawn a thread using the standard library. + * Johnathan: I think what we need to safely support host emulation is very similar to what we need to safely support unit tests, and unit tests are very valuable. + * Alyssa: Yeah we want there to be no way that a bunch of tests run at once cause UB. + * Hudson: Could we just switch based on the architecture? + * Alyssa: Yeah, right now I just assume that if you are riscv32 you are single threaded + * Hudson: And we could do the same for thumbv7-whatever + * Alyssa: Yeah, how should I upload that? Regular PR? How should we mix it with this DeferredCall PR stuff? + * Hudson: I think your thing could be a separate PR that we could integrate with DeferredCall afterwards. + * Alyssa: I would request a TODO on the deferred call PR + * Hudson: Sure + +## Significant PRs + * DeferredCall PR () + * A PR adding a ProcessConsole Command History (), which lets you toggle through N most recent commands entered into the ProcessConsole, the default for N right now is 10, and this does make for a better interface. + * Jett: This is something that Alex and I talked about a long time ago, since we have a downstream version of this, our version has state machines that help with processing escape sequences and that kind of stuff. Would it be helpful for me to add that to this PR, or do you think it is too late for your students? I feel like the way we do it is nice, it is pretty unit-testable and I don't mind sharing it for use upstream. Or is it too 11th hour? + * Alex: I think it would be helpful, we just need it in within 2 weeks for our RustNation tutorial. My honest suggestion is to merge what we have now and then have another PR to improve it. + * Jett: Sure, I will comment on your PR but consider it non-blocking. + * Alex: That sounds great. + * Brad: Low-level question: why do we need curly braces in the board main.rs files? + * (scattered guesses...) + * Alyssa: You cannot use a full path to a constant, it can be a constant in the same module and then it is not a problem. + * Hudson: Merged this week, we really only had the RP2040 PWM support that I consider significant. Johnathan also had a couple small PRs for the license header checker. + * Johnathan: Yeah, those were pretty uncontroversial after the call from last week. + + +## RustNation Preparation + * Hudson: Alex, were there any other PRs that you need to get in before RustNation? + * Alex: Nope, it was just the ProcessConsole PR that we just discussed and the RP2040 PWM PR that you just mentioned, bors did something fishy with the second one. + * Hudson: Yeah, that was really weird -- I ran bors r+, bors batched that PR with another, the build passed and the commits from both PRs got merged into master. But the PWM PR remained opened, looking as though it had not been merged. I went ahead and just closed it manually, but the commits from the PR are actually in master. + * Alex: Yeah, apparently there is an issue with bors that they cannot simply mark a PR as merged, just as closed, and somehow Github figures out that it was merged. If you squash commits, sometimes PRs do not get marked as merged (I have seen this in another repository). + * Phil: I want to say the ProcessConsole PR looks like a great addition. diff --git a/doc/wg/core/notes/core-notes-2023-02-17.md b/doc/wg/core/notes/core-notes-2023-02-17.md new file mode 100644 index 0000000000..2f5e97d5f8 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-02-17.md @@ -0,0 +1,126 @@ +# Tock Core Notes 2023-02-17 + +Attendees: +- Branden Ghena +- Amit Levy +- Johnathan Van Why +- Philip Levis +- Hudson Ayers +- Alyssa Haroldsen +- Leon Schuermann +- Brad Campbell + + +## Updates +### Hudson + * Hudson: We merged a few PRs pretty quickly this past week to support Alex and his RustNation tutorial. All reasonable. One of them: adding reset to the processconsole is maybe worthy of further discussion. The RP2040 doesn't have a reset button, and they didn't want to have to unplug/replug all the time, especially with virtualbox and USB passthroughs. + * Phil: I had to do this for RPis. USB is robust, but it's not that robust. Easy way to break things. + * Leon: Short remarks on the PR, the question is whether the reset function is actually safe. + * Hudson: It's memory safe, I think. Just shouldn't be exposed to capsules. + * Phil: Seems like a clear case for a "capability" + * Amit: From a process console standpoint, if you can restart applications, then restarting the whole board is similar. + * Hudson: We're going to open a new PR after RustNation to talk about stuff more. +### Leon + * Leon: I'm working on instruction tracing infrastructure for RISC-V on Tock. This is on LiteX now, but should work on OpenTitan stuff too. Should allow us to dump instruction traces from real running boards, including registers and stuff. Could work with context switches too! Unfortunately not compatible with ARM, but hopefully a very useful debugging tool. + * Amit: The state is that it ran all night long, generating more data then the workstation could handle. Couldn't trigger the bug though. + * Branden: What bug? + * Leon: There was a race condition in the RISC-V process switching code. We're hoping to detect these in a more automated fashion. + * Hudson: Does tracing affect timing? + * Leon: No, although it can slow the simulator based on disk latency for storing results. + * Phil: You might reach out to Dawson, as he does stuff with bare-metal OS development for his classes. He might be good to talk to about this stuff. He is very interested in detecting bugs, and going deep on hardware. + * Amit: Good idea, we'll reach out. +### Alyssa + * Alyssa: The function pointer thing for dynamic dispatch. I plan on next week finishing an implementation that would work for any of our traits. Look out for it. + * Hudson: So the benefit for arbitrary traits is that we don't have to track a bunch of things in the vtable like destructor that we don't need. + * Alyssa: Hopefully it should always be inlined. Also looking into static vtables. + + +## Proposed Edits to TRD01 + * Hudson: Current text of TRD01 says any changes must deprecate the document and create a new one. But something frustrating is that changing syntax of something unrelated requires deprecating the TRD in order to update the code example. + * Hudson: I had to do this for the Time TRD, because it had an example of a deferred call and we changed that API. + * Hudson: Phil might disagree with me. But I think there are some clear spots where the current approach has issues. We link to TRDs all the time, and deprecating and replacing keeps these links to outdated documentation. + * Phil: That's the moment in time though. You could end up with issues talking about a TRD, but the linked document doesn't match the discussion. It could cut both ways. + * Hudson: You're right. They could end up linked straight to a code example that's deprecated and not notice. + * Hudson: Generally, there's a clear history of the document in Git. It totally makes sense to deprecate and change if we're changing the API, but for unrelated example code, that seems bad. We've had people implement things based on the old time TRD before, because they didn't realize. + * Phil: People don't read. I do see your point, but it's a question whether the documentation is the current state of the system, or if the documentation is a point in time. In order to see the history and prior versions, should I need a Git repo instead of just needing the documents. + * Alyssa: Git does store that history though. And having multiple copies of documents is less elegant than having links directly to specific versions of the document on certain commits. + * Hudson: I think Phil's argument is that tooling like Git changes. So you don't want history to rely on it. + * Alyssa: We'll move our history to that new system. + * Phil: The issue here is that if documents should never be finalized because people don't understand which version they're on... + * Alyssa: I do get that, but for unrelated changes, it seems good to make those in place. You could keep a changelog in the file. + * Phil: That does go back to the "people don't read problem". This is based off of RFCs and Python Enhancement Proposals. Maybe Tock changes more in part because of evolution of Rust and our understanding of how to use it. So maybe that's a bad model for Tock. + * Hudson: I think it's reasonable that none of the rules in the TRD are changed once the TRD is finalized. I just think that for code samples, I think some of the finalized TRDs have non-compiling code in them, which seems bad. + * Phil: Let me toss out an alternative. I think part of the challenge is that the code in Tock changes and improves in a way that's rare in systems. So why don't we do versioning? So we could have multiple versions of TRDs, and the most recent link points to new versions. + * Hudson: How does that work? + * Phil: You keep file copies for old versions. Maybe this is an age thing, but the idea that I must use Git to see history feels wrong to me, like no one will actually do that. + * Alyssa: On the other hand, will people not submit changes because the process is too hard? + * Hudson: Actually, people do submit updates and don't update the TRDs. + * Phil: It's the code examples that touch other parts of the system. + * Hudson: It's hard to check that the code examples in the documents compile. + * Alyssa: We could do doctests + * Hudson: We end up having to ignore more things that are architecture-specific. + * Alyssa: Unit tests everywhere! + * Phil: The reality is that the TRD stuff was copied from TinyOS, which had a very different development model. We'd write a TEP in TinyOS and it would persist for twenty years. That is NOT the reality in Tock. + * Hudson: I will open a PR with proposals + * Branden: I do strongly want to have Finalization remain part of the idea + * Phil: I do see the issues here. I think you could have different versions JUST for code API changes. + * Hudson: I am opposed to new copies of files. I am okay with some kind of change in the document to signify that a change has been made. But I don't want old stuff hanging around. That way we don't have links to possibly outdated docs. + * Phil: My point is that you would have copies of prior versions. So the duplicate is only for deprecated/outdated files. Maybe related to releases. + * Hudson: Okay, I'm not so opposed to that. + + +## Split Capsules + * PR #3396 https://github.com/tock/tock/pull/3396 + * Leon: We have talked many times about separate capsule subcrates. We oscillated between which kind of crates can pull in external dependencies. Our solution that we've been converging on is that different subcrates of capsules could pull in dependencies, so you could depend on a set of essential core capsules, which should never need external dependencies. Then also optional capsule crates which might have dependencies you have to check on. Crypto is one example. Or TCP network stack is a good use case. + * Leon: Hudson proposed an initial division. Instead of nit-picking how many crates we want, for now we can just do core and not-core (extra). And we can pull things out of extra if it makes sense to separate them because they become core or rely on dependencies. + * Leon: So this PR implements that division. The only remaining question is where virtualizers should go. A separate module of the core crate, or not? + * Hudson: I think a separate module is nice. But I don't feel super strongly + * Amit: I lean in the same direction. I like namespacing. But it seems like a "whatever" decision. + * Leon: The reason to bring this up on the call. This PR touches 300 files and is a nightmare to rebase. So we should decide and then merge very quickly. + * Brad: A question. It is `core_capsules/` right now. Could we do `capsules::core::`? + * Amit: I don't think so. We can't have namespaced crates like that. Maybe we could have a capsules crate which re-exports crates, but that would remove the benefits of this change. We could just call it core and extra. But there's a global namespace of crates. + * Hudson: For example, we CAN'T use core as a name + * Leon: Yeah, we talked about the core kernel crate, and more specific names seemed better, even if verbose. I don't care about the exact name, but I don't think we can more reasonably name it + * Brad: Let's flip the words then so it's sorted reasonably + * Leon: The folders are already called core or extra only. Folders are hierarchical like that. For the term, I can do a find/replace to make it `capsules_core/`. + * Brad: So this name is weirdly board specific. Boards could rename it. + * Leon: The cargo.toml defines the name, then the cargo.toml in the boards pulls that in + * Brad: Thanks, that makes sense. So the bigger question for me is just the folder name, and then what boards do. + * Hudson: I do think that "capsules*" would be good, so the imports are adjacent in the files + * Branden: Is anyone against making this change quickly? + * Phil: I agree with it. + * Hudson: Should be safe. If it compiles, it almost certainly works. + + +## Success vs Success32 for Command Zero + * Issue #3375 https://github.com/tock/tock/issues/3375 + * Hudson: This came up in the PWM PR. We have these capsules like GPIO, LED, and ADC return Success32, as they declare the driver is present and the number of resources available. The number of pins, for example. This technically violates TRD104, which says they should return Success. https://github.com/tock/tock/blob/master/doc/reference/trd104-syscalls.md#431-command-identifier-0 + * Hudson: This mistake prevents us from generically checking for whether a driver is present. We don't have a use case for that command right now, so maybe it doesn't matter + * Hudson: A couple options: we could change these drivers, but they're stable interfaces, so we'd have to bump Tock's major version. And this doesn't seem important enough for a Tock 3.0 bump. + * Hudson: Another option would be to change TRD104 to not make this statement. That's finalized too, so we'd have to do something there. + * Hudson: Pat also suggested that we could add a new driver number for every capsule that does this, and maintain both versions. I think that's not a good idea. + * Phil: Yeah, it looks like LED was Leon and I and we just weren't careful. It think it's fine to just say we're sorry about it, do the right thing moving forward, and update them on the next major revision. It's a consistency question, it's a wart, but it's not hurting people. + * Hudson: Sounds good to me + * Phil: Going forward, should definitely follow TRD104 + * Hudson: Where do we put this? + * Branden: Tock 3.0 initial issue! + * Phil: Also a comment in the files noting that it's out of compliance + * Johnathan: A label for "fix in Tock 3.0" too + * Phil: I just want to avoid someone assuming that they follow TRD104. So some documentation seems enough + * Alyssa: Is there a route that doesn't involve changing major versions for changing TRD104 to allow success with any variant? + * Phil: I think it would be to make a new LED driver with a different API + * Branden: But if it's just inconsistent, and not hurting anybody, just leave it for now seems fine. + * Alyssa: Could we change TRD104 though to broaden possible returns? + * Phil: The idea of Command Zero was just "are you present". Not information too. That it was overloaded was a little messy. + * Alyssa: I was thinking that maybe the intention should change, since someone made this mistake + * Phil: I think it wasn't intentional in this case. + * Brad: Notably this doesn't matter in Tock 2.0 for libtock-c. https://github.com/tock/libtock-c/blob/master/libtock/tock.c#L693 + * Brad: I think just documenting it sounds good for now + * Phil: We could also maybe just look at multiple return variants and detect success anyways. + * Johnathan: We could maybe add it to libtock-rs. Gotta check exact wording of TRD104 + * Hudson: It would need to be at a different layer in libtock-rs to implement it. + * Phil: `is_success` in the kernel already returns true for any success type. https://github.com/tock/tock/blob/2be6fdb00fc4d34f4902746a9e8360abc8a0447b/kernel/src/syscall.rs#L325 + * Johnathan: It's different in libtock-rs right now. Actually, according to TRD104, the libtock-c implementation is buggy right now. See section 3.2, return values. There's no promise that future return values aren't up there. + * Johanthan: Although, we can probably rely on it in libtock-rs since it would already break libtock-c + * Hudson: Okay, I'll open a PR that adds comments, and I'll make a note that it's broken right now on an issue. + diff --git a/doc/wg/core/notes/core-notes-2023-02-24.md b/doc/wg/core/notes/core-notes-2023-02-24.md new file mode 100644 index 0000000000..9d83d95e75 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-02-24.md @@ -0,0 +1,41 @@ +# Tock Core Notes 2023-02-24 + +Attendees: +- Leon Schuermann +- Brad Campbell +- Alexandru Radovici +- Viswajith Govinda Rajan +- Johnathan Van Why +- Hudson Ayers + +# Updates + +- JVW: New design for Tock registers PR. Idea came up to only support MMIO (not + RISC-V CSRs). Need to work on a design to see how the MMIO only approach + compares to the more general. +- Hudson: We would lose support for in memory registers and RISC-V CSRs? +- Yes, if we want it to be separate. +- Extensibility for tock-registers is challenging. Could integrate. +- Do other tock-registers users use the extensibility options? +- Do litex registers use extensibility? +- Leon: Maybe, but should just clean that up to avoid the issue if needed. +- We do use in memory registers. +- Brad: part of this is to understand + +- Leon: PR#3396. Still open. Still hard to rebase. + +- Alex: presented at Rust Nation. +- 40 people. Github classroom failed. +- Will submit PR with tutorial. + - Integrate in to tock book? + - Book is a one-stop-shop for tutorials/getting started. +- https://github.com/UPB-RustWorkshop/rust-nation-template +- Updated libtock-rs to export modules correctly +- Difficult to get apps to load, need addresses to match + - elf2tab --protected-size to buffer changes in TBF header + - issues with app name size + - way to customize linker file? + - no auto layout option to libtock runtime + - might need to add support outside of libtock-rs to build for slots + - libtock-c handles this + diff --git a/doc/wg/core/notes/core-notes-2023-03-03.md b/doc/wg/core/notes/core-notes-2023-03-03.md new file mode 100644 index 0000000000..066b261eeb --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-03-03.md @@ -0,0 +1,133 @@ +# Tock Core Notes 2022-03-03 + +Attendees: +- Hudson +- Alexandru +- Branden +- Amit +- Johnathan +- Alyssa +- Leon + +## Updates + +Leon: PR to split capsules is in. + +Leon: I'm continuing to work on optimizing RISC-V context switches. + +Amit: Could you explain them optimizations? + +Leon: We used to stack all kernel registers, so that the assembly block could do anything. +The current change marks all registers as clobbered, rather than stacking/unstacking. So +now LLVM is automatically stack everything we need, and nothing more, rather than manually +stacking everything. + +Alyssa: Is there an artifact? + +Leon: #3407 + +Leon: I'm also wondering about this for userspace. But one thing is we don't want to leak +anything to userspace. + +Branden: Which direction is this saving or not saving, right now? + +Leon: Only relating to kernel registers from the kernel context. + +Branden: Doesn't this mean we could leak information? + +Leon: No, we restore the entire application register file. We might potentially have +some application contents until they are overwritten by the kernel. + +Branden: And we trust the kernel. + +Hudson: I have rebased deferred call PR and addressed all of the comments. Thank +you for the soundness review, Alyssa. There's still the TRD 1 issue, needing to update +TRDs that have deferred calls in them, but I don't want to block on that. #3382 + +Hudson: Let's get to the agenda. Tock registers. Johnathan? + +Johnathan: I'd like to get eyes on this. I have some ideas to change Tock registers +to fix the unsoundness issues and support testing. It's a complex, interconnected design, +which I had trouble describing. I really want help writing the design document, I need +help explaining the design. + +Phil: I said I would help. + +Branden: I had a tough time looking at all of the traits. + +Hudson: I've started to look through it some, but I don't quite understand it all yet. + +Alyssa: Please send me the PR? + +Johnathan: Sounds like lots of people will take a look, so we can table this to +next week. + +Hudson: What's your porting plan? + +Johnathan: I was assuming we could keep the existing macro in place, and start +changing things over. It's a huge change, will require a lot of testing. + +Phil: Can we do this for the next release? Make it a milestone? + +Branden: Not everything is switched over from even the last one. + +Phil: Yeah, let's clean this up and try to pay off this technical debt. + +Hudson: Alex would like to talk about Rust applications. + +Alex: We have a CAN driver, which receives synchronous from the bus. +We need to signal this to userspace. In C, you get one notification, +it starts filling in data, you get another notification, and can start +processing the messages. You can do this like ADC, but I don't see how +to do this in Rust. Maybe I have a buffer in the capsule, I copy it +into a buffer swapped with the application? + +Johnathan: I believe you can modify allow read-write to modify the reference +and it would be sound. When you have nested scopes, it'll be unallowed when +the inner scope begins. You can't move the reference into the outer scope, +this is the concern, that this is the dangling reference issue. + +Alex: So I could have a shorter lifetime than the original. + +Johnathan: If outer scope allows the buffer, and the inner scope swaps, +this should be safe. + +Alex: But when inner scope finishes, it unallows my buffer. + +Alyssa: Wouldn't this shrink the lifetime? + +Alex: If I exit the scope, it will be unallowed. I was wondering if someone +who is using libtock-rs has bumped into this? + +Johnathan: Nope. + +Alyssa: I don't fully understand the problem, can you message me on Slack? + +Johnathan: I'll try to open a group chat between the three of us. + +Alex: That would be great, I would appreciate it a lot. + +Hudson: That was everything on the agenda for today. Anything else? + +Alyssa: We want into some issues with the UART trait and wanting to +do some unsafe magic. I can gather the details, but to get around it, +we would have to change the interface to the trait. Essentially replacing +the mut ref with some wrapper around it. It's specifically because of +protection guarantees on references. If you pass a mutable reference, it +has to be valid for the function, even if you never use it after you +invalidate it? + +Leon: Don't we use static mutable? + +Alyssa: Makes no difference. It has nothing to do with lifetimes. I want to try to +identify the larger problem. It's one of those things, where if we were using +async this wouldn't be a problem. But we needed to create an unsafe interface +and that unsafe interface is unsound. I'll talk to Jett and see if I can get +more info. + +Hudson: An issue would be great. + + + + + diff --git a/doc/wg/core/notes/core-notes-2023-03-10.md b/doc/wg/core/notes/core-notes-2023-03-10.md new file mode 100644 index 0000000000..2bfb663ca6 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-03-10.md @@ -0,0 +1,33 @@ +# Tock Core Notes 2023-03-10 + +Attendees: + - Branden Ghena + - Hudson Ayers + - Alyssa Haroldsen + - Johnathan Van Why + - Amit Levy + - Pat Pannuto + +## Updates +* Alyssa: Someone at Google published an open source registers library, + it creates registers from an svd file. + Seems to be the only registers library that does not use VolatileCell, so it is + inherently more sound. Johnathan could probably base his tock-registers + updates off of it. Link is https://github.com/chipsalliance/caliptra-sw/tree/main/registers +* Hudson: I am planning to merge DeferredCall today if no one has complaints +* Amit: I am going to work on updating the PR to add a default license notice + to all Rust files, it just uses a sed script with a little manual checking so + I will run it again on current master +* Johnathan: Make sure to update the .lcignore file so it checks all the files +* Amit: Yeah that should make things much easier to verify which is nice +* Alyssa: DeferredCall is a breaking change, will there be a version bump? +* Hudson: We only have versioning for the userspace <--> kernel interface now, + not for any changes to crates in the Tock kernel repository that only have interfaces + to other stuff that will run in the kernel. We might want to do that eventually. +* Alyssa: Yeah it certainly seems like that would be a nice to have so we could know what changes need + to be made before we do a merge and everything breaks. +* Alyssa: Why doesn't Tock generate `macro\_rules!()` definitions from svd files? +* Hudson: Tock svd2regs.py which does this, but I have not used it +* Johnathan: Opentitan also has an internal tool that does this for its own internal format of svd files. +* Alyssa: I would like to talk about the userspace ordered prints PR, but Phil + isn't here so maybe that is best left for next week. diff --git a/doc/wg/core/notes/core-notes-2023-03-31.md b/doc/wg/core/notes/core-notes-2023-03-31.md new file mode 100644 index 0000000000..c0dea25af5 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-03-31.md @@ -0,0 +1,110 @@ +# Tock Core Notes 2023-03-31 + +Attendees + - Branden Ghena + - Amit Levy + - Phil Levis + - Vish + - Johnathan Van Why + - Vlad R + - Pat Pannuto + - Alexandru Radovici + - Brad Campbell + - Vadim Sukhomlinov + - Alyssa Haroldson + + +## Updates + * None + + +## Ordered userspace printlog + * Phil: Ordered userspace printlog stuff. It's working such that all writes to the console are temporally ordered. But if there's not enough space it'll be chunked out and delayed, so kernel writes could be in between. User writes are lossless. Kernel writes are not and could fail. I had to fix some bugs: if you ask how much space there is left, it doesn't consider the warning message that it printed too much, so I could print less. + * Phil: Unfortunately the version in there now requires changes to libtock-c so it can handle partial writes and loop. I'm going to try to remove that requirement so it can stay the same. It will mean that long userspace writes could take a long time to complete. + * Alyssa: How does this handle multiple apps? + * Phil: It is temporally ordered, but if there are many long writes, it might be a delay before the next occurs. But it will be ordered with concern to actions the app takes. So if you see a message, you know where the app was. + * Alyssa: Does the application wait for the print to complete before issuing the new syscall? + * Phil: Is the application print synchronous or asynchronous? + * Alyssa: It's using blocking commands + * Phil: Then it will be ordered. If it was an async print, then it could be theoretically possible that a kernel print will jump in before. In practice it would be pretty rare. If the userspace async prints and there isn't enough space, then the console driver starts a retry timer. If it starts that timer then the next system call leads to a tiny debug statement, there could be space for that and it might jump in. We'd otherwise need to block kernel prints, which we can't. But synchronous stuff is in order. + * Alyssa: Our Tock fork has a blocking command syscall. That's not upstream yet, right? + * Phil: No, not yet. Jett wanted to make sure it's good and stable before sending it up. + * Alyssa: I'm excited about it, and I'll do a good review. + * Phil: I'll do the fix for userspace soon. + * Phil: Last time I mentioned the weird behavior of not printing things sometimes, and Brad was right in guessing that it was a malloc issue. + + +## Design problems on Sync Cell + * Alyssa: We've discussed a data structure that's marked sync on embedded platforms but locks behind a mutex on host platforms. It would have operations like Cell. The question is whether this design choice is reasonable for applications. That we can assume we'll be single-threaded with no preemption while things are running. There are yield points, but you won't have unsynchronized writes to memory. + * Alyssa: So a question is what about host platforms. Should it be a mutex? Or a single shared global static or thread-local? Even behind a mutex it could be messed up if multiple tests are running concurrently. + * Alyssa: Problem number 2 is that in the kernel we have to worry about preemption from interrupts writing to a sync cell. If that happens, you could have a corrupted type. You could be halfway through writing a bit pattern when things go wrong. It's undefined behavior too. So, what level of safety should be required? + * Alyssa: I think that using another crate called zero-copy that defines key marker traits like "from bytes" that defines that all bit patterns are valid would be useful. So we have a bound similar to that? It would remove the undefined behavior, although not corruption. We could alternatively make all operations normal and safe and mark that it can't be used from interrupts. That's similar to solutions other systems use. Or we could mark most main operations unsafe. + * Amit: When we are on the kernel side, you mean interrupt service routines, right? (yes) + * Alyssa: Yes, in the interrupt context. + * Amit: So, in general the rule in Tock kind of has to be, maybe some special case exceptions, but thou shalt not touch shared data from interrupt contexts. Things that capsules and the rest of the kernel touch must be accessed in a single threaded way. + * Phil: When we designed stuff, we said if there was something super-performance critical, you could do some things in interrupt contexts, but all bets are off. In practice, we have never seen that. Does TI50 need it? + * Alyssa: Not necessarily. We're just worried about the footgun + * Amit: And there's nothing _preventing_ someone from putting some access within an interrupt service routine without at least being unsafe. + * Alyssa: Is a big warning message enough? + * Phil: For interrupt handlers, yes. They're generally considered stuff you don't touch unless you know something crazy is going on. + * Alyssa: So it could have the same bounds as normal cell in user and kernel space. + * Alyssa: There are other questions, like how to know what platform we're on. Should there be some kind of a flag that tells you if you're guaranteed to be single-threaded or not? Because SyncCell would need to swap out internal implementations. + * Branden: Isn't there already a global flag if you're in testing mode? + * Alyssa: 1) No, it's only for the crate under test, not other stuff, and 2) you might do host emulation where you're not in test mode. + * Amit: Maybe we could have an implementation of thread-local storage for Tock. + * Alyssa: I do want SyncCell to be zero cost like a static mut. For host stuff it could have costs, maybe. + * Alyssa: Also what about mutex versus thread-local? + * Amit: Does thread-local guarantee the right things? Is there guaranteed to be no other kind of concurrency that would break this in testing? + * Alyssa: Not sure what you mean + * Amit: If for some reason if someone tested with tokio and tests were async and concurrent within a thread or something. + * Alyssa: It would still be sound and there wouldn't be data corruption. But there could be something writing and something reading and a state you don't expect. + * Phil: I guess, and maybe I'm confused, that if you can represent it as thread local then there wouldn't be any waiting on a mutex, if it's recursive, so you wouldn't have to wait on the mutex anyways. Why bother implementing that if it's never going to matter. Plus implementing recursive locks is tricky to get right. + * Alyssa: There's a warning now that reentrancy deadlocks. So it isn't recursive right now. + * Alyssa: Also, disadvantage of thread-local is that it requires it to be constructed with a macro. Which is non-trivial. + * Phil: So this sounds like a software engineering versus performance issue. + * Alyssa: If OnceCell has been stabilized, that resolves a lot of my worries. + * Amit: It has been. What are those worries? + * Alyssa: Mutex required a constructor, so I needed to use OnceCell for it. + * Amit: Mutex new() is const as of Rust 1.63 + * Alyssa: There does exist a thread-local struct library. But it requires stuff I don't want to use. + * Amit: So back to my earlier suggestion, we could in Tock have a thread-local construct, which looks like the macro from the standard library maybe, which is essentially a no-op but is not a no-op in host emulation. Or maybe it does let us have some restrictions, and enforces no access to shared states in interrupt handlers. So this could address the software engineering need of having to declare variables in different ways on host platforms. + * Alyssa: Not sure that would work. I see a lot of possible engineering issues. Very tricky to do correctly. + * Alyssa: How do you write a thread-local without a thread ID? + * Amit: One way of doing it, a platform-specific implementation, you know that the implementation is just a global. There's nothing. But the implementation on host-emulation platforms with threads would use the standard library one maybe. + * Alyssa: How is the swap out done? + * Amit: I don't know. Not sure if "whether std is available" is something we can check? + * Alyssa: That would be neat + * Amit: I've seen crates do this explicitly by having the std feature, but that's a little more poisonous. Probably have to pass it around everywhere. + * Alyssa: I like the idea of exposing whether you want something thread-local or not. And giving that a shared interface. I still don't see how we'd be able to construct a thread-local without a macro. Seems necessary for how Rust does it. + * Alyssa: So if SyncCell new used a macro for a constructor, is that okay? + * Amit: I'm not sure I'm following. + * Alyssa: If you made sync cells with a macro, is that fine? + * Phil: I'd need to see the code. No immediate red flag, but I'd have to see some examples to know. + * Alyssa: Do you have any better ideas for determining whether the host platform can support threading? Is there a global Tock flag we could create or that already exists that guarantees single-threading? + * Phil: Having always lived in a single-threaded world, I don't know. + * Alyssa: So we'd need some way to detect and add something to Tock to determine it. + * Alyssa: Because we have a fixed set of platforms, we check the platform now. But that's not scalable to Tock in the same way. + * Phil: In terms of C, there are compiler flags for this. Whether you're freestanding or not. I don't know of Rust equivalents. I'd guess that it doesn't because of how fundamental multithreading is. + * Alyssa: There's something in the target for the toolchain. But I don't think it's available to compiled programs. + * Johnathan: We're actually the only Rust project I know of that's single-threaded. Even the embedded stuff assumes threading in most cases. + * Alyssa: Maybe something in build.rs with a config. + * Phil: What's weird is that no-threading is the common case. It's only under host stuff that there ever is threading. + * Alyssa: Okay, I'll review possibilities and come back. + + +## Licenses + * https://github.com/tock/tock/pull/3317 + * Amit: We should do a last call on the license notice in all Rust files. Most of the files in the repo. In accordance with Johnathan's checking tool. There are some notes to fix, but otherwise I think that we're ready to go on this. It would be nice to do it quickly, because changes in main that conflict require fixes. + * Johnathan: It massively sucked to review this. The only way I can review changes is by diffing against the version I just reviewed. I'm going to end up reviewing unrelated PRs. + * Johnathan: Here's the problem. We need Leon because there are seven files he wrote where I'm unsure of the copyright year. So there are only 12 files that would change in total, I think. + * Amit: I'll find Leon today and chat with him. Some are easy since we can see the year it was committed in. + * Johnathan: It was on the edge of 2022 or 2023 + * Phil: You can move copyright forwards, but not backwards. + * Phil: I'd like to propose we commit them as 2022 and we can fix it in a separate PR if needed. + * Johnathan: I say switch them to 2023. + * Phil: Doesn't really matter. That's fine. + * Amit: I can fix the duplicate headers Johnathan commented on too. We could also piecemeal fix them later if we notice it. + * Johnathan: SHA256 stuff was copied in and shouldn't have a Tock header. The rest is easily fixable. + * Amit: I will fix right now. + + diff --git a/doc/wg/core/notes/core-notes-2023-04-14.md b/doc/wg/core/notes/core-notes-2023-04-14.md new file mode 100644 index 0000000000..a19510d0b5 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-04-14.md @@ -0,0 +1,134 @@ +# Tock Core Notes 2023-04-14 + +Attendees: + - Branden Ghena + - Johnathan Van Why + - Hudson Ayers + - Leon Schuermann + - Alyssa Haroldsen + - Pat Pannuto + - Alexandru Radovici + + +## Updates + * Pat: Master student here (Tyler) who is working on a Thread network stack for Tock and will be around + +## Refactor capabilities + * https://github.com/tock/tock/pull/3409 + * Hudson: The PR looks huge and changes hundreds of lines of code, but it is really just a simple change to capabilities + * Hudson: Instead of traits, they're stucts with a private field and are unsafe to create. Generic over type T where type T is one of several specific capability structures. There is a high-level struct, which it could be parameterized by. These are all still zero-sized. + * Hudson: The improvement is that you don't need traits anymore, so no vtables ever (which wasn't a problem in practice anyways) and the generics syntax everywhere improves. + * Hudson: I was in favor of this, because not using trait objects and generics simplifies things. + * Hudson: The overhead hasn't really changed though, and it's a lot of churn, which has concerned Phil. So anyone using capabilities externally or capsules which have capabilities would have to follow the changes and make them themselves + * Leon: I think this is slightly inelegant in using the particular module structure to stop things from creating structs. Inside a module you could create the struct without calling its constructor. + * Hudson: The module is just capabilities.rs right? + * Leon: yes, but it requires that module structure + * Hudson: That doesn't seem like an uncommon pattern to me. Maybe not in Tock, but in other places in Rust. + * Hudson: We could create an explicit module inside the file. + * Pat: We do have some capabilities like network stuff that are in other files and other locations + * Leon: I think with this approach we could put in any generic type T, which could be an empty enum + * Hudson: But just having a capability that wraps a random type doesn't satisfy a requirement for a specific capability + * Alyssa: Is it important to have a trait which shows whether a capability is _actually_ a capability? I think no + * Leon: I guess I'm saying that we don't have creeping of module requirements in other capabilities right now. Right now we only define the capability wrapper once + * Alyssa: About the implementation, why pass a borrow of a capability and not just make the capability copy? + * Hudson: We discussed this a little in the PR. If it's copy, then one capsule receiving it could make copies and hand it out. Obviously there would have to be an API for that. But it feels weird. In practice, I don't know how much of a concern it is. + * Alyssa: Or we could make it clone + * Hudson: Well that's safe to do anywhere + * Alyssa: It's also safe to copy a reference to the capability as implemented now + * Hudson: Because creating a capability requires unsafe, at least that has to come from main.rs. Where a capsule shouldn't have the ability to make more + * Leon: An immutable reference could be limited to a lifetime, which could be nice + * Alyssa: We could have capabilities wrap a lifetime too. It could still be a zero-size type and could not have copy implemented + * Leon: That could be nice + * Hudson: Rust isn't going to make an optimization to remove a parameter that isn't used? If it's inlined it will remove it otherwise no? + * Alyssa: I think so. C wouldn't be able to + * Leon: If we store capabilities somewhere, that won't be optimized away. + * Hudson: They should be stored as an owned type + * Alyssa: I think making it copy, or clone if we're concerned, would make the most sense. Be a fully zero-sized type + * Hudson: We did have that conversation. The author changed it to be copy and said there was no size change + * Alyssa: I think it makes more sense too. What does it mean to take a reference to a capability + * Hudson: I'm still thinking through the ability to copy and share capabilities. You could copy the reference, but those are at least limited to the scope of the function call + * Alyssa: I could see the lifetime being useful. Could do that just as easily by putting a lifetime in capability + * Hudson: But then everywhere that accepts them would have to be parameterized by the lifetime + * Leon: They could be static for now + * Hudson: Then everything does have to live forever + * Alyssa: It could declare it as static with new + * Hudson: Then we lose the ability to restrict the lifetime, right? + * Alyssa: We'd be leaving the option open for the future. + * Hudson: I see that as less compelling than the PR implementation which leaves it open now + * Alyssa: Oh, that doesn't match the PR overview anymore + * Leon: So we create capabilities and pass them to something that holds on to them for a long time, likely the duration of the kernel. Or we pass them into functions that are short-lived. It seems like not much of a burden to make those functions generic over a lifetime + * Alyssa: Adding lifetimes to places is toil + * Hudson: I don't like it. It's not a huge inconvenience, but definitely additional churn + * Alyssa: Considering how often we depend on things being static, does it even matter to have capabilities not be static? + * Hudson: References right now have a shorter lifetime + * Alyssa: Yes, but all of the static dependencies exist right now. So a capability is really just another static dependency + * Hudson: With the current implementation, if a capsule duplicates a reference it couldn't hand it to something that could store it, since it has a limited lifetime. If it could be duplicated and held onto, then that could be used later + * Alyssa: I think it would still make sense to have a lifetime in the capability, then we could control things + * Hudson: But then it would always be static + * Alyssa: New could have a different lifetime + * Hudson: But it's got to be created in main.rs, and it would have to be static. Then the capsule couldn't further limit it + * Alyssa: You'd need a reborrow ability. Subgrant or something + * Hudson: Would you actually think to do that? + * Alyssa: I still think capabilities should be copy though + * Hudson: Is that because it's cleaner? + * Alyssa: Yes, primarily + * Hudson: I think if we go the copy route, then we do and decide that it's safe. I think copy with an internal lifetime is likely little benefit for the cost + * Alyssa: Adding lifetimes is never fun + * Hudson: And people are always going to instantiate in main, and likely won't bother reborrowing + * Alyssa: You only need to do it if you have a wrapper type that can't be copy + * Hudson: But you'll copy something that contains a phantomdata over a static reference, the copy will still have a static lifetime + * Alyssa: If it had the capability with a lifetime, it could still shorten the lifetime + * Hudson: I just don't think it would happen in practice + * Alyssa: We could do a covariant lifetime. So if we wanted to say the capability is only valid for 'a we could do that. But we could have most things construct or accept static + * Alyssa: I don't see obvious solutions here + * Hudson: I'd prefer copy everywhere and not have lifetimes at all. I'm not sure limiting lifetimes is actually a better security thing + * Branden: You did trust the capsule once. So you don't really have to worry about it passing the capability onwards + * Alyssa: I mostly agree. I think implicit copy no lifetime is probably best + * Hudson: I think it's marginally better than what we have now + * Alyssa: It removes a lifetime everywhere + * Hudson: They're almost always inferred, so not much clutter + * Alyssa: So why not put the lifetime in the capability? + * Hudson: Then it couldn't be inferred, right? + * Alyssa: No, I think it could + * Hudson: If a capability is generic over some lifetime, then you have to specify that lifetime everywhere right? + * Alyssa: I think you can just depend on the lifetime elision rules? + * Hudson: You can elide lifetimes that are generic parameters of a struct + * Alyssa: It's not recommended but you can. '_ is how you don't elide it. But you can fully elide it in stable Rust + * Hudson: Even if the struct has other parameters? + * Alyssa: You can do it with refcell right now + * Hudson: So summary, Alyssa is in favor of changing back to copy for the theoretical ability to restrict the lifetime. I am a little reluctant to do that, but maybe I just didn't understand the mechanism and its costs in adding lifetime annotations + * Alyssa: Any function signature that uses it right now has a lifetime, so I think it's no change + * Branden: So it would really make sense to see all three options: prior version of PR with copy, new version without copy, or existing Tock code. We should really see all three and figure out which is best + * Pat: If it's just ergonomics, then maybe this goes on a big list for Tock 3.0 + * Alex: I agree with this + * Hudson: Phil is right that this will break everything that is out-of-tree and require updates. The code is easier to read as a result, but there's ergonomic upsides and downsides, so maybe this is a wash until the next time there is a major breaking change like Tock 3.0 + * Alyssa: I do think the capability model was very confusing right now and unexpected. So I would like a change for a future version of Tock. I'm fine putting this off + +## Tockworld + * Email from Amit: likely east coast, likely end of July + * Johnathan: I might have a hard not attend at end of July + +## Tockloader Rust Port + * https://github.com/tock/tockloader/issues/98 + * Alex: We started re-writing Tockloader in Rust, which makes Tockloader be more consistent and will hopefully help installations + * Alex: My student started working on it, and wanted to open a branch on Tockloader to get feedback from the whole team, not just me + * Branden: Does it make more sense to have a branch or a separate repo under the Tock organization? + * Hudson: I'll make a repo + + +## Maybe-uninit and Syscalls + * Alyssa: Generally, I want to be able to handle maybe-uninit better + * Alyssa: We talked about passing uninitialized data across the syscall boundary and maybe-uninit data. The issue I have is that I want to make sure it makes sense that passing uninitialized data across a syscall boundary, the bits are frozen and the compiler should consider them initialized. + * Alyssa: Doing this in the kernel is also harder, because there's no write-only process slice. If I added one of those, mirroring the read-only and read/write, would that be interesting? + * Leon: The C userspace creates memory by just doing it, so I don't think the kernel can really make any assumptions about whether userspace considers memory initialized or not + * Johnathan: OpenTitan when the think boots, reading from memory will fault. I think one of the bootloaders zeros everything to make ECC checks pass, but we should check that + * Leon: I think there's a violation if the kernel would read process memory which is 1) not stable or 2) would cause a crash. If that exists, it's the kernel's responsibility to resolve it. Must be initialized before giving it to an app. Or some guarantees from the architecture. + * Johnathan: Agree with Leon + * Leon: So I don't think we need typing for that. In general, we do memory setup in assembly before even letting any Rust code run. Stuff needs to happen before Rust starts up + * Alyssa: Why? I'm not sure I follow + * Alex: I can share an experience with ECC memory. Unless you zero it out fully, it will start randomly faulting. So I don't think you could have some RAM that's not fully initialized. It could randomly fault later + * Johnathan: So the kernel has to initialize it before giving the memory to an app + * Leon: There's no way for the kernel to trust whether userspace has initialized things like it promised. And it would be very hard to track these things + * Alyssa: Okay, we can discuss more next week + + diff --git a/doc/wg/core/notes/core-notes-2023-04-21.md b/doc/wg/core/notes/core-notes-2023-04-21.md new file mode 100644 index 0000000000..f41fe2a702 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-04-21.md @@ -0,0 +1,175 @@ +# Tock Core Notes 2023-04-21 + +## Attendees +- Hudson Ayers +- Amit Levy +- Leon Schuermann +- Alexandru Radovici +- Branden Ghena +- Tyler Potyondy +- Brad Campbell +- Johnathan Van Why + +## Updates +- Hudson: Alistair asked for folks to look at PR 3360 -- he made some updates + on the PR so now it does not duplicate capsule code and now it just adds new + system call driver numbers. Alistair would appreciate folks taking another + look at that, especially now that it is not a very big change +- Leon: I am still not a huge fan of adding new driver numbers for existing + peripherals if they do not add any new functionality. I still think in the + long-term that we want to have something like a driver registry where we can + actually have semantic meanings for two instances of a driver on a given + board and then a way to request a combination of drivers under a certain + label. I am not opposed to this in the short term though. +- Hudson: Yeah, I think Alistair probably agrees with that but that seems like + something which might still be a good ways off. +- Hudson: We were supposed to continue Alyssa's discussion from last week, but + she is not here today so we will have to defer that agenda item. + +## External Dependencies +- Hudson: We talked a while back about moving forward with external + dependencies in the Tock kernel by starting to split up capsules. Leon got + around to implementing the first split between core and extra, which is + great, but we need to figure out next steps on this. My understanding based + on PR 3346, which is where we initially had some of this discussion on this + issue, is that we want to have an external subcrate outside of the other + capsule crates, and that is where something like Alistair's external + dependency would first live. +- Leon: I had the impression that when we split up capsules our intention was + to sort of gradually start splitting out subsystems for which it is natural + for them to rely on external dependencies, and limit the impact of that by + having boards only rely on crates they require. I think LoRa was maybe an + example where would just have a LoRa crate in the capsules folder. +- Hudson: Yeah, I think the original motivation for this was Alistair wanting + to use ghash for AES-GCM in PR 3092. +- Leon: Yeah I'm recalling this. We had talked about the networking stack being + its own crate and relying on smoltcp potentially. +- Hudson: I think that, recalling some more now, we had a couple options when + we initially discussed this. One was breaking capsules up into a ton of + crates, which people did not like so we did not go that way. This core / + contrib split is more just a nice clean division for now and a roadmap to + splitting up the capsules directory at all. I think we had talked about doing + something like what you described, where every capsule that would require a + unique external dependency would like in its own crate -- that is for + Alistair's example we would have a crate for aes_gcm in the capsules + directory, and only that crate would have an external dependency. +- Leon: I don't think this needs to be a strictly per-capsule basis, but if it + does not make sense for a certain subset of structs in Rust capsules to live + without an external dependency, we should probably draw some fuzzy boundaries + about the precise subsystem that they encapsulate, and make that an external + crate to separate that from the rest of the crates which we maintain. +- Hudson: Ok, yeah. +- Leon: I am not fundamentally opposed to a big external crate, but we don't + want to make it so projects needing one dependency to have to pull in all + dependencies. +- Hudson: I think we should tell Alistair to make a single crate for aes_gcm, + in the capsules directory, which can have a dependency on ghash, and then + opentitan can depend on that capsules. +- Hudson: We can figure out how to broaden the scope of this crate or create + new ones as other external dependencies get added. +- Leon: Yeah I think that sounds like what we had talked about and what I had + envisioned. We need to document this in the external_depenednecies.md file in + this PR. I could give a shot at creating a new version of it which reflects + this design. +- Hudson: Great, I am happy to look through that. +- Brad: Where would this new crate live? +- Hudson: In the capsules directory +- Brad: Is capsules a crate? +- Hudson: No, it is just a directory, core and extra are the two crates within + that. +- Hudson: For external crates, we might want another directory under the + capsules directory, to delineate that all crates within that directory are + separate because they have external dependencies. +- Brad: So how much logic should these new capsules have? +- Hudson: I think basically the minimum amount of logic to get things working + that does not require other capsules to depend on this one. +- Brad: Ah, but this new crate could use say the core capsules? +- Hudson: Yes. +- Leon: I think it is very important that we are not too strict in limiting + what can go in these new crates, that was the motivation for splitting this + up in the first place, since we concluded it is impossible to extract every + crate into its own trait, especially because of issues with macros +- Brad: So these will be full-fledged capsules? +- Hudson: Yes +- Brad: But they are in their own namespace, so the dependency does not + propgate +- Hudson: Yep, exactly, only boards that needs these deps will pay the cost of + them +- Brad: I think this sounds like a nice solution +- Branden: I am sold +- Hudson: I like that we have this one example from Alistair that we can use as + a trial. It is possible there is something we have not considered, so we + should not be married to the initial text of this document, but we can always + make changes based on our experiences. +- Branden: It helps that we do not expect to have a ton of these +- Hudson: Yeah, any maybe we should add some langauge to the document that + makes it clear that we will not just accept any dependency. +- Brad: Do we have in this document anything that outlines the issues with + other approaches, that describe why we chose this one? +- Hudson: No, I don't think so. +- Brad: I will try to draft that up and add it so we keep that knowledge + around. +- Leon: Great, and then we can reconvene on this next week before we ask + Alistair to make changes +- Branden: Put out a call for when you think its ready, I have been ignoring it + for awhile since it has been in limbo +- Leon: I think this is pretty exciting for long standing projects like a + proper BLE stack, for isolated subsystems I think this could be a really + elegant solution. +- Hudson: I agree. I do think we got kinda lucky that Rubble did not end up + being the first dependency that we used this for since it is in maintenance + mode. +- Brad: Sure, but it definitely still informed this notion of board specific + dependencies. +- Leon: I got a prototype working using smoltcp so that could be another thing + to try this with +- Hudson: Yeah, Tyler, you should chat with Leon about Tock networking since + his ethernet work probably has some overlap with what you want to do, + especially regarding TCP and IPv6 + +## Tock World Planning +- Hudson: Brad, thoughts on hosting at UVA? +- Brad: Sure +- Amit: Yeah, the late July time period on the east coast worked best for most + people, and we have not done UVA yet +- Hudson: I am always looking for an excuse to go back to UVA +- Brad: What did we do last year? +- Branden: 1.5 days, but the half day went long +- Amit: I think we can afford a bit longer this time. There might be some + recent industry developments to discuss, and funding stuff. Might be good to + do a training. +- Alex: I can volunteer to do a training +- Amit: That would be great +- Brad: It sounds like 2 days would be a good starting point. +- Hudson: I personally would request that it butt up to a weekend on one end + +## Other Stuff +- Brad: No progress on stable compiler stuff?? +- Hudson: asm\_const is blocked on inline\_asm, which at least seems to be + making some progress +- Brad: I see, just added a link to that to our tracking issue +- Hudson: naked functions unfortunately seems to be totally stalled, and I am + not sure there is a clear path forward there, which is a bummer since it was + looking like it was going to be stabilized just a few months ago. +- Brad: Yeah, I guess let's just keep an eye on it. +- Brad: I have also been thinking about bluetooth. It seems like it is really + hard to add, there is not good support in Rust. I would love any ideas on + what we could do short of writing our own stack (which is not something I can + do). +- Branden: Unfortunately I do not have any ideas +- Hudson: Yeah, there don't seem to be any great Rust libraries out there, the + few that do exist do not inspire confidence. Most projects just seem to link + against C libraries if anything. +- Brad: Has anyone tried just running the nordic softdevice on the network core + of the nrf5340? +- Hudson: Not that I know of, I think even that approach has some memory safety + concerns because both cores can access the same memory I think +- Amit: I think that fundamentally the right way to think of this is as not + that different than a peripheral implementing logic like this in hardware. +- Hudson: Sure, anyway, maybe that is something worth looking into. +- Alexandru: I have students working on ambient light and ADC for libtock-rs. + It looks like these are only implemented for Hail and Imix.Can anyone test? +- Hudson: I am happy to. I also can look into getting you some Imixes + eventually. +- Alexandru: What is a realistic timeline for deciding on TockWorld dates? +- Amit: I am hoping within the next couple of weeks. diff --git a/doc/wg/core/notes/core-notes-2023-04-28.md b/doc/wg/core/notes/core-notes-2023-04-28.md new file mode 100644 index 0000000000..5d82fb3daf --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-04-28.md @@ -0,0 +1,175 @@ +# Tock Core Notes 2023-04-28 + +## Attending + +- Hudson Ayers +- Branden Ghena +- Alyssa Haroldsen +- Pat Pannuto +- Tyler Potyondy +- Alexandru Radovici +- Leon Schuermann +- Johnathan Van Why + +## Updates + +### Async/Await Update for `libtock-rs` (and potentially the kernel) + +- Alyssa: might have a method to have smaller async/await support in + Tock. Can use one global vtable instead of individual vtables per + future. Primarily for `libtock-rs` for now. + +- Pat: no dynamic memory? Problem with many of these is that, + per call site memory must be allocated for the future's state. + +- Alyssa: Planning on using a static buffer. + +- Johnathan: not a hard problem to solve. Tricky to avoid reducing the + bloat around "what woke you up" and "what signal to handle". + +- Alyssa: [RTIC](https://rtic.rs/1/book/en/preface.html) looks like + they have been adding async support, so looked at some of the + innovations. + +### Availability for TockWorld 6 Meeting + +- Hudson: Poll has been sent around via email, please respond! + +### nRF52 - Support New Access Port Protection Mechanism (PR #3422) + +- Hudson: Brad introduced support for some new JTAG access port + restrictions introduced on recent nRF52 chips. Seems ready to go, + people should take a look and test. + + This is nice, as it makes recent nRF boards usable out of the box + again. + +### Updates to the `ExternalDependencies` Documentation (PR #3312) + +- Hudson: Brad updated the document according to our discussion last + week. Would appreciate if people take a look. + + +## MaybeUninit / Write-Only Allow Buffers + +- Alyssa: Discussion from 2 weeks ago. I'm replacing some of our + storage read calls with calls that are able to take `MaybeUninit` + data as their input. This is so long as the syscall does not read + from it. + + Considered the impact of reading from it, should not be worse than + just reading garbage data. + + Wanted to know whether there was any interest in a write-only + process slice / write-only allow. + +- Leon: Where we left off last week: we viewed this proposal in the + context of chips having ECC memory, which can cause faults if read + before initialized (written to). + + Seems that now we're mostly focused on reading uninitialized memory + in the sense that it may hold arbitrary data, but not that it could + fault the chip? + + Seems very important to disentangle these two issues: + - seems hard to use such infrastructure to handle "dangerous" + uninitialized memory (e.g. ECC memory) which can cause faults + - much more reasonable when we're talking about memory which just + hasn't been initialized to known-good contents, but is nonetheless + readable. + +- Alyssa: So should we consider uninitialized memory as part of our + threat model? + +- Leon: What precisely does "uninitialized" mean here? + +- Alyssa: Precisely the definition of `MaybeUninit`. + +- Leon: Tricky to use this Rust-focused definition when talking about + memory shared across the system call boundary. We essentially only + take in an arbitrary slice of bytes; can't rely on this slice to + contain well-formed data as required by the system call handler. In + practice, we thus validate the contents of that slice in a system + call driver. + +- If passing in a buffer to e.g., read data from flash into that + buffer, there is not any validation apart from the buffer's length. + Just want to make sure that the contents aren't read when they are + not intended to. + +- Leon: Seems reasonable. Still unsure whether `MaybeUninit` is the + right tool to use here. We're still operating on a slice of + arbitrary but fixed bytes. + +- Alyssa: This is what freezing across the system call boundary means. + + Maybe an out-reference is a better choice here. + + For systems where reading uninitialized memory can cause a fault, + + Values are frozen across the system call boundary from a Rust safety + perspective. Let's say we have a system which faults when a memory + location is read before it is written. When userspace shares such + data with the kernel, it could still fault. Don't think it's a + problem for Rust safety, but rather system resilience. + + Out-references that wrap `MaybeUninit` perfectly wrap write-only + memory. + +- Leon: I'm focusing on the issue that there does not seem to be a + reasonable approach for us to track whether a given memory location + has been initialized by userspace. As a result, if we were to give + userspace some properly uninitialized memory on these systems, we + can't reliably determine at runtime whether the kernel may accept a + readable or write-only slice of memory. + +- Alyssa: Today, an allow requires passing in a `&mut [u8]` slice, + which captures that it is initialized. Of course, this doesn't hold + in C. So right now, such shared memory is always readable and + writable. + +- Hudson: But if userspace lies about that and claims that some memory + is initialized, when it actually isn't? + +- Alyssa: Same risk as we have today. + +- Hudson: Leon's trying to get at the fact that running Tock on a + system where the memory model is designed in this way is not a valid + use of Tock. Tock can't trust the applications. + +- Alyssa: Depends on definition of trust. If the MMU faults that + specific app, then it's fine. + +- Leon: Would fault the kernel though, given it is reading + uninitialized data passed in by the application. + +- Hudson: Sounds like we're getting way outside the scope of current + Tock. Currently have no means of unwinding such faults. Seems weird + to design a system to such hypothetical changes. + +- Alyssa: More thinking of preventing bugs in the kernel. + +- Leon: A write-only system call for that purpose seems fine. Just not + for handling such faulting memory. We would work around the latter + by just initializing all memory on app startup. + + Would still need a motivating example. + +- Hudson: Seems like this is mostly about making it easier to write + correct code. There may be some vague security arguments, but that + might be pretty contrived. + +- Alyssa: Would at least like to see some documentation regarding the + safety & security considerations of sharing uninitialized memory + as part of a readable allow operation. + +- Johnathan: Could extend `libtock-rs` to pass a `MaybeUninit` into an + allow, but can't take it back, would effectively operate as a + `Result`. + +- Alyssa: If we we to introduce a new `WriteOnlyAllow`, should that be + a new system call variant or just a flag? + +- Leon: Don't know. There's a code-size concern with adding a new + system call variant, similar to the userspace-readable allow system + call. diff --git a/doc/wg/core/notes/core-notes-2023-05-05.md b/doc/wg/core/notes/core-notes-2023-05-05.md new file mode 100644 index 0000000000..0c5e8d29d1 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-05-05.md @@ -0,0 +1,72 @@ +# Tock Core Notes 2023-05-05 + +Attendees: + - Branden Ghena + - Johnathan Van Why + - Leon Schuermann + - Alyssa Haroldson + - Alexandru Radovici + - Amit Levy + - Hudson Ayers + + +## Updates + * Alex: Working on ethernet along with some support from Leon. It did work and a packet has been successfully sent! + * Leon: I was trying to get a hold of the STM evaluation board with Ethernet, but it's not for sale anywhere due to chip shortage. I do have a somewhat _cursed_ NXP board. Complicated, but I think it's close to working. Would be nice to have multiple examples + * Alex: I might be able to ship you a board! I also have some contacts at NXP + + * Amit: Student working on experimental moving kernel into interrupt-mode finished her thesis. See if she'll still work on it afterwards. I don't have synthesized things to share about it yet, thesis was very prototype. Still seems promising. Stripped down blink without timer worked. Reasonably significant savings for performance for basic system calls, which is where I'd expect the _least_ performance savings. Other than some engineering issues, it actually seems pretty doable, at least on ARM. + * Amit: I expect to have more to share about that in the future + + +## Bors Deprecation + * https://github.com/tock/tock/issues/3428 + * Hudson: We could host our own bors or move to github's merge queue, which I think Leon and I both think is a good idea + * Hudson: We would need to experiment with the merge queue for a bit to make sure it'll be good, but I think it'll be doable. I'm happy to take a stab at it, maybe I'll try on libtock-c to start + * Hudson: Alex also wanted it for tockloader-rs, although I don't think it'll be needed if you don't have CI + * Alex: I would like to experiment with it though, can you give me permissions? + * Amit: Done + * Leon: I could also take a look, but I can just assist Hudson if he has time + * Leon: The only thing I wanted to discuss was starting somewhere else other than the main one + * Hudson: libtock-c does have some CI actions, so it should be a fine test location + * Amit: Could we hit a github action limit? + * Hudson: We're replacing bors, so I think it should be okay still + * Alex: I think they have unlimited minutes for open-source projects + * Alex: For enterprise, the Tock CI is HUGELY consuming. So it _would_ be good to only run some tests. It doesn't seem like merge queue supports this. + * Alex: Merge queue is also very strange, it runs the tests as if they were in the PR, but actually they run in an environment without git somehow, where git commands fail. Then after they merge the start running the tests. It's very strange when I played with it in other repos. Pretty horrible and not well documented + * Hudson: Okay, this might not be straightforward then. We call git commands in Tock makefiles, I think + * Branden: If bors is deprecated, what is Rust doing? + * Hudson: They use their own stuff + * Johnathan: bors-ng was an open-source tool replicating rust's bors stuff. But then github thought it was a good idea and made merge queue. So now they're deprecating it + * Leon: There's a good blog post about why they are deprecating it. Bors couldn't do some things in github, it was impossible. And if githubt has a new tool, they're not going to support bors-ng. https://bors.tech/newsletter/2023/05/01/tmib-76/ + + +## External Dependencies Documentation + * https://github.com/tock/tock/pull/3312 + * Leon: I don't know if there's a ton to lead here. We've talked about this a bunch and I've tried to update the document to reflect our conclusions and still be coherent. But since it's distributed across so many discussions it's hard to keep track over the status of things. + * Leon: So my proposal is to do a brief pass over the current state of the document and see its state + * (pausing to read) + * Hudson: Seems right to me. Maybe we could move the rationale to the end, so most people can focus on what the rules are + * Amit: Agreed seems right to me too. But in the top, maybe don't throw shade at cargo so much + * Branden: Do we need wrapper-traits or not? We say not to use them up top, but say they might be good for board-specific stuff later + * Leon: Yeah, good catch. I think they make sense where they make sense. Not a requirement, but if they're useful we're good with them + * Branden: I think it's good to say "do what makes sense", so I'm on board with that + * Johnathan: I think this policy only applies to thinks in the kernel binary, not tools for building to binary. Is that correct? We might want to clarify + * Hudson: That is correct + * Johnathan: For OpenTitan, we're considering the opentitan tool being a dependency, which doesn't appear in the final binary but is useful for testing. So we could add a "scope" section to the PR + * Hudson: Great, after a few more edits, I think this is ready to merge + + +## Tockworld Planning + * Results on survey discussed + * Most popular dates are July 21, 24, 26-28 + * Johnathan: unavailable in that entire range unfortunately + * Amit: I wonder if we could find a time that better works for Alex and Alistair. Maybe we could find a time that partially works for each of them? + * Branden: Two full days makes sense. Last time we did 1.5 days and the half went long + * Amit: I wonder if it would make sense to do a training session there + * Alex: I would be interested! But we would have to have boards in the US already + * Amit: Then we could extend to three days and maybe bring in folks for the training day who wouldn't otherwise be coming. Maybe some lowrisc or zerorisc folks. + * Amit: So maybe the 24-25 range? Or 26-28th (most likely) + * Amit: So the next step is to check in with Brad and confirm real dates so we can get flights + * Hudson/Amit: Notes that transit from DC airport to UVA will be a two-hour drive or possibly an Amtrak train ride + diff --git a/doc/wg/core/notes/core-notes-2023-05-12.md b/doc/wg/core/notes/core-notes-2023-05-12.md new file mode 100644 index 0000000000..01ea3a8f29 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-05-12.md @@ -0,0 +1,219 @@ +# Tock Core Notes 2023-05-12 + +Attending: +- Hudson Ayers +- Brad Campbell +- Branden Ghena +- Alyssa Haroldsen +- Pat Pannuto +- Tyler Potyondy +- Alexandru Radovici +- Leon Schuermann +- Johnathan Van Why + +## Updates: + +- Alex: Ethernet -- got RX/TX working on the STM board. Will progress to HILs + from there. + + - Leon: Now have 3 Ethernet MACs supported, good basis to progress towards HIL + design from here. Tried to continue port on NXP chip, can't receive yet. + +- Tyler: Getting up to speed with the UDP stack. Made some good progress on + getting Thread to work. Managed to join an open Thread network, but facing + some issues w.r.t. to IPv6 addressing. Would appreciate talking to someone + with experience with the IPv6 stack. + + - Hudson: Touched the addressing part a few years back. + + - Tyler: Issue relates to requests within the larger subnet of addresses in + Thread. Not a specific IP. + + - Hudson: Addressing portion is very bare-bones. Not any discovery mechanisms + implemented, etc. Seems like Leon is going to run into this soon with his + Ethernet-based stack. + + - Leon: Looked into the existing stack, quite limiting and purpose-built for + 6LoWPAN stack. Maybe need to consolidate and redesign it. + + - Brad: Is there a tracking issue? + + - Leon: Concerning Ethernet and higher-level layers, there's an Ethernet + tracking issue. + + - Hudson: There is a very old 6LoWPAN tracking issue. Should be updated. + +## Merge the External Dependencies doc (#3112) + +- Hudson: Brad wanted to merge the external dependencies document. Has approval + from Leon, me, implicitly from Brad. Last-call label attached. Merging. + +## libtock-rs API PRs + +- Hudson: Alex requested looking at a few libtock-rs PRs. + + *List posted in chat, examples:* + - [NineDOF API](https://github.com/tock/libtock-rs/pull/468) + - [Sound Pressure API](https://github.com/tock/libtock-rs/pull/469) + - [Buzzer API](https://github.com/tock/libtock-rs/pull/470) + +- Branden: What's the approval policy again? + +- Hudson: Believe it's one week and Johnathan's approval for significant PRs. + +- Johnathan: These are not significant though. One approval, and usually try to + keep them open for at least one workday. + +## TockWorld 6 Planning + +- Brad: sent around link / template page. Decided on starting Wednesday, July + 26th. Plan was to do a day-long tutorial on Tock. Alex, you've done some + workshops recently. Can you give some insights? + +- Alex: latest workshop we held was at a Rust workshop. Was a 2-day event. We + tried to show students how to contribute to Tock. + + In London, we had 2 hours for our workshop. We designed a small application + where attendees were asked to write a driver. Things were displayed on a + screen, where that infrastructure was written by us is well. If we have a day, + this could be done step-by-step. + + We need to choose the right hardware though. + +- Brad: Goal is to get a description together quickly, such that we can invite + people. This way we give people enough time to plan ahead. + + Which hardware should we use? What topics are interesting? + +- Leon: Hardware availability -- anyone have experience? STMs are impossible to + get right now. + +- Alex: Can confirm. RP2040 devices + MicroBit are available. However, Raspberry + Pi has issues with Windows and AMD processors. Could use help debugging those + issues. + +- Hudson: Does it work on Windows when using a Linux VM? + +- Alex: Likely, although not with an AMD processor. It works on most bare-metal + Linux machines. + + We can also use two RP2040s, where one programs the other. + + Could get a "Pico Explorer base" board. Has a screen, some buttons, a small + breadboard; educational board. + + MicroBit doesn't have a screen, difficult to attach peripherals. Good for + kids. + + Adafruit Clue boards work very well. + +- Branden: Brad and I have some MicroBits, as long as we're not giving them away + we should be fine. + +- Leon: What about nRF devboards? Slightly more expensive, but they have a + full-fledged JLink on there, which is nice. + +- Branden: Teach a class with them. Have 40. + +- Alex: MicroBits are available on Mouser, can dispatch immediately. + +- Hudson: Disadvantage of MicroBit? + +- Brad: Marketed towards kids, looks like a toy. We have industry professionals + come in. + +- Branden: But it does have sensors and outputs on there, which the nRF board + does not (just 4 LEDs). + +- Alex: On the Adafruit Clue, the Tockloader / Tock Bootloader integration works + very well. + +- Hudson: When we use a board with a screen, it seems like we're emphasizing + that particular subsystem of Tock. If our support isn't great (e.g., updating + the screen is slow) then that might give bad impressions. Haven't actually + used Tock's screen support. + +- Alex: We used an STM Discovery board for our first workshop. Looks + professional, but flash page size is an issue, Tock bootloader doesn't work + with it. + +- Leon: Setting up the STLink with openocd is a pain. JLink or Tockloader + support would be much nicer. + +- Alex: May be able to flash this board through a browser. + +- Hudson: Even if we can do that, Tockloader is nice and we should try to use + it. + +- Hudson: Summarize. We want a board which + - we could get enough of + - should be supported by Tockloader + - maybe(?) has a screen, which could be used to run the existing tutorial + applications. + +- Brad: Maybe not just have one board. If we keep the MCU the same, we may be + able to use different boards. + +- Leon: Many of the interesting tutorial applications do depend on the partiular + inputs and outputs available on a given board, though. + +- Brad: Maybe first think about the types of sessions we'd like to have, then + evaluate which board works best. + +- Pat: Demonstrate Tock as a secure operating system, e.g., code signing? + Loading applications? + +- Brad: Surprising to me is that most of our contributions over time don't seem + to be capsules. From a "what's the most utility" perspective, is capsules + actually right? Is doing a tutorial on the core kernel / chip drivers too + complicated? + +- Branden: We could be having people reimplement chip drivers, demonstrate MMIO, + etc. + +- Leon: When talking about the core kernel, there's a risk of this becoming a + lecture instead of a tutorial. Also, talking about chips / drivers, those are + not the most consistent, some of them do inherently unsound DMA copies, not + particularly well structured right now. Might not want to highlight that. + +- Alex: What about a practical project? E.g., coffee machine running on Tock? + +- Pat: Think about differentiating factors of the target audience. E.g., + demonstrate isolation by trying to break one app from another. Have it be + open-ended, where people can get creative. + +- Brad: Second that. Maybe introduce mechanisms such as signing apps, syscall + filtering, etc. Need to get those mechanisms supported on the nRF platform + then, though. + + In theory, we can also write up some sensors to the nRF boards. + +- Leon: May be able to solder up some Arduino shields beforehand. + +- Pat: Good idea by Hudson - two types of registration: one with the board + included, one where we provide boards for the workshop. + +- Leon: When we're trying to break applications / the security model, and we're + using nRFs anyways: we could be running OpenSK, and people could take that + home and play around with it. + +- Pat: Other applications could be something like an occupancy sensor. A third + party app could try to forge that count. + +## I2C Implementation Generics (#3431) + +- Alex: SPI used generic, I2C used trait objects. Generics allow more + gateway-optimizations such as function elimination, but code size is not + affected much. Disadvantage: type signatures look more complex. + + Also, now we have an additional type, because we need to have a type to plug + into to generic for I2C implementations which don't support an SMBus. + +- Leon: In the past we've almost always decided that generic are preferable, so + think this is a good change. + +- Branden: Are we worried about the churn of this change? + +- Leon: Here we have actual benefits of making the change. + +- Brad: Also brings this HIL in line with the others. diff --git a/doc/wg/core/notes/core-notes-2023-05-19.md b/doc/wg/core/notes/core-notes-2023-05-19.md new file mode 100644 index 0000000000..34c78b27db --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-05-19.md @@ -0,0 +1,98 @@ +# Tock Core Notes 2023-05-19 + + - Pat Pannuto + - Amit Levy + - Alexandru Radovici + - Leon Schuermann + - Brad Campbell + - Branden Ghena + - Caleb Stanford + - Hudson Ayers + - Johnathan Van Why + - Tyler Potyondy + +## Updates + - (crickets) + +## Agenda + +### External Dependencies & Cargo Scan + + - Caleb: Off the bat, it's likely that cargo scan can't solve the current problem, but interesting to see if we can't solve in the future + - Caleb: Working on supply chain security for Rust + - Caleb: cargo-vet is a Mozilla tool that is currently usable / near-released. It lets you audit supply chain dependencies, but very manual. Line-by-line declaration of what's safe. Not a lot of enforcement that you're actually auditing anything, results in a 'looks good to me effect'. + - Caleb: Trying to bring some static analysis into workflow to improve automation. i.e., in a big codebase, likely only need to look at a few critical sections, think syscall effects, unsafe, other edge case things + - Caleb: Current state: We have a tool, it's usable, but it's focused on unsafety and side-effects. Rust, despite being safe, does allow side-effects. + - Caleb: For your use case, it looks like interest in crypto libraries e.g. ghash. Where you'd need functional correctness. But we filter out that expected code. + - Amit: That's a slight mischaracterization. Tock relies on Rust's memory/type safety for isolation within the kernel. Many not-fully-trusted things (capsules, ~= driver in linux) are compiled into the kernel. The promise we want to make is not that the driver is functionally correct, but rather that if they are buggy they won't corrupt the rest of the system. + - Amit: If ghash is buggy or cryptographically leaky, that is not necessarily the kernel's concern, as long as there's no way for the library to say "leak user memory somewhere else" or "read HW stored secret (it doesn't have access to)" + - Amit: We generally rely on Rust type safety for this. What's made us wary about third-party libraries in the past is that we thus have to check out all uses of unsafe rust + - Caleb: That's interesting! Focus is not cryptographic assumptions? + - Amit: It's sort of subtle, but yes, the core kernel doesn't rely on crypto as a core library or for security or correctness. Builds of the kernel, certain products, etc, may have that requirement. But that's for the board/platform owner; and they can already include things that include unsafe Rust etc. We want to be able to make strong guarantees about memory safety in the kernel + - Amit: In particular, if there's a bunch of drivers useful across many boards, those are semi-trusted; they probably do the right thing; but you're not going to audit the complete driver behavior when you want to add a complex sensor. That's what we want the strong compile-time guarantees for. + - Caleb: Compile-time means you're okay with auditing? + - Amit: We're fine to audit. Current approach is the 'forbid unsafe' trait for these crates, plus some restrictions on available interfaces. What we don't have to date is ways of enforcing this on third-party / dependent crates. + - Caleb: And that's because of how cargo works? + - Amit: Yes. + - Caleb: And the only thing you're looking at right now is ghash? + - Hudson: Because of this limit on auditability / enforcement, we have been very resistant to any external crates. We've been using ghash as our motivating example to figure it out. In practice, that's manifest as our new [External Dependencies](https://github.com/tock/tock/blob/master/doc/ExternalDependencies.md) policy + - Hudson: Goal in the long term would be able to do this for more down the line. E.g., we've thought about Rubble Bluetooth crate (though that's now dead?). + - Caleb: Interesting... I think our tool may be more applicable than I thought. We are at early stages and are looking for real-world case studies. Once the tool is a little be more mature, we can try running it on Tock and seeing if we get useful results.. + - Caleb: I did a quick run on ghash. Ghash doesn't have any unsafety or side effects in the whole thing. + - Caleb: I guess in that case are we worried about the board that's using ghash? + - Amit: I believe that ghash that some of its optional dependencies may use unsafe. So really what we care about is 'for a particular instantiations that there is no unsafe'. I suppose if there's more precision in 'whether any of the unsafe is reachable' that's also interesting; but first-order is a grep of unsafe in the set of all dependencies actually instantiated. + - Amit: The more interesting perhaps is the counterfactual. Ghash is being pulled in because it meets the criteria of it would likely be worse for us to reimplement or even vendor due to the subtlety of crypto. In the past, however, Hudson spent a huge amount of time reimplementing 6LowPAN and UDP and such in a way that was Tock-specific because we don't have a way of reasoning about whether a third-party dependency is using unsafe, or if a version bump starts to, etc. So ultimately, what _could_ we pull in and how could we allow developers to rely on third-party libraries in a way that we've forgone with great expense to date. + - Amit: This may be less satisfying in the short term as running on Tock today will say no dependencies :) + - Caleb: Clarification—are you interesting in auditing your own code or just dependencies + - Amit: Our own code base is trusted. + - Amit: I don't want to dismiss that if there are tools that could help us verify that our trusted code is actually trustworthy is valuable. But conceptually, that's the boundary + - Amit: For dependencies, we don't really have any (except crates in the same repo) + - Amit: The second thing, unclear if the tool does this or could, but it seems like it'd be nice to allow people to pull in a dependency that's a big useful crate that maybe uses unsafe, but the Tock code uses the dependency in such a way that the unsafe is never exercised. That seems useful. If the unsafe part doesn't affect the compiled artifact, that seems useful and cool + - Caleb: We're close to having that tool. We're not quite there, but that's the plan and soon on the roadmap (to filter out parts of deps you don't use) + - Hudson: This applies to transitive dependencies? + - Caleb: Yes... we're also in the process of writing that :) + - Caleb: Currently, it will work on a single crate a time, but we're working on transitive + - Hudson: Is it an issue that Tock runs on embedded platforms / binaries? There are large parts of the Tock kernel you can't run on a Linux machine? + - Caleb: Great question. Platform-specific code and build.rs and build-time / compile-time flags? + - Hudson: Any of these... I might imagine runtime profiling of all the code that could be called as a way of seeing all code paths. Or it could be some symbolic execution thing, where you could ignore platform-specific stuff (e.g. just executing llvm ir, etc) + - Caleb: It gets more complicated because of that, particularly because arbitrary stuff can happen at build time. I need determinism of the build. + - Hudson: Is this only a problem if people use build.rs? + - Caleb: build.rs is the biggest problem, but also macros and side-effects at build time + - Hudson: We don't use procedural macros at all + - Hudson: And minimal use of build.rs + - Pat: We're actually stronger here, we intentionally target deterministic, reproducible builds + - Caleb: This is actually the best-case scenario for an auditing tool; you forbid a lot of what makes our life difficult. Also worried about platform-defines / conditional code + - Pat: You're going to love us again. We generally forbid `cfg` + - Caleb: Okay, this is sounding great. We can run on ghash and its transitive dependencies, but that will likely be a trivial audit... it sounds like more interested in the future case? + - Hudson: I would say we are more interested in the future crate. But, it would be really nice to have confirmation that we don't touch any of ghash's unsafe + - Branden: What would this look like one day.. a CI thing? + - Hudson: In the medium-term, any time we update a dependency to a new version, this is a tool that we run. That can be CI-enforced. + - Amit: Also depends on how expensive it is. In the Safe Haskell world, which is more rudimentary, it's just part of the compile process. + - Caleb: We've thought about CI integration.. on the roadmap.. if that would be the most useful use case we can triage that up + - Amit: On our side, it looks like there's essentially a command that you run. Seems pretty easy for us to just add a target to our Makefile that the CI calls. If it fails it fails, etc + - Branden: We update dependencies rarely enough that manual is fine for now. + - Amit: Could imagine cool ways to go from here.. if it's expensive that you really only want to run when-needed, could do some pass for a hash of the dependency tree, etc + - Caleb: It's pretty lightweight. We're not doing any heavyweight symbolic execution or something that takes hours to run. + - Caleb: But it does require human-in-the-loop to make decisions about what it finds and what you want to do with them + - Amit: Is the invariant it finds nothing / or don't admit + - Pat: Is it deterministic in what it reports? Can we mark something as verified / allowlisted / audited? + - Caleb: That's exactly what we're working on right now. Goal is to be able to prove that existing audit is still valid for an updated crate. + - Caleb: How can I be helpful from here? + - Amit: That's a good question. One of us should find a student :) + - Caleb: I'm doing a lot of development on cargo-scan, but finding a student would be good + - Amit: It would be good to have someone more familiar with Tock working on this + - Alexandru: I have some students who might want to this + - Caleb: Happy to set up a more focused meeting with a student, have them reach out + - Caleb: I don't think cargo-scan is mature enough to run on all your dep's at once + - Caleb: Maybe we follow-up in a month, and we can establish whether it's useful + - Alexandru: That works great here—we have a program that sponsors student open source over the summer, starts in one month + - Caleb: That sounds great; happy to tailor development towards your use cases; looking for users + - Amit: Is this something we fits the eventual POSE security role? + - Pat: Absolutely, it's right in the heart of the 'infrastructure' we defined + - Pat: Also, we have TockWorld July 26-28, it would be great if you can come + - Caleb: Maybe... what is it? + - Amit: Annual, in-person meetups / workshops with folks involved in Tock development and use. This time around we're looking to kick off bootstrapping of a sustainable open source ecosystem. Goal is gathering wants and desires from stakeholders; get buy-in from OSE commitments; in general more formal processes for forming, auditing, maintaining security guarantees very in scope + - Caleb: Sounds good. Let's stay in touch. Especially if we can get a student involved, good opportunity for focused push and uptake. + +## Outro? + - None. diff --git a/doc/wg/core/notes/core-notes-2023-06-02.md b/doc/wg/core/notes/core-notes-2023-06-02.md new file mode 100644 index 0000000000..3383a05bee --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-06-02.md @@ -0,0 +1,166 @@ +# Tock Core Notes 2023-06-02 + +Attending: +- Hudson Ayers +- Brad Campbell +- Alyssa Haroldsen +- Amit Levy +- Pat Pannuto +- Tyler Potyondy +- Leon Schuermann +- Johnathan Van Why + +## Updates + +### TockWorld 6 Tutorial + +- Brad: have a registration link for the Tock tutorial, will link that + from our page on the tutorial. + +### New Memory Model Implemented in Miri + +- Alyssa: New memory model has been integrated into Miri. It's "tree + borrows" instead of stacked borrows. It appears to have been + endorsed by the original author of stacked borrows. + + Still investigating whether it solves our read-only process slice + problem (as what we're doing there is technically unsound). + +- Johnathan: Ralf Jung's latest blog post explicitly calls that out as + allowed now. + +- Amit: High-level overview of the problem? + +- Alyssa: Read-only process slice contains `Cell` internally, and is + primarily used as a reference-only type. You're supposed to only use + it to read process memory that is not supposed to be changed, and + the kernel cannot legally write to. But it shouldn't be undefined + behavior if C were to mutate it, or from within Rust using `unsafe`. + + However, in stacked borrows, converting a `&u8` into a `&Cell` + is considered an invalid operation, as doing so asserts that you + safely have write access to that memory. The idea in our case is + that we don't expose APIs to mutate it externally, so we should be + able to perform this cast. + + This fixes that, as it was not a mutation that caused these issues, + but really the `transmute`. + +- Amit: This is a proposal for a new aliasing model? + +- Alyssa: Always been proposals, there has never been an official Rust + memory model. + + Just for better understanding of what's needed in the language, and + to have a tool which can verify correctness dynamically. + +- Amit: Assuming that this adopted, we wouldn't change Tock code? + +- Alyssa: Big part is the practicality to run Miri. We can use Miri to + verify that we're not violating the memory model. Making sure + everyone understands the same rules. + +- Hudson: There was agreement that there are some set of things which + should be deemed safe in Rust, but were not allowed under the + stacked borrows model. As a result, Miri would throw errors. And + because Miri is the best tool we have to check whether code is + unsound, it was treated as a specification. + +- Leon: This is very exciting. We had a solution for this (using a + slice of units) that would probably have been sound under the + stacked borrows model. However, I was hesitant to push this over the + finish line, as it would have added a lot of new unsafe code into + the process slice infrastructure. Keeing the current code and + waiting for tree borrows to stabilize is much nicer. + +- Alyssa: References are also much more ergonomic. + +### Thread Networking + +- Tyler: Made some progress on Thread networking. Design decisions + with help from Hudson and Pat. Initially was planning on having the + Thread capsule be implemented on top of UDP; Hudson and Pat thought + it may be better to use the UDP mux which already exists and have it + sit on the same layer as UDP itself. + + Working on decrypting Thread messages. Should hopefully have some + PRs ready soon. + +- Amit: Where is this implementation going to exist? Capsules, + userspace? + +- Tyler: Current plan, capsules/extra, along the network + stack. Similar to UDP. + +### (Informal) Networking Working Group + +- Leon: Seems to be growing interest in Networking (6LoWPAN, Ethernet, + Wi-Fi) on Tock. Maybe it would make sense to have an (informal) + networking working group? That could help reason about interfaces & + consolidate efforts. + +- Amit: Sounds like a good idea. Could unify around some interrelated + common goal, such as a gateway device. + + Could also think about single userspace interface vs. different + ones, etc. + +- Leon: Exactly. Reason for bringing this up is a WIP design on how to + handle (allocate, pass around) network packets in the + kernel. Currently still in an early stage with crazy type + signatures, but might be good to tune people in and get feedback. + + In other news, with the work by Alexandru's student (Ioan Cristian), + we now have a physical board, FPGAs and simulators/emulators running + a userspace network stack with an HTTP server. Maybe interesting for + app updates? + +### External Flash on nRF52840DK + +- Brad: For using the nRF52840DK, if we want to use it for the + tutorial, we may want to use the board's external flash to transfer + data from a participant's laptop to the board (as opposed to + compiling it into the kernel). However, the Nordic tools don't seem + to be able to read the flash correctly. Chip is very + complicated. WIP. + +## Tock Matrix Server and Slack Bridge + +- Amit: We want to deploy a Tock home-server on Matrix. This will be + an additional option for people to join the Tock chat community. It + would be bridged to Slack, so folks on Slack won't be affected. + +- Hudson: People would still be able to join Slack as usual, but now + they could also join via Matrix? And we'll announce this everywhere + we currently direct people to Slack? + +- Amit: Correct. + +- Hudson: Are there concerns that we're violating Slack's TOS? + +- Amit: I have no such concern. + +- Hudson: One issue we had with another user using a Matrix + integration is that replies in a thread wouldn't generate a + notification. Maybe that's fixed? + +- Amit: Not a thing anymore. Leon and I use Matrix internally. It's + very seamless for everybody involved. + +- Leon: Full disclosure: DM's don't work, but both parties will + receive a message stating that this is unsupported. + +- Amit: Could potentially solve that with puppetting, but yes. + +- Hudson: Does this mean that there's going to be two instances of + people? Amit and Matrix-Amit? + +- Amit: If somebody has accounts on both, there might be two + accounts. They can be bound together. But in practice, people will + be just using one or the other. + +## Open Pull Requests + +- Brad: Long-standing issue of removing `'static` lifetimes from HILs + for clients. Can't get the `sam4l`'s SPI & I2C drivers to compile + with that. Maybe people can have a look at that? PR #3460. diff --git a/doc/wg/core/notes/core-notes-2023-06-09.md b/doc/wg/core/notes/core-notes-2023-06-09.md new file mode 100644 index 0000000000..2bf3a80ae3 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-06-09.md @@ -0,0 +1,50 @@ +# Tock Core Notes 2023-06-09 + + - Brad Campbell + - Branden Ghena + - Johnathan Van Why + - Alyssa Haroldson + - Pat Pannuto + +# Updates + - Brad: Bors is failing all of my PRs and I need to figure out why + - Brad: TickV support in Tockloader. Can read/write stores on flash or in a local binary file. This is a little tricky on the nRF52840 because the only way to write external flash is through Nordic's poorly documented, closed-source tool. Writing seems to have issues. It's resolvable hopefully. But generally this lets us interact with TickV databases from the host. + - Brad: From Alistair - he demonstrated that his LoRa stuff is working! + +# Agenda +## Outstanding PRs + - Branden: Brad, are any of your PRs holding you back that we haven't reviewed them. + - Brad: Yes, looking. + - Brad: https://github.com/tock/tock/pull/3464 removes the last pub static mut and seems uncontroversial + - Alyssa: Yay + - Brad: https://github.com/tock/tock/pull/3453 is an ADC implementation for the nRF. The ADC HIL was written for the SAM4L, so the major challenge with the nRF is that the range of frequencies you can sample at with the nRF is not as wide as the SAM4L. + - Branden: Without using another timer peripheral, right? (yes) + - Branden: What is the problem with the HIL? + - Brad: Well, if you write an app for the SAM4L and port to the nRF it won't work. + - Branden: It would get an error right? + - Brad: Not currently. That could be a resolution + - Brad: https://github.com/tock/tock/pull/3448 also needs review. Nordic implemented the hardware for doing Bluetooth, so anything the Bluetooth spec didn't require they didn't support. So for our HILs, we can do encryption, but decryption isn't supported. + - Branden: How does that make sense? Doesn't BLE send AES both directions? + - Brad: Not sure. I'd guess it's not required. + - Brad: Also, I don't know of any way to use the cryptocell hardware without the precompiled binary they provide. So this is likely as good as we'll get. + - Brad: https://github.com/tock/tock/pull/3466 turns off SPI on the nRF when it's not in use. Necessary so we can read from external flash as both the kernel and JTAG need to access the same pins. Probably what we wanted anyways. Part of the pesky problem where the SPI HIL has the "init" function, and it's not clear what that needs to do. So now read/write turns on, does the operation, and then turns off. + - Brad: Finally, the rest of the PRs are more minor or really for Alistair. Mostly TicKV related + + +## Tutorial + - Brad: I wanted to hear from Tock people about tutorial planning. What are people feeling? It doesn't seem like there's been lots of excitement from people other than me about doing dev + - Branden: Hitting end of the quarter. Excited about demo pushing some functionality in Tock forward. Happy to work on it starting next week. + - Branden: I am worried the audience might be very small. But I think that's likely okay. Still going to be useful even with a small audience. + - Pat: Similar. Going to be a couple weeks, but interested. + - Branden: One way this could work is for Brad to come up with a task list and put it out there, and then the rest of us can tag on + - Brad: I am a little worried about "last-minute code", since we have a much higher expectation of code reliability these days compared to like five years ago + - Pat: Maybe we should have an earlier deadline for beta testing. We can do a real, "virtual tutorial" with some of our students as attendees. Force use to have the whole thing written in advance and work through any bugs / technical glitches + - Brad: Seems reasonable + - Branden: I'm a little concerned that Amit or Alex might have stronger concerns about exactly what the tutorial covers + - Brad: I think the userspace side is the biggest open question there. The kernel side we've got a few ideas put together + - Brad: I do want the security key demo, but it's hard for me to see what's reasonable and real versus what's fake and doesn't have the same level of interest + - Brad: TLDR: if you have ideas about what to highlight from userspace, that would be helpful + - Pat: One thing we talked about before that's hard to do in a tutorial is the advantage of hardware abstractions. For a tutorial that would require multiple hardware for multiple people. Maybe a few extra boards at the front of the room. I think it's pretty slick if you can just have the same app run on multiple boards + - Brad: Hmm. I wonder if a software environment would work for that? + - Pat: There's that LiteX thing, but I don't know capabilities/limitations there + diff --git a/doc/wg/core/notes/core-notes-2023-06-16.md b/doc/wg/core/notes/core-notes-2023-06-16.md new file mode 100644 index 0000000000..43eb1b6eaa --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-06-16.md @@ -0,0 +1,177 @@ +# Tock Core Notes 2023-06-16 + +Attendees: +- Alexandru Radovici +- Alyssa Haroldsen +- Brad Campbell +- Hudson Ayers +- Johnathan Van Why + +# Updates +* Alyssa: I'm taking another look at the MapCell safety PR (#3325) +* Brad: I have TicKV support in `tockloader`. Also we can now add TBF headers to + compiled binaries, which is useful if you want to add permissions or + something. I also have a HMAC-SHA256 implementation, but that is pending on + potential changes to the Digest HIL, which is why I put it on the agenda. +* Hudson: I guess Leon's update is that he has switched us to merge queues. + Tracked in #3428, and it was #3483 that got this in. It's been tested on a + couple PRs. Interface much nicer than Bors. There are a couple things still + outstanding, such as the Netlify integration not working. If docs break in a + scenario that only happens in the merge queue. Fixing that would require + deploying on pushes to all branches, which may cost more money. It is no + longer a required check. I can't remember any cases where Netlify broke but + normal docs worked. Leon proposed a workaround to build docs using GitHub + Actions rather than Netlify, proposed in a PR, already looked at by Brad and + Johnathan. + +# PR #3482 -- source of XML files (adding headers) +* Johnathan: There was a question about where files added by @valexandru came + from. Want an answer so we can add the correct headers. +* Alexandru: Looks like garbage added by MCU tools. I'll ask Alex. Can probably + remove them. +* Alexandru [later, in chat]: I got an answer from Alex, the redlink tool used + to flash the device uses this file. This can be seen in the Makefile. This is + similar to openocd.cfg. +* Hudson [in chat]: Sounds like we do need those then. Do you know how + the files were originally generated? +* Alexandru [in chat]: I think that mcuexpresso generated them + +# PR #3479 -- Add `set_client()` for `ClientDataHash` and `ClientDataVerify` +* Brad: I'm not sure where this stands. Originally there were different traits + for doing 2 of the 3 operations. There are traits for different client types + for different operation combinations. However, there isn't a way to use that. + I'm trying to add functionality to call `set_client` for each client type. + However, the Rust client types don't work for that -- can't save a supertrait + somewhere when you only need a fine-grained trait. +* Hudson: To make in concrete. You want to save which supertrait where? +* Brad: Say you have something that implements `Digest`. You call + `my_hmac.set_client()` and give it a `DataHashClient`. But there's also a + `set_client` that just takes a `DataClient`. What if you call both and set + both clients? Does that overwrite the other one? Does it not do anything + because you already set the client? Right now it doesn't do anything, we only + use the super traits, not the fine-grained one. +* Hudson: I think the answer is runtime checks. It should always be the case + that all of the clients either have the same object for all 3 or you have 1. + The first time you can call `set_client` for `DataHashClient`, which will set + both, but if there's a later call then a runtime client should refuse to + accept a different client for `DataClient`. If you want to change the client, + you should have to set them all to `None` first. Or do we want to support + having different clients? +* Brad: I think the intent was to have that finer control. +* Hudson: Okay. I was thinking the intention behind the design was to handle + hardware that only supports some operations. +* Alyssa: Won't this require a bunch of extra vtables to be generated? +* Hudson: It'll require 2 more vtables than a single shared client. I don't know + how many instances. +* Alyssa: Minimum of 4 words each. +* Hudson: If you have an application that has multiple clients of this HMAC + hardware, that's a decent amount of complexity, you would expect to have some + size. +* Alyssa: Yeah, I'm thinking if there's a way to sidestep the problem and have a + single trait, and have that trait describe whether a feature is supported or + not. +* Hudson: Do we have an example of somebody setting a different client for these + operations? +* Brad: I think the answer is no. It's really hard to reason about what that + would look like. Maybe `Digest` isn't a good example -- you want to be able to + add data, and verify it's not something else, but when would adding data know + to call verify? The straightforward case is "I need verify, so I specify that + trait, and if it doesn't implement it I get a compile error". I think our path + forward right now is we can do the 3-clients-or-1-client, and by using a + nightly feature +* Alyssa: Keep in mind the size impact. +* Brad: Or we can keep following the use-the-most-super-trait approach. +* Hudson: It's hard because we don't have a use case, but it seems clear it + could be useful. +* Alyssa: Why not just return a not-supported error if it tries to do an + operation it can't. +* Brad: We want compile-time checks. +* Brad: You could imagine something that has two fine-grained traits, where you + only want to use one. +* Hudson: Is ADC and ADCHighSpeed an example? +* Brad: It's not. Maybe that's the problem -- there isn't a good example where + they don't nest in a more usefully-separate way. For Digest, who will have a + use case where you only want to add data. Say we didn't have the data one, + just hash and verify. It may be very feasible to implement both but have a use + case where you don't need both. When you call `set_client` it doesn't do + anything and you don't get a callback, so you have to support the verify + callback. +* Hudson: If you don't want it, then drop the callback. +* Hudson: I have to drop for another meeting, I'll take a look later today. + +# HMAC and SHA virtualizers don't virtualize +* Brad: I was trying to implement `set_client` for that code, but didn't realize + the code doesn't do what it's supposed to and couldn't get the types to work. + Johnathan? +* Johnathan: I haven't really looked at it, kinda avoiding the crypto stuff. The + OT project likely won't use it -- we'll do our own bespoke stuff -- except + maybe to interface with app ID verification. +* Brad: We kinda merged a bunch of code we shouldn't have because it doesn't do + anything, now we're in an awkward spot. + +# MapCell Safety PR (#3325) +* Alyssa: How did you test the size impact? +* Brad: I think Hudson and others ran the same tool as the size CI check. +* Alyssa: Can I run that on my machine? +* Brad: Yes. I'm sure it's a `make ci-`. +* Alyssa: Johnathan, do you know something about it? +* Johnathan: If it runs in GitHub Actions, then you can find the workflow file + and find the command from there. +* Alyssa: I had some ideas for how to decrease the size, mostly by reducing + panics. Could get rid of `try_borrow_mut` to get rid of the reentrance, can + fix soundness and save space. +* Alyssa [conversation resumed after next conversation]: What is the + relationship between MapCell and TakeCell? +* Brad: We had issues using TakeCell with large types, as large copies would + happen. Created MapCell to address that. +* Alyssa: Looking at the source code, the description of MapCell looks like the + opposite of reality. +* Brad: I think we're above my pay grade. I think you talking to Amit would + resolve this in a few minutes. +* Alyssa: If I sent in a PR with some suggested rewordings, would people like + that? I'd love to know the original motivation for this before I do it. +* Brad: I've told you what I know about it. I use `tock-cells` but don't + implement it. It seems that clarification would be great. +* Alyssa: I guess the first step is for me to reproduce the case that caused + MapCell to be created. +* Brad: IIRC, the high-speed multi-buffer ADC is where we noticed issues. + +# TockWorld Tutorial +* Alexandru: I would like to start preparing. Is there a document somewhere for + planning? Any audience you know about? I see you've chosen a Nordic board. +* Brad: There seems to be a limited audience, we need to invite people. We have + buttons and LEDs. To answer your question, no, I have nothing to drop in + that's tested. +* Alexandru: Is the intent to have pre-set-up breadboards/extensions/etc or have + the attendees wire it themselves. +* Brad: One option is to use the bare board without anything. +* Alexandru: The purpose being "this is how you upload Tock, this is how you + sign apps, etc". +* Brad: We can talk about signing apps, we can write capsules, we can have + malicious apps. +* Alexandru: That was my question -- what can we do without additional sensors + attached? +* Brad: Nothing has been decided yet, listing options. I kind of like the idea + of having options so if at attendee wants to connect sensors they can. If + someone is more interested in Rust or security or whatever we can have a + version for that. +* Johnathan: zeroRISC has interest in a tutorial on writing capsules and on + using `libtock-rs` apps. May have representation on this call in the future. +* Alexandru: Should we assume applicants know Rust? +* Brad: Not sure. +* Alexandru: If they've never written a state machine and don't know Rust it'll + become difficult. +* Brad: Yeah, that's true. My impression is that if capsule writing is too hard, + just getting a start at it is fine. I don't think we should have something + where you need to complete part of it to do another part, that could be + problematic. I think it's okay if someone just can't complete it. It's just + one part of it. +* Alexandru: I've seen an interesting approach in the UX. They had several + milestones, but if you miss one, they have the code for the next milestone. +* Brad: That could work, yeah. +* Alexandru: Do you want me to sketch a proposal for next week? +* Brad: I think we should work towards convergence, absolutely. I've been + writing guides in the Tock book. They're not finished but there are pieces. My + approach has been to have the different guides and we can pick and choose + which to use. Then people not at the tutorial could use them in the future. + They're in a branch in the book repository. diff --git a/doc/wg/core/notes/core-notes-2023-06-30.md b/doc/wg/core/notes/core-notes-2023-06-30.md new file mode 100644 index 0000000000..0cedb54d08 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-06-30.md @@ -0,0 +1,111 @@ +# Tock Core Notes 2023-06-30 + +Attendees: +- Branden Ghena +- Alexandru Radovici +- Phil Levis +- Brad Campbell +- Leon Schuermann +- Johnathan Van Why +- Hudson Ayers +- Alyssa Haroldson +- Amit Levy + + +## Updates + * Leon: One of Alexandru's students has been spending a lot of time on an Ethernet driver for an STM chip we support. It's up as a PR. Pretty exciting to see a full-featured chip with working ethernet support. We can run a web server on that code in userspace. The PR is up on a branch, then after it gets merged we'll do a bigger PR from that branch to Tock Master. + * Brad: Leon has also been making Elf2Tab changes + * Leon: Not all of them correct! I spent the last week reading through ELF files, which are horrible. + * Brad: But, there's a new flag for setting the protected region size, which is great. + * Brad: Most of my work this week has been getting the Key-Value stack squared away. + * Hudson: I also saw some PRs to libtock-rs from Leon + * Leon: Those are mostly related to the Elf2Tab Stuff + * Johnathan: FYI, I'll be out next week, in the week after, and then out for three weeks after that. + + +## Key-Value Stack + * https://github.com/tock/tock/pull/3508 + * Brad: Story is that I thought it would be great to use key-value stuff. But the current implementation has some issues. Not virtualized in the kernel (I added that). Assumptions about size of buffers. When you retrieved a value you didn't get the length of the value, just a buffer and no indication of how much of it is valid. Various lower-level details had issues too. + * Brad: Because it's so broken right now, it's really not usable. I'd like to just get it fixed. I'm not sure what the appetite is for fixing it. I'm looking for more comments from Alistair about whether he agrees with the direction or if I'm misunderstanding something major. I'm worried that the PRs will sit forever. + * Hudson: Do the outstanding PRs fix everything? Or is there still stuff broken after them? + * Brad: If all of my PRs were merged, we'd be in a good spot. + * Hudson: So you just need PR review and testing. + * Amit: Alistair has owned this and is presumably using it somewhere. It's unclear from his comments whether your changes are misguided. + * Brad: There are two levels to this. The key-value stack in the Tock kernel, and the TicKV library. TicKV seems to mostly work. I added one change to the data size, which seems unambiguously good. There's a wrapper with an async API as TicKV is synchronous. TicKV seems to return errors in cases when there aren't, seems to be a disagreement about conventions. So we could consider changing that. MOST of these changes though are in the key-value store which is in the kernel. + * Amit: I think if we were all to review, we'd probably say it's fine since we don't have any strong attachment to the implementation. But Alistair might disagree. Has he given any high-level issues with stuff? Essentially: should we just get eyes on stuff and merge it, or do we need a bigger discussion? + * Brad: I don't know. I tried to bring it up, but haven't gotten a clear answer. I does definitely need reviewing. + * Amit: Are there stake-holders other than Alistair? + * Brad: There has been at least one issue posted to Tock about TicKV. But I can't say if other people are really using it. + * Alexandru: We tried to use TicKV, but it was unusable for our purpose. + * Hudson: I've been going through Brad's PRs. Looking at Alistair's comments, I don't see any high-level complaints. So I don't get the feeling that he'll be upset if this goes through or that the changes are a worse design. + * Amit: Okay, I'll review and approve those. I'll also reach out to Alistair about it individually. + * Hudson: I don't think there's any agreement to stability for the key-value interface. So updates seem great. + * Amit: It's plausible that there are multiple use cases with different use cases. We could plausibly even end up with multiple key-value interface designs for different things. Rather than trying to maintain "one true interface", we can have multiple solutions. + * Brad: And because we have HILs, we're pretty good on that front to start with + * Hudson: Is my understanding correct that part of the urgency here is hoping to use the key-value stack for the tutorial? + * Brad: Yes + * Hudson: That seems cool and is a good motivator + * Brad: I did open a tracking issue: https://github.com/tock/tock/issues/3524 + * Brad: It is hard to track all of the changes. I made separate PRs, some of which break if pulled on their own. + * Hudson: Maybe we can start with the smaller stuff and then merge the others as one big PR? + * Hudson: #3489 and #3490 build. #3491 is the first "broken" one. + * Amit: #3508 is the big "all-inclusive" one. And I think in practice it's not _so_ big. We could do this as just one PR. + * Hudson: So I think we look at #3489 and #3490 first, merge those if warranted, then move to #3508 + * Brad: That sounds okay. I just worried about one PR that changes HILs and Kernel and capsule, etc. But it sounds like we've got that all under control + + +## Renaming LeasableBuffer + * https://github.com/tock/tock/issues/3504 + * Brad: We should be using the idea of a LeasableBuffer more. Really solves passing slices and lengths around + * Brad: I think the name, while descriptive, hinders use because it sounds scarier than it is. I think a rename would help us get it off the ground. I thought of "splice", but I'm not married to it + * Hudson: Why "splice"? + * Brad: Riffing off of slice + * Phil: I would argue against "splice" because it means something for Vector already + * Phil: I would split this into two parts. 1) should we use it everywhere, and I think it sounds like maybe yes. 2) does changing the name have an impact, and I'd like to test that. + * Amit: I am in favor of using LeasableBuffer everywhere. I intended to + * Leon: We do still have this pending issue of unsoundness when dealing with DMA peripherals. We can't retain a Rust slice in memory when doing that. So if we did use LeasableBuffer, and made some small change underneath, we could solve soundness there too. As of now, it doesn't do this. But I think if we changed everything to use it, we could make the modification once inside it and fix stuff everywhere. + * Brad: I agree with Amit that it seems so intuitively an improvement. If we had done it two or three years earlier it would have been better. But my other rationale right now is that it's just hard to use due to naming. Typing "leasable" is rough to start with and LeasableMutableBuffer is a big mouthful. + * Phil: I have used LeasableBuffer several times. When I didn't use it was when I wanted to be very explicit about memory and where I am in it. And if LeasableBuffer changed later, that could affect things. I didn't want the automagic + * Amit: Yeah. Hopefully it shouldn't appear as magical, since it's not doing much. It's just allowing you to pass a sub-slice without requiring the code to figure it out and length checks and stuff. So you can treat it just a like a regular slice. + * Phil: Brad's point about the length field is well-said. It's this weird thing. C allocators keep track of how long stuff is: you just have a pointer. But in those cases you're not usually moving the head pointer, just the end. I could buy that whenever a system call passes something in, we should use a LeasableBuffer for continuity. And you likely wouldn't often bother to pull stuff out of it + * Amit: For the network stack, we did this thing where we had a buffer to fill up a network packet, and libraries fill in different header parts in the buffer. So there are multiple slices to be passed around. And passing offsets/lengths could be error-prone. + * Brad: If you _really_ want to manage your memory, you could just use a pointer. + * Phil: Definitely don't want that + * Brad: I agree that LeasableBuffer sounds like it adds uncertainty. Where slice is built in and sounds trustable + * Amit: I think the name implies that there is weird behavior under the hood. Where as &[] sounds like something which will never change and can be trusted. It also affect us implementing it. If it were called "slice", then we'd never consider taking on features. + * Phil: It wasn't the name for me. I had motivation of just wanted to know exactly what my code was gonna do. + * Amit: It is okay not to use it everywhere. And it is easy to interchange between the two. + * Phil: Let's try changing an API: UART or SPI or something, and see it + * Brad: An example exists. I just switched the key-value stack to use LeasableBuffer + * Phil: I'll look at that and see how it feels. I just think the name isn't the most important thing. + * Alyssa: Docs and examples would increase usability too + * Alyssa: I do disagree with the name "splice". It's actively confusing as it's about joining things. What about subslice or something like that? + * Brad: Also, if you look at the PR, there are doc additions too. https://github.com/tock/tock/pull/3519 + + +## Digest Trait + * https://github.com/tock/tock/pull/3479 + * Brad: There's an "update the digest HIL with set_client()" PR. That relates to adding the HMAC. We have to decide on the HIL first. + * Brad: Phil, can you look at this? + * Phil: Looks fine. When we can do proper upcasting, it would be okay. But I approve this + * Brad: This just needs to be merged + + +## Protected Region Size + * https://github.com/tock/tock/pull/3515 + * Brad: Everyone who looks at this part of Tock wonders the same thing. TBF headers have a portion at the beginning of application space that they can't change. So there's a protected_size field that was meant to mean the number of bytes at the start that they couldn't touch. But that got implemented in Elf2Tab as meaning the space between the end of the header and the start of where the app can access. The whole region is already protected. So I think we can just change meaning to meet Elf2Tab. That's what Leon's PR above does. + * Branden: There's something I'm not understanding. These are in flash? Can't they not be modified anyways? + * Leon: This is an important contract about what parts are mutable. So if we put apps into RAM, this would matter. Or some microcontrollers which allow ROM to be modified in some way. + * Amit: It's plausible that an application could write to things if the region isn't write-protected + * Leon: I had issues with applications and noticed that the spec and practice. So given that this field exists, we need to bring it in sync with the documentation + * Branden: Okay, this seems good. Is there any disagreement? (sounds like no). Then we should all take a look at the PR and approve and merge it + * Leon: One more thing about Elf2Tab changes + * https://github.com/tock/elf2tab/pull/70 + * Leon: Part of the changes I pushed was to have the protected region size specified in the ELF file. This feature is often about padding the start of apps for non-position-independent implementations. So we can now embed it in the ELF binary to allow the linker to put the header plus some padding to align the start of an app to a specified address. So in libtock-c we have application start addresses pushed back by a certain number of bytes to try to reserve room for the header, but we rely on Elf2Tab aligning things to 256-byte boundaries. So all of these changes are to make it more sane to have start addresses specified and to have the linker understand them + * Leon: My overall goal is to make non-position-independent code (which we're stuck with right now), more intuitive to use and understand + * Brad: There's a new API between a userspace linker and it's compilation, and Elf2Tab, via a flag + + +## TockWorld planning + * Branden: I will send an email about TockWorld planning to some subset so we can have an extra meeting about it + diff --git a/doc/wg/core/notes/core-notes-2023-07-14.md b/doc/wg/core/notes/core-notes-2023-07-14.md new file mode 100644 index 0000000000..a7e7d1f9e6 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-07-14.md @@ -0,0 +1,68 @@ +# Tock Core Notes 2023-07-14 + +Attendees: +- Branden Ghena +- Amit Levy +- Phil Levis +- Leon Schuermann +- Tyler Potyondy +- Saurav K +- Johnathan Van Why +- Alyssa Haroldson +- Alexandru Radovici +- Hudson Ayers + + +## Updates + * Tyler: I've been working on Thread networking support as a kernel capsule. I've been able to make good progress and can send/receive UDP messages and I have the encryption library working. All of that came together yesterday. Our goal has been to join an OpenThread network and have a Tock device join as a sleepy end-device. We got all of the handshakes working such that the OpenThread board recognized the Tock board as a child. Going to be polishing up that code and then sending a PR soon. + * Phil: That's a great accomplishment. We've had big struggles with IP stuff in the past. + * Amit: When you say you have a UDP packet working, is this before joining a network? I don't remember the Thread stack + * Tyler: Actually the Thread network stack and our prior work in Tock align well. It's 15.4 on the bottom and IPv6 above that and UDP for the payloads. It uses UDP for some of the fields to make requests to other Thread devices. So the UDP messages are used for joining the network and making parent requests. + * Branden: Thread gives devices a TON of different addresses. What have you implemented? + * Tyler: For anyone not familiar, depending on the status of the device, there are different classes of devices and they can be addresses different ways. Currently, it just has a hard-coded IP address based on the MAC address. For the end device that's all I need. There will be a lot of figuring to move from hard-coded values to more generalized things. + + * Leon: I've been looking at the PMP implementation for RISC-V and it looks like we haven't been revoking previously allocated regions since 2020. I made a PR that's a hotfix to solve that. It's kind of a major issue. I browsed the rest of the code and it's kind of a mess right now. I've been working on a rewrite to make the code cleaner, more maintainable, and more efficient. + * Amit: What was the bug you're addressing? + * Leon: The failure mode right now is that if you configure a process, then reconfigure the PMP to give no access to memory in userspace, the process keeps running just fine. The PR I put out yesterday does resolve this and invalidates regions as necessary. Generally, the different modes are hard to reason about now, which is why I'm doing a rewrite. + + * Alyssa: PR for fixing MapCell safety. It would be good to get more eyes on it. + * Amit: Yes! That's on my to-do list. Although others should look as well. + * Hudson: I've been working my way through the PR backlog I've got and will be there soon + + +## Tock Cells + * Alyssa: I was hoping to re-architect Tock Cells so there's one module that re-exports everything and makes the individual parts private. + * Amit: I'm happy with whatever is common. + * Alyssa: The common choice is to have a flat API rather than having the API mirror the file structure. With modules for structure. + * Amit: I have no objections. + * Leon: We should be careful with things on crates.io, but I think we're not on crates.io for Tock Cells. Since we didn't publish this, I think it's fine + * Alyssa: We have made some breaking changes already. + * Hudson: Yeah, few guarantees for internal Tock kernel APIs. After the next set of changes to MapCell and everything, it might make sense to version and release Tock Cells. Not urgent though. + * Alyssa: MapCell is particularly nice. I haven't seen other things that do what it does as well as it does + + +## TockWorld Planning + * Amit: To catch others up, on the tutorial front, we're planning to discuss and meet on Monday. Should have more updates then. One thing to talk about is what we want to focus on for other days. + * Leon: I did like the split sessions based on specific issues. Then presented results to the entire group. + * Alyssa: FYI, I won't be able to make it this year + * Amit: For smaller conversations, if we set up a remote option for you, would that make sense? + * Alyssa: Yes. + * Hudson: Looking at prior agenda: https://tockos.org/tockworld5/agenda Anyone can throw out ideas for talks they want. Definitely want some room for specific talks and some for group breakouts. + * Amit: Agreed on high-level structure. I know Leon has some thoughts on OpenTitan work. Maybe Alex's student on networking stuff. + * Amit: For specific breakouts: community management/engagement (NSF POSE funding), networking (multiple fronts in work here Thread from Tyler, Ethernet from Leon and Johnny), testing smaller/larger scale (integration tests and unit tests), certification and cryptography (external crypto libraries as part of this) + * Branden: Lets be sure to have lots of breakout sessions, separating community from networking for instance so people can be involved in both. + * Leon: Some could be large enough that the whole group should be involved + * Hudson: Breakouts also depend on total attendance, which we're not sure of and Brad is out today. It would be good to have a firm headcount in advance + * Poll of attendance includes: Amit, Branden, Leon, Tyler, Pat, Brad, Alex + 3 students, Hudson, maybe Phil. Likely Arun and another + * Phil: Is anyone from Google or Ti50 coming if Johnathan and Alyssa are out? + * Alyssa: Unsure. I'll follow up + * Amit: Back to sessions: outreach, structure/organization, and funding + * Branden: I think two, or _maybe_ three, topics max per breakout given the number of people + * Leon: I want to give a talk on what I've done in the last few weeks. No firm commitments from Arun or other about a talk, although I'm nudging + * Hudson: This does seem like a reasonable approach so far. One thing we didn't do last time, but I'd be interested in is thinking about hardware for Tock and what people use primarily. There's a pretty fractured hardware split for main developers these days + * Branden: From last time, I do want a "state of Tock" discussion again. Also thinking about where to spend effort for future development has been good + * Hudson: Also definitely thinking about where the spend money if we have it, for sure + * Leon: This may be reaching into Monday's discussion, but a concern I have is that there isn't a ton of time to get a tutorial done at this point. I'm curious whether it would also make sense to do more of a hackathon or something instead. Not clear what level of interactivity we're expecting there. + * Amit: I think when we discussed this originally, our hope was to have a specific project to walk people through. The hope was to do an HOTP device. We should finalize this on Monday. I think we're actually quite close to having that working. + + diff --git a/doc/wg/core/notes/core-notes-2023-07-21.md b/doc/wg/core/notes/core-notes-2023-07-21.md new file mode 100644 index 0000000000..781bcd6ebc --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-07-21.md @@ -0,0 +1,96 @@ +# Tock Core Notes 2023-07-21 + +## Attendees + - Amit Levy + - Leon Schuermann + - Tyler Potyondy + - Branden Ghena + - Philip Levis + - Vish + - Hudson Ayers + - Brad Campbell + - Alyssa Haroldson + - Pat Pannuto + - Alexandru Radovici + +## Updates + - [n/a, see first agenda item...] + +## TockWorld Planning + - Agenda + - Draft here: https://tockworld-2023--tockosorg.netlify.app/tockworld6/agenda + - Branden: First, any unconfirmed things we need to confirm? + - Pat: Mostly set, only non-core open question is Alistair, who Amit is talking to + - Hudson: Add stable rust check-in; I'll present + - Alyssa: Is there anything on testing? + - Pat: Yes, it's part of the Focus Areas from TockWorld5, which has a "check-in" talk scheduled + - Leon: Do we need to check-in on connectivity given the networking session? + - Pat: It can be a short, one-slide thing at that point + - Alexandru: What are you looking for from the OxidOS talk + - Branden: Floor is totally yours; assume some of us don't know what you're doing + - Hudson: In the past, corporate talks have covered, "here's what we're using Tock for; what we're using it on; why we chose Tock; what we need from Tock; here's what we think might be different from other users' priorities" — just a template / ideas, please use the time how you would like + - Tutorials + - Branden: Earlier this week, TW6-WG met and Leon/Amit showed off HOTP example. Demoed on nrf52840dk, though should be board-independent + - Branden: I've broken that app into four milestones of 'how you would build this as an app'. Talking with Amit, it's not about how to build HOTP from scratch, but how to add things to make more featureful, e.g. multiple keys, save keys to flash, etc. + - Branden: Pillar two is a capsule with an encryption oracle holding secret + - Branden: Pillar three is from Brad, looking at adding app-signing s.t. syscall filtering allows selective permissions to apps for these secret manipulation APIs + - Alexandru: What ergonomics w.r.t. app ids and matching signing etc are we planning? + - Brad: Using default checker Phil wrote, which by default uses short IDs + - Brad: Though the API exposed winnows to an Option of Some/None whether app has credentials + - Alexandru: So boils down to if app has credentials, can do; if not, can't? + - Brad: Correct + - Alexandru: That seems simple enough + - Branden: Leon/Amit, what's the status of the middle bit? + - Leon: Going to get that sorted this weekend. Keeping it small/simple, emphasis on how to deal with async APIs in Tock, how HILs work, how user/kernel boundary works; probably a simple fixed encryption task + - Leon: Undecided whether should be template to fill in like userspace or start from scratch? + - Branden: Kinda depends how much stuff there is; either way, having milestones that folks can jump to is valuable + - Leon: Yeah, I plan to have different states of the capsule ready and available to download + - Leon: I don't foresee any issues; should be a simple component given what we have + - Leon: I do think writing a capsule from scratch, especially allow buffers and command handling, is valuable to understanding how Tock works + - Alexandru: Depending on background, coming in blank is really hard. Would encourage some kind of template + - Leon: Yeah, wouldn't give them an empty downstream tock or something. Probably a rough template with skeleton for AppState, etc + - Branden: All of these questions are unfortunately a bit hard to answer without seeing the final goal + - Leon: Not sure how much progress Amit has made. Even if it's currently nil, I think it should only take half a day to get this written + - Alexandru: I'm happy to take a pass adding comments and breaking it into milestones / instructions + - Branden: And that includes writing tutorial instructions / steps + - Alexandru: As long as a native English speaker promises to proofread :) + - Branden: I'm sort of treating this tutorial session on Wednesday as a trial run, and it may be a bit rough, but most if not all of the audience are our students and amenable folks + - Brad: Yeah, that sounds right + - Branden: Yeah, so this can be the foundation for more complete tutorial sessions in the future + - Alyssa: Can I request that the website be not-a-branch? The optics of a blank agenda on the public-facing page at this phase are not great + - Pat: Will merge after call + - Hudson: Are you comfortable sprinkling in stuff you brought up into your talks or need dedicated time? + - Alyssa: I think I can work things in; working list of thoughts: ufmt in the kernel, Adding blocking command syscall, Implementing zerocopy APIs, Results of a small async runtime experiment, Improving MMIO soundness + - Branden: Yeah, overall I think this should work great, and we can be flexible in timing as well in person + - Alyssa: One preview, insight for making ufmt smaller is to make it more like printf (i.e. template with parameters vs directives) + - Hudson: Which would make it more different from what it is now + - Alyssa: Very different implementation under the hood, but ideally the same interface + - Alyssa: Much more efficient to one work on string with placeholders than series of fragments + - Phil: Re blocking commands—keeping discussion focused on blocking command is good vs blocked syscalls etc; but make sure that the solution space is covered; e.g. when you perform a blocking command, can callbacks be issued? i.e. callbacks only in response to a yield + - Alyssa: Yeah blocking commands and lumped syscalls seem orthogonal to me + - Hudson: Agree, keep these separate to avoid the more contention grouped syscall + - Alyssa: Exactly, don't block blocked commands on group syscalls + - Alexandru: Having some challenges with schedulers, in particular, interrupt awareness, e.g. for differences in timing sensitivity between UART and Ethernet has been an issue + - Alyssa: Clarify this? + - Alexandru: Scheduler should understand which interrupts will be handled by the kernel and which will take longer + - Alexandru: Also need to talk about dedicated or slotted Upcalls + - Pat: I will parse these notes and update the agenda + - Pat: Also, clicked merge, the draft agenda is now live + - Brad: Would like to do an elf2tab release today-ish + - Leon: I've been using tip, and have no issues + - Branden: Definitely required to be live by tutorial time + - Leon: Does this also require a newer release of tockloader? + - Brad: Yeah, it does + - Branden: And we needed and update to tockloader for the tutorial as well anyway + - Brad: Ultimately, this is a good thing. This is a key step towards support for credentials, which we should be using more often anyways + +## Other / Misc? + - Alyssa: StaticRef is soon to be NonNull + - Alyssa: Trying to do more upstream-first, but some things blocked on StaticRef, would be nice to get that in + - Alyssa: Anything blocking the new MapCell? + - Leon: Just letting the last-call linger for a moment + - Alyssa: Would help to have more clarity on whether I'm blocked on something / needed or just letting time go before merging + - Brad: That's really what the last-call tag is; we think this is good to go but want to give folks a window for any final comment + - Alyssa: Any guidance on when last-call is added + - Leon/Brad: Nothing formal; usually only used/needed on low-level/risky PRs or those with lots of discussion; usually applied after a small-N reviews + - Branden: Reminder, meeting next week is cancelled in favor of seeing y'all in person! diff --git a/doc/wg/core/notes/core-notes-2023-08-04.md b/doc/wg/core/notes/core-notes-2023-08-04.md new file mode 100644 index 0000000000..ccbf560ff0 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-08-04.md @@ -0,0 +1,98 @@ +# Tock Core Notes 2023-08-04 + +## Attendees + * Branden Ghena + * Hudson Ayers + * Phil Levis + * Leon Schuermann + * Chris Frantz + * Johnathan Van Why + * Brad Campbell + * Alexandru Radovici + * Pat Pannuto + * Alyssa Haroldson + + +## Updates + * Brad: After tutorial at Tockworld, I went through the Tock book to clean stuff up and remove redundancies. Also, I added more setup documentation so we don't need to maintain separate kernel infrastructure to support the tutorial. Live in the book now. + * Leon: I've been working on porting the tutorial userspace to libtock-rs, working on the HMAC driver now. I am envisioning a minimal example first without app-flash and without USB HID. Maybe we'll add TicKV support at some point. + * Brad: That's great. I was curious if we should make this a separate app from the libtock-rs repo to see what it looks like to do a totally separate app. + * Leon: I do think it makes sense as a separate app in the tutorial. + * Brad: I meant as a model for an out-of-tree libtock-rs app + * Leon: I'll think about that. The context is that my company wants to try the demo, but without libtock-c + * Branden: Wanted to announce the Network Working Group. https://github.com/tock/tock/pull/3578 It's going to have calls every other week to talk about network interfaces in the kernel and to userspace and support for network stacks, such as buffer management. Others are welcome to join if interested. We discussed at Tockworld and had support from the core team. Phil and Johnathan weren't there, do you support creation of the working group (Yes from both) + * Chris: I'm working on updating open-titan support to bring support up to the tape-out version. https://github.com/tock/tock/pull/3586 is the initial work on that. There will be a few separate PRs to handle everything. Some constants for base addresses and register constants. We'll take on the drivers one-at-a-time moving from hand-crafted registers to auto-generated ones. We'll also complete the drivers for the features that the chip supports. Current drivers are an incomplete start, but a little work will bring them up to compliance with the hardware and the capabilities in the HILs. + +## Yield-for RFC + * Pat: There's an RFC for yield-for: https://github.com/tock/tock/pull/3577 Thinking about moving the yield-for logic into the kernel simplifies correctness in userspace. You don't have to deal with the concept of multiple parallel upcalls that could occur. For example, `printf` in a callback wouldn't have to worry about reentrancy. We'll always get back to exactly where we are, if we want to. Also helps efficiency for userland syscall stuff, potentially. + * Phil: This is interesting. I'm curious what the similarity is here with a blocking command. While you're blocked here is that timers can't expire. That's a concern. + * Pat: I view it as an opt-in to synchronous behavior. + * Phil: But we have multiple ways of doing things. So we have to be clear which way to do things when. When do you want to do which? + * Pat: We don't have blocking-command as a concept upstream. Even in the batched system calls, you could skip some of that. + * Phil: From an idea this sounds great. From a sense of what are the tradeoffs. + * Branden: To confirm, this is the same blocking-command that Alyssa has been talking about (Yes) + * Hudson: There is some discussion of this on the PR that we could push. Amit is interested too, and we could have a longer discussion next week. + * Alyssa: A question here is whether users would need to implement command and blocking command or if there's some restriction. I think I have a design where anything can use either. + * Pat: I suspect that's similar to the implementation of yield-for internally + * Alyssa: You do have to specify in userspace which number you're waiting for. If blocking commands are implemented by the capsule, the kernel could ask which command and which subscribe it's waiting for. So it could go in the kernel rather than userspace. + * Pat: Yes. I will go through the writeup for this RFC and add more details. I think I missed some of your concerns. If there are multiple possible upcalls, the design now is that you just wait on one. But if you wait for data and there's an error upcall, you're in trouble + * Phil: A common way there is return values to determine what happens. There are cases where there are one-to-one mappings between sync and async interfaces in other OSes, but there are cases where it doesn't make sense to have both. + * Alyssa: If yield-for lands first, blocking command could be built on top of it. + * Pat: Yes, that was the design. + * Alyssa: I think there should be discussion about the name to make sure it's clear too. + * Pat: Sure. + +## Libtock-rs Updates + * Brad: Two classes of changes. I was trying to get the examples working and ran into some bugs I made PRs for. Also as part of the key-value overhaul, I thought it would be a good example driver to implement. I did that, although we're updating that in the kernel, so I don't want to pull that right away. + * Brad: Leon also opened a tracking issue with missing drivers that libtock-c supports but libtock-rs doesn't. https://github.com/tock/libtock-rs/issues/489 The idea here is that each core team member would implement at least one driver as an example and to get used to libtock-rs + * Brad: The second part is the build system. Right now you pick boards when you compile, but it would be nice to have multiple fixed-address versions compiled and have Tockloader pick which one it wants. So the update in https://github.com/tock/libtock-rs/pull/482 builds for a bunch of possible addresses and lets Tockloader choose. + * Hudson: The approach to build for multiple locations, I'm interested to hear Johnathan's thoughts on how it would interact with the existing design. + * Johnathan: I was going back-and-forth with Brad in the comments. Everything has been changing on that PR pretty quickly. I think Brad came up with a solution last night that would make me happy. It's a really hard problem. Cargo doesn't really understand the concept of building binaries multiple times and then combining them into one TAB. Cargo doesn't get that, and the only other system is `make`. So, it's not easy to expose without extra build infrastructure. + * Alyssa: We do have some Bazel in our downstream working for Tock. It is really good for this + * Brad: Let me give you a quick overview of where things stand on my end. Johnathan's idea of having a build-focused crate that handles the linker scripts and is a build dependency. So if you want to choose board based on build variable, you just import that crate. It should handle the linker script nonsense. The second step is make, which is indeed bad at being externally usable. I did some hacking and made a way to define targets for flash-ram-arch-tuples. So out-of-tree boards would have to copy something, but I think it might really be minimal. + * Johnathan: So the stuff with the fixed-target function could be in a separate makefile and apps could include/call it if they want. + * Brad: Yes. They could also include other addresses with the same function and it should "just work". + * Johnathan: That might be a good approach. + * Johnathan: One extra thing, I'm seeing a lot of stylistic things that I never wrote down that I disagree with from your changes. No way for you to know because it's only in my head. I'll make those comments on the PR after we agree on a design. + * Hudson: For issue #489, we talked about at Tockworld that people should each sign up for one driver to get a feel for how things work. That would be good for everyone to get a feel for how stuff works. + * Brad: I wanted to mention that it is still a little challenging to get libtock-rs apps that will work on a particular board. There is some other machinery in progress that we should talk about on the repo. The main challenge is that if Tockloader is going to load an app on your behalf, it's pretty good about choosing the Flash address, but doesn't have a great way to choose the RAM address. So you can get apps that are at the right Flash address, but not the right RAM address. So you have to watch out for that. + * Leon: This is identical in libtock-c for RISC-V. We might be able to have a start-ram address in the kernel, which we could know at compile-time if we violate. Right now we just start app RAM right after the kernel RAM, so it moves. By having it be fixed instead, we make it must more likely that apps will run. + * Brad: That will work for RISC-V on libtock-c, but it won't work or libtock-rs. Because different platforms might put their RAM at totally different addresses. + * Leon: But we do have linker scripts per-platform, so we could encode it there. + * Brad: Yes, that would work. + * Leon: It's not a full fix. Multiple apps at once still has issues. But it would at least make a single app work. + * Brad: I do think I have a solution for all of this. The idea would be to encode the memory address in the kernel binary in an attributes section. That way Tockloader can discover where app memory is at, which would let Tockloader be aware and adapt. https://github.com/tock/tock/pull/3588 + * Leon: The compile chain would still need to pick addresses correctly when compiling though. (Yes) + * Branden: A question for Johnathan. I think there has been confusion about the limitation of libtock-rs to wait on multiple things. Can you explain and clarify. + * Johnathan: You can do all of the asynchronous stuff you want, 20 things at once, as long as it's all in one function. You can't have multiple threads running at once: blinking a light in the background while running cryptography. There isn't a limit on how many callbacks you can do, but the subscribe/allow are tied to a lifetime that's on the stack. It's possible you could do that. But that's not the design right now. There's not a hard limit, but all of your asynchronous stuff is supposed to be local or lifetimes will mess up + * Leon: Is that similar in semantics to a "select" system call? + * Johnathan: I _think_ it's weaker than select. With something like select you can build a library that allows multiple things to run and build stuff on top of it. + * Leon: So you'd have to wait for all callbacks to return? + * Johnathan: Not really. It's just a lifetimes issue. + * Leon: So this does sound reasonably flexible though. You can have an event loop with multiple callback and switch between them. + * Johnathan: Yes, all of those callbacks need a lifetime though. And I do have like four or five emails that have been sitting in my inbox with ways we could incorporate futures without a huge cost. It's a HUGE API overhaul though. It would be great if they pan out though + * Alyssa: I am planning on doing some work on that and will let you know. + * Branden: To wrap up, this is really a compile-time limitation, so it's not a trap someone is going to fall into. Instead it'll be obvious that it won't work, or at least confusing because someone can't figure out how to do it with lifetime requirements. + * Johnathan: That's right. It'll be a failure at compile time. The thing that could be sharp is that "share" is used to allow, and it does unallow when it returns, so that could catch you by surprise + * Branden: Great. Good to hear it's not going to surprise us more generally + + +## Open Titan Upstreaming + * Chris: We do want upstream code that can run on open-titan, run some apps, run some tests. Downstream, we'll have our own definitions for things that are actual products. So the upstream won't match the products exactly, but having both of them serves as a good tutorial for new users of the Open Titan stuff. They can look at both as references. We intend to assemble a reference OS in the Open Titan repo with a userspace that demonstrates the basic features of the chip. The stuff in the repo will put together a kernel and userspace that can run on the chip. + * Brad: That sounds great! + * Chris: I've done some work on starting this in the opentitan repo. I think Leon has done some work advancing it from there. + * Leon: I do want to give overview of high-level plans if we have time. (we have time) + * Leon: I am still new in this area, but here are what I think the ideas are. We've designed Tock to hopefully be usable by downstream, but never really validated. Right now, things are well-aligned. Trying to rely on the upstream code base for Tock, relying on the core capsule crates. But a downstream Board crate, and _maybe_ a downstream chip crate. Chris has a proposal that integrates this and pulls crates from upstream Tock. + * Leon: Something to talk about next week on the OpenTitan WG call is that the versions of Tock and OpenTitan are out of sync. Now that OpenTitan has a taped-out chip, that should be stable, so how do we go about updating and synchronizing. + * Leon: There's a question about which regression tests we should/shouldn't be passing. There's also how do we run. You'll need to be on a downstream chip. So how does Tock make sure it doesn't break things? It needs to run on a downstream environment for tests. We could follow the downstream instructions, but that's weird for Tock to want to do. + * Chris: I think now that OpenTitan has a stable chip, it's probably easier to have something in upstream Tock that will be reasonably close to always working on that stable underlying hardware/software support. I do agree with Leon that the current set of tests is hard for me to say what is/isn't a regression as those tests haven't tracked the project. The tests are out of date, and I'm honestly pleased that many pass as-is. We will have to think about more comprehensive tests. But at least we now have a stable baseline for hardware. + * Pat: So should Tock just set up our CI with the taped-out version of the chip? Or should we have CI calling into your ecosystem to test stuff? + * Chris: From our perspective, we'll have tests in our codebase that use our test infrastructure to exercise things we care about from Tock. Leon gave me a quick tutorial of the Tock CI environment and driving test boards. And that's what we have too. Unfortunately, we're based around Bazel, we can't build the tests with Cargo. So you can't even reference them from Cargo. + * Pat: Yes. Something we've talked about is that there will be proprietary things that could be called from Tock and should be aware if we break something for that user. It could even be a non-blocking test to just make us aware that we would be breaking something for someone else. + * Leon: Another takeaway is that if the tests are ultimately going to use some third-party build tools, is it worth hanging on to a reverse-engineered README which has some bespoke workflow for building stuff, or is it better to just call out to external downstream docs to say her's how they do it. + * Brad: I think that we should not duplicate. The setup seems like it should be outsourced. However, Tock likes to be as user-friendly as possible. So we should add some tips for how to get stuff working. We should make sure not to remove stuff from the README if it's not existing somewhere else. + * Leon: Yes. I think the motivation was to bring the README stuff back once stuff works again. Perhaps the next steps are just building Tock in the Bazel downstream chain, then Tock can reference that. + * Brad: One more question is if this should be a giant PR or piecemeal. We like it where code works at every PR instead of half-working states. But that might be too much work. + * Chris: I'm close on the initial PR to having it working. That first PR is an atomic change that brings the upstream Tock in alignment. Then the rest should be upgrades that can be down piecemeal afterwards without breaking anything. + + diff --git a/doc/wg/core/notes/core-notes-2023-08-11.md b/doc/wg/core/notes/core-notes-2023-08-11.md new file mode 100644 index 0000000000..1acddca072 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-08-11.md @@ -0,0 +1,69 @@ +# Tock Core Notes 2023-08-11 + +Attendees: + - Branden Ghena + - Hudson Ayers + - Alyssa Haroldsen + - Johnathan Van Why + - Amit Levy + - Pat Pannuto + - Chris Frantz + - Alexandru Radovici + - Leon Schuermann + - Tyler Potyondy + - Alistair Francis + +## Updates +* Tyler: we had some issues with Thread, OpenThread not acknowledging things due to a lack of acks. Nordic boards don't have HW support for acks. Doing a software implementation. +* Leon: first networking WG meeting, discussed a bunch of things. Deliberately left the WG open for anyone to join. Mostly started with bureaucracy, PR to formally establish WG, talked about status of existing subsystems. + +## Agenda Items +* Hudson: last week we decided to defer discussing OpenTitan until Alistair could make the meeting, he's here. +* Leon: This can be short. We had a conversation about how we want to bring OpenTitan support up to date, as silicon goes to tapeout. We are interested in having a version of Tock run on the latest tapeout chip. We will support our board for the current hardware. Thank you, Chris, for all of your work. +* Leon: With respect to porting efforts, documentation efforts, using the downstream OT environment, e.g., on silicon, verilator, or the chip, the key takeaway is we don't want to delete anything in the upstream repository that isn't anywhere else. But once the downstream has instructions on Tock, we want the upstream documentation to point to the OpenTitan documentation. +* Leon: We also had a discussion on how to keep upstream Tock up to date with the downstream code. +* Leon: We didn't decide on anything, it was just a high-level discussion keep Alistair's concerns in mind. +* Alistair: This is pretty high level, sounds fine. It would be nice to keep the instructions in Tock on how to do this. It's nice to have it within Tock, run make, rather than go here. +* Hudson: It does sound like we don't have complete instructions. +* Leon: OT is in the process of using Tock as its own OS. A series of PRs are trying to get a version of upstream Tock, rather than pointing at a commit, to be built in the downstream toolchain. The issue with having the information in Tock is it's dependent on a downstream repository. +* Leon: The goal is to have OT using its downstream repository use its own system while using CI against upstream, to make sure that it always works. +* Chris: Two conflicting needs here. Tock wants to depend on a stable version of OT. You don't want every commit in OT to possibly move registers, change peripherals. Earl Grey isn't moving anymore. OT thinks of itself as a model repo for the project. It has hardware, software, test code, all of the code for the testing environments. We want a reference OS to be in that repository as well. The kernel as it should be constructed for that reference OS. Linking in the apps that have the base functionality we think is necessary. +* Chris: The kernel and apps want to refer to a fixed version of Tock. That's one of the needs. We also want to take advantage of all of the testing infrastructure, both for kernel driver and userspace things, and dispatching those things to our test environment. This lets us qualify a fixed version of Tock that we can verify for a project. We don't want Tock to drift and break something without our knowing about it. We want to execute all of the tests, don't use the fixed version, test against the HEAD. This is what we're trying to achieve. OT wants to depend on a fixed version of Tock. Tock wants to depend on a fixed version of the hardware. +* Phil: That's a great way to put it -- each side wants the other to be fixed. So it suggests each side should do that, it's not that Tock should refer to the OT documentation. +* Hudson: It's great that while OT is working against a fixed release, it's testing against HEAD. It sounds like you'd prefer Tock does the same thing, so working against a fixed release but testing against the HEAD, so we can detect problems? +* Chris: That would be great, but I don't know if it'll work. There are resources Tock depends on in Earl Grey. If we discover bugs in that chip that we want to fix for the production version of the silicon, when we go to silicon for production we are going to take those improvements in the master branch and tape that out. Nothing in master with respect to Earl Grey is planned to change, but there will be at least one IP might change a little, we will disable something we don't have a use case for. We don't want to contend with what's changing there. +* Hudson: Just wanted to make sure we don't remove docs, then don't have instructions. +* Phil: How do we handle CI failing against HEAD in the other repository? Whose fault is it? That seems like a long debug cycle. +* Leon: We will be able to trigger a downstream test in response to an upstream commit, and make it a non-required check. E.g., if something +breaks and it's not upstream's fault. +* Phil: I'm worried about the debugging cycles. +* Hudson: This is getting a little into the weeds, it should be pushed to the OT group. +* Phil: Agreed. + +* Hudson: How about the pull request review document? +* Brad: Nothing to talk about, let's just read it. + +* Hudson: Yield for? +* Brad: This is complicated and difficult unless we are talking about precisely the same version. We really need to stick to specific names and terminology or it's too confusing. These are just codenames. +* Phil: Unsheathe your dagger definitions! (James Joyce) + +* Brad: There's also fixed offset apps. This makes libtock-rs a lot easier to use, and RISC-V on libtock-c. +* Hudson: High-level, what's the summary? +* Brad: Major change with libtock-c and libtock-rs is that libtock-c is for CortexM and so works for a lot of platforms. libtock-rs has very different addresses. The loader has to choose a useful RAM address. Don't have a way for the loader to know "what's a valid RAM address". Now the kernel can tell it "these are the addresses I'm going to use for app RAM." +* Hudson: And you are on-board, Johnathan? +* Johnathan: Yes. +* Leon: What's the status of the elf2tab linker madness? +* Brad: I've been using it, and I'm pretty happy with it. There seems to be a behavior in the linker, where it can actually put a segment before flash, it'll move it, relating to alignment. +* Hudson: This isn't just a bug in our linker scripts? +* Leon: Pretty sure it's not. +* Brad: We can tell the linker to use a different alignment, that helps, but doesn't solve the problem. +* Leon: Let me test this PR with one of the broken ELFs. + +* Hudson: For yield-for, I sent you a bunch of messages on Slack, Alyssa. +* Alyssa: I'll take a look. +* Hudson: I was trying to get libtock-rs to looking a bit closer to what Ti50 does, e.g. uformat. Looking at different implementations of yield-wait-for and how they affect code size. I had a couple of questions. +* Alyssa: Some syscalls would benefit from centralizing subscribe ID, others wouldn't. I'll answer your questions on your branch. +* Alyssa: The thing that comes to mind is our storage reading, that's always inlined. That's repeated in a bunch of places. + + + diff --git a/doc/wg/core/notes/core-notes-2023-08-18.md b/doc/wg/core/notes/core-notes-2023-08-18.md new file mode 100644 index 0000000000..f488fd09dc --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-08-18.md @@ -0,0 +1,80 @@ +# Tock Core Notes 2023-08-18 + +## Attendees +- Amit Levy +- Brad Campbell +- Alyssa Haroldson +- Alistair Francis +- Chris Frantz +- Leon Schuermann +- Pat Pannuto +- Johnathan Van Why +- Hudson Ayers + +## Updates +- Amit: Ethernet over USB implemented and working on nRF52. +- Brad: Expanding clippy lint allows. Clarify which clippy lints we need to + allow for clippy to pass. + - Will be PRs addressing some. Feedback will be helpful. + - Chris: Which group is not a good fit? + - "restriction" +- Pat: Tyler opened PR for software acks for nRF52. Next up is support for Tock + as sleepy end device for Thread. + - Hudson: will send imix, but + + +## PMP Redesign +- https://github.com/tock/tock/pull/3597 +- Two implementations: PMP and ePMP (OT). Some issues and duplications. +- Goal: separate implementation for pmp logic from specific impl of tock process + regions. + - Simple PMP implementation + - OT/earlgrey implementation +- Alyssa: should we get a review from Vadim? A: yes. +- Current implementation not matching hardware as riscv has evolved. Boot stages + can affect how the PMP is configured. +- Downside + - No longer a single implementation that can be used on any chip. + - On complex chips, have to implement bottom half of ePMP yourself. + - ePMP moved to chip folder, must be duplicated for each custom chip. + - But we currently only have 1 (OT) which is very unique + - There may be a generic ePMP implementation for future chips. ePMP is + standard. + - earlgrey version of ePMP: board or chip? all earlgrey will use the same + bootrom that sets up regions. +- New modular design sets us up to support ePMP in the future. +- Amit: need to move low level conversation to issue or separate discussion. +- Brad: let's implement ePMP when we have an ePMP chip. This seems like good + progress. +- OT essentially customizes the ePMP by setting up locked regions. + - How much does this configuration complicate the implementation? + - Difficult to do and ensure correctness. +- Open tracking issue. +- Move to OT call. + + +## Maintaining 3.0 Changes +- https://github.com/tock/tock/pull/3622 +- What is our version policy? +- How do we maintain changes for major version changes? +- Action: read policy. + + +## Command 0 +- https://github.com/tock/tock/issues/3375 +- TRD104 specifies that command 0 returns Success if driver exists. A couple + stabilized drivers return Success(u32) with the number of available resources. +- What to do? + - Change to match TRD104: breaking change. + - Makes the simple check clear and easy to do. + - Can implement the check in the kernel. + - Update TRD104. + - Makes sense to get the same functionality plus more. + - Seems more elegant. +- Advantage to changing TRD104: no code needs to change. Stabilized drivers stay + the same. +- Brad: what is the motivation to change TRD104? Are there new use cases? +- TRD104 written after drivers. +- Command 0 is a safe check to determine if a driver is present. + + diff --git a/doc/wg/core/notes/core-notes-2023-09-08.md b/doc/wg/core/notes/core-notes-2023-09-08.md new file mode 100644 index 0000000000..4e26c04b63 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-09-08.md @@ -0,0 +1,208 @@ +# Tock Core Notes 2023-09-08 + +## Attendees +- Alexandru Radovici +- Amit Levy +- Brad Campbell +- Branden Ghena +- Hudson Ayers +- Johnathan Van Why +- Leon Schuermann +- Pat Pannuto +- Philip Levis +- Tyler Potyondy + +## Merging PRs +- Amit: After a PR has been approved, who clicks the merge button? +- Brad: Has something changed with merge queues relative to Bors that changes + the answer? +- Amit: I've noticed some PRs sit for a while without getting merged. +- Branden: It's hard to tell when we're done approving and should merge a PR. +- Brad: I'm pretty aggressive on merging PRs. I like to either merge, or tag it + with something orange or yellow, meaning we're waiting on something. +- Leon: When I'm unsure if a PR is ready to merge, I think last-call is a good + way to indicate I'm going to merge it soon. +- Amit: Two core team approvals has been a rule. I agree that something like + last-call is a good "oh I'm not really sure". If there's two approvals and + last-call is over 2 days old, just merge it. + +## Updates +- Amit: The NSF funding finally came through. We're going to start hiring + people. +- *Everyone*: hooray! +- Brad: I spent a few minutes trying to update the HOTP app from the tutorial to + use our KV store. If we can get that added then that fixes the credential + problem and means we can directly port it to `libtock-rs`. +- Amit: And it would be a good litmus test for the KV store. +- Leon: I have ported a minimal version of that to `libtock-rs`. There were some + issues trying to read from console. I didn't want to break the USB interface, + so reading the console requires a per-character read, which causes it to miss + characters. If we can sort this out sometime, I think we could reasonably push + a minimal example somewhere. +- Branden: Networking WG update: Spoke about buffer management, thinking about + questions about goals and what the problems are. Discussed how buffers are + used by hardware. Discussions still ongoing. Recurring topic is patterns that + are sometimes async, such as at the bottom of Ethernet. On-chip peripherals + respond instantly while peripherals over SPI fail asynchronously. Interfaces + have to either support both or everything has to be async. We don't have a + good answer or deeper thoughts, I just see it coming up repeatedly. +- Johnathan: At OT call, I discussed some work I'm doing on an alternate API for + `libtock-rs`. Still ongoing, may not have time to finish. + +## Transition to list.tockos.org mailing lists +- Amit: Our mailing lists are currently semi-scattered. tock-dev is basically + dormant, I believe there's an OpenTitan one on Stanford's mailman, Helena on + Stanford's mailman. It's been working fine, but if you want more it's a + bummer. I created lists.tockos.org. It's mailman 3, which is way nicer than + mailman 2, and I suggest we transition some or all of the mailing lists over + there. +- Phil: I'm happy to send you the full member lists. It's a nice historical + thing, but it's important that things transition to a neutral place. +- Amit: Can login with GitHub or Google accounts now in addition to creating an + account. + +## Uberconference +- Amit: We've been using the same room for all meetings. I created an + organization called Tock. We can create specific rooms for different meetings, + and have people who are not me be administrators. The downside is the URLs + have random nonces in them so you'll have to store the URLs. +- Phil: We use Uberconference because it's what we've always been using. We can + switch if something is substantially better. +- Amit: Personally I like Uberconference's ease of use by dial-in, but I don't + feel strongly. +- Phil: Sounds like you don't see anything substantially better. +- Pat [in chat]: IIRC, the original motivation for UberConference was better + (free) international dial-in support — I think that has long-since equalized + however + +## Tagging/devolving PR review by WG +- Amit: Increasingly, there are PRs that should be reviewed by a subset of + people, which includes people not on the core WG. Maybe it's fine for some of + these things to move forward in ways that do not meet core kernel standards. + We occasionally tag things with WGs but we could imagine evolving that. Maybe + if something is within the purview of e.g. the networking group, then the + people in the networking group should be reviewing it. +- Brad: I agree. I think the networking group should open a pull request to + automatically tag their PRs, which is what OT does. +- Amit: So there's a way to automate it? +- Brad: Yes. The only thing that is tricky is when there are other labels, they + fall into a bucket, and not a lot of PRs only have WG labels. #3660 is a good + example. If there's a single label and it's `WG-`, then it makes sense for + only them to look at it. +- Pat: Everything should have a working group tag, with a default to `wg-core`. +- Hudson: You can tag the working group as a reviewer, as an alternative to + using a label. We have OT and core GitHub groups. Could make one for + networking as well. A reviewer from those groups will satisfy it. Downside is + when you're scrolling the list of PRs, it doesn't show up. Label shows up in + PR search results. Could do both. +- Amit: Both might be fine. In general, the labels are useful -- I like to + filter -- but maybe we can experiment it. I wonder if something like #3643 + with a ton of labels — it's not really an OT WG thing. Networking group notes + (#3661), has the doc label but it is not documentation but WG notes. If the WG + members feel it accurately reflects the meeting then it should be merged. Are + there PRs where a specific WG tag wouldn't be able to indicate who is + responsible? +- Brad: How about #3597? +- Pat: I think it's wg-opentitan but it is P-significant which pulls in more + attention. +- Brad: It's a good example of a related issue because it also has the kernel + tag, but maybe not a good example for this purpose. With this PR, the kernel + changes are not significant. +- Amit: My judgement is that this is a wg-core PR. Because the important + stakeholders care about RISC-V, then they should also be consulted. So this is + like a both. There are going to be things that touch design-level things in + the kernel and significantly affect the design of certain subsystems. + Hopefully not frequent but I expect a lot around major releases. +- Brad: There's an advantage to the person opening the PR to have them be + smaller-scoped. I'd like to incentivize that behavior. +- Amit: Generally, WG members have people who we trust in them. So we should be + able to notice important changes in PRs sent to working groups. +- Brad: I like the auto label, I don't think it's scalable for people to tag + their own. +- Pat: Also they can't, you need triage permission in the repo to apply the + label. +- Brad: The labeler will do what the labeler will do. +- Hudson: Yes, it will add and remove tags, but that's not nice when people want + to manually add labels themselves. +- Branden: I would note that automatic labelling will be woefully insufficient. + I'm trying to do it for the network group. I have to find every file in the + repo that has something to do with networks. It's not as clean as OT. E.g. + there's a radio driver in `chips/nrf`, so it's a bunch of files not + directories. +- Pat: The labeller is going to have to parse files and look an the HILs inside + of them. +- Branden: I can manually list them, it'll just get out of date. I'll have to + keep updating them. +- Brad: Either way it'll be out of date. I think automatic is better. +- Branden: I'll have a PR soon. +- Brad: I wonder if we should get rid of the kernel tag, which we kinda use but + don't really, and replace with wg-core. My proposal is a bit different from + Pat -- I don't think we should tag *every* PR -- but we should tag important + ones. +- Hudson: I think manual tagging is ideal for the core WG. +- Brad: I disagree. We often see the title says one thing, and the PR changes + others. Having the labels show up as a warning is important. +- Pat: My motivation for everything having a tag is so everything has someone + responsible for it. Currently, there's the fallback of the core WG having + responsibility for untagged things. Putting tags on all makes it more + explicit. +- Brad: I see what you're saying. I don't quite like that because I want it to + be automatic. I'm kinda reinventing `significant` but doing it automatically. +- Amit: So wg-core would be at least everything in kernel/ and arch/, right? +- Brad: Right +- Amit: I'm just trying to clarify what you're suggesting. +- Brad: That's a reasonable starting point. Perhaps I've sidetracked the + discussion a little bit. My main takeaway is that imperfect automation is + better than manual processes we can do perfectly. +- Brad: Hudson left, but an open question is whether we can have the labeller + tool not remove tags? I'm wondering if we can make it leave manually-added + `wg-` flags. +- Amit: I move that we switch towards Brad's version of the suggestion, with the + open question of exactly how to do it, but auto-labelling WGs. Have wg-core be + auto-labelled for everything in kernel/ and arch/. Essentially evolving the + decision about things tied to a particular WG to that WG. +- Brad: SGTM +- Amit: Okay. Maybe an action item is to figure out how to do with w/ or without + GitHub's auto-labeller. If we can, can we make sure that the auto-labeller + will not remove a wg- tag. +- Brad: Right +- Amit: If it's impossible to do then maybe we figure something else out. We can + probably fork the labeller action and modify it, e.g. + +## Allow Command 0 to return Success w/ value or lock it down (#3626) +- Brad: I added #3626 to discuss. +- Phil: Sounds good to me. Should we finalize TRD 104 before we add updates? +- Pat: I have 2 PRs open against 104 right now. +- Phil: I think it is important to document the 2.x system calls. +- Pat: I could update this to finalize 104 and create a draft PR that deprecates + 104 and target against that. +- Phil: I don't think we have to deprecate, it can be an extension. Command 0 + should be in the finalized version, but adding system calls should be an + extension. +- Phil: I think the 104 text was written to accomodate that. +- Brad: It sounds like #3626 should be included in 104 before finalizing it. +- Phil: I think so. Incorporate #3626 then finalize. +- Brad: Okay +- Phil: I'll read through 104 to make sure we can do the yield-wait-for. +- Brad: Are people willing to approve #3626 +- Amit: I've already approved it. +- Phil: I am happy to approve it. +- Leon: I'll do a final review today, but generally LGTM. +- Brad: I don't know about this RFC thing. If we deleted the line, I approve. We + have one TRD with one bullet point with this, which seems ad-hoc. +- Phil: I agree the bullet point is weird. +- Pat: The rationale is a lot of the TRD has exposition on the motivation behind + stuff, which is quite long. +- Phil: So write 3 sentences that summarize it. The TRD tries to be concise in + giving reasoning. +- Pat: Maybe a two-sentence reasoning and a pointer to the RFC discussion? +- Phil: There are huge discussions about 104, and there aren't pointers to it. +- Pat: I think that is really unfortunate. How will people find it? +- Phil: Go look at the PR. +- Pat: I tried to include links to the PR. +- Phil: Maybe this should just be a reference at the end of the TRD? +- Pat: Alright, moving to a reference. +- Brad: Makes sense to me. +- Phil: Example, in TCP, why is the initial window size 2? There was a long + discussion which is not in the RFC. You have to go look at the mailinglist to + see it. diff --git a/doc/wg/core/notes/core-notes-2023-09-15.md b/doc/wg/core/notes/core-notes-2023-09-15.md new file mode 100644 index 0000000000..15f9dd4859 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-09-15.md @@ -0,0 +1,151 @@ +# Tock Core Notes 2023-09-15 + +## Attendees +- Branden Ghena +- Pat Pannuto +- Leon Schuermann +- Hudson Ayers +- Alexandru Radovici +- Ionut (Johnny) +- Felix Mada +- Johnathan Van Why +- Amit Levy +- Chris Frantz +- Brad Campbell +- Phil Levis +- Tyler Potyondy + + +## Updates + * Leon: From OpenTitan call, Brad and I chatted about `set_client()` issues in various HILs. In HMAC, we have a couple of combined traits and each have a client. We also have a client that's a supertrait across multiple. This completely destroys composability of HILs on our end. Depending on what the underlying code implements, you have to use a different `set_client()` call. So you have to be aware of the specific implementation beneath you, which is less than ideal. + * Hudson: So this is the higher-layer client that's being set. I agree that seems bad + * Leon: We can split things up into multiple different clients and remove all super-clients. Or alternatively, get rid of the individual clients and just have one client trait with methods for all possible events. + * Branden: What's the downside of having one client trait? + * Leon: If you are making a userspace driver, your implementation has to hit all of the traits for callbacks, even if you don't need that callback at all. + * Hudson: Is this going in an issue somewhere? + * Leon: It's across a couple different PRs, but we should create a single tracking issue. + * Branden: In C callbacks, often they take an event-type argument. So you can just have one callback, and you can ignore types you don't expect or can't handle. Would that work here? + * Leon: I think that's not too different from multiple callbacks with no-op function for callbacks. I'm not sure of the size difference of vtables versus match statement. + * Branden: And rust loves to be pedantic with match statements so you'd likely have all cases anyways. + * Hudson: I think it might be easier to have no-op functions, where it's clear we don't care about it (rather than forgot it). + * Chris: For OpenTitan clock frequencies, we did commit it. But we found our CI doesn't share artifacts for commits on branches. So the bitstreams aren't published, even though the code is there. So the change in Tock to adjust FPGA clock rates and ratios isn't something we can test yet. + + +## ClockInterface Trait + * https://github.com/tock/tock/issues/3671 + * Alex: Followup from discussion in Networking group. Many peripherals need a way to find out what frequency their clock is running out. For the rp2040, we built our own infrastructure and added a function to the ClockInterface trait. But every board will have to implement this. So we would have to change all of the boards (and we can't do that because we can't test all of them). We could instead of returning a number return an Option and have the default return `None`. But what's a peripheral supposed to do with that? + * Pat: We have a formal definition of Frequency for the timer subsystem. They aren't the core CPU frequency, but are a notion of a discrete number of frequencies that HW could support. I wonder if it should be similar here, where it's an associated type? Actually, probably no since the clock can change frequencies. + * Hudson: We could have a trait object. + * Alex: I think the frequency trait has a static function. + * Alex: A problem is that many peripherals have many options. The RPi has 32 dividers, so many different options. We also might need to change over time. For example: low power versus high power modes. + * Pat: Frequency as a trait isn't a static function. It just returns the frequency. So it feels like you could return a struct that returns the frequency dynamically. + * Branden: Why over-engineer this? Why not just return a number? For the boards they can't test, we could just have a static number, since they almost certainly don't change their clocks. + * Pat: Don't know the difference between capital F Frequency and lowercase f frequency. When should we use each? I don't remember why we even have Frequency. I should review TRD105. + * Alex: Should we defer this for next week? + * Branden: Well, let's not defer unless there is a clear plan. I don't want us holding you for a week, and then coming back in the exact same spot. + * Alex: So the main question is how do we build an infrastructure so a peripheral can figure out the frequency the clock is at? + * Pat: And I think the architectural question is how does that relate to our time definitions in TRD105? Should we be trying to adapt/reuse that? Or is this orthogonal and should be different? + * Hudson: I agree that the question is why we should use Frequency at all. So we can see if we need to use it here. I suspect Phil will know the best. + * Pat: We could _remove_ the Frequency trait from the time HIL. But we should do something with this code that theoretically attacks the same problem. + * Leon: Some of our chips do something really weird with the trait, which has a hardware register that returns this. At the time, Rust wasn't capable of doing this with const generics. So maybe returning a number is just the right way to go. Not having to be generic over a frequency trait which returns a number. + * Alex: When adding boards, I several times had issues where I had to add Frequency traits to the kernel. Eventually we'll hit every possible frequency across enough boards... + * Pat: Yeah, I think you've walked into a bigger problem here. We're just going to have more and more of these Frequency enumerations. On first read, it looks like we're trying to use traits to stop people from making clocks that can't be realized based on the underlying time source. Maybe there's a better way to do that? + * Leon: I was thinking about PR https://github.com/tock/tock/pull/3333 as my attempt to touch this once before + * Alex: The RPi PLL can indeed create just about every possible frequency + * Pat: So how do we pass that into the Alarm requirement? + * Alex: Indeed. We really hit this with Networking. We up the clock to high speeds. + * Hudson: Do you need to change clocks, or just set the clock to a high speed at runtime? + * Alex: We'll need a couple of profiles which we can switch between: high-speed, low-power, etc. So we would switch dynamically. + * Hudson: This is starting to sound like PowerClocks. It got complex though. We had to add a lot of stuff for the SAM4L, because certain peripherals start or stop working based on the clocks. So you have to reconfigure certain peripherals each time you change clocks. And you have to change the clock speed based on user calls to service them properly, delaying them until the clock was ready. + * Amit: PowerClocks never made it upstream because it was relatively involved and the SAM4L wasn't worth the effort since others weren't using it. PowerClocks did yield good results though. + * Hudson: I thought it also required a lot of interfaces that only made sense if EVERY chip implemented it, and that was a huge lift. + * Amit: And the nRF52 does that all magically in hardware. + * Branden: Right, it doesn't let you chose at all, and just "does the right thing" + * Amit: Maybe worth revisiting. Alex, maybe we can send you the paper and see if it matches what you guys want. + * Alex: We need to include Felix here. He did the CAN stuff and had clock issues. + * Alex: For us, this is a must-have. We have requirements where the chip has to go into low power. + * Leon: Echoing this sentiment, PR 3333 does implement dynamic clock switching. That code could be useful. + * Branden: A big thing I see here is that profiles could make this simpler than infinite modes. We don't need to handle arbitrary complexity, only a few discrete states. + * Alex: Exactly, and boards could say that certain modes aren't available. + * Hudson: Okay, we'll send over the PowerClocks paper and send over code, then we'll use that as a starting point for further thought. + + +## STM32 Clock Management + * https://github.com/tock/tock/pull/3528 + * Brad: This has been around for a while. It's adding features for the STM family for several different variants. The issue is that features don't work well for this use case. Features are supposed to be additive, not mutually exclusive. And chip variants are mutually exclusive. I'd like to get rid of the features and pass in the options to the crate. + * Pat: Are you describing something similar to what Leon did for the frequency variant configuration in the OpenTitan space? + * Brad: It could be. It would be at the chip level instead of board. + * Leon: That should work, as long as you're using the specific chip variant. + * Branden: Do you have a link? I don't know what this is? + * Leon: https://github.com/tock/tock/pull/3640 + * Leon: One takeaway was that we wanted a good precedent of how to do features correctly. Type signatures do get long and unwieldy. + * Brad: https://github.com/tock/tock/pull/3640/files#diff-2ebe726d4be8b51b1c35fbd4be6b1ef605e264656feeada21790ec4a755e43dbR82 + * Brad: That link has a specific use of the change to OpenTitan. Do we know how bad the type signatures would get? + * Leon: It's pretty bad. You have to add the type to all uses of the chip, which itself is a generic argument for many things. I solved it with a type alias, that can be used. + * Pat: A type alias makes sense. You're describing a discrete thing, which is a type of chip that specifies specific choices for the variant. + * Brad: Why does that have to propagate up beyond the chip crate? + * Leon: Oh, the chip crate can bind to the variant it wants? + * Phil: Do we have a sense for how many layers of hierarchy there might be? STM32, STM32F4, variants so on? + * Alex: They have a ton in the family, and they sort of do and sort of don't overlap. They have a manual for STM32F40x7 where the x can be anything, but they also have STM32F46x. We have some chips that differ by two registers and some by MANY. + * Brad: Regardless we'll run into this problem. + * Leon: I'd say A) we want to share peripherals whenever possible and B) we'd probably have a single STM32 trait which encapsulates the differences and lazily add things to the trait. + * Alex: What do you mean by trait? + * Leon: There's a file in the PR that isn't meaningful at runtime, but contains differences between chips. + * Leon: https://github.com/tock/tock/blob/master/chips/earlgrey/src/chip_config.rs + * Phil: Not having duplication is what we want to do. But when the hierarchy gets deep or complicated enough, you start chasing constants around and have to look at a ton of traits. Seeing things in one place versus de-duplication + * Leon: I agree. It's a "noble goal" but we do have different drivers when necessary. + * Pat: Tyler and I were talking about this. I wonder if it's a tooling issue. I wonder if the place to store this is in the tree, but there's a tool we write that shows you "here's where everything comes from for this chip"? But now I'm just reinventing device trees in a way. + * Brad: But we're not super deep right now. It's four devices. We could work on something more complete outside of this. + * Phil: For four, just flatten it. Works for any small number + * Brad: So have four copies of the flash driver? + * Phil: The constant definitions at least. The deduplication point is especially true for code, but not for constants. + * Leon: I think we've only been meaning to talk about deduplication for code. + * Phil: I might be confused here. + * Pat: It's come up with constants more on the nRF52 side of things, where there's a base and configs add more registers to parts of the hardware. So really just the memory map extends. + * Leon: To illustrate our point, for this particular STM case. I think Brad and I are proposing to create a trait that captures the relevant constants, then four implementations of the trait: one for each variant. + * Pat: I like that solution here. Does that make sense to Alex and Johnny? + * Alex: Yes. Similar to Leon's PR, right? + * Brad: Yes. Just wouldn't be exposed to the board + * Phil: I would say, the devil is in the details. If this doesn't seem to work when implementing it, we can circle back to the question again. + * Alex: Okay. We'll modify the PR and move forward then. + + +## Returning to Frequency Trait Discussion + * Pat: We don't have anything in-tree right now dealing with dynamic frequencies of clocks. Holly's work doesn't exist in-tree, so we were discussing what stuff should look like for Alex and company. PowerClocks feels like it had full power, with infinite choices but lots of complication. But we discussed that we could do discrete profiles, like low-power, high-power, etc. which could simplify this? + * Branden: We also discussed the Frequency HIL from the timer/alarm interfaces, and whether they would make sense here or not. + * Phil: The key things from PowerClocks was knowing _when_ you can change clocks so-as to not disrupt an operation. You could definitely quantize things. + * Amit: Our takeaway was that we would share PowerClocks as a reference for them, then jump off from there to think more deeply about what makes sense. + * Phil: Makes sense. I think Holly had some great insights into the complexity here. I don't think any of us would argue the implementation was the perfect thing to do. Take inspiration from it, rather than treating it as the final word. We could even rope in Holly possible. + * Amit: Power management and clock management is one of the original motivating factors for Tock, which was less important for the hardware and applications we've used so far, but it's exciting to see it as a real requirement again. + * Alex: The whole discussion started in the peripheral knowing what it's clock frequency is. + * Phil: I think you could certainly go with a simpler form of the mechanisms from PowerClocks. But it can be pervasive, with everything needing to know about it. And maybe some chips can implement it and others can ignore it, depending on need. + * Alex: Another angle of this story was to add a function that returns a frequency. But should we return a number or the Frequency trait? Our PLLs can generate more-or-less any frequency possible in a range. + * Phil: I'll need to refresh my knowledge of the frequency trait. Definitely tricky. No question that the time stuff was intended for static use, not dynamic use. + * Alex: Even while not changing the frequency when running, we could have a different frequency per board based on which peripherals are needed. Ethernet or not, for example. So the exact same chip will need to run at different frequencies. + * Phil: What's the issue with the Frequency trait? + * Alex: Branden and Hudson said it adds complexity. + * Phil: It does + * Leon: We also have this Frequency trait type-system concept that returns a runtime number. It's a weird hybrid place where everyone expects a constant value, but you don't have to? + * Amit: It was pre-const-generics + * Leon: Yes. And const-generics break with some chips + * Phil: So chips have no way of doing dynamic frequencies without callbacks. For example, a timer needs to know when clocks change so it can change counters and prescalers and stuff + * Alex: I think that is version 2 of this. So version 1 is the peripheral knowing what frequency we're running at. Version 2 will be changing it. + * Phil: Doesn't the Frequency HIL trait do that now? + * Alex: We would have to add it in the board and export it + * Leon: I think that's okay + * Alex: How would the chip get something from the board? + * Leon: Have the chip take a generic argument + * Alex: So the chip would have a generic frequency parameter you can access? + * Leon: Yes. And you could request a static number at runtime + * Amit: I suggest we have a dedicated asynchronous or even synchronous discussion about this in the future + * Alex: We can continue next week. We'll look at Leon's idea. + * Phil: So Leon's idea is that there's a const-generic parameter that lets the chip look at the frequency? + * Leon: Something that implements the Frequency trait anyways. So you could use this to call and request the frequency value. + * Phil: I wonder what will happen compiler-wise with it passed as an argument to the chip. Will it be able to figure out it's a constant and do useful optimizations? + * Leon: For sure it'll know this at compile-time. It'll be labeled as const. + * Phil: But there could be a code-path in the board which could pass in different options. + * Leon: It would monomorphize and create the options, as a generic parameter. + * Alex: That would stop us from changing the clock in the future. + * Leon: Our current infrastructure really can't support changing frequencies, until we add support for that. + + diff --git a/doc/wg/core/notes/core-notes-2023-09-22.md b/doc/wg/core/notes/core-notes-2023-09-22.md new file mode 100644 index 0000000000..cf99b9fcb0 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-09-22.md @@ -0,0 +1,139 @@ +# Tock Core Notes 2023-09-08 + +Attendees +--------- + + - Pat Pannuto + - Amit Levy + - Alexandru Radovici + - Hudson Ayers + - Viswajith Govinda Rajan + - Branden Ghena + - Leon Schuermann + - Philip Levis + - Brad Campbell + - Chris Frantz + - Johnathan Van Why + - Tyler Potyondy + +Updates +------- + - Brad: I bought this, https://www.makerfabs.com/makepython-nrf52840.html, nrf52840 with a display; possibly a way to test the screen stack + - Brad: There is a bug in their schematic... so they may not have tested this much + - Phil: What's the bug? + - Brad: Using a drop-in module, and they swapped two pins; LED is on 1.10 in schematic but on 1.11 in practice + - Hudson: How did you find that out...? + - Brad: To their credit, there's a github repo which includes their hardware file. Tried to figure out which nrf module they used. Used an image search of all things to find the module, and realized the pin order was different + - Hudson: What's the bootloader situation? Same as the Clue boards? + - Brad: Maybe? There are two USB headers, so you can have a UART if you want or USB direct to to NRF; I used UART so tockloader can do the baud rate trick + - Brad: Bootstrapping however for first load of bootloader will require JTAG + - Hudson: And there's still the panic issue with CDC... + - Alexandru: No debugger, just USB connection? + - Brad: Correct. + + - Tyler: Quick Thread update: PR went out middle of the week, #3683 + - Tyler: Tock now consistently joins open thread network, child can join a parent; no heartbeat yet, but joins work reliably now + - Hudson: That's very exciting :) + + +Component PRs +------------- + - Hudson: PRs from Brad: #3657 and #3681 + - Hudson: Brad, start with the `pub type` PR? (#3657) + - Brad: Originally motivated by the key/value stack; many layers and it doesn't use `dyn`; types become very redundant and long + - Brad: There's been discussion on the OT call that asks whether there is a nice way to do this? How bad are macros? How much of this should be integrated into components versus something we just kind of try out in one place? + - Brad: It's not the most exciting PR, so want to raise attention and get some thoughts + - Phil: I look at the change in code and I'm excited.... + - Brad: That's why I opened the temperature stack change; without that changes look like swap of one line of code for another + - Phil: That's true, but it matters where the code is and what it represents + - Phil: Having a complex type definition such that one a type is used it's simple, that's like C++ templates -- footnotes, not inline digressions + - Hudson: Yeah, I think I'm pretty on board. I think we should be trying to avoid `dyn` when possible, and this change makes avoiding `dyn` more palatable + - Leon: We talked about this on the OT call Wed, but what turned me around is the thinking that these type aliases do for type creation what components to for encapsulation of peripheral creation + - Brad: Other thoughts? + - Phil: Ancillary point, but, components and their macros are always challenging and we've evolved with discovery of new subtleties... do we have a doc anywhere of the whole view of the component system, what the macros all do, and how the system works? + - Brad: There's really two layers. The `static_buf` family of macros are well documented. We cleaned this up, and now there's just some upper-layer macros that effectively call `static_buf` a lot; not sure those are documented well anywhere + - Hudson: Are you counting your stuff in the Tock Book? + - Brad: Forgot about that.. + - Phil: But nothing in `doc/` + - Hudson: Part of the problem is that we keep planning to improve things here, so final docs a bit held back + - Phil: Yeah; not a blocking thing for this PR, just flagging that when components change, others come back to it and can be lost + - Brad: The only real documentation is in tutorial format, in the book, and in the porting guide + - Brad: But there's not doc with the rationale, constraints, etc + - Brad: If there are any other thoughts/comments please post on PR + - Brad: The reason I have more tepid enthusiasm is because of the change to the top of `main.rs`; we're going to end up with a huge block of these type definitions. Have to use in the platform struct and where we instantiate the component. It's a bit clumsy I think + - Leon: Is this part of the change strictly necessary? Can we keep the change contained to the components? + - Leon: I agree, having the virtual kv types without any context is not very elegant + - Brad: It doesn't have to be, but my main motivation is the Platform struct; without declaring the types earlier, we'd have to use the existing giant type definition + - Leon: I don't think that's entirely true; would have just the base types without lifetimes or platform-specific bits + - Phil: Your point is well taken, but that may just be an artifact of having only one `main.rs` file; where other paradigms would have a dedicated declaration file that hides it away. For all of C's failings, `typedef` is pretty awesome + - Brad: Leon, I'm trying to see if I can see how that would work; my experience writing this PR is that it's really hard to keep track of all of this. Once it's there it's clear, but creating it is really hard + - Leon: I think--not sure if true---there isn't any real guidance on how to use components and which layers should plug into what; we have like 10 layers of nesting, but it's not clear what component plugs into what other component; can see the frustration in trying to write that + - Leon: Now we have two stages; instead of plugging types as they are realized, you plug the outputs of one component into the input of another; and this entire type composition is a bit statement at the top of main. Indifferent to the necessity of that second step. + - Brad: Right, and I think that's another thing we have wrestle with a bit. Similar to how using `dyn` simplifies things, we've also written components such that you take whatever was in e.g. imix, a first reference impl, and factoring that out. Coming from one implementation leads to unintended structure on how things are to be used in components. Not clear when things should be encapsulating more or less. The way the types actually work out will ultimately depend on how much encapsulation there is, and this encapsulation makes it hard to explain how to compose things + - Leon: Right, and this is really circling back to the challenges in composition, not type aliases? + - Brad: There's an expected structure, do we encode that in the component somehow such that there can be less redundancy in the `main.rs` files if you're just doing the 'standard' thing + - Leon: There's two parts to encoding structure. On the one hand, this is where type aliases fall short; they allow for these template patterns, but don't allow for traits. The compiler can only tell you that the composition of underlying types doesn't work, not that the composition of aliases shouldn't have work. Now this also means that you have to understand the subsystems in order to understand how to compose holistically. + - Brad: Exactly. The fine-grained components are great when you want to be able to do something different, but it means the whole stack has to be in your working memory in order to be able to compose anything. + - Brad: What I'm hearing is that this is a step forward; no real objections; worth adding. I would like to play around to see if we can move any of what's in `main.rs` into the component in a way of explaining "this is a valid way of composing components in this file". It's a type, you can use it or not, so not binding, but eases + - Phil: That sounds good. This is a step forward, but not a final + - Brad: Yeah, this is actually the second PR exploring this; the first was very macro heavy, did not like that as much + +libtock-c newlib updates +------------------------ + - Brad: This kicked off with Alistair updating newlib to 4.3 + - Brad: We've had this weird dependency in that we ship newlib, but also requires that you have newlib installed so that you have headers + - This PR removes any dependency on local install; so can use toolchains from ubuntu/risc-v/arm/homebrew/whoever + - This thus builds everything by default now, and provides the libc/c++ libraries for risc-v + - Leon: This is awesome. + - Leon: But, didn't we have an issues that newlib headers are GPL licensed? + - Brad: That's kind of why I brought it up + - Hudson: Are the headers GPL?? + - Leoon: They seem to be mostly BSD, but there's a smattering of other licenses; couple public domain, couple redhat, couple GPL... this is a mess + - Hudson: Yeah, that's frustrating + - Leon: It's always bothered me a little how much we vendor + - Leon: What a lot of other projects do is keep built things out of the repository, and download / link / etc on build + - Pat: That works? That we have a built thing that we host that people download and that's okay? + - Leon: I'm not a lawyer... but that seems to be state of the art + - Johnathan: I suspect if we distribute binaries we have to be able to distribute sources + - Hudson: Can't just link to sources? + - Johnathan: Would need a backup, if that link disappears, we have a problem + - Philip: The variety of these header files makes me skittish + - Brad: About merging them? About using them? + - Philip: Umm... both? all? + - Johnathan: We have an existing issue with libtock-c that's a licensing concern + - Johnathan: It hasn't been address yet, but we had been considering moving to picolib-c or other implementation + - Leon: That open PR looking at that only compiles one application that does no allocation; picolib-c isn't compatible with Tock's `sbrk` implementation + - Pat: Could we just add a `memop` to make this work with Tock for whatever their `sbrk` needs? + - Leon: picolib-c doesn't support any kind of hook for a custom `sbrk`; i.e., can't perform `memop` in piclob-c; it expects only a flat address space + - Phil: So it expects no MPU + - Leon: Yeah, only bare metal + - Alexandru: Could we commit something to picolib-c? + - Leon: Yeah, probably; their build/link/etc ecosystem is just complex + - Leon: The summary from the open PR / first experiment is that updating to picolib-c is a significant undertaking + - Alexandru: huge problem for OxidOS + - Branden: What does industry use + - Alexandru: Use vendor libraries from Vector etc. There are required certifications. These libraries cost $1k+ per-project. + - NXP uses redlib: https://community.nxp.com/t5/LPCXpresso-IDE-FAQs/What-are-Redlib-and-Newlib/m-p/475288 + - Branden: So even if we wrote our own, we'd have a problem + - Alexandru: Yeah. We actually hit this problem with Rust too. Ferrous systems certified the complier, but not the core library; and there isn't sufficient tooling to enable certification of the core library currently + - Brad: Should we just vendor all this crap in one bit zip blob? + - Leon: I'd be in favor, if only because we're currently mixing the headers from some random Debian image with something we've build + - Brad: The other thing with this pull request is that it adds multiple many-megabyte libraries + - Leon: This always surprises me with libtock-c on a clone + - Brad: Yeah, so this would be the way out + - Phil: Looking at picolib-c, it has support for things like `mmap`; it really expects only a flat address space? + - Leon: May be more than than; but if you dig into the `sbrk` path, it's a fixed implementation that just moves the heap until it hits a predefined start symbol. I tried to pull this part out; they have scripts that let you configure parts of the library for platforms, but this does not seem to be a configurable parameter + - Brad: So, what we can do is create a package that people can download in support of libtock-c apps + - Brad: And namespace it some what such that if people want to try picolib they can, etc + - Brad: That would let us disentangle this; right now it's all really messy; it would be a hard switch, one or the other + - Pat: Yeah.. that seems reasonable + - Leon: It would be nice to get clarification on whether this actually solves the license problems with newlib + - Leon: Though at-worst it's the same as-is license-wise, and is a strict usability improvement + - Brad: ... assuming our mirrors don't go down :) + - Leon: We can always add more! + - Brad: But yeah, that's helpful + +Last-Minute Update +----------------- + - Leon: QEMU has new release that fixes PMP, one less blocker for that PR + - Leon: This, unfortunately, also means that we won't be able to use any packaged QEMU for the foreseeable future, so we'll have to target some release diff --git a/doc/wg/core/notes/core-notes-2023-09-29.md b/doc/wg/core/notes/core-notes-2023-09-29.md new file mode 100644 index 0000000000..0250b7b2cb --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-09-29.md @@ -0,0 +1,65 @@ +# Tock Core Notes 2023-09-29 + +Attendees: +------------ + +- Tyler Potyondy +- Johnathan Van Why +- Hudson Ayers +- Alexandru Radovici +- Brad Campbell +- Leon Schuermann +- Pat Pannuto + +Updates: +------------- +- Brad: I have been working with the nrf52840 board I bought with a display. +- Brad: I am mainly working on the screen HIL and understanding the state of that. + +Tock Registers PR +----------------- +- Hudson: We should discuss the release/PR for Tock registers (https://github.com/tock/tock/pull/3693). +- Pat: I can make the case we can just merge this. +- Hudson: I agree. +- Pat: Tock registers have been used by downstream users off and on. +- Pat: As Tock registers gains more traction, we have an obligation to downstream users to make it more stable. Most of the recent updates have been rather trivial. There have only been four commits in the past year, two of which are rust updates and a clippy update. The only changes to code are the matches all API changes Hudson made 10 months ago. I know we have a long standing mission to update some safety issues, but I propose we roll those changes up in 2.0. +- Johnathan Van Why: I agree. What I am working on would be 2.0. +- Hudson: The fact that there might be a major transition seems to be a time to move to 2.0 to communicate breaking changes. We may annoy people if they didn't realize there are likely to be a breaking change. +- Pat: This doesn't seem contentious. +- Pat: More contentious, I propose we pull Tock registers into a separate repository and have it be +a dependency of Tock. +- Leon: Back to Pat's original point, I agree with everything said; it concerns me that we will be releasing a stable version that may have some soundness issues. We should communicate this for internal tracking and external users. +- Pat: Do we have that written cleanly anywhere? +- Leon: I don't think so, we should. +- Johnathan: I can write that up. +- Pat: Reasonable to document known issues. +- Pat: How do people feel about me creating a Tock registers repository under the Tock organization? +- Brad: We need to be careful we are not breaking backwards compatibility. +- Brad: Downside, we technically cannot do this and be in compliance with the external library policy. +- Leon: Because the Kernel crate pulls it in? +- Brad: I don't know if the kernel does, but chips does. +- Pat: Does it count as an external dependency if it is under the Tock umbrella? +- Hudson: I think we can easily explain/justify anything under Tock umbrella is not an external dependency. +- Hudson: This may be confusing though if developers believe Tock does not use external dependencies, but then see cargo.io downloading. +- Brad: If this is an internal dependency and is controlled by Tock, why bother with adding a whole new repository and make seem to be new project when it is not? +- Leon: One argument is that having these workspace projects and cargo is unwieldy. How cargo searches within workspace project is unclear. It would be easier and more clear for this dependency to be a separate repository. +- Hudson: We could in theory have Tock registers as its own workspace still in a top level Tock repository the same way we have some of the tools, right? +- Leon: You cannot specify subpaths in cargo. The semantics are super weird. +- Brad: I make a proposal that we could move it to an external repository, only include in kernel crate and therefore be very explicit about how we manage this. +- Hudson: It should never be the case that kernel crate vs chip crate pulls in different versions. +- Hudson: As a prerequisite, we need to only pull into one kernel crate; this shouldn't be too difficult. +- Brad: I am in support of this. +- Pat: Action items (1) Release 0.9 on cargo after merge; (2) Create Tock registers repository; (3) Create PR in Tock removing references to registers and update documentation to explain. +- Hudson: Another downside to this is that being a separate repository means fewer people will review/see changes. +- Pat: I think that this may be good in the sense only people interested in Tock registers will see changes. +- Brad: Why not include the history? +- Leon: I think this is helpful. +- Pat: Okay, I will look into this. +- Brad: Having thorough CI testing across different repositories is becoming more confusing.This is something we should think about. +- Hudson: This is an interesting point, we may merge something to Tock registers and then later find out that it is unusable from Tock's perspective. +- Leon: We could integrate a CI workflow (similar to userspace libraries). +- Leon: We should certainly codify this and test prior to releases. +- Leon: One nice takeaway, we are no longer in a state with Tock using an unreleased version of Tock registers. +- Pat: We should be better about pushing out these changes and improvements. Hudson's fix in Tock registers has sat for around a year. +- Hudson: This all sounds good to me. + diff --git a/doc/wg/core/notes/core-notes-2023-10-06.md b/doc/wg/core/notes/core-notes-2023-10-06.md new file mode 100644 index 0000000000..88783e1d5d --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-10-06.md @@ -0,0 +1,182 @@ +Tock Core Notes 2023-10-06 +========================== + +## Attendees + - Alexandru Radovici + - Alyssa Haroldsen + - Amit Levy + - Brad Campbell + - Branden Ghena + - Chris Frantz + - Johnathan Van Why + - Pat Pannuto + - Phil Levis + - Tyler Potyondy + +## Updates + - Amit: We're making some progress on finding someone to hire to work on CI + infrastructure, security, testing, etc. + - Brad: I just pushed a bunch of releases. `elf2tab` and `tockloader` have new + public versions. There've been some changes that are helpful to have in a + release. From the OpenTitan side, not too many updates, but Johnathan has + been working on I2C. + - Branden: I can give a network WG update as well. This past week, we were + looking at the tock-ethernet branch. It's existed for a while, the open STM32 + ethernet PR is open against the branch. It's probably time to start pulling + the branch into main. It made sense when we were trying to move fast, but + we're stable enough that it doesn't need to be its own branch anymore. The + other topic of discussion is buffers, discussing how sk bufs work in Linux + and how we may make our own version. Discussed how the type system could help + here, such as preventing passing buffers that aren't big enough to hold + headers. Looking to have Leon prototype something small. Alex and I were + discussing implementing it on non-network drivers. Display driver has this + problem, as does the SD card driver. Those are more mature, seems like a good + place to prototype it then move into the less-mature networking stuff. + +## PR #3701 (scheduler trait) + - Brad: We don't have Hudson so maybe it's not a good idea to discuss now, but + it's a pretty low-level change. + - Branden: Do you have a feeling it'll be controversial? + - Brad: Either nobody is going to say anything and it'll get stuck or people do + have feelings and we're not going to know. + - Phil: I'd love to hear Hudson's thoughts. I'm skittish about things like + "this interface works, but it's not ideal" -- sounds like churn to me. I try + to suppress that, but I don't always do so. + - Brad: Where are you seeing that? + - Phil: #3702 is the issue that describes the problem solved by #3701. It's + basically like I think we should have a different idea. + - Brad: You're responding to the general idea it isn't ideal. + - Phil: Yes + - Brad: I thought you were talking about a change where someone said "oh it + works but". I think this discussion started at TockWorld. I didn't quite + understand what was happening. I thought that was referring to a previous PR + not a new issue, there was another thread on this. + - Phil: #3701's first sentence says "this pull request reimplements the + scheduler interface as discussed in issue #3702". #3702 explains the + reasoning. + - Brad: Correct. I didn't even look at the number, I just assumed it was this. + So that was my confusion. It's a more general version if you will. But I + suspect it's thinking about the same thing. + - Branden: We might have to kick the can until we get Alex and co or Hudson and + co or both. + - Brad: That's fine, I just think it won't make progress unless we discuss it + on a call. + +## Blog + - Brad: This is a minor update, but I'm trying to revitalize the blog a little + bit. Just to kind of maintain a sort of presence for the project. I'm kinda + sacrificing like depth for more frequent updates. It's only taking me 5-10 + minutes to write a post to highlight things. If there's an interesting PR + merged you can make a blurb about it and send it my way. + - Amit: That's really good. + - Branden: Yeah + - Brad: It's also helpful to have the `significant` tag to filter on. + +## PR #3576 Scheduler + - [Alexandru joined here]. + - Alexandru: I'm not particularly attached to it, so if there's a better idea, + I'm all years. + - Branden: Can you give us some context comparing this to #3701 -- is that + unrelated or tied into the same issue? + - Alexandru: I don't know + - Branden: Oh if you don't know it's related then it's probably a separate + effort. + - Amit: It is unrelated. #3701 is about process scheduling, and #3576 is about + capsule scheduling. If I can summarize this correctly, when there is an + interrupt, we go into a chip-specific scheduler to call into capsules based + on which interrupts have fired, but that is unaware of priority or any other + scheduling choices. You'll basically always prioritize which one you put in + the match statement, or something like that. Maybe that's not guaranteed, I'm + not sure. As opposed to doing something more clever. Correction: it's + whichever one has the lowest number. + - Phil: This discussion came up two months ago, and I pointed back to something + that came up four years ago. We've run into it in Cr50. We had a state + machine, and two interrupts would fire with timing tight enough it would + always handle a particular interrupt first. There have been other solutions, + being able to re-order in terms of priority and such. + - Amit: Do you mean the queue? + - Phil: #1181. + - Alexandru: This was done using hardware tools? I see NVIC + - Amit: I don't remember the specific details, but an approach would be to mask + the interrupt bits with NVIC priorities or something. Not mess, like filter. + - Pat: I think NVIC stuff was an optimization. At the time, we weren't doing + any prioritization, so I don't think these changes rely on having them. + - Amit: My take on this is as we discussed at TockWorld, I think there are more + efficient solutions to do this, given some potential changes to how interrupt + handling overall works in Tock. At least, my recollection of this proposal + seems like it is probably good enough for now. + - Alexandru: The discussion on the PR is if we need more structure than my + ad-hoc approach. This is connected between the chip, arch, and kernel crates. + Another observation my team has had from writing scheduler specs, the + scheduler not only makes decisions but also triggers kernel work. Somehow the + scheduler does more than just makes the decision. + - Amit: I'm not following that. You're saying w.r.t. your PR that a critique of + your PR is that it leans into that coupling even more? + - Alexandru: Exactly. Right now, the interrupt handling is a mixture between + the arch crate, the chip crate, and the scheduler. The scheduler, besides + making decisions, has this function to do kernel work. The scheduler runs the + kernel work, instead of the kernel passing scheduling decisions to the + scheduler. The scheduler needs to know the chip crate. + - Amit: I see. + - Alexandru: I think this is Ioan's PR from yesterday. I think he was saying + something related to that. This is connected with my PR, because in my PR the + problem of knowing the interrupts is an intricate problem between the arch, + chip, and scheduler. Brad's suggestion at the time was to have a better + infrastructure for that. + - Amit: I think I understand what you're saying about the inversion of control + for the scheduler, but that in itself doesn't seem to impact the ability to + use the system. It's more of a design improvement? + - Alexandru: Right + - Amit: Hopefully it would pay dividends down the line, but it's not like "oh + we can't do this thing because of the design". The interrupt priority for the + scheduler is an important feature to have. + - Alexandru: You have a good observation that my PR is not about interrupts but + about executing specific capsules. + - Amit: Right. But there are, in cases that you're interfacing, there are + certain kernel functionalities that need to take priority. I don't know + they're necessarily contradicting each other, there's a form that's getting + things done now. May not be the most beautiful in the long term, and a more + sweeping change would include revising how this is done, but within the + current framework we can evaluate on whether it improves more than it breaks. + If we change the scheduler more broadly, maybe that impacts how this is + implemented. Basically, I'm suggesting we shouldn't block Alex's PR on a + potential longer-term design discussion. Timelines are different. + - Alexandru: I'm okay with that, but + - Phil: There are mechanisms for this in the hardware but we're not using it, + we're just scanning the bits. To toss out an idea: we have interrupt priority + levels, we can keep multiple sets of bits, as long as the number is small, + and go from there. + - Amit: We're currently using the hardware bits. + - Phil: When you scan them, you don't get the priorities. If instead of one set + of bits, you had say four. + - Amit: Broadly speaking, that's the change Alex is approaching. + - Alexandru: The problem with the hardware approach is I might not want to + execute one of the interrupts. Executing the interrupt is just the bottom + half. + - Phil: That's why you disable them. + - Alexandru: Well, yeah. Technically I could disable them and mask them in + hardware. You're saying, if I don't want to execute that, I could disable + them in hardware, but then I don't know if they're pending. + - Phil: What I'm saying is that instead of using the interrupt pending + register, you'd maintain software state. In the interrupt handler itself, you + would index into the bitmask for that priority to set the bit. You could + always disable the interrupt, then it wouldn't set the bit. + - Alexandru: I need to do scheduling decisions like "as long as I have CAN + interrupts I will never execute UART interrupts". + - Phil: The way I would expect that to manifest is to put the CAN interrupts + into a higher-priority bitmask, then when I check interrupts, check that + bitmask first. + - Alexandru: I need to take a closer look on how this works. + - Phil: Maybe we should exchange some email or something. There are some tricky + parts to it, but this makes more sense. This is a problem we have encountered + many times. In Cr50 it used to be that if any interrupt fires your interrupt + handler can be triggered. I could disable interrupts 4, 5, and 6, but if I + then enable interrupt 4 and 4 fires, the system will see them all pending. + - Alexandru: I need to take a closer look at how the hardware interrupt + handling works. Thanks for helping. + - Phil: This is a recurring problem so it would be great to solve it. + - Alexandru: Regarding the second PR, I'll have to take a look. Can't currently + do a detailed design. I'll read the PR and comment on that. + - Phil: I have a comment where I said I was worried, but I read through it and + am not concerned. + - Alexandru: I'll take a look. diff --git a/doc/wg/core/notes/core-notes-2023-10-13.md b/doc/wg/core/notes/core-notes-2023-10-13.md new file mode 100644 index 0000000000..683beead06 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-10-13.md @@ -0,0 +1,78 @@ +# Tock Core Notes 2023-10-13 + +## Attendees +- Branden Ghena +- Alyssa Haroldsen +- Philip Levis +- Pat Pannuto +- Brad Campbell +- Johnathan Van Why +- Alexandru Radovici + + +## Updates +- Alyssa: Attended "Open Source Firmware Conference" and gave a talk on five tips and tricks for embedded rust. https://docs.google.com/presentation/d/1WaeCaDqaJjYKmaIOEiYUlXzdFhc62127b03xm4nqxi0/edit?usp=sharing + + +## Yield WaitFor +- https://github.com/tock/tock/pull/3577 +- Alyssa: It would be good to come back to this. When working on an async runtime, there was a way to structure yield that would save a lot of code size. It's been a few months since I was playing with it though. +- Alyssa: Refreshing memory, I wanted "Yeild-WaitFor-NoCallback" but it would also let you know whether a callback was run. So you know whether to run the futures or poll it. +- Branden: Wait, how do you want a callback? +- Alyssa: Hmm. Confused things. Does it wait forever on the callback? (yes) I wanted to wait on everything +- Phil: So a regular yield. Maybe you wanted to yield but to know _which_ callback executed? +- Branden: Isn't that solvable with a global flag variable or something? +- Alyssa: The issue was saving data everywhere instead of just having it returned in a register +- Phil: A code size thing +- Alyssa: Especially for the async runtime, which has a code size issue for sure +- Phil: Back to the PR, I think "Yield-WaitFor-NoCallback" has a big downside. You can block indefinitely waiting on a specific callback, which could delay all the other outstanding callbacks in the system. My concern is that this could create a new set of problems, which are maybe less significant, but we're not sure yet. Example, after printing for a while, we realized the usefulness of yield-for. So until we really write code, we can't see how big the wrinkle is. +- Phil: Example, two libraries. One assumes that it's got some latency requirement for its callbacks. The other uses "Yield-WaitFor-NoCallback" and stops the first from working with the added latency. +- Pat: This idea pushes me more towards the "Select" style of thing, where you can select what you do or do not wish to run. It gets messy once you have multiple owners/interests in a single app, which is what libraries sort of do. +- Alyssa: I would caution library users that aren't aware of the user's needs to not use the new yield. But the big addition here is not code size but safety. Guaranteeing no reentrancy is really helpful. +- Brad: We do have latency today as it stands +- Phil: Today if you do a yield, callbacks still arrive for other things. Other things still get the processor. +- Brad: That's true, if you do all the work in your callback. +- Phil: Generally, the libraries are expecting asynchronous callbacks. +- Branden: The library was always waiting on the user to eventually call yield. Here, we're just leaning towards longer wait times. +- Phil: Indefinite wait times +- Alyssa: I think this is a documentation issue. I see this as an academic concern that we could avoid in practice +- Phil: So a write works well, but what about a read? +- Alyssa: Don't do that. Make sure you're using things for the real library need +- Pat: Thinking about how other libraries and runtimes do this. In C, the default is that everything blocks and nothing is async except for signals. +- Phil: There's async posix and there's posix +- Pat: So expect everything to block unless you explicitly sign up for async everything. +- Phil: So for me, the blocking commands seem a bit safer in that developers can explicitly decide what's going to block. +- Alyssa: On the other hand, we've discussed that it's not great to have a split ecosystem of drivers where some do and some don't block. They should match the userspace goal +- Pat: Like the Rust ecosystem, throw things at the compiler or typesystem. So maybe some kind of "blocking" capability gets passed if the library wants to privilege to block. Capabilities are for handling things that aren't memory safety, but could be or not allowed by the system. +- Phil: What bothers me with "Yield-WaitFor-NoCallback" is that you get a mix of libraries which do or don't use it and do or don't expect it. It becomes unclear from the userspace about which libraries you can compose together. Knowing which libraries could malfunction could be a documentation or naming thing +- Alyssa: What would make something "Yield-WaitFor-NoCallback" safe? +- Phil: A library that doesn't expect timely delivery of async callbacks? +- Branden: My example would be a radio driver partially pushed into userspace. It's got timing guarantees on responding to packets, or else an acknowledgement gets missed altogether. So it can't function with arbitrary long delays +- Phil: And a safe example would be a printing library. It doesn't expect asynchronous delivery of callbacks. +- Alyssa: So something that would malfunction if callbacks are delayed versus something that doesn't have a timing requirement +- Phil: Another example. So lets say I write a dumb library which responds to the button interrupt. When it gets a callback it toggles the LED. So it waits for async callbacks and does a toggle. This is not safe for "Yield-WaitFor-NoCallback". Because if the user presses the button, there could be a noticeable delay before the LED changes. +- Phil: And a print driver doesn't relinquish control, it does a yield waiting in a loop. The library doesn't return until it's done. So it's safe. +- Phil: So if the print library does a loop and some other callback runs, then that other callback could do a "Yield-WaitFor-NoCallback". So there's a delay there +- Branden: Is that an issue? Print doesn't care about the delay +- Branden: This is all a timing/latency issue. So things that are timing sensitive should go in separate apps. +- Alyssa: This is up to the discretion of the library author +- Phil: And we could mark them in some way so we can tell them apart +- Phil: I do remember Oxide starting with Tock, spending a week tracking down a bug because they missed some semantic about callbacks. So I worry about someone just composing libraries and things not working for subtle reasons. +- Alyssa: I do get that concern +- Phil: The reason this came up for me is that it's an implementation decision whether to use "Yield-WaitFor-NoCallback". So this internal detail bubbles up in a way that users need to be able to see and understand. +- Alyssa: I think it's a specialty syscall for specialty circumstances and should be communicated as such +- Pat: I agree with the complication and am not sure about the answer. Mixing async and sync is hard and maybe you just need to choose which world you live in. It's nice to have this sync capability but mixing it will always be hard and awkward. So being explicit with it seems like it could be important. Compiler-enforcement would be nice here instead of documentation-enforcement. +- Phil: Should we have a libtock sync and a separate libtock async? +- Johnathan: Libtock-RS is kind of in a halfway point. It's kind of more sync, but there are often operations with a timeout or some way to abort it. That's done in Tock by running two things in parallel with async mechanisms. And that's where you do a couple things at a time, but it's still a synchronous structure for a program. Definitely a hard problem in general. Much easier to write synchronous APIs, but cancellation becomes a real concern. +- Alyssa: There was an Open Source Firmware Conference about using Rust in embedded. The example of running an operation with a timeout was so elegant with futures. +- Johnathan: Libtock-rs is like that now, but with uglier syntax. So it might be achievable to support futures in Libtock-RS if everything is together. The compiler could figure it out. Where doing everything async everywhere would lead to no hope that the compiler could take away all the fluff. +- Johnathan: So far my experience is just larger code sizes. Still a work in progress +- Alyssa: I agree that an async runtime will always have some problems. The way libtock-rs is doing it now is fine enough, just not super elegant. Getting rid of closures for unowned memory seems impossible to get rid of. +- Johnathan: Maybe with pinning. Whoever owns it will need to pin it. +- Alyssa: If you have your allow API instead of accepting a mutable reference, accepting pins, that could work maybe. Hard though. +- Johnathan: Taking a step back, if you want to write a driver with an async implementation, you need a handle that says what lifetime you can use data for. The lifetimes look different between pin and closures though. One change we could do is instead of having a closure-based API, you app could have every driver expose operations which take closures for what to do while waiting for the operation to finish. +- Alyssa: I think it should be possible for closures, but it should generalized to a larger zero-copy trait. +- Johnathan: No time to look at it anytime soon though +- Phil: So my conclusion is that there will be issues, but they're probably minor and we'll figure out a way to manage them. My concerns were somewhat reduced. So I think we can move forward when we have time + + diff --git a/doc/wg/core/notes/core-notes-2023-10-27.md b/doc/wg/core/notes/core-notes-2023-10-27.md new file mode 100644 index 0000000000..b1d4f3acaa --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-10-27.md @@ -0,0 +1,88 @@ +Tock Core Notes 2023-10-27 +========================== + +## Attendees + - Pat Pannuto + - Andrew Imwalle + - Branden Ghena + - Amit Levy + - Alyssa Haroldsen + - Brad Campbell + - Leon Schuermann + - Johnathan Van Why + - Tyler Potyondy + - Alexandru Radovici + +## Welcome Andrew + - Andrew Imwalle: Working on team at HP Enterprise on a security processor + - Amit/Andrew: Project has been going on long enough and is now mature enough that there is likely a stream of upstreaming PRs to come (TickV zeroizing being the first) + +## Networking WG Update + - Branden: WG is bi-weekly, but as a refresh of last week's update: + - Branden: Reserving port numbers for Thread (led to longer core team call... see last week's notes) + - Branden: Other big focus point is buffer management—sync/async, contiguous or not, etc; Leon driving questions around how much we can make the type system to while still providing a lot of runtime usability. + - Branden: Pretty close to something we can actually try in real interfaces. SD Card and Display drivers may actually be first target as the code paths are more mature but share similar buffer concerns. + +## OT WG Update + - Brad: Merged updated to autogenerated register interfaces; new tool removed the `top_` hardware artifact that had been leaking through + - Brad: Also some updates coming down the pipe in GPIO and I2C + - Amit: Historically, there had been two paths for OT+Tock, one driven by Alistair & co, and the other by OT core / etc, and we had seen some divergence. Is that still the case, or are the more recent PRs resolving some of this? + - Johnathan: The OT repository is pulling in Tock as a dependency. We're doing what we can in the Tock project to have things directly in upstream. We will always have some indirection, e.g. custom board, but a lot of the drivers/etc will stay upstream + - Johnathan: Long-term, OT has some minimum code standards, and it is in our interest/goal for upstream to meet internal requirements + - Amit: What's the resolution of the OT PMP / ePMP / etc situation? + - Leon: This has been involuntarily on the backburner for a few weeks, but it's on critical path of a research project, so things should move forward again soon + - Leon: I've been at SOSP and the Workshop on Kernel Safety and Isolation; interesting talk on Veris, formal verification in/on Rust, from some MSR folks + - Leon: Led to conversations around our (e)PMP implementation, and think there may be some opportunity for FV to be applied to embedded systems without being unreasonable / intractable; think (e)PMP could be an ideal subsystem to start with that + - Leon: However, do want to get current code finished/shipped before looking to implement FV + +## General Updates + - Alexandru: Sent email to ESP, we'll see what comes + +## Form a Community Working Group? + - Amit: Proposal—we should have one + - Amit: Primary motivation is making sure we get moving soon on TockWorld7, but imagine more in the future + - Brad: What would be in the purview of this WG? + - Amit: Events like TockWorld, but also other kinds of community outreach, e.g. tutorials, and possibly certain kinds of documentation + - Pat: Maybe also the public face of Tock, the main website, the blog, etc + - Leon: Maybe separate issues? I would be like to be interested in helping events, company outreach, etc + - Amit: Alternative proposal is not a community WG, but a 'TockWorld Task Force' + - Amit: The difference is that it's time-limited and more focused + - Amit: Can take on work and discussions that we wouldn't necessarily have here, but not a WG with unbounded time + - Leon: I think that make sense, and I would like to spend some time on that + - Leon: A more general community WG for documentation / etc + - Branden: Logistic concern—do we have the person-hours to staff another working group (e.g., Leon is on... all of them) + - Amit: I agree with both of those... with my advisor hat on, I appreciate Leon's desire to help with community things, but "you got research to do man" + - Amit: I'd propose this includes at minimum Pat (as it's going to be in San Diego) + - Amit: I am actually not formally on any other WGs, so I volunteer; probably don't need more than 1-2 people + - Amit: It could just be in principle me and Pat... + - Amit: The point is largely around accountability + - Pat: Agree that smaller-set accountability for things that 'core' is responsible for e.g. web updates useful + - Pat: See value in continuity of event organizers; WG can persist across TockWorld events + - Brad: Unclear if this is for 'events in 2024' or 'events generally' etc; Task Force model makes more sense now, and if TF is successful, can shed light on purview for a more sustained WG + - Amit: Propose a TockWorld 2024 Task Force with founding members of Pat and me + - Brad: I volunteer to be on this as well. + +## Yield Wait For RFC? + - Amit: This has stagnated for ~a month + - Amit: I don't recall where it stands... Pat? + - Pat: Life and family in the way, more so especially in November :/ + - Brad: I thought that we were converging; next step was to actually implement enough apps in userspace to see if it actually made a difference + - Pat: I did some of that—was given 4 apps to port, I did console, the other three were a lot of work for speculative update and console so worth it that thought we'd decided that was sufficient + - Amit: Okay for this to move a slow in the short-term future, but can't fall on the floor completely + - Amit: I will, at least tentatively, take over this PR for the near-term + - Amit: Primary mandate will be to summarize status, disposition, and next steps that need to happen; will possibly take on the implementation depending on result here + - Alyssa: Anything you need from me right now? + - Amit: Almost certainly yes, but I don't know what that is right now. Part of my pending summary will include specific asks where appropriate. + - Alyssa: Next week I can have this read that we can discuss a bit better + +## Open Discussion + - Andrew: Is there any kind of formal release schedule or plan? + - Amit: We've experimented with a few different styles. We tried a time-based cadence, but didn't fit flow well. Current model is largely critical mass of accumulated changes + - Amit: Looking forward, we are in the process of hiring a staff engineer to help support things like CI, security audits, etc; would include managing releases, so next release could be a great thing to have them on board for + - Andrew: Is there a timeline to hiring? + - Amit: Have several applications in, need to conduct interview process, then hiring logistics; ideally on the order of month to hire + - Amit: Is having a release in the (more?) near future useful? + - Andrew: Not necessarily in the near future, but as we start contributing more, would be useful to see changes we contribute come out in stable releases + - Branden: For a long time, we've been pushed by demand—if there's a reason a release would be useful for someone, that's a reason to do a release + +Fin. diff --git a/doc/wg/core/notes/core-notes-2023-11-03.md b/doc/wg/core/notes/core-notes-2023-11-03.md new file mode 100644 index 0000000000..e996e86c4d --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-11-03.md @@ -0,0 +1,233 @@ +Tock Core Notes 2023-11-03 +========================== + +## Attendees + - Alyssa Haroldsen + - Amit Levy + - Brad Campbell + - Branden Ghena + - Johnathan Van Why + - Hudson Ayers + - Leon Schuermann + - Phil Levis + - Tyler Potyondy + +## Updates + - Leon: Revisiting the RISC-V PMP update. This changes a bunch of critical + infrastructure, so we want a few rounds of good reviews. More importantly, + it's not ready for merge just yet. While switching to the kernel memory + protection, I found the VexRiscV CPU has an integer overflow bug. I proposed + a fix upstream. Hopefully it'll be merged soon and we can pull it in to + exercise these code paths in our CI. + - Brad: Update to the libtock-c build system discussion from a month-ish ago. + It started from an upgrade of the newlib version, and has graduated to an + option to not use the users' libc headers and instead use our own newlib + headers. Now we don't rely on package managers shipping them, just need a + toolchain. It's in place but the RISC-V GCC toolchain is still new and + fragile. Works with the newest toolchain but not older versions. Plan is to + compile libraries with the older version and hope the work with the newer + version. + - Branden: It's really exciting to get us off from "whatever happens to be on + somebody's system". + +## Networking WG Updates + - Branden: Spent a bit of time discussing buffer management, and a lot of time + talking about what it will look like to have an OpenThread application-land + library. Particularly, if we can take C libraries that exist and package them + into an application, what would that look like -- do we have the right kernel + support for it? What will it stress? Not an immediate concern, but + brainstorming for that process. Leon had good papers to share on latency and + measurement, which Tyler is looking into. + - Brad: It'd be interesting to have a stack like that. + - Phil: This is what we did for the original 15.4 stack, which I ported to a + userspace process. It turned out to be valuable, because it gave us a working + stack. Made it easier to do A/B testing as we reimplemented it. + - Leon: Also merged the STM ethernet support into the Tock ethernet branch. + Hopefully in the next week or two we'll have a big PR from the ethernet + branch towards Tock to merge in the infrastructure. + +## More updates + - Amit: I've been discovering more Tock users, including a Microsoft team using + Tock in a secure enclave-type use case. Sounds like there is some duplication + of efforts. Promising -- means maybe there's more incentives to work across + and with upstream. + - Phil: What's the group at Microsoft? + - Amit: Folks working on Chariot, not the same as Caliptra. They also announced + at the event that they are making an enormous investment in Rust. + - Hudson: That's exciting. Do you think you'll get them to join calls soon, or + just hoping for repo contributions? + - Amit: One of the two. + - Hudson: It would certainly be cool to see something from them. Maybe a + presentation like what Alyssa's done -- what are the pain points, what do you + like -- but I realize that's a big ask. + +## `tock` PR #3597 + - Johnathan: PR 3597 is stalled on what I think is a QEMU inaccuracy. No-one + working on the PR has QEMU development expertise or the time to debug QEMU. + My inclination is to just disable the test for now, but looking for others' + input. + - Leon: Had similar issues with QEMU-hardware discrepancies while working on + PMP. Switching contexts to fix QEMU is a lot of overhead. I don't think it's + reasonable to do this for every bug. The other issue we'll be facing is I + know OpenTitan is maintaining a QEMU fork with a more accurate hardware model + of OpenTitan. Those two models will likely merge, and the upstream OpenTitan + development will target their version of QEMU, which is not the upstream QEMU + version. + - Hudson: Why wouldn't the upstream version try to take in the changes? + - Johnathan: Aware of a difference in technical design that may not be + acceptable to upstream. + - Amit: We only care about QEMU to the extent it is a good representation of + the real hardware. If emulator quirks are really getting in the way, it seems + counterproductive to stall progress on supporting the hardware because the + emulator is incorrect and is failing the CI. + - Hudson: Does this PR passes the forked QEMU's CI? + - Johnathan: That has not been tested. + - Amit: One of the FPGA boards is becoming available to potentially stick in a + test rig. My proposal would mean to not block on this, if that means + disabling the test. Using the forked QEMU seems fine in principle, but that + meant compiling QEMU as part of the CI which takes a long time. + - Leon: We do that anyways. We're tracking latest head. + - Amit: I would say if we're confident the problem is with the emulation and + not the code, we should move forward with the PR, and if that means disabling + the test, then disable the test. + - Leon: The one thing that makes me skittish, is that right around the time + this PR came up, is we fixed an actual driver bug thanks to a QEMU-based + test. While there may be some divergence between the hardware and the model, + there seems to be a benefit to having those tests. + - Amit: Two questions. First question: would that have been caught on a + hardware-based test as well? + - Leon: The issue with this bug is we encountered it on every 10th or 20th CI + run. Me updating QEMU to include the PMP fix changed timing and made it + consistently reproducible. I don't know if hardware would've let us reproduce + it in the same way. + - Amit: Sounds like there's nothing special about QEMU. + - Leon: There is some benefit to heterogenity in our infrastructure. I think + we'd lose something if we disabled them. + - Hudson: The magical thing about QEMU is we have it set to run on every PR. + - Amit: I'm not suggesting the long-term solution is to not run QEMU tests, I'm + suggesting a temporary solution of either disabling that test or to ignore + the QEMU CI for now. I don't the solution is to not merge the PR. + - Hudson: Johnathan, is it one test or all of them that aren't passing? + - Johnathan: If I remember correctly, it's just one test, that checks if the + kernel boots. + - Leon: We run one big integration test, then if that passes run a bunch of + smaller tests. + - Johnathan: Oh, it's the initial test that fails. + - Hudson: We've had some issues be caught by the hifive tests, but opentitan + seems to have more tests. It sounds like it would be easy to switch to the + QEMU fork. If we start ignoring the QEMU CI, then it'll become hard to turn + it back on. If it's hard to switch to the fork, then probably not worth it. + - Leon: I'm concerned about a temporary fix becoming a permanent one. What is + our policy on this? + - Brad: Nothing. We're Tock developers. We don't expect people to contribute to + QEMU whenever we hit issues. It's a tool. If it works, great, but if not then + we can't always fix it. + - Amit: It would be great to test every PR, but if that's not practical, then + what Brad said. In the medium term, I think the thing is to try using the + OpenTitan fork of QEMU. I'm acknowledging that is a nontrivial effort, and + this is a trivial PR. What should precede what? + - Hudson: I was trying to get a feel for the level of effort of this. Is it as + simple as pointing to a different repo, or is it significantly more involved + than that? + - Leon: My knowledge base it the fork exists, but I have not used it so far. + - Hudson: Is it publicly downloadable? + - Leon: It's a public repository. lowrisc/qemu. It's in a branch called + ot-earlgrey + - Amit: May be as simple as pointing to that. Could try that real quick, make + it part of this PR. + - Hudson: That's my stance. If it's that easy, then great, but if not, then we + shouldn't ask the PR author to fix CI. + - Leon: I agree. My one remaining question is we have someone working actively + on Tock who is an upstream QEMU maintainer. Would we want to consult Alistair + on this? + - Amit: Ideally, this would've been discussed within the OpenTitan WG. I think + we should be able to do things temporarily that are not permanent statements. + I don't think we should block individual PRs on QEMU bugs. Whether a fix is + upstreamed is a separate question and we should talk to Alistair about that. + - Leon: That makes sense to me. + - Hudson: I think we should certainly have a discussion with him. He's been + given a couple heads up on this. + - Brad: I think we can do things in parallel. Get on PR through, and have + another that tries to make improvements. The more we stack things serially, + the harder it is on us. + - Johnathan: So, try pointing to lowrisc's QEMU in the PR, if that works + trivially, great. If not, disable the test in the PR. Either way, discuss it + in the OpenTitan WG. + +## `libtock-c` PR #353 + - Brad: The question is where we put all the pre-built things. The main reason + we're pre-building things is so we can compile with PIC. Today we're saying + "we only have hardware boards with these architectures, so lets cherry-pick + these ones". However, if we're building them all, why not include them all? + That drives up the size. What do we want to do moving forward? + - Amit: What are the options? + - Brad: Continue to commit the raw compiled libraries into git as we're doing + today, we could commit zipped (or otherwise compressed) versions into git, + could use gitlfs, or we can package it and host it on whatever server we want + and have the build system fetch and use it. The PR does the last one. + - Amit: GitLFS essentially looks similar to what you're doing manually here. + Leon has a better technical sense of this, but it is essentially committing a + hash of the artifact into the actual repository, with a pointer to the + artifact on another system. It tracks changes that way. Indeed the goal is to + avoid tracking large files in git which git is not particularly good at. + GitLFS is pretty specific to GitHub, which is not ideal. I think it's not + supported it git natively -- you need to install an addon, although I think + most git distributions do that anyway. + - Alyssa: It also involves billing. + - Leon: I think that's an accurate description. I've tried it a few times and + never got it to work. + - Branden: That was my opinion. I tried it 6 or 7 years ago, and I remember it + not working at all. + - Alyssa: I recall it working, but we had issues with billing and quota. + - Leon: It's really expensive. GitHub has insane charges for it. + - Alyssa: How big are the actual files? + - Brad: About 150 MB for one version. + - Leon: The issue with GitLFS is you're paying for traffic quotas, and we don't + have any control how many times a user tries to download it. Alistair's + concerns are valid that files we host elsewhere may be gone, but that'll also + happen if Tock stops wanting to pay for LFS. Most of us are at universities + with web space and bandwidth available for free, so I don't think that'll be + much of a concern, especially with multiple mirrors. + - Branden: Users can also build it for themselves, we presumably have + instructions. + - Amit: It's unlikely we'll run up tens of thousands of charges for this; we + can almost certainly find a way to afford GitLFS or run it on our own. The + main advantage of GitLFS I see is that by default, we would get a history + view. Maybe with this strategy, that wouldn't be the case, as the mirrors may + want to remove the old versions. I think that is almost entirely ameliorated + if this is a replacement for building something locally. Provide hosted + versions for people using real releases, with a fallback of building from + source. + - Leon: We could also enforce a policy of tagging uploads with the date, and + not delete them. + - Amit: Ultimately, this is what package managers do. + - Brad: We definitely could be doing a better job of explaining how we got to + the precompiled thing. It will not be easy to recreate one of these things + back in time, because toolchains and whatnot. We could better document that. + Certainly the intent is there that you could recreate it. + - Amit: Is it not something that we could put into a script? + - Brad: What you get is highly dependent on the toolchain you use when you run + the command. + - Amit: Leon, sounds like a job for Nix. + - Leon: Yes, but also I was going to suggest Docker. + - Johnathan: Second Docker. + - Leon: I love reproducible artifacts as much as the next guy, but I don't + think it'll be critical if we lose year-old toolchains at some point. + - Amit: In the long run, it is important. In general, an important thing in + practice is that people may need to reproduce and patch an old version + deployed in the field. They just need the particular version used to build + an application. That's also maybe big-boy problems. + - Leon: You would hope they did due diligence on their part and saved the + binaries they needed. + - Amit: The question is how attractive is our setup for people who have those + concerns. + - Brad: Realistically, are we going to pay for GitLFS when we can have it for + free without doing anything? + - Branden: I think it's fine to host at another location. + - Brad: Is there a service that will run a Docker container for you? + - Amit: Azure? Google Cloud Run? Lambda? + - Brad: Because I don't have the RISC-V binaries, I'm motivated to run this in + a specified environment. + - Amit: We could do this in GitHub Actions. + - Brad: That sounds hard. + - Amit: GitHub actions is just Docker. diff --git a/doc/wg/core/notes/core-notes-2023-11-10.md b/doc/wg/core/notes/core-notes-2023-11-10.md new file mode 100644 index 0000000000..c6bb4b200e --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-11-10.md @@ -0,0 +1,89 @@ +Tock Core Notes 2023-11-10 +========================== + +## Attendees + - Branden Ghena + - Leon Schuermann + - Alyssa Haroldsen + - Hudson Ayers + - Brad Campbell + - Johnathan Van Why + - Alexandru Radovici + - Amit Levy + +## Updates + +### Newlib + * Brad: For libtock-c newlib packaging process. Last time we talked about this, there were some questions about where to store the artifacts and how reproducible it is. I now have a docker file that is pretty close to being able to build the artifact both for us and in our CI. It seems that newlib is not as extensively tested as I had hoped it was. + * Hudson: The reason you need a docker container is that Ubuntu 22 shipped with GCC 10? + * Brad: We just wanted a way that even if the artifacts disappear, someone could go back and rebuild newlib for some moment in time. As a side effect, and why I really wanted it, is that it lets me build it at all. There is indeed a conflict with newer and older versions of GCC. For RISC-V newer versions of GCC build a newlib that doesn't work with old versions. + + * Leon: I have a CW310 board, which is a reference for the OpenTitan RISC-V FPGA. Zerorisc provided it. I may or may not be able to provide remote access to that board for testing changes. + +### Syscall Tunnel + * Johnathan: I'm building a proof-of-concept for what I call the "syscall tunnel" for libtock-rs. It forwards system calls over a UART to allow apps to run on a Host computer and forward requests to Tock on a microcontroller. + * Johnathan: I'm running into a rough bug here, which might be undefined behavior? I can't reproduce in Miri. + * Branden: What's the purpose behind that? + * Johnathan: Testing infrastructure. Make calls to a tock board to perform operations. Then run a check on some other board to see that it received it. + * Brad: So the Tock board runs some userland app that receives the syscalls? (yes) + * Johnathan: Right now it needs the pin-based allow API + * Hudson: For the device under test here, how does it communicate over UART back to the host? Does it send additional system calls? Or does userspace have raw control of the underlying UART? + * Johnathan: Just normal console calls. Works fine. Although it gets really confused if the host makes a console call. + * Branden: I have some undergrad doing a similar thing: two microcontrollers running Tock which could send syscalls back and forth between them + +### Ferrocene Compiler + * Hudson: Folks might have seen that Ferrocene had its first certified compiler release just a few days ago. Various certifications. Cruise heard about it and was interested now that it's automotive certified. Ferrocene is just the upstream rust compiler, but like 8 months back or so? I think that once we get to running on stable rust, it would be interesting to try to match up Tock's stable rust with Ferrocene's. + * Amit: I had a chance to talk to Florian at Ferrocene and asked some questions. Some clarifications are that Ferrocene is not "certified" but "qualified" which is essentially an attestation that the process the company went through to choose the compiler and modify it plus the process they have for patching bugs is up to snuff for the automotive agency. To clarify that it's maybe a little less meaningful than I had originally understood, although I agree that tracking Ferrocene releases would be nice. + * Amit: Certification seems to only happen on actual products. + * Alex: The problem is still that the core library isn't certified. So I'm not sure how you can use it. Another company, AdaCore worked with Ferrous Systems and also has a "qualified" compiler now, but still no library. We definitely need that + * Hudson: There may have been a disagreement about open sourcing from what I've seen + * Amit: The answer I got from Ferrous systems about the core library is that compiler uses the core library and it's not necessary for the core library to be certified to use the compiler. And that you "could" use your own core library for compiling your program. + * Alex: Yes. I just don't want to write my own library and certify it. That's a lot of stuff. Something like 60K lines of code. Libc is comparably MUCH simpler, like 1K. + * Alyssa: I think you could have way less Core library and still "compile". Although it would be the most basic thing that works. Way less lines, just no functionality. + * Amit: So it's worth exploring what subset of the Core library is actually necessary for different parts of Tock. + * Alyssa: I think any lang item that's mentioned has to exist. Lang items connect the compiler to the core library. + * Alex: Iterators are required for sure if you want to do for loops... + * Branden: Panic info too, I guess + * Amit: https://github.com/rust-lang/rust/blob/master/compiler/rustc_hir/src/lang_items.rs + * Johnathan: Funny. Does this solve our "core fmt is bloated" issues? If we write our own core we could do better? + * Alyssa: Not without changing the API. `ufmt` has a different API. You could make an alternative library removing most functionality. + * Johnathan: Panic utilities might rely on some functionality + * Alyssa: Maybe a lot of it could call an undefined method, and you could see what is or isn't used + * Amit: Interesting to consider. To the extent that it's a possible necessity to have an alternate Core library, it would be an opportunity to change some aspects. We could remove things we don't want + * Alyssa: There is a question about how much you want to break from upstream core. I personally think that certification should happen on the upstream Core, if possible + * Hudson: I think the cost to certify is somewhat linear with the volume of code. Which is a problem + * Alyssa: A problem is that upstream core compatible code would end up having almost all of that. + * Amit: In other scenarios, like security certification, there are other avenues like looking at the binary which maybe don't need a wholly certified toolchain and libraries + + +## 64-bit Tock Support + * Amit: Heard from a few people that there is at least one compelling use case to support 64-bit Tock, besides Host-based emulation. For tagged memory architectures where the only option is 64-bit. Probably ARM, maybe RISC-V. There might be some PRs pushing it upstream at some point. Might be worth putting some thought into this in advance. What would make those dealbreakers and what concerns might we have? + * Brad: Our concern abstractly has been cannibalizing what we're good at to support an extension. So we could end up having internal APIs designed so that it's not how you'd do it with a 32-bit only OS, even though that's really our sweet spot. The tension there has been the concern. + * Brad: It seems like there could be a way to do it. It would need to be a concious effort that it'll look strange because it's really a 32-bit API being shoved into a 64-bit system + * Amit: So maybe the system calls are still 32-bit information, even if the registers are actually bigger. Is that what you're thinking? + * Brad: Yes. That's a good example of tension + * Hudson: It seems possible that there are more examples. + * Branden: I have a hard time thinking about what the issues will be? Certainly the lower-level assembly for swapping into processes, but that would be different for any new architecture. + * Amit: Alyssa looked into this? + * Alyssa: Predominantly places where `usize` and `u32` are conflated. I think we resolved most of them + * Johnathan: I don't remember the kernel side implementation. The libtock-rs side just used `u32` for a lot of things even when `u64` could have been used. Command arguments for instance, could be larger which could give more capabilities, but would be a difference. In emulation we do limit to 32-bits for emulation. + * Alyssa: Passing pointers through command is a big issue here. + * Johnathan: And we don't pass pointers through command in libtock-rs. + * Alyssa: If I changed the syscall interface for 64-bit, I'd make things pointer size but not `usize`. + * Amit: Not sure what you mean + * Alyssa: Rust sort of supports 16-bit systems too. So `usize` can't be used directly. Some abstraction instead. + * Branden: Presumably peripheral registers would be 64-bit too. So they would need to write to 64-bit registers in the drivers. But our register library should "just work" for that? + * Johnathan: I strongly suspect that when these PRs come, there will be a tension. They'll have a 64-bit view of the world, compared to our 32-bit view of the world. + * Alyssa: Really we just need to avoid pointers not being address size + * Brad: For sure about Johnathan's point. You can always just not use upper bits. But if you expect that you can, there'll be an issue. + * Alyssa: I think my concerns are similar to any system supporting both 32 and 64 bit, not unique to Tock + * Brad: Well, we need to decide if we're really providing value to the 64-bit space. Do we want to support it well, or just at all? Other systems focus on 64-bit and are okay with 32-bit being clumsy. + * Johnathan: I don't remember the resource constraints for the 64-bit systems. If they have drastically more RAM/Flash that'll also cause a difference in thinking and conflicts. + * Amit: And again like Brad is saying, a place where Tock is maybe not bringing as much value + * Hudson: PR #2041 had some 64-bit thoughts https://github.com/tock/tock/pull/2041 + * Hudson: This was 3 years ago. It partly waited on the author and partially a reluctance on our end. Worth looking at the discussion to see what changes were proposed + * Branden: Going back to the start, why is tagged memory interesting to people? + * Johnathan: CHERI is what people are very interested in + * Amit: And broadly, tagged memory is a way of doing fine-grained memory access control + * Alyssa: Such that exploits are much harder to gain access to memory + diff --git a/doc/wg/core/notes/core-notes-2023-11-17.md b/doc/wg/core/notes/core-notes-2023-11-17.md new file mode 100644 index 0000000000..caee0f7eb4 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-11-17.md @@ -0,0 +1,148 @@ +# Tock Meeting Notes 11/17/23 =================================== + +## Attendees +- Hudson Ayers +- Amit Levy +- Leon Schuermann +- Branden Ghena +- Alyssa Haroldsen +- Jonathan Van Why +- Andrew Imwalle +- Tyler Potyondy +- Alexandru + + +## Updates +- Leon: PMP implementation is still waiting on upstream fixes in the upstream + RISCV implementation to trickle down, then they can update an external + dependency and get that merged. The dependency is a softcore CPU + implementation +- Tyler: Submitted CPS-IoT tutorial proposal. Minimal 1-pager that Pat helped + with. Hopefully happening in the Spring. CPS-IoT is IPSN + RTAS + IoTDI + 1 + other. Mix of real time and sensor net conferences. +- Tyler: Goal is to advertise Tock as a sensor platform you can easily get up + and running for IoT / networking research. +- Amit: Probably will be a bit different than other tutorials as a result + +## Networking Group Update +- Leon has been making some buffer management updates. Effectively, trying to + find a way around the Rust compiler to have a set of abstractions for + representing buffers that allow us to verify at compile time that a set of + network layers conform to hardware’s expectation about reserved headroom and + space toward the end of a buffer + +## TockWorld Planning Update +- Survey coming out soon with times / dates surrounding June +- If there is anyone not on this call or that Brad/Pat might not obviously + think of, send those folks to Amit + +## Yield Wait-For System Call discussion +- https://github.com/tock/tock/pull/3577 +- Amit: I added a summary at the bottom of the PR +- Amit: Goal is to find a path forward +- **Reading Break** +- Amit: My suggestion is that we accept some unanswered questions and focus on + just finding a path that seems viable and then we can learn from how it goes +- Amit: Summary of Yield-WaitFor-NoCallback (YWFNC): There are two problems + with current Yield system call: it is too easy to write app code that will + blow up your stack, in the case that there are calls to yield() within a + callback. Additionally, there are requirements for application code that + operates synchronously (efficiency or safety reasons). Currently those are + implemented by faking blocking calls but that is often insufficient. +- Amit: Competing goal is to support better patterns without having two + parallel worlds of Yield and “blocking yield” that capsules and apps have to + implement in parallel. The goal is for modifications in capsules to support + both paths to be minimal +- Johnathan: That characterization seems reasonable +- Amit: Johnathan can you elaborate on this from the libtock-rs perspective? +- Johnathan: My syscall tunnel app work has given me some perspective on this + problem. It has to run normal yields until [garbled]. So that is an + interesting thing I have not seen represented in the TRD. +- Amit: What I was getting at is that there are optimizations you can do in C + like only ever registering one callback once, but doing the same thing in + Rust does not work safely +- Johnathan: That is true with the current libtock-rs APIs, but it should be + possible with the pin-based APIs using statics and a sync-wrapper +- Amit: But it still does not solve the re-entrancy issue more generally +- Johnathan: Yeah re-entrancy is a pain +- Alyssa: There could be an up call registered for an entirely different + subscription which could contain a print, so does YWF, other unrelated + upscales can be invoked, right? +- Amit: The semantics of YWFNC, no callbacks period run on that process +- Alyssa: I see, no callbacks on the process, not the subscription +- Amit: A significant difference from what I had proposed originally, but that + is a good point that that is necessary to actually address the reentrancy + issue +- Alyssa: I want to clarify that if I have code with a subscribe which has an + up call waiting for a stack variable. Would it be correct to replace that + with no subscription at all, just a command then a YWFNC. +- Amit: That is correct, I believe. The data that would have been passed in a + callback will be returned in YWFNC. And the subscribe number is passed to the + kernel in the YWFNC. +- Alyssa: Can you add in your summary at the bottom a description of + yield-result? +- Alyssa: What is “Yield-Result”? Whether an upcall occurred or not? +- Johnathan: Yes +- Amit: Where are we seeing this? +- Alyssa: I ctrl-f’ed `yield-result` +- Amit: I think that is a mistake actually. YWFNC should not have a + `yield-result`. +- Amit: You found an inconsistency. This is here because the TRD includes + multiple versions of yield-waitfor and some of them do this. We can take away + one of the arguments if we stick with YWFNC. There is no way for an up call + to be called! +- Alyssa: I propose that we use the same registers as other yield calls to + reduce kernel overhead +- Johnathan: They overlap with the yield number if you try to put them in the + same spot as subscribe +- Leon: I think we currently always extract the same registers anyway and then + route them to the right calls +- Amit: Alyssa is probably right that we end up with more register shuffling + this way +- Alyssa: Yeah, but sounds like it is a moot point. +- Amit: Question for Alyssa: it was your suggestion that we have an optional + subscribe, the idea was that YWF has similar semantics to YWNFC except that + you optionally pass in a callback, and if you do so that is called before + returning. Is that accurate that you suggested that? +- Alyssa: I think so — looking for my comment +- Amit: There seems like a lot of buy-in for no callback right now +- Alyssa: I do like my option for a bit more control and it seems it would work + well for Rust as well +- Amit: My suggestion is that we just go for it, and once we have accumulated + some experience and evidence with this design we can evaluate it better. Even + with that suggestion there are two things I think might be worth hashing out + ahead of time. There are other potential costs in terms of reentrancy / + correctness issues that a design might incur. And it would be good to have + some way of evaluating whether on net we have improved in those dimensions. +- Amit: We also want some gauge for what would count as a code size or + performance benefit. It seems likely there is overhead to doing any of this + in the kernel. We want to see some benefit on the application side and that + would be nice to quantify. +- Amit: In Pat’s prototype C application there was some benefit and we want to + see if we can do better with a better implementation. It would be nice to + have some idea of at what point that tradeoff is positive: 1 application? 10? + What complexity application? +- Hudson: Are you advocating an order here? +- Amit: Not necessarily since it seems we are gonna go with this anyway. Just + want a sense for what are good benchmarks. +- Alyssa: I think the kernel implementation having less than 500 bytes of + overhead would be good +- Alyssa: The benefits from blocking command were hard to quantify because of + other simultaneous changes but we would like to see similar performance. We + can ask Jett and see if he remembers +- Amit: If there are two scenarios, one is we get a benefit on the application + side on each driver that uses this pattern, and the other is we get it for + each callsite of that driver, it seems like maybe the latter is better? Do we + have a feeling on this? +- Hudson: A great test would be, once we implement this, if Ti50 uses it, does + their size improve or worsen? +- Amit: Say we don’t merge this or update libtock-c / libtock-rs in the main + branches, because that would be sort of a commitment. Instead we go off into + a corner and try it out, I think we want a metric for if it worked. +- Amit: My concern is that our design might be bad in a way that is really + close to being the right thing but far enough off that we don’t see any + benefits, and I don’t want to end up there. +- Hudson: I propose savings of ~2.5kB for 5 applications versus the 500 byte + estimate of overhead in the kernel. +- Alyssa: Jett thinks he has the numbers for savings from blocking syscall and + will look for them. diff --git a/doc/wg/core/notes/core-notes-2023-12-01.md b/doc/wg/core/notes/core-notes-2023-12-01.md new file mode 100644 index 0000000000..8200d6e13c --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-12-01.md @@ -0,0 +1,61 @@ +# Tock Meeting Notes 12/01/23 + +## Attendees +- Branden Ghena +- Alyssa Haroldsen +- Jonathan Van Why +- Alexandru Radovici +- Phil Levis +- Brad Campbell + + +## Updates + +- Brad: Work on https://github.com/tock/tock/pull/3653 + - New board with nRF52840 and LoRa (LR1110) +- Branden: NWWG: Update on OpenThread integration with Tock + - C library from OpenThread foundation (everyone uses) + - Plan is to re-use this library as well + - Designs: service in userland or encapsulated in kernel (Leon's approach) + - Phil: is code open, or compiled? Open, have all files. + - Phil: meet with J. Hui? + - Observation: Linux includes Rust in C kernel, Tock includes C in Rust kernel! + - Phil: RF233 started as userspace driver, eventually stack in kernel + - Alex: Challenge with asynchronous kernel + - Brad: Does OT library sit on existing 6LoWPAN in kernel? + - Some of it. Not directly on HW, would use some of in-kernel stack. +- JVW: New Rust code size working group. + - JVW planning on trying to participate + + +## libtock-c: newlib and libc++ + +- Brad: https://github.com/tock/libtock-c/pull/353 +- Background: + - Compatibility problems: + - GCC10 compiler doesn't work with GCC13 headers + - GCC13 doesn't work with GCC10 compiler + - Newlib 4.3 doesn't work with GCC10 + - It's hard to get riscv compilers and riscv newlib +- We now compile newlib and libc++ for all architectures and pacakge headers + - Docker files for reproducibility +- Upsides + - Can update newlib since we retain old version of gcc10 (ubuntu) + - Can compile riscv by default + - No longer have hidden dependency on newlib (for headers) + - Clean mechanism for compiling and distributing compiled artifacts +- Downsides + - Compilation is now varied depending on the compiler version the user has + - It's possible the first person to get gcc14 (or later) will run into compatibility issues + - Larger binary downloads (one time cost) +- Thoughts? + - Seems ok + + +## Dialpad?? + +- Could switch to google meet via OpenTitan +- Could switch to zoom +- We are using Dialpad via tock org +- Bummer to switch right after getting tock rooms for different meetings +- Not clear there is enough need to switch right now diff --git a/doc/wg/core/notes/core-notes-2023-12-08.md b/doc/wg/core/notes/core-notes-2023-12-08.md new file mode 100644 index 0000000000..44b3542261 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-12-08.md @@ -0,0 +1,56 @@ +# Tock Core Notes 2023-12-08 + +## Attendees +- Tyler Potyondy +- Phil Levis +- Andrew Imwalle +- Alex Radovici +- Alyssa Haroldson +- Leon Schuermann +- Johnathan Van Why +- Hudson Ayers + +## Updates +# Binary Working Group +- Hudson: Rust binary size working group. Some people reached out over github. Johnathan discussed this last week. +- Alyssa: Working to help make the documentation more understandable. +- Alyssa: Some interesting topics have come up already, in particular regarding string formatting. +- Alyssa: I want to put together the argument that printf style strings are the smallest way to do this. +- Hudson: I am interested in if the core library gets compiled with your optimizations even when you are not using build standard. It seems people have claimed this is the case, but I remember seeing some pretty different / worse codegen when I looked into this a year ago. This may not be up to date, but I would be curious what the current standing is. +- Alyssa: It makes sense to me that there would be a prebuilt copy optimized for size and a prebuilt copy optimized for space. +- Alyssa: One other question, would inlining mirror require rebuilding? +- Hudson: I do not think so. +- Alyssa: One other thought: unpacking large result objects often results in poor codegen even with inlining. Perhaps mirror inlining may help with this. +- Hudson: I cannot remember if we turned this on in Tock. + +## TickV Discussion (https://github.com/tock/tock/discussions/3709) +- Andrew: Just as a heads up, some of the agenda emails have been going to junk. +- Andrew: I have been focusing on TickV and key value for my team. +- Andrew: The TickV spec states that a limitation is fragmentation when something is written to a region in flash memory and is never removed. +- Andrew: In such cases, even if the rest of the region is cleared, the flash memory might still indicate that the region is full, despite having only one valid entry. The current implementation of garbage collect is unable to solve this problem, which leads to the flash filling up while only having a few valid entries. +- Andrew: I have been looking for a way to handle this and would love feedback. +- Hudson: This could be helpful to place in the issues section on the main Tock repo. +- Hudson: The two main people who have done development on this are Brad and Alistair. +- Andrew: Maybe we can postpone this discussion to include them. +- Andrew: There was a previous discussion regarding the zeroize function in tick-v. +- Andrew: Effectively zeroize does not have any downsides. Primarily a security improvement. I do not want to make a PR for things people have not signed off upon. +- Hudson: I am not entirely familiar with TickV, but I assume there is a performance tradeoff for doing this. +- Andrew: This is true. There is a slight performance hit. Alistair's biggest concern was it may cause additional wear. There may be an increased code size, but this should be minimal since it would replace the functionality of invalidate. +- Hudson: It seems Alistair signed off on some parts of your previous discussion. +- Hudson: Alistair is the most familiar with this. People are more likely to look at PRs than issues. +- Andrew: The conversation seemed to have stalled. +- Andrew: One other question, do we have plans for LMS verification? If not, my team would be interested in working on this. +- Johnathan: Are you talking about adding a crypto API for signature verification or integrating it into app id? +- Andrew: I believe integrating it in. This is not my specific project, but I believe Tock currently only supports RSA. There are not any Rust embedded optimized versions of LMS. +- Johnathan: Adding a new crypto API would likely just be adding a new syscall driver. App id was designed to be extensible. +- Johnathan: Neither should be controversial. +- Alyssa: I would want to know what cryptographic review the implementation has undergone. This should be included in the PR. +- Andrew: I believe it should be the standard replacing RSA as RSA becomes deprecated. +- Alyssa: This review should be regarding the implementation. +- Hudson: This would be a neat feature. I doubt their will be reluctance. + +## TockWorld +- Hudson: Latest status, Brad sent out a survey. +- Alex: I was interested if these poll results / potential dates are available. This would be helpful for planning. +- Alex: If we can settle on dates in January, that would be very helpful for us. +- Hudson: I will reach out to Brad for an update. diff --git a/doc/wg/core/notes/core-notes-2023-12-15.md b/doc/wg/core/notes/core-notes-2023-12-15.md new file mode 100644 index 0000000000..87e450485a --- /dev/null +++ b/doc/wg/core/notes/core-notes-2023-12-15.md @@ -0,0 +1,121 @@ +# Tock Meeting Notes 11/17/23 +============================= + +## Attendees +- Branden Ghena +- Amit Levy +- Johnathan Van Why +- Leon Schuermann +- Alexandru Radovici +- Tyler Potyondy +- Hudson Ayers +- Alyssa Haroldsen +- George Cosma +- Brad Campbell +- Andrew Imwalle + + +## Updates + - Alex: Introducing George Cosma who is my student for several years, working on tockloader-rs + - Alex: From networking group: Leon created a stable version of the PacketBuffer library. We're hoping to be close to have a buffer management solution that doesn't use any unstable features. We'll be starting proof-of-concept with it + - Alyssa: Two issues about ReadableProcessBuffer we could add to agenda + - Leon: Not fully working yet, but we have a CW310 board that OpenTitan runs on that's not generally accessible. So our thoughts were making it available over the internet. Could be an infrastructure for flexible access to boards for CI or for letting other developers play with them. + - Tyler: Once that's working, and maybe if it's more concrete, I'd love to touch base. We have a test network at UCSD that would be neat to let people work with. + - Amit: There were some sensor networks testbeds back in the day, right? + - Brad: Yes, multiple + - Tyler: It would be great to be able to run CI jobs with a working network + - Leon: This project is hacked together right now. Linux container that has access to only a single board. Definitely work in progress + - Tyler: Is there a way to only run CI if it touches certain files? + - Alex: Running conditional jobs is a bit of a nightmare on github, especially with merge queues + - Branden: Maybe on command? Something like a bors command that could run stuff + - Alex: I do have some stuff I can share. I can send a workflow file if anyone needs it + + +## Tockworld Scheduling + - Amit: Tockworld is an annual in-person two-three day workshop/mini-conference for Tock developers. The hope in particular this year is to make it a bit broader. It's previously been the active developers getting together in person to discuss development questions and plans. But this time around we're also hoping to have a broader appeal and include users and other curious folks. + - Amit: Based on the availability we collected with a survey, it seems like the two most appropriate times for Tockworld would be August 14-16 or June 26-28. Held in San Diego. + - Alyssa: Preference for August, all things being equal + - Alex: Might not be able to attend in August + - Amit: It's not going to totally work for everyone. Some people didn't get or fill out the survey. It would be great to have people from industry attend if possible. So, one question is whether one of these time frames makes more or less sense. + - Jonathan: Either could work for me + - Hudson: I could make either. My broad observation is that my coworkers tend to take vacations in August. That's anecdotal though + - Amit: Alex's unavailability in August might be representative of people in Europe + - Alex: It's actually a summer school thing. We've been doing for a very long time. I might be able to push it around if decided very soon. + - Amit: Okay, so it does seem like June 26-28 is the most favorable time unless people voice big concerns + - Branden: Not to put Andrew on the spot, but would someone from your team be interested if they are available? + - Andrew: I'll reach out to the team. For the dates, I have no particular preferences. + - Amit: Okay, tentatively June 26 then + + +## Tockloader-RS Discussion + - https://github.com/tock/tockloader-rs + - George: Goal is to port Tockloader to Rust. What I'm here for today is to ask for feedback. I've been working on this for the last 6 months. Some feedback I need is that we need to have a structure in place so development can accelerate. As of right now in the main repo there's basically nothing, just some command-line parsing. The PRs have added a lot more stuff + - George: So, I want to figure out how to get new developers able to work on the project. And I want more bite-sized PRs. Right now it would be a huge lift to start, as there's basically no starting point. + - George: So I want to request aid in this area + - Amit: One useful data point is that I don't think many people are actively looking at tockloader-rs. So it's great to point out that someone is working on it and that we should pay attention. One useful thing to frame would be given that the python version of Tockloader is pretty full-featured, what seems like the minimal viable product for tockloader-rs that would make a transition seem possible or worthwhile? + - Brad: Hard to actually answer that. Some thoughts are the install, listen, and list commands, which people use the most. Those commands are mostly "stabilized" right now, although I don't think we made that official anywhere. Then there's a bunch of extra commands, but those are extra. + - Branden: How many boards would it need to work for? + - Brad: I think realistically I think we'd want to support at least the serial connection, and jlink or openocd or both. Once you do one doing both is easy. Once you do that, supporting additional boards is pretty trivial. + - Alex: Is there some way to get feedback on this PR: https://github.com/tock/tockloader-rs/pull/8 + - Amit: Yes, definitely. We should get George on the mailing list and slack too, for better communication. In the future, since this repo is lower on the critical path for most people, it would be good to have the option to nudge people for feedback + - Branden: So George, to give you the help you want, staring with comments on PR 8 would be best? + - George: Yes. PR 8 would be best to start. The other PRs are for other features that will be based on PR 8 + - Alex: Where do we bug people on Slack? + - Amit: Maybe even the devel mailing list. A problem here is that we literally won't see PRs here, unless you bring them up + - Brad: Core channel in slack too + + +## Precompiled Newlib + - https://github.com/tock/libtock-c/pull/353 + - Brad: Nothing has really changed since the last update on the call. I can repeat that if useful + - Amit: I was hoping that we could use the time to solicit Brad to remind us what the purpose of doing this is and what challenges we're facing, so we can figure out how/when to merge + - Brad: 1) we were never compiling newlib binaries for Risc-V, so this does that + - Brad: 2) we used our own compiled newlib, but implicitly required people to have newlib installed because we used the installed headers + - Brad: 3) also different versions of GCC can or cannot compile the latest newlib for all systems. Example is Ubuntu can't compile the newest newlib for Risc-V + - Brad: 4) we haven't had a reproducible environment for building binaries + - Brad: So now we have system that will pull in pre-compiled binaries from a mirror we host for ARM and Risc-V. The reason this isn't a slam dunk is that the C++ libraries seem to have a strong dependency between the version of GCC and version of headers in the library. So the compiled libraries really need to have the exact same compiler used as they were compiled with. So the build system figures out which GCC you have for ARM and Risc-V, then downloads the built files which match that version. So there's a build system where the build you're doing is dependent on which version you have installed. This is not exactly where we wanted to be. But that does resolve allowing us to update newlib. + - Brad: That's the major changes summarized + - Branden: There's also the issue of having pre-compiled binaries downloaded from a random server we host that was discussed and I believe resolved. + - Brad: Yes, we did decide this was okay. We have hashes committed and now have a reproducible environment that could recreate them. + - Leon: I also made a system to periodically check that the mirrors are still up + - Amit: Does this interact in any way with newlib licensing concerns? + - Brad: I think it's orthogonal + - Branden: Well Brad has made an entire system for creating and shipping binaries and headers in environments. So that would apply to new libraries too + - Leon: Also we have a generic license file in the repo and say it applies to everything. Now that the files are out of our repo, we don't have to worry that we're incorrectly claiming license on them. + - Amit: This answers the question to me so I'm comfortable moving forward + - Leon: There's a warning that exists with newlib now that we compile it ourselves. That's still an issue? + - Brad: Yes, thanks, there are a couple. The newlib headers are somehow incompatible, maybe this only shows up for us? The bug should theoretically be upstreamed. There's also an issue that libC++ stuff, we have a warning on that headers match your compiler. But now that we're shipping our own headers and not using the compiler ones, that warning gets triggered, but somehow doesn't cause the build to fail. The third one is that we can't #include assert anymore, and I don't know why. It complains that things are getting double-included and I can't explain why. + - Alyssa: Different header guards maybe? + - Brad: Maybe? I found some comment somewhere that assert actually doesn't have header guards at all? I just removed the include in the one app that uses assert. If someone wants to investigate more, be my guest + - Brad: One last thing. That PR is at a state where it's done in that it should be reviewed. There might be ways to improve it. But it shouldn't be merged yet. Once everyone is approving it, I'd like to re-run the docker stuff and make sure that everything matches. Then we can merge. + + +## ReadableProcessBuffer + - https://github.com/tock/tock/issues/3756 + - https://github.com/tock/tock/issues/3757 + - Alyssa: Both related but somewhat orthogonal. Base question: can we make breaking changes to ReadableProcessBuffer and WriteableProcessBuffer? + - Amit: We absolutely can. The system call interface is where we drew the line for breaking changes that would require a major version update. Of course we should tread lightly in terms of not breaking non-upstreamed builds willy-nilly. But I don't think that either of the changes you're proposing would do that unless maybe someone has a different implementation for a different architecture. Doesn't seem like a big deal + - Alyssa: WriteableProcessBuffer doesn't have a mutable pointer function. There is a way to get it, but it's not implementation-safe. + - Alyssa: The other is that both should really be unsafe traits, because anything that takes or returns a raw pointer should be unsafe. If anything is requiring the pointer to be a valid raw pointer, then you need an unsafe guarantee that they programmed a valid pointer there. You could imaging someone returning NULL, which is sound but other people working with it could assume it's valid. The two options for fixing that are to make it unsafe or make it a sealed trait so external users can't implement it. I lean towards the unsafe trait so external users can implement their own + - Hudson: I lean the same way + - Leon: One case we can refer to is the integration of the allow system call that lets users change the buffer while allowed. Downstream users might want to implement different calls like this. + - Amit: And we know there's support out-of-tree for different architectures. So there doesn't seem to be a disadvantage to having it be an unsafe trait. There is a clear advantage that adding an implementation wouldn't require modifying a specific module that would have to be forked or accepted upstream. + - Amit: I think Alyssa answered this in the issue, but there's virtually no downside to marking the trait unsafe, except that arbitrary untrusted code can't implement it, which is exactly what we want. If you're using some code that implements this, that code needs to be part of your auditing. + - Alyssa: Yes. Other implementations should be audited for sure + - Alyssa: One issue is if a downstream users has a forbid on unsafe code, this could cause churn. For people with a "pure safe codebase" + - Amit: That seems both unlikely and a thing to challenge/avoid. For this code be truly trusted, we'd have to do a bunch of checks that we really can't do + - Alyssa: Right, there's no safe way to prove a raw pointer is valid. + - Leon: I would say that we need lots of documentation about WHY it's unsafe + - Alyssa: I know how I would write the safety documentation + - Leon: We cared a lot about documenting that particular infrastructure. I'd add that we don't make stability guarantees for this particular infrastructure, although this is tricky for _other_ changes (not these ones you proposed) because this does affect our ABI behavior. But this case is okay. + - Amit: As for adding `mut_ptr`. First of all it seems like a yes. I don't know why it's a breaking change though. + - Alyssa: Downstream implementations would have to implement it and I don't feel comfortable providing a default. + - Alyssa: Thanks. I'll send PRs for this + - Amit: Yes. My take is that both of these seem good. `mut_ptr` seems less necessary but is good. And I don't think that it's a problem to change this as fixing it downstream is easy. And we really are very unlikely to guarantee stability at this level. It's not even capsule-level. + - Alyssa: Does anyone know any safe traits that take or return raw pointers? + - Amit: Not off the top of my head + + +## Future Meetings + - Next call is Friday, January 5th. Next two weeks off for holidays. + diff --git a/doc/wg/core/notes/core-notes-2024-01-05.md b/doc/wg/core/notes/core-notes-2024-01-05.md new file mode 100644 index 0000000000..8d563976fb --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-01-05.md @@ -0,0 +1,256 @@ +# Tock Core Notes 2024-01-05 + +Attending: +- Alyssa Haroldsen +- Andrew Imwalle +- Brad Campbell +- Branden Ghena +- Johnathan Van Why +- Leon Schuermann + +# Updates + +- Branden: Two Agenda Items: + + - Amit made a post on Slack on code size in Tock. May want to talk + about it today. Although not dialed in today... + + - If Andrew wants to talk about it -- state of TicKV. + +- Brad: Playing around with my old PR on doing RSA signatures for app + signing. Trying to lay some groundwork for doing that. Things have + gotten slightly better with regards to external library + support. Rust crypto RSA library uses heap-allocated numbers. There + is currently work underway to switch out the bigint library that + this crate is using. I'm told the next iteration will allow for + fixed-sized values. + + - Branden: Timeline uncertain? + + - Brad: Yes, although a lot of progress has been made on the library + side. + +- Leon: Have been able to test compiling libtock-c's with the + precompiled newlib, seems to work. It's great to see this being + portable, even works on NixOS! + + - Brad: Its a little unclear what the order of operations is here? + We should merge and then replicate those precompiled packages to + more places. Shouldn't need to change anything, given the SHA + doesn't change. + + - Branden: Previously we were told to hold off merging. Is this + ready now? + + - Brad: Yes, it's ready! + +# TicKV Update + +- Andrew: Posted an issue a couple of weeks ago concerning + fragmentation. Alistair seems to like one of the solutions I + mentioned. Given that this is blocking for me, we'll be moving + forward to implementing it. This seems to be a good replacement for + garbage collection. Functionally, it fixes the case where you could + be told that the flash is full, when there's actually still space + left (just fragmented). + +- Brad: Good to hear that you don't feel stuck on that. + + In general, Alistair is the original creator of the library, but + it's also under the Tock umbrella. It's not finished, and I'm not + too worried about preserving the original model. If this is strictly + better, we should go ahead with it. + +- Andrew: Alistair is slightly concerned with maintaining useful + properties, such as consistency on power loss, etc. Of course want + to preserve those. + +# Conferences & Tutorials + +- Brad: Had a couple discussions about different tutorials we may want + to do. We're committed to doing one at CPS-IoTWorld. There's another + potentially interesting one more focus on hardware security. Are + there others that people might be interested in? + +- Leon: When we talk about security, I'm curious what the focus of our + tutorial should be on. E.g., I'm still working on a system for + safely executing C code in the kernel, which may be fitting (e.g., + to drive trusted hardware). But without that, what should the focus + be on? + +- Brad: Good question. We as developers are thinking about the most + recent changes. I think we should separate those concerns -- + tutorials don't need to focus on only that. We should try not to + focus on these developments, because it's hard to make them into a + tutorial. For someone who's new to Tock, everything's new -- so we + can present established tutorials like the Security Key example we + presented at TockWorld. + +- Leon: This makes sense to me. It takes a significant amout of time + and effort when using these tutorials as driving force to polish and + present on new subsystems. + +- Brad: Yes, it's hard to develop something, get it working reliably, + and teach people about it! + +- Leon: I'd be happy to see the USB security key tutorial be reused + and continued. I can only speak to the reception of it at the + company I was doing my internship, but at least there is was very + well received. It helped people understand general Tock kernel + concepts. + +- Branden: It is a lot easier to iterate on something, than making + something entirely new. We should be able to improve on our security + key tutorial much more easily. + +- Leon: Regarding a HOST'24 tutorial, I'm perhaps slightly worried + about the target audience. We're not necessarily focused on + considerations around timing side channels, etc. + +- Brad: Yes, it's such a range of topics, so it's hard to know what an + audience is actually interested in. + + Comment on doing an internal variant of the tutorial is good, it'd + be great to do some more trial runs, e.g., at universities? + +- Leon: How should we be moving forward on this? + +- Brad: There's a few of us who are motivated generally. If a specific + opportunity comes up for anyone, we're happy to support it! + + Hard part seems to be finding opportunities in the first place + (interested parties, content). We have people interested in figuring + out logistics. + +# Tock CI Architecture & Demo + +- Leon: Have been working on a prototype for a Tock CI system. This is + motivated by me requiring access to a CW310 OpenTitan devboard, of + which we only have one, and not being able to carry this with me + while traveling. + + Sketched out and implemented a prototype system that we may be able + to use more generally for Tock CI. + +- Brad: Interested to hear about this, and what pieces we could re-use + for this "cloud CI" idea that we're persuing? + +- Leon: I could do a quick demo and screenshare? + + Disclaimer: the system that I hacked together is largely motivated + by me trying out interesting technologies. Would not want to give + the impression that this is an authoritative design in any way. + + [shares screen] + + Current system components: + - Central web interface for management. Allows scheduling jobs on + multiple boards. The web interface is running on a dedicated + server in a datacenter, it is not physically connected to the + boards. + + - The boards are connected to a different computer via USB, which + runs a so-called "runner" software. You can select an + "environment" to launch on a board runner, which governs the + container environment provided to you. E.g., you can boot a given + version of Ubuntu, which has a certain set of packages installed. + + This environment is then started in a container (system-nspawn), + which provides access to only this board. Containers use a fresh, + epehmeral root file system. Other boards are inaccessible. + + The container is then accessible via SSH. + +- Brad: This looks cool. Where is the container running? + +- Leon: This is running on the physical computer (e.g., Raspberry Pi + or any other computer), connected directly via USB. + +- Brad: Basically, you end up with a setup similar to if you just + SSHed into a desktop sitting in your office, except that you can't + see it. + +- Leon: Exactly. Did think about adding a camera live stream, but not + prototyped yet. + +- Leon: Couple more technicalities: + + - This does not require any firewall exceptions / open ports. The + "runner" computer next to the board connects to the central server + and forwards all traffic through outgoing HTTPs connections + (tunneling SSH through WebSockets). + +- Brad: The local CI server basically needs: + + - some physical connection to the board + + - ability to run the containers + + - a little bit of software for management. + + Somehow you want to attach containers to a given board. How would + you configure that? + + - Leon: The runner has a configuration file, where you can specify + the exact set of devices passed through. Using udev rules to + expose devices under well-known paths. + +- Brad: The execution of a container is associated with a given user? + + - Leon: Yes. Containers run from images, which serve as + templates. Two boards can share the same image, one board can have + multiple images available. + + Idea behind this was to use it for both interactive development + and for CI by just switching out the container image. + +- Brad: Right. One model may be that we take this scheduler, and when + a new CI workflow is started assign it to a board. Perhaps none is + available, in which case we can either enqueue it, or return an + error. + + - Leon: The system does support some very primitive queueing. If you + attempt to start another job on a board that is currently in use, + the job will be started when the other one is terminated. + +- Brad: When we have a CI workload, do we want to have tests baked + into the container, or do we load them in after the fact? + +- Brad: Hopefully we can get someone to help with this. One request + that I might have: the hardware platform will be physically + distributed, and thus as pain to micro-manage / debug / etc. So it + seems tricky to get right, or just specify to avoid dealing with a + bunch of heterogeneity. + +- Leon: Agreed! One really important thing to figure out there is how + we integrate more buses and peripherals (i.e., GPIO + connections). Can be a USB GPIO expander, FPGA, or something else. + + Deployment question is an interesting one, and a hard one to get + right, but seems to be somewhat far out for now. + +- Brad: The reason why I think that it is quite important to figure + this out is that the system should really be distributed, and we + want to able to tie in downstream users and contributors with their + own platform. In theory things like OSes should provide the required + abstractions, but in practice they're not sufficient. Trying to get + this right the first time the best we can seems important. + +- Leon: Good point. Have some knowledge to go by here, e.g., + netbooting a bunch of Raspberry Pis at a student ISP distributed + over a large city. We should be able to take some inspiration from + that. + + For tying in downstream users and deployment, there are more + concerns, such as trust: are we going to manage these systems? Will + we have a point of contact with downstream deployments? ... + +- Brad: Hopefully what happens is that the value we get is so high + that we have this built-in incentive for downstream users that may + resolve some of these issues. People would be motivated to keep this + going. + + We want to have a structure in place where we tell downstream users + exactly what our expectations are (general architecture, OS, + software components, availability, ...), that would set us up for + success. diff --git a/doc/wg/core/notes/core-notes-2024-01-12.md b/doc/wg/core/notes/core-notes-2024-01-12.md new file mode 100644 index 0000000000..fae71588bf --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-01-12.md @@ -0,0 +1,147 @@ +# Tock Meeting Notes 01/12/24 =================================== + +## Attendees +- Hudson Ayers +- Amit Levy +- Leon Schuermann +- Branden Ghena +- Alyssa Haroldsen +- Jonathan Van Why +- Andrew Imwalle +- Tyler Potyondy +- Alexandru +- Brad Campbell +- Philip Levis + + +## Updates +- Phil: Could we start sending out a link to the new dialpad in the weekly email? +- Hudson: Yes, sure. +- Alexandru: Can we also have a calendar invite for this meeting? +- Amit: Yeah sure thing +- Alexandru: That would be helpful for the time difference with daylight savings +- Alexandru: I can do networking working group updates. We discussed yesterday about finalizing the packet buffer and we are hopefully on the right track with Leon's working prototype. It seems we need to define an API for it, and the consensus is we will define the API as we go. I have a student who is going to build a smarter console for Tock, a desktop application that will show outputs from different apps in different windows using Leon's packet buffer. I think Leon and Tyler are really close to getting Thread working on Tock, and they will use a C app to insert Thread stack into Tock. The target for testing packet buffer is TockWorld with a working proof of concept. +- Branden: Networking working group meeting are moving to Mondays at 11am Eastern, anyone is welcome to join +- Brad: This console project is one of those that hasnt worked yet +- Alex: I am hopefuly the packet buffer will help. The alternative console driver will just add headers to messages and then the desktop app will parse those. Eventually, the goal would be to merge this with the current console in a transparent way, where the headers are only added if the desktop app is detected. +- Brad: Yeah, this would be really great. +- Alex: Plan would be to integrate this into tockloader-rs. +- Leon: I have an update on the PMP design and implementation. The consensus has been we just need to do the work to get CI passing and test this on some proper boards. We had some issues where QEMU had a broken ePMP implementation, then the PMP implementation in the liteX RISC-V boards was broken, I fixed that as well. The PR is ready, and this is a major change to a core subsystem, 3.5k line PR, and we really want to get this right given how much we have iterated on this interface. I would really appreciate people taking a final look at this, proposing any changes needed, and ultimately merging it. + +## PR Roundup +- Hudson: Brad shared an update Rust PR, I see that is in the merge queue now, which is exciting. +- Hudson: Brad also shared two Component Type PRs -- PR 3774 and 3775, I assume you were asking people to quickly review these? I will take a look at those. +- Hudson: Brad shared a TicKV fix, PR 3776, I assume this was also a review request. I see you already got Alistairs approval. +- Hudson: Brad also shared a link to his precompiled library mechanism for libtock-c. Do you want to talk about that? +- Brad: I do not +- Branden: It has three approvals, does anyone have any last concerns? +- Amit: What are we waiting on for this? +- Brad: Nothing on my end +- Amit: It looks like it needs to be rebased or something +- Brad: It does? +- Amit: I can do an update with rebase +- Brad: I can't imagine much has changed +- Amit: Ok, I am hitting merge when ready on this +- Hudson: Brad also has a collection of documentation PRs. There is PR 3777 which is an update to the syscall docs. There is 3778 which removes courses from the Tock repo. I assume the idea here is these courses are out of date and possibly misleading and get in the way when you grep for stuff? +- Brad: Yeah I think we forgot those were there. +- Phil: I think we should not delete those but clearly mark them as old. +- Phil: Nobody will look back through the git history for these, I don't like deleting the history of educational materials. +- Brad: I agree on some level, but I think we are unlikley to resuscitate these, and we have so much better material now. +- Phil: This is something that is coming up at Stanford for old courses too. This is important as educational materials and to showcase history. +- Phil: What if someone wants to find a course they took in 2018? +- Branden: Would a seperate repo to archive these be appropriate? +- Phil: Sure +- Phil: I can be the person to make a separate repo +- Hudson: I assume we could just put them in the Tock-archive repo +- Phil: OK, great, I can do that since I am being the squeaky wheel. +- Hudson: Next up, Brad has been moving technical docs from the Tock repo to the Tock book. There is PR 3779 to the Tock repo to remove them from the Tock repo, and then tock-book repo PR #23 to move them into the book. I know a lot of people do not have notifications enabled for that repo so wanted to call that out. +- Brad: Yeah -- we have this book that is searchable, has a nice ToC, other instructional information, and we have things in various places. I would like to see the de-facto place for documentation about Tock and its implementation be in one place, and that one place be consistent and have the nice trappings of mdbook. I like the automatic table of contents. But there is some documentation about the project, or about how we manage Tock, that does not make sense to move in my opinion because it more so documents the Tock repository, so I think it should stay. +- Hudson: It does seem the main downside here is people do not neccessarily find the book as easily as they find the doc folder. +- Amit: An additional downside is that the goal of the book should be to educate/teach, and the goal of documentation is primarily to serve as a more formal reference. So I don't think that things like TRDs should move out of the Tock repo -- and I realize Brads PR does not do that. And there are probably other kinds of documentation that do not belong in the book. +- Amit: I think only having material that will eventually be phrased/formatted for teaching would lose a more complete/formal reference. Maybe that stuff would be better as doc comments or TRDs. +- Brad: Two things: some stuff would not move to the book (TRDs)... +- Brad: To me, all of this stuff is teaching -- it tells you why a design is the way it is or how different pieces work like TBF headers. +- Amit: I was not disagreeing with anything you moved, but expanding on the set of things that might not belong in the book beyond what you mentioned. +- Brad: Got it. +- Leon: I like that currently PRs can atomically update an implementation and its documentation +- Leon: I do not know if that is a problem that needs to be solved, but this could be bad for documentation that really related to the peculiarities of a particular subsystem. +- Brad: I think that is one of the main reasons we have not done this. However having read our documentation this is already not happening. I had to make a few correctness fixes during this process already. Also, most of our documentation PRs are just doc PRs. there is not much overlap with development PRs. There is not much in our docs that is that implementation specific. +- Amit: I agree with that. In practice there is not much there to be lost. +- Branden: My biggest concern is just the discoverability aspect. I do not want someone to not find the docs. I agree that could be handled with links in the main repo. +- Brad: This is not something I had thought of -- when I look for rust documentation I do not go to their github! I just search, and end up with one of their three different books. +- Branden: But think about how you look up Contiki documentation +- Phil: Both are true, some people do it both ways and you want to accomodate both. +- Amit: Can your doc removal update the main README at the top to add a link to the Tock book? +- Brad: Sure +- Amit: Or a documentation heading somewhere prominent. +- Brad: I should have done that anyway. +- Amit: And maybe we should be doing some other discoverability things, like the tockos.ors website has the Tock book second to last, and has links to documentation in the website repo itself that I would bet are out of date. +- Brad: Yeah I agree, I do not really have a vision for what the tockos website is supposed to do. +- Amit: Haha, I spent most of my time building it figuring how to get the Tock SVG to tick. +- Tyler: I think the Tock book was one of the more helpful resources when I was getting involved, and I only found it through the website, adding it to the README would be very helpful. +- Brad: Yeah, I want us to always send people one place, which will eventually make things much more intuitive than they are now. +- Hudson: Brad had two other PRs -- you updated the HOTP tutorial. +- Brad: Yeah, we switched from app state to key value in the source code and this completes that update, it is also related to those component type PRs, the whole point of that is to make writing tutorials like this easier so they are not tied to one specific hardware platform. +- Hudson: And last was book PR #29, which added TRDs to the book. Are those duplicated? +- Brad: Those are just links to where the source is actually stored. +- Hudson: And they just show up as though they are inline in the book? +- Brad: That is right. +- Amit: Because this is pointing to master, I believe this is going to pull in text at compile time, when the book is compiled, so there is a bit of weirdness of rebuilding the same version of the book gives a different result, and if it does not get rebuilt for a while it might be out of date. But I see the value. +- Hudson: What is the cadence on which the online version of the book is updated if people are not submitting PRs to it? +- Amit: Never +- Hudson: Could we configure netlify to do a periodic rebuild? +- Amit: Probably, but we have not. +- Branden: Or trigger tock-book's netlify from the Tock repo? +- Amit: We could, but... +- Hudson: Yeah, a medium amount of work. Regardless. I am pro-this design. +- Tyler: I wanna circle back to the HOTP tutorial. I have some undergrads I gave that tutorial and some parts were not working. IIRC I also replicated the issue. On your end is it still all working? +- Brad: I have tested it a little bit but not every step. The problem is that the end-to-end thing almost worked and it has been slow to re-update it. It just needs a bit of love when we are not stressed and rushing at the last minute. We really need to get this version to something that does not require committing a custom board to the repo. +- Brad: There is another libtock-c PR I did not put in the email on this topic. +- Brad: As far as the TRDs go, in theory they are not changing, and the ones that are in the works could just not be included in the book, I don't think these things change that much. +- Branden: I agree with that Brad. +- Branden: Any last agenda items? + +## OT Calls +- Johnathan: Have the OT working group calls moved? +- Brad: They have not been occurring, it has been pretty much just down to the two of us. +- Johnathan: OK, wanted to make sure I was not unintentionally no-showing. +- Johnathan: Leon, I just saw your comments on Alistairs PR. There is still something wrong. Alistairs PR only fixes one out of the eight pins, but I am gonna go through and fix that whole config. +- Leon: That is great to hear. The way I have been testing this is compiling and bootstrapping CW310 and seeing if it prints. +- Leon: I would have liked to see which commit actually broke this. +- Johnathan: It is from when Michał redid the pinmux driver. +- Leon: More generally, I am now in a position where using the test infra I introduced last week I can run things on a CW310 and it works, which will be pretty nice. +- Johnathan: I am about to go through a transition, since we are moving to hyberdebug with a different board. +- Leon: Did you get a CW340? +- Johnathan: No, a different board. I do not have it yet but will soon. + +## Tock World 7 +- Brad: What do we need to do for Tock World 7 to be able to advertise it? +- Amit: We need to decide on ticket pricing, high level schedule, maybe venues +- Brad: And what is our current ticket pricing? +- Amit: Whatever it was that you disagreed with +- Andrew: It was $100 on the website +- Phil: Where does that number come from? +- Amit: I pulled it out of thin air when setting up the eventbrite. +- Phil: But what are our costs? For instance having academic resources bring our costs down. +- Amit: Yes. We do not know our costs, in practice, we also have funding we could use to run it. +- Phil: I suggest set the price based on things that scale with people, like food, and for fixed cost things, assume we have funding for that. +- Amit: Yes +- Brad: So how much is that? +- Phil: Depends on location +- Amit: I think we need Pat to figure that out with UCSD people. +- Branden: Brad or I can ballpark from when we each hosted. +- Phil: I would add 50% for UCSD. +- Phil: For tinyOS tech exchanges the host would often cover food because it was small. +- Amit: Yeah, that is what we have done so far. +- Tyler: Did we settle on June 26-28? +- Amit: Yes +- Brad: I get it, but we are just pushing things back. If we put it in the critical path we are just delaying this and then the next things is its April. +- Amit: No it is not in the critical path. We are gonna do cost of UVA food + 50%. And then if we are wrong it is ok because we have funding. +- Phil: And if it is an overestimate give slightly nicer food. +- Amit: We were discussing a hybrid schedule, I put something on the site that said one day for ecosystem stuff, one day for tutorials, and third day as a core-dev day. That mattered because you and Pat suggested putting the tutorial at the beginning or end because it might want a different venue. +- Amit: I do not think we want to change those dates after the fact. +- Amit: Lets put tutorials on the Friday? +- Brad: When Pat and I talked about it we said ascending levels of openness - core dev, then Tockworld, then tutorial. +- Amit: That works for me. I will update that on the website. Brad and Branden, look at food costs and share that? Then we can advertise. +- Brad: Cool, and then for the day that is open to all, do we have thoughts on speakers / invites? +- Amit: I think we should maybe discuss this a bit offline. diff --git a/doc/wg/core/notes/core-notes-2024-01-19.md b/doc/wg/core/notes/core-notes-2024-01-19.md new file mode 100644 index 0000000000..f25a4ec35b --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-01-19.md @@ -0,0 +1,130 @@ +# Tock Meeting Notes 1/19/24 + +## Attendees +- Alyssa Haroldsen +- Alex Radovici +- Amit Levy +- Andrew Imwalle +- Brad Campbell +- Hudson Ayers +- Johnathan Van Why +- Leon Schuermann +- Pat Pannuto +- Tyler Potyondy + +## Updates: + +- Alex: We started working on the multiplexing of the console. It looks promising. + +## [https://github.com/tock/tock/pull/3772] - Signature Credential Checking +- Brad: This adds a structure for checking with signatures for app credentials. Implementing the logic needed for this to check signatures using the signature HIL interface. +- Brad: Once we can validate the cryptographic signature, then all that code has to do is implement the HIL. This will do all the app checking and is just a few lines in `main.rs`. +- Hudson: This is the more generic version of what you submitted a while ago for the RSA. +- Brad: The RSA would plug into this (would not require RSA support). +- Hudson: This would be great for Phil to weigh in on. It connects with his other app signing infrastructure. +- Hudson: Anything in particular you want people to pay attention to? +- Brad: My hope is to bring this to people's attention since there are no comments on the PR. +- Hudson: Call to everyone, please take a look at this. + +## [https://github.com/tock/tock/pull/3782] - Shared `build.rs` +- Hudson: This has request changes from Brad that were later updated to an approve. +- Brad: I marked this as significant. The actual code changes are minor, but the conceptual changes are significant. It affects how platforms are setup and how they are built. We only have two core team approvals. +- Leon: Having read through this, I think this is a good change. We need to make changes to the board crates if you want to have an out of tree board. This is a relative minor change and I think is a good starting place. One drawback is this does change something for downstream users. +- Brad: I don't think this changes much for downstream users. You can just your own `build.rs`. +- Leon: This changes something for when you want to take a board out of tree. This is one more thing that needs to be changed. +- Hudson: Anyone with an out of tree board already has their own `build.rs` file so this would not create issues unless they link to this which I think is unlikely. + +## [https://github.com/tock/tock/pull/3785] - Key Value Syscall Documentation +- Hudson: Brad went through and added documentation for syscalls. Alistair signed off [check spelling]. +- Brad: This is part of my larger series of updates for updating documentation. + +## [https://github.com/tock/tock/pull/3791] - Fix Calculation for Subslices +- Brad: I was trying to use subslices. Currently, if you use subslices and adjust the start of the slice but keep the end of the slice unspecified (go until the end), it only works the first time. You get an invalid slice and it appears your code is broken when in actuality, the subslice is not doing the right thing. +- Alyssa: Are there unit tests we could copy from the standard library to exhaustively check for correctness? +- Hudson: I imagine there is something somewhere we can adapt. +- Alyssa: More unit testing would catch this and be useful for edge cases. +- Brad: What does that mean for this PR? +- Hudson: I think we should merge. +- Alyssa: Are there unit tests for this PR? If not, please add some. +- Hudson: Currently there are not. +- Alyssa: We should add this. +- Hudson: Should we block the PR over this? +- Alyssa: I would not say we need to block this, but this would be an important followup. +- Alyssa: Unchecked pointer manipulation math should always be heavily tested in my opinion. +- Hudson: For what it's worth, there is not an `unsafe`in this file. We are only manipulating within an already allocated slice. +- Alyssa: So does this mean it could not go out of bounds? What if we added too much and that is unchecked? +- Amit: I think it will result in a panic. +- Hudson: Errors in this file on its own could not cause something unsound, just an out of bounds panic. +- Alyssa: True, but it is reasonable for code to assume that the implementation of subslice is unchecked. +- Leon: This is particularly scary for code that interacts with DMA peripherals with subslice. + +## [https://github.com/tock/tock/pull/3793] - TBF Footer Return Error +- Brad: This is a mistake in the TBF parsing library. Where we put internal error and it really should have been bad TLV. +- Brad: If you do hit this error, it seems like there is a bug in the code when in actuality it is possible to trigger this error because you created a bad TBF. +- Brad: I think internal errors should only be for mistakes in Tock. + +## [https://github.com/tock/tock/pull/3795] - Enabling Capsules to get Short ID +- Brad: We discussed this during the app id discussion. As far as I can tell, there is no way for a capsule to actually check a process's corresponding app id via the short id. This makes you unable to do anything with identifying applications. +- Brad: I think that process ID is our handle for capsules to access internal process specific items like this so I added the function there. +- Brad: This way you can get the identifier for tracking which application is doing what. +- Hudson: This makes sense. + +## [https://github.com/tock/tock/pull/3803] - Stable Rust Discussion +- Brad: There has been an open Rust issue for naked functions. +- Brad: Naked functions have been the major blocker for Tock using stable Rust. +- Brad: A lot of people were exciting in 2022 to stabilize naked functions. There is one person standing in the way of that. It does not look like it is going anywhere. +- Brad: A lot of people who care about this have switched to `global asm` and is not worried about stable Rust. +- Brad: I think we should get off this bandwagon and just use `global asm`. +- Brad: Doing that would mean we can compile cortex-m with the stable compiler. We would require one other change to get this to work for RISC-V. +- Brad: This does work, now that being said, this is a proof of concept in a way. I imagine we will still compile in nightly, but as part of testing we will confirm that it is possible to compile with stable if people desire that. +- Hudson: And we are going to insure that by configuring Hail to compile using stable by default? +- Brad: That was my thought. Hail seemed like an okay candidate. +- Leon: From previous discussions, I remember having nightly in the long run being an optional feature and building all boards in both nightly and stable. I believe this shouldn't be too much work to integrate into CI. +- Brad: Currently, we are compiling our own standard library. I do not think we want to stop doing that. For CI, we could do what you propose Leon (compile for stable and nightly). +- Amit: Remind me why we would stick to nightly still? +- Brad: Because we want to compile our own standard library with optimizations which cause code size savings. +- Brad: We also want to use custom tests framework. +- Hudson: And there is also the virtual function elimination option which provides substantial size savings. +- Alyssa: Just a warning with that flag, we've managed to get some miscompiles from it, but only in bazel. +- Alex: While code size is a concern, being able to compile using stable rust is a major advantage for safety critical users. Nightly is not accepted / certified. +- Brad: Absolutely. I do not know if this is the best way, but I added a `make` flag to turn off all things in the build system that require nightly so it can compile with stable. +- Leon: I am in favor of these changes. I dislike `global asm` with needing external function definitions and separate asm blocks even if they are kept close together in the source files. +- Leon: This would make some of the bugs I have experienced in the hard fault handler in cortex-m more likely to appear. You lose the tight coupling between the function signature and the assembly that stands behind that. +- Leon: I guess this is a necessary evil, but I believe it goes against the direction I think this particular piece of code in Tock should move. +- Brad: You are not alone. I do not know how else to proceed. Either someone needs to get naked functions working in the Rust compiler or more directly say we need this on the github issue. +- Alyssa: When was the last time we mentioned this is needed on the github issue? +- Brad: I did this, but one person has been causing this to be a standstill. +- Alyssa: What specific engineering needs are there? +- Johnathan: The current implementation make use of some LLVM support for naked functions that is apparently a little hacky and not something the Rust compiler should rely on long term. The proposed refactoring is to change the implementation on the Rust side so that when it mono-morphizes the naked function it outputs a global asm statement instead in the LLVM IR which is apparently a better solution to the problem. +- Johnathan: What is unclear to me is if changing this refactoring after stabilizing would be a breaking change. That is unknown to me. This may be the reason for the hesitancy. +- Hudson: Brad do you want to mention the target feature situation? +- Brad: Rust added a hidden nightly dependency. There is cargo Rust feature that allows for conditional compilation. +- Brad: This is nice because we could have code that is only for a subset of cortex-m chips in a crate with code for all cortex-m chips. This flag is only set correctly on the nightly compiler. This is another thing we need to revert to be able to use stable. +- Hudson: The failure mode for using stable on the target feature was surprising to me. +- Brad: This is not clear what is supposed to happen. +- Hudson: Your PR relies on a more namespacing approach to separate these things? +- Brad: The traditional solution is to create a new crate for platform specific crates. +- Hudson: Any other comments on this? We will will wait for a few more reviews on this PR. + +## [https://github.com/tock/tock/pull/3796] - Enable Short ID Calculation to use References to Process +- Brad: I think it was generally understood that we should be able to take the process name and create an identifier based on that. +- Brad: In fact, the draft TRD indicates that exact use case. The draft TRD indicates that the function that creates the short ID would be given this process reference. +- Brad: In the actual Tock source code, that function is only given the credential that was used. +- Brad: This PR adds both: you get the credential that was used and a reference to the process. If you want to make your short ID based on something in the headers, you can. +- Brad: This is particularly important because the credential itself is not in the integrity region. This means you couldn't place an identifier there since you cannot check that. +- Brad: So if you wanted to use something that was covered under a signature, it needs to go somewhere else. +- Brad: It makes sense that the short ID creation function would have access to the entire process object. +- Hudson: The main discussion on the PR was if the credential should be passed in at all. +- Brad: Yes, I'm not sure how deep to go into this here now. +- Hudson: Your change seems to be a needed fix. Whether the credential should be there could be discussed as a followup. + +## Documentation / Tock Book PRs +- [https://github.com/tock/book/pull/23] - Move Kernel Docs to Book +- [https://github.com/tock/book/pull/24] - HOTP Tutorial Update +- [https://github.com/tock/book/pull/25] - Stack Diagram to TickV +- [https://github.com/tock/book/pull/28] - Add Chapter Listing +- [https://github.com/tock/book/pull/29] - Rendered TRDs + +## App ID Discussion - Should Errors be Propagated? +- Brad: I intend to convert this to an issue. This warrants another round of discussion. I think Phil will need to be involved. +- Brad: I do not think app IDs are currently usable as we had intended them to be. diff --git a/doc/wg/core/notes/core-notes-2024-01-26.md b/doc/wg/core/notes/core-notes-2024-01-26.md new file mode 100644 index 0000000000..4b9ca0cfd0 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-01-26.md @@ -0,0 +1,151 @@ +# Tock Core Working Group Meeting Notes 01/26/24 +================================================ + +## Attendees +- Branden Ghena +- Hudson Ayers +- Leon Schuermann +- Tyler Potyondy +- Brad Campbell +- Andrew Imwalle +- Alyssa Haroldsen +- Pat Pannuto +- Alexandru Radovici + + +## Updates +### OpenThread Porting +* Tyler: From OpenThread porting, some good progress. Leon and I have been working on getting OpenThread in Tock. Leon is doing encapsulated functions, while I'm doing libtock-c. Both in parallel, to find something that works. We successfully fixed some faults for OpenThread as a libtock-c application. We still need to implement the abstraction layer for controlling the radio. +* Tyler: I was curious what people's thoughts were on doing a PR for the state we're at now. It runs and doesn't crash, but doesn't actually _do_ anything since it doesn't control the radio. +* Brad: PRs to libtock-c? Or Tock? I think big commits are fine. +* Branden: And if this is really self-contained, it matters even less. Plus, it would be good for a PR to actually work. +* Tyler: Okay, we'll stay in a branch for now. +### Kernel ELF Files +* Andrew: Tock's ELF file doesn't quite follow the correct ELF format for entry points. Neither Tockloader nor Tock-bootloader load the ELF file, they use the bin. So we're going to submit a PR to fix that. This is in the kernel. +* Hudson: Why does this matter for you? +* Andrew: We're using the ELF file for loading rather than the BIN file. So we want the entry point in the ELF file to meet the standard. +* Leon: Will this amount to changes in the linker script? +* Andrew: Not sure. I'm not actually doing it. +* Branden: We don't care about the BIN at all right now? +* Leon: I think one of Alex's boards might use it maybe? +* Andrew: So this PR will be coming soon. + + +## Exit syscall on process finish +* https://github.com/tock/libtock-c/pull/246 +* Leon: This is a good change and something we should do. It does however break our API in a major way for downstream users. +* Leon: So I wanted to talk about what guarantees we provide for libtock-c and whether this should be a major release for libtock-c +* Brad: I'm not aware that we have any guarantees at all about libtock-c +* Hudson: So releases of libtock-c right now are related to the Tock version that they target. So it's sort of misleading that we have libtock-c on version 2 +* Pat: The two releases have historically been simultaneous to ensure that it works in union +* Hudson: So we really haven't promised anything about libtock-c internal APIs +* Alyssa: Not really internal here. It's pretty visible +* Hudson: True. We should consider how this impacts people and give them advising about it +* Alyssa: Put it in the release notes at least +* Leon: That's good for me. I'll put this in the PR notes +* Hudson: And I think that's what we've done historically + +## PMP redesign +* https://github.com/tock/tock/pull/3597 +* Leon: I just don't know what the best step forward is. It's a pretty major change to what we should consider a very important subsystem. But it's a LOT of changes. Giant PR. So when do we consider this good to go, review-wise? +* Hudson: There are some reviews here. Chris, Alistair, Brad. +* Leon: Yes. And there have been OpenTitan tests on this PR. +* Hudson: I agree that you're unlikely to get other in-depth reviews on this large change. Given that we know there are problems and that this fixes them, I think we could move forward. +* Brad: I think this is a case where the most-qualified person is the person who wrote it. So we should go with it. +* Leon: It's always easy to say that your own code is good. I do think we have sufficient evidence that the current code is broken and this new design fixes those issues. There may still be issues, which we'll find in the future. +* Alyssa: My initial look indicates it's using references to IO memory, which sucks. Oh, it's actually references to statics and using those as pointers. But the statics have a size of u8. +* Leon: You're looking at the constructor for the board? What's happening there is that to set up memory protection properly, we need to know where the regions are from the linker script. We use those values purely as integers. +* Alyssa: I'd be more comfortable if we never made a reference to it. The only change I would make to fix that is "core pointer address of" +* Leon: This is copied from how we do process loading. Should probably fix everywhere, but it's in ALL boards, so we should make that a separate PR. +* Alyssa: Agreed. Rust's memory model doesn't work well with situations where the pointee is smaller than the actual size you're working with. Raw references are better for that. +* Leon: Okay, that makes sense. +* Hudson: Yeah, let's make an issue about that. But not hold up this PR on that. + +## Cortex-M Hardfault Handler +* https://github.com/tock/tock/pull/3798 +* Leon: A lot of Brad's PRs are blocked on this. And it feels like a significant issue for our threat model. Issue is that I can't really test any Cortex-M0 changes. +* Alex: We have pico debuggers and I can help you with that. Let's take it offline. +* Branden: So you just need to test on an M0? +* Leon: Right. I tested on an M4, but I don't have an M0 right now. +* Alex: I will do that. Just message me +* Brad: If we need to, we _could_ separate those two changes. +* Leon: The faster the better +* Pat: I will take a look ASAP + +## Documentation Working Group +* https://github.com/tock/tock/pull/3815 +* Brad: This would create a documentation-focused working group. The motivation is that I want the ability to merge a bunch of documentation changes on a much faster basis, rather than waiting on Core to look at them. This would oversee the Book and general repo documentation +* Hudson: Yeah, this seems good. From the PR, I also agree that it doesn't make sense for this to handle TRDs or Working Group call notes. +* Brad: Definitely agreed. I updated the PR to clarify that. +* Leon: It's still kind of vague here. Not spelled out clearly what the burden is to merge a documentation PR. +* Brad: That's a good point. If we could do that, it doesn't seem like we'd need to wait so long on current PRs. So it's kind of hard to say what "acceptable" documentation is +* Leon: Yeah. I think I'm onboard +* Leon: So what do we formally need to do to establish a new working group? +* Branden: I think there's a write-up on how to form working groups. We just did this for Networking group +* Brad: Seems like something the Documentation WG could answer! +* Branden: Call for concerns? (No concerns raised) +* Leon: I'll mark on the PR that we approved today + +## Obsolete ProcessLoad Variants +* https://github.com/tock/tock/pull/3805 +* Brad: This really just removes two lines of code. It removes two process loading enum variants that we aren't actually using. So it does raise the question of _why_ we aren't using them. It feels like you should want to know _why_ it isn't working. But I think because process loading is asynchronous, we don't have an obvious way to return them. +* Brad: So, it kind of makes sense to remove dead code. But also this feels like a different issue of completeness/correctness. So I want to look a little deeper at the boot process to see if there's a better way to express where things are failing. So my impression is that this is an in-progress thing. +* Hudson: We should tag Phil on this +* Alex: Where this is coming from: we're trying to write specifications for Tock code. But we found this example that cannot be specified. +* Brad: Because you don't know where they come from? +* Alex: Yes. Every line of code has to have a requirement. And there's no clear requirement for these lines of code. So we need to make it more clear + +## Handling PR Reviews +* Brad: Generally, my question is what do we do with PRs that no one is looking at? +* Leon: For this, I don't think many people feel comfortable judging whether a change is correct or proper for this subsystem. I have no idea how credential checking works, for example. +* Pat: So does that just put us in a position where this code is not robust? Maybe we need to have a default person from Core who needs to review each PR, and needs to go learn it if they don't know it. +* Alyssa: A review rotation could be useful. If you're not sure, you could pull someone else in or learn it. But at least pointing out _someone_ responsible would be nice. So PRs don't just sit around with no ownership. +* Hudson: It would be nice to have the review rotation apply after N days have passed without anyone reviewing. So the appropriate people review in many cases, but then _someone_ is dragged in if no one volunteers. Not sure how to automate this +* Pat: We could manually do this on Core calls for now. +* Alyssa: How does Rust do review rotation? Something like GWSQ (from google). You assign it as the reviewer and it assigns someone else. It might round-robin but there might be other strategies too +* Leon: I just found a github action that does this. I could play with it and modify it to assign people +* Hudson: I think that would be a reasonable approach +* Branden: I think what we're doing now is not working. So trying something at least would be beneficial +* Alyssa: `triagebot` is the tool + + +## Signature Credential Checking +* https://github.com/tock/tock/pull/3772 +* Brad: This is a step in the right direction, but not the end yet. It's certainly easier to reason about as 100 lines of changes instead of a lot more +* https://github.com/tock/tock/pull/3793 +* Brad: This is the same thing. A minor issue for credential checking +* Branden: This actually has reviews. So this is just a situation where we need to click the button +* https://github.com/tock/tock/pull/3807 +* Brad: #3807 is also in the exact same situation +* Leon: Merged that one + +## AppID +* https://github.com/tock/tock/issues/3813 +* Brad: I sent this last week, but realized it would be helpful to write things down more first, which is this issue. So what I'm proposing is that credential checking, determining if a process is valid to run, should be separate from assigning an identifier for the process. This are sort of tied together because of use cases. So there's some general ambiguity here. But when you actually try to use this, credentials are great for checking hashes and signatures, but they're not great for identifying what app this is. Because if you change the code the hash changes, but the application ID changes the same. And when you check these, you end up copying around a bunch of code, because you might change the hash method, but the naming method is often the same. +* Brad: So in code, I think we should remove this trait and decouple the two operations. That's my proposal. +* Branden: But we'd still have a way to swap out either? +* Brad: Yes, each board could chose either independently +* Pat: I vaguely remember wanting to protect some AppIDs, so you can't do bad things? +* Brad: I definitely want identifiers to happen AFTER credential checking. I do think this is an important question though. Is there a reason that a non-credentialed app can't have an identifier? +* Pat: I think we want non-credentialed apps to have an identifier for debugging and development purposes. Blink and whatnot. +* Brad: Today you can implement that sort-of. The logic requires that you have _some_ credential. But that could be padding and you could write your checker to approve padding as a valid credential, then give it an ID. I'm not sure any of that was an intentional design decision. +* Pat: Part of this is probably okay, because if you have a board where you're worried about protecting AppIDs, then you could. It's just a think that board authors would need to know about... +* Brad: Okay. Good point. We could make that more explicit. If implementing the ID assignment aspect had an indication of whether it requires valid credentials, that could fix that. We could make it explicit in the way that the trait is designed. +* Pat: That seems reasonable +* Branden: This makes a lot of sense to me. Decouple the two of these but have an explicit ordering for them. +* Brad: You definitely don't want people to think that they can put the AppID in the footer, because you can't check it. We realized that's not going to work + +## TockWorld +* https://github.com/tock/tock/pull/3806 +* Brad: I have a PR that would put the blurb on the README. Any thoughts? +* Pat: Sounds good. We should advertise aggressively. +* Leon: As long as you feel we're ready to advertise, I'm good about this +* Pat: The ticketing part is still a to-do for me. Hoping to have that done very soon. +* https://github.com/tock/world.tockos.org/pull/2 +* Brad: Also planning the days. Day 1 would be core group, so people on this call. Day 2 would be broader tock discussions. Day 3 would be a tutorial. So I want it to be clear that it's not a secret or invitation-only, but I want it to be clear that only people who really _want_ to be there should. So I was trying to clear up that intent. +* Alyssa: This text looks good to me +* Andrew: To me as well +* Alyssa: So the first day is targeted at contributors to Tock +* Branden: And the second day is users, or interested parties. Anyone. + + diff --git a/doc/wg/core/notes/core-notes-2024-02-02.md b/doc/wg/core/notes/core-notes-2024-02-02.md new file mode 100644 index 0000000000..2ff43e1428 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-02-02.md @@ -0,0 +1,107 @@ +# Tock Meeting Notes 02/02/24 + +## Attendees + +- Amit Levy +- Leon Schuermann +- Branden Ghena +- Jonathan Van Why +- Andrew Imwalle +- Tyler Potyondy +- Brad Campbell +- Philip Levis +- Pat Pannuto + + +## Updates + +- Leon: Some progress on auto PR assignment + - Do we want this? + - https://github.com/tock/tock/pull/3822 + - How to test? Just run it live? +- Amit: I think we want this, testing live is fine. + - Need some expectations for what the assignee is supposed to do. +- Leon: Assignee is responsible for PR. +- Leon: Bot could automatically send stale PRs. +- Amit: We could have script for what to do with stale PRs. What do we want? + - Just merge stale PRs? Get more comments? +- Brad: How does it work? +- Leon: Runs on github actions. Run into github limitations if use too much. + - Can run in dry-run mode. +- Brad: Testing live seems fine. + +- Leon: Working on hardware testing. + - Slack: #ci-hw + - Working on tests running on RPi with boards attached. USB and then GPIO connections. + - Test spins up netboot RPi to execute tests. + - Start on hook with github actions. + +## Process Checking PRs + +- #3772 + - Adds another process checker. + - Phil: I can take a look. + - This doesn't need to be in the kernel crate. We could move to some other crate. + - Could mismatch hash function. +- #3818 - AppID based on process name. + - Just need(ed) a review. + - In kernel crate, but doesn't really affect core kernel APIs. + +## Compile on Stable + +- #3803 - merged + +## Cortex-M Crates + +- Rename to the actual names (eg Cortex-M3 -> v7m)?. +- Brad: Not in support. Marketing names are more familiar to developers. +- Phil: Agree, chips say "Cortex-M4". +- Amit: Cortex-M define ISA + default peripherals? + - Pat: Not sure. +- Brad: ok to have both, nice to expose the familiar names to boards +- Leon: complexity with hierarchy/subsets +- Phil: People coming to the repo looking for specific cortex-m. + +## Legal TRD + +- Merged. + +## Documentation Working Group + +- Proposal on how to handle PRs across areas for documentation +- Amit: Concretizing process for creating working groups. + - How do people join WGs? Join DOC WG? Ex: join OT WG? +- Amit: Use WGs to help manage PRs. + - We have PRs in a series of repos (userpsace, kernel, book, etc) + - These PRs don't get looked at quickly enough, or get looked at but not merged + - Suggestion: having groups with purview of specific parts of repositories or parts of repos would help + - Amit: as example, feel like so many PRs is overwhelming + - In contrast, if my purview was more limited it would be more scalable. + - Also would get best people to look at PR to be more likely to +- Brad: general support +- Phil: sounds reasonable +- Brad: this proposal seems like it implies that it would grow involvement. That implies giving up control. That is somewhat significant. + - Leon: Problem of divergence between groups / code conflicts between groups. + - Phil: Chairs could help. Perhaps from core WG. + - Phil: Help ensure continuity and connection + - Phil: Filesystem divisions can help. Separate folders maintain distinctions. + - Amit: Yes, but there will be conflict points (HILs, for example) + - Amit: Another challenge: where does code size monitoring live? That would have to be umbrella. +- Amit: Action Item for me: write up a proposal on this +- Amit: How do people join WGs? + - OT? Pretty open. + - Net? Open to join. +- Amit: Someone else could lead OT? + - Brad: OT really is both OT and RISC-V, I'm more involved with RISC-V + - Brad: but if someone is primed to do more with OT WG, that would probably be better for Tock +- For the net WG, Alex is chair but not in core. +- Amit: WG chair two functions: administrative (ie scheduling) and technical leadership + - More important for the technical leadership to be involved with core +- Amit: Don't want working groups to grow uncontrollably +- Leon: Name of working group is important. Shows what the purview is. +- Branden: Does every WG need to have meetings? + - Amit: no. + - Role of core WG evolves in the future + - Phil: WG focus could evolve and change +- Brad: So, DOC WG? + - Leon: clicked merge. diff --git a/doc/wg/core/notes/core-notes-2024-02-09.md b/doc/wg/core/notes/core-notes-2024-02-09.md new file mode 100644 index 0000000000..6acb6ff886 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-02-09.md @@ -0,0 +1,124 @@ +# Tock Meeting Notes 02/09/24 + +## Attendees + +- Branden Ghena +- Amit Levy +- Leon Schuermann +- Brad Campbell +- Alex Radovici +- Jonathan Van Why +- Alyssa Haroldson +- Tyler Potyondy +- Pat Pannuto + + +## Updates +### Display Support +* Brad: OLED screen work is in a good state in libtock-c. Support for the monochrome display is good +* Amit: Where does the display driver live? +* Brad: It's in the kernel +* Amit: And the C library just does like fonts and stuff +* Brad: Yes. The C library layers on top of the syscall interface we already had + +### Hardware CI +* Leon: Update on Tock CI development. System I present a few weeks ago, is now being used for a CW310 OpenTitan board. You can start jobs and get access to a Linux container that has access to the board. So it's a good solution for boards we don't have many of, and in the future for automatically running jobs. +* Amit: Remote, reservation-based build environment. On the path towards building a CI system +* Brad: Would CI be essentially the same as a person using it? +* Leon: The base for this can talk github API, so whenever a github workload is created we get a webhook request to the server along with which type of job. The job is scheduled with a parameter of the workflow ID. So the environment would run a github action runner in "ephemeral" mode. Then that can run the job and reports back. The only difference is that we won't have to pretend to be a user and send SSH commands. We'll start a workflow that actually just runs some tasks + +### Tockworld +* Amit: Tockworld update. Thanks to Pat we're moving forward with securing space at UCSD + + +## Mutable static references +* https://github.com/tock/tock/issues/3841 +* Alex: We have a problem with the new version of Rust. It won't compile mutable static references in the future. It's a warning right now. We get 25 warnings on the Microbit right now, which will become errors soon. +* Brad: CI is checking this warning here: https://github.com/tock/tock/pull/3842 +* Alex: Leon incidentally fixed one of these issues in the boards file, with the `ptr_address_of` change Alyssa proposed +* Leon: Yes, I still need to rebase that, but the change is mostly good. +* Alex: Deferred calls and several other places use static mut references too +* Amit: So that'll be important to address +* Alyssa: We might have to change to unsafe cell with a helper that gives a reference. Would have a .get_mut() function +* Amit: Unsafe cell has a const constructor, so that's probably fine. Just extra wrapping code +* Johnathan: `static mut` is stabilized as part of the 2021 edition. So the stable compiler can't deny that anytime soon at least. I feel like they can't just switch it off like that with Rust's stability promises +* Amit: Yeah. So it might not be urgent. +* Branden: Maybe this is real because we're on a nightly? The warning seems to promise that it will be an error +* Amit: It's an edition thing. The Rust 2024 edition will break it, even if the Rust 2021 edition allows it. +* Alyssa: Dependencies can stay on 2021, even if you're on 2024 +* Amit: We should still fix. Especially if it's as simple as Alyssa suggests +* Alyssa: You could do `&mut* ptr_address_of()` still. That overcomes the lint +* Johnathan: That's fine for now. But it would be good to move towards what they want at some point. They're trying to get us to avoid this for a reason, so we'll likely eventually have a Tock cell that wraps unsafe cell in some way. +* Alyssa: Ti50 has a version like this. It acts differently on chip than on host for testing purposes. Unsafe sell or a checked, sync cell of some type + +## Removing naked functions +* https://github.com/tock/tock/pull/3802 +* Amit: This would get us to stable, I believe +* Brad: Yes. We've handled the last few other things. So that means we can merge this to remove the last nightly feature on ARM, and the Hail board compiles on stable! +* Branden: What's the nightly feature on RISC-V? +* Brad: ASM-const for the CSR library. It's more difficult. +* Jonathan: I see a rewrite coming when we update Tock registers +* Brad: The point is, if we're going to compile 99% of our boards on nightly, because we still like our nightly features, this is just a proof-of-concept that we _can_ compile on stable and will test roughly half of our code on stable. Importantly the capsules and kernel crates +* Alyssa: I see progress towards stable as unambiguously good +* Leon: I do think this is a great idea. What has made me skittish is that while we were making these changes, we had problems with the linker placing assembly code and not being able to branch between them. So I'm worried that the linker won't be able to make some optimizations now. The benchmarks CI run doesn't show any massive memory increases (or decreases) so we're probably okay, but I want awareness +* Amit: How did we fix it? +* Leon: I think we jump to a constant now, instead of a relative jump +* Amit: This is now on the merge queue +* Branden: Why do we want to be on nightly at all? +* Brad: Two makefile-level things that boards can opt into. Compiling our own Core library with optimizations, and a testing framework. +* Leon: I think we can use macro_rules to hack around the RISC-V CSR stuff. We could have an expansion which builds the actual CSR assembly strings from a macro +* Pat: I think we had the macro version before, and left it because it was ugly. So we could certainly go back +* Brad: So overview, we could go back to that. If there's someone who's motivated to see RISC-V on stable we wouldn't preclude that. I don't personally care though, because I think one example on stable is good enough. +* Brad: Our plan for stable, is that once the Tock registers update that will affect the CSR stuff, and then we'll chip away at that feature too. +* Branden: And it is nice to have some things on nightly and some things on stable, so we're testing both +* Johnathan: We also want to test stuff with MIRI, and that'll require nightly. +* Alyssa: Unfortunately MIRI won't be stabilized until the Rust memory model is stabilized, so it's going to be a _while_. More than 3-5 years out +* Amit: Comparatively, naked functions are basically dead though + +## Process Checking +* https://github.com/tock/tock/pull/3772 +* Amit: Phil isn't here to discuss unfortunately +* Brad: I do want to handle this, but I agree we could push it + +## Libtock-C Refresh +* Amit: High-level, Libtock-C could use some love. Tyler brought part of this up +* Brad: Yeah, Tyler brought up how clumsy libtock-c is to use. It's been cobbled together at low-effort for testing kernel features. Then I realized I wasn't sure what it meant to "fix" it or how to start. So, I want to crowdsource ideas on how to improve it +* Branden: A trivial answer is documentation, which is quite lacking +* Tyler: An example, there's an assumption listed in one of the alarm files that may or may not be valid anymore. It would be great to know "what are the assumptions" about how you use APIs. For example, requirements based on the frequencies they're running at. Probably falls under documentation +* Amit: Aside from documentation, two things. First, we could consider the build infrastructure. Even with the best C tools, stuff is hard. But things like CMake are more ergonomic for relying on libraries. That could be a big deal. It's a chicken-and-egg thing where people aren't asking for this, partially because it's not easy to use right now. +* Leon: It would be great to pull libtock-c into an out-of-tree library too +* Leon: Also, we have pretty inconsistent APIs in libtock-c. I'm not sure even all kernel changes ever made it into userspace. It would be great to version individual subsystems. We basically don't have any stability guarantees for libtock-c right now +* Tyler: As part of the CI tests for libtock-c, we build things, but I don't think we actually have unit tests for anything. +* Leon: There's one very limited one. The Tock CI tests an ancient version of libtock-c for the litex board +* Amit: The second one, was that it could be worth separating the libtock-c which we currently have which is a useful proving ground and isn't particularly good for real applications from an alternative userspace. We could have a new one designed from the top down. Although there's a question for who would do that +* Brad: Where do those interfaces come from? +* Amit: There _are_ users that are building C apps. They don't currently contribute upstream. I'm not sure what their libtock-c stuff looks like or how it works. I just know that they are building applications in C for Tock. +* Amit: In general, the interface would come from applications. We might need to talk with groups who do make applications +* Amit: There is this IoT application thing now that Tyler is revisiting. C seems important in that domain. +* Amit: We could replicate some similar API from an existing system. Proton-style for example. It would be much more constrained, but that enables better documentation since there's less stuff +* Brad: So how much would be exposed and where are the pain points is the question. If a more-documented smaller API would be helpful, that's one answer +* Brad: One idea I had was just reorganizing things. Making a folder-structure here for categories of drivers seems useful +* Branden: The examples folder is a mess too. And there's a tests folder in there where it's unclear what goes where +* Pat: Could we match the syscall numbers which are in tables in the kernel by types. We could use those same types for our folders +* Brad: Yes. I'll look at that +* Brad: Does any of this seem like the thing that would move the needle Tyler? +* Tyler: I think it would help. As someone who came into the project recently, and I'm working with two undergrads on the open-thread abstractions, there are function prototypes where we're connecting OpenThread to Libtock-C. The biggest pain-points are the documentation, but specifically and worse the inconsistencies in APIs and places where the kernel has changed and we just get some generic error when making a call. +* Tyler: I'm not sure if a simpler redesign would be good. It would help. But I can't decide on restart versus repair +* Brad: I think the consistency thing is a clear issue. Part of what I would hope to do with a reorganization is separate the testing stuff from interfaces that are more preferable. It's fine to have an interface to a specific IC, but we really want a Temperature interface. So we could guide people to more general, well-made APIs +* Brad: One question, are you saying some things just don't work on Master for kernel and userspace? +* Tyler: There are syscalls that are deprecated and just do nothing in the Thread stuff for example. You do get an appropriate error back at least +* Brad: That's a bug in my mind. Someone should have "fixed" libtock-c +* Tyler: I don't know if there's a way to automate this, but it would be neat to tag things in the kernel with what they associate with in userspace, so making changes in one would flag a required change in the other. The same issue will otherwise occur in a few years even if we fix everything now, as long as nothing is checking that we keep the two in alignment. +* Leon: I don't fully agree with classifying them all as bugs. One of my conclusions is that in the kernel the development of kernel stuff that guides releases goes mostly independently of capsules. We don't use capsules to inform kernel releases often. So they change and get out-of-sync. Having some relation of which capsule API we want and whatever libtock-c currently expects would be good +* Brad: Anyone is free to stabilize a capsule syscall interface, although we do so rarely. Then userspace wouldn't have to change because it's stable +* Leon: I guess. I'm concerned that's not realistic though. For example, was the alarm stuff a part of the release? +* Brad: I think we deprecated the old stuff, but left it there. I am aggressive about not breaking userspace after we agreed on stability +* Brad: Users do expect functions to work. So some way to check that the functions work would be useful. Possibly CI testing which could block PRs for kernel or userspace. I think fixing a lot of entry-level stuff would help a lot. But then we still do need to decide on what our interfaces _should_ be. I'm not sure +* Tyler: Depends on who's using it. They might define usability differently +* Amit: Hopefully we could talk to some of the users and ask questions about their use +* Tyler: One more thought, speaking to the alarm infrastructure, there's a surprising and troubling amount of bugs in the implementation. The time-as-tick PR but also I think the queue of alarms has some issues with sorting. I'm not sure it's actually a real-world issue but it could be an edge case. So I think we should be thinking about unit tests too +* Branden: I think that's a rare case though. Most drivers just rely on the kernel syscalls to do almost all work +* Tyler: The alarm has a lot of logic. Maybe there are others with a lot of logic and we should have testing for them. It is a shame if the kernel does all this hard work, and libtock-c ruins it. +* Amit: I suspect there's not much logic, primarily because libtock-c was for testing the kernel. A redesign could have more logic though +* Tyler: So moving forward, documentation push first, and I'll help there. Then we could kick-off the process of talking to stakeholders. Would be useful + diff --git a/doc/wg/core/notes/core-notes-2024-02-16.md b/doc/wg/core/notes/core-notes-2024-02-16.md new file mode 100644 index 0000000000..3d77030785 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-02-16.md @@ -0,0 +1,96 @@ +# Tock Meeting Notes 02/16/24 + +## Attendees + +- Branden Ghena +- Hudson Ayers +- Leon Schuermann +- Jonathan Van Why +- Andrew Imwalle +- Tyler Potyondy +- Brad Campbell +- Philip Levis +- Pat Pannuto +- Alex Radovici + + +## Updates +### Certification and Unit Tests +* Alex: Started writing unit tests for Tock (in the process of certifying). We're not sure how to do this, and posted an issue looking for help/advice: https://github.com/tock/tock/issues/3854 +### Board with Display +* Brad: PR on makepython-nrf52840 board with screen drivers needs reviews. Board isn't very interesting, but the capsules matter: https://github.com/tock/tock/pull/3817 + + +## Async Process Loading +* https://github.com/tock/tock/pull/3849 +* Brad: This is a refactor on process loading/checking to make it all asynchronous. It's been tricky to handle the synchronous code that exists with the rest of the asynchronous Tock stuff, specifically you don't get errors along the way, just one error for the whole thing. So the PR spells out some details on why this refactor helps +* Brad: The major change is that we currently load anything that looks like a valid TBF into a full process object, then decide if it's valid. So we committed a bunch of resources to something that might not be credentialed. So by splitting the tasks of checking and creating, we can stop it short and skip over things that are never going to be valid. This is particularly helpful on the path towards dynamic process loading, for loading new processes at runtime +* Phil: One point you made there, is the idea that you have a process that's parsed and syntactically valid from a TBF perspective, but we haven't loaded it so we don't know if it has a proper ID. If I want to check a signature, do I just check once, or each time I want to run it? +* Brad: Just once. Between parsing the binary and creating a process standard object in the processes array. +* Phil: What happens if I have two images, 1 and 2, and both of them have TBFs checked. I want to load version 1, then later want to stop it and load version 2 into that slot. Then later want to go back to version 1. Do I have to check the signature every time? Or just once? It's probably not a show-stopper, I'm just trying to understand. +* Brad: The primitive that's in the PR right now is that once a process is in the processes array, it has a valid credential. If you modify the binary, that would not be true. Other than that, the credential should still pass. +* Phil: So, three steps, checking loading and running. The credentials are part of the loading step. So if you have a shortage of process array elements, then re-checking signatures when swapping could be an issue. Although that's very hypothetical. +* Brad: There are two ways to think about that. Yes, you could end up having to recheck. But part of the implementation adds a process binary array, and that holds the object after parsed, but before loading into a full process. +* Phil: So you'd have unloaded, but parsed, binaries. +* Brad: Yes, you could do that +* Phil: So there are 4 levels: exists, parsed, loaded, running. That makes sense +* Brad: I had hoped that we could get away without parsed, but then I don't know how to do version checking. You need to hold everything you might want to run, so you can choose the best one to run. +* Phil: Something to think about for async is what are the operations that are async. It sounds like parsing is now async. Really, what's the granularity of async operations in the new approach? +* Brad: It's really just the credentials checker. Then the entire loading process happens as an async thing. You could a bunch of deferred calls, but you don't have to. There is one though, so we start on an async and can do callbacks. So a drop-in replacement could read from an external chip. +* Phil: That makes sense to me. This is a good idea. It was something I struggled with and was trying to figure out how to make it async, but the PR was so big already +* Brad: Definitely +* Brad: The other change that falls out of this: the core kernel loop treats the processes array like it did before credentials checking. Anything in that array is totally valid to execute. So all of the checking happens before the array is populated. +* Phil: That cleans up the loop. That's very nice +* Brad: Yes. That removes overhead if you don't want to do checking +* Phil: My one comment: now that process loading/checking is complicated with a four-stage state machine. It would be good to write a document describing it, as it will be totally non-obvious. What are the states, how do they transition, etc. +* Brad: Yes, good point +* Brad: Last thing, which will maybe be in this PR. What does happen if you want to dynamically swap processes. The idea that everything in the processes array is valid would no longer be true, and we'd need some way to check for uniqueness at that point. I'm still figuring that out. +* Brad: If you did a process update while the system is running, you have a new binary and would like to stop the old and load the new. But we need to make sure the uniqueness doesn't ever get violated. +* Phil: I thought you checked that it's unique before making a new process? +* Brad: Right. That was easy, but is hard in this PR. Because the kernel no longer has the checking mechanism to do that check. No reference to a checker. +* Phil: It'll probably need a reference. Whatever handles a call to transition a process will need that. +* Brad: Right now, if you only consider the boot case, you can do this once. But if there's a way to add a new process you need to do it again. And how that should work is a bit tricky. +* Phil: It just needs a reference, right? Or something else can mark a process as "has clearance to run". Which must be something that can assert uniqueness. +* Brad: Yup. I got to this stage yesterday or so. Still considering it. +* Phil: If you don't want the main loop to have a reference to the checker, you could add a new process state about whether a process is cleared as unique. And the kernel will only start those that are cleared. +* Phil: I am happy to continue to be a sounding board for this. I'm not good at tracking the github stream though. Send me an email about it please and that'll go faster. + +## Signed Processes +* https://github.com/tock/tock/pull/3772 +* Brad: It makes sense to have a trait per hash so we can keep track? +* Phil: Not per hash. Per signature algorithm. So you know which kind was used. And those types should define the size of the data and the contents +* Brad: Doing that elegantly doesn't seem possible right now. I might have a less elegant way to do it with a rust feature. +* Phil: Is the issue having two types? +* Brad: You can have the trait, and in theory there's just a constant attached to the trait which is the size. That's a nightly feature. +* Phil: Can't you associate a type with it? +* Brad: Yes, but not a constant one + +## Libtock-C Revamp +* https://github.com/tock/libtock-c/pull/370 +* Brad: I wrote a guide about how we could arrange the libtock C library to be usable but more predictable. Looking for comments there +* Hudson: I'll look into this +* Branden: I really strongly like this. I think it's a big step forward for libtock-c +* Leon: So this is mostly the status quo, but with a synchronous namespace? +* Brad: Two other things too. Requires wrappers for low-level syscalls. Second it is very prescriptive on what those names look like. + + +## Unit tests for Tock +* https://github.com/tock/tock/issues/3854 +* Alex: We want a certified version of Tock. Needs unit tests for every single line +* Alex: But it's difficult to test free-standing functions. For example, the TBF library. When doing unit tests, we have to mock up various other functions. The only way we found to do this is configurations for testing/not testing. I'd love some thoughts on how to do this +* Alex: We want, long term, these tests to get back into Tock. I know Tock doesn't like conditional compilation +* Pat: We do have some of this already. Conditional compilation for testing was the one type we really were okay with. I think it's just in arch right now? +* Alex: We'll need it in the kernel too though. And it can't just go in the test suite, it's got to go in the main code because when we compile it for testing we have to pull in a different mocked-up crate. Something I know is giving correct or incorrect answers so I can do unit tests on a function-by-function basis. +* Hudson: Yeah, but every dependency in the kernel having a config for testing or not testing will be really ugly, right? +* Alex: That's the issue. For anything that has a trait or a generic works fine. But everything, like the kernel, that doesn't do this is not fine. We would either need to modify the kernel to take the tock TBF as a trait, which wouldn't be a code size increase at least. Or we need configure +* Hudson: Yeah, that would have it's own problem. It would add generics everywhere and explode a bit. +* Leon: Doesn't Rust support default arguments for generics? That would potentially mean we could add a limited set of generic parameters but other things wouldn't need to care? +* Alex: So we could add it to kernel resources, add a default associated type, and in testing we'd override the kernel resources? +* Leon: Yes? It's still not "nice", but at least there's an interface contract that you explicitly write out in the code. I'm still not sure this is the right solution. But it could be useful. +* Alex: We're willing to try some things and see if it works well +* Alex: Generally, we'll need to do this everywhere we use libraries. Particularly, everywhere we use Cells. +* Johnathan: That seems like a weird line. Cells seems like something that doesn't need to be stubbed. Not sure if it's a requirement. +* Alex: It's a might right now. We'll argue it's a low-level primitive, but certification may still require it +* Alex: Long-term if anyone wants to use Tock in safety-critical environments, or even IoT in EU soon, certification will be a must +* Leon: We'll take a look at Leon's suggestion though, and see how it goes + diff --git a/doc/wg/core/notes/core-notes-2024-03-01.md b/doc/wg/core/notes/core-notes-2024-03-01.md new file mode 100644 index 0000000000..20b28441db --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-03-01.md @@ -0,0 +1,188 @@ +# Tock Meeting Notes 03/01/2024 + +## Attendees + +- Amit Levy +- Phil Levis +- Hudson Ayers +- Leon Schuermann +- Branden Ghena +- Pat Pannuto +- Alexandru Radovici +- Johnathan Van Why +- Brad Campbell +- Andrew Imwalle +- Tyler Potyondy + + + +## Updates + +Amit: Assignment of PRs. + +Leon: We have a new bot in our organization, to deal with lingering pull requests that are ignored or not getting reviews. It's currently limited. Assigns reviewers to all PRs that are not Draft and aren't marked as Blocked and don't have reviews. I hope this captures our existing policies. + +Phil: Maybe 3 days instead of 7, so a WG could meet and assign reviewers? + +Leon: Thought about it, if we are flexible and re-assigning reviewers, then that could take off the brunt of the load to discuss in the call. + +Amit: Assignment is to triage. Which may mean reviewing. May mean assigning to someone else. In other cases maybe means at least re-visiting that pull request somewhat frequently. + +Phil: So it's administrative responsibility, not code review. That's fine. 3 days is OK. + +Leon: Assigned person can delegate, assigning a label. + +## Kernel Testing + +Brad: We have these kernel tests. They're just commented out. If they fall out of correctness, nobody notices, they just fall out. I went back to the notes from years ago and looked at it, we haven't miraculously arrived at a good approach. I approach brute force. We have a board defined with the tests enabled all the time. They're always checked. Doesn't require manual effort to comment in tests. This would also let us test different schedulers, test different credential checkers. This also lets us reflect all of the options somewhere. + +Brad: Downside is it's the brute force method. We have more boards we have to maintain. + +AMit: If instead we had some kind of system, a macro, the boards are generated in some methodical way, would that ameliorate the concern of having 100 boards? + +Brad: It would to me. + +Hudson: Well we won't have a 100 -- do we think that's where it will end up. The numbers were helpful in your PR. If it's 1% or 2% of PRs, that's not so bad. I had a skewed perspective. + +Phil: There's a tension -- we think boards define the system, but we are worrying about having a lot of them. + +Amit: What level of test are you thinking of here? The Example Test board you have in the PR is not at all NRF specific. Is that representative. Or conversely, do we think that most tests that would fi this model are board specific, chip specific? + +Brad: There will be a roughly equal measure of both. I have another board that does kernel tests for the NRF. There are other issues there, so I didn't include that one. + +Amit: Will they reply on processes? + +Brad: Yes. Some of them. + +Amit: Is that in the board configuration? In tree? A reference? + +Brad: I was imagining this would just go in the README. "Here are apps you can run with this board." But we would later define a way to run a specific test, with CI. + +Amit: I'm thinking through thoughts and alternatives. Another approach: every board has a test version. That exercises a bunch of functionality. Integration. Another is unit tests, not tests you can just run on the host. They rely on hardware. "Am I correctly getting an interrupt for the AES peripheral on this chip?" Another is more like big integration tests, that would run in CI, that involve applications as well, fully automated, maybe this should just be in a different repo. + +Brad: You're talking about a lot of very relevant things. I agree with all of them, we need to move towards doing all of them. I don't know what your concrete proposal is, though. What I like about just getting over this idea that we should have few boards, we just get over this. We can have boards for whatever you want to do. It might become unwieldy, we might figure out ways to make it more straightforward, if we were going to arrive at the perfect solution, we would have done so in the next 3 years, we need to get further along in testing. + +Amit: I propose a small amendment to the RFC that either boards/configuration would be more informative, separate top-level called board-tests. + +Brad: I hear you, I think there is a benefit to not using the word test, it has meaning in Cargo. If we come up with a word like capsule, we know what we mean. + +Amit: My take away is the potential clutter is worth it, if and when we can come up with a name or separation. + +Phil: I think this is a good idea, it might force us to make it easier to maintain and make boards. + +Leon: I think this resolves some other longstanding tension, would clearly define which are alternatives, versus which are demonstration boards. + +Brad: Move further discussion to the pull request. + +Amit: How do we go from here? Does this RFC become a real PR? + +Brad: Yes, change the title. + +## Updating Nightly + +Brad: We can't updated to nightly due to static muts. Leon has an example of rust code that will get rid of the warning. Are we comfortable with using this that gets rid of the warnings in the 50 or so spots? Or do we want a Tock abstraction? + +Amit: My understanding of Leon's suggestion, LocalCell, is not to take the code exactly, it deals with reentrancy that doesn't matter. + +Brad: I was talking about "One workaround are diffs that look like". + +Leon: We've been talking about this on the OpenTitan call a way back. Why is this popping up now? What does Rust want us to do with this? We do need them, but there is this dangerous part, of converting them to references or mutable references. One of the dangers we are exposed to, is passing this around. We are still risking violating Rust's aliasing rules around this variability. LocalCell would reduce us to the actions of core pointer read, and core pointer write. No intermediate reference. No leaking of references. + +Hudson: So you are advocating LocalCell over Brad's alternative. + +Leon: Or CorePtr read and CorePtr write. Single read, single write. + +Amit: That only works if we are storing things that we can read and write in that way. + +Hudson: Like objects. + +Amit: How much does this come up? + +Leon: We aren't concerned with atomicity of writes and reads. + +Amit: But it would be copied. + +Leon: How often do we take a reference without reading it? Which is the same as doing a copy. + +Amit: We allocate components statically, in static muts effectively. Those can't be copied out and back in. That's not meaningful. I claim that with components, we are good. They are all locally scoped static muts, in the stack. Where does this show up? + +Leon: Shows up in Cortex-M arch crate. Global state shared between system call and trap handler. Boolean uisze. + +Amit: In Cortex-M it's possibly there because an atomic doesn't work in Cortex-M0. + +Leon: Yeah and we don't need atomic, we just need volatile. + +Amit: Which is what they compile to on M4. + +Brad: What are you proposing, Amit? + +Amit: If it's like 10 cases, and they are all special cases, then just the diff in the comment seems fine. + +Brad: It's hard to know, because the kernel crate has to compile before the rest. There's a lot of these. Processes array trips this. Drivers trip this. DeferredCell is the first one. + +Hudson: I linked to 3 static muts in every board. ProcessArray, chip structure, and the printer. + +Leon: I do believe these instances are masking something we're doing that's unsafe or unsound. For instance, the processes array is a complex beast. How do we ensure that we are sound? + +Amit: So, LocalCell, there we would want something like the scope function. Which imposes a lifetime constraint on the reference. + +Hudson: I suspect the right answer, is this should unfortunately be handled case-by-case. Some are fine with LocalCell, others might not. There isn't one true way forward. + +Leon: Something we are still doing, taking references pointing into process flash. Even the storage driver can violate aliasing rules there. As much as this is terrible and hard work and take a long time, we have to resolve all these use cases. + +Hudson: Let's wait for Alyssa to comment on this PR, spend the next 20 minutes on WG reorganization PR? + +Brad: We should talk about what the worst case scenario is. We don't want to be able to never update nightly again. + +Amit: If I agree to tackle this, is this something we can follow through. + +Brad: If we had a plan within a month to resolve this, that would be great. We need a concrete end to this. + +Amit: We should resolve this within a month. After we've had other people who have really good insight into this, like Alyssa. Leon and I shouldn't go into a dark basement and solve it without talking to people. + +Alexandru: I'll add Alex. This is boiling for us. We can talk on What'sApp. + +## WG Re-Org + +Hudson: I'll share your post to the mailing list. + +Amit: My proposed proposal is modest, but the implications would be less modest over time. Even over a short period of time. Very high level... + +Phil: How about we just read it? + +*reading* + +Amit: What do people think of the high-level proposal? Convert to a PR to change the README for the core WG, and the framework for the working groups to break up to/establish/convert. + +Phil: Who owns the HILs? And let's not use the name "Communication", it has a meaning in EE. + +Amit: OK, I was just trying to capture USB. + +Amit: I was trying to move HILs outside of the kernel crate, into places more for their use cases. But the sensors HILs should be managed by the people doing sensors. 15.4 should be networking. + +Branden: We are a small group, there is overlap, a core member is in every WG. + +Leon: I'm wary of how groups will implement this, the idea that each group sets its own standards. Many contributions do overlap. What do we do with those contributions? + +Amit: I think part of the dance that we should have is, both figuring out division of working groups along with potentially adjusting what is where in the various source trees. So there is less overlap. HILs are one example of this. If someone adds a sensor implementation that motivates modest change into the HIL for temperature sensors. That would currently touch the kernel crate and raise alarms. But perhaps we shouldn't care, that should be a trait that's alongside the sensor related stuff. + +Leon: Makes total sense. We need to define this exact escalation procedure. We might have a divergence in architecture that's not reflected in CI status going red. E.g., some HIL use some Cell type, others use another. + +Phil: It's OK if there are traits outside the kernel. E.g., a specialized 15.4 trait, or a bunch of specialized sensors. + +Amit: Maybe this gets worked out through directories. It lets people do stuff, then upstream it later. + +Brad: Separate between Core capsules and extra capsules. + +Phil: Or POSIX and ioctl. + +Brad: My comments on the initial review of this is, seems like a lot of formalism to end up where we are now. The version of the proposed working groups in the email, would be a bigger umbrella, and diffuse responsibility more, would require standing up those working groups. + +Alexandru: A lot of WGs, a lot of overlap. + +Amit: I did limit myself to 8 non-core WGs, which happens to be the number on the core WG, minus Pat. + +Phil: It's not that every WG has to meet every week for an hour. Working groups of 2 could be fine. + +Amit: Yeah, Crypto isn't going to be extremely involved. We can empower people with responsibility over fractional merging decisions. It's not now, you are part of core, we trust you. There can be more local trust. + diff --git a/doc/wg/core/notes/core-notes-2024-03-08.md b/doc/wg/core/notes/core-notes-2024-03-08.md new file mode 100644 index 0000000000..89ac5d0370 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-03-08.md @@ -0,0 +1,277 @@ +# Tock Meeting Notes 03/08/2024 + +## Attendees + +- Alyssa Haroldsen +- Amit Levy +- Andrew Imwalle +- Brad Campbell +- Branden Ghena +- Johnathan Van Why +- Pat Pannuto +- Tyler Potyondy + +## Updates + +- Branden: Networking WG update. The OpenThread stack works right now. Tyler is + using a C library as the OpenThread stack, which communicates with a capsule + to drive the radio. It is capable of joining a Thread network, remaining + attached, responding to requests, etc. Did not encounter timing issues -- the + most timing-sensitive parts are handled within the driver, the app only + handles non-timing-sensitive parts. What's next is a more flushed-out story + for switching channels, a better RSSI story, and a flash implementation (I + think a PR or issue was posted today). +- Amit: What does flash implementation mean? +- Branden: The OpenThread stack wants to save state to help re-join the network. +- Tyler: That's a blocker because e.g. OpenThread won't commit network keys + until it believe it has been saved to flash. We're currently faking that + storage in RAM. Working on putting it into flash to be spec-compliant. +- Branden: Notably, none of them are blockers. It's pretty much working. +- Tyler: The biggest blocker is potentially the ring buffer, which we've + discussed. Have had some issues with that and timing sensitivity. Will + continue discussion on the PR. May need a new construct for this. +- Branden: For background, the capsule is trying to share packets with apps fast + enough. +- Johnathan: Reminder there's already a TRD for a shared kernel-userspace buffer. +- Tyler: Having some issues around upcall semantics. +- Brad: Maybe need to look into it more. +- Amit: I know the goal at one time was to get a Thread sleepy-edge-node (or + something) device running. Since the path has been to support OpenThread in + userspace +- Branden [in chat]: Sleepy End Device (SED) +- Amit: is the path still in reach, or are there differences. +- Tyler: No, they're not. I haven't tested it, but theoretically they can talk. + Previously I was having to implement the logic in the capsule, which was + simple for SED, but a router is more complicated. We're in reach of having + general OpenThread functionality. Being able to have a router device should be + in reach. +- Amit: So overall, Thread is a generally full-featured protocol and it is an + existence proof of taking a nontrivial full-stack C library and running it in + a Tock process. +- Tyler: Yes. +- Alyssa [in chat]: Question about the userspace readable allow: why not instead + pass the buffer up to user space in a syscall, with the driver indicating to + the kernel that the memory is readable by a specific userspace app? That could + also allow sharing buffers between apps. +- Branden: Networking WG has also been working on a better strategy for sharing + buffers. Leon and one of Alex's students (Amalia?) has been working on a + method that attaches process IDs to buffers. +- Brad: Documentation working group update: we have a bunch of READMEs in the + repo, and scripts to keep them up to date. I'm working to run them, creating + PRs, and setting up a CI thing to keep them up to date. We also merged the + capsule test trait, and Branden and I are working on a PR for the book to + point users to the capsule test trait. +- Johnathan: OT working group discussed the safety of RISC-V CSR accesses, I'll + take a look at the PR and comment on it. +- Brad: Have been working with a LoRa chip, have the LoRa stack running. It + joins networks and transmits packets. Think we can merge library support to + libtock-c by the end of the semester. +- Amit: Update on mutable statics. Leon and I have prototyped a design that we + think can replace `static mut` in a way that is both sound and zero-cost in + the current Tock case. Incidentally it would allow support for multi-core in + principle with some cost, including potentially being suitable for host + environments (as Alyssa suggested). It's along the lines of what Alyssa has + sketched, looks like `thread_local!` with a different implementation. There's + probably a diff that I could pull up but we'll probably have a PR in the next + few days. +- Tyler [in chat]: @Alyssa Seems like the biggest downside to this is that the + buffer size will be static since it comes from the kernel. By allocating in + userspace, we are able to provide a buffer of larger or smaller size (this is + important for if the userprocess wants to provide a larger buffer to not drop + packets if the userprocess expects to infrequently yield and handle upcalls) +- Alyssa: The main issue I've found is you can't get a `&mut` reference, so we + have to modify types to take a `Deref` type instead. +- Amit: With `thread_local!` you get a closure where you operate on a + non-`'static` version. Could maybe do a guard type. +- Alyssa: What I have in our current codebase returns a `RefMut` from a + thread_local that unsafely escapes the thread_local. It returns a wrapper type + that is zero-cost on chip but on host it is a `Cell>`. The `RefCell` + remains locked, but because it is not `Send` or `Sync`, you can't have + unsoundness due to sharing it across threads. I think it would be more + ergonomic, as nested closures get sucky. +- Alyssa: Rust will soon be stabilizing exclusive range patterns. I know there + was some unpopularity with that in this group -- there's a lint being landed + at the same time. +- Amit: Why didn't we like it? +- Alyssa: The argument was you can accidentally create a wildcard branch that + includes more than you expected, e.g. `(0..10, 10..20, 20..30)` misses 10, 20, + and 30. +- Alyssa [in chat]: @Tyler why would the buffer size be static? The kernel can + send a pointer and size, even coming from within the userspace grant +- Tyler [in chat]: That could work. I'll need to think more about this but this + is an interesting alternative + +## Kernel Testing (#3873) + +- Brad: We discussed this last week. I made a comment summing up some of the + discussion. One question was the name, then there hasn't been other + discussion. I dunno what that means -- everyone happy? More time to think? + Just need to argue about the name? +- Amit: I was under the impression it would be merged. +- Brad: There are still concerns about the name. +- Amit: Yes. +- Branden: I'm good with the name. +- Amit: The name being boards/configurations +- Alyssa: I can't help but bring up config as a shorter version. +- Discussed "config" vs "configuration", some jokes were passed about file size. +- Amit: I was one of the people unhappy with the name, but in lieu of better + suggestions, bikeshedding the name is not a reason to hold this up. There is + some confusion about the name "boards" to begin with that may be a reason to + rename them in general. +- Alyssa: Yeah, different teams use "board" very differently. +- Amit: So this won't change the imperfectness of the concept-we-call-boards. +- Brad: I like the comment of putting "test" in the crate name, so there's no + ambiguity that this is not a "real" board. +- Amit: Is "test" not a valid crate name? +- Brad: Then you have a folder called "test", and it'll crate ambiguity in what + "test" means. +- Alyssa: Could call it a "development" board. +- Brad: That has the same problem. +- Alyssa: "Simulated" +- Brad: Well, it's not simulated. +- Amit: Often `test` folders are for things like unit tests, and more + integration-y tests go elsewhere. +- Alyssa: They generally go in a folder called "tests" too, as a sub-crate if + they're complicated. +- Amit: This would be a folder of crates, because each test case is its own + crate. Each case deployed to a different device. +- Brad: But "test" is ambiguous. +- Amit: We don't really have anything called "test" +- Alyssa: which is not great +- Amit: I don't think this will be overloaded with something else. +- Alyssa: As someone who's not very familiar with hardware, if I open a project + and see "test" it's not clear if it's on-device tests or on-host tests. +- Brad: This is about verbal communication. If I say "you need to run the test", + it's unclear what that means. +- Alyssa: We'd call that "dev board tests" +- Brad: I'm proposing "configuration board" +- Alyssa: That sounds like something else +- Brad: At some point, it's a Tock-ism. +- Amit: I don't share the same fear. If we have a top-level folder called + "tests", with a "nrf52840dk_test_id_sha256", and you told me "run the + nrf52840dk test ID sha256 test", it's a natural place to look. +- Brad: But what if I told you to run the kernel test called "sha256", you'd + have trouble finding it. You have to go to capsules/extra/src/test to find + that, don't you know? +- Amit: I would have Alyssa's intuition that unit tests are in-situ with the + code. If you told me to run "sha256 unit tests in capsules" I would go look + for the implementation for tests. +- Brad: These things sound like they would seem so obvious once you know the + repository, but we're creating more things all called the same thing. +- Amit: Can you point to the file or folder that you think is overloaded? +- Brad: These board are not just meant to be running tests. They're really + supposed to be variants or configurations -- they may look like normal boards + but setup with code that's not commonly used. Won't necessarily look like a + test. +- Amit: I still suggest that we avoid further bikeshedding. We can rename a + folder in the future. To ask for one thing: change the PR title, then I'll + approve it. +- Brad: Can do. + +## Flash HILs (#2248, 2993) + +- Amit: There were efforts two years ago to revise the flash HILs (or add an + additional one) to adapt storage that is different in nature to the ones the + current flash HIL is designed for and maybe address other issues. Those PRs + are very stale. One sticking point is they got a lot of feedback and + enthusiasm, then things fell silent. Is this something we're ready to engage + in, or should we close them? It doesn't seem super urgent for the rest of us, + but I don't feel the flash HIL is perfect. I suspect Phil -- who's not here -- + would be the most vocal. +- Brad: Which PR exactly +- Amit: #2248 +- Branden: It's in the 2000s +- Brad: I think that's a sign. Adding synchronous HILs is a bad idea -- we don't + need it. +- Amit: and #2993. It was making progress, got blocked by a release, then + interest waned, and yeah. Not really changing paradigms but expanding the + abstraction to cover more kinds of devices. +- Brad: We would run into the problem of "do we have code that would use it"? +- Amit: I have this board. Development on that project stagnated. To be clear, + my question is not "is this a PR we should merge now?", it is "this is a stale + PR... are we going to revive interest enough?" +- Brad: If we don't have a use case, we probably won't have the interest. If we + have that, then maybe. I would be more interested in thinking about it. Does + that make sense? +- Amit: Not entirely. This was blocking for upstreaming the board. +- Brad: Is there a pull request? +- Amit: No, it's sitting in a different repository. +- Brad: We have a policy that if we add code, we have to use it. +- Amit: I would upstream that board. It is straightforward except for the + storage. +- Brad: Isn't there another layer like a capsule or something? +- Amit: Yes. It's a watch that works that I wore for 6 months. I think it's an + nrf52840, most hardware is supported. +- Brad: Is this the SMAQ3? +- Amit: Yes. +- Brad: That's already upstreamed. +- Amit: I can look at the details again. This would not be orphaned changes. I'm + hearing that the answer is #2993 maybe, #2248 no. +- Brad: Yeah +- Amit: I'll either take on #2993 or close the PR. + +## TBF header parsing (#3902) +- Amit: I think they're fairly non-controversial. Brad, could you describe a bit + of both? I think the TBF parsing is more relevant to folks, but. +- Brad: #3902 changes how TBF headers are parsed. Currently we have + fully-expanded objects the size of the TLV stored in flash. A lot of legacy + reasons why it's written this way, but it means the object is large. Avoided + passing it on the stack, but my other PR does that, which led to some issues. + Are storing it in process memory for every process, currently 368 bytes (or + similar). This PR would parse things used a lot into full objects, then + lesser-used would be stored as a slice and parsed upon use. This makes the + object shrink to 120 bytes. +- Amit: And the cost is it may be slower to access later. +- Brad: Right +- Branden: Is it slower? Don't we have to parse it at some point? +- Brad: Were parsing it at boot time, moving to whenever we call "get me the + storage permissions". Could happen many times. +- Amit: Another side effect is it seems to save a bunch of code size on many + boards. Don't know exactly why. +- Brad: That makes sense if you never call the function to get a particular + item, the parsing code for that item is removed. +- Alyssa: We currently have totally-custom parsing code; I'll see if the + techniques applied here apply there. +- Amit: Seems fairly uncontroversial to me. I think the main drawback may be in + power-constrained settings. +- Brad: The other benefit is it removes a statically-defined length. No longer + limited to 8 permissions. +- Amit: Lets discuss the other PR. +- Brad: I have a board with a monochromatic screen, I've ported a library to + libtock-c and we can draw on and use the screen. +- Amit: And I believe it gets the screen size dynamically from the kernel, + right? Can apps take a variable amount of the screen? +- Brad: In principle, you can create windows on the screen. Can assign those + windows to processes by name. +- Tyler: I was able to test it. This will hopefully be used for CPSIOT + tutorials. Worked on a PCB to make it plug into the nrf board we're using. +- Amit: We need approvals, particularly for the PR against libtock-c. People + should look at it. +- Tyler: I have the ability to approve, but does that account for anything? It's + been unclear to me if it's helpful. +- Amit: Since you do not have approval permissions, it doesn't count as one of + the two necessary, but seeing other approvals is a signal. + +## TockWorld +- Johnathan: We're ready to make travel plans, correct? +- Brad: Yes +- Amit: Yes + +## libtock-c redesign (tock/libtock-c#370) +- Brad: It's a lot of work, could use help. Touches every file. +- Branden: I thought we were going to merge the PR then fix, I didn't realize + the PR would fix everything. +- Brad: I think we'd end up with half-and-half and never finish, so I wanted to + do it all. I was somewhat concerned we'd finish it then someone would decide + they don't like it. If anyone has any interest in what they look like, now is + the time to comment. +- Amit: I'm interested, I'd like to take a look, you know my reliability. +- Tyler: I can devote some time to that. +- Brad: I think if we're going to do tutorials, lets get the better version out. + Do it now, I don't see any reason to wait. +- Tyler: I agree. +- Brad: I'm really unhappy with the formatting, I wish C formatting tools were + easier to use. +- Tyler: Could continue to iterate on the design. +- Brad: No way do we want to do this again. Don't think if it as iterative, + think of this as one and done. diff --git a/doc/wg/core/notes/core-notes-2024-03-15.md b/doc/wg/core/notes/core-notes-2024-03-15.md new file mode 100644 index 0000000000..4f595931be --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-03-15.md @@ -0,0 +1,138 @@ +# Tock Meeting Notes 03/15/2024 + +## Attendees + +- Amit Levy +- Phil Levis +- Hudson Ayers +- Alyssa Haroldsen +- Pat Pannuto +- Johnathan Van Why +- Brad Campbell +- Andrew Imwalle +- Tyler Potyondy + +## Updates + +Johnathan: Working on third attempt at a sound and testable tock registers design. So far it seems promising. + +## TockBot Check-in +Hudson: What are people's thoughts on PRs immediately being assigned to people? Should we alter the amount of time people need to respond within. +Hudson: We had originally said three days, but there was some pushback to that. +Amit: Generally a week seems reasonable to at least triage. +Hudson: A week seems reasonably to me too if they are assigned immediately. +Hudson: Are we at all worried that this may lead to fewer people looking at these PRs since they will already see that someone else is assigned? +Amit: I think you are right, if a new PR is assigned to someone else, I am unlikely to look at it immediately unless I have a stake in it. We should have some mechanism that is equivalent to looking at unassigned PRs if a week passes without the assigned review activity. +Pat: Could this be the bot assigning a needs triage tag? +Hudson: What would looking more automated look like? +Amit: This could look like that the bot removes the "needs triage" label once the PR is assigned. +Hudson: This does feel like the sort of thing that is entirely possible. This seems reasonable. +Amit: Is anyone against immediate assignment? +Alyssa: This seems to generally be a good idea. Is there a ping to create a notice if there is prolonged inactivity? +Amit: I am not entirely sure, but we need to do that anyway. + +## Reviewed Count Requirement +Amit: Within the documentation working group, there have been a number of PRs that seemed obvious to approve. In the case of say Brad submitting a PR in the documentation working group, it seems only one approval should be required to merge. +Amit: I propose that we step back from the two approver rule. If the person submitting the PR is someone who can approve (core working group), than the first approver can merge the PR if they feel comfortable with the PR. +Phil: Is this something on a per working group policy? +Phil: It seems documentation is smaller so fewer reviews make sense. +Amit: Agreed, I am suggesting this for now for the core wg. +Hudson: I am in support of this. +Amit: To be clear, I am proposing that the approver can merge with one approval on trivial issues. +Amit: For example, if Brad submits a PR that adds a two line change, this should not need to block on two reviewers. +Pat: I believe our policy for upkeep PRs is already one approver. +Amit: Alright, this may be a moot point then. + +## RSA2048 Credential Checker https://github.com/tock/tock/pull/3445 + +Brad: This is blocking on the RSA library supporting none alloc environments. +Brad: There is currently a PR to change the RSA library which, in theory, it would allow us to rewrite the library in a way that allows for using a dynamically allocated type or a statically allocated type. From there, we then could complete this PR. +Hudson: It seems we should mark this as blocked upstream for now. +Brad: Yes, agreed. +Hudson: Unfortunately, it seems adding block labels after someone is assigned, does not unassign them. +Hudson: I think we want to keep the signal of people only being assigned to PRs that are ongoing. +Amit: That's fair. + +## 64 bit timer PR https://github.com/tock/tock/pull/3343 + +Amit: There were a number of comments on the PR. +Hudson: Phil, it seems you had a number of comments on this PR. I can look through this again. +Phil: Yes, I will go through this again to refresh on this conversation. + +## cortex-m: Add initial dwt and dcb support https://github.com/tock/tock/pull/3246 +Hudson: This is listed as waiting on the author. I suspect the author is no longer working on this PR based on the last message. +Amit: This seems relatively trivial to get working since it is on the nrf52840DK board. +Hudson: I may be able to work on this. +Amit: It seems it may be best to close this. I have however used this in the past for benchmarking. +Amit: Occasionally someone comes along to ask how to do this which having this PR to point to is helpful. +Amit: Having the PR open does not cause this to be implemented. +Amit: I think the two paths forward are one of us completing this if someone has cycles, or closing the PR. +Hudson: I have worked on similar things before and I am familiar with this. I think this is useful so I will assign it to myself and see in the next few weeks if I have time to get to this. + +## STM: Improve App Programming https://github.com/tock/tock/pull/3166 +Hudson: This is also waiting on the author (Alex). We should touch base with Alex on this. +Hudson: I am of the opinion that if a PR is stale for over a year, we should close the PR. +Brad: We should close this. This makes app loading very difficult. + +## Fix Temperature Sign (bme280 / bmp280 drivers) https://github.com/tock/tock/pull/3112 +Hudson: This is from Branden in 2022. It looks like the primary blocker was waiting for a breakout board with this temperature sensor. +Amit: It also will need to be rebased. +Hudson: This seems like another example where we shouldn't assign someone to this (since it is blocked on the author). +Hudson: I am going to remove Johnathan as assigned. +Johnathan: I think it is entirely reasonable that Branden should be able to add me again once it is ready for review. +Hudson: I do not get notifications for all activity in my email, I only have activity specific to me (such as assignment) into my emails. I would prefer to only get emails when it is a PR I can do something about. +Amit: So perhaps we should have the bot send an email when you are assigned. +Hudson: Others may disagree with removing the assignee when waiting for the author since it may lead to a number of stale PRs. + +## Tile on Display https://github.com/tock/tock/pull/3067 +Hudson: It seems Phil was ready to merge, but it appears it blocked on merge conflict issues. It seems the original author has not been active recently. +Amit: I can work on rebasing this. You can assign this to me. +Phil: Feel free to ping me. +Brad: Displays are non trivial. There is a lot of diversity in hardware and there are implications for a number of software components we have. There is a more recent PR with an entirely new display HIL. +Brad: Part of the issue with this PR was it was done somewhat piecemeal. +Amit: Yes, this is a very good point. Let's assign me to look into this. Perhaps the outcome is to not rebase and merge but drop this in favor of a tracking issue. + +## OTA App Project https://github.com/tock/tock/pull/3068 +Brad: This is still a work in progress. I guess this PR is still open as a placeholder. +Hudson: Should we mark this as a draft or tag it as a work in progress? +Brad: I do not believe we will try to update this specific PR. +Amit: What should be the outcome then? +Hudson: It seems to me that this should be closed since you plan to replace this with a new PR. +Brad: There is a certain discoverability that is lost when contributors make PRs from their own repos. +Amit: The PR remains, but is marked closed. +Hudson: I agree with Brad that marking this as closed makes it seem that someone is no longer working on it. +Amit: No one seems to be working on this. The last commit was a year ago. +Brad: This code is going to be a PR. +Hudson: If this is still being worked on in the background. This should be converted to a draft. +Brad: I agree. + +## Code Size Progress Report +Amit: Me and some others are trying to evaluate and characterize the code size issue. Obviously, we've heard code size is an issue in a number of cases. +Amit: When we wrote the SOSP paper, a full sensor network app was something on the order of 10-20KB. A blink app with just the kernel was under 5KB. Those were all manually optimized for the paper. +Amit: Now the vast majority of boards we have upstream are 100KB for the kernel. +Amit: The smallest are around 30KB. +Amit: We are investigating what the smallest possible Tock kernel we can achieve. +Amit: This would be a board with no apps that prints hello world to the console just in the kernel. +Amit: This is somewhat fuzzy as changes almost nothing in the kernel, but mainly fudges the chip code to remove unnecessary peripherals. +Amit: This is the starting point for proving there is something still here to optimize. +Amit: The next goal is to do this in userspace. +Amit: It seems promising to have minimum target high bar we can set and getting there in practice is likely going to require some software engineering to exclude unnecessary code and playing better with the LTO. +Amit: Some improvements may require more meaningful changes. For example, the nrf52840 possess a lot of peripherals. A number of these peripherals are unused an are quite expensive from a code size perspective. +Hudson: I think this is a solved issue (linked PR https://github.com/tock/tock-bootloader/pull/19/files). +Amit: Wow, I will look into this. +Hudson: I used this when writing my thesis. +Amit: Another preview as we get lower and lower down of future optimizations: the systick implementation takes ends up compiling as a u64 divide intrinsic. +Amit: We haven't tried to get rid of this yet, but this is an area to improve. +Hudson: I believe for RISC-V we did not see this since there was a different systick implementation. We manually went and edited the code in those places. +Alyssa: What is systick? +Hudson: Systick is the cortex-m peripheral used for keeping time/scheduling etc. +Alyssa: We currently have a list of symbols that we block. It goes through searching for and removing specific symbols. +Amit: That is the progress report. This remains high level for now. One thing I wanted to solicit feedback for now, is that a minimal blink application should be very small. +Amit: I am interested in people's thoughts for examples of minimal code size applications. I think having a more fully featured would be helpful. +Amit: I do not think full openthread support is feasible at the moment. +Amit: Potentially a BLE test app would be good that implements a minimal feature set? +Hudson: It sounds you are looking for a libtock-c application? +Amit: I am looking for a complete set of kernel and application targets. +Brad: We have the HOTP demo and I am also working on a soil moisture demo. +Amit: This could be useful. Is that basically an ADC and sending that over the network? +Brad: It will have one different apps doing Lora, BLE advertisements, and OpenThread. diff --git a/doc/wg/core/notes/core-notes-2024-03-22.md b/doc/wg/core/notes/core-notes-2024-03-22.md new file mode 100644 index 0000000000..998fc37c3b --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-03-22.md @@ -0,0 +1,105 @@ +# Tock Meeting Notes 03/22/2024 + +## Attendees +- Branden Ghena +- Hudson Ayers +- Leon Schuermann +- Amit Levy +- Tyler Potyondy +- Johnathan Van Why +- Brad Campbell +- Alyssa Haroldsen +- Pat Pannuto + + +## Updates +### Failing CI on recent PRs +* Amit: If you have any outstanding PRs that failed to build on the CI even though they should have, it was an issue with static mut on the new stable rust. It's been patched temporarily, so the PRs will pass, but you have to rebase on master and re-push for CI to pass. I tried to do that for other people in most cases. +### Dialpad Logistics +* Hudson: Last week's meeting wasn't recorded automatically. I turned back on automatic recording. There's also an option to share recordings with additional dialpad accounts. +* Amit: Unfortunately, accounts cost money monthly per seat. So it's harder to do. + +## Removing MacOS CI Builder +* Amit: Rationale is that it takes forever. At least twice as long as everything else. And we run everything twice: once for initial PR and once for merging. If the MacOS builder regularly catches important MacOS problems, it's worth keeping. But I think it doesn't. Not any Tock OS code bugs or mis-compilations that wouldn't be caught on other builders. It's more focused on the build process for MacOS as a dev environment. Overall, I think if it's not essential it would be quality-of-life improvement to remove it +* Johnathan: I vaguely remember it catching a build script issue before. We could make an issue where we track whenever it breaks on MacOS in the future, so we could re-enable later if it was a bad call. +* Hudson: Is the main pain the slowness of the merge queue? And would that be resolved if we only ran the MacOS CI on the initial PR and not the merge queue? +* Amit: The merge queue is more painful. We can always hit the "merge when ready" button. +* Leon: Not true. It's marked as a required check, so we can't hit the button until it's ready. +* Amit: Another option, does running it nightly (or something) on Master. Would that be sufficient? +* Leon: We could have a github action job for that +* Hudson: The question is whether we'd notice if it broke +* Leon: The libtock-c mirror check opens a github issue if a build fails. So that would work +* Hudson: That seems great +* Brad: I'm very for this. I wanted the MacOS check gone for a while +* Pat: I usually advocate for keeping MacOS around. Why is the merge queue being slow an issue? It's hit it and forget it. So who cares? +* Leon: The merge queue just goes through PRs one at a time and is pretty slow +* Amit: And merges can cause conflicts +* Brad: And often we merge a bunch of PRs at once when people have time. And you have to babysit it to fix things +* Pat: Okay, this isn't too strong of an issue for me +* Branden: I'm also for this. If we get issues that MacOS CI is broken, we'll fix it pretty quickly in practice. +* Amit: MacOS CI does break rarely. If it's every few years, we're fine. If we find the frequency is often, then we'll revert this. +* Pat: I do think of CI as our statement of what first-class environments are. And not including MacOS does make it feel less tier-one. But I do see pragmatism winning out. +* Amit: Yeah. I do see that. The issue is just that the github CI runners are stupid slow +* Pat: Maybe we could buy a Mac mini and put up the runner ourselves? +* Amit: Interesting idea +* Leon: There's also a security issue. You have to do a LOT of work to run stuff in a VM. Someone could try to take over the machine. We also want a clean, reproducible build environment. +* Pat: I think we'd just still only run it once it goes into the merge queue +* Johnathan: But someone could open a PR that changes it to run immediately right? +* Amit: There is a limit that first-time contributor checks don't run +* Leon: You also can't change the file for how it runs in a PR. It respects what's in master +* Brad: I think realistically we aren't going to have our own Mac mini runner. +* Hudson: Seems like more effort than fixing the very few MacOS bugs per year +* Amit: Okay, I'll take on the task of handling this + +## Moving modules out of the kernel +* https://github.com/tock/tock/issues/3845 +* Brad: In the kernel we have traits and implementations of those traits. But the implementations don't need internal kernel stuff and could live anywhere. That's the point, that you could make your own implementations, and ours are just for convenience. +* Brad: So, is it worth moving these implementations somewhere else? +* Amit: Yes +* Branden: What's an example? +* Brad: Implementation of what happens when a process faults. Another example is application ID assignment, we have some default ways. +* Hudson: I like the idea of them not being in the kernel crate, but I don't like them in capsule crate, as they aren't capsules? +* Amit: How are they not? +* Hudson: I feel like this would further confuse what it means to "be" a capsule +* Amit: These don't require unsafe, right? (Yes) +* Amit: Organizationally, there are capsules_core, and capsules_extra. So these don't fit in those, but there could be a separate crate either there or somewhere else without capsules in the name. I guess it makes sense that people wouldn't look for these where the sensor drivers are. +* Hudson: These are indeed not sensor drivers. I think it's reasonable to limit capsules to drivers of some type. I wonder where a good place would be +* Hudson: From https://github.com/tock/tock/tree/master/capsules capsules are drivers or virtualization or syscall interfaces +* Amit: We could have a new top-level crate for these +* Amit: Although, I'd define capsules as "untrusted kernel components". Not part of the trusted-compute base. But that's not super important to fight +* Hudson: Do the implementations touch internal kernel private fields? +* Brad: No, or it wouldn't work +* Amit: These are really things that are meant to be "pluggable". +* Amit: So is the only issue with this bikeshedding what their name is? (Yes) +* Brad: Okay, I'll make a first PR that moves these somewhere + +## Signup for libtock-c updates +* https://github.com/tock/libtock-c/pull/370 +* Brad: Two things. I'm looking for help here first. Just implementing the changes. +* Brad: Number two, I want to get rid of some old APIs. We have some custom system call interfaces for one-off chips from years ago. It really ties an application to a very specific interface, and every chip would need its own bespoke interface. This isn't something we use or want to promote +* Amit: I agree in concept. Can you give an example though? +* Brad: Things I put in the chips directory. Some of these are things that should be updated to the generic system call interface. +* Amit: Okay. I support this. This sounds like part of a libtock-c reboot, where part of that is removing unnecessary or stale old stuff. +* Amit: One question, should the removal be in the same PR or a different PR? My sense is that if we removed a bunch of these from mainline, we'd also remove the example applications for them. And probably no one would notice or care. So could that be a separate PR? Or should we keep it all together? +* Brad: I don't care +* Branden: There's no need to make extra work, but if it's easy to make it as a separate PR, that seems useful. +* Amit: Okay, let's do that. +* Brad: Back to the first issue of needing help. +* Amit: My suggestion, is it a lot of work to make a checklist of categories? (No) Then I can claim some of those categories. It's straightforward moving, right? +* Brad: It's a little more than that. Halfway to rewriting. New names and sometimes cleaning stuff up quite a bit. Some of the files are pretty non-standard, like the screen for example. +* Amit: Oh, it's the standardizing part that's work. I see +* Amit: And we shouldn't separate that from the moving of files +* Brad: No. It would be more work to change things twice. And it makes sense to make one big change +* Amit: Okay. Make the categories list of what's left, and I'll grab some and others can too +* Brad: One more question: Should we have a rule that the libtock library doesn't have any internal global state except for perhaps static functions. +* Amit: Meaning no global variables? +* Brad: You have interfaces that are public. Then internal functions that are private. But no private internal state +* Amit: Okay, so looking randomly at one of these, there's a statically allocated results struct. +* Brad: That's in libtock-sync. It MUST have some small amount of private internal state to synchronize things +* Leon: I think this is a good idea. Because it will eventually support having multiple instances of a system call driver. +* Amit: Okay, so looking at libtock-c in the PR, we do still have a static result, which could have been stack allocated instead or passed in. And now it's non-re-entrant and can't be reused with the current design. +* Amit: So, no private state seems like a fine rule. But we should keep our minds open to it when doing this PR and see if there's a good case for it we didn't anticipate +* Brad: I'm reasonably confident that there isn't one. And we rarely do it now for the async stuff. But there is some use of internal state that might even seem intuitive, so I think we just have to pick a consistency thing. I want everything predictable +* Amit: I agree. And we can back off if something causes us to question it +* Branden: I super agree that we don't want internal private state. And if you're documenting it, we should also decide what state we DO need for libtock-sync as we should be minimalistic about it + diff --git a/doc/wg/core/notes/core-notes-2024-03-29.md b/doc/wg/core/notes/core-notes-2024-03-29.md new file mode 100644 index 0000000000..6257597e68 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-03-29.md @@ -0,0 +1,102 @@ +# Tock Meeting Notes 03/29/2024 + +## Attendees + +- Amit Levy +- Andrew Imwalle +- Brad Campbell +- Leon Schuermann +- Johnathan Van Why +- Pat Pannuto +- Tyler Potyondy +- Hudson Ayers +- Alexandru Radovici +- Alyssa Haroldsen +- Vishwajith +- Branden Ghena + + +## Updates + +- Amit: Code size analysis. At a completion point. Writing up results now. + Describing where code size is going, and some recommendations. Initial doc in + progress. +- Some results: TBF header parsing not so bad compared to other features. GPIO + init is tricky. Some small but surprising things. + +- Tyler: OpenThread: libtock-c PR open. 6LoWPAN bug and alarm are two remaining + issues. 6LoWPAN bug addressed by trait updates (in progress). +- 7 minute timer issue. nRF can handle a prescalar. Need more comments on this. +- PRs to look at: https://github.com/tock/tock/pull/3933, + https://github.com/tock/tock/pull/3940 + +- Alex: Packet buffers with [u8]. +- Leon: Migrating console API down to UART to new interface. Adding room for + headers and footers in buffers. Useful for protocols. Right now just a byte + slice interface. +- Brad: This didn't work for subslice. +- Leon: Many design iterations, now have a design with is a superset of + subslice. +- Alex: Right now use 12 bytes overhead. Once `const` instructions in Rust is + stable this overhead goes away. +- Alyssa: Will likely be a while until that is stabilized. I recommend nesting + in another type with a fixed footer. +- Leon: Current implementation does not need nightly features. + +- Hudson: Rebased https://github.com/tock/tock/pull/3934. Need libtock-c driver. + Interface with debug!() macros? Only works on cortex-m right now. +- Brad: Is there a riscv equivalent? +- Hudson, et al.: Unsure. + +## Bar for Capsule PRs. + +- https://github.com/tock/tock/pull/3881 is for userspace support for NMEA + devices. +- Issue arising: "is this the right interface?" or "is this interface and its + capabilities appropriate for upstream Tock". +- How do we determine if something is suitable for upstream? When do we say no? +- Brad: I2C some open questions. NMEA: is this suitable? +- NMEA: unclear +- Hudson: could this be specific to one chip? +- We don't know the exact chip. +- Downside: others would come around and think they could use something, and it + doesn't quite work right. Some design decisions which weren't great get + copied. +- Our bar should (basically has to) be lower for brand-new + support/drivers/chips. +- NMEA support is good. +- Part of the logic is outsourced to userspace. Difficult to reason about the + driver. +- NMEA model is like UDP. Have to wait until data is available. Odd fit with + I2C. +- What is the process for handling this? Hard to just say "no". Want to avoid + randomness in the response. +- Can write down what we want or what the expectations are. Hard to draw the + line when the metrics are more subjective. +- Just might not want to include things in upstream. +- Can we cite previous examples? + + +**ACTION ITEM**: Amit: propose a plan for normative documentation. + +## Timer and Prescalar + +- OpenThread needs a global time clock. On nRF wraps at 512 seconds. +- https://github.com/tock/tock/issues/3938 to change the prescalar. +- `time` HIL has overflow functionality. Could notify userspace on overflow and + keep track of wrap arounds for current time. +- Not sure even what the right timer to use is. +- Might be better to use a timer with higher resolution. +- Can't tie a networking stack to a specific timer. +- Might not want to wake up every 7 minutes in any case. +- Could ensure that the counter provided to userspace always wraps at 32 bits. +- Could shift left 8 bits to make a 24 bit counter look like a 32 bit counter. +- Brad: what is the simplest approach that really solves this problem? +- Always have a 32 bit counter. Userspace sets a timer to check more frequently + than the timer wraps around. + +## Libtock-c rewrite + +- https://github.com/tock/libtock-c/pull/370 +- 10 left + apps +- Assign to Amit diff --git a/doc/wg/core/notes/core-notes-2024-04-12.md b/doc/wg/core/notes/core-notes-2024-04-12.md new file mode 100644 index 0000000000..17c4e54af0 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-04-12.md @@ -0,0 +1,94 @@ +# Tock Meeting Notes 2024-04-12 + +## Attendees +- Branden Ghena +- Hudson Ayers +- Phil Levis +- Andrew Imwalle +- Leon Schuermann +- Johnathan Van Why +- Tyler Potyondy +- Alexandru Radovicii +- Brad Campbell + + +## Updates +* None + + +## Libtock-C Rewrite +* https://github.com/tock/libtock-c/pull/370 +* Brad: Since last week, there's not been a ton of progress except for Hudson on SPI. +* Hudson: Started looking into libtock-c rewrite and ported SPI driver. Also renamed that to SPI controller to match the kernel capsule +* Brad: For the remaining ones, is it okay to call some "outdated" and leave them alone? +* Hudson: I think it makes sense to me. We can move forward incrementally, as long as everything builds. Notably, currently things don't build +* Brad: What doesn't build? +* Hudson: Examples, which haven't been ported to the new APIs. So I guess you can build, but you can't test +* Brad: Yes, a handful have been adapted, but not all of them. +* Brad: I think it does make sense to not let a perfect end-goal stand in the way of progress +* Phil: Can you provide background? +* Brad: It's a format for writing the libtock-c library drivers, and the actual implementation of that rewrite. Here's the actual documentation: https://github.com/tock/libtock-c/blob/4d03cc5c0b1cbd464d5efac021534d0177ed2d3c/doc/guide.md +* Phil: Is there anything technically difficult about it, or just lots of work? +* Hudson: There are a few drivers like timers, that don't fit well into the new design, so we have to think about those. But mostly it's not crazy difficult. +* Phil: I would be interested to pitch in, in a few weeks, if stuff still needs to be updated +* Hudson: I was planning to spend time on alarm/timer next week, but maybe I should spend time on porting examples instead? +* Brad: I'm conflicted. Timers are so common that they seem pretty important. +* Brad: The timing requirement here: tutorial is going and needs documentation, but we really want to focus the docs on the updated version of libtock-c. The current version is rather inconsistent. So we don't want the tutorial to wait too long, but we want it to be on top of this update. +* Phil: If we plan to have to the code updates done by the end of April, that might be okay. +* Hudson: Another though, I wonder if it's better to port apps "one driver at a time" rather than "one app at a time". Because you could just change names of functions. +* Brad: It's just like the drivers, they vary. Some apps are straightforward and some are very much not. I'm not sure what's easier. +* Hudson: Okay. So you have run into places where you can't just rename the function. +* Brad: Yeah, they range all over the place. Some are trivial and some are a very different model. +* Phil: From the tracking item in the PR, there aren't many left +* Hudson: True, but the examples aren't on this tracking list and most haven't been updated. +* Phil: Looking through, there are like 70 apps between examples and examples/tests. So there are a lot +* Branden: That's its own mess too. We could address that now, but don't have to in this PR +* Phil: So backing up to the tutorial, what do we need to do for it? The important thing is the tutorial code being in good shape. But we do also want to avoid leaving junk around. +* Hudson: So maybe I'll do alarm/timer first, then focus on examples +* Brad: IPC, Console, and Alarm are the big three. And of those Timer is most important. Printf isn't going to change, and we don't use IPC in most stuff. +* Phil: How about, since I was deep in the timer stuff, I could lend Hudson a hand so he doesn't get lost in it +* Hudson: When I looked into it, it seemed that most stuff wouldn't change. One thing that's weird is that we have an alarm.h and timer.h but one alarm.c which defines things across multiple header files. There's some internal stuff that would go in syscalls. Also that we have delay_ms and yield_for_with_timeout, which would probably go in the libtock-sync folder in the redesign. But maybe these are things they would need for any platform/application. +* Brad: delay_ms is clearly synchronous. +* Hudson: I'm about an hour in to looking at it and wrote down some notes on things that are unclear +* Phil: I can't code until next weekend, but I could advise on questions +* Hudson: At the end of the day, until all the examples are ported there's no way to use this for the tutorial. But I don't think it'll take longer to do examples first, then timer after, then go back and fix some more examples. +* Brad: I will try to make the same checklist for examples. I don't think it's as bad as it seems +* Brad: I do want to know that we're going to merge this +* Hudson: I think as soon as most of the examples work and can build, we can merge. It's definitely an improvement +* Hudson: We do have to be careful to test things, as there are places we could introduce bugs. But also, I fixed a couple bugs in SPI when porting, so maybe things are improving too + + +## PicoLib Support in Libtock-C +* https://github.com/tock/libtock-c/pull/357 +* Brad: This has come up various times. But until recently, it seemed like we had to use PicoLib OR Newlib, but not both. Now that we have Makefile support for libraries, it's actually more straightforward to just support both. +* Brad: So this PR adds PicoLib. It kind of worked. There are a few low-level, read write seek, functions that have different names. But I added a wrapping to convert +* Brad: So, this compiles. I probably works. A question is whether we want this. +* Hudson: I'm interested in how different the compiled binary sizes are. +* Brad: No idea +* Hudson: I also assume that Alistair would be interested in this, as I believe he was advocating for PicoLib a while ago +* Leon: How did you work around the issues Alistair ran into? PicoLib apparently had an sbrk implementation we couldn't overwrite easily. +* Brad: There was a configuration option somewhere in the PicoLib documentation. It was hard to find. Here's my build script: https://github.com/tock/libtock-c/blob/21174ddfd0965069a6ab75fdc609200a74bf4a2e/picolib/build-arm.sh +* Phil: To check on https://github.com/tock/libtock-c/pull/353, does that PR mean that you can't build things the first time without an internet connection? +* Brad: Yes +* Brad: So, the question from me: is there interest in PicoLib and is it worth trying to merge this PR? +* Johnathan: I think it would be great to add it. There is a file or two in PicoLib that are AGPL licensed. That makes it very scary for proprietary software authors. So there was a question of whether that file needed be deleted or something. My opinion is that PicoLib support is almost all of the battle. Then it can be on the people who are concerned to provide their own PicoLib files that don't include that. +* Hudson: So the other issues Alistair had are resolved in your PR? +* Brad: Yes, as far as I know. +* Hudson: So it seems like the top-level ask here is for someone to approve this PR or suggest any remaining changes + + +## Handling static mut +* https://github.com/tock/tock/pull/3945 +* Brad: So what's the status here? +* Leon: The solution proposed should address our concerns, and doesn't make things more unsound than they maybe already are. I haven't been able to follow this lately due to other deadlines +* Leon: Brad's comments are valid concerns and we need to express some of why we need this. Some of the confusion is that this is derived from a multi-core version that we also have. But we distilled it down to the APIs that are needed for both, and then the PR only has a single-core system. It gets us something sync that also has interior mutability +* Brad: My understanding is that after last week, we are in general agreement with what you're saying. I believe Amit was going to open a new PR with just the types and new documentation. Then we'd have a separate PR for integrating the types. +* Leon: Okay. I'm not sure what the current progress on that is. +* Brad: To me, it feels like this was farther away from mergeable than indicated on March 1st. And maybe we should implement the hack solution so we don't have to wait on this and can move on +* Leon: I think with documentation this is mergeable now. I am personally fine with a different stop-gap solution though, such as the address_of macro. That change wouldn't break anything additionally. +* Leon: A concern is that doing that would reduce the pressure to come up with a good solution. We do things now that are very much unsound, and CoreLocal does address some of those issues. So we really want to integrate this kind of a change +* Brad: I don't think there's hesitation to including this. The challenge is actually doing the documentation and thinking about the upgrade path for other systems with different architectures. Those things take time and are necessary. So, I don't think that a stop-gap will remove the interest in this. But I also think this won't get merged without those things. +* Johnathan: Reminder that Alyssa is giving a talk on global mutable data after this meeting +* Leon: Okay, maybe by end-of-weekend Amit or I will push the stop-gap solution. And then we can get back to this PR after other deadlines + + diff --git a/doc/wg/core/notes/core-notes-2024-04-19.md b/doc/wg/core/notes/core-notes-2024-04-19.md new file mode 100644 index 0000000000..1559221ea9 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-04-19.md @@ -0,0 +1,21 @@ +# Tock Meeting Notes 2024-04-19 + +## Attendees +- Branden Ghena +- Brad Campbell +- Pat Pannuto +- Johnathan Van Why +- Viswajith +- Alyssa Haroldsen + + +## Updates +* Chatted about Makefile syntax and debugging for a bit +* Brad: Working on updating the nightly https://github.com/tock/tock/pull/3842 Still some build errors. Failing in the tests, which only run on CI. It looks like the stopgap PR didn't quite cover everything + +## Libtock-C Rewrite +* Brad: I'm going to try to rebase the libtock-c rewrite PR. We should then have a better sense of how close it is to compiling +* Branden: That's multiple Makefile updates lately, right +* Brad: Yes, everything for the split of libtock and libtock-sync. The others are for openthread + + diff --git a/doc/wg/core/notes/core-notes-2024-05-03.md b/doc/wg/core/notes/core-notes-2024-05-03.md new file mode 100644 index 0000000000..1a5341000d --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-05-03.md @@ -0,0 +1,101 @@ +# Tock Meeting Notes 2024-04-19 + +## Attendees +- Branden Ghena +- Amit Levy +- Alexandru Radovici +- Samir Rashid +- Leon Schuermann +- Anthony Tarbinian +- Amalia Simion +- Hudson Ayers +- Tyler Potyondy +- Alyssa Haroldsen +- Johnathan Van Why +- Andrew Imwalle + + +## Introductions +- Samir and Anthony are MS students at UCSD. Joining today to talk about Flash isolation +- Amalia is a student working with Alex and is working on the PacketBuffer application for console + + +## Updates +- Tyler: State of the world before the CPS Week Thread/Tock tutorial. We're mostly merged with the PRs and Todos. There are some alarm fixes needed still, but I think the smaller Alarm PR Leon has is likely to fix it. Also one Link-quality Indicator as well. The libtock-C port is also almost ready to merge. +- Tyler: In terms of writing, Leon and I have a vision for the applications and we'll hopefully finish the implementations today. We'll do the writing next week and we'll call out to others to check over that writeup + + +## PacketBuffer / Console Demo +- Hudson: Background is using PacketBuffers to separate kernel and process output +- Amalia: (sharing screen to demo things) +- Amalia: Developed a host-side application that can lead you interact with multiple Tock processes simultaneously. This app lets you choose between the applications on the board, keeps a list to let you select which one you're talking to over serial. Also lets you choose the kernel as one of the output/input sources. +- Amalia: This is the first version of the application. You can only see one process at a time. But there is also work in progress to have a layout which shows multiple process outputs simultaneously. +- Amalia: (demonstrates both of these working!!!) +- Hudson: This is really exciting (general agreement). A question, what happens to output from an application that's not in the foreground? Is it buffered for later? +- Amalia: There's a parser for each app. The messages sent when the process isn't in the foreground is saved. Separate buffers for each process +- Alex: It's a VT100 terminal too, so it renders with colors and stuff +- Amit: What about input? Can that be multiplexed too? +- Amalia: Not yet. Tock is also not able to multiplex input, so even typing in the app goes to the process console +- Amit: Can you explain details of how this works? How does the PacketBuffer come into it? +- Amalia: The buffer infrastructure Leon made allows for appending headers and footers to payloads throughout layers of a driver stack. In the uart stack, when a message is sent a process ID is appended, and also there's another flag for whether the source is a process or the kernel. With this info, the host application is able to distinguish the data sources. We also append a footer for the end of the packet. So the host-side application looks for the end of the packet, parses the header, then filters to the proper application console. +- Leon: I am also really excited about the state so far. I made the first version of the PacketBuffer, but Amalia has done a TON of work from there. Two things to mention about the PacketBuffer: 1) it completely avoids reallocation by communicating how much space is needed for lower layers and 2) it's guaranteed at compile time that there's actually enough space for the headers/footers. So a driver will never run out of space it needs to append stuff +- Alyssa: Do we use nightly generic const expressions? +- Leon: No. Unfortunately, we do everything manually for calculating sizes to avoid that. It would be super nice if we could add it, if it ever became stabilized +- Amalia: At each layer we have our own needed Headroom and Tailroom, and the lower layer's needed Headroom and Tailroom. So there are lot of const generic values passed through the interfaces of layers. +- Leon: Specifically, there's an operation that can convert between generic types with differing amount of headroom/tailroom by having a wrapper that's entirely inlined and optimized away +- Hudson: What's the raw serial output look like when this is running? Does it still make sense? +- Amalia: Sort of. There are non-printable characters that appear everywhere. Before and after each set of bytes sent. +- Hudson: It's not too bad, actually +- Alex: So, what would be the roadmap to implement this? Our hope is to integrate this host-side application into libtock-rs. How would this be merged into Tock side? +- Hudson: I think it depends a little bit. Do you expect libtock-rs to be at feature parity when you merge this? +- Alex: Probably not. We're hoping for usable this summer, but Tockloader has many features. We could add an interface for this to Tockloader, at least so it could remove the non-printable characters +- Hudson: We could totally have it as an option and explain how to enable and use it +- Leon: Also reasonable to me would to have it only be enabled after you send some magical character sequence. So any board would look fine until you started the host-side application, and it would switch at runtime +- Alex: I think that would work +- Hudson: Any idea what the code-size overhead of this would be? +- Leon: We haven't measured at all +- Branden: If it's a separate capsule in main.rs, we could have boards use it but people who care about code size can just not use it +- Leon: That could work. We would have to move the UART HIL to packetbuffers if we wanted this +- Leon: My vision for this now is to re-approach the PacketBuffer implementation and propose that to merge into Tock fist. Then later we would plan to merge the changes to UART. Then this bigger change +- Alex: So we should implement the PacketBuffer stuff in the UART HIL and console and whatnot, but without adding any extra data first. Then we could measure the size of that +- Tyler: So for the python Tockloader listen, we would just filter out the bytes here? Do we think other users might be using some other serial interface and this would get annoying? This would somewhat force Tockloader on users +- Hudson: Leon's suggestion of only enabling it after a magic control sequence from the host would fix this. So Tockloader implementations could send that sequence but other serial consoles would not +- Alex: Yeah, a process console command maybe. We might also have a flag that completely disables it. +- Alex: Amalia will be at Tockworld this year, so we'll plan a larger demo there + + +## Process Isolation for App Non-volatile Storage +- https://github.com/tock/tock/issues/3905 +- Tyler: Some context here, OpenThread needs to write some persistent state to Flash. Particularly the network key. Currently we just dump that into shared Flash which any app can access, which isn't great. So Samir and Anthony have been working on the problem +- Anthony: The motivation is that OpenThread stores crypto keys, and those should be isolated and other processes shouldn't be able to read it. We started by looking into what Tock currently has for Flash interfaces, the app_flash_driver and nonvolatile_storage_driver. +- Anthony: App Flash does have isolation, but it forces us to write the entire Flash region every time it gets changed. And we don't want to write a full 4 kB every time we change anything. +- Anthony: Non-volatile storage does let you write on a page basis (smaller than 4 kB), but doesn't have isolation +- Anthony: We wrote some approaches in the issue. Option 1 was to have a main.rs file that allocates certain regions of flash to certain apps. The downside is that the kernel must be aware of the processes running on the board. +- Anthony: Option 2 is for processes to make requests of how much Flash they need, and the app tracks ownership of page allocations. So there would be some table (in non-volatile memory) with allocations. Certainly more complicated, and there's a question of how long does the process claim the page for. What ever releases pages? +- Anthony: Option 3 was designed by Alistair and suggests that instead of hard-coding regions, we make some generic per-app flash size. So every app would get one page or ten or something. And user processes could query to see how much space they have and interact with it. Still sort of has issue of ownership of process Flash regions and whether they're ever given up. +- Anthony: One more idea we brainstormed with Pat but haven't written yet. We could offload the complexity to Tockloader and have Tockloader read Flash regions and swap out the Flash storage with the application, so that state gets preserved. So if you re-flash the app, the Flash storage would come with it. Then you wouldn't lose any state when you add new apps. +- Anthony: We wanted to bring this up as a design overview to get some thoughts from the group. +- Tyler: We're hoping to learn what people have strong opinions on before starting work +- Branden: I'm not sure about the application needs. When does storage go away and when does it persist? If you reflash the kernel does it go away? What if you reflash an app? +- Tyler: Good question. Currently, I think as long as an application is on a board, it should keep the region. +- Branden: I think there are cases on both sides. Sometimes you want storage to stay as you make little changes and bugfixes. Sometimes you want the storage to disappear when you've changed how it works. +- Tyler: For Tockloader support, it would be about moving an app. We think that should also still have persistent memory, since the application is still installed. +- Tyler: I do think the ideas from Alistair and Brad on the issue about some linked-list structure for tracking the allocation of regions would be useful. Do other people on the call have differing opinions from them or pushback? +- Leon: This isn't the first time we've had this conversation. I think this was envisioned as part of App ID too. Maybe Phil should be part of the discussion +- Johnathan: It was certainly part of the original design +- Leon: Our workaround for the tutorial is that kernel can reserve flash storage in the kernel, and we're giving the app access to that. But reflashing the kernel erases that, which is unfortunate. The reason we're choosing this right now, instead of putting it somewhere else, is that we would need to have a new region that Tockloader is aware of so it doesn't overwrite it. +- Leon: We do have some changes in Tockloader where we place a footer in the kernel binary that has some details about application flash. So we could maybe have an extra field in there that tracks where bonus flash for applications resides +- Hudson: I would love to see any solution move forward and not let perfect get in the way of good enough. I think an initial design can ignore Tockloader adding/removing/moving applications and treat that as erasing Flash. So we promise storage across reboots, but not across loading applications +- Alex: One extra complication, this stuff works pretty well on the nRFs, because there are many flash regions. But STMs and NXP microcontrollers have much coarser flash regions and can't modify one without moving code to memory and executing from there. We already have some Tockloader issues on those platforms so be careful not to make that worse +- Tyler: Good to know +- Tyler: I know there was a proposal for app signing once upon a time. Is there something we should be careful about to not exclude using that stuff at some point? +- Johnathan: There's a very long thread on the Tock dev mailing list from 2020 that explores that in way too much detail. For the purpose of storage isolation, if two processes have the same application ID, they are the same app and should have access to the same storage. That's not just concurrently, that might be across reboots, reflashes, etc. So your storage driver can still identify it on reboot. What I don't know is if that property was maintained for short-IDs for applications. Overall, I would say that your identity should be aligned with application ID. +- Johnathan: There is a kernel question about when to deallocate memory. If a process isn't there at all, it may just temporarily be gone +- Johnathan: So maybe browse that email thread, but definitely read Phil's Application ID TRD +- Samir: Thanks for all of that! One question we are considering: should an app ever be able to ask for more flash, or is there some initial startup size specified during flash time, and that's it? +- Tyler: A parameter in the Makefile like STACK_SIZE for example +- Hudson: Okay, that's pretty different from Alistair's proposal then. Where apps could make dynamic requests at runtime. +- Tyler: Still a thought in progress. It seems like maybe a hardcoded design would be simpler than a dynamic design +- Hudson: I do see the advantages of both approaches, and haven't thought enough to support either right now + + diff --git a/doc/wg/core/notes/core-notes-2024-05-10.md b/doc/wg/core/notes/core-notes-2024-05-10.md new file mode 100644 index 0000000000..f29391da47 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-05-10.md @@ -0,0 +1,67 @@ +# Tock Meeting Notes 2024-05-10 + +## Attendees +- Branden Ghena +- Brad Campbell +- Leon Schuermann +- Amit Levy +- Andrew Imwalle +- Hudson Ayers +- Viswajith +- Johnathan Van Why +- Alyssa Haroldsen + + +## Tutorial Updates + * Leon: We're doing a final run-through of the tutorial. All the code's in place, although there's still a PR or two that won't be controversial. We're still breaking down the code into milestones and making sure things are consistent with the libtock-sync API. + * Leon: We're also doing a cleanup of the writing and publishing a new VM image. + * Leon: There will be one more semi-large libtock-c PR, which only adds the tutorial folder with a bunch of apps. So that shouldn't be too hard to merge + * Brad: Are there other things in the branch that need to be merged upstream, other than what's in the PRs right now? + * Leon: There's an open PR for the entire branch, I think. We're pushing directly to the branch and everything goes there, so the PR should reflect the changes. That's a libtock-c PR + * Leon: The changes on the PR, as far as I see, migrate openthread to libtock-c-sync, and add a bunch of applications in the examples / tutorials folder + * Kernel PR - https://github.com/tock/tock/pull/3979 + * (There is no PR in libtock-c at this time, it'll be here soon) + * Leon: For the next few days, anything non-controversial but time-sensitive we'll send out a notice in the tutorial slack channel. + * Brad: That sounds good + * Leon: We think everything should be in by tomorrow + * Brad: The getting started guide is also in progress: https://github.com/tock/book/pull/38 + * Branden: Can you explain this? + * Brad: Backstory from Pat was that they did a tutorial runthrough at UCSD, and the getting started guide was a bit much. I agree that it's got a TON of information, and it's not always obvious which parts are important. So the thought was that breaking it down into parts could help guide you to what's most important. + * Brad: So I did a first pass in this PR. The goal was for Pat or Tyler to review and make additional updates for problems they saw from the runthrough + * Brad: As far as the tutorial text goes, is there anything you need there? + * Leon: We have a draft but we're going through it right now. Tyler is still producing text for the openthread parts. That won't be as bad as it sounds though, as people will only be doing small additions to the openthread starter code. + * Leon: I'll keep people in the loop as the text changes and when we want reviews + * Leon: For the entire tutorial we have roughly 3-4 hours, counting some setup. So we are tuning down the tutorial a little to make it fit in time. We have roughly 10 people signed up now. So it's possible that people will finish early, and for those we intend to do ad-hoc walkthroughs of app signing and things like that. + * Brad: I think you don't have to worry about adding more stuff. People will either just want to stop when finished, or will want to dive deeper and will be able to be guided to documentation + * Branden: Hands-on stuff like this always takes longer than you expect too. So I wouldn't worry about getting done too early + * Leon: We do have a getting-started guide, but people aren't necessarily going to go through it in advance, even if we tell them to. So that will take some time too + * Brad: I do wonder whether you could get people to compilation _while_ you're doing the introduction stuff. Particularly first-round compilation on some of the libraries + * Leon: I have the VM on some flash drives that I can just do first-round compilation on for them before handing out. + * Leon: We also have a tock-assets website that uses a CDN, so it should work better than you might expect, even in Hong Kong + + +## Rust-NL + * https://2024.rustnl.org/ + * Leon: We met with a bunch of people from the Rust Embedded working group, that work on the HAL crates and things like that + * Leon: They run into many of the exact same issues we do. Static muts, and hardware CI, and stuff. Really nice to have conversations with them. + * Amit: Even if sharing code isn't going to happen at a large scale, I think there are many overlaps in challenges and sharing of solutions. + * Leon: We'll present a more organized version on a subsequent call + * Brad: It would be pretty cool if we could share testing hardware, for overlapping boards + * Hudson: Was there any mention of the recent libtock-rs PR to using some of their stuff? + * Leon: I mentioned it and they approved of it. They thought that this is what their interfaces are for and that it makes sense. + + +## Libtock-C + * Brad: Libtock-c rewrite is in, with new interfaces and a new style guide. It's hopefully MUCH more consistent for all drivers. So there's some consistency and most drivers work the same + * Brad: Other benefits are the split between libtock and libtock-sync. Libtock does asynchronous code only. There are no uses of yield and you'll only get an asynchronous API. There's almost no internal state too, so almost everything is exposed back to you. The services folder is the exception to the state thing. + * Brad: If you want a synchronous version, which needs some small internal state, that's the libtock-sync code. So hopefully this makes it clearer what to use depending on your desires + * Hudson: I think it's an enormous improvement + * Brad: I will add, that from porting some of the examples that if you're using the sync API it's mostly the same as before with some renaming. The asynchronous versions, which have callbacks customized for the use case, take a little learning but even with that for the majority porting should be straightforward. Not a lot of gotchas. + * Hudson: I noticed in the now-merged PR had a description that some BLE, IPC, and I2C stuff isn't ported over yet. Is that still true and is it tracked in an issue somewhere? + * Brad: That's correct. We do need a tracking issue for that + * Hudson: We also move some stuff to a folder outside of CI. Should we include that in the issue too? + * Brad: Some of those, but not all. Old courses don't really matter. But some of it is SPI stuff that needs to be updated + * Brad: Amit is putting work into IPC and maybe has something that's close anyways + * Hudson: Okay, I'll make a tracking issue for this + + diff --git a/doc/wg/core/notes/core-notes-2024-05-17.md b/doc/wg/core/notes/core-notes-2024-05-17.md new file mode 100644 index 0000000000..e5dc8cf6fe --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-05-17.md @@ -0,0 +1,111 @@ +# Tock Meeting Notes 2024-05-17 + +## Attendees +- Branden Ghena +- Brad Campbell +- Amit Levy +- Hudson Ayers +- Pat Pannuto +- Johnathan Van Why +- Alyssa Haroldsen +- Phil Levis +- Alexandru Radovici + +## Updates + +- Amit: Synthesizing RustNL takeaways. +- Pat: CPS/IoT Week tutorial: small but engaged audience. + - Tutorial went well, good starting point for TockWorld7. +- Amit: re: TockWorld 7. + - Talks to fill schedule. + - Not (currently) including googlers. + - Call to register coming soon. + - We can pay for speakers. +- Brad: soil moisture sensor demo app in progress. Works for sensing and screen. + Todo: connect to Thread and compile libtock-c out-of-tree. +- Alex: work ongoing to simulate Tock. + - Compile kernel on webassembly. Run the kernel with applications. Swap out + hardware at HIL-level. + - Plan is to upstream. + - JVW: new registers intended to support similar goal. Allow using Tock + peripheral drivers and swap out at the hardware layer. + +## PRs + +- https://github.com/tock/tock/pull/3343 + - How to proceed? + - Somewhat stale at this point. + - Connects to https://github.com/tock/tock/pull/3975 + - Might have issues for non-64 bit use cases. + +- https://github.com/tock/tock/pull/3067 + - Needs an owner moving it forward. + - Several screen-related PRs and issues all outstanding. + - Close for now without prejudice (bradjc) + +- Perhaps we need more structure on how to propose designs + - Alyssa: I can provide a template + - Corporate environment more of a group decision making process to prioritize + development (or drop support) + - Amit: is the clarity from the process going to outweigh the higher burden to + contribute? + +- https://github.com/tock/tock/pull/3258 + - Never reached agreement on how to implement notifications about how capsules + can tell if buffers are swapped. + - Issue: callback misused. + - Might be worth it if the functionality is important + - Goal of removing callbacks was to make PRs easier to review + - How urgent? + - Have a workaround, so a better approach would be usable. + +- https://github.com/tock/tock/pull/3256 + - Waiting on Leon + +- https://github.com/tock/tock/pull/3258 + - Likely harder to misuse + +- https://github.com/tock/tock/pull/3268 + - Assigned to Pat + +- https://github.com/tock/tock/pull/3549 + - Waiting on Leon + +- https://github.com/tock/tock/pull/3696 + - Brad to look at + +- https://github.com/tock/tock/pull/3867 + - Needs review - tockbot assignment + +- https://github.com/tock/tock/pull/3964 + - Waiting on Pat + +## libtock-c Versions + +- Users of libtock-c and stabilizing the library +- Affects on downstream users + - Could try to create a policy/versions + - Could say "not a priority" +- Can do a libtock-c release + - Also fine to have new interface unreleased for now +- Reasonable to separate libtock-c releases from kernel releases +- Brad: I think it is hard to stabilize the libtock-c interface without + corresponding stability in the kernel ABI for a specific driver. +- Need to do release testing. +- Idea + - Release testing + - Mark in libtock-c that stability matches the kernel + +## libtock-rs + +- JVW likely spread thin (confirmed) +- Many users outside of core team +- What ask can we make of users to help contribute upstream? +- https://github.com/tock/libtock-rs/pull/540 is one approach? + - Might help, probably not for everything +- Are there out-of-tree drivers for libtock-rs that could be upstreamed? + - Brad: It's hard to tell if users of libtock-rs are using a forked/divergent + version, using just the existing upstream syscall drivers, or have drivers + which would be upstreamed. + - There are many good reasons not to upstream. + - Asking for upstream drivers could be a good way to encourage contributions diff --git a/doc/wg/core/notes/core-notes-2024-05-24.md b/doc/wg/core/notes/core-notes-2024-05-24.md new file mode 100644 index 0000000000..0c65211e20 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-05-24.md @@ -0,0 +1,101 @@ +# Tock Meeting Notes 2024-05-24 + +## Attendees +- Branden Ghena +- Hudson Ayers +- Amit Levy +- Leon Schuermann +- Pat Pannuto +- Tyler Potyondy +- Alexandru Radovici +- Johnathan Van Why +- Brad Campbell +- Alyssa Haroldsen + + +## Updates +### Tock Peripherals Update + * Johnathan: Tock register draft PR is out. https://github.com/tock/tock/pull/4001 Implementation only partially complete. Adds new `peripheral!` macro, which solves two issues: 1) unsoundness of having references to MMIO memory and 2) the existing registers implementation has no support for unit testing. It also allows for unsafe registers and allows for registers to be config'd in or out of existence. + * Johnathan: Design documents are up for now. So that's what should be reviewed. Some proc macro parsing is complete, but would change if the design changes. + * Johnathan: Goal is for most people to agree on design. Then I'll get to porting a driver over as an example. Then last a few people, Leon and Alyssa, can review some of the macro implementation. + * Hudson: So this would be our first thing in Tock to use procedural macros? (yes) Are we pulling in external dependencies for that? + * Amit: I believe these are build-time dependencies, not run-time dependencies. + * Johnathan: Correct. + * Amit: So this is morally similar to pulling in the CC package to compile C binaries, or something like that. My perspective is that this does NOT constitute having external dependencies in Tock, but rather in the Tock build infrastructure. + * Pat: We once decided against proc macros, like back in 2017. + * Amit: I think they were very unstable in 2017, and aren't now + * Johnathan: This definitely needs proc macros to be manageable. + * Pat: Agreed. I just meant we should look to see what the old rationale was. + * Amit: I do think it was just stability stuff at the time + * Pat: Also, I don't want a full porting guide, but I do want an example of how much code really changes in practice. Seems to mostly be additional parentheses. Maybe a little paragraph explaining that would be nice + * Amit: I think the comments in peripheral.rs show some examples of what this would look like. + * Johnathan: Yeah, the README is supposed to be the high-level what it can do. Then the comments have all the features and syntax. + * Amit: From the embedded working group discussions at Rust-NL, this design would address many if not all of the shared pain points that many people have about embedded Rust and MMIO. Those folks are primarily interested in automatically generated stuff from svd2rust. But both at the implementation level of avoiding unsoundness and syntactically of having a clear mapping of registers, I think this is in broad strokes a VERY good improvement. And hopefully not just for Tock. + * Alyssa: Agreed. This is a massive improvement. + * Hudson: Do you think the embedded Rust people would consider pulling it in as a crate? + * Amit: Maybe. They definitely want auto-generated bindings from SVD files. + * Johnathan: I would love the input to be SVD. But I don't really have time to understand that format. So an svd2rust that outputs this syntax would be useful. There could also be multiple syntaxes in proc macros, so we could add stuff if it would enable them + * Leon: One takeaway from Rust-NL, they're dealing with huge register files, and they didn't want compile-time expansion because it took so long. Not sure if they actually measured this. + * Amit: Interesting. I wonder if it's because they're pulling everything from the SVD no matter what, or if their chips are different. + +### CPSWeek Tutorial + * Tyler: Tutorial update. The people there really enjoyed the tutorial and the format. We had the screen working and touched a lot of parts of what Tock offers for apps and networking. Major plus is that we have the tutorial now. We did face some issues with the VM image freezing, and we need to figure out how to set people up with the build system. It worked fine for one person, but kept freezing and was unusable for others. We worked around it, but it wasted time, and was really only okay because of small attendance. + * Tyler: We also had a bug in OpenThread with libtock-sync that we didn't catch until we were using the network for quite some time. I'll be patching those soon, sending was hanging. + * Tyler: We also had small attendance: only four people. So thinking for what venues to approach and how to advertise would be helpful for future tutorials. + + +## TockWorld Registration + * https://world.tockos.org/tockworld7/agenda/ + * Amit: We're ready for people to register. We have an agenda for all days, although some possible changes still. The agenda is pretty exciting actually. + * Amit: So, we should encourage people to come. So it's time to reach out to people and propose that they come. For undergraduate students, we'll be able to do some travel grants. + * Amit: Keynote is Florian Gilcher from Ferrous Systems talking about Ferrocene. But also Bobby Reynolds talking about x86 Tock port for Pluton. And Irena Nita talking about WebAssembly TockOS. And Lawrence Esswood from Google about porting Tock to a CHERI-based system. And Amalia Simion on multiplexing serial messages. + * Alex: I'm hoping that some of that stuff gets open-sourced. Very excited about it. There's a lot of duplication of effort going on. We'd also benefit for the x86 stuff for education. + * Tyler: Amit mentioned student travel grants. We also have a growing group at UCSD who are working on Tock. How should we direct them to registration? + * Amit: Pat and I will figure out how best to handle it and advertise broadly. + * Alex: Do we need to register for a ticket as well? + * Pat: Yes, if you're coming buy a ticket + + +## New Kernel Release + * Amit: We should do a new kernel release. Mostly because it's been a while and it's good to keep it as some pace. There are changes and fixes to important systems. So one question there is do folks agree? + * Amit: And then are there particular features that are around the corner which we would want to include in a release? For example, IF the peripheral stuff was close to being done we'd possibly wait for it. But for something several months away, we should just move ahead now. + * Amit: We should also use this as an early testing ground for some automated hardware testing on what we have set up + * Hudson: Is there anything that people feel needs to get in? + * Branden: Maybe some of the timer stuff? I didn't follow what the state of that is + * Leon: That's close + * Amit: Okay, those are clearly in then. + * Pat: What about 15.4 interface stuff and known bugs? + * Amit: I do think getting 15.4 stuff in before a release would be nice. But I don't know if it should be blocking. It's not central for donwstream users. + * Hudson: I think the stuff with known bugs is likely in libtock-c instead of the kernel + * Tyler: That's correct + * Tyler: I don't know if we want to block on it, but Brad is working on splitting up the 15.4 stack. + * Brad: No, let's not wait on it. That's going to have lots of little future changes, so we don't need to force it right now. + * Hudson: Last week we also discussed not tying together libtock-c and kernel releases. So we could tag a release and start testing soon. I'll create a tracking issue + * Brad: already made it https://github.com/tock/tock/issues/3197 + * Hudson: I'll add some stuff to that and update it. + + +## Reframing Core Call and Meetings + * Amit: Background here is that the core calls over the years have had multiple purposes. One is to catch up on PRs and core WG business like Tockworld. The other has been outreach like having people jump in with questions. It's been invite-only, but pretty open to pulling people in. In practice, it's really just been the core folks anyways. + * Amit: So, we should be more explicit on the role of these meetings so they can be most useful for that. Probably making decisions on PRs that can't happen async over the PR. + * Amit: Other background is that as I've made more effort to talk to Tock users out there, I'm hearing a lot of similar things: complaints/feedback, questions they didn't know who to ask, and lots of good-to-know stuff like custom features or use cases that aren't going to upstream but could direct our efforts. So, instead of me doing one-on-one meetings, it would be good to have a forum to give those voices some space. I do NOT think the core calls would be right for that. Some of it happens on Slack, but that's usually more academic people. And Slack isn't good for long-term things. + * Amit: So some suggestions. One would be to keep the core calls an actually make them more directed towards voting/deciding on stuff. Those can be more boring and efficient, and could maybe even move based on schedules and timing. + * Amit: In addition, we might consider a community call or Tock office hours, or something. That only a few people would regularly be on, but people know they can join. The ESP Rust people do something like this. So it would be oriented towards communicating from the core working group to others, such as about big features like Tock peripheral changes, and could be a Q&A feedback thing from users. + * Amit: Another suggestion would be other communication tools? We don't use the mailing list much, but we could use it more and advertise it more. Some people didn't know they existed, so we should advertise better. Maybe other tools like Discorse or Zulip or something to avoid email but still avoid live chat. + * Hudson: What do you think that other medium should be? + * Amit: I don't know. I hate all these tools, but hate some less than others. I found that when Rust moved to Zulip that it was a bit tough to deal with, but it does seem effective. Similarly, Discorse is basically a glorified mailing list. + * Brad: I think we can just do our best for whatever people use. I'm more concerned about the effort to lead all this stuff. Without staff support, we could do something really well for a month or two, but it'll fall off whenever someone gets busy. Everything you're saying sounds great, but how do you make it work without staff support? + * Amit: I agree with that. We are working on it, but we're not sure who that staff person would be. Could be one of us, but that's not a long-term solution. For how we can pay them, we do have funds now, but it's a tad tricky to pay people who should clearly be remote. Presumably this would be one of the perceived benefits to financially support the Tock foundation, but that's a chicken-and-egg problem because it doesn't exist yet. + * Hudson: Okay, so we could adopt a new tool and maybe that would help somewhat. But it's also not clear to me to what extent that would do anything. Zulip works well for Rust because it's ONLY Zulip and Github. + * Amit: There is a Discord actually, and migration to Zulip has happened slowly and organically. + * Amit: Actually, I'm not really suggesting switching tools unless someone has a magic bullet. I think more important is to focus the Tock core calls, and set up some Tock office hours time. I'd be happy to lead the office hours for the next few months. + * Hudson: I am pro this community call idea. + * Branden: Needs some specific guidelines and suggestions for what topics would be handled in the community call. And advertising to people so they're aware it exists + * Brad: Would risc-v stuff make sense here? That call never quite hit critical mass. + * Amit: Maybe? Discussions yes, but decisions/promises no. + * Hudson: Maybe still small decisions, just not project-wide stuff. + * Amit: The goal being that not attending the community call doesn't hurt your ability to influence Tock for things you care about. And the Core call would still be relatively open, but would be transparent about being boring. + * Alyssa: Could do a Slack post about it, and emote react to say you are thinking about coming (and cancel if no one does). + * Hudson: Especially useful for students playing around with Tock who just want to ask a couple questions + * Amit: We'll keep chatting and plan to start doing something like this on the sooner side + diff --git a/doc/wg/core/notes/core-notes-2024-05-31.md b/doc/wg/core/notes/core-notes-2024-05-31.md new file mode 100644 index 0000000000..4f3ab89c11 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-05-31.md @@ -0,0 +1,390 @@ +# Tock Core Notes 2024-05-31 + +Attending: +- Hudson Ayers +- Branden Ghena +- Alyssa Haroldsen +- Philip Levis +- Amit Levy +- Tyler Potyondy +- Alexandru Radovici +- Leon Schuermann +- Johnathan Van Why + +## Updates + +### UART TRD Rebase + +- Leon: Working on rebasing the port to the UART TRD & HIL. Can't really use + git-rebase, because it's been pending for too long and the codebase has + changed significantly. + + Not sure whether this is breaking any UART implementations, as sometimes the + changes are substantial. Can catch that during release testing? + +- Hudson: Might be a while to the next release, if we do one soon? + +- Leon: Could try to squeeze it in. + +- Hudson: Otherwise, might test a few of the platforms ourselves. Otherwise, + users can always report issues to us. + +## `tock-register-interface`: Keep `RegisterLongName` and Register Type Separate? + +- Hudson: Johnathan posed the question of whether we want to keep + `RegisterLongName` and the register data type separate. + + Let's read https://github.com/tock/tock/pull/4001#discussion_r1619542913. + +- Johnathan: Analogous to the current syntax. We could have the register types + take just one argument, such as either the `RegisterLongName` or the integer + type. + + If Amit's argument is correct (that we had both to make it explicit how long a + register is, as we did not have explicit offsets), then this should no longer + be necessary with this proposal. + +- Branden: This seems better, now that we have explicit offsets. But we can + still use just regular integers without `RegisterLongName`? + +- Johnathan: Yes. + +- Phil: Amit's analysis as to why we had both previously is correct. + + Do we still want to support non-uniform sizes? Trying to think of an MMIO + register that is larger than the word width, but we need to be able to access + it atomically. + +- Branden: So you want a bitfield, and have it be of different size than the + other bitfields? + +- Johnathan: Not supported in tock-registers right now. + +- Branden: Could have two bitfield declarations. + +- Hudson: New design looks good. + +- Alyssa: + + In chat: > +1, also the size of the field itself can be looked up at the > + `register_bitfields!` declaration location + > + > `#[repr(transparent)] struct AlignedTo([U; 0] T>;` + > + > could also do this by wrapping the original register bitfield? + + Concerning alignment. + +- Johnathan: In the new design, there's a disconnect between the register type + and the register size. The actual size is communicated through a new trait. + +- Alyssa: Makes it more conceptually confusing. Having a thing of length, e.g., + 4, but the actual type be zero-sized. + +- Leon: Right now, it seems like we just rely on the alignment working out. Are + you validating that the alignment is corresponding to the types' size. + +- Johnathan: No, but should be easy to add. + +- Leon: Should probably do that by default. If we ever don't want that, we can + add an additional parameter to override. + +- Jonathan: On LiteX, does the alignment of a field always correspond to its + size? + +- Leon: The platform ensures that any given register is always exposed to + natural alignment, or a more coarse-grained alignment. For us, we should be + able to, e.g., rely on the fact that a u32 will be always be placed at an + offset aligned to 4 bytes or greater. + +- Johnathan: Okay, so a u32 on a LiteX system where it takes up 16 bytes of + address space -- if core::mem::align says its alignment is 4, then the + register's alignment constraint is still 4, even though the register takes up + 16 bytes of address space? + +- Leon: Correct. + +## 64 bit Timer Proposal + +- Leon: Follow-up to the alarm capsule rework, given the imminent merge of + [#3975](https://github.com/tock/tock/pull/3975). + + What's our take on Alistair's proposal to extend the userspace timer interface + to 64 bit: + - [timer: Add support for 64-bit + timers](https://github.com/tock/tock/pull/3343) + - [Expose 64-bit timers to the userspace alarm + capsule](https://github.com/tock/tock/pull/3355) + + This is related to the problems we had in porting OpenThread to + `libtock-c`. Summary: platforms with a hardware-timer of 24-bit had their + Ticks value directly passed through to userspace. Thus, the timer exposed to + userspace did not wrap at `2^32`, but `2^24` ticks. This meant that it was + impossible for userspace to predict when the timer was going to wrap, and thus + it could not build a "virtual" timer that reliably worked for longer than the + relatively frequent `2^24` ticks rolled over. + + Many different proposals discussed, such as implementing wrapping in the + kernel. With the new fixes, we're left-justifying Ticks values exposed to + userspace, so they always wrap at `2^32`. Turns out that this is exactly what + userspace always expected, documented in a comment from Amit in 2017 in the + userspace alarm implementation. + + The PR that we're about to merge makes sure that the kernel delivers on those + assumptions, but does not break the interface. + + A couple years back, Alistair proposed to pass 64-bit alarms to userspace. Our + current PR solves at least one of those issues -- userspace can now build + timers that do last for longer than the exposed `2^32` ticks. + + Question now: what's happening with Alistair's PR? + +- Amit: With your PR merged, we would support arbitrary bit-width timer + infrastructure in the kernel, including 64-bit, right? + +- Leon: Yes. + +- Amit: Only question is whether the system call driver should be 32 or 64 + bit. In my opinion, it seems perfectly fine for some niche use-cases to have + their own system call driver that have their own API, but the common case is + 32 bit. This seems sufficient for most use-cases, and so it should be the + canonical upstream one. There may be other that lived upstream, for those + other usecases. + + We would want to have a compelling reason to either complicate the existing + driver to everyone, or switch to 64-bit which would make things more expensive + (for some users, uneccessarily so). + +- Phil: Agree with that. One perspective is that on RISC-V we do have those + 64-bit timers, so we should be able to use them. However, reasoning about the + interactions with 32-bit is complex. It's understandable that Alistair wants + this to be supported for his platforms. + + Given the complexities around timers smaller than 24-bit, where we + left-justify to work around these issues, would that mean that for all + platforms we now left-justify to 64-bit? That doesn't make much sense. + + Agree that 32-bit should remain standard, and 64-bit can be its own driver. + +- Leon: Also answers my question in which direction to guide this PR; seems like + we want to suggest pushing for just another driver. + +- Amit: Do all RISC-V platforms have 64 bit? + +- Phil: Yes, mtimer, the standard RISC-V timer is 64 bit. + +- Leon: Not all platforms have mtimer though... + +- Phil: For new PR, looks like you wrote a lot of tests. With the old version, + one of the challenges was to cover all of the crazy edge cases. + +- Leon: Wanted to bring this up -- would like to see more eyes on the alarm + driver rewrite. It's hard, and I don't claim that I got it right. However, the + new design learned a lot from the old one; none of the actual wrapping logic + is different. It just splits it into individual functions that are testable, + and removed some other quirks around event handling and state machine + progression. + +- Phil: Run a test where you have multiple kernel timers and multiple userspace + timers, and let it run for a few days. + +## PRs #3252 and #3258 (Add syscall notification mechanism / attributes to shared buffers) + +- Leon: System call notification mechanism proposed by Alex two years + ago. Hudson commented on it, and it got my attention because of a slightly + related issue in the alarm rewrite. + + For the alarm driver, we have this optimization where we count the number of + alarms that are set. However, this does not handle the case of process faults + and restarts correctly. For now, removed that optimization. + + This is tangential to Alex's initial motiviations for this PR. + +- Alex: Not for notifying about faulting apps, but for knowing whether a buffer + was swapped, because the capsule should know whether it should reset its write + offset in a buffer. + +- Leon: Reason I brought this up is that presumably the implementation of any + given notification mechanism is going to be quite similar. Didn't mean to + divert this discussion from the original use-case. + + Was looking for a fundamental discussion for as to whether we want to have a + notification mechanism for certain kernel events at all. + +- Alex: I would support for a capsule to at least have a way of determining + whether such an event occurred between any two commands (like buffer swapped / + application crashed, etc.). + +- Hudson: Push back against adding application crashes into the scope of this + PR. Might open pandoras box, where capsules are much harder to reason about + and test if they can run arbitrary code in response to these events. + +- Alex: Could figure out whether an app crashed depending whether the grant is + empty or not. + +- Hudson: But not if it restarted. + +- Alex: Can set a magic value in the grant logic. + +- Leon: Other case not covered -- peripherals that are only enabled when an app + is using their respective system call drivers. When an app faults, the + peripheral will never be turned off. + + This is tangential the main question -- want to see whether there is + interested in having any notification mechanism, for any type of event. Should + avoid further stagnating this PR. + +- Hudson: The ability to potentially extend this PR to cover these other + use-cases would seem like it's better than #3258, which would not support + this. + + Alex, have you used either of these implementations in a while? + +- Alex: No, but can port it. + +- Leon: Before we spend time on this, should clarify whether we do not want + #3258 instead. This one instead adds an attribute to buffers to indicate that + they have been swapped. + +- Alex: In favor of #3258 more than #3252. Problem is: right now, a capsule + needs to rely on an application for it to be correct. + + There was pushback against having a capsule be able to react to + allow/subscribe events, and #3258 is the only way to prevent that and be able + to tell whether a buffer was swapped. + +- Leon: I do agree with the fact that, in the general case, capsules should not + be able to react to allow/subscribe events. However, it does seem like the + change to the alarm driver tells us that sometimes this is genuinely + useful. It's not a system call but a process state change, but the pricinple + is the same. + + Right now, we have incorrect capsule code that is hard to get right without + notification mechanisms, whereas we did not want to introduce those mechanisms + also to avoid capsules becoming too complex. + +- Alex: We should decide whether capsules should support notifications, with the + caveat that with notifications the capsules might do arbitrary things. + +- Amit: Share the intuition that the attributes approach is lighter-weight and + less intrusive to drivers that do not care about this aspect. + +- Leon: It's simple, and light-weight, but seems like it's a modification to + some core infrastructure to achieve its goals. Not sure I like that it's not + very general. + + The implementation is also slightly hacky, by using a bit in the buffer's + length field as a niche, etc. + +- Amit: Not tied to this implementation. + +- Leon: Even then, I'm feeling uneasy about this as I cannot directly see how + this feature would generalize to user use-cases? + +- Amit: Examples? + +- Alex: Any network, any streaming capsule will have these types of issues. + +- Leon: Given that this is so specialized, would it make sense to add a + dedicated allow system call, similar to the userspace-readable allow? + +- Amit: Sounds like it would add a fairly significant overhead. + +- Leon: With userspace-readable allow, the implementation is shared with regular + R/W allow, just different semantics in the design document -- not enforced. + + Here we could do something similar, but the buffer would be wrapped in a + special ring-buffer interface that allows you to place a packet into, or pull + one out of the buffer. + +- Amit: Does it alleviate your concerns if we mark this interface as unstable? + +- Leon: Sure. + +- Phil: Whether we generalize this to other attributes, I'm a little skittish on + as well, but this does seem like a basic piece of information that a capsule + should have access to. + + Leon, understand your point about ring buffers -- we were using them for + high-speed ADC at some point -- but it's pretty hard to get those right, + especially for a very simple problem: I want to know whether something was + changed. + +- Hudson: We keep mentioning that this is necessary for capsules to be correct + -- not convinced. These seem like optimizations. PR contains example for touch + driver outlining interactions between allow and command. None of these seem + like they'd actually be different from app misbehavior, such as re-allowing a + buffer that has not been fully consumed. + +- Alex: Problem: you have an offset in a buffer. The capsule writes to that + offset. It needs to know when it can reset this offset (start writing at zero + again), which is when the app provides it a new buffer to write into. + +- Phil: Streaming stuff into buffer from UART -- one approach is to just swap + out the buffer as soon as there's contents in there. But then the capsule + would resume writing in the middle of the new buffer. You can swap the buffer + and tell the capsule, but there's a race condition there. + + It's really about having an index pointer into the buffer. + +- Alex: Solution we have right now is to maintain offset in the buffer. If the + app fails to zero it, the capsule is incorrect. + +- Leon: I don't understand this very last part, why would "the capsule [be] + incorrect"? Except for the guarantee by the kernel that it won't lie about + whether a buffer has been swapped, you could represent this by a one-bit + "ready" handshake in the buffer. + + Capsule should _always_ check that their buffer indicies are in bounds, and if + userspace supplies an invalid offset, it would simply cause the capsule write + at an incorrect index. + +- Phil: Correctness in the sense of "you want the capsule to write data at some + offset, and know whethere that offset is". + + Let's say we're doing high-speed ADC, sampiling 10KHz, I'd like to pass a new + buffer for the capsule to continue streaming into. How do I do this such that + the capsule knows it should write at index zero, not index, e.g., 43. + +- Leon: For example by reserving the first byte, and having the app ensure that + every new buffer it shares zeroes this byte. The capsule then writes a + non-zero value to that before it writes any data. When it contained zero + previously, reset your offset. + +- Tyler: This is essentially what I implemented for 15.4. + +- Alex: This is what we have in the CAN driver, but I'd like to avoid relying on + the app. + +- Phil: In the sense of "if the app doesn't zero it, I don't want to check". + +- Hudson: If the app doesn't zero it, all that happens is that the capsule + writes to the middle of the buffer. This doesn't affect other parts of the + system. + +- Phil: What if it's shorter. + +- Leon: We'd have to do a length check every time we write (which we'd likely do + anyways). + +- Phil: Now we're passing more structured data in the buffer (length/value). Not + convinced, but this does seem like a viable alternative. + + I agree that the added complexity from a notification mechanism is a tougher + sell. + +- Leon: We might want to just have a thin wrapper around existing allow buffers + that expose these semantics. + +- Phil: Makes sense. If the abstraction works well for our three use-cases (ADC, + 15.4 and CAN), then it's probably pretty good. + + Seems like a good thing to add to guidelines for writing capsules. + +- Amit: What does this mean for #3258? + +- Leon: Seems like we can relatively easily get all of the benefits of this + approach without modifying any of the core kernel infrastructure. So I'd vouch + for exploring that path. + +- Amit: We should set a deadline on this. Let's revisit around TockWorld. diff --git a/doc/wg/core/notes/core-notes-2024-06-07.md b/doc/wg/core/notes/core-notes-2024-06-07.md new file mode 100644 index 0000000000..6a6fd912fd --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-06-07.md @@ -0,0 +1,99 @@ +# Tock Meeting Notes 2024-06-07 + +## Attendees +- Alex Radovici +- Brad Campbell +- Amit Levy +- Pat Pannuto +- Leon Schuermann +- Johnathan Van Why + +## Updates +- None + +## Buffer Swapping PR https://github.com/tock/tock/pull/4023 +- Alex: Problem with buffer used for streaming/networking. Application uses two buffers that are swapped and driver does not know when the buffer is swapped. This is important for indexing. If we use a command to signal when this swap has occurred this becomes a race condition. +- Alex: We have two PRs for this. Neither of these PRs solve the generic problem. +- Alex: I've tried to create a more generalized solution for this. +- Alex: To answer Brad's question from the PR, I called this a ring buffer because Leon/Amit called this a ring buffer on the last call (when it is really just a buffer). +- Leon: One design is to just swap buffers, the other is a ring buffer that wraps. The ring buffer seems like a more general design. +- Alex: How do you imagine a ring buffer that is not contiguous in memory? How would the application consume the buffer? +- Alex: Would there be mutability/immutability unsoundness with this? +- Leon: There is not unsoundness since we are switching between user/kernel space and only one is running at any given time. +- Leon: With the ring buffer design, we would never switch buffers. +- Leon: I think your proposed design is more useful. +- Amit: What role does the checksum provide? +- Alex: To avoid accidental/invalid values. This is just an XOR between the first three bytes. +- Amit Which value would be invalid? +- Alex: This is to check that the application placed a zero on the first four bytes. +- Amit: What specifically though does the checksum provide? +- Alex: This avoids not being able to use a buffer that is shared without being properly initialized. If the checksum fails and the buffer was previously used for something else, the capsule would reset the buffer. +- Leon: I am skittish of having this check since this is not something we check against in other implementations. +- Leon: The kernel assumes the app only allows memory that is not being used by other apps. I do not think it is the kernel's job to manage this. +- Amit: We are not trusting the application, we are just allowing it to shoot itself in the foot. +- Amit: Hypothetically if we do not have the checksum, what is the worst thing that can happen? +- Alex: The capsule would not be able to write to the buffer for incorrectly formatted buffers. +- Alex: With the checksum, the kernel can detect misformed buffers (not set to zero) and can reset this from the kernel. +- Alex: I do not feel strongly about including the checksum. +- Amit: What questions remain with this? +- Brad: My question is do we want a ring buffer? 15.4's ring buffer that Tyler implemented overwrites old packets if it fills. +- Leon: Alex's proposed design is simple. It is not clear to me if we should have a ring buffer overwrite data or simply block until data is read from it. +- Brad: The benefit of this design is the simplicity. The challenge for this with a ring buffer is the size is not necessarily known, whereas in 15.4 the packet size is known. +- Brad: It would be hard to implement a ring buffer that wraps "mid packet". +- Leon: Assume we get a packet from 15.4 and want to send this over to ethernet. Alex's proposed design would be easier to pass this to other subsystems. A ring buffer would be more challenging to slice and take ownership. +- Leon: Specifically, I am making an argument for this design being better than nothing. +- Amit: I do not think there is a debate here. +- Alex: I will rename the PR to not be named ring buffer. +- Leon: To be clear, a ring buffer would be useful elsewhere, but for Alex's ADC streaming case, a ring buffer is overcomplicated. +- Amit: The answer seems to be we do not need a ring buffer for this and should rename this PR to something more specific. +- Amit: Would this work for 15.4? +- Brad: This seems like it would work for 15.4, +- Tyler: I agree that this seems like it will work for 15.4. In 15.4 we currently use two buffers that are swapped by application. Each buffer is a ring buffer that overwrites the oldest data. This buffer is of size n * 15.4 packet size. +- Tyler: It would not be much of a change to switch to Alex's proposed design. +- Brad: ADC driver has multiple allow buffers and iterates through them. There is only one allow buffer for this PR. +- Alex: This PR does the same thing but reduces the number of system calls. +- Leon: How would this be used for a network layer? +- Alex: The idea would be to have multiple packets since the app is slower than the kernel. +- Alex: From the point we notify the app, new packets are received and we are losing buffers. +- Leon: So you are swapping buffers still. +- Alex: My opinion is if we need to read app/kernel +- Brad: I still think this is a good solution. +- Brad: Summarizing, the kernel gets data from source, if the data comes slowly, most drivers are fine. The challenge is if data is received quickly. The kernel always needs a buffer that it can place data into. To do this, we need multiple buffers that are always available. This approach uses one allow buffer that we can place multiple things into. The upcall will notify the app there is data. The app allows new buffer (which is essentially atomic operation). +- Amit: Without this, we have the ability to swap buffers atomically. What is missing from that? +- Brad: The application may not do the swap quickly enough. +- Amit: This is just an accounting mechanism to say that a buffer will contain one or more things. +- Leon: One other side to this is the accounting parameter can tell the kernel in a unified manner where it should start writing again. +- Alex: One thing is that this design loses new packets. Tyler mentioned 15.4 loses old packets. +- Leon: Having non fixed size packets is important too. +- Amit: Won't the packets do this themselves? +- Amit: We could also get around this with prepending this in the driver semantics. +- Alex: Should I implement the ring buffer functionality as well? With fixed or variable packet size? +- Amit/Brad/Leon: I vote no. +- Leon: It seems the main benefit of this is the streaming aspect of this. Presumably the name should reflect this. +- Amit: It seems this is a network queue. +- Tyler: I will look this over in more detail after the call to confirm this will work with 15.4, but it does not seem like it will have issues. + +## Yield Wait + +- Brad: The todos are to review what is implemented, see if it needs to be updated, and perform more testing. +- Alex: I can work on this and take ownership of this. +- Amit: This is very high impact and important. +- Leon: The need for this came up when preparing for the tutorial and created a lot of issues. Having yield wait will allow for more easily creating reliable apps. +- Alex: I have had issues with this as well. + +## Storage Permission TRD https://github.com/tock/tock/pull/4021 + +- Brad: The existing implementation stems from TBF header storage permissions. This is very similar, but has some minor changes. This goes with AppID to identify who is the owner of anything that is stored. The storage permissions header did not use AppID since AppID did not exist at the time. +- Amit: Is the intention for this TRD to dictate the default policy if otherwise unspecified? Is the implication of this TRD that there cannot be a storage driver with a different policy? +- Brad: How the policy is set is a separate discussion. Different implementations of different drivers can have different policies. +- Brad: The intent is that if there is nothing that disqualifies an app from accessing it's own state, it should be able to do this. This design makes it more clear that this needs to be specifically excluded. +- Amit: Rephrasing the question, if some other stakeholder does not agree with the specified policy here, does this TRD rigidly enforce this? Would they need to fork? +- Brad: If you implement something that stores state, you need to have an API that gets permissions for applications. You can't implement your own permission system (have to use the kernel permission). You also have to use AppID to identify state attached to an application. Using a different permission system would require forking. +- Johnathan: This was stabilized four years ago with the threat model being stabilized. This TRD seems to just make the storage TRD compliant with the threat model we've already stabilized. +- Brad: This is not meant to codify how you implement this trait, it allows for others to implement the trait as desired. +- Leon: One question, does this relate in any way to richer abstractions (file systems) we wish to implement in the future. How would or does this relate at all? +- Johnathan: The analogous unix abstraction for AppID here is user. +- Leon: I want to ensure that this TRD would not restrict us from implementing future implementations. +- Amit: You could imagine attaching a permission implementation to each storage system object. +- Leon: It may be beneficial to add a sentence stating and clarifying this for other uses and that this is just the canonical case. +- Alex: I am planning to implement FAT this summer. diff --git a/doc/wg/core/notes/core-notes-2024-06-14.md b/doc/wg/core/notes/core-notes-2024-06-14.md new file mode 100644 index 0000000000..0a741c7f25 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-06-14.md @@ -0,0 +1,151 @@ +# Tock Meeting Notes 2024-06-14 + +## Attendees + +- Hudson Ayers +- Phil Levis +- Amit Levy +- Brad Campbell +- Leon Schuermann +- Branden Ghena +- Alyssa Haroldsen +- Johnathan Van Why +- Pat Pannuto + + +## Updates + +- Amit: I have been looking back at IPC because some people are actually using it +- Amit: I am going to try to fix some issues with discovery that are dependent on + app ordering +- Amit: There are bigger problems with IPC, we are using it for open +thread where it has been problematic, this is a problem for user space services +in general. +- Amit: pretty big design tradeoff space here, depends on what we +might think are the actual meaningful use cases for IPC in Tock. Mostly a heads +up that I will soon start soliciting feedback on this. +- Branden: are you considering message passing? +- Amit: Anything is on the table, but the use case I +am referencing involves sharing 16kB buffers. It is possible there could be two +separate interfaces here (shared memory + message passing) +- Phil: Finished up +class on rusty systems. Asked students for their favorite paper, they said +Hudson’s code size paper. +- Amit: I have been looking through how we are doing in +code size, and we seem to have gotten some good gains from the last few Rust +updates. On the nrf52840dk about 15kB. +- Amit: I realized by accident that +changing some cargo options can save a few additional kB on the same board. +- Amit: We reposted a tweedegolf blog about identifying size bloat in Tock +binaries. +- Amit: The authors of that post are leading the charge in upstream Rust +to reduce the size of core. For example they added a flag to the core library +to push size optimization into the Rust compiler rather than LLVM. +- Pat: TockWorld is 2 weeks away! Less than half the people on the agenda have +registered, please do so. +- Alyssa: I will be attending TockWorld! +- Pat: I will extend the early-bird registration to Monday + +## Tock Storage Permissions (PR #4021) +** time spent reading PR + discussion ** +- Phil: I think Alistair’s FIDOv1 / FIDOv2 use case +can be solved in other ways that are not yet discussed on the PR. +- Amit: Without essentially applications being able to declare themselves what they can read +and write to, how would storage for FIDOv1 know to have the FIDOv2 app shortID +in the readable permissions set? +- Johnathan: You have to push a new version of +FIDOv1 that includes that permission. Otherwise there is a fundamental threat +model violation +- Amit: I agree +- Brad: This TRD is not about how permissions are assigned +- Brad: I think Alistair would say that Fidov1 can use data that FIDOv2 +wrote and it will just work and understand it +- Phil: So Fidov2 would change the keys under FIDOv1? +- Brad: yes +- Phil: that sounds like a terrible idea, FIdov1 +didn’t allow it +- Alyssa: The shortID could maybe be mixed with a secret? +- Brad: I have a branch with the implementation of this which informed the TRD. That +hasn’t really changed the policy but has changed how storage permissions are +implemented in the kernel. +- Amit: I think we should vote to either say that we do +not need this TRD, or we approve it as is, or that we say there are specific +things that need to be changed before merging, or that we really do not know +and what specifically would need more discussion. +- Amit: Does anyone have comments before we vote? +- Johnathan: An AppId is how an app gets access to its +secrets. We do not want to assume some trusted registry or secrecy of app +binaries. I don’t think there is a good way to allow access to another app +without an explicit permission without one of these things. To add access after +the fact you should update the original app. +- Johnathan: One thing that remains +unclear to me are the uniqueness guarantees of ShortIDs. My intent was that +ShortIDs must be unique across running processes and consistent across all +running instances of an app on Tock systems. +- Johnathan: 2 years ago we +effectively created a requirement to have nonvolatile storage accessible by any +implementation of AppId / ShortID that meets the requirements +- Amit: I mean, +alternatively a customer could have a registry +- Johnathan: I think if we have +the property that no two AppIds can map to the same ShortID, and shortIDs are +consistent, they are 1:1 bijective with AppIds. Then the policy that assigns +the ShortIDs determines security policy, but that could be a board level thing, +but consumers of ShortIDs do not need to care. +- Pat: What is the scope of these +ShortIDs? +- Johnathan: I think the scope should be specific to the “Tock system”, +which we have to define. +- Johnathan: For example, if you do not have nonvolatile +storage, every reboot defines a new Tock system. +- Brad: The goal of ShortID was +to not make requirements that individual orgs might not want, and instead be an +adaptable framework for use by those organizations. +- Pat: I think I am coming +around here. What might help is being more explicit that StorageIDs are +deriving from AppIDs and the policies around their uniqueness/ownership will +depend on the board configuration. Someone just reading the storageID document +may be missing some of this context about AppIDs than just a link to the other +doc. +- Brad: I see that. +- Brad: Most of these comments are about AppID +- Amit: and the legitimacy of using ShortIDs in this case. My claim is that the assumption +the kernel should be allowed to make about ShortIDs is that they are a 1:1 +mapping with AppIDs. That assumption may not be true in some cases and it is +not great if that is the case, in a not so different way than say hash +collisions. +- Johnathan: To legitimize the use of hashes, if you trust no-one is +trying to create hash collisions, that is good enough for security +- Amit: A not bogus way to generate shortIDs would be to take a cryptographic hash of AppIDs +and take the lower 32 bits +- Johnathan: That might make people think we are more +secure than we are. +- Brad: Do we want that policy to be the same one that we use +to create ShortIDs, or do we want another WriteID policy? +- Amit: To my mind, having another kind of ID does not fix this, and there is a significant benefit +to using a single ID instead of many, and I still don’t see a compelling reason +not to do so. This seems like the right design to me. +- Johnathan: StorageIDs have to either be based on AppIds or be as complex, and we can’t afford +duplicates of something so complex in embedded. +- Amit: let’s vote! +- *vote in chat* +- Approved by: Brad, Amit, Branden, Johnathan, Hudson +- Abstained: Alyssa, Leon +- Pat: approve with no changes to policy, but request for explicit +statements to the relation between ShortID and AppId. +- Pat: I will summarize this in a comment on the TRD PR + + +## Yield WaitFor + +- Brad: I updated the PR, renamed some terminology and types , tried to remove +the iterative thing that happened with naming. I also updated the TRD. I think +the implementation is good enough to evaluate this. +- Amit: Lets turn this into a +non-draft PR and properly review it +- Brad: OK, I will do that. +- Brad: The one meaningful change is there was confusion about which yield parameter becomes +the upcall ID, and I smoothed that out. Was mostly moving text around. +- Hudson: Do we want a release before or after Yield WaitFor? +- Brad: Ideally we would do a release right before and right after, but I don’t think we have the +motivation. diff --git a/doc/wg/core/notes/core-notes-2024-07-12.md b/doc/wg/core/notes/core-notes-2024-07-12.md new file mode 100644 index 0000000000..af1b29dde7 --- /dev/null +++ b/doc/wg/core/notes/core-notes-2024-07-12.md @@ -0,0 +1,112 @@ +# Tock Meeting Notes 2024-07-12 + +## Attendees + +- Branden Ghena +- Amit Levy +- Leon Schuermann +- Alexandru Radovici +- Alyssa Haroldsen +- Brad Campbell +- Andrew Imwalle +- Pat Pannuto +- Tyler Potyondy +- Johnathan Van Why +- Hudson Ayers + + +## Updates +* Alex: Two interns started working on tockloader-rs +* Branden: Some thinking about Integration Testing going on. Feel free to add comments here: https://docs.google.com/presentation/d/1XXDGhU1Qckvjh-0POrclnhw-SklpiOI2OtiswG0Nbrg/edit?usp=sharing +* Alyssa: For Chromebooks, in the System settings, some will say that they're running Ti50, and that means they're running Tock. +* Alyssa: chrome://system -> cr50_version = ti50 means TockOS +* Amit: I'm going through the breakouts from Tockworld, collecting notes and sending out messages with some action items. We're missing notes from the Code Size breakout, if anyone has them or just any action items, that would be great. +* Alex: I'll think about it and come back +* Amit: Brad already made some progress on the non-xip (execute in place) stuff with documentation +* Brad: Can you include Windows support in that effort? +* Amit: Yes. Who should be included in it? +* Brad: At the Thread tutorial, Bobby was the point person who was able to get Tock working on Windows and was helping others. + + +## Lingering PRs +* Amit: Looked through active PRs and pretty much all of them are either blocked or have recent comments. +* Branden: Should we be assigning people to _every_ PR? PRs that get comments don't have an assignee, which means that they might linger. +* Leon: I did contribute to Cargo recently, and they automatically assign someone who can reassign as needed. Was pretty nice. +* Brad: I thought we did this now? +* Amit: Two differences: 1) we only assign nightly and 2) we only assign if there are no comments. So should we automatically assign? +* Brad: I thought we already were, so that's good +* Amit: We also have a policy on how long last-call PRs should sit. Should that be automated in some way? +* Leon: We could auto-merge on last call after some amount of time? +* Branden: Can only core team members assign last-call label? +* Leon: I'll have to check on that +* Brad: I like merging last-call automatically +* Alyssa: I'd request that these be 24 business hours if possible +* Amit: Okay, so does everyone agree to move to assigning PRs immediately? (all agree or are quiet) + + +## Libtock-rs 802.15.4 Raw Support +* https://github.com/tock/libtock-rs/pull/551 +* Johnathan: I foresee a bad contributor experience here and want to prevent it. The PR here adds 802.15.4 raw support and is generally approved, but isn't passing Miri in CI because of undefined behavior. The contributor isn't clear how to track down the problem. +* Johnathan: I skimmed briefly and found some things that look unsound, but I don't have time to help fix this due to job and vacation. +* Johnathan: So the question is what to do. Are we willing to accept unsound PRs? Or do we have a way to help them fix this? The author seems possibly less experienced with handling unsound Rust issues like this +* Amit: All the unsound stuff does seem to be isolated to the 15.4 buffer management stuff. This contributor reasonably seems to have copied the C version, which unsurprisingly doesn't meet Rust's soundness requirements. +* Amit: This feels to me like something that the Networking WG might have some stake in? +* Tyler: I'm fairly unfamiliar with libtock-rs. Is this possible using entirely safe Rust? If it's refactoring the code, that's easier for us to help with. Is this possible to do in safe rust? +* Johnathan: It should be possible. However, it might require new APIs in libtock-rs to do so. So there should be a way, but it might require some unsafe libtock platform code +* Amit: In the interim, at least two things seem reasonable to me. It's not trivial to make big changes to libtock-rs. The new buffer proposals could help with this maybe? So 1) this seems fine to me if the code lives outside of libtock-rs for experiments with some documentation that it's unsound and 2) we could allow this unsoundness with a big warning sign and todo to warn people not to copy it or use it in practice. I do share the concern that it's not obvious to tell this person how to fix it, and saying "sorry go figure it out" is not a great contributor experience +* Brad: One thing is, I don't think changes to buffers is going to solve this. It's basically already doing the buffer stuff we proposed. We've put a lot of work into cycling through buffers and were concerned it would have issues in libtock-rs, and now it looks like that's true. We don't know though whether it's really really hard, or just hard because experienced people haven't looked into it. +* Amit: We will have to resolve this for libtock-rs at some point, and maybe that'll fix 15.4 when it happens as a plug-in solution? So maybe this could move forward independently of that. This does seem to inform our buffer management plans though +* Alyssa: My policy: module-private unsound concrete code is tolerable so long as it's well documented as such. But it should not be merged as-is with a public unsound API. That should be marked unsafe at least +* Amit: Does that seem reasonable? +* Johnathan: With the correct comments, that seems fine? +* Amit: Like "This is unsound and unsafe, so do not use" +* Johnathan: Ideally, someone should understand the issue from the comments. There are two issues here, actually Miri UB and having the manually call Drop. +* Amit: So the question is, who's going to do this? +* Brad: Could we dedicate next week's meeting to it? Having a bunch of people look into the issue? +* Amit: Johnathan, do you think that would help? +* Johnathan: Maybe +* Amit: What if the Network Working Group looks at it? +* Branden: I'm worried that it's a Rust issue not a Networking issue +* Alex: I could take a look though +* Tyler: If it passes the Miri test does that mean it's good to go? +* Johnathan: It could still have issues that the tests don't exercise. +* Amit: So the question isn't necessarily how to minimally change this example, but rather how to model the buffer management in Rust. Which as Brad is pointing out, is a general question +* Alex: Skimming the code, we did have a similar problem once with CAN, and we never figured out a safe solution. +* Branden: We'll add this to the agenda for Networking WG for Monday. At least we can look at it. +* Pat: Should we invite the PR author too? +* Branden: If we have bigger changes, yes. I would like us to understand what's up before spending someone else's time on it +* Hudson: If we switch to static lifetime buffers, would that help our problem? +* Johnathan: I think it would not fix the issue. It would make a buffer you can share, but never get back to read. +* Hudson: The problem is that if you share a read/write buffer with a static lifetime, then you never get it back because you shared it forever? (yes) +* Amit: Okay, so a good first step for the Network WG would be to articulate the problem and have a plan, which might be that we need someone else to help figure it out + + +## AppIdPolicy +* https://github.com/tock/tock/pull/4028 +* Brad: Motivation is subtle, but there's a lot of description. The issue that came up last week is that it would be nice to not just have a usize, but have a generic type. However, that propagates _everywhere_ in the kernel. So one option is to just leave it as usize, and you have that many bits. Another option is that we keep templating and say that it's okay that things spread. Or maybe a magic solution I don't know about, if there is one +* Leon: I'm not personally worried about usize not having enough bits. But it's kind of a bummer we can't just pass an enum or a struct, which is clearer than a magic usize. However, I can see that having a generic type literally everywhere in our code base is a problem. The usability does take a hit. +* Amit: So do you think we should stick with usize? +* Leon: Yes. Or communicate the information out-of-band. Out-of-band you'd have to have your verifier write to some separate array that's the same size as the number of processes you're verifying, which seems frustrating. +* Leon: I don't think usize is elegant, but I think it's better than nothing +* Brad: Imagine a scenario where you want to have signed apps and have two keys. One key allows privileged behavior, and the other key is for signing apps but not privilege. So what you need is a verifier that can distinguish between the two keys, and you need some way to communicate with the rest of the Tock kernel what privilege is allowed. We've previously thought about doing that with an AppID short ID. To do that though, you need the AppID assigner to know which key was used, and there's no good way to do that right now. The verifier just says yes or no. +* Brad: So, it seems like the verifier should keep some information around about which key was used. The verifier and assigner run separately, possibly at separate times, so the information needs to be stored somewhere. That's what this enables. +* Leon: And my reasoning for generics, was that it makes sense to have some generic type T which only the verifier can make, and you could opt-out if you don't care, by specifying a unit type. +* Leon: My concrete proposal: we could change it later to a generic size if we needed. So we can leave it as a usize for now. One thing we could do is wrap the usize in a struct, so it's more clear that it's meaningful. +* Brad: That's fine +* Amit: And that's the main outstanding thing from last time's call? (yes) +* Alyssa: One possibly useful thing. There's https://docs.rs/zerocopy/0.8.0-alpha.16/zerocopy/trait.TryFromBytes.html which can convert bytes into enums. Which is a safe, checked transmute. +* Leon: I can see the appeal, although I'm worried that it buys us a bit, but we're still stuck at some predetermined size underneath. So we might as well just stick with the usize + + +## Storage Permissions +* https://github.com/tock/tock/pull/4031 +* Brad: I wrote the TRD and wrote most of this along the way. We merged the TRD. The only big difference now, is that I've added capabilities so only privileged code can create the storage. This is really just an implementation of the TRD though. So instead of a single way to assign storage permissions to applications, any kernel can decide how to do it +* Amit: So does this just need eyeballs? Or is there a particular issue? +* Brad: Just eyes right now +* Leon: I'll take a look at it + + +## Core Team Call Timing +Amit: I'd like to do another round of scheduling for this call. To see if there's a time that works better. If there are no objections, I'll circulate a survey (no objections) + + diff --git a/doc/wg/documentation/README.md b/doc/wg/documentation/README.md new file mode 100644 index 0000000000..3174250e0c --- /dev/null +++ b/doc/wg/documentation/README.md @@ -0,0 +1,33 @@ +Tock Documentation Working Group (DOC) +====================================== + +- Working Group Charter +- Adopted 02/02/2024 + +## Goals + +The goals of the Tock Documentation Working Group (DOC) are to: + +- maintain and improve the [Tock Book][book] +- oversee and improve the [API docs][api] +- maintain and improve the informational content on the [TockOS website][tockos] +- review changes to TockOS documentation in the [doc folder][doc] + +## Members + +- Brad Campbell (Chair), University of Virginia +- Branden Ghena, Northwestern University + +## Code Purview + +The DOC working group maintains: + +- https://github.com/tock/book +- A subset of https://github.com/tock/tock/tree/master/doc + - Syscall documentation + - General Tock documentation + +[book]: https://book.tockos.org/ +[api]: https://docs.tockos.org/kernel/ +[tockos]: https://tockos.org/ +[doc]: https://github.com/tock/tock/tree/master/doc diff --git a/doc/wg/legacy/2018-11-20.md b/doc/wg/legacy/2018-11-20.md new file mode 100644 index 0000000000..b62aaa371b --- /dev/null +++ b/doc/wg/legacy/2018-11-20.md @@ -0,0 +1,30 @@ +Attending: Amit, Brad, Phil, Branden + * Updates: 10m + * Amit: external app library + * Phil: + * Tock updated on H1B to 1.2, Cortex-M3 MPU. USB transport + * New cut for the UART HIL has PR up + * Brad: getting things ready for 1.3 release. tracking down two userland bugs. Student interested in resurecting RISC-V port for Tock: E21 core running with Tock on FPGA, minus interrupt controller. + * Agreement on test coverage for release (see https://github.com/tock/tock/issues/1201): 10m + * Are any test cases missing from tracking issue? + * "lua-hello"? yes. different big app, maybe, for next time + * Tutorial tests? Starting in 1.4. + * Add tests for kernel for imix: + * virtual uart, aes, aes_ccm, rng: Phil will do + * Board testing assignment: 10m + * Hail: Brad, Branden + * imix: Hudson, Phil, Holly + * nrf52: Amit + * cc26x6: Amit + * Release by Decemeber 1, barring unexpected big bugs + * Milestones for 1.4 + * Unified tutorial: having 5-7 tutorial "units", each one page, can combine into courses, etc. + * Courses are just tutorials with hands-on help + * Redesigned UART, GPIO, and Alarm HILs + * Virtualize all system calls across processes + * SPI, ADC, AES, Ambient light, Button, Console, CRC, DAC, GPIO, GPIO Async, Humidity, I2C master/slave, i2C Master + * Process fault handling and management: + * Allow processes to fault without crashing kernel + * Restart processes correctly on fault + * Restart processes correctly while currently running + * Add flag to TBF for process to request particular behavior diff --git a/doc/wg/legacy/2018-12-04.md b/doc/wg/legacy/2018-12-04.md new file mode 100644 index 0000000000..49cdbc5ce2 --- /dev/null +++ b/doc/wg/legacy/2018-12-04.md @@ -0,0 +1,25 @@ +Attending: Amit, Brad, Phil, Holly, Hudson, Johnathan, Brande, Pat, Jean-Luc + + * Updates: 10m + * 1.3 release! + * Brad: Has been working on ESP wifi chip working, picking back up again. Slow but steady process. + * Phil: Turns out Steve Clark put together a board with the ESP + SAM4D + * Phil: + * Just synced Tock on Titan to 1.3. So Titan is up to speed with 1.3 + * UART HIL: Niklas gave a whole bunch of detailed comments on programming style, will look over carefully, probably tomorrow + * Amit: Daniel Deluci has a port of 802.15.4 to the nRF52840 + * Issues for 1.4 + * Current list: https://github.com/tock/tock/issues?q=is%3Aopen+is%3Aissue+label%3Arelease-blocker. + * Testing for 1.4 + * Re-giggering our tests so they are more consistent. E.g. make them more consistent so it's easier to figure out what should happen with each + * New tool! https://github.com/tock/tock-ci + * Can flash apps for you + * Then checks serial connections for particular string match + * Current goal is to help with manual testing process + * Johnathan: + * Google have been putting together their own test suite to run + * Don't have a public solution, but an internal solution that images hardware based on pub-sub of binary images + * Phil: trade-offs between pub-sub vs. compiling on test machine? + * Johnathan: + * basically the test machine is ultimately fairly hand-designed, rather than a travis-CI type general hardware + * Can a developer use this? E.g. I, as a developer, can run tests remotely without direct access to test hardware diff --git a/doc/wg/legacy/2018-12-11.md b/doc/wg/legacy/2018-12-11.md new file mode 100644 index 0000000000..a24426767e --- /dev/null +++ b/doc/wg/legacy/2018-12-11.md @@ -0,0 +1,28 @@ +Attending: Amit, Brad, Phil, Johnathan, Holly, Hudson, ? + + * Updates: 10m + * Brad: WiFi working on ESP32---connecting + HTTP requests, logic is mostly in userspace, but works none-the-less + * Phil: Huawei agreed to fund network security that Hudson + Phil working on + * Amit: board admin in the repo + * Pat: would be good to have a list of boards with support table for each + * Maybe in the wiki + * Would avoid having pointers to non-master boards lost forever + * Generalizing the Radio HIL + * Resources: + * HIL: https://github.com/tock/tock/blob/master/kernel/src/hil/radio. + * PR that raised the issue: + * Brief summary: + * A bunch of RF233 specific details in the HIL. Totally needs to be cleaned up. + * E.g. SPI message for RF233 structure is basically part of the HIL + * Hudson: Daniel responded recently that he's been able to hack together a port with the existing HIL + * Other substantive change, that's not as good: + * removes buffer management + * rather than passing in buffers in board initialization, global buffers are referenced unsafely + * but initialization indeed should be radio-specific, not in the HIL + * Hudson: still need a HIL for multi-protocol radios + * Driver logic ISRs: the good, the bad, and the ugly + * Mailing list thread: https://groups.google.com/d/msgid/tock-dev/CACW1YzsUHhBD4GLXSXD4PFSLPZto1NFFQRozR0vhfkUgt5a61g%40mail.gmail.com + * Relevant PR discussion: https://github.com/tock/tock/pull/1220#issuecomment-445122564 + * Summary: https://mailman.stanford.edu/pipermail/helena-project/Week-of-Mon-20181210/000715.html + * Motivation for 1.4 items + * Phil: on board for Time, GPIO HILs diff --git a/doc/wg/legacy/README.md b/doc/wg/legacy/README.md new file mode 100644 index 0000000000..441033300b --- /dev/null +++ b/doc/wg/legacy/README.md @@ -0,0 +1,6 @@ +# Legacy Notes + +Prior to the creation of the working group structure, several of the major +developers held regular calls. Newer notes were converted to [core](../core/), +but a handful of older notes from this less organized time survived. Those +are archived here. diff --git a/doc/wg/network/README.md b/doc/wg/network/README.md new file mode 100644 index 0000000000..357849d8c3 --- /dev/null +++ b/doc/wg/network/README.md @@ -0,0 +1,49 @@ +Network Working Group (NWG) +================================= + +- Working Group Charter +- Adopted 8/11/2023 + +## Goals + +The goals of the Network Tock Working Group (NWG) are to: + +- design a set of interfaces (traits) for networking +- design the user land interfaces for networking +- define how buffer management should be implemented +- determine other kernel infrastructure useful for networking + +## Members + +- Alexandru Radovici (Chair), Politehnica University of Bucharest +- Branden Ghena, Northwestern University +- Leon Schuermann, Princeton University +- Tyler Potyondy, UCSD +- Cristian Rusu, University of Bucharest +- Felix Mada, OxidOS Automotive + +## Membership and Communication + +The networking working group membership is open to Tock developers interested +in the design of network interfaces. It is +intended to be a smaller group that represents the major perspectives +and issues, rather than a complete group. Group membership is decided by +the group: the exact process is not yet determined and may organically +evolve as the group gains momentum. + +The group has a teleconference call once every two weeks. All working group members +participate in the call. Other people may be invited to participate to +help contribute to particular topics or on-going discussions. The +working group chair decides who beyond the working group members may +participate in the call. +Those looking to engage with the working group are encouraged to join the `#network-working-group` channel on the [Tock slack](https://github.com/tock/tock/#keep-up-to-date). +The working group publishes detailed notes of its calls. These will be +posted within a week of a call. This delay is to give participants an +opportunity to correct any errors or better explain points that came up. +They are intended to be a communication mechanism of the group, its +discussions, the technical issues, and decisions, not a literal +transcription of what is said. + +## Code Purview + +At this time, the network working group does not have responsibility for code in any particular directories of Tock. That may change as work progresses, with the possibility of a subdirectory within `capsules/` being created under the purview of this working group. diff --git a/doc/wg/network/notes/2023-08-24/2023-08-24_thread_stack_branden.drawio.pdf b/doc/wg/network/notes/2023-08-24/2023-08-24_thread_stack_branden.drawio.pdf new file mode 100644 index 0000000000..775ed8254a Binary files /dev/null and b/doc/wg/network/notes/2023-08-24/2023-08-24_thread_stack_branden.drawio.pdf differ diff --git a/doc/wg/network/notes/2023-08-24/2023-08-24_thread_stack_tyler.drawio.pdf b/doc/wg/network/notes/2023-08-24/2023-08-24_thread_stack_tyler.drawio.pdf new file mode 100644 index 0000000000..c61cd12ab0 Binary files /dev/null and b/doc/wg/network/notes/2023-08-24/2023-08-24_thread_stack_tyler.drawio.pdf differ diff --git a/doc/wg/network/notes/2023-09-07/leon_tock_buffers_presentation.pdf b/doc/wg/network/notes/2023-09-07/leon_tock_buffers_presentation.pdf new file mode 100644 index 0000000000..131b7f6ecf Binary files /dev/null and b/doc/wg/network/notes/2023-09-07/leon_tock_buffers_presentation.pdf differ diff --git a/doc/wg/network/notes/2023-11-30/tyler_openthread_designs.pdf b/doc/wg/network/notes/2023-11-30/tyler_openthread_designs.pdf new file mode 100644 index 0000000000..8e8de9e41e Binary files /dev/null and b/doc/wg/network/notes/2023-11-30/tyler_openthread_designs.pdf differ diff --git a/doc/wg/network/notes/network-notes-2023-08-10.md b/doc/wg/network/notes/network-notes-2023-08-10.md new file mode 100644 index 0000000000..96e1f477f0 --- /dev/null +++ b/doc/wg/network/notes/network-notes-2023-08-10.md @@ -0,0 +1,96 @@ +# Tock Network Notes 2023-08-10 + +## Attendees + * Branden Ghena + * Alexandru Radovici + * Felix Mada + * Leon Schuermann + * Cristian Rusu + * Tyler Potyondy + +# Introduction + * Alex: First meeting! Goal to define how networking is going to work in Tock. Round of introductions first! + * Felix: Working at OxidOS, mainly on CAN bus. Started with Tock a couple of months ago. Worked on embedded for four-five years. + * Leon: PhD student at Princeton working with Amit Levy. Working on Tock for five years. Funny enough my first real project was an Ethernet driver for Tock. Still in progress... + * Tyler: Master's student at UC San Diego working with Pat Pannuto. Only involved in Tock for a few months. Working on adding support for Thread networking: 802.15.4, 6lowpan, UDP, etc. + * Cristian: I teach at University of Bucharest. Background in wireless communication: 5G stuff. I'm here because I know about Tock and networking. + * Alex: Professor at Polytechnic University in Bucharest. Started on Tock in 2020, interested in Rust OS for teaching. Still interested in network communication for teaching. + +# Organization of meetings + * Alex: Let's begin with organization. I say the same as Core WG model: someone takes notes and posts to github. Markdown. + * Leon: One thing that bugs me from the Core call is that when you volunteer notes you end up having to multitask and do notes later. Maybe we could have a way to share the notes in real time, so someone else can take over + * Alex: That sounds good to me. What platform? + * Leon: Tool called HackMD + * Branden: I vote for that for next meeting + * Branden: How do we handle agenda? Should we send a notification in advance, or start the call with agenda. Or end with agenda? + * Leon: We can play this loose. I do like having the call for items as a reminder + * Branden: Is sending out the agenda reminder on Slack fine? + * Alex: We'll need to make sure everyone is on it + * Alex: What about drawings? Need some collaborative platform for that. + * Tyler: Generally in Tock, the documentation in terms of diagrams is pretty lacking in Tock. I think a great goal would be to ensure that we create visual documentation as part of our working group. + * Alex: I used draw.io in the past + * Branden: Alex has an ACTION ITEM to figure out a drawing platform + +# Working Group PR + * Branden: https://github.com/tock/tock/pull/3578 needs two more small edits before merging + * Alex: I will update that tonight + * Branden: We can merge tomorrow on the core team call + +# Overview of Network Stacks + * Leon: Would be good to do a recap of what people on this call have worked on. Then we could have visualizations of those for next week. + * Alex: CAN, Ethernet, Thread all seem in progress + +## Ethernet + * Leon: Trying to start with Ethernet. Been working on this for a long time. We have discussed at several past meetings of Tock groups. We specifically chose Ethernet as an example because it's one of the simplest transports to integrate with existing IP networks. Back in 2022 we didn't have any layer2 transport implementation. Over the last year we have written three proper implementations of Ethernet MAC layer. They differ in feature set, APIs, buffer management. So a key takeaway now is defining higher-level interfaces and going above layer2 in the OSI stack. IP layer, UDP transport, ultimately UDP/TCP based applications. We have a work-in-progress branch in Tock and Libtock-C where the Tock kernel has a Ethernet TAP driver (TAP/TUN from BSD Linux) which is a driver which forwards Ethernet layer2 to userspace and back. And we have userspace libraries to implement IP: LwIP and `smoltcp`. We're at the stage in the work-in-progress environment where we can share a small HTTP webpage. At Tockworld 2022 we had decided to start with Ethernet layer2. It's a great place to start, but definitely not what we want in the long run. + * Branden: So we have three implementations? + * Leon: We do have three entirely different Ethernet implementations. STM32F4 series by Alex's student Cristian. Lite-ETH for FPGA by Leon. VirtIO QEMU device by Leon. Fundamentally different. + * Branden: Something I'm interested in is how they compare/contrast. Topic for a future meeting. + * Alex: We have the same TAP driver implemented for each? + * Leon: The drivers all use the same HIL to interface with the TAP driver. + * Branden: Oh wow. So all three of these fit with the same interface. + * Leon: Amit has been working on Ethernet stuff. He has some form of Ethernet-over-USB (USB CDC-EEM), which presents the Tock board as an Ethernet device when attached over USB. I believe he has some "rudimentary" IP and TCP stacks in the kernel, mostly for his own testing. + * Alex: Is this for the nRF chip? + * Leon: Should be anything that implements a USB stack + * Alex: We have a USB stack, semi-working, for the RPI2040 + +## Thread + * Tyler: Hudson worked on a 15.4/6lowpan stack historically. As an aside, for the Nordic dev boards the chip doesn't have hardware support for automatically sending acknowledgements and it turns out that we have to re-write 15.4 for the Nordic boards to support OpenThread. The board was being turned on/off after each packet, which ruined timing for acknowledgements. Imix did have hardware support for ACKs. Moving on from here, we've been thinking about routing in IPv6 layer which will be shared with Ethernet for example and Thread uses UDP as well. + * Tyler: Current capabilities of the Thread stack. It is limited so far. It can allow a child to join a router as a sleepy end-device, most simple device operation. The userspace boundary there is not very clearly defined, just working on the capsule side of things. My application connecting to userspace will have some API for joining and using a network. + * Alex: So you'll have a capsule that does all of the work. The capsule would interact with the stack instead of userspace. Interesting to see a capsule as the "user" rather than userspace. + * Tyler: Currently none of this is upstream though. It's on my own branch. Should be pushing it soon, after rewriting the Nordic 15.4 driver. + * Branden: Any thought about Thread on top of Imix? Or just Nordic boards for now. + * Tyler: Just Nordic for now since it's what I have. Reaching out to Hudson about other boards. No reason it _shouldn't_ work on other 15.4 boards, but it's untested. + +## CAN + * Felix: CAN is a weird protocol. If your buffers are full you actually have to prioritize which frames should stay. A lot of the stack is about prioritizing frames. I can transmit frames if I have a high delay between them. If I overload it with frames per second right now, it goes crazy. CAN has pretty limited hardware IP, the same implementation in multiple MCUs. The difference is often which version of the IP with some small additional features. The STMF4 has just a couple of hardware queues. The bigger NXPs have hundreds of queues. Also they have a lot of outputs. Can use an MCU as a gateway to connect one CAN bus to another. I have a small stack that's all in the kernel for the receiving part. Sometimes it misses frames, but it can detect that it misses frames. On the transmission part, if I send a ton of frames then it goes crazy. + * Felix: Most of the stack is in software, but I have two hardware abstraction layers. One for the universal way of working with CAN hardware, one for very specific hardware where the manufacturer changed a lot of things. That latter HAL just has a simple "send" option. + * Branden: What does your userspace interface look like? + * Felix: I have a couple of buffers where userspace can put frames. Interface just sends. On receiving, most frames just arrive into buffers. + * Branden: What does the interface to userspace look like? + * Felix: There is a special interface that userspace adds which has a header that the capsule decodes. The capsule then encapsulates the data from the userspace and figures out how to send it. + * Leon: Do you have any thoughts on interoperability between Ethernet and CAN? + * Felix: CAN is really just a way to send 64 bits. You have to decide what the bits mean. There is a CAN transfer protocol for sending huge frames. It still doesn't define meaning of bits though. So for Ethernet to CAN, you just stuff the ethernet packet bits in CAN. + * Leon: So for bridging, I would make my own encapsulation protocol with headers, and then make my own translation component which translates the frames. + * Felix: Yes, sort of. In AUTOSAR there is some logic. Certain signals correspond to certain collections of bits. + * Leon: So a question from Tockworld I had was how to make an interface to bridge protocols. So I'm interested here + * Alex: I think in cars they use mostly UDP. So they take the frame from CAN and drop it in a UDP frame. Then they just need to add retransmission, for some types of packets, others they don't care if they lose. + * Branden: Do you have support for large packet types? + * Felix: Not at this time, although we can fragment across small packets + +## LoRa + * Branden: The existing LoRa support in Tock is entirely Alistair's effort, but I'll say some things on it. LoRa is long-range wireless (kilometer range) while still at low energy and at low bitrate (kbps). There only exists really one family of chips for LoRa communication, which are usually communicated with over SPI. So Alistair's efforts connect a raw SPI interface to userland, and there are C libraries to drive the LoRa radios over SPI. This would usually have problems with timing, but LoRa is very slow, with a full second between reception and an ACK, for instance. So this works. + * Alex: A LoRa gateway could be an interesting connection of networks here: LoRa + Ethernet + * Branden: Yes, although a LoRa gateway is complicated and we don't even come close to having support for it. Gateways need to service multiple incoming packets simultaneously, which does lead to timing requirements. + * Leon: Do you think LoRa will still benefit from a buffer management interface? + * Branden: Yes. It still has buffers to pass around like anything else. Since it's not so timing sensitive, copies aren't an issue, but it could still use them. + +# Wrap Up + * Leon: Think about things to prepare for next week's meeting. + * Alex: Definitely action item for drawing + * Tyler: As I've been working on 15.4 driver, there doesn't seem to be a lot of considerations for being low power. Is that something we want to talk about? + * Branden: I don't think so. Tock has historically been low-power-ish, just like it's real-time-ish. Not actually real time, but fast enough. Not actually low power, but lower power enough. + * Leon: Amit once described Tock as a platform for your OS. You can configure the kernel and set options to either target high-powered gateway devices or lower powered devices with less resources. I think the lesson, in my view, is that it doesn't hurt to make things more efficient. Including functionality which uses more power wouldn't kill us though. + * Tyler: Example, in Thread the radio is probably just always on + * Branden: We eventually want an interface to turn it off, but that seems okay + * Leon: One action item for me is digging up the half-finished buffer management proposal (based on Linux SK-buf). Not finished in any way, but good to present on. + diff --git a/doc/wg/network/notes/network-notes-2023-08-24.md b/doc/wg/network/notes/network-notes-2023-08-24.md new file mode 100644 index 0000000000..5a4233632c --- /dev/null +++ b/doc/wg/network/notes/network-notes-2023-08-24.md @@ -0,0 +1,121 @@ +# Tock Network WG Meeting Notes 2023-08-10 + +- **Date:** August 24th, 2023 +- **Participants:** + - Alex Radovici + - Tyler Potyondy + - Branden Ghena + - Leon Schuermann + - Felix Mada + - Amit Levy +- **Agenda** + 1. Updates + 2. Alexandru: Solution for drawing during meetings + 3. Tyler: Interfaces for different layers used for Thread networking + 4. Leon: Tock-ethernet integration strategy +- **References:** + - [NRF52840 802.15.4 PR](https://github.com/tock/tock/pull/3615) + - [STM32F429 Ethernet PR](https://github.com/tock/tock/pull/3523) + +## Updates +- Branden: There's a label for Network WG related PRs on the Tock github repo +- Tyler: Working on 15.4 PR. Need some closure on behavior for transmit +- Branden: We had a discussion about handling on/off states for the 15.4 + radio. Other things in Tock like GPIO just "do the right thing" and set + themselves in the state they need to be. The 15.4 radio HIL expects callbacks + when changing power states though, which likely makes sense for external radio + chips and radios that take some real-world time to start up. + + Long story short: in Tyler's PR, calling `transmit()` also turns the radio + on. While this might be fine, we may want to back away from that in the + future. +- Tyler: Previous PR did not even have a check for that before. Add this + functionality after some discussion with Amit. + + Alternative would be to return an error, if the radio is off. +- Amit: Takeaway is - what Tyler implemented is fine, what was there before was + probably also fine. Figure out what the right behavior is, not clear if the + old was _wrong_. + + The existing clients (like XMAC?) won't work before your PR. This would need + to be fixed, but not on this PR. +- Branden, Amit: Action for here - Tyler should leave the PR as is, it already + has one approval. +- Amit: Finished EEM (Ethernet over USB driver). It'll be nice that's available + on anything with USB. Also, it's a very simple hardware implementation; + sidesteps a bunch of the low-level implementation details at is relies on USB + for the transport. Good simple simulation of Ethernet hardware (maybe too + simple) but great for experimenting. +- Leon: Especially good for working on higher layers without an Ethernet board + + +## Solution for drawing during meetings + +- Alex: There's nothing comparable to draw.io. Draw.io allows us to log in with GitHub. As long as we can save files in a GitHub repo, everybody should have access to drawings. +- Branden: Do we need to have a new repo to contain the drawings? +- Alex: A branch might be sufficient. We could select the branch which will also contain the meeting notes (create it ahead of a given meeting). + + Drawings are portable, so you can open them in your browser with the draw.io website. + +- Branden: Is there a good way to make a shared channel or group for it? +- Alex: Draw.io is working in your browser, and it plugs into a backend to save files. +- Branden: We wouldn't be able to see things as people draw in real time? +- Leon: Maybe just use a screenshare? (yes) +- Leon: We can embed drawings into PDF files themselves. + +## Interfaces for different layers used for Thread networking + +- Tyler: Was quickly sketching up a design of what Pat and I came up with yesterday. ([sharing draw.io diagram](./2023-08-24/2023-08-24_thread_stack_tyler.drawio.pdf)) + - Original thought was to put Thread on top of UDP. + - It may be best instead to just give Thread access to each of these layers. + - Userspace driver would issue system calls to the Thread control-layer to the Thread component itself (control path). + - Actual communication payload would go into the UDP layer directly through something like a socket-interface (data path). + - Don't know whether it'd be an issue where Thread would sidestep the typical layering. + - When you specify a socket that you bind to, you can specify the interface you want to bind to. +- Leon: One option is to move the UDP/TCP boxes into the Thread component here, so the socket is accessed through the Thread interface. So when a socket is specific for a given underlying interface, it might make sense to make the UDP component instantiated within the Thread capsule. Then Ethernet could conceptually instantiate its own UDP component +- Amit: Right now, in the 6LoWPAN stack there is a UDP component. If I'm not mistaken, it's not explicitly a system call driver (rather a library for constructing UDP packets). What's the imagined purposes of having UDP and (in particular) TCP in the kernel at all? +- Tyler: You're proposing UDP and TCP would be moved outside of the kernel? +- Amit: TCP seems hard to implement in the kernel. In userspace, there is dynamic memory allocation and libraries, etc. + + Is the purpose of having them in the kernel purely for abstraction purposes? Or perhaps also for ACL reasons? +- Alex: It seems there is another reason - some hardware peripherals expose UDP and TCP sockets directly. So having support in the kernel would let us work with those or with other boards. +- Leon: Apart from these external hardware devices, I think UDP/TCP could definitely be in userspace. So we could construct things assuming that we accept arbitrary IP interfaces. Then it would be easy to move the interface to kernel for some things. The userspace application could be the same. +- Alex: Another note is that having TCP/UDP in userspace would mean if multiple apps need to communicate they would both need complete copies of the stack since we can't share libraries. We could use IPC for this though. +- Amit: For code size? +- Alex: TCP is stateful. +- Amit: Per-stream stateful. +- Amit: UDP is super simple. Not stateful, couple of headers. +- Amit: For Thread, it might not be required to have the control and data plane in the same capsule. +- Tyler: Does Rust have already existing libraries +- Leon: We have SmolTCP running in userspace. (not merged or in a public branch yet) +- Amit: Where does Thread come into play in terms of managing the link? It sends broadcast packets to join a network, etc. Presumably it manages when the radio wakes up and when to listen for incoming packets. Is it also interposing between layers of the data plane? +- Tyler: Short answer: no. he packets are just 15.4 / UDP. Caveat: for the mesh-link establishment, it uses the same encryption of the link layer. UDP needs to have knowledge of the encryption state of the Thread link layer. Other state is also used as part of the encryption, such as the frame counter. +- Amit: One thing that seems nice about the presented design is that it seems like there is the ability to update the control-interface of the network protocol can be updated independent of the data path (e.g., when a new version of Thread comes around). Using that, could we, for example, switch between an XMAC and Thread network by just swapping out one capsule? +- Branden: In the diagram -- UDP still connects down to 6LoWPAN, and that still connects to 15.4? So this means that the 6LoWPAN capsule needs to be quasi-virtualized, with two clients? +- Tyler: There are some degrees of virtualization at each of these layers. By the next call, I can have a more extensive update on that. We will need virtualization. I have not drawn the upcall paths; that may become tricky. +- Leon: By _virtualization_, do we mean packets routed to either the "Thread" or "UDP" components? If so, does Thread have a way to determine whether a given packet goes to the data- or control plane, like IP with the nextheader field? +- Branden: Yes, it would need to have that. Thread indicates this using TLVs. +- Amit: Is data-plane traffic wrapped in the payload following these TLVs? +- Tyler: I believe this is handled by means of a specific port (`19788`). +- Amit: If applications are sending payload data, the 15.4 frame will not have any Thread-specific TLVs, right? +- Tyler: Yes. Once the network is established, everything is sent with link-layer encryption as well. Once you formed your network, the only control that is occurring are heartbeat messages. The UDP port is what differentiates control from data messages. Thread does not intend to replace or wrap UDP / 15.4, but simply acts as a control layer on top of it. +- Branden: [*shares different draw.io image which adds an "encryption" box between 6LoWPAN and 15.4*](./2023-08-24/2023-08-24_thread_stack_branden.drawio.pdf) +- Tyler: This seems accurate. +- Amit: Perhaps the encryption is not a dedicated component between layers, as it might be implemented in hardware or implicitly as part of one of the other components (such as the 15.4 implementation). +- Tyler: It's very near to where we're able to join a sleepy-end device. Hoping to get a PR soon which may not be a finalized version of this, but gets us closer to a fully-working implementation. + + +> Notes from Amit from dialpad chat: +> Like, basically, the thing that's titles "Thread" in the kernel in Tyler's diagram > needs to be in the kernel in order to have control over a fixed set of things: +> +> - It needs to be able to control the power/read-state of the radio itself +> - Maybe set MAC and/or IP addresses +> - Plug in some crypto stuff (maybe set the key, add some information, etc) +> +> That all seems _pretty_ generic, beyond thread, such that all the thread specific stuff might fit in an app +> It sounds like, from a reception perspective, the thread application receives UDP packets on port 12345 (whatever.. > I keep forgetting) and that's it. So it's _basically_ like a normal application on the reception side + + +## Tock Ethernet +- Leon: Workshopping Ethernet support in Tock over the next two weeks with Amit. Hoping to talk about that and SK_BUFF stuff next meeting + diff --git a/doc/wg/network/notes/network-notes-2023-09-07.md b/doc/wg/network/notes/network-notes-2023-09-07.md new file mode 100644 index 0000000000..694a17044a --- /dev/null +++ b/doc/wg/network/notes/network-notes-2023-09-07.md @@ -0,0 +1,102 @@ +# Tock Network WG Meeting Notes + + +2023-09-07 +=== + +- **Date:** September 7th, 2023 +- **Participants:** + - Alex Radovici + - Tyler Potyondy + - Branden Ghena + - Leon Schuermann + - Cristian Rusu + - Felix Mada + - Ioan-Cristian Cirstea +- **Agenda** + 1. Updates + 2. Buffer Management + 3. Discuss 14.5 Layer Security (maybe during updates?) +- **References:** + - [14.5 Layer Security](https://github.com/tock/tock/pull/3652) + - + + +## Updates +### Drawings +- Alex: collaborative drawing with draw.io isn't really possible. Connection to github can commit automatically, but doesn't really share. Screensharing is the best option +- Leon: A bunch of commits seems terrible +- Alex: We could make a branch and commit there +- Branden: Screensharing worked great since we're all joining on computers +- Leon: Always make sure to export PDFs, which embed the draw.io source and can then be editable +### Notes PR +- Alex: PR for last week's notes should finally be good-to-go. Pulled Leon's fixes +### Clocking +- Alex: Clocking issue with CAN that we'll push PRs for +- Felix: Peripherals in Tock don't have a way of determining what their clock is. So some might run at 100 MHz others at 50 MHz depending on prescalers. I'm working on adding a trait so peripherals can determine that information and add some functionality to enable/change the PLL (but not on the fly). On STM32, a problem is that the PLL is an analog circuit and based on the target frequency, other registers also need to be changed. That needs to be done in a certain order or the PLL won't lock. +- Alex: To put this in context, we have created a more full-featured CAN driver, but ran into the clock issue +- Branden: Often we just configure all clocks at boot. Why is that not sufficient here. +- Felix: It is sufficient, there are some other registers to change the voltage regulators. Can't change those with peripherals enabled. Need to disable peripherals, change configuration, re-configure PLL, wait for PLL lock, re-enable. Changing prescalers also requires changing some overdrive-registers. Only required for some frequencies. +- Branden: Boards can still do that at startup right? The SAM4L does something similar +- Felix: Yes. But the interface for doing so isn't clear. I'll +- Alex: Main problem, different problems tackle these problems differently. The behavior _within_ the peripheral changes depending on the frequency. +- Branden: checking the clock makes sense to me in peripherals. nRF52 does the right thing, by only having one choice. SAM4L required some decent work. There was some work like "Power Clocks" out of Stanford which did such things, but it didn't ever reach the main branch +- Leon: Some of power clocks fine-grained control of the SAM4L clocks has made it into Tock in some fashion. Really arcane traits for the SAM4L exist. Probably not applicable to other chips. For other chips, the state of the art is that peripherals implement some non-standard way of interfacing with the clock manager. Some NXP chip has some traits for having peripherals be generic over a clock rate. There are tradeoffs like not being able to configure them at runtime. +- Alex: Networking needs high-speed clocks. We have the shift the clocks up to do stuff quickly. But saving power might want to shift down the clocks. +- Leon: A good point. Configuring clocks is complex. Not sure if we'll be able to find a generic way of configuring clocks. + - Branden: Takeaway -- you could get away with a non-standardized version, if it is going to make your life easier. If you have something portable, we'd love to hear about it (e.g., on the core call). +- Alex: Need to be portable to other chips. +- Felix: Interfacing problem is not at clock initialization, but when peripherals are changing clocks. E.g., changing the UART baudrate when switching clocks. Ioan did some tests when changing clocks and had to catch panics to show that the UART could print anything +- Alex: Moving forward, Felix can prepare a PR and we can talk about it on the core call. +- Branden: The STM32 is great as it gives you so many options, but we've been taking the easy route with the chips we primarily support. +- Alex: We'll probably need some interfaces that have "full speed" or "power saving". Could just leave unimplemented for some chips. We don't really need to dynamically adjust, just two modes. +- Branden: Great insight +- Felix: If you want to go to sleep, you have to notify peripherals. So if something is buffered, it needs to finish the transaction first. Similarly capsules could have something buffered. This back-and-forth is going to need to happen with everything. +- Leon: One question is how other embedded OSes solve these problems. Sounds like an engineering nightmare even without Rust +- Alex: Matters more with networking, as high-powered clocks take lots of power and low-power mode matters even more + + +## Buffer Management +- [Presentation slides](./2023-09-07/leon_tock_buffers_presentation.pdf) [Web link](https://docs.google.com/presentation/d/1Yh2bvCnUM0obqiZIPPpip9j1CCBqhZPg1Ts2P_x9u3g/edit?usp=sharing) +- Leon: Put together some slides on stuff before calls. Looking for active discussion here about goals and constraints +- Leon: First is goals. Network packets can be large, so compute and memory is expensive. Really don't want to copy buffers. In Tock now, we basically always do copies for virtualization. But multi-KB buffers are too big for this. So zero-copy whenever possible. +- Leon: Want to support wide variety of hardware. Generic as possible. +- Leon: Generally avoid run-time overheads, including spurious memory allocations or over-allocated memory +- Leon: Other thoughts? +- Branden: Dynamic memory is one. We don't want to do runtime dynamic allocations in Tock as they could run out leading to difficult to predict bugs. They should either be compile-time or be allocated upon initialization. +- Tyler: In 15.4 you have to add keys, which are for encryption/decryption. The userspace driver actually stores them and there are some lookups where you ask the driver for the linked list of which key it is. This is because there's no dynamic allocation. You don't know how many keys it might need, so they go in grants. Makes for a complicated lookup +- Branden: Especially if you wanted a capsule that could send packets. +- Leon: Continuing, I thought about Ethernet implementations to start (didn't look into 6lowpan or CAN yet). Starting with simplest: LiteEth MAC. Uses something like DMA which exposes two SRAM ring buffers. So you have one location for TX one for RX. Packets MUST be contiguous allocations. You put data in these or read from these as arbitrary memory. Could even pass a reference to part of the ring buffer area up to the rest of the kernel, ring buffer can hold multiple packets. +- Leon: Next is VirtIO MAC. Hardware and Software share ring buffers via "descriptors", a struct with a pointer and length and flags. Can chain descriptors. DMA does handle linked lists of data. No constraints on granularity or length. Packets require a VirtIO-Net header appended to them with configurations. +- Leon: STM32 or NXP iMX.RT1060 MACs. These are representative of full-featured MACs in embedded chips. Similar in spirit to VirtIO but with more constraints. Minimum/maximum buffer sizes for contiguous allocations. Might need multiple descriptors for a really big buffer. Packets need to be a minimum size or there will be under-run when transmitting. +- Leon: Finally, external MACs. SPI bus to chip. Internal ring buffers on chip that can be read/write. So buffer copy operations can be asynchronous operations in Tock. +- Branden: Added asychnrony here makes things really complicated. +- Leon: I agree. It's really common though, especially for other networks (LoRa). Unavoidable +- Alex: WiFi is like 99% external chips too +- Leon: For other protocols, I haven't looked much yet. Amit and I talked about about USB EEM (Ethernet over USB). Feels a bit like an external chip Ethernet. Requires a header to be pre-pended to packet. Can't presume that we can split into multiple buffers. +- Leon: Can others fill in how other protocols differ? +- Felix: SPI to CAN converter with asynchronous buffer transfers with possibility of failure. Microcontrollers with internal peripherals get immediate feedback about failure or success of frames. Some have big queues of frames, some are smaller. Asynchronous operation adds a lot of complexity. +- Alex: Peripheral I had for CAN had a specific memory location for buffers with slots for each frame (frames are fixed-length). Memory space for FIFO that is hidden is common too. Chips that are automotive-grade have a big chunk of memory that can be configured for how to use for sending / receiving. +- Leon: Wild collection of different interfaces and expectations is somewhat worrying. CAN does seem to meet the variety from Ethernet MACs. So hopefully any solution meets both of these. +- Tyler: Memory is passed from layer to layer. 15.4 is the hardware layer and DMA is configured on the nRF52 with a pointer and length and the radio copies directly into that when it receives a packet. I think for packet receiving, you have to change the DMA pointer after reading the packet. Then that buffer gets passed up the layer stack with some copies occurring in places. +- Tyler: For the RF233, I'm not very familiar. External chip, likely with asynchronous buffer management. +- Leon: Summary - I had feared that Ethernet was going to be way more complex. But all of these have complications and I'm actually encouraged by that. +- Branden: A complexity is that some things can follow linked lists and some require contiguous allocations. +- Leon: This is going to take multiple meetings to talk about, but lets keep going a little. +- Leon: Looking at the current Ethernet HIL, the TX path takes a static u8 buffer, so hardware could send it. The RX path takes a slice `[u8]` (not static), as it could be a slice of something from lower memory +- Alex: CAN actually has the same interface https://github.com/tock/tock/blob/d702f33e9c8c0d01df0135d9e404ac68329a4ac0/kernel/src/hil/can.rs#L788 +- Leon: USB has to add headers, which is easy because you can reslice to remove the headers. Adding headers is hard though. Could split packet over two USB transactions, but that's not the same as other Ethernet interfaces. +- Alex: Another use is framebuffers for drawing to screen. Need to pre-pend SPI header to each data payload so it does copies. +- Branden: SD Card does this too +- Leon: Prototyping some "PacketSlice" code in Rust that replicates the SKBuff structure from Linux. Basic idea is a set of Rust types that allow you to fall back to treating something as a contiguous buffer allocation. But with two markers for head-room and length of data (for tail). Could support pre-allocated buffers with excess memory beyond your packet size. With const generics, we could have a PacketBuffer with a given amount of headroom. Could change the type when writing to buffer. Interfaces could specify required headroom. +- Leon: Second idea is that we could still create a linked list of these SKBuffs. A flag in the generic type could control whether it is allowed to be a non-contiguous allocation or not. +- Leon: Been working on this. It's hard to get this working in Rust when all of this has to happen statically at compile time. +- Branden: This is great. I really appreciate the thoughts here. What I really like here is the compile-time type-based checking of whether you've got the right type of buffer and enough space available. I was thinking about this idea and grappling with the issue of "how do you know if there's enough headroom available" question. +- Branden: I think that the original high-level design was for SKBuffs to be a contiguous memory space while BSD mbufs had linked allocations of memory. But I suspect that over time they've both become complicated enough that they could replicate each other. +- Leon: It's quite possible to get SKBuffs with slight different semantics and just break upper layers. They've gotten very complex. +- Branden: Something to consider: Mbuf has an "m_pullup" function which can MAKE something contiguous from a non-contiguous buffer. +- Leon: I have something like that in my prototype +- Tyler: Where would this live? Any ideas? +- Leon: Not sure yet. +- Alex: Could be applied to many buffers in Tock, framebuffers and SD Card and stuff. + diff --git a/doc/wg/network/notes/network-notes-2023-09-21.md b/doc/wg/network/notes/network-notes-2023-09-21.md new file mode 100644 index 0000000000..dc85c0fd65 --- /dev/null +++ b/doc/wg/network/notes/network-notes-2023-09-21.md @@ -0,0 +1,171 @@ +# Tock Network WG Meeting Notes + +- **Date:** September 21st, 2023 +- **Participants:** + - Alex Radovici + - Felix Mada + - Branden Ghena + - Tyler Potyondy + - Leon Schuermann + - Cristan Rusu +- **Agenda** + 1. Updates + 2. Continue discussion about PacketBuffers +- **References:** + - [Thread Network Child Device](https://github.com/tock/tock/pull/3683) + +## Updates +- Tyler: The Thread Child joining PR (https://github.com/tock/tock/pull/3683) is shipped, hopefully gets merge during the next week, Pats needs to look at it, I still have a quick question +- Branden: Is this the last PR for Thread? This gets you to a full working Thread Child implementation? You had some earlier PRs to get this work? +- Tyler: It does not have UDP yet, it just shows up in the Open Thread network. I rewrote the whole driver, after 4 months of working on it. I decided to hold of to UDP and sending heartbeats. There will be another PR, that will be the milestone + +## Packet Buffers +- Leon: stopped last time due to out of time, happy to continue. I planned to have more examples for this meeting, but the quarter started :) Would this discussion be productive? Not sure that this is the best way forward. +- Branden: Fair question. Do you have any questions that the group should talk about? + - You might have a prototype? + - You are still collecting questions? +- Leon: It's a hybrid approach, we converged during the last meeting to some ideas. The hardware/network constrains we talk about last time confirmed that we need to use something like an `skbuf` or `mbuf` structure. +- Leon: There are two aspects: + 1. What does the Rust compiler permit us to write? - exploration approach, probably not parallel; (Branden: not many people are Rust experts) + 2. How do we bridge these concepts into the actual semantics exposed and expected by different protocols? This is a much more difficult questions. (Branden: it needs at least a prototype interface) +- Leon: Do we need a working prototype or a sketch? +- Branden: A sketch +- Leon: Two ways: + - A sketch in the next 5 mins + - I con just release something in the next two days +- Alex: can we do both? start sketching something here and Leon take it forward? +- Leon: typing rust... +- Leon: The concept is to have a type of buffer that support contiguous and non contiguous allocation, supports the concept of headroom to prepend headers with no allocation. +- Leon: impl the `PacketBuffer` trait on several types of buffers +- Branden: can you specify `when HEADROOM >= NEW_HEADROOM` +- Leon: There's a hack you can use. Use impl the right traits and impl associated const and numerics in traits. You can assign an associated type that is a const unit? And you can have an assertion that the compiler executes to panic during compilation. (TODO: Leon double-check this couple of sentences) +- Leon: Example of this is: https://github.com/tock/tock/blob/8c440023feeb9fe4923ee330b1cd76ab93acf005/arch/rv32i/src/pmp.rs#L281 +- Branden: The idea here is when you create a PacketBuffer from scratch, it has the max headroom, and then reduce it? +- Leon: example below the rust code +- Branden: How does IP know that it's a max of 32 and 14? +- Leon: the current solution needs the developer to determine this, if the dev chooses a value to small, the instantiation would cause a compile time error +- Tyler: Are you saying that the higher layers use larger buffers? +- Leon: No, the buffer size itself remains constant across layer +- Tyler: The buffer is wrapped at each layer and grows in size? +- Leon: That is true if there is support for non contiguous allocation, but this is not compatible with all the different hardware that we want to support +- Leon: What we can do is, have a constraint so that the if anything says the buffer must be contiguous, then nothing can be a linked list +- Tyler: All with type checking? +- Leon: Yes, that would be the idea +- Alex: Question here: If you have a device that supports non-contiguous buffer, contiguous still works right? Would you have to wrap it to make the types work? +- Leon: You can freely convert one way but not the other. You can always shrink the headroom and you can always turn a contiguous into a non-contiguous, but not the other way around. The one really inelegant thing is that we can't encode these rules to automatically calculate the type for a given layer. The IP layer needs to look at lower-layer implementations to decide what it's requirements are. The benefit is that it fails at compile time if the IP layer guesses wrong. Specifying too much works, but too little should fail to compile +- Branden: This would lead to a scenario where your code doesn't compiler, and you have to enlarge the headroom. This goes back to the initial `static_init!`, which made us edit the board main.rs file whenever we guessed the wrong size for some thing. +- Alex: What is the meaning of `current_head`? +- Leon: Each member of a linked list would be linked into another buffer. Each would have a head and tail element pointer. All data between head and tail would be the "actual data". That mechanism allows us to have fixed buffers that can still grow in head or tail. Head allows us to pre-pend header info without reallocating. Tail allows us to add more data. +- Leon: Let's say it has 4 bytes of headroom and we want to change the min headroom, that doesn't change the Head pointer, it just changes the type signature to make things match. +- Alex: I thought reduce_headroom would actually move the head pointer +- Leon: Oh, it should really be called "reduce_type_headroom" or something. It only changes the type. It doesn't move the actual head pointer +- Alex: Why would you need this? +- Leon: You need to transmute types to convince the compiler that things are compatible. +- Branden: It's more like "guarantee_minimum_headroom" +- Leon: And there is some _other_ function "prepend" which allows you to move the head and get more space. It could fail at runtime if there isn't enough space left in the buffer +- Tyler: One quick question and a couple of comments. Would this replace the subslice type? +- Leon: good question, I do not have an answer to it yet +- Tyler: This seems the way it works for UDP, you slice it and the lower layer holds on to the full buffer allocation, but passes a chunk of it up. +- Leon: Technically what we do here is a strict superset of the underlying impl, the semantics exposed in the API are the inverse +- Tyler: Comment - I think you mentioned earlier, I don't think this is huge holdup, we do this on a net stack, and these stacks have standards with specific buffer sizes. +- Leon: True, good point, one caveat, these sizes that we specify (numbers) are propagated from the hardware impl upwards. The headroom will change based on the hardware. +- Branden: If you have VirtIO ethernet, you might have another headroom requirement that is internal to the hardware and doesn't go to the outside world +- Leon: All these constants depend on the composition of the types that you use. +- Tyler: Really exciting, this seems that it will "just work", does not seem to be a huge change in what people are using now +- Leon: Trades a lot of buffer complexity with really ugly type signatures +- Alex: this can be used for other hardware, like SD cards and displays +- Branden: This types seem impossible :) + +## The Thread Child joining PR +- Tyler: PR is up, a lot of stuff is just to get it to compile. Not as much complicated state-machine stuff as prior commits, even if there is more "code" here +- Tyler: One question: I have an issue, at the Thread layer, UDP packets have another layer of encryption (MLE encryption). We use the AES-128 CCM crypto engine. Crypto needs a static buffer to be passed to it. In order to save memory, my implementation only uses a buffer for recv and send, and just passes that buffer to the crypto function when recv/sending (as opposed to having a separate crypto buffer as well). +- Branden: It hands off ownership back and forth to the crypto engine +- Tyler: The issue is that you can't just pass a slice, it has to consume the buffer as it needs a static buffer. One of two things have to happen: + - 1. That buffer has to be replaced when the callback occurs. + - 2. You have no way of knowing how long the original packet was and if I pass in a buffer that is 60 bytes, I cannot return 200 bytes buffer back (the original size of the buffer) +- Tyler: The way `SubSlice` (previously `LeasableBuffer`) works: + 1. yous slice it + 2. you pass in the slice + 3. reset on the callback +- Tyler: The compiler does not accept it, it needs a static one. +- Branden: The interface for the crypt expects a static buf instead of a slice? Can we change that or that will break other things? +- Tyler: my workaround - I have another variable as part of the state machine that tracks the size of the buffer passed to crypto. +- Tyler: unless I save how long the buffer was, the crypt engine returns the full 200 bytes buffer and we have no way (without tracking the size) to know how long the packet itself was. +- Branden: You take the whole static buffer, say 200 bytes, and pass it to the crypto, saying it has 60 bytes. Now the engine knows to do crypto on the 60 bytes and later returns the buffer in a callback. But the engine does not return the length in that callback, so instead you have to hang on to it yourself so you remember when the callback occurs. +- Tyler: Curious if someone encountered this? +- Branden: I wanted to suggest `LeasableBuffer`, but it seems that you tried it. +- Branden: I think your solution is a reasonable thing to do +- Branden: Perhaps you should change the callback to include the length? +- Tyler: This seems reasonable. +- Alex: This will be a problem for `PacketBuffer` +- Tyler: The problem is that buffers have to be static, that takes a way a lot of the abstractions +- Branden: For hardware buffers need to be static +- Branden: I think this group is great for these things exactly, as we can discuss these solutions that we come up. + + +```rust + +// head tail +// | | +// [0, 0, 0, 0, DATA, 1, 2, 3, 42, 0, 0, 0, 0] -> [ ... ] +// PacketArray.reduce_headroom::<2>() + +trait PacketBuffer { + fn reduce_type_headroom(self) -> PacketBuffer + when HEADROOM >= NEW_HEADROOM + { + + } + + fn prepend(data: &[u8]) -> PacketBuffer { + self.current_head -= data.len() + } + +} + +// Borrows a slice to an array +struct PacketSlice<'a> { // implements PacketBuffer + current_head: usize, // offset into the buffer in some way + slice: &'a mut [u8], + next_buffer: &PacketBuffer<_, _>, +} + +// Owns an array +struct PacketArray { // implements PacketBuffer + current_head: usize, + arr: [u8; SIZE], + next_buffer: &PacketBuffer<_, _>, +} + + +impl EthernetHIL for MyEthernetMAC { + fn transmit_buffer(buf: impl PacketBuffer) { + + } +} + +// Example of a stackup that needs header room +// allocate buffer +// | +// v +// UDP +// | +// | +// IP (24 bytes header + max(32, 14) = 56 bytes) +// | +// |------------------------------\ +// Ethernet A (32 byte headroom) Ethernet B (14 byte headroom) + + +// allocate buffer +// | +// v +// UDP +// | +// | +// IP (non-contig, 0 byte headroom) +// | +// |------------------------------------------\ +// Ethernet A (non-contig, 0 byte headroom) Ethernet B (non-contig, 0 byte headroom) + +``` diff --git a/doc/wg/network/notes/network-notes-2023-10-05.md b/doc/wg/network/notes/network-notes-2023-10-05.md new file mode 100644 index 0000000000..b4aea0a06d --- /dev/null +++ b/doc/wg/network/notes/network-notes-2023-10-05.md @@ -0,0 +1,82 @@ +# Tock Network WG Meeting Notes + +- **Date:** October 5, 2023 +- **Participants:** + - Alex Radovici + - Felix Mada + - Tyler Potyondy + - Branden Ghena + - Leon Schuermann +- **Agenda** + 1. Updates + 2. Ethernet Planning + 3. Buffer Management +- **References:** + - [#3683](https://github.com/tock/tock/pull/3683) + - [#3695](https://github.com/tock/tock/pull/3695) + + +## Updates +- Tyler: Sticking around for a PhD 🎉, so I should be around for a bit +- Tyler: PR for Thread Child (https://github.com/tock/tock/pull/3683) is moving along. Should be merged soon. It's time to take a look at it if you want to. Not really any open questions here, just need comments. +- Tyler: Also, draft of radio capabilities is coming soon +- Leon: Ethernet support for STM32 needs a review too: (https://github.com/tock/tock/pull/3695). It's includes the clock changes Ionut has made and is a follow-up on a PR that had too many changes. So there have been eyes on it (more than it appears). Also the PR goes to tock-ethernet instead of main. +- Alex: Teaching a class on Rust! + + +## Ethernet Planning +- https://github.com/tock/tock/pull/3695 (and more generally the tock-ethernet branch) +- Leon: The Tock-Ethernet branch emerged from dev plans on Ethernet starting almost two years ago. There have been local versions of userspace Ethernet stacks since 2019, but the code has always been rough and not ready for release. Thinking about constraints on supporting Ethernet, back then it wasn't clear we even wanted to support it, figured a branch that had some infrastructure would be good. Goal was to use the branch to iterate on a HIL that could support various Ethernet chips. +- Leon: Since then, the branch has grown to pretty stable, though preliminary, structure for network access on Tock through Ethernet. Contains multiple network cards. Contains a HIL for sending/receiving Ethernet packets. Contains a capsule that can transfer packets from the HIL to userspace with an internal ring buffer. That's enough to run an HTTP server on Tock it turns out! +- Leon: Some high-level questions: Should we keep operating in a branch? When do we decide it's "stable enough" to merge into Tock main? +- Branden: Isn't maintaining a separate branch a pain? Does it track main? +- Leon: Yes. We've been doing merges, only ever a month behind at worst. Not been too bad. The one big issue was that Github doesn't deal with PRs against branches that merge in significant parts of another upstream branch. The way Github presents the diff is different from your local client. So PR authors need to reset their branches to something rebased on this branch, or the Github diff looks like a ton of meaningless changes. That might be a good reason to merge. +- Branden: Second, what are we waiting on? Since it really only works with itself, it's okay to have even crappy code in Tock main. Experimental system that won't break other things. +- Leon: I think that makes sense +- Leon: Also, the TAP driver for userspace that moves packets from the HIL to userspace is pretty rough. Could definitely use a rewrite. +- Branden: Could happen after we merge with main though. +- Tyler: I think sooner rather than later makes sense. Especially since it only really affects others using Ethernet. +- Tyler: For the 15.4 stack, I've found that really really explicit documentation about what is missing still would be really useful. Made the 15.4 stack more difficult to learn because I assumed some things existed. So I recommend comments, docs, headers in files, anything to show people what's "known missing". +- Leon: Yes, definitely. There are new people joining Tock all the time who don't have all the history. I think this will be especially important for the HIL, which is pretty minimal right now. +- Leon: Do we have any official classification for the states of HILs? +- Branden: No, I don't think so. Mostly legacy knowledge in the group about which things are or aren't stable. +- Leon: So again, not demonstrated well for new/outside contributors +- Leon: I think moving forward with PRs to upstream, after the STM32 stuff lands, would be good +- Branden: One challenge is just the stability of Tock at this point. We used to just push stuff all the time in the old days. But now we have PRs like this that sit around, even though they are for an unstable system and should just move fast +- Tyler: Where would someone start looking into things? +- Leon: I think this PR is actually relatively self-contained. It's been pared down to the minimal stuff. In short, I think the kind of work you've done on the Thread stack thinking about vision isn't where we are here. We're just trying to implement drivers to send packets at all. It's been a very big effort. +- Tyler: Is this the board that Alex brought to Tockworld for Leon? Is it using an FPGA? +- Leon: Right board. But not an FPGA, that was a different demo I did. That's a RISC-V board I have been working on with libtock-c. And Ionut has been working on this STM32 ARM board with libtock-rs. So we're really in a state where we can mix-and-match and things work. +- Leon: Also, Amit has the ethernet-over-USB working. So any USB-capable Tock board can present itself as a ethernet port when plugged into a computer. The profiles are USB EEM and USB ECM. Used for USB ethernet adapters. +- Alex: When I plug in the board, what do I see? A new ethernet card on your computer? (Yes) +- Leon: So anything the computer sends on that interface appear on the device. It's a "virtual cable". Like you had an ethernet port with a cable, plugged into the Tock board + + +## Buffer Management +- Alex: I'm interested in more discussion about the Buffer stuff we were working on last time +- Alex: So a question from last time. I was under the impression that if you have a constant parameter, that a smaller one would still work. That's wrong though. It has to match exactly (yes) +- Alex: So, you can reduce headroom. But how do you get it back? +- Leon: Yes. One of the ideas is that `reduce_type_headroom` doesn't necessarily give you a brand new type, but it has a lifetime. That doesn't work great with static lifetimes though. +- Leon: Or you can potentially have `reduce_type_headroom` return a new type and also a marker type that holds the information about how much the headroom was reduced. You can plug those back together in some way to combine them. So you split the info on your buffer and then can recombine them. +- Alex: How do you prevent the user from mixing two pieces that weren't originally split? +- Leon: Rust aliasing rules prevent buffers from overlapping. So the pointer of a buffer is enough to determine this. +- Leon: I think these might be overly complicated. I think the internal information in the struct has enough information to know where the headroom was. +- Branden: That's what I expected. +- Alex: Problem is what if you reduce headroom several times. You need a stack of info? +- Branden: I don't think you need a pop. As long as you know min and max, you can just reset to any number between them +- Leon: Yes, each thing would reset to the new headroom, which must have enough space for that by initial construction +- Alex: So each driver would have to manually determine the size it wants +- Branden: Yes. That seems reasonable to me as a network driver choice though +- Leon: Need to separate the ideas of things that are inherently memory unsafe versus semantically tricky. Changing buffer size, between the limits, is safe from a memory perspective. +- Alex: Could we have a PR with just the buffer stuff to start? +- Leon: Could, for sure +- Branden: We will need to display real-world use, hopefully in two different use cases +- Leon: We should definitely chat about the precise requirements of the display driver +- Alex: I actually have two display drivers. One can do separate SPI transfers, one must do a single transfer, so it really really needs to append headers to the front of something +- Branden: SDCard does this too. With lots of copies right now +- Alex: Another use case is for WiFi. We have the Arduino Nano WiFi which has exactly the same problem +- Leon: I have been trying to keep in mind the complexity of the network stack when engineering this idea. Things get pretty messy when thinking about examples there. So a simpler interface to start with will be very useful +- Alex: The "bus driver" has the same issue. It's a shared bus for displays that can use either I2C or SPI. +- Branden: Didn't know that existed. Neat +- Branden: So to start, I think a really really minimal implementation of this idea would be super useful. No bells or whistles. Just the basics. Then we can implement something with it and start asking for features +- Leon: Pretty clear the direction I should move here. Just need the time to do it. A good plan would be for Alex and I to meet up sometime next week to talk about this diff --git a/doc/wg/network/notes/network-notes-2023-10-19.md b/doc/wg/network/notes/network-notes-2023-10-19.md new file mode 100644 index 0000000000..0f77a61240 --- /dev/null +++ b/doc/wg/network/notes/network-notes-2023-10-19.md @@ -0,0 +1,108 @@ +# Tock Network WG Meeting Notes + +- **Date:** October 19, 2023 +- **Participants:** + - Tyler Potyondy + - Branden Ghena + - Leon Schuermann + - Felix Mada + - Alex Radovici +- **Agenda** + 1. Updates + 2. Reserving Ports + 3. Buffer Management +- **References:** + - [#3683](https://github.com/tock/tock/pull/3683) + - https://github.com/lschuermann/packetbuffer/blob/12bf7e3959b96354089f8250edf5357c3c1afb72/src/lib.rs + + +## Updates +- Tyler: Thinking about capabilities for radio control. PR is still coming soon. + + +## Reserving Ports +- Tyler: Thread sends Mesh-Link Establishment (MLE) messages that keep the mesh attached and determines parent nodes and stuff. Those messages are sent over UDP, with port 19788. So for Thread to work at all, it MUST have control over that Port. So I was thinking about whether Thread should exclusively reserve this port, or if anyone can take it and we just bind to it. +- Tyler: So the general question is, if a capsule requires a certain port, should it reserve it in some way or just request it and fail? +- Branden: Ports being reserved for applications is normal. HTTP, SSH, etc. +- Leon: Does this only affect things if Thread is loaded, or would it be a change to all UDP stack usage even without Thread? +- Tyler: Tricky. Right now the network isn't created until you request from userland to create one, so it binds to the port at that point. There's a chance that someone else has bound the port by then. It's annoying to debug. +- Tyler: I'm leaning towards just letting thread fail at that point, but wanted to discuss it. +- Leon: In other systems, for well-established protocols when your application chooses to open a raw socket and conflict with something in the kernel, is that the kernel silently wins. Not sure this is right, but it's reasonable to say "the kernel always wins". Failing instead of silently not working would be even better. +- Tyler: So you think it's okay for thread to fail if another application takes the port before it? +- Leon: Maybe, we are worried about denial of service where on application just blocks others. The other option is that the kernel can just yank ports from applications. I don't like unconditionally reserving though, as there are all kinds of weird protocols which need various source ports. +- Tyler: So you're proposing that we'd let a UDP application bind to it if thread hasn't yet. But then if we want to create a thread network, the thread network could take the port and make the other application fail. +- Leon: I'd be fine with that. I'm really just saying that other OSes do this +- Branden: Could you claim 19788 at initialization time? Initialization-time failures are generally preferred. +- Tyler: That would be what Leon is against. +- Branden: No, only when you actually load Thread. When you don't include Thread in your board's main.rs file, the port is free to be used by applications. +- Leon: FWIW, I prefer this solution. +- Tyler: Do you think this should be a panic, Branden? +- Branden: If you can conclude immediately that something's wrong, a panic seems reasonable. +- Leon: Doesn't this conflict with dynamic binding of applications to ports? +- Branden: Those applications would be too late then. Should be second-class to things that are known at configuration time. The scenario we're talking about here is a board which has both $protocol and Thread, and both claim it. Then we should panic. Similar to if the kernel registers two HTTP servers on the same port. +- Tyler: Just to clarify, `main.rs` initializes everything fully? +- Leon: In practice yes, but not necessarily +- Tyler: Does that cause an issue too? That would be a weird runtime-versus-static conflict. UDP is always initialized before Thread, but hopefully processes are after that? +- Leon: Yes, processes are after that. But conceptually you could instantiate a kernel peripheral later, and that case would have two reasonable APIs: the bind-to-port API should return an error and there should be a force-bind API which replaces a currently bound port. Ultimately, these nitty-gritty details of exact error behavior and semantics can be determined by the dev based on how they wire things up. We do always complain about how complicated `main.rs` is, but as a benefit, you do have lots of flexibility. +- Tyler: So moving forward, bind to 19788 in the component. And panic if it's already taken. +- Branden: If you can make it that the API from Thread returns an error, but then the component panics, that'd be a great design. +- Tyler: Yeah, the component would check for failure and panic. +- Branden: That's ideal +- Leon: Agreed. We have the components as a default, but they're not at all required. So others who disagree can instantiate peripherals themselves. + + +## Buffer Management +- Leon: https://github.com/lschuermann/packetbuffer/blob/master/src/lib.rs +- Leon: Specifically, https://github.com/lschuermann/packetbuffer/blob/12bf7e3959b96354089f8250edf5357c3c1afb72/src/lib.rs +- Leon: Some work in progress here. Doesn't compile, might not be around forever. Types are maybe not beautiful right now, but I tried to pull our discussions into this. So this is a set of type abstractions which could set up the buffers we talked about. +- Leon: PacketBuffer is the main type here. Generic over whether it's contiguous and how much header space it has. Header isn't necessarily the true space, but at least the minimum available space. +- Leon: We'll add methods here. We might have methods to give us a raw pointer for DMA use for example. +- Leon: This is a trait as it could be backed by Rust slice or Rust struct. +- Leon: PacketBufferEnd holds no data, always contiguous, no header room, it's a dummy end element for linked lists +- Leon: PacketSlice is the sliced instance of a single element from a PacketBuffer. So it's got generics for itself and generics for the _next_ PacketBuffer in the linked list +- Leon: Similarly is PacketArray, which has contiguous and a fixed length generic, plus again stuff for the next one. +- Branden: So PacketArray and PacketSlice are implementations of PacketBuffer (yes) +- Branden: Why linked lists? +- Leon: Most generic implementation. This lets you combine things in arbitrary arrays, like header followed by data, followed by more data, etc. Even for hardware without linked buffer support, you end up with a two item list, where the second is a PacketBufferEnd, which is a zero-sized type. +- Leon: Going into the example here, we can make a slice, make a PacketSlice over it, and that uses `from_slice_mut_end()`, which implicitly creats the PacketBufferEnd for you to stop the list +- Leon: We say here that the header has 32 bytes, so that 32 is taken away from the full size of the slice so it's reserved. +- Leon: To pass this to something that has a headroom requirement, we can move headroom around as needed. This should take no runtime overhead, but fail at compile time if headroom is too small. +- Branden: Can you ever get back to the bigger size? +- Leon: Working on that. There was a compiler bug with which trait it was calling. I'll fix that at some point. Totally doable. +- Leon: Back to example, we can make a PacketArray around our PacketSlice. Since it's two buffers now, it's got to be non-contiguous. If you put True there you'll get another compile-time error. +- Leon: Slices do know their original sizes and can be restored, but that's a runtime check. That's a destructive operation. It might eat some of your data if you're not careful. +- Leon: Making smaller headroom is non-destructive as it should check at compile time. +- Alex: So the array next packet buffer would be prepended? +- Leon: Yes, but non-contiguous +- Alex: So could you add space for footers? Maybe non-contiguously appending buffers? +- Leon: Not in its current form. I'll have to think about that. It's a lot of linear type composition right now, like linear list operations. We're only really modifying what's in front. So the magic here is that we still have the old type hidden deep in the type somewhere. We overwrite by pre-pending the new type. +- Alex: If you can pre-pend like this, do you still need the headroom? For contiguous things +- Leon: I think footers are totally doable +- Alex: We need to abstract this away somehow +- Leon: I'm hoping type inference saves us. Hoping hard +- Branden: Maybe a macro to create this for us? +- Leon: Yes +- Leon: I will have to think about footer changes though +- Alex: There's no footer for ethernet or IP? (no) +- Leon: There is a 32-bit CRC, but that's usually done by the MAC without allocated space +- Branden: I think footers are going to have to exist for a general solution +- Leon: Okay, adding one more confusion. So, how does this work for structs and clients passing stuff. +- Leon: I have an example mimic-ing that. Dispatching a buffer and handing it back. This example is rough, but perhaps understandable +- Leon: We have a ImANetworkLayer which is a lower layer like IP. Has minimum requirements imposed by the lower layers beneath it. It accepts a PacketBuffer generic over those same constants. +- Leon: We can't directly take clients, but need adapters that do the type conversions. +- Leon: I have a higher-level adapter. This does the conversion from one layer to another, taking BOTH generic sizes. Contiguous must match between the two layers, but headroom doesn't. +- Leon: We have a reference to the type we actually want to call methods on. +- Leon: When we actually want to dispatch a buffer, it gets passed a higher-layer buffer type and converts it to the lower-layer buffer type seamlessly with a shrink call +- Leon: I haven't written the upcall part yet, but that would do the same adaption but with restore call +- Leon: All of this conversion should be almost zero cost, except for the restore which needs a size comparison. +- Leon: Still working on moving some things between traits to make code compile, but promising +- Leon: I'm really hopeful that most things inline and vanish totally. We'll have to do a measurement though +- Branden: So after types and Rust fighting, we'll definitely need to focus on ergonomics +- Leon: Two aspects there, 1) understanding and juggling types and 2) making it nice +- Leon: I think the understanding isn't so bad with documentation. Writing it and fixing compiler annoyances is hard, but the WHY isn't so bad +- Leon: What I'm really worried about is that it might look really ugly +- Branden: Again, I'm really hopeful for macros helping us here. The entire adapter looks like stuff that's autogenerated. Could turn into something that's a single `generate_adapter!` macro +- Leon: Optimistic. +- Branden: I do want to try this out before ergonomics. We want the lower stuff solid before thinking about exactly how people are going to use it +- Leon: Still need to develop larger, do some measurements. Then we need to actually apply it and see if it works. Finally, even if all of that works, ergonomics will be key + diff --git a/doc/wg/network/notes/network-notes-2023-11-02.md b/doc/wg/network/notes/network-notes-2023-11-02.md new file mode 100644 index 0000000000..008433231f --- /dev/null +++ b/doc/wg/network/notes/network-notes-2023-11-02.md @@ -0,0 +1,58 @@ +# Tock Network WG Meeting Notes + +- **Date:** November 02, 2023 +- **Participants:** + - Tyler Potyondy + - Branden Ghena + - Leon Schuermann + - Felix Mada +- **Agenda** + 1. Updates + 2. Buffer Management + 3. OpenThread Support +- **References:** + - [OpenThread Platforms](https://openthread.io/platforms) + - [OpenThread Porting](https://openthread.io/guides/porting) + + +## Updates +- Leon: Merged STM32 Ethernet PR into Tock-Ethernet! Really high quality code. Next is merging the branch itself +- Branden: The more you can limit the changes to just "ethernet" stuff, the easier that PR will be to merge +- Leon: Also needed to increase the stack size for boards. Hoping to fix that. The only change outside of ethernet stuff will be adding this stack to some demo boards +- Leon: Boards are: STM board now, LiteX simulation, LiteX ARTY board, QEMU with VirtIO, nRF series with ethernet over USB. So we can polish our initial HIL, ensure that things work, then make one coherent PR + + +## Buffer Management +- Leon: Update is no updates yet! Spent a few hours after the call last time, and almost all of the code compiles, but there's still some that doesn't which may require some architecture redesigns. Might be losing some type information when passing things down that we still need when passing buffers back up. +- Leon: Even from this current work-in-progress design, I think we can extract a simpler design that will support what we need for some initial tests. + + +## OpenThread Support +- Tyler: https://openthread.io/guides/porting and https://openthread.io/platforms +- Tyler: Amit's feedback has been pretty strongly anti-capsule implementation. I think it's made sense to start there. Especially since we've exposed some bugs in 15.4 implementation. +- Tyler: Longer term though, it needs to be robust and bullet-proof for adoption. So for Thread support, we have a really good opportunity to keep the library as a process in userland. +- Tyler: There's a guide for OpenThread already to porting and what hardware you need. Requirements are: 15.4 which we have, alarms are good, True RNG is fine on Nordic although other boards are unclear and I don't know if we have an RNG driver, non-volatile storage I'm unsure +- Leon: The insight I'll give is that there are a few non-volatile storage options, some of which work for users and some don't as well. There is a Key-Value storage, there's also a region of flash that you can just write to. Both have some issues though, and aren't bullet-proof implementation right now. For example, the region of flash doesn't support atomic operations at all, so it could break across power cycles. +- Tyler: Two thoughts on that. The strategy I had related to Tock: so far when working on Thread I found several issues across other capsules. The use case showed that the bugs exist, then we could fix them. So I don't mind pushing on stuff. +- Tyler: My guess for non-volatile storage is that it's only for preserving across power cycles, so that's probably fine. +- Branden: For RNG support, I'm not sure, but the nRF has a peripheral for it that works great. +- Branden: But it's limited if there's no peripheral available. There is a paper by Phil and company about building your own RNG too... +- Leon: Support is actually pretty good. There is a HIL and several non-nRF boards have RNGs +- Tyler: Impression was that there is existing work on it. +- Tyler: Support for OpenThread should be relatively easy, been done for RIOT and FreeRTOS. +- Branden: Turning the OpenThread C library into an application in userspace? (Yes) +- Tyler: Priority scheduler -- maybe more of a concern with this? +- Branden: We have one: https://github.com/tock/tock/tree/master/kernel/src/scheduler +- Leon: And I did an analysis of it, looking at a precision time protocol in userland: https://leon.schuermann.io/publications/2021_Schuermann_ptp-time-sync-embedded-systems.pdf +- Branden: I'm not sure if anyone's actually using them though +- Leon: There are a few upstream boards playing with them. So I suspect they at least basically work, although there are some well-known long-standing issues. For example, the priority scheduler has quadratic time complexity when scheduling, so it's very expensive. Fixing it requires redesigning the list infrastructure though, so it's a big lift. Overall, you have to be prepared to open a can of worms. But coming at it with a motivating use case will help things to move forward +- Tyler: This is definitely something to circle back to. Going to be a long-term effort, with little progress in the short-term. +- Tyler: I am really interested in the idea of having OpenThread as an application rather than reimplementing it ourselves. It seems really useful long-term. There are possible parallels in BLE for example, if such libraries exist +- Branden: More generally, I think just having a userland communication library pushes on a lot of other Tock infrastructure and will show us what needs improvements. For example: inter-process communication +- Leon: One thing Brad got excited about last time was C implementations for network libraries isolated in the kernel (https://dl.acm.org/doi/10.1145/3625275.3625397) +- Leon: So far, I think we did try some BLE libraries in userland, but the latency was just too high for them. +- Tyler: One more thing, I've been working on a GRFP application, and it was useful to get feedback about my research proposal. What Pat and I came to was developing over the course of a PhD a software version of secure IoT, similar to what AzureSphere does with hardware. So the first step for me is thinking about networking. So that's continuing to push me in this direction and I'm very excited about it +- Branden: Back to OpenThread porting. I think the next really hard challenge is just thinking about what the real latency requirements are. I'm hopeful that it's not too tight, but I'm not sure +- Tyler: Agreed and hopeful. The really tight part is auto-acknowledgements which are happening in the capsule still. +- Leon: And if it turns out it can't work in a regular C app, we could keep pushing on my isolated C kernel stuff, which will essentially run it as a capsule. The biggest issue right now is that it's on RISC-V right now, and we'd need to port to ARM for Thread stuff, as I don't think there are any RISC-V boards that support Thread right now + diff --git a/doc/wg/network/notes/network-notes-2023-11-16.md b/doc/wg/network/notes/network-notes-2023-11-16.md new file mode 100644 index 0000000000..82f24cd6cc --- /dev/null +++ b/doc/wg/network/notes/network-notes-2023-11-16.md @@ -0,0 +1,80 @@ +# Tock Network WG Meeting Notes + +- **Date:** November 16, 2023 +- **Participants:** + - Felix Mada + - Tyler Potyondy + - Branden Ghena + - Leon Schuermann + - Alex Radovici +- **Agenda** + 1. Updates + 2. Tock Networking Tutorial Proposal + 3. Buffer Management +- **References:** + - [Packet Buffer Approach 1](https://github.com/lschuermann/packetbuffer/blob/d51ef6b922a18d6a7a64c8e0585237845892dba1/src/lib.rs) + - [Packet Buffer Approach 2](https://github.com/lschuermann/packetbuffer/blob/cfd38a50ead0f7608dd053fe850d2e3d6bd91e85/src/lib.rs) + + +## Updates +- Leon: Merged STM Ethernet support. Thanks to OxidOS folks for development. Very high quality. On todo list is to rebase Tock-Ethernet on Tock master and make a PR to merge everything. +- Tyler: Doing a master's thesis writeup on Tock/Thread stuff. + + +## Tock Networking Tutorial - CPS Week +- Tyler: Pat and I have planned a Tock/Thread networking tutorial at some conference, focusing on CPS-IoT Week in Hong Kong this year, May 13-16 2024. Larger academic audience. Tutorial would be demonstrating Tock as a platform you could use for IoT research, rather than developing Tock. Tutorial proposal is due tomorrow. +- Leon: Interesting. I'll see if I can get involved in this, need to check with Amit +- Tyler: I'll circle back with Pat to think about this +- Branden: Concern is that Tyler might not have any support here. Maybe he'll get support on the demo, but if no one else goes to Hong Kong, then everything falls on Tyler in the end +- Leon: What do you expect the audience to be? Number of attendees and familiarity with embedded/Rust +- Branden: It's a big mix of people. Lots of folks from embedded/networks, but also just as many folks from Real-Time or CPS Theory. You'll get people who don't even know command line. +- Branden: Bonus concern is whether Tock is ready for IoT work yet. A little early since some of it doesn't exist. +- Leon: We've really focused on soundness and security, rather than networking so far. We would need to invest more time into development for a proper IoT tutorial. +- Alexandru: Could likely send someone to CPS Week in May. +- Tyler: Hope is that the OpenThread port could be pretty developed by then. + + +## Buffer Management +- Leon: Last time we talked about this, I gave a rundown of the type infrastructure I tried to create to capture our requirements. We concluded that this seems generally promising but still had some issues. Plan today is to page in some of these issues and highlight some architectural changes. +- Leon: Approach 1 - https://github.com/lschuermann/packetbuffer/blob/d51ef6b922a18d6a7a64c8e0585237845892dba1/src/lib.rs +- Leon: To go over this, we have a PacketBuffer trait. Generic over whether it's a contiguous buffer (array) or non-contiguous (linked list), and that amount of headroom we have (buffer space available at the front). We have the ability to shrink headroom, to reduce the headroom, claiming some of it for writing data into. +- Leon: This commit also has an example Network layer which has some requirements that it's contiguous and has some amount of headroom. Then we had a concept of a higher-layer adapter which takes a type that has a larger headroom and reduces the amount to the exact amount required by our network layer and passes it downward. The adapter handles both passing down and up the stack, is capable of converting a buffer back to its original type. +- Leon: So the network layer stores a number of higher-layer adapters, which take on the role of a "client". +- Leon: This solution has an issue. Does not compile due to nasty issue. In theory all of this makes sense because we can statically determine if headroom is strictly larger, which allows a type conversion. That works. The issue though is that by downsizing the high-level headroom of the PacketBuffer, we get a type back that's generic over the right types, but we don't actually know anything else about the type except that it implements the interface. We don't know if it's the same type as type NPB here. Just because a type implements the same interface in Rust, that doesn't mean it's an identical type. We can create a type from a higher-level, but we can't prove it's the same. +- Leon: Approach 2 - https://github.com/lschuermann/packetbuffer/commit/cfd38a50ead0f7608dd053fe850d2e3d6bd91e85 +- Leon: So, we can switch to `dyn` types. Downsides: needs vtables which is more flash and slower speed. Also can't have features like associated trait methods, or methods that don't take a self parameter. Might make some of the features tricky to implement. +- Branden: Why does that matter? +- Leon: You can no longer have associated constants, types, or static methods. I believed those to be useful for some of our types, like iteration. It may be that this is not an issue, but it's a change from my prior implementation attempt. +- Leon: Now we need to make new functions, not associated with anything, that convert from one `dyn` type to another. That does seem to work. +- Leon: For the example from before, we can have a method in the higher-level adapter that takes in a PacketBuffer, which is a concrete type from a higher-level layer. And we can pass in a static reference to this type. Then we can convert that reference into a `dyn` reference and pass it to the lower layer. But, if we have a static mutable reference, and take a static mutable `dyn` reference, we lose the information of the original reference, and we can't reconstruct the original type. +- Leon: So what doesn't work when we use `dyn`, is that we can't have a "pass buffer back" which goes from `dyn` back to the original type. So using trait objects works generally for our scenario. And it works for changing headroom and passing down. What doesn't work with trait objects right now is taking the buffer and converting it to its original types. Especially with static references to these buffers. +- Tyler: When you're saying the original types, what do you mean by that? Can you give an example? +- Leon: Yes. So an original type on line 416 here, we take a chunk of bytes and turn them into a slice and makes a PacketSlice type which holds the buffer and sets aside some amount of headroom. Then we want to shrink the headroom when passing it down. So we pass in a mutable reference and get back a `dyn` reference. But we eventually do need to get back to the original type at some point to release the slice. +- Tyler: There are also two lifetimes in the PacketSlice? Why are those underscores? +- Leon: Yes. I'm not yet at the point where non-static lifetimes work for these conversions. Work in progress. +- Leon: So in summary, we solved the first half of the issue and did shrinking, but we can't yet unshrink. +- Leon: There is a different proposal where in each layer when we convert a buffer, we store a "shadow copy" of some information about this buffer. So when we pass a buffer back up, we can use the information in this copy plus the reference, to be able to reclaim our original type. That's what I'm working on now. +- Leon: So we have a `capture()` function which can claim a buffer and give out a `dyn` reference. And a horribly unsafe `restore()` function which takes a reference and gives back a buffer. What we're trying to do is that whenever we do a cast from type to trait object, we need to save the original type somewhere. +- Alex: Two questions here. There's some kind of downcast in Rust if you know the type right? Second, doesn't every type have an identifier? Could we store that? +- Leon: Yes. Two lines of thoughts here, but I think you're on the right track. First, Rust's references, especially static references, have guarantees about memory not being reused. So we can maybe store a pointer to that struct and get some guarantees due to it being a static lifetime. Second, we can hopefully use Rust's type IDs, plus that trait objects have a reference to a type's vtable, so we can uniquely identify the type. +- Alex: So you could check, and then panic +- Leon: Yeah. We return an option right now. There are checks, which I haven't figure out what are yet, but we could do the checks and then perform the operation. +- Alex: That seems sound to me +- Leon: If we can guarantee the uniqueness of the type ID. And we respect inheritance rules +- Alex: Doesn't Rust have a downcast function which should do this? The Any Trait. I _think_ it should do what you're trying. +- Leon: I have tried to use this in my previous projects unsuccessfully. But it does sound like what you want. Skimming some documentation, it's achieving what I wanted, but safely +- Branden: So in summary, we can create a real thing in a higher level. Then we can pass around trait objects everywhere and do conversions on them. Finally, when it gets to the very top, we can convert back. +- Leon: And we still have static compile-time analysis here for almost everything. +- Leon: One runtime failure. If your network card has multiple outstanding requests and swaps buffers by accident and passes the wrong type back up, the upcall conversion path will fail at runtime. +- Leon: Another thing I haven't seen yet is what the +- Branden: Do we still need the adapter layer? If translations are a standalone function +- Leon: Still needs the capture idea somewhere, could exist in the higher layer maybe? Could have a method that just takes a dyn refernece and a method that returns a dyn reference. If we pass the dyn reference into the client, the client knows the type it needs to recreate. The type is implicitly encoded by using this particular client method. +- Alex: Yeah, it would still call the "restore" method, but it would be generic and Rust would figure it out at compile time +- Leon: Hmmm. Hard to think about live. +- Alex: Is there any way we can do the capture/restore with the from/into or tryFrom? Does the assert break this? +- Leon: The assert doesn't matter. The function could be in an into trait, and we'd panic if it's not true, but the assert only relies on values known at compile time, so the rust compiler will evaluate at compile time. So I think we can put this in any trait. The only ugly thing about this right now, and Rust is actively discussing this, this fails when you compile but not with a cargo check. So it'll only panic when actually producing a binary. The reason for this is that the Rust compiler front-end doesn't do the monomorphization to expand these constants. That happens later, so the check doesn't notice it. Tock CI would still catch it though. +- Leon: Thanks for this insight Alex. New todo for me is to try the Any stuff and see if it breaks. +- Leon: The current code is terribly unsafe, but it does compile and is facially sound. +- Alex: A question is where this will be placed. In a library? Maybe question for next time. +- Alex: I will talk to someone about trying this on the display driver. + diff --git a/doc/wg/network/notes/network-notes-2023-11-30.md b/doc/wg/network/notes/network-notes-2023-11-30.md new file mode 100644 index 0000000000..144b80d5d5 --- /dev/null +++ b/doc/wg/network/notes/network-notes-2023-11-30.md @@ -0,0 +1,73 @@ +# Tock Network WG Meeting Notes + +- **Date:** November 30, 2023 +- **Participants:** + - Alex Radovici + - Tyler Potyondy + - Branden Ghena +- **Agenda** + 1. Updates + 2. OpenThread in Tock Design +- **References:** + - [Tock OpenThread Designs](./2023-11-30/tyler_openthread_designs.pdf) + - [Leon's Encapulation Paper](https://dl.acm.org/doi/10.1145/3625275.3625397) + + +## Updates +- None + + +## OpenThread in Tock Design +* [OpenThread Design Diagrams](./2023-11-30/tyler_openthread_designs.pdf) +* Tyler: Overview want full thread networking, the parsing and stuff is tricky. OpenThread C library does this well. So we want to re-use their implementation. Other OSes do this: Zephyr, FreeRTOS, etc. Bespoke implementation was good to start, but for most of the requirements they don't seem to timing sensitive and Tock can handle them from Userspace (we hope). +* Tyler: So I have some design ideas for today. Three, but I think the third is most useful. +* Tyler: Design 1 - We have a Thread capsule controller which can talk to Apps and to an OpenThread app-service. Apps would have to make syscalls. Thread would have to make syscalls. It's the obvious design, but a lot of syscalls and latency. Possibly some timing issues with Thread. Maybe would work? +* Tyler: Designb 2 - We have a the same apps, but no Thread capsule. IPC from apps to OpenThread for Thread requests. Only OpenThread would do syscalls to 15.4, crypto, rng. In practice, our IPC implementation might require syscalls anyways, so this isn't really an improvement. +* Tyler: Design 3 - Use Leon's "encapsulated library" option. So OpenThread would be an encapsulated crate, like Leon presented at Tockworld this summer. So apps would do syscalls to a Controller capsule that talks to it, but no full context switches or syscalls are needed between the Controller capsule and OpenThread library. At least, I believe they would be lower cost calls. +* Tyler: My first implementation is going to go with Design 3, but just have a C dependency to start for testing purposes. We'd put OpenThread in a crate with the Foregin Function Interface, then pull in that crate into Tock and link against it / compile it. I'm not 100% familiar with linking or compiling, so not 100% sure how this will work. Open to thoughts. +* Alex: I did this with a C++ binary. It did seem to work. The smaller the interface the better. Exceptions can propogate too, which is bad. +* Tyler: How did you create the interface? +* Alex: For the ESP, we couldn't use bindgen, I guessed the interface signatures. Bindgen did work in a different project, but didn't do Enums well. C will keep an Enum in the smallest size possible, but Bindgen will always keep Enums as 4 bytes. So you need a flag when you compile the C code to keep Enums at the maximum size +* Tyler: So Alex, for the ESP32 you needed to reverse engineer the library. But if you have the source did you use Bindgen? +* Alex: Yes. Bindgen worked, but we only did C interfaces not C++. +* Tyler: Did you use the CC crate to compile? +* Alex: I'm not sure. But I can connect you with the person who did this, Dan. +* Branden: So the ESP32 had a binary blob. +* Alex: What would happen with the ESP32 is that the function call would go through, but the kernel will still crash. Not sure why +* Alex: Bindgen has two ways of dealing with Enums. One way is defined constants, the other is Rust enums. But the Rust enums are different from the C enums from the compiler. Different representations led to crashes. +* Tyler: Did you do static linking of the library? Or compile it as part of the Rust project? +* Alex: Not sure +* Tyler: One tricky part is that OpenThread does have a big build system. Which we don't want. +* Alex: How does Zephyr do this? They have to include it in their build somehow. +* Tyler: I did that approach. But a lot of what happens with building OpenThread for Zephyr is through West and I was having a hard time understanding it. A lot of unrelated stuff. +* Branden: So you want to compile it as part of Tock, not as a seperate, already-linked binary blob? +* Tyler: I think. There are build scripts for OpenThread, which has a lot of nested build scripts. The first specifies the platform which invokes CMake and Ninja and eventually it links the OpenThread repo into the platform-specific stuff. That's just one big script and then you flash it. +* Branden: If you were just going to compile OpenThread as a binary blob, adding it to the kernel should be easy from a memory standpoint: just reserve a chunk of memory in the LD file. I'm honestly not sure how to connect up functions to that blob and calls from that blob though. +* Alex: So, Tock needs to be asynchronous in the kernel. Is OpenThread async, or does it wait for a while? +* Tyler: That's a good question. I don't think it's a huge concern for just playing around with it. The way I thought about this: if OpenThread hangs, is there a way to reclaim control from it? I don't have an obvious answer here. I do doubt OpenThread is async, but I'm not sure if we could have an interrupt that takes control back. +* Alex: You'd need a different stack. I don't remember, but don't think, that Leon's stuff supports that. You'd never be able to continue running OpenThread again, if you mess up its stack. +* Tyler: Looking at Leon's paper (https://dl.acm.org/doi/10.1145/3625275.3625397), I think there is a separate stack? +* Branden: Basically, we're thinking about treating it like a process or like a capsule. If a capsule hangs, the kernel hangs. If a process hangs, we can timeslice it and come back later, or even restart it. +* Alex: But can you continue it afterwards if you time slice it? Would the network still work? +* Tyler: Not sure. Thread does save stuff to nonvolatile memory, so just restarting should maybe be okay and not even time things out in the best case. Maybe there are ways to work around this. +* Tyler: So the question is whether OpenThread is asynchronous enough to keep the kernel running. +* Alex: Yes, if incoming interrupts stop it. And we can wrestle control back. We'd HAVE to treat it as a process but avoid a context switch? But that code would need to be trusted, as it could theoretically stop its sandbox if it's in supervisor mode? +* Tyler: I do think Leon set up the encapsulated function to be untrusted +* Alex: Too much speculation here. Need to read paper and/or talk to Leon and get back to you +* Tyler: Yeah. Great points though. Definitely concerns I shared +* Branden: One extra concern for you. Be aware that Leon's encapsulation stuff is a research project, not a fully functional library of its own. So you're going to have to think of yourself as a developer for that project if you want to get it working, not just a user. +* Tyler: Agreed. That was my expectation and I've talked with Leon about that. +* Branden: Going way back, I also think that Design 1 could be a good place to start. It certainly seems easier than getting Design 3 working in any form. +* Tyler: Yeah, and it would be good to have for comparison for a potential paper. So I'd probably do both anyways. Pat was pushing for Design 3 to start. +* Branden: Finally, another repo for you that uses OpenThread from my previous research lab. I can put you in contact with the person who worked on it (Neal Jackson). https://github.com/lab11/nrf52x-base +* Alex: Generally, I'm excited about the encapsulated C code idea. There are lots of cases where there are existing C libraries that we want to be able to reuse rather than rewrite. +* Branden: Goal was always to do those in userland, but that doesn't seem to have panned out. +* Alex: MPU regions has always hurt that. Alignment requirements for MPU in ARMv7 makes apps use way more memory than you need. In automotive they just use the MPU for separating 1 or 2 domains. But tasks or processes are never in separate memory regions. It wasn't designed for apps. Not sure how this works on RISC-V and ARMv8 is much better, I believe. +* Tyler: What is ARMv7 versus ARMv8? +* Alex: Newer ARM cores. Some Cortex-R +* Branden: Or Cortex-M33 or things like that. nRF53 series, I think. But nRF52 is ARMv7 +* Tyler: Can't we load lots of processes now on nRF52s? +* Branden: So, the problem is memory. Because of the alignment issues on the MPU, you need to waste a ton of space for each application, unless they're toy applications. +* Alex: We had a big application to controlled a screen. It MUST be the first application due to alignment issues, for example. +* Alex: Looking at the Cortex-M33 documentation for MPU alignment. For ARMv8, all regions must be 32-byte aligned. Region size must also be a multiple of 32 bytes. So that's WAY better. + diff --git a/doc/wg/network/notes/network-notes-2023-12-14.md b/doc/wg/network/notes/network-notes-2023-12-14.md new file mode 100644 index 0000000000..b2692522ae --- /dev/null +++ b/doc/wg/network/notes/network-notes-2023-12-14.md @@ -0,0 +1,69 @@ +# Tock Network WG Meeting Notes + +- **Date:** December 14, 2023 +- **Participants:** + - Tyler Potyondy + - Branden Ghena + - Leon Schuermann + - Felix Mada + - Alex Radovici +- **Agenda** + 1. Updates + 2. Buffer Management + 3. Next Meeting Plan +- **References:** + - [PacketBuffer Mockup](https://github.com/lschuermann/packetbuffer/commit/acea95a5b28ebc5343289e1b62144bd89b153715) + - [Trait Casting Playground](https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=2c7fb2971338e478d5d9efadd821372f) + + +## Updates +- Tyler: CPS-IoT tutorial for Tock is accepted! Any advice is appreciated. +- Leon: Have some undergrads here that could help with it. +- Branden: Sooner is better. Especially if you have a design, you can pull in others to help build stuff. +- Alex: Tyler should reach out to me for planning on students. + + +## Buffer Management +- Leon: https://github.com/lschuermann/packetbuffer/commit/acea95a5b28ebc5343289e1b62144bd89b153715 +- Leon: To review, my original proposal used PacketBuffer types and changed the generic arguments with regards to headroom when passing around. The problem is that Rust treats the types as entirely different for monomorpization. So we can't convert types, even if they implement the same trait, because the compiler could be doing entirely different things. +- Leon: The solution to this is `dyn` traits. But when we pass around static trait objects, we lose access to the underlying type and there's no way to reconstruct it. +- Leon: As Alex mentioned, the `any` trait in Rust can get the TypeID for a trait, which can let us convert a reference for a unknown type back into a proper type if those IDs match. That has a guarantee that things will actually match. +- Leon: I implemented that, which wasn't super straightforward. There was actually a PR in December that enabled this in Rust. So current stable breaks, as trait upcasting is "experimental". But for nightly this moved all the way to stable, with no feature flag. It'll be in the next rust release. Marked for stabilization. +- Leon: Playground with example: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=2c7fb2971338e478d5d9efadd821372f +- Leon: So now we have some trait and a struct that implements it. In the main method, we create a static mutable reference to that struct. We can even create a `dyn` trait object from that reference. To convert back from the trait object to the original type, I tried to use the typecasting infrastructure by getting the type ID and doing a comparison for specific reference types. If they are equal, we can transmute, right? That was my first attempt in `safe_upcast()`. +- Leon: However, it turns out that the two IDs never match. That was weird to me because they _are_ actually the same type. But it turns out taking the typeID of a trait object won't match the ID of the proper type. It encodes that it is a trait object in the typeID. So the only way to see if TypeIDs line up in Rust is with a proper `Any` object, which overloads the `.type_id()` field to reflect the proper underlying type. +- Leon: The new feature just merged lets us do trait upcasting, which is if the trait has a strict superset trait requirement that they must implement `Any`, then we can convert from trait object into an `Any` trait object. Then we can use the `downcast_ref` method to convert back into a struct. +- Branden: `downcast_ref()` panics? +- Leon: It returns an option actually! +- Tyler: So bigger picture, what's the reason for the upcast downcast? +- Leon: For our real use-case, we have a PacketBuffer with requirements encoded in the generics of the type. We want to convert implementations to traits that can be passed into a shared network layer. That works by just converting types into trait objects. On the way back in the upcall though, we don't have a way right now to convert the trait objects back into real structs. And we need the real struct to access the underlying buffers again. +- Leon: So what I've done is implemented this particular way of converting Any trait objects into proper types. One caveat is that Any is only implemented for `static` types. So we can't use lifetimes everywhere and all buffers have to be static. This would be a bummer, but it's the Tock reality anyways. +- Leon: So now we can shrink buffer headroom when passing down. The layer gets the trait object, and then right now just passes it back as a mock-up. Then our higher layer we convert into a dyn Any reference, then `downcast_mut` to get the original type. Or if it's wrong it panics right now. +- Tyler: That makes sense now +- Leon: There is one tiny bit of unsafety, and maybe unsoundness. `shrink_packet_buffer_headroom()` checks that the parameters for the buffer are valid such that it _could_ shrink in size. Then it does an unsafe transmute. While that does make sense conceptually, technically we're still converting between different types. So it's important for us to be sure that we can do this conversion without breaking any Rust invariants. One issue: Rust could choose different layouts or methods based on the generic parameters. We can prevent that by making a wrapper around our type. These parameters are markers, which shouldn't affect layout. So we should be able to have the PacketBuffer wrap a type which isn't generic over the parameters. So the method can pull out of one wrapper and put into another wrapper. +- Alex: So you'd do a move? Construct a new type and move the contents? +- Leon: Yes. One additional thing though, we'd have to pass back the exact same memory space. The cool thing in Rust though is that if you have a mutable reference you have full control of the memory. So we can swap in a dummy type, then change the thing, then swap back. +- Leon: So we have to receive and pass back the exact same pointer. But mem::swap will let us take a type out of a memory location, extract the buffer by destructing it, make a new type, and then swap back in to the location. So we just have to guarantee that the types have the same layout. +- Alex: Does this work here? +- Leon: I believe so. We'll only swap the internal type. +- Branden: And that can replace the transmute. +- Leon: Yes. It _probably_ creates valid binaries as-is. But probably isn't a good enough guarantee +- Leon: So testing is what's next. Testing on the screen interface would be useful. +- Alex: This is all on github? +- Leon: Yes, you can use this right now +- Leon: And this should be stable very soon. Unless something is broken with its implementation. +- Branden: Why is the function called `downcast_mut` anyways? +- Leon: Any is a super type. So we're going to something totally generic, then "down" to something specific +- Branden: So this requires static? Is that an issue? +- Leon: I think the possible use case is "what if we just wanted to add a small header to something". That could be local allocation. Is that what you're talking about? +- Branden: There were some case where passing slices made sense instead of everything +- Leon: So here we can still pass the whole thing but do operations on it that feel like slicing. The only reason I can think of why we want to pass a slice is to dynamically pull chunks of memory from a pool. But _that_ pool would be static, so we could make those static probably +- Alex: We might need some kind of dynamic allocation for network stacks +- Leon: Agreed. Avoid if possible, but we'll deal with that when we get there +- Branden: Something really great here is that the transformations are very minimal magic. Like three lines of code and it's all casts. Having the PacketBuffer not feel very magic will help get people to use it. It's encouraging +- Leon: Plan from here is to make the wrapper so I can get rid of the transmute + +## Next Meeting Plan +- Branden: We'll meet on January 11th for our next meeting. Same time +- Branden: After discussion, it sounds like we're good through January. Might reschedule time for February onward if needed. + diff --git a/doc/wg/network/notes/network-notes-2024-01-11.md b/doc/wg/network/notes/network-notes-2024-01-11.md new file mode 100644 index 0000000000..9133b73caa --- /dev/null +++ b/doc/wg/network/notes/network-notes-2024-01-11.md @@ -0,0 +1,111 @@ +# Tock Network WG Meeting Notes + +- **Date:** January 11, 2024 +- **Participants:** + - Branden Ghena + - Leon Schuermann + - Tyler Potyondy + - Alex Radovici + - Felix Mada +- **Agenda** + 1. Updates + 2. Pick New Meeting Time + 3. Buffer Management Next Steps + 4. Thread Tutorial +- **References:** + - [Thread Tutorial Planning](https://github.com/tock/book/pull/26) + + +## Updates +- Alex: Trying to get a student to use Leon's buffer management proposal to build a better console for Tock, enabling several applications to read and write at the same time. This way multiple apps can be served on the console at the same time. +- Branden: is this related to the proposal where we add a header to each message to tell which process it's from? +- Alex: exactly. +- Branden: would be great if we could also read these messages in any generic serial terminal. +- Alex: Problem -- partial messages. For now, we'll try something and see where it goes +- Branden: For sure +- Alex: Also, this is a good example for using the Buffer Management stuff +- Alex: Eventually, I'd like to see support for this merged into tockloader-rs (on a branch) + +## Pick New Meeting Time +- Branden: Sorry, for needing to move things! +- Branden: Let's move to Mondays at 10am Central. I'll move the calendar invite + +## Buffer Management Next Steps +- Leon: Exciting to hear that Alex has a student who can possibly drive this forward +- Leon: Our position from last meeting is that we have a reasonably solid understanding of a set of types that seem to be sufficient to cover some of our use cases and compile and are sound. There are no unsafe usages or transmute calls. Things are still not perfect, but it's getting there. +- Branden: So types, but not functionality right now? +- Leon: We do pass these types between layers and can even pre-pend some bytes to a buffer. But there's not anything reasonably close to a real-world use case implemented with them yet +- Alex: One question is where we add this to Tock +- Leon: Before that, I'd like to see this working in some subsystem before adding it more generally. It is just a single Rust module. It could be added as a kernel utility +- Branden: I agree with that +- Alex: So we could do that. Then we'll use it in the capsule and the chip-level UART driver +- Leon: I am confident that we can get our base use cases working with what's here. Something I'm still working on are layers that use packets that might have layers that use contiguous buffers and layers that use non-contiguous buffers. The hope here was that there could be specializations of the general implementation for each case. Pre-pending data and passing between layers when entirely contiguous or entirely non-contiguous works in my examples. Or compiles at least. +- Branden: When would we need to change between the two? For example, if we had a Thread stack where in it might we change from one type to another? +- Leon: Requiring non-contiguous buffers would be easier. So we might have an IP implementation that uses it. But it wants to support hardware in either case. So the upper level only passes one type of buffer, but then both cases should be supported when we hit hardware. +- Leon: We could fail back to only having one type of buffer. Probably contiguous because it would support all use cases +- Branden: Do we only need to translate when hitting hardware? Or higher in the stack? +- Leon: In your network stack if you have a layer somewhere that pre-pends an entirely separate non-contiguous buffer, at this point you're unable to support hardware that requires contiguous buffers. +- Branden: Sort of. +- Leon: You could copy. But it seems bad to have devices that can do DMA but we still need to copy +- Branden: Is the goal to do DMA right from the buffer management mechanism? +- Leon: We do want to do DMA directly from these buffers. We could always copy before DMA as a fallback to support any type. +- Branden: So going back, what do we do next? +- Leon: More focused time to finish/polish the implementation. And try to use it in some use case that resembles a network stack. +- Branden: So Alex and students are working on part two of that, what goes into part one? +- Leon: Thinking about functionality that the Buffer Management supports. APIs for writing data, reading data, etc. +- Branden: Should we try brainstorming APIs on this call? +- Leon: Or by just using it? Then we can clean it up afterwards +- Alex: Yeah, that's not a problem. We can add APIs as we go. We do also need documentation though. Particularly how to use things and why certain things exist. +- Leon: I'm not sure how useful rust-doc style comments will be. There could be a more lengthy blog post style about the design. +- Alex: Possibly a TRD +- Leon: Possibly. Not sure we need the formalism just yet +- Alex: Even rust-doc comments to start should be enough. Acronyms and words that have particular meaning that wouldn't be clear to a user +- Alex: How should we use this packetbuffer as-is? +- Branden: Could just copy-paste +- Alex: Okay, so we'll work on a branch with a copy of this. Then we'll work from there. Can Leon make code changes as a PR to that branch if/when necessary? +- Leon: Yes, no problem. We could also jump on a call with your student to start using and implementing this. +- Branden: I think the plan here would be to demonstrate it and work on it in your console example. Then we'll end up making a PR to tock that only adds Buffer Management stuff. Then later we'll do a PR for your console stuff using the Buffer Management stuff. This will slow down overall PR for console changes, as long as you're okay with that +- Alex: That sounds good. My overall goal is to bring Buffer Management stuff into TockWorld in June +- Branden: Agreed +- Alex: I am very pleased with where this has gone so far +- Branden: I do think the APIs will be a lot of work, but at least it's not magic type-system work +- Leon: Agreed. I was always worried that the type system stuff wouldn't work at all + + +## Thread Tutorial +- https://github.com/tock/book/pull/26 +- Tyler: We are about 100% committed to the tutorial at CPS week. We'll confirm in the next few weeks or so for sure. +- Tyler: For today, it would be good for everyone to look at the Tock Book PR about this and provide comments. +- Branden: From a high level, the goal is to 1) learn to use Tock and load applications, 2) write an application to act as a Thread child and connect to an existing network, and 3) do some application while connected to transfers data across the network and demonstrates reliability +- Tyler: One other comment is that Leon and I have been working on getting the OpenThread build system to work as a library for Tock. We've been meeting somewhat regularly and making good progress so far. Hopefully soon we'll have OpenThread working in Tock. +- Leon: I've really just been the rubber duck here having Tyler bouncing ideas off of me +- Alex: How do you deal with Tock asynchrony? OpenThread is synchronous, right? +- Leon: With processes asynchrony wouldn't be an issue. That's true for encapsulated functions too. They're sort of a hybrid of a kernel and userspace thing. The key that I've seen from OpenThread is that it doesn't require us to run one single blocking function. You can call individual OpenThread functions on reception of packets, so we're only ever making a call for as long as it takes to process a single packet. So really we'd be using OpenThread with Tyler's 15.4 implementation. +- Branden: OpenThread has to send too, right? Not just process incoming packets +- Leon: You send on users wanting to send packets, or on timer ticks. So those are events for triggering it. +- Alex: But when you send a packet, you need to know if it succeeds or fails, and that's asynchronous in Tock. +- Leon: OpenThread invokes a callback which is responsible for handling packets. So in the callback, we'd pause execution, switch to the kernel, do the work, and not switch back until we have a result +- Alex: Okay, that's exactly what I was interested in. You can stop it and resume it later. That's great. So this is going to exist publicly soon and open-source? +- Leon: Yes. Soon. And it will be entirely open-source +- Alex: Can you point me to the code? OxidOS has a huge interest in this +- Leon: For specifically OpenThread, I talked to some people about whether it would be reasonable to execute OpenThread in this hybrid mode. The answer is that we'll find out as we go. We're hoping to find some way to get some basic stuff working with the least effort. And if that happens to be a userspace application, we'll do that. The reason why I think my system could be promising is that it doesn't come with as much cruft as building userspace stuff does with the libtock-c build system. And it might just be easier to call functions rather than have a lot of callbacks and allow system calls. So we are trying my system, but will fall back on a userspace implementation +- Tyler: Right. There is a huge asterisk still on whether the system will entirely work. We believe it will work, but aren't entirely sure yet. +- Leon: Currently the exploration into the CMake build system will benefit both of these approaches at once. We just need some ELF file, whether it runs in the kernel or userspace. +- Tyler: A question: is it possible to invoke CMake within the Makefile? So we'd have some third-part directory and we could invoke it. +- Branden: I don't know if there's a hook to build an external library. Existing stuff expects to be pre-built and just grab the ELF +- Leon: There is actually a hook for an external Makefile for your library! You can use it, but only if it exports the right Make variables. So I'm worried about integrating CMake into a Make build system. This is kind of exotic. +- Tyler: CMake essentially creates a bunch of makefiles, though right? +- Leon: That is accurate +- Tyler: One thought I was having is what if we invoke CMake on OpenThread. Then we use the Make system to call those Makefiles? +- Leon: I really think that CMake just uses Make as effectively an executor as a few build steps. I really don't think that the Makefiles generated are suitable for being imported by anything else. Ultimately I think for now that what Branden suggested is reasonable. If we can get an ELF file somehow, we can plug it in somehow. The later would could think about how to integrate this upstream. +- Tyler: So that ELF file that you're talking about, we would need to specify for CMake to create an ELF file with the right compiler arguments? +- Leon: I think so? But I'm anxious about the compiler arguments. We could run in verbose mode to see compiler arguments from libtock-c +- Tyler: We'll have to think about how to "overwrite" the OpenThread compiler arguments with our own +- Leon: Plus the scary thing is that OpenThread itself has libraries like mbed-tls. So we have to compile those with the right arguments too +- Tyler: We could have a meeting with someone about the libtock-c build system +- Branden: The knowledge is split across myself, Brad, and Pat. I think Brad touched it most recently, but I was the one who originally added the double-dollar-sign stuff to it +- Leon: What is the double-dollar-sign stuff? +- Branden: It's multiple replacement steps for the variables. First we do one replacement, then later we do a second round of replacement based on that first one. Essentially, it's creating sets of rules for specific architectures +- Leon: Oh, it's meta-programming +- Tyler: Another approach was to try to make a new build system for OpenThread, but I hit a dead end there and moved to this new approach. +- diff --git a/doc/wg/network/notes/network-notes-2024-01-22.md b/doc/wg/network/notes/network-notes-2024-01-22.md new file mode 100644 index 0000000000..6184261390 --- /dev/null +++ b/doc/wg/network/notes/network-notes-2024-01-22.md @@ -0,0 +1,74 @@ +# Tock Network WG Meeting Notes + +- **Date:** January 22, 2024 +- **Participants:** + - Alex Radovici + - Tyler Potyondy + - Felix Mada + - Leon Schuermann +- **Agenda**: + - 1. Updates + +## OpenThread Update + +- Tyler: Been working on getting the OpenThread port working. Leon + helped with using the CMake build system to generate the required + static libraries. That is working now. Matched the libtock-c + compiler flags in their build system. Leon has also been working on + the Encapsulated Functions integration. In libtock-c we simply link + in the static library. + + Now encountering an error: OpenThread creates a quite large globally + defined array. During the initialization phase, when it's allocating + that memory, the program faults. Alex mentioned that this may be an + issue with the size allocated to the app. Gave it all the memory + that the nRF board has. Anyone has an idea? +- Leon: Had same issue with LwIP. Did pretty much the same debugging + steps, happy to do some rubber-duck debugging. +- Alex: Process loading debugging. +- Alex: Other solution: wait for button press and use the procss + console to get process placement information. +- Leon: Panic message also shows MPU cofiguration. +- Tyler: Been looking at that message. Tried to increase the + application heap (40kB), should be amply big for this. +- Leon: May also want to look at OpenThread's configuration around + heap allocation. Ability to wire up an external allocator. It may + try to allocate memory at an address that isn't backed by any active + MPU region. +- Alex: You said something about BSS? That should be unrelated. +- Tyler: My understanding -- data section is for initialized global + variables, BSS is for initialized to zero. When I get the panic + message, can I infer the location of the BSS section from that? +- Leon: An app in Tock always has two memory regions allowed in the + MPU: an execute-in-place (XIP) flash, and a section in main + memory. When the application is first started, it runs an + initialization routine defined in `crt0.S`. It's passed information + by the kernel that tells it where its various memory regions are + located, and where to find its binary. Uses this information to, for + instance, zero out the BSS section. +- Leon: Another interesting debugging utility could be the "Low Level + Debug" driver. Provides some primitive syscalls to convey + information to the kernel and then print on the console. +- Leon: The panic message itself only shows us the regions that are + accessible, not where the application places the various memory + sections. +- Alex: Does it fail within the `crt0.S` file? +- Tyler: Not sure. +- Alex: You can add system call tracing to see whether the app does + memops. +- Tyler: Can see in the panic message that there are memops occurring. +- Alex: The first few memops are occuring immediately on application + startup. Those should work. Then comes re-location, does it fail in + that step? Can also try to build a non-relocatable binary. +- Leon: Most productive way forward seems to do a debugging + session. Let's sync up asynchronously. + +## Console Multiplexer + +- Alex: Amalia has started on the console multiplexer and got some + initial results. Leon, did you push the buffer management code + somewhere? +- Leon: Still have some local, work in progress changes. Looking + forward to sync up synchronously. +- Alex: Some time next week? +- Leon: This or next week. diff --git a/doc/wg/network/notes/network-notes-2024-02-05.md b/doc/wg/network/notes/network-notes-2024-02-05.md new file mode 100644 index 0000000000..3f8e4f1cf9 --- /dev/null +++ b/doc/wg/network/notes/network-notes-2024-02-05.md @@ -0,0 +1,20 @@ +# Tock Network WG Meeting Notes + +- **Date:** February 05, 2024 +- **Participants:** + - Branden Ghena + - Leon Schuermann +- **Agenda** + 1. Quick check-in on status +- **References:** + - [3833](https://github.com/tock/tock/issues/3833) + + +## OpenThread +- Branden: Tyler posted https://github.com/tock/tock/issues/3833 about OpenThread progress +- Leon: Yes. I think the libtock-c route is the major thrust at this point. Likely to work faster. Still working on encapsulated functions from a research prospective, but it's going to take a while +- Leon: As a backup, we could just link the C code directly into the kernel if timing stuff really didn't work. + +## Buffer Management +- Leon: I pushed buffer management stuff and need to get in contact with Alex's student. Next steps really need a prototype example. + diff --git a/doc/wg/network/notes/network-notes-2024-02-19.md b/doc/wg/network/notes/network-notes-2024-02-19.md new file mode 100644 index 0000000000..4a1548a7c4 --- /dev/null +++ b/doc/wg/network/notes/network-notes-2024-02-19.md @@ -0,0 +1,99 @@ +# Tock Network WG Meeting Notes + +- **Date:** February 19, 2024 +- **Participants:** + - Branden Ghena + - Tyler Potyondy + - Leon Schuermann + - Felix Mada + - Alex Radovici +- **Agenda** + 1. Updates + 2. Thread PRs + 3. Tutorial Logistics +- **References:** + - [3851](https://github.com/tock/tock/pull/3851) + - [3859](https://github.com/tock/tock/pull/3859) + - [Tock Book 26](https://github.com/tock/book/pull/26) + + +## Updates +- Tyler: We should talk logistics for CSP-IoT tutorial with Alex +- Leon: I made some pretty substantial progress on the encapsulated process framework. We had to call C functions with the right ABI/signature and translate this though our framework. At this point, I'm reasonably confident that I have a generic solution to that which can be automated elegantly. I'm still missing the last 10% or so, but it's increasingly promising now. Good fallback for Thread if we have libtock-C timing issues. + + +## Thread Status & PRs +- Tyler: State of the world, good progress on the libtock-c front. Everything compiles. Build system is pretty under control at this point. The platform abstraction layer is the last thing to tackle: radio, flash, entropy, and alarm. Alarm is mostly finished and works. Entropy is working. Radio is the most tricky and I've made some PRs for it. Flash has two UCSD undergrads working on it. I have a simple representation of Flash in RAM for now. Turns out that without the Flash working, OpenThread does some weird stuff. +- Branden: Is it weird without the Flash stuff saved in RAM? +- Tyler: It works fine with a RAM representation. The goal is to remove all "asterisk, it works except" disclaimers for now. I think that's a good goal that's achievable. Background: OpenThread needs to save channel and other parameters for it to work, and those really ought to be non-volatile so it stays in the network on reboot. +- Leon: A warning, app-signing plus writing to flash is a bad combo. It turns out writing to your own flash breaks your signature. That's using the AppFlash driver. We could avoid it altogether, or we could try to fix it. But we can't have both right now. +- Tyler: The radio stuff is getting close. From libtock-c sending is working. OpenThread's internal libraries are working too. The function calls roughly set up configuration for the network, then thread start. That starts sending parent requests. All of those are correctly encrypted. They send when they should with correct backoff. So sending is working (once the PRs get merged). Receiving is still in progress. +- Tyler: One reception concern, OpenThread wants a timestamp for when a packet was on the radio. That might require some heavy rework. But I've been looking at how the nRF boards implement this abstractions. They essentially query the time function when the receive callback is called. So we could just do that too. I think that since the ACK happens right away, other stuff is pretty lax on timing concerns. Actually, OpenThread lets you choose how much you're handling timing versus how much the board implementation is. +- Leon: This sounds very similar to the work I needed to do to get IEEE1588 working with timestamps for Ethernet. I did this as a research thing, but one of those versions just had me read the timestamp register of the chip in the radio driver and pass that up to userspace. That gave me a fairly accurate view. That's probably something we could loop through the interfaces for Thread too if necessary. The nice thing about doing it in the Ethernet MAC is that you can read the timer register without going through layers of abstraction. +- Tyler: That's useful. That could be a good abstraction. +- Branden: Or the easy take is to just plop on a timestamp in userspace when you get it. +- Tyler: Yeah, that'll be step one. I think the OpenThread library isn't as time sensitive since it's pushing stuff off to the board. +- Felix: A question, how do you push the timestamp up the stack for Ethernet? +- Leon: In the upcall path, we have an argument, a 128-bit integer that's wrapped in some type about where it was gathered. That's even too complicated for this case, maybe. We might literally just include an integer in the upcall in the interface. +- Felix: Linux or FreeBSD does something similar. But the push the timestamp through the error stack in a weird way. +- Leon: I did look at the Linux implementation. I couldn't figure out what they were doing even after a close look. They have error handlers on file descriptors to push stuff into userspace because of API restrictions, I think. Tock is much simpler, since we can just change interfaces. +- Tyler: Another thing, OpenThread can do encryption in software in userspace (mbedtls), or it can push it down to the board hardware to do. This is a question for everyone, but my gut feeling is that although I've used our crypto implementation for AES128-CCM and I'm pretty confident it's working, I'm even more confident that the mbedtls crypto will be better than ours. So I'm in favor of having OpenThread do all of the packet creation and encryption, and then just sending the fully-formed packet down to userspace. +- Leon: My thoughts, I just spent a lot of time on system call overheads. We do have high overheads for the round-trips for using crypto. Even in the kernel we do pretty expensive dispatch to drivers/hardware. And we have to copy userspace buffers into kernel buffers for DMA. So the question is a tradeoff of the code size for userland apps and the timing overhead of encrypting the payload. Because, I think, Thread only encrypts management traffic which is pretty small. I think it's probably better to just stay in userspace. +- Tyler: Encryption is called as part of sending. So it's just one syscall, but the capsules would connect to encryption mechanisms. So when you send you tell it what encryption you want. +- Leon: I didn't realize that. So encryption is on the path to the radio +- Tyler: The packet will be fully encrypted. Code size is the biggest tradeoff? +- Branden: Probably a timing tradeoff as well. It might not matter in the end. If we just make a decision and the timing works, then great! +- Tyler: The fewer things Tock needs to do, the better (in terms of framing, forming packet, etc.). OpenThread does expect to form and frame the packets. I don't know if there's a way to disable the MLE encryption, I think it always does that itself. It's the link-layer encryption that you could handle yourself or have it do. +- Tyler: https://github.com/tock/tock/pull/3851 +- Tyler: current way you send 15.4 packet is specifying: + - payload + - dest addr + - security configuration + Capsule takes this, kicks off sending. If we want to implement encryption we need to decompose the constructed packet, pull the security config out, encrypting, etc. So what this PR does is provides a way for there to be a "raw/direct" send functionality. So there's another path through the 15.4 capsule that just sends the packet as-is without changing anything. +- Tyler: It would be useful for people to take a look at the PR and give any comments. +- Leon: I think this is great. It's always useful to keep these raw interfaces around, even after better things exist, for testing and whatnot. Ethernet has a raw interface like this. +- Tyler: Other PRs (libtock-c and tock getProcessBuffer API documentation) related to this. +- Branden: Thank you for opening the latter PR. Even if it's not incorrect, there's still a misconception and we should improve on the documentation. +- Tyler: Yeah, I'm always a little hesitant to open things like that to see if I just misunderstand. I think it would make sense to add some comments about the guarantees here. +- Leon: Yeah, you're spot on. We should definitely add this to the documentation. +- Tyler: Seemed weird that you could get a buffer that you have never shared. +- Branden: Are these the only PRs that we need to pay attention to right now? Brad's PR removing `dyn` is open too https://github.com/tock/tock/pull/3859 +- Leon: It really shouldn't change anything about composition of capsules. As long as you don't care about swapping out interfaces at runtime, this is a net improvement. +- Tyler: Other major TODO: switching channels. We don't have this implemented. Wrote a draft in September, still need to finish it. What I'm thinking current is that the current method for setting channel is a constant in the main.rs file for Channel Number. That's passed in as the 15.4 driver is created and can't be changed. Adding the ability to change channels isn't that hard, what's harder is controlling who can change the channel and when. For example, if you're listening for 15.4 packets you sure expect the channel not to change. +- Tyler: My idea: trivial kind of "lock" on the channel (e.g., through TakeCell). You'd say "I now have control over changing channels", and then other apps are prevented from that. +- Branden: Channel control would be a mechanism that you grab in userspace, at init-time. +- Tyler: Not necessarily. I think I would have Thread grab at start and just never release for now. I think there's a way you could grab and release when you want. +- Leon: This is exactly what we do for current non-virtualized capsules. Be careful to make sure this still works if an app has died. The "lock" needs to be freed +- Tyler: Can you share an example of this with me so I can follow it? +- Leon: Yes +- Leon: Example of a non-virtualized capsule that allows a process to take ownership: https://github.com/tock/tock/blob/906bb4fb237531d3eb21b86857bf30e5d5340743/capsules/extra/src/lps25hb.rs#L346 +- Branden: I think your initial design sounds right for now. Long-term a virtualizer would base the decision about channel changing on what the state of the radio is (no changing during reception). And you could have multiple transmitters changing channels, as long as they have some way of specifying which channel, with the radio changing back and forth. +- Tyler: We'll have to think about the actual radio driver implementation. Even with no one requesting it, it stays in the receiving state. +- Branden: You'd need to implement a channel search mechanism to find a network on startup. +- Tyler: OpenTread does that -- just need to give it the ability to set the channel. Been pleasantly surprised with how little it assumes you'd do yourself. For example, the alarm never sets multiple alarms at once and virtualises multiple alarms for you. It really does expect to have bare on-metal functions that set registers and have no functionality. +- Branden: Believe OpenThread to be a fairly high-quality artifact, and this confirms that. + + +## Tutorial Logistics +- Leon: How much time will we have for the tutorial? +- Tyler: Half-day tutorial session. Unclear how long that is (3-5 hours?) +- Leon: Okay, that'll have a big impact on whether we do two things or one +- Tyler: We should definitely consider what our tutorial plan is and make a vague plan. +- Tyler: For Alex, we wanted to touch base on students coming to the tutorial session, and see if things are coming along. +- Alex: Still working through paperwork. TockWorld is easy and has five people coming. The tutorial has more work in progress. +- Leon: One of Amit's other students is starting to use Tock and hacking on kernel things. He may also join the tutorial session in Hong Kong. +- Alex: So the next step is having a meeting of everyone to start working on Thread. We'll need to get some boards on hand to play with it. +- Tyler: Need the nRF52840DK for that. Several of them to make a network +- Alex: Send me information on what to buy +- Tyler: Right now, my energy is just on the libtock-c port of OpenThread. Don't want to do too much of the tutorial until something works. The next step is to come up with a draft of the tutorial plan. I'll try to have a rough sketch of it for the next Network WG call. +- Tyler: In the Tock Book repo, there's an open PR with some discussion https://github.com/tock/book/pull/26 to look through for now +- Tyler: Once we have a plan, we can divide up tasks on writing and testing and whatnot. Ball needs to start rolling on that soon, which requires some starting on my end +- Tyler: It'd be good to get materials together early on, such that participants can download ahead of time. +- Leon: Small update on my side. I heard from a undergrad working on the existing book tutorials and was going to discuss with them some issues they had. They'll be around to test out Thread stuff too +- Branden: I'm also teaching a wireless class this spring and will have people on-hand who know how Thread works but not how Tock works. So they could test too if needed. +- Leon: Will ask Amit to buy some more nRFs. We should make sure that even if something happens, we should be able to support at least most of the people with boards. +- Alex: Any import restrictions in Hong Kong? Concerns about how long that could take if they're concerned. You could be stuck there indefinitely if they get concerned. +- Branden: Should be an action item: talk to people who have done something like this? -> Tyler, Leon(?) +- Alex: We should try to ship them there or buy them there directly. Maybe with the conference organizers. +- Tyler: Reach out to organizers? +- Branden: Yes! If they know of solutions, problem solved! diff --git a/doc/wg/network/notes/network-notes-2024-03-04.md b/doc/wg/network/notes/network-notes-2024-03-04.md new file mode 100644 index 0000000000..becef46e54 --- /dev/null +++ b/doc/wg/network/notes/network-notes-2024-03-04.md @@ -0,0 +1,78 @@ +# Tock Network WG Meeting Notes + +- **Date:** March 04, 2024 +- **Participants:** + - Alex Radovici + - Tyler Potyondy + - Branden Ghena + - Leon Schuermann + - Felix Mada +- **Agenda** + 1. Updates + 2. OpenThread + 3. Buffer Management + 4. Tock Ethernet +- **References:** + - [2625](https://github.com/tock/tock/pull/2625) + + +## Updates +### WiFi HIL PR +- https://github.com/tock/tock/pull/2625 +- Alex: I'm looking for someone who wants to implement WiFi for the RPi PicoW, which is the Pico plus a infineon chip that does WiFi. There's a Rust driver for that WiFi already that's asynchronous!! Our goal is to port that over to Tock at some point. +- Alex: One downside: we do need to load binary firmware onto the infineon chip too. So the binary will be data in Tock +- Leon: This is okay actually. OpenTitan has a firmware blob for the big number accelerator, and we even have the infrastructure to parse binaries from the TBF +- Leon: I also use a similar method for encapsulated functions +- Alex: Motivation, we have a class that uses this board but can't use Tock because we need to connect to WiFi +### PR Assignment +- Leon: Should PRs with the network WG tag be automatically assigned to someone on the Network Working Group instead of Core? +- Branden: I don't think it's necessary right now. Few PRs and we've been attentive to them + + +## OpenThread +- Tyler: Good progress! OpenThread stack works right now: fully joins a Thread network and remains attached with child update requests. The wireshark trace for OpenThread on Tock matches OpenThread on baremetal Nordic boards. +- Tyler: As part of that, a major fix and weird bug: in 6lowpan with fragmentation packets. With 15.4 the max packet size is 127 bytes. 6lowpan provides a way to fragment across packets and recombine. As part of that, the packets send in quick succession. The current design for receiving: the user process provides a buffer with 129 bytes of space. The kernel when receive is called transfers the buffer into the user buffer and schedules a callback. But the kernel maintains control of the buffer until the upcall has been handled. So what was happening is that the second packet arrived and overwrote the first packet. So the first one wasn't received and all of the packets were dropped. The solution is a larger buffer for queuing packets. So I made a way for the user process to provide a ring buffer instead of just one packet. That's not too much of a change in all honesty, but expect the PR today. +- Leon: This is a very familiar problem to me. The TAP driver on tock-ethernet runs into this problem. We can't be sure when userspace will be scheduled and for how long and whether it processed everything. The design of Tock's upcalls right now and capsules not knowing if they've been received is intentional, but there's a broader need for a more efficient ring buffer data structure. Way back when we redesigned the userspace, either the kernel or userspace should have sole ownership of the data when allowed. So a "shared" ring buffer is likely non-compliant. It needs to be unallowed before modifying it. +- Tyler: I very carefully handle that actually, and think I'm in compliance +- Leon: That's great. I think in the long run we'll want to have an explicit system call for transferring packets through a lock-read data structure. +- Tyler: Right now it's a pretty rudimentary data structure. The userspace unallows the buffer before making changes and allows the buffer back after copying data over to a separate userspace location. Long term, do you think we should implement a bigger fix now? +- Leon: Wouldn't want to hold anything up on this. Your PR is probably a good first step. What I want is a general solution that's got stability guarantees. So I wouldn't want to blindly promote a solution for general use +- Branden: What's left for OpenThread development then? +- Tyler: "Works". Only on channel 26 right now. Getting channel switching implemented keeps falling on the priority list, getting sending / receiving to work has been challenging. Need to be able to switch channels, clean up PR and submit it. There is some hand-waving around signal-strength indication (currently just hard-code RSSI to -50 and link-quality indicator). This information doesn't make it up to the 15.4 part of the stack. Thread works by having a child issues a parent request, and chooses best parent based on the RSSI (so important for router selection). In my opinion, the most challenging part is getting the radio packets correctly parsed. It made me really happy to see that Thread works generally. + + Current architecture: in a loop: call "do thread work", then yield. When a packet arrives or alarm fires, there's a delay before yielding again and I didn't know if that would be okay. + + Next steps: let device running, see whether it crashes or falls off the network. +### Remaining OpenThread Work +- Branden: What's left for OT? Channel switching, RSSI, flash implementation, ...? +- Tyler: Progress underway for the flash stuff by two students. +- Branden: Of those three, probably least important. The first two are probably useful for the tutorial. +- Tyler: Tentative timeline: have most of this upstream in the next two weeks. +### Tutorial Planning +- Tyler: One more thing on the demo front, Brad sent in the CPS-IoTWeek channel a message demoing a board with screen. However, we didn't find a board that doesn't have a JTAG built in. We looked for an Arudino shield with a screen, but there wasn't anything good. I may quickly try to make a screen shield myself to support the tutorial. Would be neat. +### 15.4 Design +- Tyler: 15.4 related design question. We have a send raw syscall now. We also need a raw receive at some point. We want OpenThread to handle the decryption. So we should let the user process do this instead of the kernel. So we need a way to handle a non-decrypted packet to the user process. Right now we're just delivering all packets so OpenThread works. +- Branden: Is it some packets are decrypted by the kernel and some by userspace or all either userspace or kernel? All in either direction seems easy. Some is harder. +- Tyler: Hmm. I think I just need to make a PR with more context. The change itself is minor but there's a design decision. +- Tyler: I will make a PR for this soon. + + + +## Buffer Management +- Leon: Some interesting developments. Chatted with Alex's student Amalia twice. Talked about motivations and then went into the Tock UART stack where this would be deployed first. She's been refactoring some of the HILs to use to opaque packet-buffer type. I've been working on transforming the playground example into something we can really use throughout the kernel. Plans to meet again and keep pushing on the UART subsystem. +- Leon: For this first iteration while working on it, I realized we're likely better off removing the complexity of non-contiguous buffers to start with. We are confident that they could work in conjunction with everything else, so we could reincorporate them later. But for now, it's really adding a lot of complexity when working on the basic implementation. +- Leon: The current implementation has headroom and tailroom implemented for networking features. And documentation! +- Leon: I'll push my stuff to a branch relatively soon +- Branden: We're doing this for UART? I though the first target was the screen? +- Leon: We thought a better goal was the UART multiplexer. It's a sweet spot because we have a lot of existing infrastructure that could use it, and what we do is pretty simple: prepend four bytes to the buffer that indicate if it's a new message, an application ID, and 16-bits of length of the current stream. +- Alex: The console also needs headers too. The console multiplexes applications. The UART multiplexes data. There are three virtual UARTs: one for console, one for debug, one for processConsole. This is messy, but is good because there are multiple headers to append, which makes it a good buffer management example +- Leon: The good thing is that UART stuff isn't actually changing. So we can just work on stuff above it. + + +## Tock Ethernet +- Branden: Just a check-in about whether anything needs to be done here. +- Leon: Should really open a PR at some point. It does need a ring buffer to userspace solution, which Tyler's stuff could actually be useful for. The one single reason not to merge it today is that we'd have to increase the memory use for the stack frame on every board by a large amount. +- Leon: No one's using it right now, so it's low priority. If someone needed Ethernet support, it would increase in priority. +- Alex: Would be great to have it, but not waiting on it right now. + + diff --git a/doc/wg/network/notes/network-notes-2024-03-18.md b/doc/wg/network/notes/network-notes-2024-03-18.md new file mode 100644 index 0000000000..11e9b73905 --- /dev/null +++ b/doc/wg/network/notes/network-notes-2024-03-18.md @@ -0,0 +1,44 @@ +# Tock Network WG Meeting Notes + +- **Date:** March 18, 2024 +- **Participants:** + - Alex Radovici + - Tyler Potyondy + - Branden Ghena + - Amit Levy +- **Agenda** + 1. Updates + 2. OpenThread +- **References:** + - [3923](https://github.com/tock/tock/issues/3923) + + +## Updates +- Alex: Leon and Amalia working on buffer management. Writing and re-writing code. Constant expressions would be really really helpful here. Overall, we're still figuring out how to best use the buffer management system, and also how to design it. It is progressing! + + +## Tutorial +- Tyler: Has there been movement on tutorial involvement? +- Alex: It's moving forward with information soon. +- Tyler: I need names by March 30th for the conference + + +## OpenThread +### Channel switching design +- Tyler: For channel switching, are we okay to just allow any process to change the channel? +- Branden: If you loaded multiple processes, 15.4 stuff would break anyways, right? +- Tyler: Likely? Not entirely sure, but I wouldn't trust it +- Branden: Then having just one process able to touch the radio seems fine. This could be handled maturely with syscall filtering on a per-app basis. +### Other stuff +- Tyler: Channel switching and RSSI PRs will be coming soon. +- Tyler: There's also a 6lowpan bug that I'm working on. https://github.com/tock/tock/issues/3923 +- Tyler: Libtock-c PR coming soon as well. There's a big change to the build system to support OpenThread. Currently it fails CI and it's unclear why. +- Tyler: All other PRs are taken care of right now +### Tutorial planning +- Tyler: Discussions will be coming in the Slack channel. Not yet at the point where people should test anything, but I expect that this will come at some point. +### Future 15.4 changes +- Tyler: I do think there are some capsule changes we should make. Encryption, for example, the process seems strange right now. There are more stream-lined ways to do this. And I think we could focus on doing stuff in userland, which I think researchers would want. +- Tyler: It would be great for the capsule to have some features, and then userland to be able to make choices and implementations that can modify how things actually work. +- Tyler: 15.4 has gone through several implementations already. I think some of the work just needs to get rid of deprecated functionality too. Streamlining for usability and readability. +- Branden: I do think that a focus on users is good. Who are they and what do they actually want. + diff --git a/doc/wg/network/notes/network-notes-2024-04-01.md b/doc/wg/network/notes/network-notes-2024-04-01.md new file mode 100644 index 0000000000..01bcced78a --- /dev/null +++ b/doc/wg/network/notes/network-notes-2024-04-01.md @@ -0,0 +1,115 @@ +# Tock Network WG Meeting Notes + +- **Date:** April 01, 2024 +- **Participants:** + - Alex Radovici + - Amalia Simion + - Branden Ghena + - Leon Schuermann + - Tyler Potyondy +- **Agenda** + 1. Updates + 2. PacketBuffer Proof-of-Concept + 3. OpenThread Update +- **References:** + - [PacketBuffer Integration Work](https://github.com/CAmi307/tock/tree/dev/pb-integration) + - [PacketBuffer Implementation](https://github.com/CAmi307/tock/blob/dev/pb-integration/kernel/src/utilities/packet_buffer.rs) + - [#3933](https://github.com/tock/tock/pull/3933) + - [#3940](https://github.com/tock/tock/pull/3940) + - [Libtock-C #380](https://github.com/tock/libtock-c/pull/380) + + +## Updates +### Amalia Introduction +- Alex: Introduction, Amalia is doing the packet buffer implementation with Leon. Working on project as part of a graduation project. +### Encapsulated Functions +- Leon: Working on encapsulated functions a bunch. It seems to be pretty-much working at this point. One example we may want to tackle is porting some C network stack using that framework. +- Branden: Could do OpenThread so you get a comparison +- Leon: OpenThread is VERY complex. Probably too big of a thing to start with +- Alex: I'm very interested in porting the CAN stack with this. If you could give us access. +- Leon: There have been a lot of changes to it previously, so it wasn't made public. But it's pretty decently stable at this point. +- Alex: Well we could get started on CAN stuff on our own. Very interested. We could do the effort and send you questions if needed. No tutorial required or anything +### OpenThread +- Tyler: Some outstanding PRs in libtock-c and the kernel itself. Would be good to add to agenda today. + + +## PacketBuffer Proof-of-Concept +- Leon: Amalia and I have been meeting for last several weeks. Actually porting the UART subsystem and console driver over to a PacketBuffer implementation. +- Leon: Porting started pretty smoothly, but we realized there were some conceptual issues. One thing that was hard in Tock was the idea of splitting a buffer over multiple separate buffers, effectively a linked-list of buffers. So we removed that for now. We still believe it could be supported with the current design, but removing it simplified things. So we have a simple linear buffer that can be annotated with headroom or tailroom so things can be pre-pended or appended respectively. The sizes of head/tail room are part of the types, so that there is enough space should be guaranteed at compile-time. +- Leon: Amalia has ported most of the UART HIL transmit path and callback path, changing types from taking a static mut buffer to taking a PacketBuffer. Right now we always annotated zero bytes for headroom or tailroom. +- Leon: One additional weakness in the original design. The way we composed buffers was with a generic type wrapper that encoded the head/tail room. To be compatible with different implementations, we might need to reserve more head/tail room than is required for a given device. Those annotations are actually the MINIMUM amount available, and the underlying type could have more space. So both the wrapper and the underlying buffers store head/tail room numbers, which could differ, but the underlying buffer will always be greater than or equal to the wrapper. +- Leon: So the problem: we store this in a struct and the struct has a reference to the actual buffer. The issue was that the buffer was one memory allocation and the struct was a second memory allocation. This changed the premise of how we construct the types in Tock. So you needed a TakeCell for the buffer and a second TakeCell for the struct. +- Branden: The struct holds a reference and not the actual buffer? +- Leon: Yes, because the size of the actual buffer is unknown. We're using traits +- Leon: So, what we do is store the struct values in a fixed offset in the beginning of the slice, making that part inaccessible to users. These are the actual head/tail room, in addition to the wrapper. So that means we have one allocation of space, just with a little extra space. +- Leon: The most important bit here is that what we have now actually works. +- Branden: This is similar to the C idea of having a struct with a zero-length array at the end. And then just increase the allocation size for array data. But rust isn't okay with that. +- Leon: Yeah, that's right. +- Leon: There is a runtime penalty for using this design. We're constructing this type from a Rust byte slice, which has no alignment rules. So when we store a usize at offset zero, that might not be 4-byte aligned. So our code has to manually combine the bytes back together into a 4-byte type. This is effectively an unaligned read, which slows down runtime a little, but seems probably okay. We could probably do some things to force alignment if it was required. +- Leon: The nice thing about the current design: the slice-based approach is just one implementation, and we could have multiple implementations that all worked simultaneously and wouldn't affect downstream users. The interface remains stable +- Tyler: A few questions. Is the code for this in a branch somewhere? +- Leon: Yes, a branch in Amalia's code. https://github.com/CAmi307/tock/tree/dev/pb-integration +- Tyler: Are you just trying to iron out the type system details now, or is there a mockup you're working with? +- Leon: Amalia has been working with a proper partial-integration of this into Tock. This is going to be an iterative design, where we should go back to the interface and add convenience methods wherever useful. +- Branden: If the head/tail room was non-zero, are there methods to work with that? +- Leon: Yes? I believe they all exist, although they likely aren't tested at all. Here's a link to the actual packet-buffer file, with the interface right at the top: https://github.com/CAmi307/tock/blob/dev/pb-integration/kernel/src/utilities/packet_buffer.rs +- Branden: Okay, I think this is a "complete" type system interface, but likely not complete functionality interface +- Leon: Yes. That will need work, but isn't overly concerning. +- Leon: We also need to reason about safety. I'm pretty confident it's sound, but its worth an overview +- Leon: Finally, I believe Amalia has this _actually working_ on the UART code-path now, where printing still works. It's not giving any advantages yet, but it's not breaking anything. +- Branden: So hypothetically we're at a point where we could get metrics, like size increase or latency perhaps +- Amalia: I finished the implementation with zero head room and tailroom and it seems to be working now. I didn't find any issues yet. No overly large latency. No runtime errors. Right now I'm trying to get the point where we would prepend something in the buffer, and I'm running into weird compilation issues. So that's the current status. +- Leon: I'm very excited to continue work on this +- Branden: How hard was the effort of changing to this? Was it mechanical or a big redesign, or somewhere in the middle? +- Amalia: For me it was huge, because I was also understanding Tock at the same time. It's not quite mechanical, but there's a pretty clear flow for adding this. +- Leon: I haven't been doing the integration work myself. From looking though, there is a large buy-in cost to making the changes at all. But the changes themselves are rather mechanical. The hard part was figuring out how to transition the Tock code to this, but the diff isn't actually big. You're replacing parameter/callback types, and you have some magical incantations you need to know about for converting types. +- Branden: And having a working example would definitely help future efforts to understand what the transition would look like +- Branden: Future plans on UART side -- move forward into work on prepending things to implement console virtualization? +- Amalia: Yes, that's correct. +- Branden: To some degree, diverges from this work. Your goals will become somewhat different from Leon's goals. +- Amalia: At the same time, I'm working on an host-side application that performs filtering based on headers introduced with the packet buffer. +- Branden: This is awesome, long standing issue. +- Branden: To Leon, what's the next step as far as we're concerned for the PacketBuffer stuff? +- Leon: I do agree that Amalia and I will diverge at some point, but I don't think we're there yet. The interface we currently have is limited, and as Amalia needs things I can add them. The experience overall will guide what is needed in an interface. Thinking about the APIs for prepending and appending. So definitely still a lot of collaboration. + + +## OpenThread Update +- Tyler: Biggest thing I want to ask for is some eyes on some PRs +### PDSU Offset Fix +- https://github.com/tock/tock/pull/3933 +- Tyler: Pretty minor change to update the offset used for some metadata. +- Tyler: Amit had asked why we were even looking at the offset at this layer and why it wasn't lower in the stack. For further context: the PSDU is an artifact of the RF233 on the IMIX. It needed an extra two bytes for the SPI header when sending to the radio module. To keep the HIL consistent, we needed two extra bytes in the offset. We are taking this as an opportunity to avoid having this bubble all the way up the stack. We don't have enough expertise to definitively say whether this is right. +- Branden: Ready for some eyes? +- Tyler: Yes, ready to go! Thread network works with these +- Branden: Skimmed this PR already, was not sure about some things, but will take a look! +- Tyler: Indeed this requires some niche knowledge. +- Tyler: There's an extra two bytes at the start of the nRF and RF233 radios. It's a byte that's needed by the radio but not part of the packet. We definitely shouldn't pass that one byte up to MAC layer when receiving. +- Branden: So, whenever you send and receive a packet, there is an additional offset of two bytes? +- Tyler: Yes, on all boards. It's a part of the radio HIL. +- Branden: Does the nRF need those two bytes at the front, or does it throw them away? +- Branden: Probably what Amit and Brad are pushing back on -- why was this exposed to userspace at all? +- Tyler: Tricky -- it's there on the send side, not on the receive side. +- Tyler: There's also two bytes versus three bytes. We already have this idea that we have metadata on packets passed to userland. It was the offset to the payload and the payload length. And we just put those bytes into the PDSU section, since we had two bytes of space. So I added a third byte of metadata, which holds the MIC length for crypto stuff. +- Branden: So, this PR is only about having three bytes of metadata on receive packets? +- Tyler: Originally yes, until Amit's comment about the presence of the PDSU on the MAC layer. This seems like a mistake, because the PSDU offset is entirely radio-specific, not at all relevant for the MAC layer. As far as I can tell, it's not used for anything. +- Tyler: Comments on the PR can help understand this. Not much code that changes. If Branden can make some comments, this might unblock the discussion. +### Receive Encrypted Packets +- https://github.com/tock/tock/pull/3940 +- Tyler: There's a trait called RXClient which is used throughout the 15.4 stack. You set a receive client, which is then passed packets. We have this 6LoWPAN bug that was an indexing error: one of the PRs that did not drop packets that we couldn't decrypt, when reading the offset on those packets that we couldn't decrypt, we read garbage from the offset field. This adds two traits suggested by Brad, that split this up into a "Raw receive". +- Tyler: Brad did say this is reasonable, but did not yet approve it. +### Thread Userspace +- Branden: Is there stuff in userspace to discuss too? +- Tyler: No. The one PR that exists https://github.com/tock/libtock-c/pull/380 has had a lot of discussion on it, so everyone here (modulo Leon, who should do testing), should not focus on that right now. +## Thread and EUI64 +- Tyler: Design question there's this EUI64 thing, that's an identifier for a device. Nordic has this from the factory. It's common across radios for thread networks and Thread uses them to generate mac addresses and IP addresses. We need a way to pass the EUI64 to userspace. Currently, we grab it in main.rs and pass it as the address in the radio drivers. The question is where should we add a member to the struct that holds the full EUI64? It seems to me that the radio hil struct, the virtualmac, the userspace-mac, or userspace radio driver. To me, it seems that because this is radio-specific it makes the most sense to put it there. Was looking for thoughts. +- Leon: For the EUI64, is the cell type still important? +- Tyler: Yeah, I need an immutable way to store those 8 bytes. +- Leon: Immutable things can just go in the struct directly. Only mutable things need to be in cells. +- Branden: Where is the MAC address stored now? +- Tyler: A lot of places. The virtual mac plus some other places. Each client has a MAC address. +- Branden: Is our goal to present a radio that has multiple MAC addresses? +- Tyler: No for thread. It's a 15.4 6lowpan idea. And users can change MAC addresses right now, which we don't want for EUI64 +- Branden: That makes sense. For Thread, we can just present one radio, but then use higher-level abstractions like UDP ports to separate data for different applications. Each application doesn't need to pretend to be an entire radio. +- Branden: I'd make a new syscall driver to present the EUI64 information. It seems like it's outside of the radio driver's purview. +- Tyler: For imix, it grabs the serial number and calculates the address based on that. + diff --git a/doc/wg/network/notes/network-notes-2024-04-15.md b/doc/wg/network/notes/network-notes-2024-04-15.md new file mode 100644 index 0000000000..b48ab4ddc9 --- /dev/null +++ b/doc/wg/network/notes/network-notes-2024-04-15.md @@ -0,0 +1,60 @@ +# Tock Network WG Meeting Notes + +- **Date:** April 15, 2024 +- **Participants:** + - Alex Radovici + - Tyler Potyondy + - Branden Ghena + - Leon Schuermann + - Felix Mada + - Amalia Simion +- **Agenda** + 1. Updates + 2. PacketBuffer +- **References:** + - None + + +## Updates +### 15.4/Thread Status +* Tyler: 15.4 PRs have been merged. +* Tyler: Something I've been working on is setting up a small OpenThread network that will run some automated tests on whether Thread still works whenever I update things. Testing 15.4 would also be helpful. It's easy for that driver to pass CI, but fail in the real-world. Maybe eventually this could even be part of the hardware CI system. +* Branden: Sounds awesome and super valuable. Just don't let it take too much time from the tutorial. +* Tyler: Agreed. Focusing on a minimal version +### Tutorial Documentation +* Leon: Where are we in tutorial docs? +* Tyler: It's fallen a little behind. Working on that this week as a major goal. Outline and application are sketched out, we need to start writing things +* Leon: Working on SOSP deadline now, but next week I'm going to focus on this +* Tyler: Everything does seem to be working for the OpenThread port. So it's definitely time to get the tutorial stuff working +* Leon: I'll be at the tutorial, as well another PhD student from my lab. + + +## PacketBuffer +* Alex: We tried to integrate the PacketBuffer stuff, but we had to place a LOT of constant generics in the upper layers. We need to somehow pass down all the constants. Without const-generic-expressions, we're stuck doing this. I can't add one layer on top of another without knowing how much it might append +* Leon: I previously had a solution where we break layers into two parts, one to pass down buffers and one to pass up buffers. The goal was to avoid the requirement of knowing the head/tail requirements at all points. +* Alex: Please share. I'm trying to figure this out. I tried doing associated constants, but that didn't work. Macros also didn't fix it. The upper layer needs a buffer with a head/tail. Then the lower layer needs a buffer with a different head/tail. +* Leon: Each layer does need to know the head/tail room of the layers above and below it +* Alex: This isn't enough. The virtual uart device needs to take the mux which needs to take a trait of the underlying layer. So I think they have to be everywhere. +* Leon: Oh, I do think that's an issue. This is a similar issue to composing things in main.rs, which probably need type aliases to solve them, in an ugly way. I don't know that there's another way +* Alex: Const generic expressions would help. But not stabilizing anytime soon +* Alex: I was unsure whether generics are worth it for the buffer +* Leon: Generics make it hard and ugly to instantiate things, but they do allow us to be composable +* Alex: Well, just for the buffer it might not be worth, we can always use a struct with variables. +* Leon: That would be the linux kernel's skbuffer. But it wouldn't be compile-time validated. And some operations can work without run-time checks. +* Alex: I agree. I do think this is worth it. But if we ever get simple const-generic-expressions, that will make this even more valuable +* Alex: It's going to be pretty ugly to instantiate. I hope there are some type aliases or something to clean that up. +* Leon: We could pass the dynamic trait object. We might consider whether the upcall path should avoid arguments with const generics, and just takes the trait object +* Alex: The constants won't be in the type on the upcall path anyways. +* Leon: Okay, we should definitely do trait objects on the way up. And there's a helper function that does that +* Alex: Yeah, better than a string of like 6 constants. It's important for everything using this to take generics instead of trait objects though +* Leon: To summarize: we have a problem that the way we compose layers is with proper generic types, avoiding dynamic trait objects. So something like the Debug writer takes a generic type that implements the uart transmit trait, like the mux. So each layer needs to know how much headroom the layers above and below take. But that seems fine because what we could do is have all of these types represented as type aliases. On the upcall path, we would need to know how much headroom to restore on each additional layer, so the trait object would need ALL of the headroom from all of the layers. These buffers are wrapped in a type that has the generic arguments. So we could unwrap at each layer and pass the non-generic types up the stack instead. +* Alex: I'm still not sure how to deal with this if the lower UART isn't really a UART and needs another layer. With dynamic traits this gets really bad +* Leon: We should avoid those +* Alex: But on the upcall path, we need the dynamic trait, or it would be a circular reference +* Alex: Amalia did all of the work here, but it's good that we're finding issues +* Group: We discussed this problem in more depth with some quick examples. Here's the high-level takeaways: + * Most layers would need four generic constants: current head/tail room and lower head/tail room + * Some layers might needs six generic constants: upper, current, and lower + * Instantiation of these in main.rs would be ROUGH. All constants have to be listed. Would need type aliases and maybe macros to clean this up + * Overall, going to make code a little harder to read/understand, but for the value of compile-time checks + * Const generic expressions would reduce this to two (or sometimes four maybe) constants, but we can't wait on it diff --git a/doc/wg/network/notes/network-notes-2024-04-29.md b/doc/wg/network/notes/network-notes-2024-04-29.md new file mode 100644 index 0000000000..04caafcff8 --- /dev/null +++ b/doc/wg/network/notes/network-notes-2024-04-29.md @@ -0,0 +1,52 @@ +# Tock Network WG Meeting Notes + +- **Date:** April 29, 2024 +- **Participants:** + - Alex Radovici + - Amalia Simion + - Tyler Potyondy + - Branden Ghena + - Felix Mada +- **Agenda** + 1. Updates + 2. PacketBuffer Progress + 3. Thread/15.4 Check-in +- **References:** + - [PacketBuffer Console Work-in-Progress](https://github.com/CAmi307/tock/tree/6a8822afdfe19f59e22ac84aaa60a4c6a7b60e61) + - [15.4 LQI PR](https://github.com/tock/tock/pull/3972) + - [Timer Update PR](https://github.com/tock/tock/pull/3973) + - [Thread Tracking Issue](https://github.com/tock/tock/issues/3833) + - [OpenThread Libtock-C PR](https://github.com/tock/libtock-c/pull/380) + + +## Updates +- None today + + +## PacketBuffer Progress +- https://github.com/CAmi307/tock/tree/6a8822afdfe19f59e22ac84aaa60a4c6a7b60e61 +- https://github.com/tock/tock/compare/master...CAmi307:tock:dev/second-try-append-headers +- Amalia: Last time, we discussed adding constants for each layer in the struct for each layer. The ones above and below. We did that and it worked! We could append header and footer data in each layer as the packet moves. +- Amalia: In the last meeting with Leon, we decided to document things and do some testing for stability of the code. +- Amalia: After that's finished, the next big thing would be using the same mechanism for the receiver in Tock. Also some mechanism that would send the received message to the targeted process. You could imagine a user typing a message intended for one process, but not others. Some host-side application would append headers when sending that data, which Tock would decode. +- Branden: What data is being appended right now? +- Amalia: For the moment, the appended data is the process ID of the application. I'll probably need more at some point. As the kernel and applications need to be disambiguated. +- Branden: Something we had discussed when console change was first discussed. It would be nice if the data in the header/footer was semi-human-readable for people who aren't using the proper tool that can decipher it. For example, you might encode process IDs in ASCII instead of as raw numbers. Any thoughts on that? +- Amalia: No thoughts yet +- Branden: What's the plan for eventual upstreaming? +- Amalia: Definitely needs cleanup first. Probably moving some constants around, to board initialization and not in the components directly. Another thing that's missing at the moment is initialization for other boards, this is only on the Microbit so far. +- Amalia: The plan is definitely to upstream, but no particular timeline right now. + + +## Thread/15.4 Check-in +- Tyler: Two outstanding PRs. First is https://github.com/tock/tock/pull/3972 +- Tyler: #3972 is the Link-Quality Indicator which Brad and Branden commented on +- Tyler: This is really the RSSI value, which is being passed through callbacks/upcalls so upper layers can use it. The upcall previously had Pan ID, source address, and destination address. I replaced Pan ID with LQI, since it can be parsed from the packet. +- Branden: My take from the discussion is that we should remove Pan ID, source address, and destination address from the upcall. If they are all trivially parsable from the packet data, we should provide helper functions that do that parsing and not send values which could possibly conflict. Then that leaves us with room to send LQI, which isn't in the packet data at all. Along the way, we should remove all the dead code that won't be necessary anymore after the update. So, I think once you make those changes, this PR should be good to go. +- Tyler: Other PR is the timer update: https://github.com/tock/tock/pull/3973 +- Tyler: We don't need to discuss this, but I'll be updating the OpenThread logic based on this +- Tyler: This is necessary because we have a get-time-in-ms function, which wants increasing time since boot. But the ticks values wrap at 2^24 ticks for Nordic (about 7 minutes). And in the Timer interface now, we don't have a way of knowing when the wrap is going to occur. And OpenThread fails when the timer wraps back to zero. +- Tyler: The new driver and libtock-c updates should make this timer continually increasing instead. +- Branden: Reminder to update the tracking issue: https://github.com/tock/tock/issues/3833 +- Tyler: We're also ready to start reviewing the OpenThread libtock-c PR: https://github.com/tock/libtock-c/pull/380 Brad's been giving lots of help throughout + diff --git a/doc/wg/network/notes/network-notes-2024-06-10.md b/doc/wg/network/notes/network-notes-2024-06-10.md new file mode 100644 index 0000000000..f8ca89d23a --- /dev/null +++ b/doc/wg/network/notes/network-notes-2024-06-10.md @@ -0,0 +1,122 @@ +# Tock Network WG Meeting Notes + +- **Date:** June 10, 2024 +- **Participants:** + - Alex Radovici + - Tyler Potyondy + - Branden Ghena + - Leon Schuermann + - Amalia Simion +- **Agenda** + 1. Updates + 2. Tutorial + 3. TockWorld Planning + 4. PacketBuffer + 5. 15.4 Status + 6. Buffer API +- **References:** + - [4023](https://github.com/tock/tock/pull/4023) + + +## Updates +- None + + +## Tutorial +- Leon: Tutorial overview, also given in Core team. +- Tyler: In broad strokes, a success. Leon did a bunch of the writing. So there's a strong networking tutorial around now that we can use. +- Tyler: We had some bugs at the tutorial with the Thread network. The issue was due to on of the 15.4 PRs that fixed corrupted packets by dropping them. One function returned when it needed to advance the state machine first. +- Leon: We also identified some other bugs. The current re-entrant printf issues mean it's virtually impossible to build reliable userland applications right now. This has reinforced to me that if we want reliable apps, we need to fix yield-for. The only realistic way to write async things now is with callbacks that set bits, and then a big select loop in main. +- Branden: All async or sync works though right? +- Leon: Yes, but even printf is sync and gets mixed in to a lot of things. And the network stuff is async right now, so that means you never get the choice to use all sync stuff. +- Tyler: Right. I don't know how you'd receive packets with a sync operation. You don't really know how long it might be before an event occurs. You need to use async operations. +- Tyler: In practice, it's very hard to have both sync and async operations, and also very hard to not have both. +- Tyler: I'll be working on the rest of the bugs this week and test things. +- Tyler: We had an issue with the VM image freezing. Pat considered that maybe we should buy some cheap chromebooks instead of putting effort into a VM image. +- Branden: Maybe we should be working on WSL2 Tock support? +- Alex: We tried that. It just DOES NOT show the USB ports. +- Alex: We could move to probe-rs to help the toolchain work on Windows. Then people could build on WSL and load from command prompt +- Leon: Some action items from tutorials: + * Summarize the problems we had more formally + * Push on the yield-for PR with evidence that we need it + * Ensure that the tutorial still works and makes sense when fixing bugs + * Figure out what to do around VM image + + +## TockWorld +- Branden: Wanted to talk about strategy for TockWorld for this Working Group +- Leon: Okay, so let's keep thoughts on PacketBuffer reasonably brief and talk about organizational aspects. We can focus on 15.4 and tutorial developments. That way we aren't redundant on PacketBuffer +- Branden: Sounds good. And Amalia will cover PacketBuffer in her keynote, so the update from the working group will not go into PacketBuffer details + + +## PacketBuffer +- (Rewrote history a little here. The TockWorld and PacketBuffer discussions were intermingled.) +- Alex: Would like to have a plan sketched out on switching to PacketBuffer (screen). +- Leon: I like the other use cases, and I'm excited that it might be useful for several use cases. I am worried about whether that will hurt our original focus of working on IP stack +- Leon: I do think it's relatively straightforward to implement an IP stack on top of Ethernet with PacketBuffer right now. I can spend one or two days to do this before Tockworld +- Alex: Can we use the STM32 layer2 we wrote? +- Leon: We have quite a few implementations all in a branch that are all using a unified interface. So I'd like to push on that as an intermediate result for Tockworld +- Alex: So we'd port them to PacketBuffer before merging into master? +- Leon: Yes, I think it'd be more convincing if we have it working +- Branden: Idea sounds great; don't necessarily have to wait to merge PacketBuffer if it works for console. It should be its own PR still, but one use-case is enough to motivate. Don't want to block console on any pending IP-stack changes. +- Leon: Just want to make sure that we can make changes later for IP if needed +- Alex: I think we should merge stuff as-is for now, and make changes later. Rather than keep stuff all on branches. +- Leon: Amalia and I can create a PR together +- Branden: How close is the console subsystem? Would still make sense to have both at the same time. +- Amalia: It needs some cleanup and adjustments for initialization still. We also need to implement the UART for every chip supported, if I'm not mistaken. It's only done for nRF52 right now. +- Leon: Oof. I also revived a PR trying to migrate the UART HIL. And that's tricky because of how wildly different UARTs are between chips. So if we're doing that migration anyways, it's a similar effort to port towards PacketBuffer. Separately, we should think about whether it should be done together or in separate PRs. +- Alex: This is a lot of work. 1) change the HIL and 2) use the PacketBuffer. How close are we to using the new HIL? +- Leon: The new HIL exists, and I implemented a few chips in a branch. My benchmark for success is just compiling though, because I don't have hardware to test them. So we should only merge close to a release, so there's lots of testing. I think porting to PacketBuffer will be equivalent amounts of churn, but it's easier to get right. Just bytes at the bottom anyways. Changing the HIL hits the state machine. +- Alex: Porting things to PacketBuffer is something we have some students who have some time for. +- Leon: My concern is that it would be more work to do porting to PacketBuffer first, and then porting to the new HIL second. It would be less work the other way around. +- Branden: Not sure about this. We don't want to sit on PacketBuffer until vague changes like HIL changes are in. +- Leon: Yes, that's a good point. Not opposed to this. +- Branden: Should PacketBuffer just be its own PR right now? +- Alex: We'd have to do a lot of explaining on the PR, but can get feedback early. +- Leon: Okay, I'll meet with Amalia to go over the current version of the code. We'll see how much churn and cleanup, and then we'll identify the best path forward. +- Leon: Plan to get a draft PR out before Tockworld + + +## 15.4 +- Tyler: Not too much here. Brad's done a ton of work, but it's mostly shuffling around Tock kernel internals, and shouldn't affect the tutorial. + + +## Buffer API +- https://github.com/tock/tock/pull/4023 +- Alex: The problem we're facing with CAN driver is that packets come in bursts. The car often sends a bunch of packets, then sleeps. So you have this situation with a large number of packets that the application will take some time to process. So we needed to swap buffers. The application would swap the buffer and notify the driver that it's swapped the buffer out. But there's a race between swapping and saying you swapped. Packets can come in between the two. That means that the kernel can't track where it is in the buffer. So what we did was used the first bytes of the buffer to hold the offset. The kernel will update this. And the userland will clear it when setting a new buffer. +- Alex: So, I formalized this by adding functions to the process slice API to implement this with helper functions. That's the idea in a nutshell. +- Tyler: Interested to look into this and to see if it can be used for 15.4. This is nearly identical to what I implemented, but without API support. The difference with the ring buffer I'm using is that this loses packets once the buffer fills. That could create issues for OpenThread. In reality, in the off-chance that the buffer fills you'll drop a packet and the packet will get retransmitted. So I think this system would work for me. +- Branden: Can't you have both ideas together? +- Tyler: You allow a buffer, the capsule writes to the buffer, it notifies the app, but the app hasn't yielded (so didn't have a chance to swap the buffer). +- Alex: This would swap the buffer without even yielding. Depends on the app. For CAN, we checked if this was the first packet in the buffer, and then notify the app. This is a separate issue, avoiding filling up the notification queue. +- Alex: We may need to implement "reserved upcall" / idempotent upcall -- where a second upcall doesn't add anything new if one already exists +- Alex: PR will follow for this. +- Leon: Idempotent upcalls looks like the right mechanism in conjunction with this buffer structure. +- Branden: This feels like we're creating a bunch of special cases. +- Alex: Touch screen driver has the exact same issue. I only care about the "last" update. So I'm overwriting the buffer and just want to ensure that there is one upcall existing +- Tyler: So the implementation in the PR for Buffer API says we drop new information. +- Alex: The touch driver would use it a differently. We can replace whenever we receive new touch events. What's actually important here, I drop the last packet while you drop the first packets. For touch, I do want to drop the first. But both should be an option +- Leon: Okay, so it's not really necessary for Touch to use this Buffer API. Both network and touch just want idempotent upcalls. These are two separate concerns. +- Leon: Secondly, on these policy decisions for replacing early or late packets. I don't see why this wouldn't be possible for us to have in the implementation as a parameter. +- Alex: Definitely possible. We just have to pre-pend packets with the length. I'm looking into having both options. +- Branden: That makes sense to me. And the idempotent upcalls make more sense to me in that multiple different drivers need them for different but similar purposes +- Tyler: Alex, you're not changing the allow interface with these buffers, you're just creating a standardized way to represent a current length / offset in these buffers. What's the reason for the checksum still? +- Alex: The idea was to try to recover if the app misbehaves. If the app just writes a zero to the first position, or the app appends some data, the driver verifies this and sees things are uninitialized and can reset to zero. The driver would work either way, but I see this as a perk for the application. I'm not sure if this is needed or not +- Leon: I think on the core call, we determined that we don't need protect the app against its own misbehavior. We're still doing length checks all the time, so this would really just check for whether the app is written correctly. And generally, we don't do that in the kernel. So I don't buy the argument that we want the checksum. It also makes the implementation more complex. +- Alex: I could remove it +- Leon: For Tyler, the implementation just represents the length/offset into buffer in a standardized way. This infrastructure also implicitly gives you the ability to swap buffers by resetting an offset. +- Tyler: The offset would be reset when the application shares a new buffer? That makes sense +- Leon: One thing that just came to mind, maybe we can introduce a bit to show the application that there was an overflow. This is similar to the congestion notification in networks. This could be reasonable to share, as it's a real outcome +- Alex: We share this in the upcall right now. For example, we need to report an issue if we drop CAN packets. +- Leon: Okay, that makes sense +- Alex: Imagine having many packets. We have some small space left in the buffer, and can't fit a big packet so we drop it. Then we still fit in a small packet. Was this an overflow? +- Leon: Maybe we do want to signal. That's worth thinking about, I am also uneasy about special-purpose implementations here +- Alex: We could have a bit reserved for it. Most drivers will still notify in the upcall +- Alex: Right now we can't put this into an upcall, because we might overflow the notification queue. Once we have idempotent upcalls, this wouldn't be an issue any longer. +- Branden: You could get rid of the xor bits, and add 8 flag bits. And one of those will a overrun bit. +- Leon: This buffer isn't aligned anyways, as userspace can share anything. So we could just have a five-byte header +- Alex: I also wanted a version for the buffer API. Maybe four bits for that +- Leon: One trivial way to add versioning without immediate overhead: just say that one flag bit is always zero in this version. So that bit will become one if there's ever another version +- Tyler: Our current 15.4 buffer structure has a lot of wasted space, as you have to pass down the full max buffer size from userspace. Is there any issue with the waste when receiving smaller packets? Is this worth thinking about? +- Leon: Big issue. This gets MUCH worse in Ethernet. We don't want 64-byte ping packets in a 9 kB buffer... This API is still a great first step, as you can stuff packetized frames into the buffer if it's too big. So really the benefit here is for Rust to compose near-zero-cost abstractions that give you several APIs for the different structures we're making in the buffer + diff --git a/doc/wg/network/notes/network-notes-2024-07-15.md b/doc/wg/network/notes/network-notes-2024-07-15.md new file mode 100644 index 0000000000..42be257cca --- /dev/null +++ b/doc/wg/network/notes/network-notes-2024-07-15.md @@ -0,0 +1,104 @@ +# Tock Network WG Meeting Notes + +- **Date:** July 15, 2024 +- **Participants:** + - Branden Ghena + - Leon Schuermann + - Felix Mada + - Ben Prevor + - Alex Radovici + - Ionut Cirstea +- **Agenda** + 1. Updates + 2. 802.15.4 Libtock-rs Driver + 3. Console Multiplexing + 4. PacketBuffer Upstreaming +- **References:** + - [libtock-rs #551](https://github.com/tock/libtock-rs/pull/551) + + +## Updates +- None + + +## 15.4 Libtock-rs Driver + * https://github.com/tock/libtock-rs/pull/551 + * Branden: overview of what's going on. 15.4 driver is being added to libtock-rs, which we're very excited about. It likely works right now, but Rust says it's unsound. + * Branden: Big goal -- big research deployment in Poland, would like to use it, we need to help the author + * Leon: We want to figure out how to handle buffer management so that they are shared with the kernel and returned to userspace in a sound way. They way this PR currently works with them breaks Rust's demands + * Leon: The way this currently works is that it allocates these buffers from ephemeral memory. So they could theoretically be deallocated before unsharing them with the kernel. And the kernel would not be aware of that change. In that case, the kernel could overwrite application memory that's in use by a new memory object + * Leon: The Core team call discussed this briefly last week. The possible solution was to place a requirement on the buffers to no longer have a Rust lifetime. Somewhere around here +https://github.com/tock/libtock-rs/pull/551/files#diff-cb5d770d7877fbe88b63f507acfb99e1e2fc63476b6d198a733b9311b6fc937bR260 instead of the `'buf` lifetime, they could be static. And that might be a sound design at the cost of taking away flexibility. + * Branden: But an application doing 15.4 stuff isn't going to stop doing 15.4 stuff, so it seems fine that they're static + * Leon: And this generally matches the Tock design, that buffers are preferred to be static so they don't fail at runtime + * Leon: However, that's all information from the discussion on the Core team call plus a bit of skimming + * Alex: We hit the exact same problem with the CAN implementation. We stopped because we didn't want to use Unsafe in a Rust application. We discussed this with Johnathan on a Core team call: https://github.com/tock/tock/blob/master/doc/wg/core/notes/core-notes-2023-03-03.md (which also included some Slack discussion which has now disappeared forever) + * Leon: I see a crossroads here. We can try to understand this in real-time and maybe propose a solution. I'd argue that's not a great use of our time as we're not libtock-rs experts and haven't dug deeply into this. Or we can extract some more high-level plans: so I'd propose first seeing if using `static` everywhere fixes the issues. I'd be skittish developing a solution that's more fringe without Johnathan's approval, including using unsafe in a way that's not already common in libtock-rs + * Alex: Problem is that if we start using unsafe, philosophically, what's the point of having this super complicated scope API that Johnathan made? + * Leon: Unsafe for the system call bindings only, which need to be unsafe + * Alex: If you want to swap the buffer though, you basically have to use unsafe. Regardless of whether it's static or not. There's no API that gives you back a buffer. So in libtock-rs you share it and when the scope is destroyed you get it back. But unless it goes out of scope, you can't get it back. + * Leon: Ultimately, even for the static version, we'd have to dedicate an allow to be only usable on static buffers, and if you get a non-null buffer back, you can cast it to static and know that's sound. Right now there's no way to know that the buffer you get back is static + * Alex: That was the debate with Johnathan, you summarized it well + * Leon: Okay, so what we need to do first is 1) clearly communicate with the author about what's going on and 2) communicate the difficulty in shepherding this. That we appreciate this PR and do want to see it merged. But also that we're concerned about unsafe code in the kernel. + * Leon: So, we could have them use it downstream for now. And it's going to be a long process to make it sound upstream + * Branden: Paths forward: + - Happy to draft a version of that message and run it by folks + - Leon should leave a more technical comment that this is a libtock-rs problem, not a "this PR" problem + * Leon: Less convinced about the fact that making it `'static` is going to solve our issues, but can do the latter for sure! + * Alex: This was previously discussed on the Tock core call, https://github.com/tock/tock/blob/master/doc/wg/core/notes/core-notes-2023-03-03.md (which also included some Slack discussion which has now disappeared forever) + + +## Console Multiplexing + * Alex: OxidOS is financing some interns working on tockloader-rs and adding support to it + * Branden: there should be an initial PR or an RFC issue, just to make sure that the format of the "how you're packetizing console messages" actually makes sense + * Branden: Amalia presented the packetization approach, and there were questions about the protocol. Making sure that we're not moving forward without early feedback. + * Alex: For now, support basic tockloader-rs functionality. First concern -- how to distinguish between a kernel that does packetization, and one that doesn't. + * Leon: Can you create an initial upstreaming PR as part of the handover? I'm happy to look over all of this if it would be helpful (SOSP rebuttal comes in next Monday and busy for a week) + * Alex: Okay. This isn't coming until mid-August from us. Hopefully Amalia will be able to put in more effort here + + +## Tock Ethernet + * Leon: I had originally though about putting PacketBuffer into the initial Ethernet implementation, but I think maybe we should just do a PR for the stuff first and then do the transition after + * Leon: I think Ionut's Ethernet driver is in an excellent state and could go into Tock master soon. Ionut is welcome to make a PR against master + * Leon: The idea was to make a common interface, but that interface should include PacketBuffer. So we could get stuff into master and then move to PacketBuffer after + * Leon: We also pretty extensively reviewed this already, so it should be smooth to get into Master + + +## RP2040 WiFi + * Alex: Porting embassy-rs driver for WiFi to RP2040 in kernelspace. + * Branden: We talked about some of the more general issues with the RP2040. Brad said that we have a workaround for placing it into the bootloader mode with Tock's USB driver that works on the Nano 33 BLE? + * Alex: The issue is that the bootloader is hardcoded, and I'm not sure if you can reboot it _into_ that bootloader. It's possible, but a thing that stops us is that the USB stack isn't working well on Windows and Linux + * Branden: That's orthogonal though, right? + * Alex: Sort of. But the board just doesn't work on modern Linux or all Windows. So it's hard to work on this at all + * Alex: The USB stack jams after a few messages too. Could be a hardware issue? + * Alex: There's also a crazy project that runs a debugger on one core and your app (so Tock) on the other + * Leon: An issue here is that the USB stack was first implemented just for Nordic boards, and it's not a great interface. Then another group worked on the Pico, so there's quite possibly a mismatch between them + * Alex: Ionut is pretty darn familiar with the USB stack and is going to take a look at the Pico + * Ionut: I'd actually like to rework the entire USB stack from the ground because it's so hard to use + * Branden: I don't think anyone's against it. It's just so much work + + +## PacketBuffer Upstreaming + * Branden: Two concerns: + - Where the buffer size requirement comes from? Bottom-up, or top-down? + - For 15.4, PacketSize is specified all the way at the bottom, maximum packet size + - Console, message size specified at the top + Want to make sure we can handle both these cases. + - "Generic proliferation." + - "Great, to use it all these drivers get four more parameters!" + - Usability pushback. + - How can we alleviate these concerns to make sure others get value from PacketBuffer? + * Leon: My thoughts on those. + - Buffer size requirement is interesting. There's a size requirement from only one point, which is the bottom up. That's a minimum size requirement for headers and footers. There's not a maximum packet size that we can enforce bottom up (that would be MORE generic parameters). It's possible at runtime right now that an upper-layer 15.4 implementation tries to send a packet where the headers and footers overrun the maximum size parameter. And we can't guarantee that at compile-time right now. + - For the message size from the Console. The lower bottom layer always requires a minimum reservation. The upper layer specifies what the actual size of the message is inside the buffer. So the lower layer imposes header/footer constraints, the upper layer passes message size information. + * Branden: Not an explicit design decision, but it does feel weird that we guarantee header/footer size at compile time, but maximum size we don't guarantee? + * Leon: Header/footer might be the trickier problem. Plus, the current implementation does still have that error of maximum size is too big + * Leon: If we had a maximum size, we'd still have a runtime error, just at the userland interface instead of at the bottom of the stack + * Leon: For generic proliferation: less of an answer and more of a thought. I'm hoping that we can use components to our advantage. Part of the reason this is particularly bad is that we are specifying some arbitrary numbers. Components could have constants by name instead, which would make more sense when reading it. + * Branden: I'm actually okay with main.rs. I'm more concerned about proliferation within capsules + * Branden: My bigger concern is that PacketBuffer is the "straw that broke the camel's back" because there are just so many more parameters + * Leon: I agree that this is ugly. I think at some point we have to accept that Rust requires this for efficiency in Rust embedded. + * Branden: Question -- if we step back and said "could we do PacketBuffer without the generic parameters and what would we lose"? Would it still be valuable without compile time guarantees? + * Branden: I don't have an answer right now. I just think we need to be ready to address it when upstreaming PacketBuffer + * Leon: Without generic parameters, we'd have a nice API but lose all the magic that makes it more reliable and robust + diff --git a/doc/wg/opentitan/README.md b/doc/wg/opentitan/README.md index a934d49efc..2fecb3ffa5 100644 --- a/doc/wg/opentitan/README.md +++ b/doc/wg/opentitan/README.md @@ -54,7 +54,7 @@ approving, and merging pull requests for) the following code directories: - `chips/ibex` - `chips/lowrisc` -- `boards/earlgrey_nexysvideo` +- `boards/opentitan` In addition, while it is not the focus of the group, because of the tight relationship in early stages of development, the OT working group is also diff --git a/doc/wg/opentitan/case_studies/datacenter_security_model.md b/doc/wg/opentitan/case_studies/datacenter_security_model.md new file mode 100644 index 0000000000..daa989481a --- /dev/null +++ b/doc/wg/opentitan/case_studies/datacenter_security_model.md @@ -0,0 +1,329 @@ +# Root-of-Trust Security Model +## Datacenter Use Cases + +

Author: cfrantz

+

Date: 2022-11-15

+

Status: Published

+ +## Abstract + +This document describes the security model for a Root-of-Trust (RoT) +chip as currently deployed for datacenter use cases. The deployment +described represents how the current Google proprietary root-of-trust chip +(publicly known as Titan) and its firmware work. The exact nature of the +security model continues to evolve along with Google's production security +requirements and the capabilities available from Root-of-Trust chips. + +The primary purpose of the current integrations of the Titan RoT into +server products is to maintain first-instruction boot integrity and to +grant the machine a valid identity in Google's production environment +(colloquially known as _prod_). Titan RoTs also mitigate DoS attacks by +enforcing secure firmware updates. + +This document concludes with some alternative concepts for firmware +delivery considering Tock's process isolation model and Application +ID features. + + +## Background + +Google's Platforms team designs and contracts out the manufacture of +the servers and peripherals (NICs, SSDs, custom accelerators) that +comprise its production environment. Within Google, customers (ie: +product teams such as websearch, gmail, cloud, etc) purchase compute +resources to execute their jobs. + +In order for a machine to run customer jobs, it must pass a series of +health checks and acquire a cryptographic identity. A machine which +is able to present (or wield) its identity can interact with other +production services such as the cluster scheduler, storage services or +other internal services (such as the web index, image recognition, etc). +A machine which cannot present its identity is only permitted to interact +with a limited set of services geared towards automated or manual repair +of broken machines. + +The Google Titan chip is a Google-designed Root-of-Trust chip. +It features a 32-bit embedded ARM Cortex-M, internal flash and RAM +and crypto acceleration hardware, including a SHA hashing block, a key +derivation block and a bignum accelerator. + + +## How Titan is Integrated into the Datacenter + +The Google Titan chip is integrated into server products such that it has +control over the machine's reset process and boot firmware. Specifically, +the Titan chip can both monitor and drive the application processor's +reset signals and interposes on the SPI bus between the Application +Processor (AP) and its EEPROM. This design is applied to simple servers +with a CPU, to serveris with Baseboard Management Controllers (BMCs), +and to peripherals like NICs and accelerators. + +Titan integrations into server peripherals are very similar to the +integration into a server product: Titan is given low-level control over +the peripheral's reset signals and is positioned such that it has control +over the peripheral's boot firmware. There are often customizations +to the integration to meet certain requirements of the peripheral (such +as boot timing), but for the purpose of this document, the integrations +are basically the same. + + +## Titan Code Images + +Titan has 3 distinct code images. The first, called the ROM image, never +changes. The second, called the bootloader, changes rarely. The third, +called the application firmware, is a monolithic image including the +kernel, hardware support code and application code. Generally, new +application firmware is pushed to production every few months. + + +## Assumptions and Requirements + +Google's production infrastructure makes the following assumptions: + +* The Titan silicon boot process is secure. Titan will only boot + firmware signed by Google engineers authorized to use the Google Titan + signing keys. +* The Titan signing keys are kept secure and are only accessible to + an appropriately authorized quorum of engineers[^1]. +* The Titan hardware correctly enforces the key/firmware version binding + requirements. A Titan firmware upgrade allows a secure migration path + away from keys bound to firmware that is bad. + +Requirements: + +* Datacenter machines must wield their cryptographic identity to + join prod. A machine without a cryptographic identity is forbidden from + joining production and must go through the repairs flow. +* Datacenter machines must boot from the intended boot stack in order + to access their cryptographic identity. The Titan firmware is the root + link in the chain of boot firmware and software. Titan verifies its + own firmware and the machine's first boot firmware. + * Titan is not required to verify anything after the machine's + boot firmware: If the machine's boot firmware is trusted, further + verification of the boot stack can be delegated up the stack. +* Signing and verification of boot-stack items must not be onerous. + Authorized principals from the team responsible for each item in the + boot stack should sign that item. The public keys for each item must + be made available to the authors of the prior stage of the boot stack. + * Implementation details: + * Most of the items in the boot stack have their keys stored in + Google's internal key management service. Signing is an online + process delegated to the role accounts responsible for building + and releasing software in Google's release automation systems. + * The signing keys for Titan firmware are deemed too powerful + to be stored in any online system. The Titan firmware keys are + stored in an isolated system in a secure facility. The Titan + signing quorum has access to the secure facility and performs an + offline signing ceremony as part of the Titan firmware release + process. + + +## Machine Boot Process + +The following sequence details the per-power-cycle machine lifecycle: + +1. Machine powers on; the power sequencer releases resets. Independent + from the power sequencer, a simple RC circuit charges and releases Titan + from reset (typical values for the RC circuit are R = 10KΩ and C = + 0.1μF; time constant ≈ 10ms). The power-on state of Titan's GPIOs + are such that although the power sequencer has released resets, Titan + is still holding the server in reset. +2. Titan exits reset and the Titan's ROM executes. The ROM finds the + Titan's bootloader in its internal flash. Titan cryptographically + verifies the bootloader against a set of keys stored in ROM. If the + verification passes, execution jumps to the bootloader; if verification + fails, Titan resets itself. +3. The bootloader performs initial configuration of the cryptographic + hardware and low-level security features of the chip (such as locking + certain flash areas and configuring intrusion detection features). + The bootloader then scans the internal flash looking for the Titan + application firmware. The application firmware is cryptographically + verified against a set of keys stored in the bootloader. If the + verification passes, execution jumps to the application firmware; if + verification fails, Titan resets itself. +4. The Titan application firmware performs additional hardware + configuration, such as configuring GPIOs and other IO peripherals. +5. The application firmware, still holding the machine in reset, scans the + discrete SPI flash part looking for valid AP boot firmware. Once found, + Titan cryptographically verifies the immutable portions of the AP boot + firmware against a set of keys stored in Titan's application firmware. + Titan records the validity of the AP boot firmware and then releases + the AP from reset. +6. The AP boots. In cases where the AP is a server CPU that runs multiple + boot layers (e.g. UEFI, kernel, etc.), the CPU follows a measured boot + flow; each layer that runs is first measured by the preceding layer. Titan + behaves like a TPM, and only releases the machine's cryptographic identity + if the CPU boots through its intended software. +7. The machine boots. A full description of the server boot process + is beyond the scope of this document. The short version is: the BMC + boots while holding the machine's main CPU complex in reset. The BMC + scans the machine's BIOS and verifies it against public keys stored in + the BMC firmware, records the validity of the BIOS and then releases + the main CPU complex from reset. The main CPU boots, finds the OS, + verifies and records its validity and boots the OS. +8. Once the machine's operating system has booted, the security daemon + will collect the statements of validity of each of the boot stages and + present them to Titan. If Titan can determine that the entire boot + stack was the intended boot stack, Titan will grant the machine access + to its cryptographic identity and the machine will join prod. If Titan + cannot determine the validity of the boot stack, it will refuse to grant + the machine access to its identity and the machine will be forced to go + through the repairs process. +9. If at any time during machine runtime Titan detects a reset, Titan + will hold the machine[^2] in reset and the boot process will repeat from + step 5. +10. The Titan application firmware monitors the SPI bus during runtime + and enforces a write-protect policy on the SPI EEPROM (the firmware + contains a descriptor that specifies its write-protect policy, including + protected region offsets). +11. All firmware updates must be validated by Titan -- the machine is + not permitted to write directly to flash. + + +## Notable Features of the Titan Server Firmware + +The following are notable features of the application firmware for the datacenter use case: + +* It attests to the validity of the machine's boot firmware from the + first instruction. +* It securely guards the machine's cryptographic identity; the identity + is only granted to a machine that booted the intended boot stack. +* It has fail-open failure modes: excepting Titan itself, any failure + in the integrity of the boot stack still permits the machine to boot + and restricts its access to automated repairs systems. +* It provides machine resilience through flash write protection and + Managed firmware updates: Titan maintains a write-protect policy over the + EEPROM and manages firmware updates. A feature of Titan's SPI interposer + permits A/B firmware updates such that an update preserves the existing + firmware as a fall-back should the new firmware fail[^3]. +* It can tie cryptographic keys to major application firmware versions. + In the event of a serious bug being found in the Titan firmware, a new + application firmware (major version) can be released and new keys issued + tied to that application firmware. Newer application firmware can access + and wield keys tied to older firmware, but older firmware will be unable + to access the new keys[^4] + +## Tock and AppID + +The migration to OpenTitan represents a radical change from how the +current datacenter Titan firmware is developed and delivered. This change +allows us to re-examine the assumptions and requirements of the current +system and make improvements. + +### Firmware Delivery + +#### Today + +The current datacenter Titan application firmware is a monolithic software +image. The implementation consists of three main components: a kernel & +drivers component, a hardware integration component (referring to the +specifics of Titan's control over the server system) and a cryptographic +& identity services component. The boundaries between these components +are somewhat blurry and there is no strong separation between the kernel +and application components. + +The components of the monolithic image are maintained by different teams +within Google. The platforms team maintains the kernel, drivers and +hardware integration components. The prod-identity team maintains the +cryptographic & identity services component. As one might expect, having +multiple teams contributing to a single monolithic firmware image has been +a source of complexity that has occasionally led to confusion or delays. + + +#### The Glorious Golden Future + +The following sections of the document describe hypothetical use cases or +hypothetical modifications to existing use cases. They assume OpenTitan +is the Root-of-Trust and adopt OpenTitan terminology. It is assumed that +the OpenTitan chip boots securely and configures the chip appropriately +before booting the Tock kernel. + +Tock provides a kernel and userspace boundary and process isolation. +Tock also permits applications to be delivered and loaded separately from +the kernel payload. It will be possible for the datacenter firmware to +be delivered as individual components with different access controls on +each component. + + +* The hardware integration component does not need access to the + cryptographic hardware within the chip. Its main concerns are performing + reset control and managing access to the SPI flash chip. +* The cryptographic services component, likewise, does not need access + to the IO interfaces of the OpenTitan chip. + +Google platforms team, as a silicon_owner (in +OpenTitan terminology, silicon\_owner is the purchaser of the +OpenTitan chip or devices containing an OpenTitan chip), can sign and +deliver the Tock kernel for datacenter integrations. The platforms team +can also sign and deliver the hardware integration application which +provides the lowest level of machine control services for an OpenTitan +integration. + +Google's production identity team can sign and deliver the cryptographic +services application which will be responsible for guarding and +maintaining the machine or subsystem identity and attesting to the +validity of its boot firmware. + +These applications may be signed by distinct keys, allowing independent +operational security and release processes for the different +applications. The application signing keys are distinct from the +kernel signing keys and each of these keys may have different key +storage requirements. For example, the kernel key may be considered +a high-value resource restricted to an offline Hardware Security Module +(HSM) whereas application signing keys could be considered safe enough +to be stored in Google's online key management service (because they +can be rotated as part of a new kernel release). This separation of +authority and permissions can allow the platforms and prod-identity +teams to independently develop their respective applications as well as +sign and deliver those applications without need of an offline ceremony +(whereas, kernel upgrades or deployment of new application signing keys +would require an offline ceremony), thereby lowering the cost of feature +additions and other maintenance work. + + +### Delegation of OpenTitan Resources + +The separation of kernel versus application signing authority also allows +for new modes of service delivery for OpenTitan-enabled platforms. + +Examples: + + + +* A hardware peripheral in a server may wish to obtain an identity + or keys which are tied to the server platform mainboard. The team + responsible for that peripheral could write their own identity-granting + Tock application and deploy it to the motherboard's RoT (sometimes called + a Platform Root-of-Trust or PRoT). When in service, the peripheral can + establish communication with the PRoT and acquire its mainboard-derived + identity. +* Secure logging: Server platforms (or a subset of servers deployed in + prod) may require a cryptographic record of log messages authenticated + by or stored inside the RoT's tamper-resistant perimeter. An optional + secure logging application could be deployed to the RoT that generates + logging tokens needed by the secure logging subsystem. +* Cloud end-customer applications: Although this is (IMHO) a bit of + a wild use case, infrastructure providers could conceivably choose to + sell or otherwise make available execution time on the RoT and allow + end-customers to deploy their own Tock applications. We will have + to establish what sorts of Tock services and syscalls are available + to end-customer applications, but this could potentially allow cloud + customers the ability to perform certain operations within a secure + element on their purchased compute resource. + + +## Notes + +[^1]: The exact method of securing the keys is beyond the scope of this + document. No engineer has unilateral access; access is granted via an + M-of-N quorum authenticated through multiple factors. + +[^2]: There are some variations or exceptions. In all cases, Titan has + control over the machine or peripheral's boot process. + +[^3]: TL;DR: the flash part is twice the required size and Titan can + choose which half to show to the machine. + +[^4]: In the event that an attacker could force-downgrade Titan to the + known-bad firmware, attempts to access or wield the newer keys will be + denied by the hardware. diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index ff40705f05..bbbe3bf680 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -1,8 +1,12 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "kernel" -version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +version.workspace = true +authors.workspace = true +edition.workspace = true [dependencies] tock-registers = { path = "../libraries/tock-register-interface" } @@ -29,3 +33,7 @@ tock-tbf = { path = "../libraries/tock-tbf" } trace_syscalls = [] debug_load_processes = [] no_debug_panics = [] +debug_process_credentials = [] + +[lints] +workspace = true diff --git a/kernel/src/capabilities.rs b/kernel/src/capabilities.rs index b14edb9d63..5314a7b54e 100644 --- a/kernel/src/capabilities.rs +++ b/kernel/src/capabilities.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Special restricted capabilities. //! //! Rust provides a mechanism for restricting certain operations to only be used @@ -51,6 +55,12 @@ /// otherwise managing processes. pub unsafe trait ProcessManagementCapability {} +/// The `ProcessStartCapability` allows the holder to start a process. This is +/// controlled and separate from `ProcessManagementCapability` because the +/// process must have a unique application identifier and so only modules which +/// check this may do so. +pub unsafe trait ProcessStartCapability {} + /// The `MainLoopCapability` capability allows the holder to start executing as /// well as manage the main scheduler loop in Tock. This is needed in a board's /// main.rs file to start the kernel. It also allows an external implementation @@ -69,6 +79,10 @@ pub unsafe trait MemoryAllocationCapability {} /// restricted. pub unsafe trait ExternalProcessCapability {} +/// The KernelruserStorageCapability` capability allows the holder to create +/// permissions to access kernel-only stored values on the system. +pub unsafe trait KerneluserStorageCapability {} + /// The `UdpDriverCapability` capability allows the holder to use two functions /// only allowed by the UDP driver. The first is the `driver_send_to()` function /// in udp_send.rs, which does not require being bound to a single port, since diff --git a/kernel/src/collections/list.rs b/kernel/src/collections/list.rs index 3e46142c0b..ae7d3a03cb 100644 --- a/kernel/src/collections/list.rs +++ b/kernel/src/collections/list.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Linked list implementation. use core::cell::Cell; diff --git a/kernel/src/collections/mod.rs b/kernel/src/collections/mod.rs index 8c2ae4d5a5..083fc8beb7 100644 --- a/kernel/src/collections/mod.rs +++ b/kernel/src/collections/mod.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Data structures. pub mod list; diff --git a/kernel/src/collections/queue.rs b/kernel/src/collections/queue.rs index 3085fc522b..08f1c626c9 100644 --- a/kernel/src/collections/queue.rs +++ b/kernel/src/collections/queue.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for queue structure. pub trait Queue { @@ -20,6 +24,11 @@ pub trait Queue { /// Remove the element from the front of the queue. fn dequeue(&mut self) -> Option; + /// Remove and return one (the first) element that matches the predicate. + fn remove_first_matching(&mut self, f: F) -> Option + where + F: Fn(&T) -> bool; + /// Remove all elements from the ring buffer. fn empty(&mut self); diff --git a/kernel/src/collections/ring_buffer.rs b/kernel/src/collections/ring_buffer.rs index d9672668cc..ff842a1753 100644 --- a/kernel/src/collections/ring_buffer.rs +++ b/kernel/src/collections/ring_buffer.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of a ring buffer. use crate::collections::queue; @@ -13,7 +17,7 @@ impl<'a, T: Copy> RingBuffer<'a, T> { RingBuffer { head: 0, tail: 0, - ring: ring, + ring, } } @@ -107,6 +111,39 @@ impl queue::Queue for RingBuffer<'_, T> { } } + /// Removes the first element for which the provided closure returns `true`. + /// + /// This walks the ring buffer and, upon finding a matching element, removes + /// it. It then shifts all subsequent elements forward (filling the hole + /// created by removing the element). + /// + /// If an element was removed, this function returns it as `Some(elem)`. + fn remove_first_matching(&mut self, f: F) -> Option + where + F: Fn(&T) -> bool, + { + let len = self.ring.len(); + let mut slot = self.head; + while slot != self.tail { + if f(&self.ring[slot]) { + // This is the desired element, remove it and return it + let val = self.ring[slot]; + + let mut next_slot = (slot + 1) % len; + // Move everything past this element forward in the ring + while next_slot != self.tail { + self.ring[slot] = self.ring[next_slot]; + slot = next_slot; + next_slot = (next_slot + 1) % len; + } + self.tail = slot; + return Some(val); + } + slot = (slot + 1) % len; + } + None + } + fn empty(&mut self) { self.head = 0; self.tail = 0; diff --git a/kernel/src/component.rs b/kernel/src/component.rs index 872e85edee..cf19c81a94 100644 --- a/kernel/src/component.rs +++ b/kernel/src/component.rs @@ -1,38 +1,64 @@ -//! Components extend the functionality of the Tock kernel through a -//! simple factory method interface. +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Components extend the functionality of the Tock kernel through a simple +//! factory method interface. /// A component encapsulates peripheral-specific and capsule-specific -/// initialization for the Tock OS kernel in a factory method, -/// which reduces repeated code and simplifies the boot sequence. +/// initialization for the Tock kernel in a factory method, which reduces +/// repeated code and simplifies the boot sequence. +/// +/// The [`Component`] trait encapsulates all of the initialization and +/// configuration of a kernel extension inside the [`Component::finalize()`] +/// function call. The [`Component::Output`] type defines what type this +/// component generates. Note that instantiating a component does not +/// instantiate the underlying [`Component::Output`] type; instead, the memory +/// is statically allocated and provided as an argument to the +/// [`Component::finalize()`] method, which correctly initializes the memory to +/// instantiate the [`Component::Output`] object. If instantiating and +/// initializing the [`Component::Output`] type requires parameters, these +/// should be passed in the component's `new()` function. +/// +/// Using a component is as follows: +/// +/// ```rust,ignore +/// let obj = CapsuleComponent::new(configuration, required_hw) +/// .finalize(capsule_component_static!()); +/// ``` /// -/// The `Component` trait encapsulates all of the initialization and -/// configuration of a kernel extension inside the `finalize()` function -/// call. The `Output` type defines what type this component generates. -/// Note that instantiating a component does not necessarily -/// instantiate the underlying `Output` type; instead, it is typically -/// instantiated in the `finalize()` method. If instantiating and -/// initializing the `Output` type requires parameters, these should be -/// passed in the component's `new()` function. +/// All required resources and configuration is passed via the constructor, and +/// all required static memory is defined by the `[name]_component_static!()` +/// macro and passed to the `finalize()` method. pub trait Component { /// An optional type to specify the chip or board specific static memory /// that a component needs to setup the output object(s). This is the memory - /// that `static_init!()` would normally setup, but generic components - /// cannot setup static buffers for types which are chip-dependent, so those - /// buffers have to be passed in manually, and the `StaticInput` type makes - /// this possible. + /// that [`crate::static_buf!()`] would normally setup, but generic + /// components cannot setup static buffers for types which are + /// chip-dependent, so those buffers have to be passed in manually, and the + /// [`Component::StaticInput`] type makes this possible. type StaticInput; - /// The type (e.g., capsule, peripheral) that this implementation - /// of Component produces via `finalize()`. This is typically a - /// static reference (`&'static`). + /// The type (e.g., capsule, peripheral) that this implementation of + /// [`Component`] produces via [`Component::finalize()`]. This is typically + /// a static reference (`&'static`). type Output; /// A factory method that returns an instance of the Output type of this - /// Component implementation. This factory method may only be called once - /// per Component instance. Used in the boot sequence to instantiate and - /// initialize part of the Tock kernel. Some components need to use the - /// `static_memory` argument to allow the board initialization code to pass - /// in references to static memory that the component will use to setup the - /// Output type object. - unsafe fn finalize(self, static_memory: Self::StaticInput) -> Self::Output; + /// [`Component`] implementation. This is used in the boot sequence to + /// instantiate and initialize part of the Tock kernel. This factory method + /// may only be called once per Component instance. + /// + /// To enable reusable (i.e. can be used on multiple boards with different + /// microcontrollers) and repeatable (i.e. can be instantiated multiple + /// times on the same board) components, all components must follow this + /// convention: + /// + /// - All statically allocated memory MUST be passed to + /// [`Component::finalize()`] via the `static_memory` argument. The + /// [`Component::finalize()`] method **MUST NOT** use + /// [`crate::static_init!()`] or [`crate::static_buf!()`] directly. This + /// restriction ensures that memory is not aliased if the component is + /// used multiple times. + fn finalize(self, static_memory: Self::StaticInput) -> Self::Output; } diff --git a/kernel/src/config.rs b/kernel/src/config.rs index e7ec8622ac..043d0c0450 100644 --- a/kernel/src/config.rs +++ b/kernel/src/config.rs @@ -1,51 +1,61 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Data structure for storing compile-time configuration options in the kernel. //! //! The rationale for configuration based on a `const` object is twofold. //! -//! - In theory, Cargo features could be used for boolean-based configuration. However, these -//! features are generally error-prone for non-trivial use cases. First, they are globally enabled -//! as long as a dependency relationship requires a feature (even for other dependency -//! relationships that do not want the feature). Second, code gated by a non-enabled feature -//! isn't even type-checked by the compiler, and therefore we can end up with broken features due -//! to refactoring code (if these features aren't tested during the refactoring), or to -//! incompatible feature combinations. +//! - In theory, Cargo features could be used for boolean-based configuration. +//! However, these features are generally error-prone for non-trivial use +//! cases. First, they are globally enabled as long as a dependency +//! relationship requires a feature (even for other dependency relationships +//! that do not want the feature). Second, code gated by a non-enabled feature +//! isn't even type-checked by the compiler, and therefore we can end up with +//! broken features due to refactoring code (if these features aren't tested +//! during the refactoring), or to incompatible feature combinations. //! -//! - Cargo features can only contain bits. On the other hand, a constant value can contain -//! arbitrary types, which allow configuration based on integers, strings, or even more complex -//! values. +//! - Cargo features can only contain bits. On the other hand, a constant value +//! can contain arbitrary types, which allow configuration based on integers, +//! strings, or even more complex values. //! -//! With a typed `const` configuration, all code paths are type-checked by the compiler - even -//! those that end up disabled - which greatly reduces the risks of breaking a feature or -//! combination of features because they are disabled in tests. +//! With a typed `const` configuration, all code paths are type-checked by the +//! compiler - even those that end up disabled - which greatly reduces the risks +//! of breaking a feature or combination of features because they are disabled +//! in tests. //! -//! In the meantime, after type-checking, the compiler can optimize away dead code by folding -//! constants throughout the code, so for example a boolean condition used in an `if` block will in -//! principle have a zero cost on the resulting binary - as if a Cargo feature was used instead. -//! Some simple experiments on generated Tock code have confirmed this zero cost in practice. +//! In the meantime, after type-checking, the compiler can optimize away dead +//! code by folding constants throughout the code, so for example a boolean +//! condition used in an `if` block will in principle have a zero cost on the +//! resulting binary - as if a Cargo feature was used instead. Some simple +//! experiments on generated Tock code have confirmed this zero cost in +//! practice. /// Data structure holding compile-time configuration options. /// -/// To change the configuration, modify the relevant values in the `CONFIG` constant object defined -/// at the end of this file. +/// To change the configuration, modify the relevant values in the `CONFIG` +/// constant object defined at the end of this file. pub(crate) struct Config { /// Whether the kernel should trace syscalls to the debug output. /// - /// If enabled, the kernel will print a message in the debug output for each system call and - /// upcall, with details including the application ID, and system call or upcall parameters. + /// If enabled, the kernel will print a message in the debug output for each + /// system call and upcall, with details including the application ID, and + /// system call or upcall parameters. pub(crate) trace_syscalls: bool, /// Whether the kernel should show debugging output when loading processes. /// - /// If enabled, the kernel will show from which addresses processes are loaded in flash and - /// into which SRAM addresses. This can be useful to debug whether the kernel could - /// successfully load processes, and whether the allocated SRAM is as expected. + /// If enabled, the kernel will show from which addresses processes are + /// loaded in flash and into which SRAM addresses. This can be useful to + /// debug whether the kernel could successfully load processes, and whether + /// the allocated SRAM is as expected. pub(crate) debug_load_processes: bool, /// Whether the kernel should output additional debug information on panics. /// - /// If enabled, the kernel will include implementations of `Process::print_full_process()` and - /// `Process::print_memory_map()` that display the process's state in a human-readable - /// form. + /// If enabled, the kernel will include implementations of + /// `Process::print_full_process()` and `Process::print_memory_map()` that + /// display the process's state in a human-readable form. // This config option is intended to allow for smaller kernel builds (in // terms of code size) where printing code is removed from the kernel // binary. Ideally, the compiler would automatically remove @@ -61,14 +71,25 @@ pub(crate) struct Config { // is identified, using configuration constants is the most effective // option. pub(crate) debug_panics: bool, + + /// Whether the kernbel should output debug information when it is checking + /// the cryptographic credentials of a userspace process. If enabled, the + /// kernel will show which footers were found and why processes were started + /// or not. + // This config option is intended to provide some visibility into process + // credentials checking, e.g., whether elf2tab and tockloader are generating + // properly formatted footers. + pub(crate) debug_process_credentials: bool, } -/// A unique instance of `Config` where compile-time configuration options are defined. These -/// options are available in the kernel crate to be used for relevant configuration. -/// Notably, this is the only location in the Tock kernel where we permit `#[cfg(x)]` -/// to be used to configure code based on Cargo features. +/// A unique instance of `Config` where compile-time configuration options are +/// defined. These options are available in the kernel crate to be used for +/// relevant configuration. Notably, this is the only location in the Tock +/// kernel where we permit `#[cfg(x)]` to be used to configure code based on +/// Cargo features. pub(crate) const CONFIG: Config = Config { trace_syscalls: cfg!(feature = "trace_syscalls"), debug_load_processes: cfg!(feature = "debug_load_processes"), debug_panics: !cfg!(feature = "no_debug_panics"), + debug_process_credentials: cfg!(feature = "debug_process_credentials"), }; diff --git a/kernel/src/debug.rs b/kernel/src/debug.rs index f54d33b6ca..4c64bc4e9d 100644 --- a/kernel/src/debug.rs +++ b/kernel/src/debug.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Support for in-kernel debugging. //! //! For printing, this module uses an internal buffer to write the strings into. @@ -13,15 +17,15 @@ //! None, //! ); //! -//! components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(()); +//! components::debug_writer::DebugWriterComponent::new(uart_mux).finalize(components::debug_writer_component_static!()); //! ``` //! //! The debug queue is optional, if not set in the board it is just ignored. //! You can add one in the board file as follows: //! //! ```ignore -//! let buf = static_init!([u8; 1024], [0; 1024]); -//! components::debug_queue::DebugQueueComponent::new(buf).finalize(()); +//! components::debug_queue::DebugQueueComponent::new() +//! .finalize(components::debug_queue_component_static!()); //! ``` //! //! Example @@ -60,6 +64,7 @@ use crate::hil; use crate::platform::chip::Chip; use crate::process::Process; use crate::process::ProcessPrinter; +use crate::processbuffer::ReadableProcessSlice; use crate::utilities::binary_write::BinaryToWriteWrapper; use crate::utilities::cells::NumericCellExt; use crate::utilities::cells::{MapCell, TakeCell}; @@ -69,21 +74,23 @@ use crate::ErrorCode; /// core::fmt::Write), but io::Write isn't available in no_std (due to std::io::Error not being /// available). /// -/// Also, in our use cases, writes are infaillible, so the write function just doesn't return -/// anything. +/// Also, in our use cases, writes are infaillible, so the write function cannot return an Error, +/// however it might not be able to write everything, so it returns the number of bytes written. /// /// See also the tracking issue: pub trait IoWrite { - fn write(&mut self, buf: &[u8]); + fn write(&mut self, buf: &[u8]) -> usize; - fn write_ring_buffer<'a>(&mut self, buf: &RingBuffer<'a, u8>) { + fn write_ring_buffer(&mut self, buf: &RingBuffer<'_, u8>) -> usize { let (left, right) = buf.as_slices(); + let mut total = 0; if let Some(slice) = left { - self.write(slice); + total += self.write(slice); } if let Some(slice) = right { - self.write(slice); + total += self.write(slice); } + total } } @@ -110,10 +117,20 @@ pub unsafe fn panic_print( process_printer: &'static Option<&'static PP>, ) { panic_begin(nop); - panic_banner(writer, panic_info); // Flush debug buffer if needed flush(writer); + panic_banner(writer, panic_info); panic_cpu_state(chip, writer); + + // Some systems may enforce memory protection regions for the + // kernel, making application memory inaccessible. However, + // priting process information will attempt to access memory. If + // we are provided a chip reference, attempt to disable userspace + // memory protection first: + chip.map(|c| { + use crate::platform::mpu::MPU; + c.mpu().disable_app_mpu() + }); panic_process_info(processes, process_printer, writer); } @@ -324,14 +341,14 @@ pub fn debug_enqueue_fmt(args: Arguments) { pub fn debug_flush_queue_() { let writer = unsafe { get_debug_writer() }; - unsafe { DEBUG_QUEUE.as_deref_mut() }.map(|buffer| { + if let Some(buffer) = unsafe { DEBUG_QUEUE.as_deref_mut() } { buffer.dw.map(|dw| { dw.ring_buffer.map(|ring_buffer| { writer.write_ring_buffer(ring_buffer); ring_buffer.empty(); }); }); - }); + } } /// This macro prints a new line to an internal ring buffer, the contents of @@ -412,7 +429,7 @@ impl DebugWriter { internal_buffer: &'static mut RingBuffer<'static, u8>, ) -> DebugWriter { DebugWriter { - uart: uart, + uart, output_buffer: TakeCell::new(out_buffer), internal_buffer: TakeCell::new(internal_buffer), count: Cell::new(0), // how many debug! calls @@ -428,11 +445,11 @@ impl DebugWriter { } /// Write as many of the bytes from the internal_buffer to the output - /// mechanism as possible. - fn publish_bytes(&self) { + /// mechanism as possible, returning the number written. + fn publish_bytes(&self) -> usize { // Can only publish if we have the output_buffer. If we don't that is // fine, we will do it when the transmit done callback happens. - self.internal_buffer.map(|ring_buffer| { + self.internal_buffer.map_or(0, |ring_buffer| { if let Some(out_buffer) = self.output_buffer.take() { let mut count = 0; @@ -456,13 +473,20 @@ impl DebugWriter { self.output_buffer.put(None); } } + count + } else { + 0 } - }); + }) } fn extract(&self) -> Option<&mut RingBuffer<'static, u8>> { self.internal_buffer.take() } + + fn available_len(&self) -> usize { + self.internal_buffer.map_or(0, |rb| rb.available_len()) + } } impl hil::uart::TransmitClient for DebugWriter { @@ -495,22 +519,26 @@ impl DebugWriterWrapper { self.dw.map_or(0, |dw| dw.get_count()) } - fn publish_bytes(&self) { - self.dw.map(|dw| { - dw.publish_bytes(); - }); + fn publish_bytes(&self) -> usize { + self.dw.map_or(0, |dw| dw.publish_bytes()) } fn extract(&self) -> Option<&mut RingBuffer<'static, u8>> { self.dw.map_or(None, |dw| dw.extract()) } + + fn available_len(&self) -> usize { + const FULL_MSG: &[u8] = b"\n*** DEBUG BUFFER FULL ***\n"; + self.dw + .map_or(0, |dw| dw.available_len().saturating_sub(FULL_MSG.len())) + } } impl IoWrite for DebugWriterWrapper { - fn write(&mut self, bytes: &[u8]) { + fn write(&mut self, bytes: &[u8]) -> usize { const FULL_MSG: &[u8] = b"\n*** DEBUG BUFFER FULL ***\n"; - self.dw.map(|dw| { - dw.internal_buffer.map(|ring_buffer| { + self.dw.map_or(0, |dw| { + dw.internal_buffer.map_or(0, |ring_buffer| { let available_len_for_msg = ring_buffer.available_len().saturating_sub(FULL_MSG.len()); @@ -518,6 +546,7 @@ impl IoWrite for DebugWriterWrapper { for &b in bytes { ring_buffer.enqueue(b); } + bytes.len() } else { for &b in &bytes[..available_len_for_msg] { ring_buffer.enqueue(b); @@ -527,9 +556,10 @@ impl IoWrite for DebugWriterWrapper { for &b in FULL_MSG { ring_buffer.enqueue(b); } + available_len_for_msg } - }); - }); + }) + }) } } @@ -555,6 +585,27 @@ pub fn debug_println(args: Arguments) { writer.publish_bytes(); } +pub fn debug_slice(slice: &ReadableProcessSlice) -> usize { + let writer = unsafe { get_debug_writer() }; + let mut total = 0; + for b in slice.iter() { + let buf: [u8; 1] = [b.get(); 1]; + let count = writer.write(&buf); + if count > 0 { + total += count; + } else { + break; + } + } + writer.publish_bytes(); + total +} + +pub fn debug_available_len() -> usize { + let writer = unsafe { get_debug_writer() }; + writer.available_len() +} + fn write_header(writer: &mut DebugWriterWrapper, (file, line): &(&'static str, u32)) -> Result { writer.increment_count(); let count = writer.get_count(); @@ -586,13 +637,21 @@ macro_rules! debug { debug!("") }); ($msg:expr $(,)?) => ({ - $crate::debug::debug_println(format_args!($msg)) + $crate::debug::debug_println(format_args!($msg)); }); ($fmt:expr, $($arg:tt)+) => ({ - $crate::debug::debug_println(format_args!($fmt, $($arg)+)) + $crate::debug::debug_println(format_args!($fmt, $($arg)+)); }); } +/// In-kernel `println()` debugging that can take a process slice. +#[macro_export] +macro_rules! debug_process_slice { + ($msg:expr $(,)?) => {{ + $crate::debug::debug_slice($msg) + }}; +} + /// In-kernel `println()` debugging with filename and line numbers. #[macro_export] macro_rules! debug_verbose { @@ -616,18 +675,40 @@ macro_rules! debug_verbose { }); } -pub trait Debug { - fn write(&self, buf: &'static mut [u8], len: usize); +#[macro_export] +/// Prints out the expression and its location, then returns it. +/// +/// ```rust,ignore +/// let foo: u8 = debug_expr!(0xff); +/// // Prints [main.rs:2] 0xff = 255 +/// ``` +/// Taken straight from Rust std::dbg. +macro_rules! debug_expr { + // NOTE: We cannot use `concat!` to make a static string as a format argument + // of `eprintln!` because `file!` could contain a `{` or + // `$val` expression could be a block (`{ .. }`), in which case the `eprintln!` + // will be malformed. + () => { + $crate::debug!("[{}:{}]", file!(), line!()) + }; + ($val:expr $(,)?) => { + // Use of `match` here is intentional because it affects the lifetimes + // of temporaries - https://stackoverflow.com/a/48732525/1063961 + match $val { + tmp => { + $crate::debug!("[{}:{}] {} = {:#?}", + file!(), line!(), stringify!($val), &tmp); + tmp + } + } + }; + ($($val:expr),+ $(,)?) => { + ($($crate::debug_expr!($val)),+,) + }; } -#[cfg(debug = "true")] -impl Default for Debug { - fn write(&self, buf: &'static mut [u8], len: usize) { - panic!( - "No registered kernel debug printer. Thrown printing {:?}", - buf - ); - } +pub trait Debug { + fn write(&self, buf: &'static mut [u8], len: usize) -> usize; } pub unsafe fn flush(writer: &mut W) { diff --git a/kernel/src/deferred_call.rs b/kernel/src/deferred_call.rs index 70700d59c7..b26469cb02 100644 --- a/kernel/src/deferred_call.rs +++ b/kernel/src/deferred_call.rs @@ -1,84 +1,252 @@ -//! Deferred call mechanism. +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Hardware-independent kernel interface for deferred calls +//! +//! This allows any struct in the kernel which implements +//! [DeferredCallClient] +//! to set and receive deferred calls, Tock's version of software +//! interrupts. +//! +//! These can be used to implement long-running in-kernel algorithms +//! or software devices that are supposed to work like hardware devices. +//! Essentially, this allows the chip to handle more important interrupts, +//! and lets a kernel component return the function call stack up to the scheduler, +//! automatically being called again. +//! +//! Usage +//! ----- +//! +//! The `DEFCALLS` array size determines how many +//! [DeferredCall]s +//! may be registered. By default this is set to 32. +//! To support more deferred calls, this file would need to be modified +//! to use a larger variable for BITMASK (e.g. BITMASK could be a u64 +//! and the array size increased to 64). +//! If more than 32 deferred calls are created, the kernel will panic +//! at the beginning of the kernel loop. +//! +//! ```rust +//! use kernel::deferred_call::{DeferredCall, DeferredCallClient}; +//! use kernel::static_init; +//! +//! struct SomeCapsule { +//! deferred_call: DeferredCall +//! } +//! impl SomeCapsule { +//! pub fn new() -> Self { +//! Self { +//! deferred_call: DeferredCall::new(), +//! } +//! } +//! } +//! impl DeferredCallClient for SomeCapsule { +//! fn handle_deferred_call(&self) { +//! // Your action here +//! } +//! +//! fn register(&'static self) { +//! self.deferred_call.register(self); +//! } +//! } //! -//! This is a tool to allow chip peripherals to schedule "interrupts" -//! in the chip scheduler if the hardware doesn't support interrupts where -//! they are needed. - -use core::cell::UnsafeCell; -use core::convert::Into; -use core::convert::TryFrom; -use core::convert::TryInto; -use core::intrinsics; +//! // main.rs or your component must register the capsule with +//! // its deferred call. +//! // This should look like: +//! let some_capsule = unsafe { static_init!(SomeCapsule, SomeCapsule::new()) }; +//! some_capsule.register(); +//! ``` + +use crate::utilities::cells::OptionalCell; +use core::cell::Cell; use core::marker::Copy; -use core::marker::Sync; - -/// AtomicUsize with no CAS operations that works on targets that have "no atomic -/// support" according to their specification. This makes it work on thumbv6 -/// platforms. -/// -/// Borrowed from https://github.com/japaric/heapless/blob/master/src/ring_buffer/mod.rs -/// See: https://github.com/japaric/heapless/commit/37c8b5b63780ed8811173dc1ec8859cd99efa9ad -struct AtomicUsize { - v: UnsafeCell, +use core::marker::PhantomData; +use core::ptr::addr_of; + +// This trait is not intended to be used as a trait object; +// e.g. you should not create a `&dyn DeferredCallClient`. +// The `Sized` supertrait prevents this. +/// This trait should be implemented by clients which need to +/// receive DeferredCalls +pub trait DeferredCallClient: Sized { + fn handle_deferred_call(&self); + fn register(&'static self); // This function should be implemented as + // `self.deferred_call.register(&self);` +} + +/// This struct serves as a lightweight alternative to the use of trait objects +/// (e.g. `&dyn DeferredCall`). Using a trait object, will include a 20 byte vtable +/// per instance, but this alternative stores only the data and function pointers, +/// 8 bytes per instance. +#[derive(Copy, Clone)] +struct DynDefCallRef<'a> { + data: *const (), + callback: fn(*const ()), + _lifetime: PhantomData<&'a ()>, } -impl AtomicUsize { - pub(crate) const fn new(v: usize) -> AtomicUsize { - AtomicUsize { - v: UnsafeCell::new(v), +impl<'a> DynDefCallRef<'a> { + // SAFETY: We define the callback function as being a closure which casts + // the passed pointer to be the appropriate type (a pointer to `T`) + // and then calls `T::handle_deferred_call()`. In practice, the closure + // is optimized away by LLVM when the ABI of the closure and the underlying function + // are identical, making this zero-cost, but saving us from having to trust + // that `fn(*const ())` and `fn handle_deferred_call(&self)` will always have the same calling + // convention for any type. + fn new(x: &'a T) -> Self { + Self { + data: core::ptr::from_ref(x) as *const (), + callback: |p| unsafe { T::handle_deferred_call(&*p.cast()) }, + _lifetime: PhantomData, } } +} - pub(crate) fn load_relaxed(&self) -> usize { - unsafe { intrinsics::atomic_load_relaxed(self.v.get()) } +impl DynDefCallRef<'_> { + // more efficient pass by `self` if we don't have to implement `DeferredCallClient` directly + fn handle_deferred_call(self) { + (self.callback)(self.data) } +} - pub(crate) fn store_relaxed(&self, val: usize) { - unsafe { intrinsics::atomic_store_relaxed(self.v.get(), val) } - } +// The below constant lets us get around Rust not allowing short array initialization +// for non-default types +const EMPTY: OptionalCell> = OptionalCell::empty(); - pub(crate) fn fetch_or_relaxed(&self, val: usize) { - unsafe { intrinsics::atomic_store_relaxed(self.v.get(), self.load_relaxed() | val) } - } -} +// All 3 of the below global statics are accessed only in this file, and all accesses +// are via immutable references. Tock is single threaded, so each will only ever be +// accessed via an immutable reference from the single kernel thread. +// TODO: Once Tock decides on an approach to replace `static mut` with some sort of +// `SyncCell`, migrate all three of these to that approach +// (https://github.com/tock/tock/issues/1545) +/// Counter for the number of deferred calls that have been created, this is +/// used to track that no more than 32 deferred calls have been created. +static mut CTR: Cell = Cell::new(0); -unsafe impl Sync for AtomicUsize {} +/// This bitmask tracks which of the up to 32 existing deferred calls have been scheduled. +/// Any bit that is set in that mask indicates the deferred call with its `idx` field set +/// to the index of that bit has been scheduled and not yet serviced. +static mut BITMASK: Cell = Cell::new(0); -static DEFERRED_CALL: AtomicUsize = AtomicUsize::new(0); +// This is a 256 byte array, but at least resides in .bss +/// An array that stores references to up to 32 `DeferredCall`s via the low-cost +/// `DynDefCallRef`. +static mut DEFCALLS: [OptionalCell>; 32] = [EMPTY; 32]; -/// Are there any pending `DeferredCall`s? -pub fn has_tasks() -> bool { - DEFERRED_CALL.load_relaxed() != 0 +pub struct DeferredCall { + idx: usize, } -/// Represents a way to generate an asynchronous call without a hardware -/// interrupt. Supports up to 32 possible deferrable tasks. -pub struct DeferredCall(T); - -impl + TryFrom + Copy> DeferredCall { - /// Creates a new DeferredCall - /// - /// Only create one per task, preferably in the module that it will be used - /// in. - pub const unsafe fn new(task: T) -> Self { - DeferredCall(task) +impl DeferredCall { + /// Creates a new deferred call with a unique ID. + pub fn new() -> Self { + // SAFETY: No accesses to CTR are via an &mut, and the Tock kernel is + // single-threaded so all accesses will occur from this thread. + let ctr = unsafe { &*addr_of!(CTR) }; + let idx = ctr.get() + 1; + ctr.set(idx); + DeferredCall { idx } + } + + // To reduce monomorphization bloat, the non-generic portion of register is moved into this + // function without generic parameters. + #[inline(never)] + fn register_internal_non_generic(&self, handler: DynDefCallRef<'static>) { + // SAFETY: No accesses to DEFCALLS are via an &mut, and the Tock kernel is + // single-threaded so all accesses will occur from this thread. + let defcalls = unsafe { &*addr_of!(DEFCALLS) }; + if self.idx >= defcalls.len() { + // This error will be caught by the scheduler at the beginning of the kernel loop, + // which is much better than panicking here, before the debug writer is setup. + // Also allows a single panic for creating too many deferred calls instead + // of NUM_DCS panics (this function is monomorphized). + return; + } + defcalls[self.idx].set(handler); + } + + /// This function registers the passed client with this deferred call, such + /// that calls to `DeferredCall::set()` will schedule a callback on the + /// `handle_deferred_call()` method of the passed client. + pub fn register(&self, client: &'static DC) { + let handler = DynDefCallRef::new(client); + self.register_internal_non_generic(handler); } - /// Set the `DeferredCall` as pending + /// Schedule a deferred callback on the client associated with this deferred call pub fn set(&self) { - DEFERRED_CALL.fetch_or_relaxed(1 << self.0.into() as usize); + // SAFETY: No accesses to BITMASK are via an &mut, and the Tock kernel is + // single-threaded so all accesses will occur from this thread. + let bitmask = unsafe { &*addr_of!(BITMASK) }; + bitmask.set(bitmask.get() | (1 << self.idx)); } - /// Gets and clears the next pending `DeferredCall` - pub fn next_pending() -> Option { - let val = DEFERRED_CALL.load_relaxed(); + /// Check if a deferred callback has been set and not yet serviced on this deferred call. + pub fn is_pending(&self) -> bool { + // SAFETY: No accesses to BITMASK are via an &mut, and the Tock kernel is + // single-threaded so all accesses will occur from this thread. + let bitmask = unsafe { &*addr_of!(BITMASK) }; + bitmask.get() & (1 << self.idx) == 1 + } + + /// Services and clears the next pending `DeferredCall`, returns which index + /// was serviced + pub fn service_next_pending() -> Option { + // SAFETY: No accesses to BITMASK/DEFCALLS are via an &mut, and the Tock kernel is + // single-threaded so all accesses will occur from this thread. + let bitmask = unsafe { &*addr_of!(BITMASK) }; + let defcalls = unsafe { &*addr_of!(DEFCALLS) }; + let val = bitmask.get(); if val == 0 { None } else { let bit = val.trailing_zeros() as usize; let new_val = val & !(1 << bit); - DEFERRED_CALL.store_relaxed(new_val); - bit.try_into().ok() + bitmask.set(new_val); + defcalls[bit].map(|dc| { + dc.handle_deferred_call(); + bit + }) + } + } + + /// Returns true if any deferred calls are waiting to be serviced, + /// false otherwise. + pub fn has_tasks() -> bool { + // SAFETY: No accesses to BITMASK are via an &mut, and the Tock kernel is + // single-threaded so all accesses will occur from this thread. + let bitmask = unsafe { &*addr_of!(BITMASK) }; + bitmask.get() != 0 + } + + /// This function should be called at the beginning of the kernel loop + /// to verify that deferred calls have been correctly initialized. This function + /// verifies two things: + /// 1. That <= `DEFCALLS.len()` deferred calls have been created, which is the + /// maximum this interface supports + /// 2. That exactly as many deferred calls were registered as were created, which helps to + /// catch bugs if board maintainers forget to call `register()` on a created `DeferredCall`. + /// Neither of these checks are necessary for soundness, but they are necessary for confirming + /// that DeferredCalls will actually be delivered as expected. This function costs about 300 + /// bytes, so you can remove it if you are confident your setup will not exceed 32 deferred + /// calls, and that all of your components register their deferred calls. + // Ignore the clippy warning for using `.filter(|opt| opt.is_some())` since + // we don't actually have an Option (we have an OptionalCell) and + // IntoIterator is not implemented for OptionalCell. + #[allow(clippy::iter_filter_is_some)] + pub fn verify_setup() { + // SAFETY: No accesses to CTR/DEFCALLS are via an &mut, and the Tock kernel is + // single-threaded so all accesses will occur from this thread. + let ctr = unsafe { &*addr_of!(CTR) }; + let defcalls = unsafe { &*addr_of!(DEFCALLS) }; + let num_deferred_calls = ctr.get(); + let num_registered_calls = defcalls.iter().filter(|opt| opt.is_some()).count(); + if num_deferred_calls >= defcalls.len() || num_registered_calls != num_deferred_calls { + panic!( + "ERROR: {} deferred calls, {} registered. A component may have forgotten to register a deferred call.", + num_deferred_calls, num_registered_calls + ); } } } diff --git a/kernel/src/dynamic_deferred_call.rs b/kernel/src/dynamic_deferred_call.rs deleted file mode 100644 index 4aed6f29c5..0000000000 --- a/kernel/src/dynamic_deferred_call.rs +++ /dev/null @@ -1,265 +0,0 @@ -//! Hardware-independent kernel interface for deferred calls -//! -//! This allows any struct in the kernel which implements -//! [DynamicDeferredCallClient](crate::dynamic_deferred_call::DynamicDeferredCallClient) -//! to set and receive deferred calls. -//! -//! These can be used to implement long-running in-kernel algorithms -//! or software devices that are supposed to work like hardware devices. -//! Essentially, this allows the chip to handle more important interrupts, -//! and lets a kernel component return the function call stack up to the scheduler, -//! automatically being called again. -//! -//! Usage -//! ----- -//! -//! The `dynamic_deferred_call_clients` array size determines how many -//! [DeferredCallHandle](crate::dynamic_deferred_call::DeferredCallHandle)s -//! may be registered with the instance. -//! When no more slots are available, -//! `dynamic_deferred_call.register(some_client)` will return `None`. -//! -//! ``` -//! # use core::cell::Cell; -//! # use kernel::utilities::cells::OptionalCell; -//! # use kernel::static_init; -//! use kernel::dynamic_deferred_call::{ -//! DynamicDeferredCall, -//! DynamicDeferredCallClient, -//! DynamicDeferredCallClientState, -//! }; -//! -//! let dynamic_deferred_call_clients = unsafe { static_init!( -//! [DynamicDeferredCallClientState; 2], -//! Default::default() -//! ) }; -//! let dynamic_deferred_call = unsafe { static_init!( -//! DynamicDeferredCall, -//! DynamicDeferredCall::new(dynamic_deferred_call_clients) -//! ) }; -//! assert!(unsafe { DynamicDeferredCall::set_global_instance(dynamic_deferred_call) } == true); -//! -//! # struct SomeCapsule; -//! # impl SomeCapsule { -//! # pub fn new(_ddc: &'static DynamicDeferredCall) -> Self { SomeCapsule } -//! # pub fn set_deferred_call_handle( -//! # &self, -//! # _handle: kernel::dynamic_deferred_call::DeferredCallHandle, -//! # ) { } -//! # } -//! # impl DynamicDeferredCallClient for SomeCapsule { -//! # fn call( -//! # &self, -//! # _handle: kernel::dynamic_deferred_call::DeferredCallHandle, -//! # ) { } -//! # } -//! # -//! // Here you can register custom capsules, etc. -//! // This could look like: -//! let some_capsule = unsafe { static_init!( -//! SomeCapsule, -//! SomeCapsule::new(dynamic_deferred_call) -//! ) }; -//! some_capsule.set_deferred_call_handle( -//! dynamic_deferred_call.register(some_capsule).unwrap() // Unwrap fail = no deferred call slot available -//! ); -//! ``` - -use core::cell::Cell; - -use crate::utilities::cells::OptionalCell; - -/// Kernel-global dynamic deferred call instance -/// -/// This gets called by the kernel scheduler automatically and is accessible -/// through `unsafe` static functions on the `DynamicDeferredCall` struct -static mut DYNAMIC_DEFERRED_CALL: Option<&'static DynamicDeferredCall> = None; - -/// Internal per-client state tracking for the [DynamicDeferredCall] -pub struct DynamicDeferredCallClientState { - scheduled: Cell, - client: OptionalCell<&'static dyn DynamicDeferredCallClient>, -} -impl Default for DynamicDeferredCallClientState { - fn default() -> DynamicDeferredCallClientState { - DynamicDeferredCallClientState { - scheduled: Cell::new(false), - client: OptionalCell::empty(), - } - } -} - -/// Dynamic deferred call -/// -/// This struct manages and calls dynamically (at runtime) registered -/// deferred calls from capsules and other kernel structures. -/// -/// It has a fixed number of possible clients, which -/// is determined by the `clients`-array passed in with the constructor. -pub struct DynamicDeferredCall { - client_states: &'static [DynamicDeferredCallClientState], - handle_counter: Cell, - call_pending: Cell, -} - -impl DynamicDeferredCall { - /// Construct a new dynamic deferred call implementation - /// - /// This needs to be registered with the `set_global_instance` function immediately - /// afterwards, and should not be changed anymore. Only the globally registered - /// instance will receive calls from the kernel scheduler. - /// - /// The `clients` array can be initialized using the implementation of [Default] - /// for the [DynamicDeferredCallClientState]. - pub fn new(client_states: &'static [DynamicDeferredCallClientState]) -> DynamicDeferredCall { - DynamicDeferredCall { - client_states, - handle_counter: Cell::new(0), - call_pending: Cell::new(false), - } - } - - /// Sets a global [DynamicDeferredCall] instance - /// - /// This is required before any deferred calls can be retrieved. - /// It may be called only once. Returns `true` if the global instance - /// was successfully registered. - pub unsafe fn set_global_instance(ddc: &'static DynamicDeferredCall) -> bool { - // If the returned reference is identical to the instance argument, - // it is set in the option. Otherwise, a different instance is - // already registered and will not be replaced. - (*DYNAMIC_DEFERRED_CALL.get_or_insert(ddc)) as *const _ == ddc as *const _ - } - - /// Call the globally registered instance - /// - /// Returns `true` if a global instance was registered and has been called. - pub unsafe fn call_global_instance() -> bool { - DYNAMIC_DEFERRED_CALL.map(|ddc| ddc.call()).is_some() - } - - /// Call the globally registered instance while the supplied predicate - /// returns `true`. - /// - /// Returns `true` if a global instance was registered and has been called. - pub unsafe fn call_global_instance_while bool>(f: F) -> bool { - DYNAMIC_DEFERRED_CALL - .map(move |ddc| ddc.call_while(f)) - .is_some() - } - - /// Check if one or more dynamic deferred calls are pending in the - /// globally registered instance - /// - /// Returns `None` if no global instance has been registered, or `Some(true)` - /// if the registered instance has one or more pending deferred calls. - pub unsafe fn global_instance_calls_pending() -> Option { - DYNAMIC_DEFERRED_CALL.map(|ddc| ddc.has_pending()) - } - - /// Schedule a deferred call to be called - /// - /// The handle addresses the client that will be called. - /// - /// If no client for the handle is found (it was unregistered), this - /// returns `None`. If a call is already scheduled, it returns - /// `Some(false)`. - pub fn set(&self, handle: DeferredCallHandle) -> Option { - let DeferredCallHandle(client_pos) = handle; - let client_state = &self.client_states[client_pos]; - - if let (call_set, true) = (&client_state.scheduled, client_state.client.is_some()) { - if call_set.get() { - // Already set - Some(false) - } else { - call_set.set(true); - self.call_pending.set(true); - Some(true) - } - } else { - None - } - } - - /// Register a new client - /// - /// On success, a `Some(handle)` will be returned. This handle is later - /// required to schedule a deferred call. - pub fn register( - &self, - ddc_client: &'static dyn DynamicDeferredCallClient, - ) -> Option { - let current_counter = self.handle_counter.get(); - - if current_counter < self.client_states.len() { - let client_state = &self.client_states[current_counter]; - client_state.scheduled.set(false); - client_state.client.set(ddc_client); - - self.handle_counter.set(current_counter + 1); - - Some(DeferredCallHandle(current_counter)) - } else { - None - } - } - - /// Check if one or more deferred calls are pending - /// - /// Returns `true` if one or more deferred calls are pending. - pub fn has_pending(&self) -> bool { - self.call_pending.get() - } - - /// Call all registered and to-be-scheduled deferred calls - /// - /// It may be called without holding the `DynamicDeferredCall` reference through - /// `call_global_instance`. - pub(self) fn call(&self) { - self.call_while(|| true) - } - - /// Call all registered and to-be-scheduled deferred calls while the supplied - /// predicate returns `true`. - /// - /// It may be called without holding the `DynamicDeferredCall` reference through - /// `call_global_instance_while`. - pub(self) fn call_while bool>(&self, f: F) { - if self.call_pending.get() { - for (i, client_state) in self.client_states.iter().enumerate() { - if !f() { - break; - } - if client_state.scheduled.get() { - client_state.client.map(|client| { - client_state.scheduled.set(false); - client.call(DeferredCallHandle(i)); - }); - } - } - - // Recompute call_pending here, as some deferred calls may have been skipped due to the - // `f` predicate becoming false. - self.call_pending.set( - self.client_states - .iter() - .any(|client_state| client_state.scheduled.get()), - ); - } - } -} - -/// Client for the -/// [DynamicDeferredCall](crate::dynamic_deferred_call::DynamicDeferredCall) -/// -/// This trait needs to be implemented for some struct to receive -/// deferred calls from a `DynamicDeferredCall`. -pub trait DynamicDeferredCallClient { - fn call(&self, handle: DeferredCallHandle); -} - -/// Unique identifier for a deferred call registered with a -/// [DynamicDeferredCall](crate::dynamic_deferred_call::DynamicDeferredCall) -#[derive(Copy, Clone, Debug)] -pub struct DeferredCallHandle(usize); diff --git a/kernel/src/errorcode.rs b/kernel/src/errorcode.rs index 22cdcbb93a..887140617b 100644 --- a/kernel/src/errorcode.rs +++ b/kernel/src/errorcode.rs @@ -1,13 +1,13 @@ -//! Standard errors in Tock. +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. -use core::convert::TryFrom; +//! Standard errors in Tock. /// Standard errors in Tock. /// -/// In contrast to [`Result<(), ErrorCode>`](crate::Result<(), ErrorCode>) this -/// does not feature any success cases and is therefore more appropriate for the -/// Tock 2.0 system call interface, where success payloads and errors are not -/// packed into the same 32-bit wide register. +/// Each error code is assigned a fixed [`usize`] nonzero number. In effect, 0 +/// is reserved for "no error" / "success". #[derive(Clone, Copy, Debug, PartialEq, Eq)] #[repr(usize)] pub enum ErrorCode { @@ -101,22 +101,22 @@ impl From for Result<(), ErrorCode> { /// easily passed across the syscall interface between the kernel and /// userspace. /// -/// 2. It extends ErrorCode, but keeps the same error-to-number mappings as -/// ErrorCode. For example, in both StatusCode and ErrorCode, the `SIZE` +/// 2. It extends [`ErrorCode`], but keeps the same error-to-number mappings as +/// ErrorCode. For example, in both StatusCode and [`ErrorCode`], the `SIZE` /// error is always represented as 7. /// -/// 3. It can encode success values, whereas ErrorCode can only encode errors. -/// Number 0 in ErrorCode is reserved, and is used for `SUCCESS` in -/// StatusCode. +/// 3. It can encode success values, whereas [`ErrorCode`] can only encode +/// errors. Number 0 in [`ErrorCode`] is reserved, and is used for `SUCCESS` +/// in StatusCode. /// /// This helper function converts the Tock and Rust convention for a /// success/error type to a StatusCode. StatusCode is represented as a usize /// which is sufficient to send to userspace via an upcall. /// /// The key to this conversion and portability between the kernel and userspace -/// is that `ErrorCode`, which only expresses errors, is assigned fixed values, -/// but does not use value 0 by convention. This allows us to use 0 as success -/// in ReturnCode. +/// is that [`ErrorCode`], which only expresses errors, is assigned fixed +/// values, but does not use value 0 by convention. This allows us to use 0 as +/// success in StatusCode. pub fn into_statuscode(r: Result<(), ErrorCode>) -> usize { match r { Ok(()) => 0, diff --git a/kernel/src/grant.rs b/kernel/src/grant.rs index 92f0404af9..2788bbd7c9 100644 --- a/kernel/src/grant.rs +++ b/kernel/src/grant.rs @@ -1,26 +1,28 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Support for processes granting memory from their allocations to the kernel. //! -//! -//! //! ## Grant Overview //! //! Grants allow capsules to dynamically allocate memory from a process to hold //! state on the process's behalf. //! -//! Each capsule that wishes to do this needs to have a `Grant` type. `Grant`s +//! Each capsule that wishes to do this needs to have a [`Grant`] type. Grants //! are created at boot, and each have a unique ID and a type `T`. This type //! only allows the capsule to allocate memory from a process in the future. It //! does not initially represent any allocated memory. //! -//! When a capsule does wish to use its `Grant` to allocate memory from a -//! process, it must "enter" the `Grant` with a specific `ProcessId`. Entering a -//! `Grant` for a specific process instructs the core kernel to create an object -//! `T` in the process's memory space and provide the capsule with access to it. -//! If the `Grant` has not previously been entered for that process, the memory -//! for object `T` will be allocated from the "grant region" within the +//! When a capsule does wish to use its Grant to allocate memory from a process, +//! it must "enter" the Grant with a specific [`ProcessId`]. Entering a Grant +//! for a specific process instructs the core kernel to create an object `T` in +//! the process's memory space and provide the capsule with access to it. If the +//! Grant has not previously been entered for that process, the memory for +//! object `T` will be allocated from the "grant region" within the //! kernel-accessible portion of the process's memory. //! -//! If a `Grant` has never been entered for a process, the object `T` will _not_ +//! If a Grant has never been entered for a process, the object `T` will _not_ //! be allocated in that process's grant region, even if the `Grant` has been //! entered for other processes. //! @@ -29,13 +31,13 @@ //! references are stored outside of the `T` object to enable the kernel to //! manage them and ensure swapping guarantees are met. //! -//! The type `T` of a `Grant` is fixed in size and the number of upcalls and -//! allowed buffers associated with a grant is fixed. That is, when a `Grant` is +//! The type `T` of a Grant is fixed in size and the number of upcalls and +//! allowed buffers associated with a grant is fixed. That is, when a Grant is //! entered for a process the resulting allocated object will be the size of //! `SizeOf` plus the size for the structure to hold upcalls and allowed //! buffer references. If capsules need additional process-specific memory for -//! their operation, they can use an `Allocator` to request additional memory -//! from the process's grant region. +//! their operation, they can use an [`GrantRegionAllocator`] to request +//! additional memory from the process's grant region. //! //! ```text,ignore //! ┌──────────────────┐ @@ -132,7 +134,7 @@ use core::ptr::{write, NonNull}; use core::slice; use crate::kernel::Kernel; -use crate::process::{Error, Process, ProcessCustomGrantIdentifer, ProcessId}; +use crate::process::{Error, Process, ProcessCustomGrantIdentifier, ProcessId}; use crate::processbuffer::{ReadOnlyProcessBuffer, ReadWriteProcessBuffer}; use crate::processbuffer::{ReadOnlyProcessBufferRef, ReadWriteProcessBufferRef}; use crate::upcall::{Upcall, UpcallError, UpcallId}; @@ -141,38 +143,38 @@ use crate::ErrorCode; /// Tracks how many upcalls a grant instance supports automatically. pub trait UpcallSize { /// The number of upcalls the grant supports. - const COUNT: usize; + const COUNT: u8; } /// Specifies how many upcalls a grant instance supports automatically. -pub struct UpcallCount; -impl UpcallSize for UpcallCount { - const COUNT: usize = NUM; +pub struct UpcallCount; +impl UpcallSize for UpcallCount { + const COUNT: u8 = NUM; } /// Tracks how many read-only allows a grant instance supports automatically. pub trait AllowRoSize { /// The number of read-only allows the grant supports. - const COUNT: usize; + const COUNT: u8; } /// Specifies how many read-only allows a grant instance supports automatically. -pub struct AllowRoCount; -impl AllowRoSize for AllowRoCount { - const COUNT: usize = NUM; +pub struct AllowRoCount; +impl AllowRoSize for AllowRoCount { + const COUNT: u8 = NUM; } /// Tracks how many read-write allows a grant instance supports automatically. pub trait AllowRwSize { /// The number of read-write allows the grant supports. - const COUNT: usize; + const COUNT: u8; } /// Specifies how many read-write allows a grant instance supports /// automatically. -pub struct AllowRwCount; -impl AllowRwSize for AllowRwCount { - const COUNT: usize = NUM; +pub struct AllowRwCount; +impl AllowRwSize for AllowRwCount { + const COUNT: u8 = NUM; } /// Helper that calculated offsets within the kernel owned memory (i.e. the @@ -183,52 +185,72 @@ impl AllowRwSize for AllowRwCount { /// ```text,ignore /// 0x003FFC8 ┌────────────────────────────────────┐ /// │ T | -/// 0x003FFxx ├ ───────────────────────── ┐ | -/// │ Padding (ensure T aligns)| | -/// 0x003FF44 ├ ───────────────────────── | | -/// │ SavedAllowRwN | K | +/// 0x003FFxx ├ ───────────────────────── ┐ K | +/// │ Padding (ensure T aligns)| e | +/// 0x003FF44 ├ ───────────────────────── | r | +/// │ SavedAllowRwN | n | /// │ ... | e | G -/// │ SavedAllowRw1 | r | r -/// │ SavedAllowRw0 | n | a -/// 0x003FF44 ├ ───────────────────────── | e | n -/// │ NumAllowRw (usize) | l | t -/// 0x003FF40 ├ ───────────────────────── | | -/// │ SavedAllowRoN | O | M -/// │ ... | w | e -/// │ SavedAllowRo1 | n | m -/// │ SavedAllowRo0 | e | o -/// 0x003FF30 ├ ───────────────────────── | d | r -/// │ NumAllowRo (usize) | | y -/// 0x003FF2C ├ ───────────────────────── | D | 1 -/// │ SavedUpcallN | a | -/// │ ... | t | -/// │ SavedUpcall1 | a | -/// │ SavedUpcall0 | | +/// │ SavedAllowRw1 | l | r +/// │ SavedAllowRw0 | | a +/// 0x003FF44 ├ ───────────────────────── | O | n +/// │ SavedAllowRoN | w | t +/// │ ... | n | +/// │ SavedAllowRo1 | e | M +/// │ SavedAllowRo0 | d | e +/// 0x003FF30 ├ ───────────────────────── | | m +/// │ SavedUpcallN | D | o +/// │ ... | a | r +/// │ SavedUpcall1 | t | y +/// │ SavedUpcall0 | a | /// 0x003FF24 ├ ───────────────────────── | | -/// │ NumUpcalls (usize) | | +/// │ Counters (usize) | | /// 0x003FF20 └────────────────────────────────────┘ /// ``` -struct KernelManagedLayout { - upcalls_num: *mut usize, +/// +/// The counters structure is composed as: +/// +/// ```text,ignore +/// 0 1 2 3 bytes +/// |-------------|-------------|-------------|-------------| +/// | # Upcalls | # RO Allows | # RW Allows | [unused] | +/// |-------------|-------------|-------------|-------------| +/// ``` +/// +/// This type is created whenever a grant is entered, and is responsible for +/// ensuring that the grant is closed when it is no longer used. On `Drop`, we +/// leave the grant. This protects against calling `grant.enter()` without +/// calling the corresponding `grant.leave()`, perhaps due to accidentally using +/// the `?` operator. +struct EnteredGrantKernelManagedLayout<'a> { + /// Leaving a grant is handled through the process implementation, so must + /// keep a reference to the relevant process. + process: &'a dyn Process, + /// The grant number of the entered grant that we want to ensure we leave + /// properly. + grant_num: usize, + + /// The location of the counters structure for the grant. + counters_ptr: *mut usize, + /// Pointer to the array of saved upcalls. upcalls_array: *mut SavedUpcall, - allow_ro_num: *mut usize, + /// Pointer to the array of saved read-only allows. allow_ro_array: *mut SavedAllowRo, - allow_rw_num: *mut usize, + /// Pointer to the array of saved read-write allows. allow_rw_array: *mut SavedAllowRw, } /// Represents the number of the upcall elements in the kernel owned section of /// the grant. #[derive(Copy, Clone)] -struct UpcallItems(usize); +struct UpcallItems(u8); /// Represents the number of the read-only allow elements in the kernel owned /// section of the grant. #[derive(Copy, Clone)] -struct AllowRoItems(usize); +struct AllowRoItems(u8); /// Represents the number of the read-write allow elements in the kernel owned /// section of the grant. #[derive(Copy, Clone)] -struct AllowRwItems(usize); +struct AllowRwItems(u8); /// Represents the size data (in bytes) T within the grant. #[derive(Copy, Clone)] struct GrantDataSize(usize); @@ -236,67 +258,92 @@ struct GrantDataSize(usize); #[derive(Copy, Clone)] struct GrantDataAlign(usize); -impl KernelManagedLayout { - /// Reads the specified pointer as the base of the kernel owned grant - /// region. +impl<'a> EnteredGrantKernelManagedLayout<'a> { + /// Reads the specified pointer as the base of the kernel owned grant region + /// that has previously been initialized. /// /// # Safety /// /// The incoming base pointer must be well aligned and already contain - /// initialized data in the expected form. - unsafe fn read_from_base(base_ptr: *mut u8) -> Self { - let upcalls_num = base_ptr as *mut usize; - let upcalls_array = upcalls_num.add(1) as *mut SavedUpcall; + /// initialized data in the expected form. There must not be any other + /// `EnteredGrantKernelManagedLayout` for the given `base_ptr` at the same + /// time, otherwise multiple mutable references to the same upcall/allow + /// slices could be created. + unsafe fn read_from_base( + base_ptr: NonNull, + process: &'a dyn Process, + grant_num: usize, + ) -> Self { + let counters_ptr = base_ptr.as_ptr() as *mut usize; + let counters_val = counters_ptr.read(); - let allow_ro_num = upcalls_array.add(upcalls_num.read()) as *mut usize; - let allow_ro_array = allow_ro_num.add(1) as *mut SavedAllowRo; + // Parse the counters field for each of the fields + let [_, _, allow_ro_num, upcalls_num] = u32::to_be_bytes(counters_val as u32); - let allow_rw_num = allow_ro_array.add(allow_ro_num.read()) as *mut usize; - let allow_rw_array = allow_rw_num.add(1) as *mut SavedAllowRw; + // Skip over the counter usize, then the stored array of `SavedAllowRo` + // items and `SavedAllowRw` items. + let upcalls_array = counters_ptr.add(1) as *mut SavedUpcall; + let allow_ro_array = upcalls_array.add(upcalls_num as usize) as *mut SavedAllowRo; + let allow_rw_array = allow_ro_array.add(allow_ro_num as usize) as *mut SavedAllowRw; Self { - upcalls_num, + process, + grant_num, + counters_ptr, upcalls_array, - allow_ro_num, allow_ro_array, - allow_rw_num, allow_rw_array, } } - /// Creates a layout from the specified pointer and lengths of arrays. + /// Creates a layout from the specified pointer and lengths of arrays and + /// initializes the kernel owned portion of the layout. /// /// # Safety /// - /// The incoming base pointer must be well aligned but does not need to - /// point to initialized data. - unsafe fn from_counts( - base_ptr: *mut u8, + /// The incoming base pointer must be well aligned and reference enough + /// memory to hold the entire kernel managed grant structure. There must + /// not be any other `EnteredGrantKernelManagedLayout` for + /// the given `base_ptr` at the same time, otherwise multiple mutable + /// references to the same upcall/allow slices could be created. + unsafe fn initialize_from_counts( + base_ptr: NonNull, upcalls_num_val: UpcallItems, allow_ro_num_val: AllowRoItems, + allow_rw_num_val: AllowRwItems, + process: &'a dyn Process, + grant_num: usize, ) -> Self { - let upcalls_num = base_ptr as *mut usize; - let upcalls_array = upcalls_num.add(1) as *mut SavedUpcall; + let counters_ptr = base_ptr.as_ptr() as *mut usize; + + // Create the counters usize value by correctly packing the various + // counts into 8 bit fields. + let counter: usize = + u32::from_be_bytes([0, allow_rw_num_val.0, allow_ro_num_val.0, upcalls_num_val.0]) + as usize; - let allow_ro_num = upcalls_array.add(upcalls_num_val.0) as *mut usize; - let allow_ro_array = allow_ro_num.add(1) as *mut SavedAllowRo; + let upcalls_array = counters_ptr.add(1) as *mut SavedUpcall; + let allow_ro_array = upcalls_array.add(upcalls_num_val.0.into()) as *mut SavedAllowRo; + let allow_rw_array = allow_ro_array.add(allow_ro_num_val.0.into()) as *mut SavedAllowRw; - let allow_rw_num = allow_ro_array.add(allow_ro_num_val.0) as *mut usize; - let allow_rw_array = allow_rw_num.add(1) as *mut SavedAllowRw; + counters_ptr.write(counter); + write_default_array(upcalls_array, upcalls_num_val.0.into()); + write_default_array(allow_ro_array, allow_ro_num_val.0.into()); + write_default_array(allow_rw_array, allow_rw_num_val.0.into()); Self { - upcalls_num, + process, + grant_num, + counters_ptr, upcalls_array, - allow_ro_num, allow_ro_array, - allow_rw_num, allow_rw_array, } } - /// Returns the entire grant size including the kernel own memory, padding, - /// and data for T. Requires that grant_t_align be a power of 2, which is - /// guaranteed from align_of rust calls. + /// Returns the entire grant size including the kernel owned memory, + /// padding, and data for T. Requires that grant_t_align be a power of 2, + /// which is guaranteed from align_of rust calls. fn grant_size( upcalls_num: UpcallItems, allow_ro_num: AllowRoItems, @@ -304,14 +351,14 @@ impl KernelManagedLayout { grant_t_size: GrantDataSize, grant_t_align: GrantDataAlign, ) -> usize { - let kernel_managed_size = 3 * size_of::() - + upcalls_num.0 * size_of::() - + allow_ro_num.0 * size_of::() - + allow_rw_num.0 * size_of::(); + let kernel_managed_size = size_of::() + + upcalls_num.0 as usize * size_of::() + + allow_ro_num.0 as usize * size_of::() + + allow_rw_num.0 as usize * size_of::(); // We know that grant_t_align is a power of 2, so we can make a mask // that will save only the remainder bits. let grant_t_align_mask = grant_t_align.0 - 1; - // Determine padding to get to the next multipe of grant_t_align by + // Determine padding to get to the next multiple of grant_t_align by // taking the remainder and subtracting that from the alignment, then // ensuring a full alignment value maps to 0. let padding = @@ -336,39 +383,144 @@ impl KernelManagedLayout { /// least the alignment of T and points to a grant that is of size /// grant_size bytes. unsafe fn offset_of_grant_data_t( - base_ptr: *mut u8, + base_ptr: NonNull, grant_size: usize, grant_t_size: GrantDataSize, ) -> NonNull { // The location of the grant data T is the last element in the entire // grant region. Caller must verify that memory is accessible and well // aligned to T. - NonNull::new_unchecked(base_ptr.add(grant_size - grant_t_size.0)) + let grant_t_size_usize: usize = grant_t_size.0; + NonNull::new_unchecked(base_ptr.as_ptr().add(grant_size - grant_t_size_usize)) + } + + /// Read an 8 bit value from the counter field offset by the specified + /// number of bits. This is a helper function for reading the counter field. + fn get_counter_offset(&self, offset_bits: usize) -> usize { + // # Safety + // + // Creating a `EnteredGrantKernelManagedLayout` object requires that the + // pointers are well aligned and point to valid memory. + let counters_val = unsafe { self.counters_ptr.read() }; + (counters_val >> offset_bits) & 0xFF + } + + /// Return the number of upcalls stored by the core kernel for this grant. + fn get_upcalls_number(&self) -> usize { + self.get_counter_offset(0) + } + + /// Return the number of read-only allow buffers stored by the core kernel + /// for this grant. + fn get_allow_ro_number(&self) -> usize { + self.get_counter_offset(8) + } + + /// Return the number of read-write allow buffers stored by the core kernel + /// for this grant. + fn get_allow_rw_number(&self) -> usize { + self.get_counter_offset(16) + } + + /// Return mutable access to the slice of stored upcalls for this grant. + /// This is necessary for storing a new upcall. + fn get_upcalls_slice(&mut self) -> &mut [SavedUpcall] { + // # Safety + // + // Creating a `EnteredGrantKernelManagedLayout` object ensures that the + // pointer to the upcall array is valid. + unsafe { slice::from_raw_parts_mut(self.upcalls_array, self.get_upcalls_number()) } + } + + /// Return mutable access to the slice of stored read-only allow buffers for + /// this grant. This is necessary for storing a new read-only allow. + fn get_allow_ro_slice(&mut self) -> &mut [SavedAllowRo] { + // # Safety + // + // Creating a `EnteredGrantKernelManagedLayout` object ensures that the + // pointer to the RO allow array is valid. + unsafe { slice::from_raw_parts_mut(self.allow_ro_array, self.get_allow_ro_number()) } + } + + /// Return mutable access to the slice of stored read-write allow buffers + /// for this grant. This is necessary for storing a new read-write allow. + fn get_allow_rw_slice(&mut self) -> &mut [SavedAllowRw] { + // # Safety + // + // Creating a `EnteredGrantKernelManagedLayout` object ensures that the + // pointer to the RW allow array is valid. + unsafe { slice::from_raw_parts_mut(self.allow_rw_array, self.get_allow_rw_number()) } + } + + /// Return slices to the kernel managed upcalls and allow buffers. This + /// permits using upcalls and allow buffers when a capsule is accessing a + /// grant. + fn get_resource_slices(&self) -> (&[SavedUpcall], &[SavedAllowRo], &[SavedAllowRw]) { + // # Safety + // + // Creating a `EnteredGrantKernelManagedLayout` object ensures that the + // pointer to the upcall array is valid. + let upcall_slice = + unsafe { slice::from_raw_parts(self.upcalls_array, self.get_upcalls_number()) }; + + // # Safety + // + // Creating a `EnteredGrantKernelManagedLayout` object ensures that the + // pointer to the RO allow array is valid. + let allow_ro_slice = + unsafe { slice::from_raw_parts(self.allow_ro_array, self.get_allow_ro_number()) }; + + // # Safety + // + // Creating a `KernelManagedLayout` object ensures that the pointer to + // the RW allow array is valid. + let allow_rw_slice = + unsafe { slice::from_raw_parts(self.allow_rw_array, self.get_allow_rw_number()) }; + + (upcall_slice, allow_ro_slice, allow_rw_slice) } } -/// This GrantData object provides access to the memory allocated for a grant -/// for a specific process. +// Ensure that we leave the grant once this goes out of scope. +impl Drop for EnteredGrantKernelManagedLayout<'_> { + fn drop(&mut self) { + // ### Safety + // + // To safely call this function we must ensure that no references will + // exist to the grant once `leave_grant()` returns. Because using a + // `EnteredGrantKernelManagedLayout` object is the only only way we + // access the actual memory of a grant, and we are calling + // `leave_grant()` from its `drop()` method, we are sure there will be + // no remaining references to the grant. + unsafe { + self.process.leave_grant(self.grant_num); + } + } +} + +/// This [`GrantData`] object provides access to the memory allocated for a +/// grant for a specific process. /// -/// The GrantData type is templated on T, the actual type of the object in the -/// grant. GrantData holds a mutable reference to the type, allowing users -/// access to the object in process memory. +/// The [`GrantData`] type is templated on `T`, the actual type of the object in +/// the grant. [`GrantData'] holds a mutable reference to the type, allowing +/// users access to the object in process memory. /// -/// Capsules gain access to a GrantData object by calling `Grant::enter()`. +/// Capsules gain access to a [`GrantData`] object by calling +/// [`Grant::enter()`]. pub struct GrantData<'a, T: 'a + ?Sized> { /// The mutable reference to the actual object type stored in the grant. data: &'a mut T, } impl<'a, T: 'a + ?Sized> GrantData<'a, T> { - /// Create a `GrantData` object to provide access to the actual object + /// Create a [`GrantData`] object to provide access to the actual object /// allocated for a process. /// - /// Only one can GrantData per underlying object can be created at a time. - /// Otherwise, there would be multiple mutable references to the same object - /// which is undefined behavior. + /// Only one can [`GrantData`] per underlying object can be created at a + /// time. Otherwise, there would be multiple mutable references to the same + /// object which is undefined behavior. fn new(data: &'a mut T) -> GrantData<'a, T> { - GrantData { data: data } + GrantData { data } } } @@ -385,11 +537,11 @@ impl<'a, T: 'a + ?Sized> DerefMut for GrantData<'a, T> { } } -/// This GrantKernelData object provides a handle to access upcalls and process -/// buffers stored on behalf of a particular grant/driver. +/// This [`GrantKernelData`] object provides a handle to access upcalls and +/// process buffers stored on behalf of a particular grant/driver. /// -/// Capsules gain access to a GrantKernelData object by calling -/// `Grant::enter()`. From there, they can schedule upcalls or access process +/// Capsules gain access to a [`GrantKernelData`] object by calling +/// [`Grant::enter()`]. From there, they can schedule upcalls or access process /// buffers. /// /// It is expected that this type will only exist as a short-lived stack @@ -415,7 +567,7 @@ pub struct GrantKernelData<'a> { } impl<'a> GrantKernelData<'a> { - /// Create a `GrantKernelData` object to provide a handle for capsules to + /// Create a [`GrantKernelData`] object to provide a handle for capsules to /// call Upcalls. fn new( upcalls: &'a [SavedUpcall], @@ -452,7 +604,7 @@ impl<'a> GrantKernelData<'a> { // We can create an `Upcall` object based on what is stored in // the process grant and use that to add the upcall to the // pending array for the process. - let mut upcall = Upcall::new( + let upcall = Upcall::new( self.process.processid(), UpcallId { subscribe_num, @@ -467,14 +619,20 @@ impl<'a> GrantKernelData<'a> { } /// Returns a lifetime limited reference to the requested - /// `ReadOnlyProcessBuffer`. + /// [`ReadOnlyProcessBuffer`]. + /// + /// The len of the returned [`ReadOnlyProcessBuffer`] must be checked by the + /// caller to ensure that a buffer has in fact been allocated. An + /// unallocated buffer will be returned as a [`ReadOnlyProcessBuffer`] of + /// length 0. /// - /// The `ReadOnlyProcessBuffer` is only valid for as long as this object is - /// valid, i.e. the lifetime of the app enter closure. + /// The [`ReadOnlyProcessBuffer`] is only valid for as long as this object + /// is valid, i.e. the lifetime of the app enter closure. /// - /// If the specified allow number is invalid, then a AddressOutOfBounds will - /// be returned. This returns a process::Error to allow for easy chaining of - /// this function with the ReadOnlyProcessBuffer::enter function with + /// If the specified allow number is invalid, then a + /// [`crate::process::Error::AddressOutOfBounds`] will be returned. This + /// returns a [`crate::process::Error`] to allow for easy chaining of this + /// function with the `ReadOnlyProcessBuffer::enter()` function with /// `and_then`. pub fn get_readonly_processbuffer( &self, @@ -502,14 +660,20 @@ impl<'a> GrantKernelData<'a> { } /// Returns a lifetime limited reference to the requested - /// `ReadWriteProcessBuffer`. + /// [`ReadWriteProcessBuffer`]. /// - /// The ReadWriteProcessBuffer is only value for as long as this object is - /// valid, i.e. the lifetime of the app enter closure. + /// The length of the returned [`ReadWriteProcessBuffer`] must be checked by + /// the caller to ensure that a buffer has in fact been allocated. An + /// unallocated buffer will be returned as a [`ReadWriteProcessBuffer`] of + /// length 0. /// - /// If the specified allow number is invalid, then a AddressOutOfBounds will - /// be return. This returns a process::Error to allow for easy chaining of - /// this function with the `ReadWriteProcessBuffer::enter()` function with + /// The [`ReadWriteProcessBuffer`] is only value for as long as this object + /// is valid, i.e. the lifetime of the app enter closure. + /// + /// If the specified allow number is invalid, then a + /// [`crate::process::Error::AddressOutOfBounds`] will be returned. This + /// returns a [`crate::process::Error`] to allow for easy chaining of this + /// function with the `ReadWriteProcessBuffer::enter()` function with /// `and_then`. pub fn get_readwrite_processbuffer( &self, @@ -599,34 +763,13 @@ unsafe fn write_default_array(base: *mut T, num: usize) { } } -/// Lifetime of guard represents the lifetime a grant is held "open". On Drop, -/// we leave grant. -/// -/// This protects against calling `grant.enter()` without calling the -/// corresponding `grant.leave()`, perhaps due to accidentally using the `?` -/// operator to return early. -struct GrantEnterLifetimeGuard<'a> { - /// Leaving a grant is handled through the process implementation, so must - /// keep a reference to the relevant process. - process: &'a dyn Process, - /// The grant number of the entered grant that we want to ensure we leave - /// properly. - grant_num: usize, -} - -impl Drop for GrantEnterLifetimeGuard<'_> { - fn drop(&mut self) { - self.process.leave_grant(self.grant_num); - } -} - /// Enters the grant for the specified process. Caller must hold on to the grant /// lifetime guard while they accessing the memory in the layout (second /// element). fn enter_grant_kernel_managed( process: &dyn Process, driver_num: usize, -) -> Result<(GrantEnterLifetimeGuard, KernelManagedLayout), ErrorCode> { +) -> Result { let grant_num = process.lookup_grant_from_driver_num(driver_num)?; // Check if the grant has been allocated, and if not we cannot enter this @@ -643,8 +786,10 @@ fn enter_grant_kernel_managed( // // We know that this pointer is well aligned and initialized with meaningful // data when the grant region was allocated. - let layout = unsafe { KernelManagedLayout::read_from_base(grant_base_ptr) }; - Ok((GrantEnterLifetimeGuard { process, grant_num }, layout)) + let layout = unsafe { + EnteredGrantKernelManagedLayout::read_from_base(grant_base_ptr, process, grant_num) + }; + Ok(layout) } /// Subscribe to an upcall by saving the upcall in the grant region for the @@ -654,11 +799,10 @@ pub(crate) fn subscribe( upcall: Upcall, ) -> Result { // Enter grant and keep it open until _grant_open goes out of scope. - let (_grant_open, layout) = - match enter_grant_kernel_managed(process, upcall.upcall_id.driver_num) { - Ok(val) => val, - Err(e) => return Err((upcall, e)), - }; + let mut layout = match enter_grant_kernel_managed(process, upcall.upcall_id.driver_num) { + Ok(val) => val, + Err(e) => return Err((upcall, e)), + }; // Create the saved upcalls slice from the grant memory. // @@ -668,8 +812,7 @@ pub(crate) fn subscribe( // because we were able to enter the grant the grant region must be valid // and initialized. We are also holding the grant open until `_grant_open` // goes out of scope. - let saved_upcalls_slice = - unsafe { slice::from_raw_parts_mut(layout.upcalls_array, layout.upcalls_num.read()) }; + let saved_upcalls_slice = layout.get_upcalls_slice(); // Index into the saved upcall slice to get the old upcall. Use .get in case // userspace passed us a bad subscribe number. @@ -690,7 +833,7 @@ pub(crate) fn subscribe( // Success! Ok(old_upcall) } - None => Err((upcall, ErrorCode::INVAL)), + None => Err((upcall, ErrorCode::NOSUPPORT)), } } @@ -704,7 +847,7 @@ pub(crate) fn allow_ro( buffer: ReadOnlyProcessBuffer, ) -> Result { // Enter grant and keep it open until `_grant_open` goes out of scope. - let (_grant_open, layout) = match enter_grant_kernel_managed(process, driver_num) { + let mut layout = match enter_grant_kernel_managed(process, driver_num) { Ok(val) => val, Err(e) => return Err((buffer, e)), }; @@ -717,8 +860,7 @@ pub(crate) fn allow_ro( // because we were able to enter the grant the grant region must be valid // and initialized. We are also holding the grant open until _grant_open // goes out of scope. - let saved_allow_ro_slice = - unsafe { slice::from_raw_parts_mut(layout.allow_ro_array, layout.allow_ro_num.read()) }; + let saved_allow_ro_slice = layout.get_allow_ro_slice(); // Index into the saved slice to get the old value. Use .get in case // userspace passed us a bad allow number. @@ -739,7 +881,7 @@ pub(crate) fn allow_ro( // Success! Ok(old_allow) } - None => Err((buffer, ErrorCode::INVAL)), + None => Err((buffer, ErrorCode::NOSUPPORT)), } } @@ -753,7 +895,7 @@ pub(crate) fn allow_rw( buffer: ReadWriteProcessBuffer, ) -> Result { // Enter grant and keep it open until `_grant_open` goes out of scope. - let (_grant_open, layout) = match enter_grant_kernel_managed(process, driver_num) { + let mut layout = match enter_grant_kernel_managed(process, driver_num) { Ok(val) => val, Err(e) => return Err((buffer, e)), }; @@ -766,8 +908,7 @@ pub(crate) fn allow_rw( // because we were able to enter the grant the grant region must be valid // and initialized. We are also holding the grant open until `_grant_open` // goes out of scope. - let saved_allow_rw_slice = - unsafe { slice::from_raw_parts_mut(layout.allow_rw_array, layout.allow_rw_num.read()) }; + let saved_allow_rw_slice = layout.get_allow_rw_slice(); // Index into the saved slice to get the old value. Use .get in case // userspace passed us a bad allow number. @@ -788,17 +929,18 @@ pub(crate) fn allow_rw( // Success! Ok(old_allow) } - None => Err((buffer, ErrorCode::INVAL)), + None => Err((buffer, ErrorCode::NOSUPPORT)), } } /// An instance of a grant allocated for a particular process. /// -/// `ProcessGrant` is a handle to an instance of a grant that has been allocated -/// in a specific process's grant region. A `ProcessGrant` guarantees that the -/// memory for the grant has been allocated in the process's memory. +/// [`ProcessGrant`] is a handle to an instance of a grant that has been +/// allocated in a specific process's grant region. A [`ProcessGrant`] +/// guarantees that the memory for the grant has been allocated in the process's +/// memory. /// -/// This is created from a `Grant` when that grant is entered for a specific +/// This is created from a [`Grant`] when that grant is entered for a specific /// process. pub struct ProcessGrant< 'a, @@ -809,10 +951,10 @@ pub struct ProcessGrant< > { /// The process the grant is applied to. /// - /// We use a reference here because instances of `ProcessGrant` are very - /// short lived. They only exist while a `Grant` is being entered, so we can - /// be sure the process still exists while a `ProcessGrant` exists. No - /// `ProcessGrant` can be stored. + /// We use a reference here because instances of [`ProcessGrant`] are very + /// short lived. They only exist while a [`Grant`] is being entered, so we + /// can be sure the process still exists while a `ProcessGrant` exists. No + /// [`ProcessGrant`] can be stored. process: &'a dyn Process, /// The syscall driver number this grant is associated with. @@ -828,8 +970,8 @@ pub struct ProcessGrant< impl<'a, T: Default, Upcalls: UpcallSize, AllowROs: AllowRoSize, AllowRWs: AllowRwSize> ProcessGrant<'a, T, Upcalls, AllowROs, AllowRWs> { - /// Create a `ProcessGrant` for the given Grant in the given Process's grant - /// region. + /// Create a [`ProcessGrant`] for the given Grant in the given Process's + /// grant region. /// /// If the grant in this process has not been setup before this will attempt /// to allocate the memory from the process's grant region. @@ -899,8 +1041,8 @@ impl<'a, T: Default, Upcalls: UpcallSize, AllowROs: AllowRoSize, AllowRWs: Allow if let Some(is_allocated) = process.grant_is_allocated(grant_num) { if !is_allocated { // Calculate the alignment and size for entire grant region. - let alloc_align = KernelManagedLayout::grant_align(grant_t_align); - let alloc_size = KernelManagedLayout::grant_size( + let alloc_align = EnteredGrantKernelManagedLayout::grant_align(grant_t_align); + let alloc_size = EnteredGrantKernelManagedLayout::grant_size( num_upcalls, num_allow_ros, num_allow_rws, @@ -909,10 +1051,14 @@ impl<'a, T: Default, Upcalls: UpcallSize, AllowROs: AllowRoSize, AllowRWs: Allow ); // Allocate grant, the memory is still uninitialized though. - let grant_ptr = process + if process .allocate_grant(grant_num, driver_num, alloc_size, alloc_align) - .ok_or(Error::OutOfMemory)? - .as_ptr(); + .is_err() + { + return Err(Error::OutOfMemory); + } + + let grant_ptr = process.enter_grant(grant_num)?; // Create a layout from the counts we have and initialize // all memory so it is valid in the future to read as a @@ -920,23 +1066,22 @@ impl<'a, T: Default, Upcalls: UpcallSize, AllowROs: AllowRoSize, AllowRWs: Allow // // # Safety // - // For all below calls: - // - The pointers are well aligned, yet do not have - // initialized data. + // - The grant base pointer is well aligned, yet does not + // have initialized data. // - The pointer points to a large enough space to correctly // write to is guaranteed by alloc of size - // `KernelManagedLayout::grant_size`. + // `EnteredGrantKernelManagedLayout::grant_size`. // - There are no proper rust references that map to these - // addresses either + // addresses. unsafe { - let layout = - KernelManagedLayout::from_counts(grant_ptr, num_upcalls, num_allow_ros); - layout.upcalls_num.write(num_upcalls.0); - write_default_array(layout.upcalls_array, num_upcalls.0); - layout.allow_ro_num.write(num_allow_ros.0); - write_default_array(layout.allow_ro_array, num_allow_ros.0); - layout.allow_rw_num.write(num_allow_rws.0); - write_default_array(layout.allow_rw_array, num_allow_rws.0); + let _layout = EnteredGrantKernelManagedLayout::initialize_from_counts( + grant_ptr, + num_upcalls, + num_allow_ros, + num_allow_rws, + process, + grant_num, + ); } // # Safety @@ -945,7 +1090,7 @@ impl<'a, T: Default, Upcalls: UpcallSize, AllowROs: AllowRoSize, AllowRWs: Allow // large and is at least as aligned as grant_t_align. unsafe { Ok(( - Some(KernelManagedLayout::offset_of_grant_data_t( + Some(EnteredGrantKernelManagedLayout::offset_of_grant_data_t( grant_ptr, alloc_size, grant_t_size, @@ -978,46 +1123,43 @@ impl<'a, T: Default, Upcalls: UpcallSize, AllowROs: AllowRoSize, AllowRWs: Allow )?; // We can now do the initialization of T object if necessary. - match opt_raw_grant_ptr_nn { - Some(allocated_ptr) => { - // Grant type T - // - // # Safety - // - // This is safe because: - // - // 1. The pointer address is valid. The pointer is allocated - // statically in process memory, and will exist for as long - // as the process does. The grant is only accessible while - // the process is still valid. - // - // 2. The pointer is correctly aligned. The newly allocated - // grant is aligned for type T, and there is padding inserted - // between the upcall array and the T object such that the T - // object starts a multiple of `align_of` from the - // beginning of the allocation. - unsafe { - // Convert untyped `*mut u8` allocation to allocated type. - let new_region = NonNull::cast::(allocated_ptr); - // We use `ptr::write` to avoid `Drop`ping the uninitialized - // memory in case `T` implements the `Drop` trait. - write(new_region.as_ptr(), T::default()); - } + if let Some(allocated_ptr) = opt_raw_grant_ptr_nn { + // Grant type T + // + // # Safety + // + // This is safe because: + // + // 1. The pointer address is valid. The pointer is allocated + // statically in process memory, and will exist for as long + // as the process does. The grant is only accessible while + // the process is still valid. + // + // 2. The pointer is correctly aligned. The newly allocated + // grant is aligned for type T, and there is padding inserted + // between the upcall array and the T object such that the T + // object starts a multiple of `align_of` from the + // beginning of the allocation. + unsafe { + // Convert untyped `*mut u8` allocation to allocated type. + let new_region = NonNull::cast::(allocated_ptr); + // We use `ptr::write` to avoid `Drop`ping the uninitialized + // memory in case `T` implements the `Drop` trait. + write(new_region.as_ptr(), T::default()); } - None => {} // Case if grant was already allocated. } // We have ensured the grant is already allocated or was just allocated, // so we can create and return the `ProcessGrant` type. Ok(ProcessGrant { - process: process, + process, driver_num: grant.driver_num, grant_num: grant.grant_num, _phantom: PhantomData, }) } - /// Return an `ProcessGrant` for a grant in a process if the process is + /// Return a [`ProcessGrant`] for a grant in a process if the process is /// valid and that process grant has already been allocated, or `None` /// otherwise. fn new_if_allocated( @@ -1027,7 +1169,7 @@ impl<'a, T: Default, Upcalls: UpcallSize, AllowROs: AllowRoSize, AllowRWs: Allow if let Some(is_allocated) = process.grant_is_allocated(grant.grant_num) { if is_allocated { Some(ProcessGrant { - process: process, + process, driver_num: grant.driver_num, grant_num: grant.grant_num, _phantom: PhantomData, @@ -1042,7 +1184,8 @@ impl<'a, T: Default, Upcalls: UpcallSize, AllowROs: AllowRoSize, AllowRWs: Allow } } - /// Return the ProcessId of the process this ProcessGrant is associated with. + /// Return the [`ProcessId`] of the process this [`ProcessGrant`] is + /// associated with. pub fn processid(&self) -> ProcessId { self.process.processid() } @@ -1056,7 +1199,7 @@ impl<'a, T: Default, Upcalls: UpcallSize, AllowROs: AllowRoSize, AllowRWs: Allow /// /// Note, a grant can only be entered once at a time. Attempting to call /// `.enter()` on a grant while it is already entered will result in a - /// panic!()`. See the comment in `access_grant()` for more information. + /// `panic!()`. See the comment in `access_grant()` for more information. pub fn enter(self, fun: F) -> R where F: FnOnce(&mut GrantData, &GrantKernelData) -> R, @@ -1152,7 +1295,7 @@ impl<'a, T: Default, Upcalls: UpcallSize, AllowROs: AllowRoSize, AllowRWs: Allow self.access_grant_with_allocator(fun, true).unwrap() } - /// Access the `ProcessGrant` memory and run a closure on the process's + /// Access the [`ProcessGrant`] memory and run a closure on the process's /// grant memory. /// /// If `panic_on_reenter` is `true`, this will panic if the grant region is @@ -1168,7 +1311,7 @@ impl<'a, T: Default, Upcalls: UpcallSize, AllowROs: AllowRoSize, AllowRWs: Allow ) } - /// Access the `ProcessGrant` memory and run a closure on the process's + /// Access the [`ProcessGrant`] memory and run a closure on the process's /// grant memory. /// /// If `panic_on_reenter` is `true`, this will panic if the grant region is @@ -1210,7 +1353,7 @@ impl<'a, T: Default, Upcalls: UpcallSize, AllowROs: AllowRoSize, AllowRWs: Allow // challenge is that calling `self.apps.iter()` is a common // pattern in capsules to access the grant region of every app // that is using the capsule, and sometimes it is intuitive to - // call that inside of a `self.apps.enter(app_id, |app| {...})` + // call that inside of a `self.apps.enter(processid, |app| {...})` // closure. However, `.enter()` means that app's grant region is // entered, and then a naive `.iter()` would re-enter the grant // region and cause undefined behavior. We considered different @@ -1246,16 +1389,10 @@ impl<'a, T: Default, Upcalls: UpcallSize, AllowROs: AllowRoSize, AllowRWs: Allow panic!("Attempted to re-enter a grant region."); }) .ok()?; - // Ensure we leave this grant when _grant_open goes out of scope - let _grant_open = GrantEnterLifetimeGuard { - process: self.process, - grant_num: self.grant_num, - }; - let grant_t_align = GrantDataAlign(align_of::()); let grant_t_size = GrantDataSize(size_of::()); - let alloc_size = KernelManagedLayout::grant_size( + let alloc_size = EnteredGrantKernelManagedLayout::grant_size( UpcallItems(Upcalls::COUNT), AllowRoItems(AllowROs::COUNT), AllowRwItems(AllowRWs::COUNT), @@ -1263,17 +1400,13 @@ impl<'a, T: Default, Upcalls: UpcallSize, AllowROs: AllowRoSize, AllowRWs: Allow grant_t_align, ); - // Determine layout of entire grant alloc + // Parse layout of entire grant allocation using the known base pointer. // // # Safety // - // Grant pointer is well aligned and points to well initialized data + // Grant pointer is well aligned and points to initialized data. let layout = unsafe { - KernelManagedLayout::from_counts( - grant_ptr, - UpcallItems(Upcalls::COUNT), - AllowRoItems(AllowROs::COUNT), - ) + EnteredGrantKernelManagedLayout::read_from_base(grant_ptr, self.process, self.grant_num) }; // Get references to all of the saved upcall data. @@ -1282,24 +1415,24 @@ impl<'a, T: Default, Upcalls: UpcallSize, AllowROs: AllowRoSize, AllowRWs: Allow // // - Pointer is well aligned and initialized with data from Self::new() // call. - // - Data will not be modify external while this immutable reference is - // alive. + // - Data will not be modified externally while this immutable reference + // is alive. // - Data is accessible for the entire duration of this immutable // reference. // - No other mutable reference to this memory exists concurrently. // Mutable reference to this memory are only created through the // kernel in the syscall interface which is serialized in time with // this call. - let saved_upcalls_slice = - unsafe { slice::from_raw_parts(layout.upcalls_array, Upcalls::COUNT) }; - let saved_allow_ro_slice = - unsafe { slice::from_raw_parts(layout.allow_ro_array, AllowROs::COUNT) }; - let saved_allow_rw_slice = - unsafe { slice::from_raw_parts(layout.allow_rw_array, AllowRWs::COUNT) }; + let (saved_upcalls_slice, saved_allow_ro_slice, saved_allow_rw_slice) = + layout.get_resource_slices(); let grant_data = unsafe { - KernelManagedLayout::offset_of_grant_data_t(grant_ptr, alloc_size, grant_t_size) - .cast() - .as_mut() + EnteredGrantKernelManagedLayout::offset_of_grant_data_t( + grant_ptr, + alloc_size, + grant_t_size, + ) + .cast() + .as_mut() }; // Create a wrapped objects that are passed to functor. @@ -1325,15 +1458,15 @@ impl<'a, T: Default, Upcalls: UpcallSize, AllowROs: AllowRoSize, AllowRWs: Allow /// Grant which was allocated from the kernel-owned grant region in a specific /// process's memory, separately from a normal `Grant`. /// -/// A `CustomGrant` allows a capsule to allocate additional memory on behalf of -/// a process. +/// A [`CustomGrant`] allows a capsule to allocate additional memory on behalf +/// of a process. pub struct CustomGrant { /// An identifier for this custom grant within a process's grant region. /// /// Here, this is an opaque reference that Process uses to access the /// custom grant allocation. This setup ensures that Process owns the grant /// memory. - identifier: ProcessCustomGrantIdentifer, + identifier: ProcessCustomGrantIdentifier, /// Identifier for the process where this custom grant is allocated. processid: ProcessId, @@ -1343,8 +1476,8 @@ pub struct CustomGrant { } impl CustomGrant { - /// Creates a new `CustomGrant`. - fn new(identifier: ProcessCustomGrantIdentifer, processid: ProcessId) -> Self { + /// Creates a new [`CustomGrant`]. + fn new(identifier: ProcessCustomGrantIdentifier, processid: ProcessId) -> Self { CustomGrant { identifier, processid, @@ -1352,7 +1485,7 @@ impl CustomGrant { } } - /// Helper function to get the ProcessId from the custom grant. + /// Helper function to get the [`ProcessId`] from the custom grant. pub fn processid(&self) -> ProcessId { self.processid } @@ -1366,7 +1499,7 @@ impl CustomGrant { /// Because this function requires `&mut self`, it should be impossible to /// access the inner data of a given `CustomGrant` reentrantly. Thus the /// reentrance detection we use for non-custom grants is not needed here. - pub fn enter(&mut self, fun: F) -> Result + pub fn enter(&self, fun: F) -> Result where F: FnOnce(GrantData<'_, T>) -> R, { @@ -1406,7 +1539,7 @@ pub struct GrantRegionAllocator { } impl GrantRegionAllocator { - /// Allocates a new `CustomGrant` initialized using the given closure. + /// Allocates a new [`CustomGrant`] initialized using the given closure. /// /// The closure will be called exactly once, and the result will be used to /// initialize the owned value. @@ -1418,7 +1551,7 @@ impl GrantRegionAllocator { /// # Panic Safety /// /// If `init` panics, the freshly allocated memory may leak. - pub fn alloc_with(&mut self, init: F) -> Result, Error> + pub fn alloc_with(&self, init: F) -> Result, Error> where F: FnOnce() -> T, { @@ -1448,7 +1581,7 @@ impl GrantRegionAllocator { /// If `val_func` panics, the freshly allocated memory and any values /// already written will be leaked. pub fn alloc_n_with( - &mut self, + &self, mut init: F, ) -> Result, Error> where @@ -1473,8 +1606,8 @@ impl GrantRegionAllocator { /// /// The caller must initialize the memory. /// - /// Also returns a ProcessCustomGrantIdentifer to access the memory later. - fn alloc_raw(&mut self) -> Result<(ProcessCustomGrantIdentifer, NonNull), Error> { + /// Also returns a ProcessCustomGrantIdentifier to access the memory later. + fn alloc_raw(&self) -> Result<(ProcessCustomGrantIdentifier, NonNull), Error> { self.alloc_n_raw::(1) } @@ -1483,11 +1616,11 @@ impl GrantRegionAllocator { /// The caller is responsible for initializing the returned memory. /// /// Returns memory appropriate for storing `num_items` contiguous instances - /// of `T` and a ProcessCustomGrantIdentifer to access the memory later. + /// of `T` and a ProcessCustomGrantIdentifier to access the memory later. fn alloc_n_raw( - &mut self, + &self, num_items: usize, - ) -> Result<(ProcessCustomGrantIdentifer, NonNull), Error> { + ) -> Result<(ProcessCustomGrantIdentifier, NonNull), Error> { let (custom_grant_identifier, raw_ptr) = self.alloc_n_raw_inner(num_items, size_of::(), align_of::())?; let typed_ptr = NonNull::cast::(raw_ptr); @@ -1497,11 +1630,11 @@ impl GrantRegionAllocator { /// Helper to reduce code bloat by avoiding monomorphization. fn alloc_n_raw_inner( - &mut self, + &self, num_items: usize, single_alloc_size: usize, alloc_align: usize, - ) -> Result<(ProcessCustomGrantIdentifer, NonNull), Error> { + ) -> Result<(ProcessCustomGrantIdentifier, NonNull), Error> { let alloc_size = single_alloc_size .checked_mul(num_items) .ok_or(Error::OutOfMemory)?; @@ -1521,11 +1654,12 @@ impl GrantRegionAllocator { /// Type for storing an object of type T in process memory that is only /// accessible by the kernel. /// -/// A single `Grant` can allocate space for one object of type T for each +/// A single [`Grant`] can allocate space for one object of type T for each /// process on the board. Each allocated object will reside in the grant region -/// belonging to the process that the object is allocated for. The `Grant` type -/// is used to get access to `ProcessGrant`s, which are tied to a specific -/// process and provide access to the memory object allocated for that process. +/// belonging to the process that the object is allocated for. The [`Grant`] +/// type is used to get access to [`ProcessGrant`]s, which are tied to a +/// specific process and provide access to the memory object allocated for that +/// process. pub struct Grant { /// Hold a reference to the core kernel so we can iterate processes. pub(crate) kernel: &'static Kernel, @@ -1547,15 +1681,15 @@ pub struct Grant Grant { - /// Create a new `Grant` type which allows a capsule to store + /// Create a new [`Grant`] type which allows a capsule to store /// process-specific data for each process in the process's memory region. /// /// This must only be called from the main kernel so that it can ensure that /// `grant_index` is a valid index. pub(crate) fn new(kernel: &'static Kernel, driver_num: usize, grant_index: usize) -> Self { Self { - kernel: kernel, - driver_num: driver_num, + kernel, + driver_num, grant_num: grant_index, ptr: PhantomData, } @@ -1563,8 +1697,8 @@ impl(&self, processid: ProcessId, fun: F) -> Result where @@ -1581,8 +1715,8 @@ impl(&self, fun: F) + /// Calling this function when an [`ProcessGrant`] for a process is + /// currently entered will result in a panic. + pub fn each(&self, mut fun: F) where - F: Fn(ProcessId, &mut GrantData, &GrantKernelData), + F: FnMut(ProcessId, &mut GrantData, &GrantKernelData), { // Create a the iterator across `ProcessGrant`s for each process. for pg in self.iter() { @@ -1628,8 +1762,8 @@ impl Iter { Iter { grant: self, @@ -1638,7 +1772,7 @@ impl { /// The chip-dependent type of an ADC channel. type Channel: PartialEq; @@ -38,7 +42,7 @@ pub trait Adc { /// The returned reference voltage is in millivolts, or `None` if unknown. fn get_voltage_reference_mv(&self) -> Option; - fn set_client(&self, client: &'static dyn Client); + fn set_client(&self, client: &'a dyn Client); } /// Trait for handling callbacks from simple ADC calls. @@ -51,7 +55,7 @@ pub trait Client { /// Interface for continuously sampling at a given frequency on a channel. /// Requires the AdcSimple interface to have been implemented as well. -pub trait AdcHighSpeed: Adc { +pub trait AdcHighSpeed<'a>: Adc<'a> { /// Start sampling continuously into buffers. /// Samples are double-buffered, going first into `buffer1` and then into /// `buffer2`. A callback is performed to the client whenever either buffer @@ -97,6 +101,8 @@ pub trait AdcHighSpeed: Adc { fn retrieve_buffers( &self, ) -> Result<(Option<&'static mut [u16]>, Option<&'static mut [u16]>), ErrorCode>; + + fn set_highspeed_client(&self, client: &'a dyn HighSpeedClient); } /// Trait for handling callbacks from high-speed ADC calls. @@ -108,7 +114,7 @@ pub trait HighSpeedClient { fn samples_ready(&self, buf: &'static mut [u16], length: usize); } -pub trait AdcChannel { +pub trait AdcChannel<'a> { /// Request a single ADC sample on a particular channel. /// Used for individual samples that have no timing requirements. /// All ADC samples will be the raw ADC value left-justified in the u16. @@ -138,5 +144,5 @@ pub trait AdcChannel { /// The returned reference voltage is in millivolts, or `None` if unknown. fn get_voltage_reference_mv(&self) -> Option; - fn set_client(&self, client: &'static dyn Client); + fn set_client(&self, client: &'a dyn Client); } diff --git a/kernel/src/hil/analog_comparator.rs b/kernel/src/hil/analog_comparator.rs index d263cb0ccb..93d23fcac9 100644 --- a/kernel/src/hil/analog_comparator.rs +++ b/kernel/src/hil/analog_comparator.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for direct control of the analog comparators. use crate::ErrorCode; diff --git a/kernel/src/hil/ble_advertising.rs b/kernel/src/hil/ble_advertising.rs index a5bcc32b24..f208f1764d 100644 --- a/kernel/src/hil/ble_advertising.rs +++ b/kernel/src/hil/ble_advertising.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Bluetooth Low Energy HIL //! //! ```text diff --git a/kernel/src/hil/bus8080.rs b/kernel/src/hil/bus8080.rs index fdae216ef8..e608f1deb5 100644 --- a/kernel/src/hil/bus8080.rs +++ b/kernel/src/hil/bus8080.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! The 8080 Bus Interface (used for LCD) use crate::ErrorCode; diff --git a/kernel/src/hil/buzzer.rs b/kernel/src/hil/buzzer.rs new file mode 100644 index 0000000000..4ae832a24c --- /dev/null +++ b/kernel/src/hil/buzzer.rs @@ -0,0 +1,44 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Interface for buzzer use. + +use crate::ErrorCode; + +pub trait BuzzerClient { + /// Called when the current sound played by the buzzer has finished + /// or it was stopped. + fn buzzer_done(&self, status: Result<(), ErrorCode>); +} + +/// The Buzzer HIL is used to play a sound on a buzzer at a fixed frequency and +/// for a certain duration. +pub trait Buzzer<'a> { + /// Play a sound at a chosen frequency and for a chosen duration. + /// Once the buzzer finishes buzzing, the `buzzer_done()` callback + /// is called. + /// If it is called while the buzzer is playing, the buzzer command will be + /// overridden with the new frequency and duration values. + /// + /// Return values: + /// + /// - `Ok(())`: The attempt at starting the buzzer was successful. + /// - `FAIL`: Cannot start the buzzer. + fn buzz(&self, frequency_hz: usize, duration_ms: usize) -> Result<(), ErrorCode>; + + /// Stop the sound currently playing. + /// After the buzzer is successfully stopped, the `buzzer_done()` + /// callback is called. + /// + /// Return values: + /// + /// - `Ok(())`: The attempt at stopping the buzzer was successful. + /// - `FAIL`: Cannot stop the buzzer. + /// - `OFF`: The buzzer wasn't playing a sound when the stop command was called. + fn stop(&self) -> Result<(), ErrorCode>; + + /// Set the client to be used for callbacks of the Buzzer + /// implementation. + fn set_client(&self, client: &'a dyn BuzzerClient); +} diff --git a/kernel/src/hil/can.rs b/kernel/src/hil/can.rs new file mode 100644 index 0000000000..f72802e7df --- /dev/null +++ b/kernel/src/hil/can.rs @@ -0,0 +1,831 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022 +// Copyright OxidOS Automotive SRL 2022 +// +// Author: Teona Severin + +//! Interface for CAN peripherals. +//! +//! Defines multiple traits for different purposes. +//! +//! The `Configure` trait is used to configure the communication +//! mode and bit timing parameters of the CAN peripheral. The +//! `ConfigureFd` trait is an advanced feature that can be implemented +//! for peripherals that support flexible data messages. These +//! 2 traits represent synchronous actions and do not need a client +//! in order to confirm to the capsule that the action is finished. +//! +//! The `Controller` trait is used to enable and disable the device. +//! In order to be able to enable the device, the bit timing parameters +//! and the communication mode must be previously set, and in order +//! to disable the device, it must be enabled. This trait defines +//! asynchronous behaviours and because of that, the `ControllerClient` +//! trait is used to confirm to the capsule that the action is finished. +//! +//! The `Filter` trait is used to configure filter banks for receiving +//! messages. The action is synchronous. +//! +//! The `Transmit` trait is used to asynchronously send a message on +//! the CAN bus. The device must be previously enabled. The +//! `TransmitClient` trait is used to notify the capsule when the +//! transmission is done or when there was en error captured during +//! the transmission. +//! +//! The `Receive` trait is used to asynchronously receive messages on +//! the CAN bus. The `ReceiveClient` trait is used to notify the capsule +//! when a message was received, when the receiving process was aborted +//! and anytime an error occurs. +//! + +use crate::ErrorCode; +use core::cmp; + +pub const STANDARD_CAN_PACKET_SIZE: usize = 8; +pub const FD_CAN_PACKET_SIZE: usize = 64; + +/// Defines the possible states of the peripheral +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum State { + /// The peripheral is enabled and functions normally + Running, + + /// The peripheral is disabled + Disabled, + + /// There was an error while executing a request (sending + /// or receiving) + Error(Error), +} + +/// Defines the error codes received from the CAN peripheral +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum Error { + /// The previous transmission failed due to an arbitration + /// lost + ArbitrationLost, + + /// The previous transmission failed + Transmission, + + /// The internal Transmit Error Counter or the internal + /// Receive Error Counter is greater than 96 and the + /// passive error state is entered. + Warning, + + /// The internal Transmit Error Counter or the internal + /// Receive Error Counter is greater than 127 and the + /// passive error state is entered. + Passive, + + /// The internal Transmit Error Counter is greater than 255 + /// and the bus-off state is entered. + BusOff, + + /// 6 consecutive bits of equal value are detected on the bus. + /// (When transmitting device detects 5 consecutive bits of + /// equal value on the bus, it inserts a complemented one). + Stuff, + + /// The form of the received or the transmitted frame is + /// different than the standard format. + Form, + + /// There are no receivers on the bus or the sender caused an + /// error. + Ack, + + /// While transmitting a recessive bit, the receiver sensed a + /// dominant bit. + BitRecessive, + + /// While transmitting a dominant bit, the receiver sensed a + /// recessive bit. + BitDominant, + + /// The frame has been corrupted on the CAN bus + Crc, + + /// Set by software to force the hardware to indicate the + /// current communication status. + SetBySoftware, +} + +impl From for ErrorCode { + fn from(val: Error) -> Self { + match val { + Error::ArbitrationLost => ErrorCode::RESERVE, + Error::BusOff => ErrorCode::OFF, + Error::Form => ErrorCode::INVAL, + Error::BitRecessive | Error::BitDominant => ErrorCode::BUSY, + Error::Ack | Error::Transmission => ErrorCode::NOACK, + Error::Crc | Error::SetBySoftware | Error::Warning | Error::Passive | Error::Stuff => { + ErrorCode::FAIL + } + } + } +} + +/// The Scale Bits structure defines the 2 possible widths +/// of the filter bank +#[derive(Debug, Copy, Clone)] +pub enum ScaleBits { + Bits16, + Bits32, +} + +/// The filter can be configured to filter the messages by matching +/// an identifier or by bitwise matching multiple identifiers. +#[derive(Debug, Copy, Clone)] +pub enum IdentifierMode { + /// A mask is used to filter the messages + List, + /// The value of the identifier is used to filter the messages + Mask, +} + +/// The identifier can be standard (11 bits) or extended (29 bits) +#[derive(Debug, Copy, Clone)] +pub enum Id { + Standard(u16), + Extended(u32), +} + +/// This structure defines the parameters to configure a filter bank +#[derive(Copy, Clone)] +pub struct FilterParameters { + /// The filter Id + /// + /// This value is dependent on the peripheral used and identifies + /// the filter bank that will be used + pub number: u32, + + /// The width of the filter bank + pub scale_bits: ScaleBits, + + /// The way in which the message Ids will be filtered. + pub identifier_mode: IdentifierMode, + + /// The receive FIFO Id that the filter will be applied to + pub fifo_number: usize, +} + +/// This structure defines the parameters for the timing mode +#[derive(Debug, Copy, Clone)] +pub struct BitTiming { + /// A value that defines the location of the sample + /// point (between 1 and 16 time quanta) + pub segment1: u8, + + /// A value that defines the location of the transmit + /// point (between 1 and 8 time quanta) + pub segment2: u8, + + /// A value used for compensating the delay on the bus + /// lines + pub propagation: u8, + + /// A value that represents the maximum time by which + /// the bit sampling period may lengthen or shorten + /// each cycle to perform the resynchronization. It is + /// measured in time quanta. + pub sync_jump_width: u32, + + /// A value that represents the sampling clock period. + /// A period is reffered to as a time quanta. + pub baud_rate_prescaler: u32, +} + +/// The peripheral can be configured to work in the following modes: +#[derive(Debug, Copy, Clone)] +pub enum OperationMode { + /// Loopback mode means that each message is transmitted on the + /// TX channel and immediately received on the RX channel + Loopback, + + /// Monitoring mode means that the CAN peripheral sends only the recessive + /// bits on the bus and cannot start a transmission, but can receive + /// valid data frames and valid remote frames + Monitoring, + + /// Freeze mode means that no transmission or reception of frames is + /// done + Freeze, + + /// Normal mode means that the transmission and reception of frames + /// are available + Normal, +} + +/// The `StandardBitTiming` trait is used to calculate the optimum timing parameters +/// for a given bitrate and the clock's frequency. +pub trait StandardBitTiming { + fn bit_timing_for_bitrate(clock_rate: u32, bitrate: u32) -> Result; +} + +/// The default implementation for the `bit_timing_for_bitrate` method. This algorithm +/// is inspired by the Zephyr CAN driver available at +/// `` +impl StandardBitTiming for T { + fn bit_timing_for_bitrate(clock_rate: u32, bitrate: u32) -> Result { + if bitrate > 8_000_000 { + return Err(ErrorCode::INVAL); + } + + let mut res_timing: BitTiming = Self::MIN_BIT_TIMINGS; + let sp: u32 = if bitrate > 800_000 { + 750 + } else if bitrate > 500_000 { + 800 + } else { + 875 + }; + let mut sample_point_err; + let mut sample_point_err_min = u16::MAX; + let mut ts: u32 = (Self::MAX_BIT_TIMINGS.propagation + + Self::MAX_BIT_TIMINGS.segment1 + + Self::MAX_BIT_TIMINGS.segment2 + + Self::SYNC_SEG) as u32; + + for prescaler in + cmp::max(clock_rate / (ts * bitrate), 1)..Self::MAX_BIT_TIMINGS.baud_rate_prescaler + { + if clock_rate % (prescaler * bitrate) != 0 { + continue; + } + ts = clock_rate / (prescaler * bitrate); + + sample_point_err = { + let ts1_max = Self::MAX_BIT_TIMINGS.propagation + Self::MAX_BIT_TIMINGS.segment1; + let ts1_min = Self::MIN_BIT_TIMINGS.propagation + Self::MIN_BIT_TIMINGS.segment1; + let mut ts1; + let mut ts2; + let mut res: i32 = 0; + + ts2 = ts - (ts * sp) / 1000; + ts2 = if ts2 < Self::MIN_BIT_TIMINGS.segment2 as u32 { + Self::MIN_BIT_TIMINGS.segment2 as u32 + } else if ts2 > Self::MAX_BIT_TIMINGS.segment2 as u32 { + Self::MAX_BIT_TIMINGS.segment2 as u32 + } else { + ts2 + }; + ts1 = ts - Self::SYNC_SEG as u32 - ts2; + + if ts1 > ts1_max as u32 { + ts1 = ts1_max as u32; + ts2 = ts - Self::SYNC_SEG as u32 - ts1; + if ts2 > Self::MAX_BIT_TIMINGS.segment2 as u32 { + res = -1; + } + } else if ts1 < ts1_min as u32 { + ts1 = ts1_min as u32; + ts2 = ts - ts1; + if ts2 < Self::MIN_BIT_TIMINGS.segment2 as u32 { + res = -1; + } + } + + if res != -1 { + res_timing.propagation = if ts1 / 2 < Self::MIN_BIT_TIMINGS.propagation as u32 { + Self::MIN_BIT_TIMINGS.propagation + } else if ts1 / 2 > Self::MAX_BIT_TIMINGS.propagation as u32 { + Self::MAX_BIT_TIMINGS.propagation + } else { + (ts1 / 2) as u8 + }; + + res_timing.segment1 = ts1 as u8 - res_timing.propagation; + res_timing.segment2 = ts2 as u8; + + res = ((Self::SYNC_SEG as u32 + ts1) * 1000 / ts) as i32; + if res > sp as i32 { + res - sp as i32 + } else { + sp as i32 - res + } + } else { + res + } + }; + + if sample_point_err < 0 { + continue; + } + + if sample_point_err < sample_point_err_min as i32 { + sample_point_err_min = sample_point_err as u16; + res_timing.baud_rate_prescaler = prescaler; + if sample_point_err == 0 { + break; + } + } + } + + if sample_point_err_min != 0 { + return Err(ErrorCode::INVAL); + } + + Ok(BitTiming { + segment1: res_timing.segment1 - 1, + segment2: res_timing.segment2 - 1, + propagation: res_timing.propagation, + sync_jump_width: if res_timing.sync_jump_width == 0 { + 0 + } else { + res_timing.sync_jump_width - 1 + }, + baud_rate_prescaler: res_timing.baud_rate_prescaler - 1, + }) + } +} + +/// The `Configure` trait is used to configure the CAN peripheral and to prepare it for +/// transmission and reception of data. The peripheral cannot transmit or receive frames if +/// it is not previously configured and enabled. +/// +/// In order to configure the peripheral, the following steps are required: +/// +/// - Call `set_bitrate` or `set_bit_timing` to configure the timing settings +/// - Call `set_operation_mode` to configure the testing mode +/// - (Optional) Call `set_automatic_retransmission` and/or +/// `set_wake_up` to configure the behaviour of the peripheral +/// - To apply the settings and be able to use the peripheral, call `enable` +/// (from the `Controller` trait) +pub trait Configure { + /// Constants that define the minimum and maximum values that the timing + /// parameters can take. They are used when calculating the optimum timing + /// parameters for a given bitrate. + const MIN_BIT_TIMINGS: BitTiming; + const MAX_BIT_TIMINGS: BitTiming; + + /// This constant represents the synchronization segment. + /// Most CAN devices seems to have define this in hardware to 1 quantum long. + /// 1 quantum long. It is used for the synchronization of the clocks. + const SYNC_SEG: u8 = 1; + + /// Configures the CAN peripheral at the given bitrate. This function is + /// supposed to be called before the `enable` function. This function is + /// synchronous as the driver should only calculate the timing parameters + /// based on the bitrate and the frequency of the board and store them. + /// This function does not configure the hardware. + /// + /// # Arguments: + /// + /// * `bitrate` - A value that represents the bitrate for the CAN communication. + /// + /// # Return values: + /// + /// * `Ok()` - The timing parameters were calculated and stored. + /// * `Err(ErrorCode)` - Indicates the error because of which the request + /// cannot be completed + fn set_bitrate(&self, bitrate: u32) -> Result<(), ErrorCode>; + + /// Configures the CAN peripheral with the given arguments. This function is + /// supposed to be called before the `enable` function. This function is + /// synchronous as the driver should only store the arguments, and should not + /// configure the hardware. + /// + /// # Arguments: + /// + /// * `bit_timing` - A BitTiming structure to define the bit timing + /// settings for the peripheral + /// + /// # Return values: + /// + /// * `Ok()` - The parameters were stored. + /// * `Err(ErrorCode)` - Indicates the error because of which the request + /// cannot be completed + fn set_bit_timing(&self, bit_timing: BitTiming) -> Result<(), ErrorCode>; + + /// Configures the CAN peripheral with the given arguments. This function is + /// supposed to be called before the `enable` function. This function is + /// synchronous as the driver should only store the arguments, and should not + /// configure the hardware. + /// + /// # Arguments: + /// + /// * `mode` - An OperationMode structure to define the running mode + /// of the peripheral + /// + /// # Return values: + /// + /// * `Ok()` - The parameters were stored. + /// * `Err(ErrorCode)` - Indicates the error because of which the request + /// cannot be completed + fn set_operation_mode(&self, mode: OperationMode) -> Result<(), ErrorCode>; + + /// Returns the current timing parameters for the CAN peripheral. + /// + /// # Return values: + /// + /// * `Ok(BitTiming)` - The current timing parameters + /// given to the peripheral + /// * `Err(ErrorCode)` - Indicates the error because of which the + /// request cannot be completed + fn get_bit_timing(&self) -> Result; + + /// Returns the current operating mode for the CAN peripheral. + /// + /// # Return values: + /// + /// * `Ok(OperationMode)` - The current operating mode parameter + /// given to the peripheral + /// * `Err(ErrorCode)` - Indicates the error because of which the + /// request cannot be completed + fn get_operation_mode(&self) -> Result; + + /// Configures the CAN peripheral with the automatic retransmission setting. + /// This function is optional, but if used, must be called before the + /// `enable` function. This function is synchronous as the driver should + /// only store the arguments, and should not configure the hardware. + /// + /// # Arguments: + /// + /// * `automatic` - Value to configure the automatic retransmission + /// setting + /// + /// # Return values: + /// + /// * `Ok()` - The setting was stored. + /// * `Err(ErrorCode)` - Indicates the error because of which the request + /// cannot be completed + fn set_automatic_retransmission(&self, automatic: bool) -> Result<(), ErrorCode>; + + /// Configures the CAN peripheral with the automatic wake up setting. + /// This function is optional, but if used, must be called before the + /// `enable` function. This function is synchronous as the driver should + /// only store the arguments, and should not configure the hardware. + /// + /// # Arguments: + /// + /// * `wake_up` - Value to configure the automatic wake up setting + /// + /// # Return values: + /// + /// * `Ok()` - The setting was stored. + /// * `Err(ErrorCode)` - Indicates the error because of which the request + /// cannot be completed + fn set_wake_up(&self, wake_up: bool) -> Result<(), ErrorCode>; + + /// Returns the current automatic retransmission setting of the peripheral. + /// + /// # Return values: + /// + /// * `Ok(bool)` - The current automatic retransmission setting + /// * `Err(ErrorCode)` - Indicates the error because of which the + /// request cannot be completed + fn get_automatic_retransmission(&self) -> Result; + + /// Returns the current automatic wake up setting of the peripheral. + /// + /// # Return values: + /// + /// * `Ok(bool)` - The current automatic wake up setting + /// * `Err(ErrorCode)` - Indicates the error because of which the + /// request cannot be completed + fn get_wake_up(&self) -> Result; + + /// Returns the number of receive FIFOs the peripheral provides + fn receive_fifo_count(&self) -> usize; +} + +/// The `ConfigureFd` trait is used to configure the CAN peripheral for CanFD and to prepare it for +/// transmission and reception of data. The peripheral cannot transmit or receive frames if +/// it is not previously configured and enabled. +/// +/// In order to configure the peripheral, the following steps are required: +/// +/// - Call `set_bit_timing` to configure the timing settings +/// - Call `set_operation_mode` to configure the testing mode +/// - (Optional) Call `set_automatic_retransmission` and/or +/// `set_wake_up` to configure the behaviour of the peripheral +/// - To apply the settings and be able to use the peripheral, call `enable` +/// (from the `Controller` trait) +pub trait ConfigureFd: Configure { + /// Configures the CAN FD peripheral with the given arguments. This function is + /// supposed to be called before the `enable` function. This function is + /// synchronous as the driver should only store the arguments, and should not + /// configure the hardware. + /// + /// # Arguments: + /// + /// * `payload_bit_timing` - A BitTiming structure to define the bit timing + /// settings for the frame payload + /// + /// # Return values: + /// + /// * `Ok()` - The parameters were stored. + /// * `Err(ErrorCode)` - Indicates the error because of which the request + /// cannot be completed + /// - `ErrorCode::NOSUPPORT` indicates that payload timing + /// is not supported + fn set_payload_bit_timing(&self, payload_bit_timing: BitTiming) -> Result<(), ErrorCode>; + + /// Returns the current timing parameters for the CAN peripheral. + /// + /// # Return values: + /// + /// * `Ok(BitTiming)` - The current timing for the frame payload + /// given to the peripheral + /// * `Err(ErrorCode)` - Indicates the error because of which the + /// request cannot be completed + /// - `ErrorCode::NOSUPPORT` indicates that payload timing + /// is not supported + fn get_payload_bit_timing(&self) -> Result; + + /// Returns the maximum accepted frame size in bytes. + /// + /// - for CanFD BRS this should be 8 bytes + /// - for CanFD Full this should be 64 bytes + fn get_frame_size() -> usize; +} + +/// The `Filter` trait is used to enable and disable a filter bank. +/// +/// When the receiving process starts by calling the `start_receiving_process` +/// in the `Receive` trait, there MUST be no filter enabled. +pub trait Filter { + /// Enables a filter for message reception. + /// + /// # Arguments: + /// + /// * `filter` - A FilterParameters structure to define the filter + /// configuration + /// + /// # Return values: + /// + /// * `Ok()` - The filter was successfully configured. + /// * `Err(ErrorCode)` - indicates the error because of which the + /// request cannot be completed + fn enable_filter(&self, filter: FilterParameters) -> Result<(), ErrorCode>; + + /// Disables a filter. + /// + /// # Arguments: + /// + /// * `number` - The filter Id to identify the filter bank + /// to disable + /// + /// # Return values: + /// + /// * `Ok()` - The filter was successfully disabled. + /// * `Err(ErrorCode)` - indicates the error because of which the + /// request cannot be completed + fn disable_filter(&self, number: u32) -> Result<(), ErrorCode>; + + /// Returns the number of filters the peripheral provides + fn filter_count(&self) -> usize; +} + +/// The `Controller` trait is used to enable and disable the CAN peripheral. +/// The enable process applies the settings that were previously provided +/// to the driver using the `Configure` trait. +pub trait Controller { + /// Set the client to be used for callbacks of the `Controller` implementation. + fn set_client(&self, client: Option<&'static dyn ControllerClient>); + + /// This function enables the CAN peripheral with the Timing, Operation and Mode + /// arguments that are provided to the driver before calling the + /// `enable` function. + /// + /// # Return values: + /// + /// * `Ok()` - The parameters were provided and the process can begin. + /// The driver will call the `state_changed` and `enabled` + /// callbacks after the process ends. Both of the callbacks + /// must be called and the capsule should wait for the `enable` + /// callback before transmitting or receiving frames, as enabling + /// might fail with an error. While `state_changed` will report + /// the device as being in `State::Disabled`, it does not report + /// the error. A client cannot otherwise differentiate between + /// a callback issued due to failed `enable` or a peripheral's decision + /// to enter a disabled state. + /// * `Err(ErrorCode)` - Indicates the error because of which the + /// request cannot be completed. + /// * `ErrorCode::BUSY` - the peripheral was already enabled + /// * `ErrorCode::INVAL` - no arguments were previously provided + fn enable(&self) -> Result<(), ErrorCode>; + + /// This function disables the CAN peripheral and puts it in Sleep Mode. The + /// peripheral must be previously enabled. + /// + /// # Return values: + /// + /// * `Ok()` - The peripheral was already enabled and the process can begin. + /// The driver will call the `state_changed` and `disabled` + /// callbacks after the process ends. Both of the callbacks + /// must be called and the capsule should wait for the `disabled` + /// callback before considering the peripheral disabled, as disabling + /// might fail with an erro . While `state_changed` will report + /// the device as being in `State::Enabled`, it does not report + /// the error. A client cannot otherwise differentiate between + /// a callback issued due to failed `disable` or a peripheral's decision + /// to enter the enable state. + /// * `Err(ErrorCode)` - Indicates the error because of which the + /// request cannot be completed. + /// * `ErrorCode::OFF` - the peripheral was not previously enabled + fn disable(&self) -> Result<(), ErrorCode>; + + /// This function returns the current state of the CAN peripheral. + /// + /// # Return values: + /// + /// * `Ok(State)` - The state of the CAN peripheral if it is functional + /// * `Err(ErrorCode)` - The driver cannot report the state of the peripheral + /// if it is not functional. + fn get_state(&self) -> Result; +} + +/// The `Transmit` trait is used to interact with the CAN driver through transmission +/// requests only. +/// +/// The CAN peripheral must be configured first, in order to be able to send data. +pub trait Transmit { + const PACKET_SIZE: usize = PACKET_SIZE; + /// Set the client to be used for callbacks of the `Transmit` implementation. + fn set_client(&self, client: Option<&'static dyn TransmitClient>); + + /// Sends a buffer using the CAN bus. + /// + /// In most cases, this function should be called after the peripheral was + /// previously configures and at least one filter has been enabled. + /// + /// # Arguments: + /// + /// * `id` - The identifier of the message (standard or extended) + /// * `buffer` - Data to be written on the bus + /// * `len` - Length of the current message + /// + /// # Return values: + /// * `Ok()` - The transmission request was successful and the caller + /// will receive a for the `transmit_complete` callback function call + /// * `Err(ErrorCode, &'static mut [u8])` - a tuple with the error that occurred + /// during the transmission request and + /// the buffer that was provided as an + /// argument to the function + fn send( + &self, + id: Id, + buffer: &'static mut [u8; PACKET_SIZE], + len: usize, + ) -> Result<(), (ErrorCode, &'static mut [u8; PACKET_SIZE])>; +} + +/// The `Receive` trait is used to interact with the CAN driver through receive +/// requests only. +/// +/// The CAN peripheral must be configured first, in order to be able to send data. +pub trait Receive { + const PACKET_SIZE: usize = PACKET_SIZE; + /// Set the client to be used for callbacks of the `Receive` implementation. + fn set_client(&self, client: Option<&'static dyn ReceiveClient>); + + /// Start receiving messaged on the CAN bus. + /// + /// In most cases, this function should be called after the peripheral was + /// previously configured. When calling this function, there MUST be + /// no filters enabled by the user. The implementation of this function + /// MUST permit receiving frames on all available receiving FIFOs. + /// + /// # Arguments: + /// + /// * `buffer` - A buffer to store the data + /// + /// # Return values: + /// + /// * `Ok()` - The receive request was successful and the caller waits for the + /// `message_received` callback function to receive data + /// * `Err(ErrorCode, &'static mut [u8])` - tuple with the error that occurred + /// during the reception request and + /// the buffer that was received as an + /// argument to the function + fn start_receive_process( + &self, + buffer: &'static mut [u8; PACKET_SIZE], + ) -> Result<(), (ErrorCode, &'static mut [u8; PACKET_SIZE])>; + + /// Asks the driver to stop receiving messages. This function should + /// be called only after a call to the `start_receive_process` function. + /// + /// # Return values: + /// + /// * `Ok()` - The request was successful an the caller waits for the + /// `stopped` callback function after this command + /// * `Err(ErrorCode)` - Indicates the error because of which the + /// request cannot be completed + fn stop_receive(&self) -> Result<(), ErrorCode>; +} + +/// Client interface for capsules that implement the `Controller` trait. +pub trait ControllerClient { + /// The driver calls this function when the state of the CAN peripheral is + /// changed. + /// + /// # Arguments: + /// + /// * `state` - The current state of the peripheral + fn state_changed(&self, state: State); + + /// The driver calls this function when the peripheral has been successfully + /// enabled. The driver must call this function and `state_changed` also, + /// but must wait for this function to be called. If an error occurs, the + /// `state_changed` callback might not be able to report it. + /// + /// # Arguments: + /// + /// * `status` + /// * `Ok()` - The peripheral has been successfully enabled; the + /// actual state is transmitted via `state_changed` callback + /// * `Err(ErrorCode)` - The error that occurred during the enable process + fn enabled(&self, status: Result<(), ErrorCode>); + + /// The driver calls this function when the peripheral has been successfully + /// disabled. The driver must call this function and `state_changed` also, + /// but must wait for this function to be called. If an error occurs, the + /// `state_changed` callback might not be able to report it. + /// + /// # Arguments: + /// + /// * `status` + /// * `Ok()` - The peripheral has been successfully disabled; the + /// actual state is transmitted via `state_changed` callback + /// * `Err(ErrorCode)` - The error that occurred during the disable process + fn disabled(&self, status: Result<(), ErrorCode>); +} + +/// Client interface for capsules that implement the `Transmit` trait. +pub trait TransmitClient { + /// The driver calls this function when there is an update of the last + /// message that was transmitted + /// + /// # Arguments: + /// + /// * `status` - The status for the request + /// * `Ok()` - There was no error during the transmission process + /// * `Err(Error)` - The error that occurred during the transmission process + /// * `buffer` - The buffer received as an argument for the `send` function + fn transmit_complete(&self, status: Result<(), Error>, buffer: &'static mut [u8; PACKET_SIZE]); +} + +/// Client interface for capsules that implement the `Receive` trait. +pub trait ReceiveClient { + /// The driver calls this function when a new message has been received on the given + /// FIFO. + /// + /// # Arguments: + /// + /// * `id` - The identifier of the received message + /// * `buffer` - A reference to the buffer where the data is stored. This data must + /// be stored. This buffer is usually a slice to the original buffer + /// that was supplied to the `start_receive_process`. It must be used + /// within this function call. In most cases the data is copied to a + /// driver or application buffer. + /// * `len` - The length of the buffer + /// * `status` - The status for the request + /// * `Ok()` - There was no error during the reception process + /// * `Err(Error)` - The error that occurred during the reception process + fn message_received( + &self, + id: Id, + buffer: &mut [u8; PACKET_SIZE], + len: usize, + status: Result<(), Error>, + ); + + /// The driver calls this function when the reception of messages has been stopped. + /// + /// # Arguments: + /// + /// * `buffer` - The buffer that was given as an argument to the + /// `start_receive_process` function + fn stopped(&self, buffer: &'static mut [u8; PACKET_SIZE]); +} + +/// Convenience type for capsules that configure, send +/// and receive data using the CAN peripheral +pub trait Can: + Transmit + Configure + Controller + Receive +{ +} + +pub trait CanFd: + Transmit + Configure + ConfigureFd + Receive +{ +} + +/// Provide blanket implementation for Can trait group +impl< + T: Transmit + + Configure + + Controller + + Receive, + > Can for T +{ +} + +/// Provide blanket implementation for CanFd trait group +impl + Configure + ConfigureFd + Receive> CanFd + for T +{ +} diff --git a/kernel/src/hil/crc.rs b/kernel/src/hil/crc.rs index 4d3876b153..68f3eae3ac 100644 --- a/kernel/src/hil/crc.rs +++ b/kernel/src/hil/crc.rs @@ -1,6 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for CRC computation. -use crate::utilities::leasable_buffer::LeasableBuffer; +use crate::utilities::leasable_buffer::SubSliceMut; use crate::ErrorCode; /// Client for CRC algorithm implementations @@ -11,7 +15,7 @@ pub trait Client { /// Called when the current data chunk has been processed by the /// CRC engine. Further data may be supplied when this callback is /// received. - fn input_done(&self, result: Result<(), ErrorCode>, buffer: LeasableBuffer<'static, u8>); + fn input_done(&self, result: Result<(), ErrorCode>, buffer: SubSliceMut<'static, u8>); /// Called when the CRC computation is finished. fn crc_done(&self, result: Result); @@ -103,13 +107,13 @@ pub trait Crc<'a> { /// [`Client::input_done`] is called. /// /// The implementation may only read a part of the passed - /// [`LeasableBuffer`]. It will return the bytes read and will - /// resize the returned [`LeasableBuffer`] appropriately prior to + /// [`SubSliceMut`]. It will return the bytes read and will + /// resize the returned [`SubSliceMut`] appropriately prior to /// passing it back through [`Client::input_done`]. fn input( &self, - data: LeasableBuffer<'static, u8>, - ) -> Result<(), (ErrorCode, LeasableBuffer<'static, u8>)>; + data: SubSliceMut<'static, u8>, + ) -> Result<(), (ErrorCode, SubSliceMut<'static, u8>)>; /// Request calculation of the CRC. /// diff --git a/kernel/src/hil/dac.rs b/kernel/src/hil/dac.rs index 300a522c30..b56e02c453 100644 --- a/kernel/src/hil/dac.rs +++ b/kernel/src/hil/dac.rs @@ -1,12 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for digital to analog converters. use crate::ErrorCode; /// Simple interface for using the DAC. pub trait DacChannel { - /// Initialize and enable the DAC. - fn initialize(&self) -> Result<(), ErrorCode>; - /// Set the DAC output value. fn set_value(&self, value: usize) -> Result<(), ErrorCode>; } diff --git a/kernel/src/hil/date_time.rs b/kernel/src/hil/date_time.rs new file mode 100644 index 0000000000..71eb026260 --- /dev/null +++ b/kernel/src/hil/date_time.rs @@ -0,0 +1,77 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! HIL for Date Time interface + +use crate::ErrorCode; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum DayOfWeek { + Sunday, + Monday, + Tuesday, + Wednesday, + Thursday, + Friday, + Saturday, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Month { + January, + February, + March, + April, + May, + June, + July, + August, + September, + October, + November, + December, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub struct DateTimeValues { + pub year: u16, + pub month: Month, + pub day: u8, + pub day_of_week: DayOfWeek, + pub hour: u8, + pub minute: u8, + pub seconds: u8, +} + +/// Interface for reading and setting the current time +pub trait DateTime<'a> { + /// Request driver to return date and time + /// + /// When successful, this function will be followed by the callback + /// `callback_get_date` which provides the actual date and time + /// or an error. + fn get_date_time(&self) -> Result<(), ErrorCode>; + + /// Sets the current date and time + /// + /// When successful this function call must be followed by a call + /// to `callback_set_date`. + fn set_date_time(&self, date_time: DateTimeValues) -> Result<(), ErrorCode>; + + /// Sets a client that calls the callback function when date and time is requested + fn set_client(&self, client: &'a dyn DateTimeClient); +} + +/// Callback handler for when current date is read or set. +pub trait DateTimeClient { + /// Called when a date time reading has completed. + /// Takes `Ok(DateTime)` of current date and passes it when scheduling an upcall. + /// If an error is encountered it takes an `Err(ErrorCode)` + fn get_date_time_done(&self, datetime: Result); + + /// Called when a date is set + /// Takes `Ok(())` if time is set correctly. + /// Takes `Err(ErrorCode)` in case of an error + fn set_date_time_done(&self, result: Result<(), ErrorCode>); +} diff --git a/kernel/src/hil/digest.rs b/kernel/src/hil/digest.rs index 27c4e6825c..27defc2c20 100644 --- a/kernel/src/hil/digest.rs +++ b/kernel/src/hil/digest.rs @@ -1,148 +1,219 @@ -//! Interface for Digest +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. -use crate::utilities::leasable_buffer::LeasableBuffer; +//! Interface for computing digests (hashes, cryptographic hashes, and +//! HMACs) over data. + +use crate::utilities::leasable_buffer::SubSlice; +use crate::utilities::leasable_buffer::SubSliceMut; use crate::ErrorCode; -/// Implement this trait and use `set_client()` in order to receive callbacks. +/// Implement this trait and use `set_client()` in order to receive callbacks +/// when data has been added to a digest. /// /// 'L' is the length of the 'u8' array to store the digest output. -pub trait ClientData<'a, const L: usize> { - /// This callback is called when the data has been added to the digest - /// engine. - /// On error or success `data` will contain a reference to the original - /// data supplied to `add_data()`. - fn add_data_done(&'a self, result: Result<(), ErrorCode>, data: &'static mut [u8]); +pub trait ClientData { + /// Called when the data has been added to the digest. `data` is + /// the `SubSlice` passed in the call to `add_data`, whose + /// active slice contains the data that was not added. On `Ok`, + /// `data` has an active slice of size zero (all data was added). + /// Valid `ErrorCode` values are: + /// - OFF: the underlying digest engine is powered down and + /// cannot be used. + /// - BUSY: there is an outstanding `add_data`, `add_data_mut`, + /// `run`, or `verify` operation, so the digest engine is busy + /// and cannot accept more data. + /// - SIZE: the active slice of the SubSlice has zero size. + /// - CANCEL: the operation was cancelled by a call to `clear_data`. + /// - FAIL: an internal failure. + fn add_data_done(&self, result: Result<(), ErrorCode>, data: SubSlice<'static, u8>); + + /// Called when the data has been added to the digest. `data` is + /// the `SubSliceMut` passed in the call to + /// `add_mut_data`, whose active slice contains the data that was + /// not added. On `Ok`, `data` has an active slice of size zero + /// (all data was added). Valid `ErrorCode` values are: + /// - OFF: the underlying digest engine is powered down and + /// cannot be used. + /// - BUSY: there is an outstanding `add_data`, `add_data_mut`, + /// `run`, or `verify` operation, so the digest engine is busy + /// and cannot accept more data. + /// - SIZE: the active slice of the SubSlice has zero size. + /// - CANCEL: the operation was cancelled by a call to `clear_data`. + /// - FAIL: an internal failure. + fn add_mut_data_done(&self, result: Result<(), ErrorCode>, data: SubSliceMut<'static, u8>); } -/// Implement this trait and use `set_client()` in order to receive callbacks. +/// Implement this trait and use `set_client()` in order to receive callbacks when +/// a digest is completed. /// /// 'L' is the length of the 'u8' array to store the digest output. -pub trait ClientHash<'a, const L: usize> { - /// This callback is called when a digest is computed. - /// On error or success `digest` will contain a reference to the original - /// data supplied to `run()`. - fn hash_done(&'a self, result: Result<(), ErrorCode>, digest: &'static mut [u8; L]); +pub trait ClientHash { + /// Called when a digest is computed. `digest` is the same + /// reference passed to `run()` to store the hash value. If + /// `result` is `Ok`, `digest` stores the computed hash. If + /// `result` is `Err`, the data stored in `digest` is undefined + /// and may have any value. Valid `ErrorCode` values are: + /// - OFF: the underlying digest engine is powered down and + /// cannot be used. + /// - BUSY: there is an outstanding `add_data`, `add_data_mut`, + /// `run`, or `verify` operation, so the digest engine is busy + /// and cannot perform a hash. + /// - CANCEL: the operation was cancelled by a call to `clear_data`. + /// - NOSUPPORT: the requested digest algorithm is not supported, + /// or one was not requested. + /// - FAIL: an internal failure. + fn hash_done(&self, result: Result<(), ErrorCode>, digest: &'static mut [u8; L]); } -/// Implement this trait and use `set_client()` in order to receive callbacks. +/// Implement this trait and use `set_client()` in order to receive callbacks when +/// digest verification is complete. /// /// 'L' is the length of the 'u8' array to store the digest output. -pub trait ClientVerify<'a, const L: usize> { - /// This callback is called when a verification is computed. - /// On error or success `digest` will contain a reference to the original - /// data supplied to `verify()`. - /// On success the result indicate if the hashes match or don't. - /// On failure the result will indicate an `ErrorCode`. - fn verification_done(&'a self, result: Result, compare: &'static mut [u8; L]); +pub trait ClientVerify { + /// Called when a verification is computed. `compare` is the + /// reference supplied to `verify()` and the data stored in + /// `compare` is unchanged. On `Ok` the `bool` indicates if the + /// computed hash matches the value in `compare`. Valid + /// `ErrorCode` values are: + /// - OFF: the underlying digest engine is powered down and + /// cannot be used. + /// - BUSY: there is an outstanding `add_data`, `add_data_mut`, + /// `run`, or `verify` operation, so the digest engine is busy + /// and cannot verify a hash. + /// - CANCEL: the operation was cancelled by a call to `clear_data`. + /// - NOSUPPORT: the requested digest algorithm is not supported, + /// or one was not requested. + /// - FAIL: an internal failure. + fn verification_done(&self, result: Result, compare: &'static mut [u8; L]); } -pub trait Client<'a, const L: usize>: - ClientData<'a, L> + ClientHash<'a, L> + ClientVerify<'a, L> -{ -} - -impl<'a, T: ClientData<'a, L> + ClientHash<'a, L> + ClientVerify<'a, L>, const L: usize> - Client<'a, L> for T -{ -} +pub trait Client: ClientData + ClientHash + ClientVerify {} -pub trait ClientDataHash<'a, const L: usize>: ClientData<'a, L> + ClientHash<'a, L> {} +impl + ClientHash + ClientVerify, const L: usize> Client for T {} -impl<'a, T: ClientData<'a, L> + ClientHash<'a, L>, const L: usize> ClientDataHash<'a, L> for T {} +pub trait ClientDataHash: ClientData + ClientHash {} +impl + ClientHash, const L: usize> ClientDataHash for T {} -pub trait ClientDataVerify<'a, const L: usize>: ClientData<'a, L> + ClientVerify<'a, L> {} +pub trait ClientDataVerify: ClientData + ClientVerify {} +impl + ClientVerify, const L: usize> ClientDataVerify for T {} -impl<'a, T: ClientData<'a, L> + ClientVerify<'a, L>, const L: usize> ClientDataVerify<'a, L> for T {} - -/// Computes a digest (cryptographic hash) over data +/// Adding data (mutable or immutable) to a digest. There are two +/// separate methods, `add_data` for immutable data (e.g., flash) and +/// `add_mut_data` for mutable data (e.g., RAM). Each has its own +/// callback, but only one operation may be in flight at any time. /// /// 'L' is the length of the 'u8' array to store the digest output. pub trait DigestData<'a, const L: usize> { - /// Set the client instance which will receive the `add_data_done()` - /// callback. - /// This is not required if using the `set_client()` fuction from the - /// `Digest` trait. - #[allow(unused_variables)] - fn set_data_client(&'a self, client: &'a dyn ClientData<'a, L>) {} - - /// Add data to the digest block. This is the data that will be used - /// for the hash function. - /// Returns the number of bytes parsed on success - /// There is no guarantee the data has been written until the `add_data_done()` - /// callback is fired. - /// On error the return value will contain a return code and the original data + /// Set the client instance which will handle the `add_data_done` + /// and `add_mut_data_done` callbacks. + fn set_data_client(&'a self, client: &'a dyn ClientData); + + /// Add data to the input of the hash function/digest. `Ok` + /// indicates all of the active bytes in `data` will be added. + /// There is no guarantee the data has been added to the digest + /// until the `add_data_done()` callback is called. On error the + /// cause of the error is returned along with the SubSlice + /// unchanged (it has the same range of active bytes as the call). + /// Valid `ErrorCode` values are: + /// - OFF: the underlying digest engine is powered down and + /// cannot be used. + /// - BUSY: there is an outstanding `add_data`, `add_data_mut`, + /// `run`, or `verify` operation, so the digest engine is busy + /// and cannot accept more data. + /// - SIZE: the active slice of the SubSlice has zero size. fn add_data( &self, - data: LeasableBuffer<'static, u8>, - ) -> Result; + data: SubSlice<'static, u8>, + ) -> Result<(), (ErrorCode, SubSlice<'static, u8>)>; - /// Clear the keys and any other sensitive data. - /// This won't clear the buffers provided to this API, that is up to the - /// user to clear. + /// Add data to the input of the hash function/digest. `Ok` + /// indicates all of the active bytes in `data` will be added. + /// There is no guarantee the data has been added to the digest + /// until the `add_mut_data_done()` callback is called. On error + /// the cause of the error is returned along with the + /// SubSlice unchanged (it has the same range of active + /// bytes as the call). Valid `ErrorCode` values are: + /// - OFF: the underlying digest engine is powered down and + /// cannot be used. + /// - BUSY: there is an outstanding `add_data`, `add_data_mut`, + /// `run`, or `verify` operation, so the digest engine is busy + /// and cannot accept more data. + /// - SIZE: the active slice of the SubSlice has zero size. + fn add_mut_data( + &self, + data: SubSliceMut<'static, u8>, + ) -> Result<(), (ErrorCode, SubSliceMut<'static, u8>)>; + + /// Clear the keys and any other internal state. Any pending + /// operations terminate and issue a callback with an + /// `ErrorCode::CANCEL`. This call does not clear buffers passed + /// through `add_mut_data`, those are up to the client clear. fn clear_data(&self); } /// Computes a digest (cryptographic hash) over data provided through a -/// separate trait +/// separate trait. /// /// 'L' is the length of the 'u8' array to store the digest output. pub trait DigestHash<'a, const L: usize> { /// Set the client instance which will receive the `hash_done()` /// callback. - /// This is not required if using the `set_client()` fuction from the - /// `Digest` trait. - #[allow(unused_variables)] - fn set_hash_client(&'a self, client: &'a dyn ClientHash<'a, L>) {} - - /// Request the hardware block to generate a Digest and stores the returned - /// digest in the memory location specified. - /// This doesn't return any data, instead the client needs to have - /// set a `hash_done` handler to determine when this is complete. - /// On error the return value will contain a return code and the original data - /// If there is data from the `add_data()` command asyncrously waiting to - /// be written it will be written before the operation starts. + fn set_hash_client(&'a self, client: &'a dyn ClientHash); + + /// Compute a digest of all of the data added with `add_data` and + /// `add_data_mut`, storing the computed value in `digest`. The + /// computed value is returned in a `hash_done` callback. On + /// error the return value will contain a return code and the + /// slice passed in `digest`. Valid `ErrorCode` values are: + /// - OFF: the underlying digest engine is powered down and + /// cannot be used. + /// - BUSY: there is an outstanding `add_data`, `add_data_mut`, + /// `run`, or `verify` operation, so the digest engine is busy + /// and cannot accept more data. + /// - SIZE: the active slice of the SubSlice has zero size. + /// - NOSUPPORT: the currently selected digest algorithm is not + /// supported. /// /// If an appropriate `set_mode*()` wasn't called before this function the /// implementation should try to use a default option. In the case where /// there is only one digest supported this should be used. If there is no - /// suitable or obvious default option, the implementation can return an - /// error with error code ENOSUPPORT. + /// suitable or obvious default option, the implementation can return + /// `ErrorCode::NOSUPPORT`. fn run(&'a self, digest: &'static mut [u8; L]) -> Result<(), (ErrorCode, &'static mut [u8; L])>; } -/// Computes a digest (cryptographic hash) over data provided through a +/// Verifies a digest (cryptographic hash) over data provided through a /// separate trait /// /// 'L' is the length of the 'u8' array to store the digest output. pub trait DigestVerify<'a, const L: usize> { /// Set the client instance which will receive the `verification_done()` /// callback. - /// This is not required if using the `set_client()` fuction from the - /// `Digest` trait. - #[allow(unused_variables)] - fn set_verify_client(&'a self, client: &'a dyn ClientVerify<'a, L>) {} - - /// Compare the specified digest in the `compare` buffer to the calculated - /// digest. This function is similar to `run()` and should be used instead - /// of `run()` if the caller doesn't need to know the output, just if it - /// matches a known value. - /// - /// For example: - /// ```ignore - /// // Compute a digest on data - /// add_data(...); - /// add_data(...); - /// - /// // Compare the computed digest generated to an existing digest - /// verify(...); - /// ``` - /// NOTE: The above is just pseudo code. The user is expected to check for - /// errors and wait for the asyncronous calls to complete. + fn set_verify_client(&'a self, client: &'a dyn ClientVerify); + + /// Compute a digest of all of the data added with `add_data` and + /// `add_data_mut` then compare it with value in `compare`. The + /// compare value is returned in a `verification_done` callback, along with + /// a boolean indicating whether it matches the computed value. On + /// error the return value will contain a return code and the + /// slice passed in `compare`. Valid `ErrorCode` values are: + /// - OFF: the underlying digest engine is powered down and + /// cannot be used. + /// - BUSY: there is an outstanding `add_data`, `add_data_mut`, + /// `run`, or `verify` operation, so the digest engine is busy + /// and cannot accept more data. + /// - SIZE: the active slice of the SubSlice has zero size. + /// - NOSUPPORT: the currently selected digest algorithm is not + /// supported. /// - /// The verify function is useful to compare input with a known digest - /// value. The verify function also saves callers from allocating a buffer - /// for the digest when they just need verification. + /// If an appropriate `set_mode*()` wasn't called before this function the + /// implementation should try to use a default option. In the case where + /// there is only one digest supported this should be used. If there is no + /// suitable or obvious default option, the implementation can return + /// `ErrorCode::NOSUPPORT`. fn verify( &'a self, compare: &'static mut [u8; L], @@ -157,59 +228,63 @@ pub trait Digest<'a, const L: usize>: { /// Set the client instance which will receive `hash_done()`, /// `add_data_done()` and `verification_done()` callbacks. - fn set_client(&'a self, client: &'a dyn Client<'a, L>); + fn set_client(&'a self, client: &'a dyn Client); } -/// Computes a digest (cryptographic hash) over data +/// Computes a digest (cryptographic hash) over data. /// /// 'L' is the length of the 'u8' array to store the digest output. -pub trait DigestDataHash<'a, const L: usize>: DigestData<'a, L> + DigestHash<'a, L> {} - -impl<'a, T: DigestData<'a, L> + DigestHash<'a, L>, const L: usize> DigestDataHash<'a, L> for T {} +pub trait DigestDataHash<'a, const L: usize>: DigestData<'a, L> + DigestHash<'a, L> { + /// Set the client instance which will receive `hash_done()` and + /// `add_data_done()` callbacks. + fn set_client(&'a self, client: &'a dyn ClientDataHash); +} -/// Performs a verification on data +/// Verify a digest (cryptographic hash) over data. /// /// 'L' is the length of the 'u8' array to store the digest output. -pub trait DigestDataVerify<'a, const L: usize>: DigestData<'a, L> + DigestVerify<'a, L> {} - -impl<'a, T: DigestData<'a, L> + DigestVerify<'a, L>, const L: usize> DigestDataVerify<'a, L> for T {} +pub trait DigestDataVerify<'a, const L: usize>: DigestData<'a, L> + DigestVerify<'a, L> { + /// Set the client instance which will receive `verify_done()` and + /// `add_data_done()` callbacks. + fn set_client(&'a self, client: &'a dyn ClientDataVerify); +} pub trait Sha224 { - /// Call before `Digest::run()` to perform Sha224 + /// Call before adding data to perform Sha224 fn set_mode_sha224(&self) -> Result<(), ErrorCode>; } pub trait Sha256 { - /// Call before `Digest::run()` to perform Sha256 + /// Call before adding data to perform Sha256 fn set_mode_sha256(&self) -> Result<(), ErrorCode>; } pub trait Sha384 { - /// Call before `Digest::run()` to perform Sha384 + /// Call before adding data to perform Sha384 fn set_mode_sha384(&self) -> Result<(), ErrorCode>; } pub trait Sha512 { - /// Call before `Digest::run()` to perform Sha512 + /// Call before adding data to perform Sha512 fn set_mode_sha512(&self) -> Result<(), ErrorCode>; } -pub trait HMACSha256 { - /// Call before `Digest::run()` to perform HMACSha256 +pub trait HmacSha256 { + /// Call before adding data to perform HMACSha256 /// /// The key used for the HMAC is passed to this function. fn set_mode_hmacsha256(&self, key: &[u8]) -> Result<(), ErrorCode>; } -pub trait HMACSha384 { - /// Call before `Digest::run()` to perform HMACSha384 +pub trait HmacSha384 { + /// Call before adding data to perform HMACSha384 /// /// The key used for the HMAC is passed to this function. fn set_mode_hmacsha384(&self, key: &[u8]) -> Result<(), ErrorCode>; } -pub trait HMACSha512 { - /// Call before `Digest::run()` to perform HMACSha512 +pub trait HmacSha512 { + /// Call before adding data to perform HMACSha512 /// /// The key used for the HMAC is passed to this function. fn set_mode_hmacsha512(&self, key: &[u8]) -> Result<(), ErrorCode>; diff --git a/kernel/src/hil/eic.rs b/kernel/src/hil/eic.rs index cfacb51ea9..28634c39f4 100644 --- a/kernel/src/hil/eic.rs +++ b/kernel/src/hil/eic.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for external interrupt controller. //! //! The External Interrupt Controller (EIC) allows pins to be configured as @@ -25,7 +29,7 @@ pub trait ExternalInterruptController { type Line; /// Enables external interrupt on the given 'line' - /// In asychronous mode, all edge interrupts will be + /// In asynchronous mode, all edge interrupts will be /// interpreted as level interrupts and the filter is disabled. fn line_enable(&self, line: &Self::Line, interrupt_mode: InterruptMode); diff --git a/kernel/src/hil/entropy.rs b/kernel/src/hil/entropy.rs index dd3446a3ee..434dc16987 100644 --- a/kernel/src/hil/entropy.rs +++ b/kernel/src/hil/entropy.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interfaces for accessing an entropy source. //! //! An entropy source produces random bits that are computationally @@ -101,7 +105,7 @@ pub enum Continue { /// Implementors should assume the client implements the /// [Client](trait.Client32.html) trait. pub trait Entropy32<'a> { - /// Initiate the aquisition of entropy. + /// Initiate the acquisition of entropy. /// /// There are three valid return values: /// - Ok(()): a `entropy_available` callback will be called in diff --git a/kernel/src/hil/flash.rs b/kernel/src/hil/flash.rs index 1996b6b250..1bb93c2214 100644 --- a/kernel/src/hil/flash.rs +++ b/kernel/src/hil/flash.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for reading, writing, and erasing flash storage pages. //! //! Operates on single pages. The page size is set by the associated type @@ -84,9 +88,9 @@ //! } //! //! impl<'a, F: hil::flash::Flash> hil::flash::Client for FlashUser<'a, F> { -//! fn read_complete(&self, buffer: &'static mut F::Page, error: hil::flash::Error) {} -//! fn write_complete(&self, buffer: &'static mut F::Page, error: hil::flash::Error) { } -//! fn erase_complete(&self, error: hil::flash::Error) {} +//! fn read_complete(&self, buffer: &'static mut F::Page, result: Result<(), hil::flash::Error>) {} +//! fn write_complete(&self, buffer: &'static mut F::Page, result: Result<(), hil::flash::Error>) { } +//! fn erase_complete(&self, result: Result<(), hil::flash::Error>) {} //! } //! ``` @@ -95,11 +99,11 @@ use crate::ErrorCode; /// Flash errors returned in the callbacks. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Error { - /// Success. - CommandComplete, - /// An error occurred during the flash operation. FlashError, + + /// A flash memory protection violation was detected. + FlashMemoryProtectionError, } pub trait HasClient<'a, C> { @@ -134,11 +138,11 @@ pub trait Flash { /// Implement `Client` to receive callbacks from `Flash`. pub trait Client { /// Flash read complete. - fn read_complete(&self, read_buffer: &'static mut F::Page, error: Error); + fn read_complete(&self, read_buffer: &'static mut F::Page, result: Result<(), Error>); /// Flash write complete. - fn write_complete(&self, write_buffer: &'static mut F::Page, error: Error); + fn write_complete(&self, write_buffer: &'static mut F::Page, result: Result<(), Error>); /// Flash erase complete. - fn erase_complete(&self, error: Error); + fn erase_complete(&self, result: Result<(), Error>); } diff --git a/kernel/src/hil/gpio.rs b/kernel/src/hil/gpio.rs index 6ab786f1f1..f974909e32 100644 --- a/kernel/src/hil/gpio.rs +++ b/kernel/src/hil/gpio.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! HIL for General Purpose Input-Output (GPIO) pins. use core::cell::Cell; diff --git a/kernel/src/hil/gpio_async.rs b/kernel/src/hil/gpio_async.rs index db38dfbc40..f975799e24 100644 --- a/kernel/src/hil/gpio_async.rs +++ b/kernel/src/hil/gpio_async.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for GPIO pins that require split-phase operation to control. use crate::hil; diff --git a/kernel/src/hil/hasher.rs b/kernel/src/hil/hasher.rs new file mode 100644 index 0000000000..415690b7ff --- /dev/null +++ b/kernel/src/hil/hasher.rs @@ -0,0 +1,107 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Interface for Hasher + +use crate::utilities::leasable_buffer::SubSlice; +use crate::utilities::leasable_buffer::SubSliceMut; +use crate::ErrorCode; + +/// Implement this trait and use `set_client()` in order to receive callbacks. +/// +/// 'L' is the length of the 'u8' array to store the hash output. +pub trait Client { + /// This callback is called when the data has been added to the hash + /// engine. + /// On error or success `data` will contain a reference to the original + /// data supplied to `add_data()`. + /// The possible ErrorCodes are: + /// - SIZE: The size of the `data` buffer is invalid + fn add_data_done(&self, result: Result<(), ErrorCode>, data: SubSlice<'static, u8>); + + /// This callback is called when the data has been added to the hash + /// engine. + /// On error or success `data` will contain a reference to the original + /// data supplied to `add_mut_data()`. + /// The possible ErrorCodes are: + /// - SIZE: The size of the `data` buffer is invalid + fn add_mut_data_done(&self, result: Result<(), ErrorCode>, data: SubSliceMut<'static, u8>); + + /// This callback is called when a hash is computed. + /// On error or success `hash` will contain a reference to the original + /// data supplied to `run()`. + /// The possible ErrorCodes are: + /// - SIZE: The size of the `data` buffer is invalid + fn hash_done(&self, result: Result<(), ErrorCode>, hash: &'static mut [u8; L]); +} + +/// Computes a non-cryptographic hash over data +/// +/// 'L' is the length of the 'u8' array to store the hash output. +pub trait Hasher<'a, const L: usize> { + /// Set the client instance which will receive `hash_done()` and + /// `add_data_done()` callbacks. + /// This callback is called when the data has been added to the hash + /// engine. + /// The callback should follow the `Client` `add_data_done` callback. + fn set_client(&'a self, client: &'a dyn Client); + + /// Add data to the hash block. This is the data that will be used + /// for the hash function. + /// Returns the number of bytes parsed on success + /// There is no guarantee the data has been written until the `add_data_done()` + /// callback is fired. + /// On error the return value will contain a return code and the original data + /// The possible ErrorCodes are: + /// - BUSY: The system is busy performing an operation + /// The caller should expect a callback + /// - SIZE: The size of the `data` buffer is invalid + fn add_data( + &self, + data: SubSlice<'static, u8>, + ) -> Result)>; + + /// Add data to the hash block. This is the data that will be used + /// for the hash function. + /// Returns the number of bytes parsed on success + /// There is no guarantee the data has been written until the `add_data_done()` + /// callback is fired. + /// On error the return value will contain a return code and the original data + /// The possible ErrorCodes are: + /// - BUSY: The system is busy performing an operation + /// The caller should expect a callback + /// - SIZE: The size of the `data` buffer is invalid + fn add_mut_data( + &self, + data: SubSliceMut<'static, u8>, + ) -> Result)>; + + /// Request the implementation to generate a hash and stores the returned + /// hash in the memory location specified. + /// This doesn't return any data, instead the client needs to have + /// set a `hash_done` handler to determine when this is complete. + /// On error the return value will contain a return code and the original data + /// If there is data from the `add_data()` command asyncrously waiting to + /// be written it will be written before the operation starts. + /// The possible ErrorCodes are: + /// - BUSY: The system is busy performing an operation + /// The caller should expect a callback + /// - SIZE: The size of the `data` buffer is invalid + fn run(&'a self, hash: &'static mut [u8; L]) -> Result<(), (ErrorCode, &'static mut [u8; L])>; + + /// Clear the internal state of the engine. + /// This won't clear the buffers provided to this API, that is up to the + /// user to clear. + fn clear_data(&self); +} + +pub trait SipHash { + /// Optionaly call before `Hasher::run()` to specify the keys used + /// The possible ErrorCodes are: + /// - BUSY: The system is busy + /// - ALREADY: An operation is already on going + /// - INVAL: An invalid parameter was supplied + /// - NOSUPPORT: The operation is not supported + fn set_keys(&self, k0: u64, k1: u64) -> Result<(), ErrorCode>; +} diff --git a/kernel/src/hil/hw_debug.rs b/kernel/src/hil/hw_debug.rs new file mode 100644 index 0000000000..4ce358996d --- /dev/null +++ b/kernel/src/hil/hw_debug.rs @@ -0,0 +1,33 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Interfaces for interacting with debug hardware integrated in various SoCs. +//! Currently allows reading the cycle counter. + +pub trait CycleCounter { + /// Enable and start the cycle counter. + /// Depending on the underlying hardware, it may be necessary to call reset + /// before starting the cycle counter for the first time to get accurate results. + fn start(&self); + + /// Stop the cycle counter. + /// Does nothing if the cycle counter is not present. + fn stop(&self); + + /// Return the current value of the cycle counter. + fn count(&self) -> u64; + + /// Reset the counter to zero and stop the cycle counter. + fn reset(&self); + + /// Benchmark the number of cycles to run a passed closure. + /// This function is intended for use debugging in-kernel routines. + fn profile_closure(&self, f: F) -> u64 { + self.reset(); + self.start(); + f(); + self.stop(); + self.count() + } +} diff --git a/kernel/src/hil/i2c.rs b/kernel/src/hil/i2c.rs index 95455d822b..373daede8f 100644 --- a/kernel/src/hil/i2c.rs +++ b/kernel/src/hil/i2c.rs @@ -1,8 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for I2C master and slave peripherals. use crate::ErrorCode; -use core::convert::Into; use core::fmt; use core::fmt::{Display, Formatter}; @@ -32,14 +35,14 @@ pub enum Error { Busy, } -impl Into for Error { - fn into(self) -> ErrorCode { - match self { - Self::AddressNak | Self::DataNak => ErrorCode::NOACK, - Self::ArbitrationLost => ErrorCode::RESERVE, - Self::Overrun => ErrorCode::SIZE, - Self::NotSupported => ErrorCode::NOSUPPORT, - Self::Busy => ErrorCode::BUSY, +impl From for ErrorCode { + fn from(val: Error) -> Self { + match val { + Error::AddressNak | Error::DataNak => ErrorCode::NOACK, + Error::ArbitrationLost => ErrorCode::RESERVE, + Error::Overrun => ErrorCode::SIZE, + Error::NotSupported => ErrorCode::NOSUPPORT, + Error::Busy => ErrorCode::BUSY, } } } @@ -66,35 +69,35 @@ pub enum SlaveTransmissionType { } /// Interface for an I2C Master hardware driver. -pub trait I2CMaster { - fn set_master_client(&self, master_client: &'static dyn I2CHwMasterClient); +pub trait I2CMaster<'a> { + fn set_master_client(&self, master_client: &'a dyn I2CHwMasterClient); fn enable(&self); fn disable(&self); fn write_read( &self, addr: u8, data: &'static mut [u8], - write_len: u8, - read_len: u8, + write_len: usize, + read_len: usize, ) -> Result<(), (Error, &'static mut [u8])>; fn write( &self, addr: u8, data: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (Error, &'static mut [u8])>; fn read( &self, addr: u8, buffer: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (Error, &'static mut [u8])>; } /// Interface for an SMBus Master hardware driver. /// The device implementing this will also seperately implement /// I2CMaster. -pub trait SMBusMaster: I2CMaster { +pub trait SMBusMaster<'a>: I2CMaster<'a> { /// Write data then read data via the I2C Master device in an SMBus /// compatible way. /// @@ -113,8 +116,8 @@ pub trait SMBusMaster: I2CMaster { &self, addr: u8, data: &'static mut [u8], - write_len: u8, - read_len: u8, + write_len: usize, + read_len: usize, ) -> Result<(), (Error, &'static mut [u8])>; /// Write data via the I2C Master device in an SMBus compatible way. @@ -132,7 +135,7 @@ pub trait SMBusMaster: I2CMaster { &self, addr: u8, data: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (Error, &'static mut [u8])>; /// Read data via the I2C Master device in an SMBus compatible way. @@ -150,34 +153,34 @@ pub trait SMBusMaster: I2CMaster { &self, addr: u8, buffer: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (Error, &'static mut [u8])>; } /// Interface for an I2C Slave hardware driver. -pub trait I2CSlave { - fn set_slave_client(&self, slave_client: &'static dyn I2CHwSlaveClient); +pub trait I2CSlave<'a> { + fn set_slave_client(&self, slave_client: &'a dyn I2CHwSlaveClient); fn enable(&self); fn disable(&self); fn set_address(&self, addr: u8) -> Result<(), Error>; fn write_receive( &self, data: &'static mut [u8], - max_len: u8, + max_len: usize, ) -> Result<(), (Error, &'static mut [u8])>; fn read_send( &self, data: &'static mut [u8], - max_len: u8, + max_len: usize, ) -> Result<(), (Error, &'static mut [u8])>; fn listen(&self); } /// Convenience type for capsules that need hardware that supports both /// Master and Slave modes. -pub trait I2CMasterSlave: I2CMaster + I2CSlave {} +pub trait I2CMasterSlave<'a>: I2CMaster<'a> + I2CSlave<'a> {} // Provide blanket implementations for trait group -impl I2CMasterSlave for T {} +// impl I2CMasterSlave for T {} /// Client interface for capsules that use I2CMaster devices. pub trait I2CHwMasterClient { @@ -192,7 +195,7 @@ pub trait I2CHwSlaveClient { fn command_complete( &self, buffer: &'static mut [u8], - length: u8, + length: usize, transmission_type: SlaveTransmissionType, ); @@ -220,11 +223,12 @@ pub trait I2CDevice { fn write_read( &self, data: &'static mut [u8], - write_len: u8, - read_len: u8, + write_len: usize, + read_len: usize, ) -> Result<(), (Error, &'static mut [u8])>; - fn write(&self, data: &'static mut [u8], len: u8) -> Result<(), (Error, &'static mut [u8])>; - fn read(&self, buffer: &'static mut [u8], len: u8) -> Result<(), (Error, &'static mut [u8])>; + fn write(&self, data: &'static mut [u8], len: usize) -> Result<(), (Error, &'static mut [u8])>; + fn read(&self, buffer: &'static mut [u8], len: usize) + -> Result<(), (Error, &'static mut [u8])>; } pub trait SMBusDevice: I2CDevice { @@ -244,8 +248,8 @@ pub trait SMBusDevice: I2CDevice { fn smbus_write_read( &self, data: &'static mut [u8], - write_len: u8, - read_len: u8, + write_len: usize, + read_len: usize, ) -> Result<(), (Error, &'static mut [u8])>; /// Write data to a slave device in an SMBus compatible way. @@ -261,7 +265,7 @@ pub trait SMBusDevice: I2CDevice { fn smbus_write( &self, data: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (Error, &'static mut [u8])>; /// Read data from a slave device in an SMBus compatible way. @@ -277,7 +281,7 @@ pub trait SMBusDevice: I2CDevice { fn smbus_read( &self, buffer: &'static mut [u8], - len: u8, + len: usize, ) -> Result<(), (Error, &'static mut [u8])>; } @@ -287,3 +291,66 @@ pub trait I2CClient { /// successfully or if an error occured. fn command_complete(&self, buffer: &'static mut [u8], status: Result<(), Error>); } + +pub struct NoSMBus; + +impl<'a> I2CMaster<'a> for NoSMBus { + fn set_master_client(&self, _master_client: &'a dyn I2CHwMasterClient) {} + fn enable(&self) {} + fn disable(&self) {} + fn write_read( + &self, + _addr: u8, + data: &'static mut [u8], + _write_len: usize, + _read_len: usize, + ) -> Result<(), (Error, &'static mut [u8])> { + Err((Error::NotSupported, data)) + } + fn write( + &self, + _addr: u8, + data: &'static mut [u8], + _len: usize, + ) -> Result<(), (Error, &'static mut [u8])> { + Err((Error::NotSupported, data)) + } + fn read( + &self, + _addr: u8, + buffer: &'static mut [u8], + _len: usize, + ) -> Result<(), (Error, &'static mut [u8])> { + Err((Error::NotSupported, buffer)) + } +} + +impl<'a> SMBusMaster<'a> for NoSMBus { + fn smbus_write_read( + &self, + _addr: u8, + data: &'static mut [u8], + _write_len: usize, + _read_len: usize, + ) -> Result<(), (Error, &'static mut [u8])> { + Err((Error::NotSupported, data)) + } + + fn smbus_write( + &self, + _addr: u8, + data: &'static mut [u8], + _len: usize, + ) -> Result<(), (Error, &'static mut [u8])> { + Err((Error::NotSupported, data)) + } + + fn smbus_read( + &self, + _addr: u8, + buffer: &'static mut [u8], + _len: usize, + ) -> Result<(), (Error, &'static mut [u8])> { + Err((Error::NotSupported, buffer)) + } +} diff --git a/kernel/src/hil/kv.rs b/kernel/src/hil/kv.rs new file mode 100644 index 0000000000..25fff733ea --- /dev/null +++ b/kernel/src/hil/kv.rs @@ -0,0 +1,481 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. + +//! Interface for Key-Value (KV) Stores +//! +//! The KV store implementation in Tock has two levels: +//! +//! 1. **KV Level**: This level provides a standard key-value interface with +//! common get/set/add/update/delete operations. +//! +//! 2. **KV Permissions Level**: This level mirrors the `KV` interface, but each +//! call requires storage permissions. This permits implementing access +//! control permissions with key-value stores in Tock. +//! +//! The expected setup inside Tock will look like this: +//! +//! ```text +//! +-----------------------+ +//! | Capsule using K-V | +//! +-----------------------+ +//! +//! hil::kv::KVPermissions (this file) +//! +//! +-----------------------+ +//! | K-V in Tock | +//! +-----------------------+ +//! +//! hil::kv::KV (this file) +//! +//! +-----------------------+ +//! | K-V library | +//! +-----------------------+ +//! +//! hil::flash +//! ``` + +use crate::storage_permissions::StoragePermissions; +use crate::utilities::leasable_buffer::SubSliceMut; +use crate::ErrorCode; + +/// Callback trait for KV stores. +/// +/// Implement this trait and use `set_client()` to receive callbacks. +pub trait KVClient { + /// This callback is called when the get operation completes. + /// + /// If there wasn't enough room to store the entire buffer `SIZE` will be + /// returned in `result` and the bytes that did fit will be copied into the + /// buffer. + /// + /// ### Return Values + /// + /// - `result`: `Ok(())` on success + /// - `Err(ErrorCode)` on error. Valid `ErrorCode`s: + /// - `SIZE`: The value is longer than the provided buffer. The amount of + /// the value that fits in the buffer is provided. + /// - `NOSUPPORT`: The key could not be found or the caller does not have + /// permission to read this key. The data in the `value` buffer is + /// meaningless. + /// - `FAIL`: An internal error occurred and the operation cannot be + /// completed. + /// - `key`: The key buffer. + /// - `value`: The value buffer. + fn get_complete( + &self, + result: Result<(), ErrorCode>, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ); + + /// This callback is called when the set operation completes. + /// + /// ### Return Values + /// + /// - `result`: `Ok(())` on success, `Err(ErrorCode)` on error. Valid + /// `ErrorCode`s: + /// - `NOSUPPORT`: The caller does not have permission to store this key. + /// - `NOMEM`: The key could not be set because the KV store is full. + /// - `SIZE`: The key could not be set because the key or value is too + /// many bytes. + /// - `FAIL`: An internal error occurred and the operation cannot be + /// completed. + /// - `key`: The key buffer. + /// - `value`: The value buffer. + fn set_complete( + &self, + result: Result<(), ErrorCode>, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ); + + /// This callback is called when the add operation completes. + /// + /// ### Return Values + /// + /// - `result`: `Ok(())` on success, `Err(ErrorCode)` on error. Valid + /// `ErrorCode`s: + /// - `NOSUPPORT`: The key already exists and cannot be added. + /// - `NOMEM`: The key could not be added because the KV store is full. + /// - `SIZE`: The key could not be set because the key or value is too + /// many bytes. + /// - `FAIL`: An internal error occurred and the operation cannot be + /// completed. + /// - `key`: The key buffer. + /// - `value`: The value buffer. + fn add_complete( + &self, + result: Result<(), ErrorCode>, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ); + + /// This callback is called when the update operation completes. + /// + /// ### Return Values + /// + /// - `result`: `Ok(())` on success, `Err(ErrorCode)` on error. Valid + /// `ErrorCode`s: + /// - `NOSUPPORT`: The key does not already exist and cannot be modified + /// or the caller does not have permission to modify this key. + /// - `NOMEM`: The key could not be updated because the KV store is full. + /// - `SIZE`: The key could not be set because the key or value is too + /// many bytes. + /// - `FAIL`: An internal error occurred and the operation cannot be + /// completed. + /// - `key`: The key buffer. + /// - `value`: The value buffer. + fn update_complete( + &self, + result: Result<(), ErrorCode>, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ); + + /// This callback is called when the delete operation completes. + /// + /// ### Return Values + /// + /// - `result`: `Ok(())` on success, `Err(ErrorCode)` on error. Valid + /// `ErrorCode`s: + /// - `NOSUPPORT`: The key does not exist or the caller does not have + /// permission to delete this key. + /// - `FAIL`: An internal error occurred and the operation cannot be + /// completed. + /// - `key`: The key buffer. + fn delete_complete(&self, result: Result<(), ErrorCode>, key: SubSliceMut<'static, u8>); +} + +/// Key-Value interface with permissions. +/// +/// This interface provides access to key-value storage with access control. +/// Each object is marked with a `write_id` (based on the `StoragePermissions` +/// used to create it), and all further accesses and modifications to that +/// object require suitable permissions. +pub trait KVPermissions<'a> { + /// Configure the client for operation callbacks. + fn set_client(&self, client: &'a dyn KVClient); + + /// Retrieve a value based on the given key. + /// + /// ### Arguments + /// + /// - `key`: The key to identify the k-v pair. + /// - `value`: Where the returned value buffer will be stored. + /// - `permissions`: The read/write/modify permissions for this access. + /// + /// ### Return + /// + /// - On success returns `Ok(())`. A callback will be issued. + /// - On error, returns the buffers and: + /// - `BUSY`: An operation is already in progress. + /// - `FAIL`: An internal error occurred and the operation cannot be + /// completed. + fn get( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + permissions: StoragePermissions, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + >; + + /// Store a value based on the given key. If the key does not exist it will + /// be added. If the key already exists the value will be updated. + /// + /// The `value` buffer must have room for a header. + /// + /// ### Arguments + /// + /// - `key`: The key to identify the k-v pair. + /// - `value`: The value to store. The provided buffer MUST start + /// `KVPermissions.header_size()` bytes after the beginning of the buffer + /// to enable the implementation to insert a header. + /// - `permissions`: The read/write/modify permissions for this access. + /// + /// ### Return + /// + /// - On success returns `Ok(())`. A callback will be issued. + /// - On error, returns the buffers and: + /// - `BUSY`: An operation is already in progress. + /// - `SIZE`: There is insufficient room to include the permission header + /// in the `value` buffer or the key/value is too large to store. + /// - `INVAL`: The caller does not have write permissions. + /// - `FAIL`: An internal error occurred and the operation cannot be + /// completed. + fn set( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + permissions: StoragePermissions, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + >; + + /// Store a new value based on the given key. If the key does not exist it + /// will be added. If the key already exists an error callback will be + /// provided. + /// + /// The `value` buffer must have room for a header. + /// + /// ### Arguments + /// + /// - `key`: The key to identify the k-v pair. + /// - `value`: The value to store. The provided buffer MUST start + /// `KVPermissions.header_size()` bytes after the beginning of the buffer + /// to enable the implementation to insert a header. + /// - `permissions`: The read/write/modify permissions for this access. + /// + /// ### Return + /// + /// - On success returns `Ok(())`. A callback will be issued. + /// - On error, returns the buffers and: + /// - `BUSY`: An operation is already in progress. + /// - `SIZE`: There is insufficient room to include the permission header + /// in the `value` buffer or the key/value is too large to store. + /// - `INVAL`: The caller does not have write permissions. + /// - `FAIL`: An internal error occurred and the operation cannot be + /// completed. + fn add( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + permissions: StoragePermissions, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + >; + + /// Modify a value based on the given key. If the key does not exist it an + /// error callback will be provided. + /// + /// The `value` buffer must have room for a header. + /// + /// ### Arguments + /// + /// - `key`: The key to identify the k-v pair. + /// - `value`: The value to store. The provided buffer MUST start + /// `KVPermissions.header_size()` bytes after the beginning of the buffer + /// to enable the implementation to insert a header. + /// - `permissions`: The read/write/modify permissions for this access. + /// + /// ### Return + /// + /// - On success returns `Ok(())`. A callback will be issued. + /// - On error, returns the buffers and: + /// - `BUSY`: An operation is already in progress. + /// - `SIZE`: There is insufficient room to include the permission header + /// in the `value` buffer or the key/value is too large to store. + /// - `INVAL`: The caller does not have write permissions. + /// - `FAIL`: An internal error occurred and the operation cannot be + /// completed. + fn update( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + permissions: StoragePermissions, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + >; + + /// Delete a key-value object based on the given key. + /// + /// ### Arguments + /// + /// - `key`: The key to identify the k-v pair. + /// - `permissions`: The read/write/modify permissions for this access. + /// + /// ### Return + /// + /// - On success returns `Ok(())`. A callback will be issued. + /// - On error, returns the buffers and: + /// - `BUSY`: An operation is already in progress. + /// - `INVAL`: The caller does not have modify permissions. + /// - `FAIL`: An internal error occurred and the operation cannot be + /// completed. + fn delete( + &self, + key: SubSliceMut<'static, u8>, + permissions: StoragePermissions, + ) -> Result<(), (SubSliceMut<'static, u8>, ErrorCode)>; + + /// Returns the length of the key-value store's header in bytes. + /// + /// Room for this header must be accommodated in a `set`, `add`, or `update` + /// operation. + fn header_size(&self) -> usize; +} + +/// Key-Value interface. +/// +/// This interface provides access to key-value storage. +/// +/// `KV` includes five typical commands: +/// - `get(key) -> value` +/// - `set(key, value)` +/// - `add(key, value)` +/// - `update(key, value)` +/// - `delete(key)` +pub trait KV<'a> { + /// Configure the client for operation callbacks. + fn set_client(&self, client: &'a dyn KVClient); + + /// Retrieve a value based on the given key. + /// + /// ### Arguments + /// + /// - `key`: The key to identify the k-v pair. + /// - `value`: Where the returned value buffer will be stored. + /// + /// ### Return + /// + /// - On success returns `Ok(())`. A callback will be issued. + /// - On error, returns the buffers and: + /// - `BUSY`: An operation is already in progress. + /// - `FAIL`: An internal error occurred and the operation cannot be + /// completed. + fn get( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + >; + + /// Store a value based on the given key. If the key does not exist it will + /// be added. If the key already exists the value will be updated. + /// + /// The `value` buffer must have room for a header. + /// + /// ### Arguments + /// + /// - `key`: The key to identify the k-v pair. + /// - `value`: The value to store. + /// + /// ### Return + /// + /// - On success returns `Ok(())`. A callback will be issued. + /// - On error, returns the buffers and: + /// - `BUSY`: An operation is already in progress. + /// - `SIZE`: The key/value is too large to store. + /// - `FAIL`: An internal error occurred and the operation cannot be + /// completed. + fn set( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + >; + + /// Store a new value based on the given key. If the key does not exist it + /// will be added. If the key already exists an error callback will be + /// provided. + /// + /// The `value` buffer must have room for a header. + /// + /// ### Arguments + /// + /// - `key`: The key to identify the k-v pair. + /// - `value`: The value to store. + /// + /// ### Return + /// + /// - On success returns `Ok(())`. A callback will be issued. + /// - On error, returns the buffers and: + /// - `BUSY`: An operation is already in progress. + /// - `SIZE`: The key/value is too large to store. + /// - `FAIL`: An internal error occurred and the operation cannot be + /// completed. + fn add( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + >; + + /// Modify a value based on the given key. If the key does not exist it an + /// error callback will be provided. + /// + /// The `value` buffer must have room for a header. + /// + /// ### Arguments + /// + /// - `key`: The key to identify the k-v pair. + /// - `value`: The value to store. + /// + /// ### Return + /// + /// - On success returns `Ok(())`. A callback will be issued. + /// - On error, returns the buffers and: + /// - `BUSY`: An operation is already in progress. + /// - `SIZE`: The key/value is too large to store. + /// - `FAIL`: An internal error occurred and the operation cannot be + /// completed. + fn update( + &self, + key: SubSliceMut<'static, u8>, + value: SubSliceMut<'static, u8>, + ) -> Result< + (), + ( + SubSliceMut<'static, u8>, + SubSliceMut<'static, u8>, + ErrorCode, + ), + >; + + /// Delete a key-value object based on the given key. + /// + /// ### Arguments + /// + /// - `key`: The key to identify the k-v pair. + /// + /// ### Return + /// + /// - On success returns `Ok(())`. A callback will be issued. + /// - On error, returns the buffers and: + /// - `BUSY`: An operation is already in progress. + /// - `FAIL`: An internal error occurred and the operation cannot be + /// completed. + fn delete( + &self, + key: SubSliceMut<'static, u8>, + ) -> Result<(), (SubSliceMut<'static, u8>, ErrorCode)>; +} diff --git a/kernel/src/hil/kv_system.rs b/kernel/src/hil/kv_system.rs deleted file mode 100644 index ca2c3ad419..0000000000 --- a/kernel/src/hil/kv_system.rs +++ /dev/null @@ -1,222 +0,0 @@ -//! Low level interface for Key-Value (KV) Stores -//! -//! The KV store implementation in Tock has three levels, described below. -//! -//! 1 - Hardware Level: -//! This level is the interface that writes a buffer to the hardware. This will -//! generally be writing to flash, although in theory it would be possible to -//! write to other mediums. -//! -//! An example of the HIL used here is the Tock Flash HIL. -//! -//! 2 - KV System Level: -//! This level can be thought of like a file system. It is responsible for -//! taking save/load operations and generating a buffer to pass to level 1 -//! This level is also in charge of generating hashes and checksums. -//! -//! This level allows generating a key hash but otherwise operates on -//! hashed keys. This level is not responsible for permission checks. -//! -//! This file describes the HIL for this level. -//! -//! 3 - KV Store: -//! This is a user friendly high level HIL. This HIL is used inside the kernel -//! and exposed to applications to allow KV operations. The API from this level -//! should be high level, for example set/get/delete on unhashed keys. -//! This level is in charge of enforcing permissions. It is expected that -//! this level will combine the user data with a header and pass that to the -//! level 2 system HIL. -//! -//! This level is also in charge of generating the key hash by calling into -//! level 2. -//! -//! There is currently no implementation of this HIL in Tock. -//! -//! The expected setup inside Tock will look like this: -//! +-----------------------+ -//! | | -//! | Capsule using K-V | -//! | | -//! +-----------------------+ -//! -//! hil::kv_store (not written yet) -//! -//! +-----------------------+ -//! | | -//! | K-V in Tock | -//! | | -//! +-----------------------+ -//! -//! hil::kv_system (this PR) -//! -//! +-----------------------+ -//! | | -//! | K-V library | -//! | | -//! +-----------------------+ -//! -//! hil::flash - -use crate::ErrorCode; - -/// The type of keys, this should define the output size of the digest -/// operations. -pub trait KeyType: Eq + Copy + Clone + Sized + AsRef<[u8]> + AsMut<[u8]> {} - -impl KeyType for [u8; 8] {} - -/// Implement this trait and use `set_client()` in order to receive callbacks. -pub trait Client { - /// This callback is called when the append_key operation completes - /// - /// `result`: Nothing on success, 'ErrorCode' on error - /// `unhashed_key`: The unhashed_key buffer - /// `key_buf`: The key_buf buffer - fn generate_key_complete( - &self, - result: Result<(), ErrorCode>, - unhashed_key: &'static [u8], - key_buf: &'static K, - ); - - /// This callback is called when the append_key operation completes - /// - /// `result`: Nothing on success, 'ErrorCode' on error - /// `key`: The key buffer - /// `value`: The value buffer - fn append_key_complete( - &self, - result: Result<(), ErrorCode>, - key: &'static mut K, - value: &'static [u8], - ); - - /// This callback is called when the get_value operation completes - /// - /// `result`: Nothing on success, 'ErrorCode' on error - /// `key`: The key buffer - /// `ret_buf`: The ret_buf buffer - fn get_value_complete( - &self, - result: Result<(), ErrorCode>, - key: &'static mut K, - ret_buf: &'static mut [u8], - ); - - /// This callback is called when the invalidate_key operation completes - /// - /// `result`: Nothing on success, 'ErrorCode' on error - /// `key`: The key buffer - fn invalidate_key_complete(&self, result: Result<(), ErrorCode>, key: &'static mut K); - - /// This callback is called when the garbage_collect operation completes - /// - /// `result`: Nothing on success, 'ErrorCode' on error - fn garbage_collect_complete(&self, result: Result<(), ErrorCode>); -} - -pub trait KVSystem<'a> { - /// The type of the hashed key. For example '[u8; 8]'. - type K: KeyType; - - /// Set the client - fn set_client(&self, client: &'a dyn Client); - - /// Generate key - /// - /// `unhashed_key`: A unhashed key that should be hashed. - /// `key_buf`: A buffer to store the hashed key output. - /// - /// On success returns nothing. - /// On error the unhashed_key, key_buf and `Result<(), ErrorCode>` will be returned. - fn generate_key( - &self, - unhashed_key: &'static mut [u8], - key_buf: &'static mut Self::K, - ) -> Result< - (), - ( - &'static mut [u8], - &'static mut Self::K, - Result<(), ErrorCode>, - ), - >; - - /// Appends the key/value pair. - /// - /// `key`: A hashed key. This key will be used in future to retrieve - /// or remove the `value`. - /// `value`: A buffer containing the data to be stored to flash. - /// - /// On success nothing will be returned. - /// On error the key, value and a `Result<(), ErrorCode>` will be returned. - /// - /// The possible `Result<(), ErrorCode>`s are: - /// `BUSY`: An operation is already in progress - /// `INVAL`: An invalid parameter was passed - /// `NODEVICE`: No KV store was setup - /// `ENOSUPPORT`: The key could not be added due to a collision. - /// `NOMEM`: The key could not be added due to no more space. - fn append_key( - &self, - key: &'static mut Self::K, - value: &'static [u8], - ) -> Result<(), (&'static mut Self::K, &'static [u8], Result<(), ErrorCode>)>; - - /// Retrieves the value from a specified key. - /// - /// `key`: A hashed key. This key will be used to retrieve the `value`. - /// `ret_buf`: A buffer to store the value to. - /// - /// On success nothing will be returned. - /// On error the key, ret_buf and a `Result<(), ErrorCode>` will be returned. - /// - /// The possible `Result<(), ErrorCode>`s are: - /// `BUSY`: An operation is already in progress - /// `INVAL`: An invalid parameter was passed - /// `NODEVICE`: No KV store was setup - /// `ENOSUPPORT`: The key could not be found. - fn get_value( - &self, - key: &'static mut Self::K, - ret_buf: &'static mut [u8], - ) -> Result< - (), - ( - &'static mut Self::K, - &'static mut [u8], - Result<(), ErrorCode>, - ), - >; - - /// Invalidates the key in flash storage - /// - /// `key`: A hashed key. This key will be used to remove the `value`. - /// - /// On success nothing will be returned. - /// On error the key and a `Result<(), ErrorCode>` will be returned. - /// - /// The possible `Result<(), ErrorCode>`s are: - /// `BUSY`: An operation is already in progress - /// `INVAL`: An invalid parameter was passed - /// `NODEVICE`: No KV store was setup - /// `ENOSUPPORT`: The key could not be found. - fn invalidate_key( - &self, - key: &'static mut Self::K, - ) -> Result<(), (&'static mut Self::K, Result<(), ErrorCode>)>; - - /// Perform a garbage collection on the KV Store - /// - /// For implementations that don't require garbage collecting - /// this can just be a NOP that returns 'Ok(0)'. - /// - /// On success the number of bytes freed will be returned. - /// On error a `Result<(), ErrorCode>` will be returned. - /// - /// The possible `Result<(), ErrorCode>`s are: - /// `BUSY`: An operation is already in progress - /// `INVAL`: An invalid parameter was passed - /// `NODEVICE`: No KV store was setup - fn garbage_collect(&self) -> Result>; -} diff --git a/kernel/src/hil/led.rs b/kernel/src/hil/led.rs index 174a6ab4b5..3589d0d315 100644 --- a/kernel/src/hil/led.rs +++ b/kernel/src/hil/led.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for LEDs that abstract away polarity and pin. //! //! Author: Philip Levis diff --git a/kernel/src/hil/log.rs b/kernel/src/hil/log.rs index 28ccbe8f6b..2932b6d3fa 100644 --- a/kernel/src/hil/log.rs +++ b/kernel/src/hil/log.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for a persistent log that stores distinct log entries. //! //! Log entries are appended to the end of a log and read back sequentially. Log data persists diff --git a/kernel/src/hil/mod.rs b/kernel/src/hil/mod.rs index f2d3629a91..f59d9d29d8 100644 --- a/kernel/src/hil/mod.rs +++ b/kernel/src/hil/mod.rs @@ -1,22 +1,32 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Public traits for interfaces between Tock components. pub mod adc; pub mod analog_comparator; pub mod ble_advertising; pub mod bus8080; +pub mod buzzer; +pub mod can; pub mod crc; pub mod dac; +pub mod date_time; pub mod digest; pub mod eic; pub mod entropy; pub mod flash; pub mod gpio; pub mod gpio_async; +pub mod hasher; +pub mod hw_debug; pub mod i2c; -pub mod kv_system; +pub mod kv; pub mod led; pub mod log; pub mod nonvolatile_storage; +pub mod public_key_crypto; pub mod pwm; pub mod radio; pub mod rng; @@ -30,6 +40,7 @@ pub mod touch; pub mod uart; pub mod usb; pub mod usb_hid; +pub mod wifinina; /// Shared interface for configuring components. pub trait Controller { diff --git a/kernel/src/hil/ninedof.rs b/kernel/src/hil/ninedof.rs index 4d332e5033..7ed1fe5868 100644 --- a/kernel/src/hil/ninedof.rs +++ b/kernel/src/hil/ninedof.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for chips that provide 9DOF functionality. //! //! This trait file provides a standard interface for chips that implement diff --git a/kernel/src/hil/nonvolatile_storage.rs b/kernel/src/hil/nonvolatile_storage.rs index 375b9f2ae3..5602922e43 100644 --- a/kernel/src/hil/nonvolatile_storage.rs +++ b/kernel/src/hil/nonvolatile_storage.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Generic interface for nonvolatile memory. use crate::errorcode::ErrorCode; @@ -5,28 +9,38 @@ use crate::errorcode::ErrorCode; /// Simple interface for reading and writing nonvolatile memory. It is expected /// that drivers for nonvolatile memory would implement this trait. pub trait NonvolatileStorage<'a> { - fn set_client(&self, client: &'a dyn NonvolatileStorageClient<'a>); + fn set_client(&self, client: &'a dyn NonvolatileStorageClient); /// Read `length` bytes starting at address `address` in to the provided /// buffer. The buffer must be at least `length` bytes long. The address /// must be in the address space of the physical storage. - fn read(&self, buffer: &'a mut [u8], address: usize, length: usize) -> Result<(), ErrorCode>; + fn read( + &self, + buffer: &'static mut [u8], + address: usize, + length: usize, + ) -> Result<(), ErrorCode>; /// Write `length` bytes starting at address `address` from the provided /// buffer. The buffer must be at least `length` bytes long. This address /// must be in the address space of the physical storage. - fn write(&self, buffer: &'a mut [u8], address: usize, length: usize) -> Result<(), ErrorCode>; + fn write( + &self, + buffer: &'static mut [u8], + address: usize, + length: usize, + ) -> Result<(), ErrorCode>; } /// Client interface for nonvolatile storage. -pub trait NonvolatileStorageClient<'a> { +pub trait NonvolatileStorageClient { /// `read_done` is called when the implementor is finished reading in to the /// buffer. The callback returns the buffer and the number of bytes that /// were actually read. - fn read_done(&self, buffer: &'a mut [u8], length: usize); + fn read_done(&self, buffer: &'static mut [u8], length: usize); /// `write_done` is called when the implementor is finished writing from the /// buffer. The callback returns the buffer and the number of bytes that /// were actually written. - fn write_done(&self, buffer: &'a mut [u8], length: usize); + fn write_done(&self, buffer: &'static mut [u8], length: usize); } diff --git a/kernel/src/hil/public_key_crypto/keys.rs b/kernel/src/hil/public_key_crypto/keys.rs new file mode 100644 index 0000000000..6e324e8974 --- /dev/null +++ b/kernel/src/hil/public_key_crypto/keys.rs @@ -0,0 +1,285 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Key interface for Public/Private key encryption + +use crate::hil::entropy; +use crate::ErrorCode; + +/// Upcall from the `PubPrivKeyGenerate` trait. +pub trait PubPrivKeyGenerateClient<'a> { + /// The `generate()` command has been completed. + fn generation_complete( + &'a self, + result: Result<(), (ErrorCode, &'static mut [u8], &'static mut [u8])>, + ); +} + +/// An internal representation of a asymetric Public key. +/// +/// This trait is useful for managing keys internally in Tock. +/// +/// PubKey is designed for fixed length keys. That is an implementation should +/// support only a single key length, for example RSA 2048. +/// Note that we don't use const generics here though. That is because even +/// within a single key length implementation, there can be different length +/// inputs, for examples compressed or uncompressed keys. +pub trait PubKey { + /// Import an existing public key. + /// + /// The reference to the `public_key` is stored internally and can be + /// retrieved with the `pub_key()` function. + /// The `public_key` can be either a mutable static or an immutable static, + /// depending on where the key is stored (flash or memory). + /// + /// The possible ErrorCodes are: + /// - `BUSY`: A key is already imported or in the process of being + /// generated. + /// - `INVAL`: An invalid key was supplied. + /// - `SIZE`: An invalid key size was supplied. + fn import_public_key( + &self, + public_key: &'static [u8], + ) -> Result<(), (ErrorCode, &'static [u8])>; + + /// Return the public key supplied by `import_public_key()` or + /// `generate()`. + /// + /// On success the return value is `Ok(())` with the buffer that was + /// originally passed in to hold the key. + /// + /// On failure the possible ErrorCodes are: + /// - `NODEVICE`: The key does not exist + fn pub_key(&self) -> Result<&'static [u8], ErrorCode>; + + /// Report the length of the public key in bytes, as returned from `pub_key()`. + /// A value of 0 indicates that the key does not exist. + fn len(&self) -> usize; +} + +/// An internal representation of a asymetric Public key. +/// +/// This trait is useful for managing keys internally in Tock. +/// +/// PubKey is designed for fixed length keys. That is an implementation should +/// support only a single key length, for example RSA 2048. +/// Note that we don't use const generics here though. That is because even +/// within a single key length implementation, there can be different length +/// inputs, for examples compressed or uncompressed keys. +pub trait PubKeyMut { + /// Import an existing public key. + /// + /// The reference to the `public_key` is stored internally and can be + /// retrieved with the `pub_key()` function. + /// The `public_key` can be either a mutable static or an immutable static, + /// depending on where the key is stored (flash or memory). + /// + /// The possible ErrorCodes are: + /// - `BUSY`: A key is already imported or in the process of being + /// generated. + /// - `INVAL`: An invalid key was supplied. + /// - `SIZE`: An invalid key size was supplied. + fn import_public_key( + &self, + public_key: &'static mut [u8], + ) -> Result<(), (ErrorCode, &'static mut [u8])>; + + /// Return the public key supplied by `import_public_key()` or + /// `generate()`. + /// + /// On success the return value is `Ok(())` with the buffer that was + /// originally passed in to hold the key. + /// + /// On failure the possible ErrorCodes are: + /// - `NODEVICE`: The key does not exist + fn pub_key(&self) -> Result<&'static mut [u8], ErrorCode>; + + /// Report the length of the public key in bytes, as returned from `pub_key()`. + /// A value of 0 indicates that the key does not exist. + fn len(&self) -> usize; +} + +/// An internal representation of a asymetric Public and Private key. +/// +/// This trait is useful for managing keys internally in Tock. +/// +/// PubPrivKey is designed for fixed length keys. That is an implementation +/// should support only a single key length, for example RSA 2048. +/// Note that we don't use const generics here though. That is because even +/// within a single key length implementation, there can be different length +/// inputs, for examples compressed or uncompressed keys. +pub trait PubPrivKey: PubKey { + /// Import an existing private key. + /// + /// The reference to the `private_key` is stored internally and can be + /// retrieved with the `priv_key()` function. + /// The `private_key` can be either a mutable static or an immutable static, + /// depending on where the key is stored (flash or memory). + /// + /// The possible ErrorCodes are: + /// - `BUSY`: A key is already imported or in the process of being + /// generated. + /// - `INVAL`: An invalid key was supplied. + /// - `SIZE`: An invalid key size was supplied. + fn import_private_key( + &self, + private_key: &'static [u8], + ) -> Result<(), (ErrorCode, &'static [u8])>; + + /// Return the private key supplied by `import_private_key()` or + /// `generate()`. + /// + /// On success the return value is `Ok(())` with the buffer that was + /// originally passed in to hold the key. + /// + /// On failure the possible ErrorCodes are: + /// - `NODEVICE`: The key does not exist + fn priv_key(&self) -> Result<&'static [u8], ErrorCode>; + + /// Report the length of the private key in bytes, as returned from `priv_key()`. + /// A value of 0 indicates that the key does not exist. + fn len(&self) -> usize; +} + +/// An internal representation of a asymetric Public and Private key. +/// +/// This trait is useful for managing keys internally in Tock. +/// +/// PubPrivKey is designed for fixed length keys. That is an implementation +/// should support only a single key length, for example RSA 2048. +/// Note that we don't use const generics here though. That is because even +/// within a single key length implementation, there can be different length +/// inputs, for examples compressed or uncompressed keys. +pub trait PubPrivKeyMut: PubKeyMut { + /// Import an existing private key. + /// + /// The reference to the `private_key` is stored internally and can be + /// retrieved with the `priv_key()` function. + /// The `private_key` can be either a mutable static or an immutable static, + /// depending on where the key is stored (flash or memory). + /// + /// The possible ErrorCodes are: + /// - `BUSY`: A key is already imported or in the process of being + /// generated. + /// - `INVAL`: An invalid key was supplied. + /// - `SIZE`: An invalid key size was supplied. + fn import_private_key( + &self, + private_key: &'static mut [u8], + ) -> Result<(), (ErrorCode, &'static mut [u8])>; + + /// Return the private key supplied by `import_private_key()` or + /// `generate()`. + /// + /// On success the return value is `Ok(())` with the buffer that was + /// originally passed in to hold the key. + /// + /// On failure the possible ErrorCodes are: + /// - `NODEVICE`: The key does not exist + fn priv_key(&self) -> Result<&'static mut [u8], ErrorCode>; + + /// Report the length of the private key in bytes, as returned from `priv_key()`. + /// A value of 0 indicates that the key does not exist. + fn len(&self) -> usize; +} + +/// An internal representation of generating asymetric Public/Private key +/// pairs. +/// +/// This trait is useful for managing keys internally in Tock. +pub trait PubPrivKeyGenerate<'a>: PubPrivKey { + /// Set the client. This client will be called when the `generate()` + /// function is complete. If using an existing key this doesn't need to be + /// used. + fn set_client(&'a self, client: &'a dyn PubPrivKeyGenerateClient<'a>); + + /// This generates a new private/public key pair. The length will be + /// hard coded by the implementation, for example RSA 2048 will create a + /// 2048 bit key. + /// This will call the `generation_complete()` on completion. They keys + /// cannot be used and will return `None` until the upcall has been called. + /// + /// The keys generated by `generate()` will depend on the implementation. + /// + /// The original key buffers can be retrieve usind the `pub_key()` and + /// `priv_key()` functions. + /// + /// The possible ErrorCodes are: + /// - `BUSY`: A key is already imported or in the process of being + /// generated. + /// - `OFF`: The underlying `trng` is powered down. + /// - `SIZE`: An invalid buffer size was supplied. + fn generate( + &'a self, + trng: &'a dyn entropy::Entropy32, + public_key_buffer: &'static mut [u8], + private_key_buffer: &'static mut [u8], + ) -> Result<(), (ErrorCode, &'static mut [u8], &'static mut [u8])>; +} + +pub trait RsaKey: PubKey { + /// Run the specified closure over the modulus, if it exists + /// The modulus is returned MSB (big endian) + /// Returns `Some()` if the key exists and the closure was called, + /// otherwise returns `None`. + fn map_modulus(&self, closure: &dyn Fn(&[u8])) -> Option<()>; + + /// The the modulus if it exists. + /// The modulus is returned MSB (big endian) + /// Returns `Some()` if the key exists otherwise returns `None`. + /// The modulus can be returned by calling `import_public_key()` with + /// the output of this function. + fn take_modulus(&self) -> Option<&'static [u8]>; + + /// Returns the public exponent of the key pair if it exists + fn public_exponent(&self) -> Option; +} + +pub trait RsaPrivKey: PubPrivKey + RsaKey { + /// Returns the specified closure over the private exponent, if it exists + /// The exponent is returned MSB (big endian) + /// Returns `Some()` if the key exists and the closure was called, + /// otherwise returns `None`. + fn map_exponent(&self, closure: &dyn Fn(&[u8])) -> Option<()>; + + /// The the private exponent if it exists. + /// The exponent is returned MSB (big endian) + /// Returns `Some()` if the key exists otherwise returns `None`. + /// The exponent can be returned by calling `import_private_key()` with + /// the output of this function. + fn take_exponent(&self) -> Option<&'static [u8]>; +} + +pub trait RsaKeyMut: PubKeyMut { + /// Run the specified closure over the modulus, if it exists + /// The modulus is returned MSB (big endian) + /// Returns `Some()` if the key exists and the closure was called, + /// otherwise returns `None`. + fn map_modulus(&self, closure: &dyn Fn(&mut [u8])) -> Option<()>; + + /// The the modulus if it exists. + /// The modulus is returned MSB (big endian) + /// Returns `Some()` if the key exists otherwise returns `None`. + /// The modulus can be returned by calling `import_public_key()` with + /// the output of this function. + fn take_modulus(&self) -> Option<&'static mut [u8]>; + + /// Returns the public exponent of the key pair if it exists + fn public_exponent(&self) -> Option; +} + +pub trait RsaPrivKeyMut: PubPrivKeyMut + RsaKeyMut { + /// Returns the specified closure over the private exponent, if it exists + /// The exponent is returned MSB (big endian) + /// Returns `Some()` if the key exists and the closure was called, + /// otherwise returns `None`. + fn map_exponent(&self, closure: &dyn Fn(&mut [u8])) -> Option<()>; + + /// The the private exponent if it exists. + /// The exponent is returned MSB (big endian) + /// Returns `Some()` if the key exists otherwise returns `None`. + /// The exponent can be returned by calling `import_private_key()` with + /// the output of this function. + fn take_exponent(&self) -> Option<&'static mut [u8]>; +} diff --git a/kernel/src/hil/public_key_crypto/mod.rs b/kernel/src/hil/public_key_crypto/mod.rs new file mode 100644 index 0000000000..f9c86d537a --- /dev/null +++ b/kernel/src/hil/public_key_crypto/mod.rs @@ -0,0 +1,9 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Provides public/private key encryption + +pub mod keys; +pub mod rsa_math; +pub mod signature; diff --git a/kernel/src/hil/public_key_crypto/rsa_math.rs b/kernel/src/hil/public_key_crypto/rsa_math.rs new file mode 100644 index 0000000000..e3541a2d56 --- /dev/null +++ b/kernel/src/hil/public_key_crypto/rsa_math.rs @@ -0,0 +1,145 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Interface for RSA Public/Private key encryption math operations + +use crate::ErrorCode; + +/// Upcall from the `RsaCryptoBase` trait. +pub trait Client<'a> { + /// This callback is called when the mod_exponent operation is complete. + /// + /// The possible ErrorCodes are: + /// - BUSY: An operation is already on going + /// - INVAL: An invalid parameter was supplied + /// - SIZE: The size of the `result` buffer is invalid + /// - NOSUPPORT: The operation is not supported + fn mod_exponent_done( + &'a self, + status: Result, + message: &'static mut [u8], + modulus: &'static [u8], + exponent: &'static [u8], + result: &'static mut [u8], + ); +} + +pub trait RsaCryptoBase<'a> { + /// Set the `Client` client to be called on completion. + fn set_client(&'a self, client: &'a dyn Client<'a>); + + /// Clear any confidential data. + fn clear_data(&self); + + /// Calculate (`message` ^ `exponent`) % `modulus` and store it in the + /// `result` buffer. + /// + /// On completion the `mod_exponent_done()` upcall will be scheduled. + /// + /// The length of `modulus` must be a power of 2 and determines the length + /// of the operation. + /// + /// The `message` and `exponent` buffers can be any length. All of the data + /// in the buffer up to the length of the `modulus` will be used. This + /// allows callers to allocate larger buffers to support multiple + /// RSA lengths, but only the operation length (defined by the modulus) + /// will be used. + /// + /// The `result` buffer must be at least as large as the `modulus` buffer, + /// otherwise Err(SIZE) will be returned. + /// If `result` is longer then `modulus` the data will be stored in the + /// `result` buffer from 0 to `modulue.len()`. + /// + /// The possible ErrorCodes are: + /// - BUSY: An operation is already on going + /// - INVAL: An invalid parameter was supplied + /// - SIZE: The size of the `result` buffer is invalid + /// - NOSUPPORT: The operation is not supported + fn mod_exponent( + &self, + message: &'static mut [u8], + modulus: &'static [u8], + exponent: &'static [u8], + result: &'static mut [u8], + ) -> Result< + (), + ( + ErrorCode, + &'static mut [u8], + &'static [u8], + &'static [u8], + &'static mut [u8], + ), + >; +} + +/// Upcall from the `RsaCryptoBase` trait. +pub trait ClientMut<'a> { + /// This callback is called when the mod_exponent operation is complete. + /// + /// The possible ErrorCodes are: + /// - BUSY: The system is busy + /// - ALREADY: An operation is already on going + /// - INVAL: An invalid parameter was supplied + /// - SIZE: The size of the `result` buffer is invalid + /// - NOSUPPORT: The operation is not supported + fn mod_exponent_done( + &'a self, + status: Result, + message: &'static mut [u8], + modulus: &'static mut [u8], + exponent: &'static mut [u8], + result: &'static mut [u8], + ); +} + +pub trait RsaCryptoBaseMut<'a> { + /// Set the `ClientMut` client to be called on completion. + fn set_client(&'a self, client: &'a dyn ClientMut<'a>); + + /// Clear any confidential data. + fn clear_data(&self); + + /// Calculate (`message` ^ `exponent`) % `modulus` and store it in the + /// `result` buffer. + /// + /// On completion the `mod_exponent_done()` upcall will be scheduled. + /// + /// The length of `modulus` must be a power of 2 and determines the length + /// of the operation. + /// + /// The `message` and `exponent` buffers can be any length. All of the data + /// in the buffer up to the length of the `modulus` will be used. This + /// allows callers to allocate larger buffers to support multiple + /// RSA lengths, but only the operation length (defined by the modulus) + /// will be used. + /// + /// The `result` buffer must be at least as large as the `modulus` buffer, + /// otherwise Err(SIZE) will be returned. + /// If `result` is longer then `modulus` the data will be stored in the + /// `result` buffer from 0 to `modulue.len()`. + /// + /// The possible ErrorCodes are: + /// - BUSY: The system is busy + /// - ALREADY: An operation is already on going + /// - INVAL: An invalid parameter was supplied + /// - SIZE: The size of the `result` buffer is invalid + /// - NOSUPPORT: The operation is not supported + fn mod_exponent( + &self, + message: &'static mut [u8], + modulus: &'static mut [u8], + exponent: &'static mut [u8], + result: &'static mut [u8], + ) -> Result< + (), + ( + ErrorCode, + &'static mut [u8], + &'static mut [u8], + &'static mut [u8], + &'static mut [u8], + ), + >; +} diff --git a/kernel/src/hil/public_key_crypto/signature.rs b/kernel/src/hil/public_key_crypto/signature.rs new file mode 100644 index 0000000000..2a2f6c4b79 --- /dev/null +++ b/kernel/src/hil/public_key_crypto/signature.rs @@ -0,0 +1,59 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Interface for verifying signatures. + +use crate::ErrorCode; + +/// This trait provides callbacks for when the verification has completed. +pub trait ClientVerify { + /// Called when the verification is complete. + /// + /// If the verification operation encounters an error, result will be a + /// `Result::Err()` specifying the ErrorCode. Otherwise, result will be a + /// `Result::Ok` set to `Ok(true)` if the signature was correctly verified + /// and `Ok(false)` otherwise. + /// + /// If verification operation did encounter errors `result` will be `Err()` + /// with an appropriate `ErrorCode`. Valid `ErrorCode`s include: + /// + /// - `CANCEL`: the operation was cancelled. + /// - `FAIL`: an internal failure. + fn verification_done( + &self, + result: Result, + hash: &'static mut [u8; HL], + signature: &'static mut [u8; SL], + ); +} + +/// Verify a signature. +/// +/// This is a generic interface, and it is up to the implementation as to the +/// signature verification algorithm being used. +/// +/// - `HL`: The length in bytes of the hash. +/// - `SL`: The length in bytes of the signature. +pub trait SignatureVerify<'a, const HL: usize, const SL: usize> { + /// Set the client instance which will receive the `verification_done()` + /// callback. + fn set_verify_client(&self, client: &'a dyn ClientVerify); + + /// Verify the signature matches the given hash. + /// + /// If this returns `Ok(())`, then the `verification_done()` callback will + /// be called. If this returns `Err()`, no callback will be called. + /// + /// The valid `ErrorCode`s that can occur are: + /// + /// - `OFF`: the underlying digest engine is powered down and cannot be + /// used. + /// - `BUSY`: there is an outstanding operation already in process, and the + /// verification engine cannot accept another request. + fn verify( + &self, + hash: &'static mut [u8; HL], + signature: &'static mut [u8; SL], + ) -> Result<(), (ErrorCode, &'static mut [u8; HL], &'static mut [u8; SL])>; +} diff --git a/kernel/src/hil/pwm.rs b/kernel/src/hil/pwm.rs index ff6417697a..3bf943897c 100644 --- a/kernel/src/hil/pwm.rs +++ b/kernel/src/hil/pwm.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interfaces for Pulse Width Modulation output. use crate::ErrorCode; @@ -7,7 +11,7 @@ pub trait Pwm { /// The chip-dependent type of a PWM pin. type Pin; - /// Generate a PWM single on the given pin at the given frequency and duty + /// Generate a PWM signal on the given pin at the given frequency and duty /// cycle. /// /// - `frequency_hz` is specified in Hertz. diff --git a/kernel/src/hil/radio.rs b/kernel/src/hil/radio.rs index 8870a40ffe..bbc5aee46d 100644 --- a/kernel/src/hil/radio.rs +++ b/kernel/src/hil/radio.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for sending and receiving IEEE 802.15.4 packets. //! //! Hardware independent interface for an 802.15.4 radio. Note that @@ -9,108 +13,442 @@ use crate::ErrorCode; +/// Client trait for when sending a packet is finished. pub trait TxClient { + /// Send is complete or an error occurred during transmission. + /// + /// ## Arguments + /// + /// - `buf`: Buffer of the transmitted packet. + /// - `acked`: Set to true if the sender received an acknowledgement after + /// transmitting. Note, this is only set on a confirmed ACK; if the + /// transmission did not request an ACK this will be set to false. + /// - `result`: Status of the transmission. `Ok(())` when the packet was + /// sent successfully. On `Err()`, valid errors are: + /// - `ErrorCode::BUSY`: The channel was never clear and we could not + /// transmit. + /// - `ErrorCode::FAIL`: Internal TX error occurred. fn send_done(&self, buf: &'static mut [u8], acked: bool, result: Result<(), ErrorCode>); } +/// Client for receiving packets. pub trait RxClient { + /// Packet was received. + /// + /// ## Arguments + /// + /// - `buf`: Buffer containing the packet. This is the buffer provided via + /// `RadioData::set_receive_buffer()`. The structure of this buffer is the + /// same as in the TX case, as described in this HIL. That is, the first + /// byte is reserved, and the full 802.15.4 starts with the PHR in the + /// second byte. + /// - `frame_len`: Length of the received frame, excluding the MAC footer. + /// In other words, this length is PHR-MFR_SIZE. Note, this length does + /// _not_ correspond to the length of data from the start of the buffer. + /// This length is from the third byte in the buffer (i.e., + /// `buf[2:2+frame_length]`). + /// - `lqi`: The Link Quality Indicator as measured by the receiver during + /// the packet reception. This is on the scale as specified in the IEEE + /// 802.15.4 specification (section 6.9.8), with value 0 being the lowest + /// detectable signal and value 0xff as the highest quality detectable + /// signal. + /// - `crc_valid`: Whether the CRC check matched the received frame. Note, + /// the MFR bytes are not required to be stored in `buf` so using this + /// argument is the only reliable method for checking the CRC. + /// - `result`: Status of the reception. `Ok(())` when the packet was + /// received normally. On `Err()`, valid errors are: + /// - `ErrorCode::NOMEM`: Ack was requested, but there was no buffer + /// available to transmit an ACK. + /// - `ErrorCode::FAIL`: Internal error occurred. fn receive( &self, buf: &'static mut [u8], frame_len: usize, + lqi: u8, crc_valid: bool, result: Result<(), ErrorCode>, ); } +/// Client for callbacks after the radio is configured. pub trait ConfigClient { + /// Configuring the radio has finished. + /// + /// ## Arguments + /// + /// - `result`: Status of the configuration procedure. `Ok(())` if all + /// options were set as expected. On `Err()`, valid errors are: + /// - `ErrorCode::FAIL`: Internal error occurred. fn config_done(&self, result: Result<(), ErrorCode>); } +/// Client for callbacks when the radio's power state changes. pub trait PowerClient { + /// The power state of the radio changed. This is called when the radio has + /// turned on or off. + /// + /// ## Arguments + /// + /// - `on`: True if the radio is now on. False otherwise. fn changed(&self, on: bool); } -/// These constants are used for interacting with the SPI buffer, which contains -/// a 1-byte SPI command, a 1-byte PHY header, and then the 802.15.4 frame. In -/// theory, the number of extra bytes in front of the frame can depend on the -/// particular method used to communicate with the radio, but we leave this as a -/// constant in this generic trait for now. -/// -/// Furthermore, the minimum MHR size assumes that -/// -/// - The source PAN ID is omitted -/// - There is no auxiliary security header -/// - There are no IEs -/// -/// ```text -/// +---------+-----+-----+-------------+-----+ -/// | SPI com | PHR | MHR | MAC payload | MFR | -/// +---------+-----+-----+-------------+-----+ -/// \______ Static buffer rx/txed to SPI _____/ -/// \__ PSDU / frame length __/ -/// \___ 2 bytes ___/ -/// ``` - -pub const MIN_MHR_SIZE: usize = 9; +// These constants are used for interacting with the SPI buffer, which contains +// a 1-byte SPI command, a 1-byte PHY header, and then the 802.15.4 frame. In +// theory, the number of extra bytes in front of the frame can depend on the +// particular method used to communicate with the radio, but we leave this as a +// constant in this generic trait for now. +// +// Furthermore, the minimum MHR size assumes that +// +// - The source PAN ID is omitted +// - There is no auxiliary security header +// - There are no IEs +// +// ```text +// +---------+-----+--------+-------------+-----+-----+ +// | SPI com | PHR | MHR | MAC payload | MFR | LQI | +// +---------+-----+--------+-------------+-----+-----+ +// \______ Static buffer for implementation __________/ +// \_________ PSDU _____________/ +// \___ 2 bytes ___/ \1byte/ +// ``` + +/// Length of the physical layer header. This is the Frame length field. +pub const PHR_SIZE: usize = 1; +/// Length of the Frame Control field in the MAC header. +pub const MHR_FC_SIZE: usize = 2; +/// Length of the MAC footer. Contains the CRC. pub const MFR_SIZE: usize = 2; +/// Maximum length of a MAC frame. pub const MAX_MTU: usize = 127; -pub const MIN_FRAME_SIZE: usize = MIN_MHR_SIZE + MFR_SIZE; +/// Minimum length of the MAC frame (except for acknowledgements). This is +/// explained in Table 21 of the specification. +pub const MIN_FRAME_SIZE: usize = 9; +/// Maximum length of a MAC frame. pub const MAX_FRAME_SIZE: usize = MAX_MTU; +/// Location in the buffer of the physical layer header. This is the location of +/// the Frame length byte. +pub const PHR_OFFSET: usize = 1; +/// Location in the buffer of the PSDU. This is equivalent to the start of the +/// MAC payload. pub const PSDU_OFFSET: usize = 2; -pub const MAX_BUF_SIZE: usize = PSDU_OFFSET + MAX_MTU; -pub const MIN_PAYLOAD_OFFSET: usize = PSDU_OFFSET + MIN_MHR_SIZE; +/// Length of the reserved space in the buffer for a SPI command. +pub const SPI_HEADER_SIZE: usize = 1; +/// Length of the reserved space in the buffer for LQI information. +pub const LQI_SIZE: usize = 1; +/// Required buffer size for implementations of this HIL. +pub const MAX_BUF_SIZE: usize = SPI_HEADER_SIZE + PHR_SIZE + MAX_MTU + LQI_SIZE; -pub trait Radio: RadioConfig + RadioData {} +/// General Radio trait that supports configuration and TX/RX. +pub trait Radio<'a>: RadioConfig<'a> + RadioData<'a> {} // Provide blanket implementations for trait group -impl Radio for T {} +impl<'a, T: RadioConfig<'a> + RadioData<'a>> Radio<'a> for T {} /// Configure the 802.15.4 radio. -pub trait RadioConfig { - /// buf must be at least MAX_BUF_SIZE in length, and - /// reg_read and reg_write must be 2 bytes. - fn initialize( - &self, - spi_buf: &'static mut [u8], - reg_write: &'static mut [u8], - reg_read: &'static mut [u8], - ) -> Result<(), ErrorCode>; +pub trait RadioConfig<'a> { + /// Initialize the radio. + /// + /// This should perform any needed initialization, but should not turn the + /// radio on. + /// + /// ## Return + /// + /// `Ok(())` on success. On `Err()`, valid errors are: + /// + /// - `ErrorCode::FAIL`: Internal error occurred. + fn initialize(&self) -> Result<(), ErrorCode>; + + /// Reset the radio. + /// + /// Perform a radio reset which may reset the internal state of the radio. + /// + /// ## Return + /// + /// `Ok(())` on success. On `Err()`, valid errors are: + /// + /// - `ErrorCode::FAIL`: Internal error occurred. fn reset(&self) -> Result<(), ErrorCode>; + + /// Start the radio. + /// + /// This should put the radio into receive mode. + /// + /// ## Return + /// + /// `Ok(())` on success. On `Err()`, valid errors are: + /// + /// - `ErrorCode::FAIL`: Internal error occurred. fn start(&self) -> Result<(), ErrorCode>; + + /// Stop the radio. + /// + /// This should turn the radio off, disabling receive mode, and put the + /// radio into a low power state. + /// + /// ## Return + /// + /// `Ok(())` on success. On `Err()`, valid errors are: + /// + /// - `ErrorCode::FAIL`: Internal error occurred. fn stop(&self) -> Result<(), ErrorCode>; + + /// Check if the radio is currently on. + /// + /// ## Return + /// + /// True if the radio is on, false otherwise. fn is_on(&self) -> bool; + + /// Check if the radio is currently busy transmitting or receiving a packet. + /// + /// If this returns `true`, the radio is unable to start another operation. + /// + /// ## Return + /// + /// True if the radio is busy, false otherwise. fn busy(&self) -> bool; - fn set_power_client(&self, client: &'static dyn PowerClient); + /// Set the client that is called when the radio changes power states. + fn set_power_client(&self, client: &'a dyn PowerClient); - /// Commit the config calls to hardware, changing the address, - /// PAN ID, TX power, and channel to the specified values, issues - /// a callback to the config client when done. + /// Commit the configuration calls to the radio. + /// + /// This will set the address, PAN ID, TX power, and channel to the + /// specified values within the radio hardware. When this finishes, this + /// will issue a callback to the config client when done. fn config_commit(&self); - fn set_config_client(&self, client: &'static dyn ConfigClient); - fn get_address(&self) -> u16; //....... The local 16-bit address - fn get_address_long(&self) -> [u8; 8]; // 64-bit address - fn get_pan(&self) -> u16; //........... The 16-bit PAN ID - fn get_tx_power(&self) -> i8; //....... The transmit power, in dBm - fn get_channel(&self) -> u8; // ....... The 802.15.4 channel + /// Set the client that is called when configuration finishes. + fn set_config_client(&self, client: &'a dyn ConfigClient); + + /// Get the 802.15.4 short (16-bit) address for the radio. + /// + /// ## Return + /// + /// The radio's short address. + fn get_address(&self) -> u16; + + /// Get the 802.15.4 extended (64-bit) address for the radio. + /// + /// ## Return + /// + /// The radio's extended address. + fn get_address_long(&self) -> [u8; 8]; + + /// Get the 802.15.4 16-bit PAN ID for the radio. + /// + /// ## Return + /// + /// The radio's PAN ID. + fn get_pan(&self) -> u16; + + /// Get the radio's transmit power. + /// + /// ## Return + /// + /// The transmit power setting used by the radio, in dBm. + fn get_tx_power(&self) -> i8; + + /// Get the 802.15.4 channel the radio is currently using. + /// + /// ## Return + /// + /// The channel number. + fn get_channel(&self) -> u8; + /// Set the 802.15.4 short (16-bit) address for the radio. + /// + /// Note, calling this function configures the software driver, but does not + /// take effect in the radio hardware. Call `RadioConfig::config_commit()` + /// to set the configuration settings in the radio hardware. + /// + /// ## Argument + /// + /// - `addr`: The short address. fn set_address(&self, addr: u16); + + /// Set the 802.15.4 extended (64-bit) address for the radio. + /// + /// Note, calling this function configures the software driver, but does not + /// take effect in the radio hardware. Call `RadioConfig::config_commit()` + /// to set the configuration settings in the radio hardware. + /// + /// ## Argument + /// + /// - `addr`: The extended address. fn set_address_long(&self, addr: [u8; 8]); + + /// Set the 802.15.4 PAN ID (16-bit) for the radio. + /// + /// Note, calling this function configures the software driver, but does not + /// take effect in the radio hardware. Call `RadioConfig::config_commit()` + /// to set the configuration settings in the radio hardware. + /// + /// ## Argument + /// + /// - `id`: The PAN ID. fn set_pan(&self, id: u16); + + /// Set the radio's transmit power. + /// + /// Note, calling this function configures the software driver, but does not + /// take effect in the radio hardware. Call `RadioConfig::config_commit()` + /// to set the configuration settings in the radio hardware. + /// + /// ## Argument + /// + /// - `power`: The transmit power in dBm. + /// + /// ## Return + /// + /// `Ok(())` on success. On `Err()`, valid errors are: + /// + /// - `ErrorCode::INVAL`: The transmit power is above acceptable limits. + /// - `ErrorCode::NOSUPPORT`: The transmit power is not supported by the + /// radio. + /// - `ErrorCode::FAIL`: Internal error occurred. fn set_tx_power(&self, power: i8) -> Result<(), ErrorCode>; - fn set_channel(&self, chan: u8) -> Result<(), ErrorCode>; + + /// Set the 802.15.4 channel for the radio. + /// + /// Note, calling this function configures the software driver, but does not + /// take effect in the radio hardware. Call `RadioConfig::config_commit()` + /// to set the configuration settings in the radio hardware. + /// + /// ## Argument + /// + /// - `chan`: The 802.15.4 channel. + fn set_channel(&self, chan: RadioChannel); } -pub trait RadioData { - fn set_transmit_client(&self, client: &'static dyn TxClient); - fn set_receive_client(&self, client: &'static dyn RxClient, receive_buffer: &'static mut [u8]); +/// Send and receive packets with the 802.15.4 radio. +pub trait RadioData<'a> { + /// Set the client that will be called when packets are transmitted. + fn set_transmit_client(&self, client: &'a dyn TxClient); + + /// Set the client that will be called when packets are received. + fn set_receive_client(&self, client: &'a dyn RxClient); + + /// Set the buffer to receive packets into. + /// + /// ## Argument + /// + /// - `receive_buffer`: The buffer to receive into. Must be at least + /// `MAX_BUF_SIZE` bytes long. fn set_receive_buffer(&self, receive_buffer: &'static mut [u8]); + /// Transmit a packet. + /// + /// The radio will create and insert the PHR (Frame length) field. + /// + /// ## Argument + /// + /// - `buf`: Buffer with the MAC layer 802.15.4 frame to be transmitted. + /// The buffer must conform to the buffer formatted documented in the HIL. + /// That is, the MAC payload (PSDU) must start at the third byte. + /// The first byte must be reserved for the radio driver (i.e. + /// for a SPI transaction) and the second byte is reserved for the PHR. + /// The buffer must be at least `frame_len` + 2 + MFR_SIZE` bytes long. + /// - `frame_len`: The length of the MAC payload, not including the MFR. + /// + /// ## Return + /// + /// `Ok(())` on success. On `Err()`, valid errors are: + /// + /// - `ErrorCode::OFF`: The radio is off and cannot transmit. + /// - `ErrorCode::BUSY`: The radio is busy. This is likely to occur because + /// the radio is already transmitting a packet. + /// - `ErrorCode::SIZE`: The buffer does not have room for the MFR (CRC). + /// - `ErrorCode::FAIL`: Internal error occurred. fn transmit( &self, - spi_buf: &'static mut [u8], + buf: &'static mut [u8], frame_len: usize, ) -> Result<(), (ErrorCode, &'static mut [u8])>; } + +/// IEEE 802.15.4 valid channels. +#[derive(PartialEq, Debug, Copy, Clone)] +pub enum RadioChannel { + Channel11 = 5, + Channel12 = 10, + Channel13 = 15, + Channel14 = 20, + Channel15 = 25, + Channel16 = 30, + Channel17 = 35, + Channel18 = 40, + Channel19 = 45, + Channel20 = 50, + Channel21 = 55, + Channel22 = 60, + Channel23 = 65, + Channel24 = 70, + Channel25 = 75, + Channel26 = 80, +} + +impl RadioChannel { + /// Get the IEEE 802.15.4 channel number for the `RadioChannel`. + /// + /// ## Return + /// + /// A 1 byte number corresponding to the channel number. + pub fn get_channel_number(&self) -> u8 { + match *self { + RadioChannel::Channel11 => 11, + RadioChannel::Channel12 => 12, + RadioChannel::Channel13 => 13, + RadioChannel::Channel14 => 14, + RadioChannel::Channel15 => 15, + RadioChannel::Channel16 => 16, + RadioChannel::Channel17 => 17, + RadioChannel::Channel18 => 18, + RadioChannel::Channel19 => 19, + RadioChannel::Channel20 => 20, + RadioChannel::Channel21 => 21, + RadioChannel::Channel22 => 22, + RadioChannel::Channel23 => 23, + RadioChannel::Channel24 => 24, + RadioChannel::Channel25 => 25, + RadioChannel::Channel26 => 26, + } + } +} + +impl TryFrom for RadioChannel { + type Error = (); + /// Try to convert a 1 byte channel number to a `RadioChannel` + /// + /// ## Argument + /// + /// - `val`: The channel number to convert. + /// + /// ## Return + /// + /// Returns `Ok(RadioChannel)` if `val` is a valid IEEE 802.15.4 2.4 GHz + /// channel number. Otherwise, returns `Err(())`. + fn try_from(val: u8) -> Result { + match val { + 11 => Ok(RadioChannel::Channel11), + 12 => Ok(RadioChannel::Channel12), + 13 => Ok(RadioChannel::Channel13), + 14 => Ok(RadioChannel::Channel14), + 15 => Ok(RadioChannel::Channel15), + 16 => Ok(RadioChannel::Channel16), + 17 => Ok(RadioChannel::Channel17), + 18 => Ok(RadioChannel::Channel18), + 19 => Ok(RadioChannel::Channel19), + 20 => Ok(RadioChannel::Channel20), + 21 => Ok(RadioChannel::Channel21), + 22 => Ok(RadioChannel::Channel22), + 23 => Ok(RadioChannel::Channel23), + 24 => Ok(RadioChannel::Channel24), + 25 => Ok(RadioChannel::Channel25), + 26 => Ok(RadioChannel::Channel26), + _ => Err(()), + } + } +} diff --git a/kernel/src/hil/rng.rs b/kernel/src/hil/rng.rs index 145b25124f..744eda2c92 100644 --- a/kernel/src/hil/rng.rs +++ b/kernel/src/hil/rng.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interfaces for accessing a random number generator. //! //! A random number generator produces a stream of random numbers, diff --git a/kernel/src/hil/screen.rs b/kernel/src/hil/screen.rs index 3701daf766..9d35f62a47 100644 --- a/kernel/src/hil/screen.rs +++ b/kernel/src/hil/screen.rs @@ -1,4 +1,36 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for screens and displays. +//! +//! The interfaces exposed here cover both configurable (`ScreenSetup`), and +//! less configurable hardware (only `Screen`). +//! +//! It's composed of 4 main kinds of requests: +//! - set power, +//! - read configuration (e.g. `get_resolution`), +//! - configure (e.g. `set_invert`), +//! - write buffer. +//! +//! All requests, except for `Screen::set_power`, can return `OFF` under some +//! circumstances. +//! +//! For buffer writes, it's when the display is powered off. +//! +//! While the display is not powered on, the user could try to configure it. In +//! that case, the driver MUST either cache the value, or return `OFF`. This is +//! to let the user power the display in the desired configuration. +//! +//! Configuration reads shall return the actual state of the display. In +//! situations where a parameter cannot be configured (e.g. fixed resolution), +//! they return value may be hardcoded. Otherwise, the driver should query the +//! hardware directly, and return OFF if it's not powered. +//! +//! Configuration sets cause a `command_complete` callback unless noted +//! otherwise. + +use crate::utilities::leasable_buffer::SubSliceMut; use crate::ErrorCode; use core::ops::Add; use core::ops::Sub; @@ -59,19 +91,24 @@ impl Sub for ScreenRotation { } } +/// How pixels are encoded for the screen. #[derive(Copy, Clone, PartialEq)] #[repr(usize)] #[allow(non_camel_case_types)] pub enum ScreenPixelFormat { - /// Pixels encoded as 1-bit, used for monochromatic displays + /// Pixels encoded as 1-bit, used for monochromatic displays. Mono, - /// Pixels encoded as 2-bit red channel, 3-bit green channel, 3-bit blue channel. + /// Pixels encoded as 2-bit red channel, 3-bit green channel, 3-bit blue + /// channel. RGB_233, - /// Pixels encoded as 5-bit red channel, 6-bit green channel, 5-bit blue channel. + /// Pixels encoded as 5-bit red channel, 6-bit green channel, 5-bit blue + /// channel. RGB_565, - /// Pixels encoded as 8-bit red channel, 8-bit green channel, 8-bit blue channel. + /// Pixels encoded as 8-bit red channel, 8-bit green channel, 8-bit blue + /// channel. RGB_888, - /// Pixels encoded as 8-bit alpha channel, 8-bit red channel, 8-bit green channel, 8-bit blue channel. + /// Pixels encoded as 8-bit alpha channel, 8-bit red channel, 8-bit green + /// channel, 8-bit blue channel. ARGB_8888, // other pixel formats may be defined. } @@ -88,88 +125,109 @@ impl ScreenPixelFormat { } } -pub trait ScreenSetup { - fn set_client(&self, client: Option<&'static dyn ScreenSetupClient>); +/// Interface to configure the screen. +pub trait ScreenSetup<'a> { + fn set_client(&self, client: &'a dyn ScreenSetupClient); - /// Sets the screen resolution (in pixels). Returns ENOSUPPORT if the resolution is - /// not supported. The function should return Ok(()) if the request is registered - /// and will be sent to the screen. - /// Upon Ok(()), the caller has to wait for the `command_complete` callback function - /// that will return the actual Result<(), ErrorCode> after setting the resolution. + /// Set the screen resolution in pixels with `(X, Y)`. + /// + /// Returns `Ok(())` if the request is registered and will be sent to the + /// screen. A `command_complete` callback function will be triggered when + /// the resolution change is finished and will provide a `Result<(), + /// ErrorCode>` to indicate if the resolution change was successful. + /// + /// Returns `Err(NOSUPPORT)` if the resolution is not supported. No callback will + /// be triggered. fn set_resolution(&self, resolution: (usize, usize)) -> Result<(), ErrorCode>; - /// Sets the pixel format. Returns ENOSUPPORT if the pixel format is - /// not supported. The function should return Ok(()) if the request is registered - /// and will be sent to the screen. - /// Upon Ok(()), the caller has to wait for the `command_complete` callback function - /// that will return the actual Result<(), ErrorCode> after setting the pixel format. - fn set_pixel_format(&self, depth: ScreenPixelFormat) -> Result<(), ErrorCode>; + /// Set the pixel format. + /// + /// Returns `Ok(())` if the request is registered and will be sent to the + /// screen. A `command_complete` callback function will be triggered when + /// the pixel format change is finished and will provide a `Result<(), + /// ErrorCode>` to indicate if the pixel format change was successful. + /// + /// Returns `Err(NOSUPPORT)` if the pixel format is not supported. + fn set_pixel_format(&self, format: ScreenPixelFormat) -> Result<(), ErrorCode>; - /// Sets the rotation of the display. - /// The function should return Ok(()) if the request is registered - /// and will be sent to the screen. - /// Upon Ok(()), the caller has to wait for the `command_complete` callback function - /// that will return the actual Result<(), ErrorCode> after setting the rotation. + /// Set the rotation of the display. /// - /// Note that in the case of `Rotated90` or `Rotated270`, this will swap the width and height. + /// Returns `Ok(())` if the request is registered and will be sent to the + /// screen. A `command_complete` callback function will be triggered when + /// the rotation update is finished and will provide a `Result<(), + /// ErrorCode>` to indicate if the rotation change was successful. + /// + /// Note that `Rotated90` or `Rotated270` will swap the width and height. fn set_rotation(&self, rotation: ScreenRotation) -> Result<(), ErrorCode>; - /// Returns the number of the resolutions supported. - /// should return at least one (the current resolution) - /// This function is synchronous as the driver should know this value without - /// requesting it from the screen (most screens do not support such a request, - /// resolutions are described in the data sheet). + /// Get the number of supported resolutions. + /// + /// This must return at least one (the current resolution). /// - /// If the screen supports such a feature, the driver should request this information - /// from the screen upfront. + /// This function is synchronous as the driver should know this value + /// without requesting it from the screen (most screens do not support such + /// a request, resolutions are described in the data sheet). fn get_num_supported_resolutions(&self) -> usize; - /// Can be called with an index from 0 .. count-1 and will - /// a tuple (width, height) with the current resolution (in pixels). - /// note that width and height may change due to rotation + /// Get the resolution specified by the given index. /// - /// This function is synchronous as the driver should know this value without - /// requesting it from the screen. + /// `index` is from `0..get_num_supported_resolutions()-1` and this returns + /// a tuple `(width, height)` of the associated resolution (in pixels). Note + /// that width and height may change due to rotation. Returns `None` if + /// `index` is invalid. + /// + /// This function is synchronous as the driver should know this value + /// without requesting it from the screen. fn get_supported_resolution(&self, index: usize) -> Option<(usize, usize)>; - /// Returns the number of the pixel formats supported. - /// This function is synchronous as the driver should know this value without - /// requesting it from the screen (most screens do not support such a request, - /// pixel formats are described in the data sheet). + /// Get the number of the pixel formats supported. /// - /// If the screen supports such a feature, the driver should request this information - /// from the screen upfront. + /// This function is synchronous as the driver should know this value + /// without requesting it from the screen (most screens do not support such + /// a request, pixel formats are described in the data sheet). fn get_num_supported_pixel_formats(&self) -> usize; - /// Can be called with index 0 .. count-1 and will return - /// the value of each pixel format mode. + /// Get the pixel format specified by the given index. + /// + /// `index` is from `0..get_num_supported_pixel_formats()-1` and this + /// returns the associated pixel format. Returns `None` if `index` is + /// invalid. /// - /// This function is synchronous as the driver should know this value without - /// requesting it from the screen. + /// This function is synchronous as the driver should know this value + /// without requesting it from the screen. fn get_supported_pixel_format(&self, index: usize) -> Option; } -pub trait Screen { - /// Returns a tuple (width, height) with the current resolution (in pixels) - /// This function is synchronous as the driver should know this value without - /// requesting it from the screen. +/// Basic interface for screens. +pub trait Screen<'a> { + /// Set the object to receive the asynchronous command callbacks. + fn set_client(&self, client: &'a dyn ScreenClient); + + /// Get a tuple `(width, height)` with the current resolution (in pixels). + /// + /// This function is synchronous as the driver should know this value + /// without requesting it from the screen. /// - /// note that width and height may change due to rotation + /// Note that width and height may change due to rotation. fn get_resolution(&self) -> (usize, usize); - /// Returns the current pixel format - /// This function is synchronous as the driver should know this value without - /// requesting it from the screen. + /// Get the current pixel format. + /// + /// This function is synchronous as the driver should know this value + /// without requesting it from the screen. fn get_pixel_format(&self) -> ScreenPixelFormat; - /// Returns the current rotation. - /// This function is synchronous as the driver should know this value without - /// requesting it from the screen. + /// Get the current rotation. + /// + /// This function is synchronous as the driver should know this value + /// without requesting it from the screen. fn get_rotation(&self) -> ScreenRotation; - /// Sets the video memory frame. - /// This function has to be called before the first call to the write function. - /// This will generate a `command_complete()` callback when finished. + /// Sets the write frame. + /// + /// This function has to be called before the first call to the write + /// function. This will generate a `command_complete()` callback when + /// finished. /// /// Return values: /// - `Ok(())`: The write frame is valid. @@ -183,46 +241,74 @@ pub trait Screen { height: usize, ) -> Result<(), ErrorCode>; - /// Sends a write command to write data in the selected video memory frame. - /// When finished, the driver will call the `write_complete()` callback. + /// Write data from `buffer` to the selected write frame. /// - /// Return values: - /// - `Ok(())`: Write is valid and will be sent to the screen. - /// - `INVAL`: Write is invalid or length is wrong. - /// - `BUSY`: Another write is in progress. - fn write(&self, buffer: &'static mut [u8], len: usize) -> Result<(), ErrorCode>; - - /// Sends a write command to write data in the selected video memory frame - /// without resetting the video memory frame position. It "continues" the - /// write from the previous position. - /// This allows using buffers that are smaller than the video mameory frame. /// When finished, the driver will call the `write_complete()` callback. /// + /// This function can be called multiple times if the write frame is larger + /// than the size of the available buffer by setting `continue_write` to + /// `true`. If `continue_write` is false, the buffer write position will be + /// reset before the data are written. + /// /// Return values: /// - `Ok(())`: Write is valid and will be sent to the screen. - /// - `INVAL`: Write is invalid or length is wrong. + /// - `SIZE`: The buffer is too long for the selected write frame. /// - `BUSY`: Another write is in progress. - fn write_continue(&self, buffer: &'static mut [u8], len: usize) -> Result<(), ErrorCode>; - - /// Set the object to receive the asynchronous command callbacks. - fn set_client(&self, client: Option<&'static dyn ScreenClient>); - - /// Sets the display brightness and/or powers it off - /// Screens must implement this function for at least two brightness values (in percent) - /// 0 - power off, - /// otherwise - on, set brightness (if available) - fn set_brightness(&self, brightness: usize) -> Result<(), ErrorCode>; + fn write( + &self, + buffer: SubSliceMut<'static, u8>, + continue_write: bool, + ) -> Result<(), ErrorCode>; - /// Inverts the colors. - fn invert_on(&self) -> Result<(), ErrorCode>; + /// Set the display brightness value. + /// + /// Depending on the display, this may not cause any actual changes + /// until and unless power is enabled (see `set_power`). + /// + /// The following values must be supported: + /// - 0: completely no light emitted + /// - 1..65536: set brightness to the given level + /// + /// The display should interpret the brightness value as *lightness* (each + /// increment should change perceived brightness the same). 1 shall be the + /// minimum supported brightness, 65536 is the maximum brightness. Values in + /// between should approximate the intermediate values; minimum and maximum + /// included (e.g. when there is only 1 level). + fn set_brightness(&self, brightness: u16) -> Result<(), ErrorCode>; + + /// Controls the screen power supply. + /// + /// Use it to initialize the display device. + /// + /// For screens where display needs nonzero brightness (e.g. LED), this + /// shall set brightness to a default value if `set_brightness` was not + /// called first. + /// + /// The device may implement power independently from brightness, so call + /// `set_brightness` to turn on/off the module completely. + /// + /// To allow starting in the correct configuration, the driver is allowed to + /// cache values like brightness or invert mode and apply them together when + /// power is enabled. If the display cannot use selected configuration, this + /// call returns `INVAL`. + /// + /// When finished, calls `ScreenClient::screen_is_ready`, both when power + /// is enabled and disabled. + fn set_power(&self, enabled: bool) -> Result<(), ErrorCode>; - /// Reverts the colors to normal. - fn invert_off(&self) -> Result<(), ErrorCode>; + /// Controls the color inversion mode. + /// + /// Pixels already in the frame buffer, as well as newly submitted, will be + /// inverted. What that means depends on the current pixel format. May get + /// disabled when switching to another pixel format. Returns `NOSUPPORT` if + /// the device does not accelerate color inversion. Returns `INVAL` if the + /// current pixel format does not support color inversion. + fn set_invert(&self, enabled: bool) -> Result<(), ErrorCode>; } -pub trait ScreenAdvanced: Screen + ScreenSetup {} +pub trait ScreenAdvanced<'a>: Screen<'a> + ScreenSetup<'a> {} // Provide blanket implementations for trait group -impl ScreenAdvanced for T {} +impl<'a, T: Screen<'a> + ScreenSetup<'a>> ScreenAdvanced<'a> for T {} pub trait ScreenSetupClient { /// The screen will call this function to notify that a command has finished. @@ -230,13 +316,16 @@ pub trait ScreenSetupClient { } pub trait ScreenClient { - /// The screen will call this function to notify that a command (except write) has finished. - fn command_complete(&self, r: Result<(), ErrorCode>); + /// The screen will call this function to notify that a command (except + /// write) has finished. + fn command_complete(&self, result: Result<(), ErrorCode>); - /// The screen will call this function to notify that the write command has finished. - /// This is different from `command_complete` as it has to pass back the write buffer - fn write_complete(&self, buffer: &'static mut [u8], r: Result<(), ErrorCode>); + /// The screen will call this function to notify that the write command has + /// finished. This is different from `command_complete` as it has to pass + /// back the write buffer + fn write_complete(&self, buffer: SubSliceMut<'static, u8>, result: Result<(), ErrorCode>); - /// Some screens need some time to start, this function is called when the screen is ready + /// Some screens need some time to start, this function is called when the + /// screen is ready. fn screen_is_ready(&self); } diff --git a/kernel/src/hil/sensors.rs b/kernel/src/hil/sensors.rs index 6664677ebd..5841ae8ade 100644 --- a/kernel/src/hil/sensors.rs +++ b/kernel/src/hil/sensors.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interfaces for environment sensors use crate::errorcode::ErrorCode; @@ -13,8 +17,8 @@ pub trait TemperatureClient { /// Called when a temperature reading has completed. /// /// - `value`: the most recently read temperature in hundredths of degrees - /// centigrate. - fn callback(&self, value: usize); + /// centigrade (centiCelsius), or Err on failure. + fn callback(&self, value: Result); } /// A basic interface for a humidity sensor @@ -31,6 +35,69 @@ pub trait HumidityClient { fn callback(&self, value: usize); } +/// A basic interface for a Air Quality sensor +pub trait AirQualityDriver<'a> { + /// Set the client to be notified when the capsule has data ready. + fn set_client(&self, client: &'a dyn AirQualityClient); + + /// Specify the temperature and humidity used in calculating the air + /// quality. + /// + /// The temperature is specified in degrees Celsius and the humidity + /// is specified as a percentage. + /// + /// This is an optional call and doesn't have to be used, but on most + /// hardware can be used to improve the measurement accuracy. + /// + /// This function might return the following errors: + /// - `BUSY`: Indicates that the hardware is busy with an existing + /// operation or initialisation/calibration. + /// - `NOSUPPORT`: Indicates that this data type isn't supported. + fn specify_environment( + &self, + temp: Option, + humidity: Option, + ) -> Result<(), ErrorCode>; + + /// Read the CO2 or equivalent CO2 (eCO2) from the sensor. + /// This will trigger the `AirQualityClient` `co2_data_available()` + /// callback when the data is ready. + /// + /// This function might return the following errors: + /// - `BUSY`: Indicates that the hardware is busy with an existing + /// operation or initialisation/calibration. + /// - `NOSUPPORT`: Indicates that this data type isn't supported. + fn read_co2(&self) -> Result<(), ErrorCode>; + + /// Read the Total Organic Compound (TVOC) from the sensor. + /// This will trigger the `AirQualityClient` `tvoc_data_available()` + /// callback when the data is ready. + /// + /// This function might return the following errors: + /// - `BUSY`: Indicates that the hardware is busy with an existing + /// operation or initialisation/calibration. + /// - `NOSUPPORT`: Indicates that this data type isn't supported. + fn read_tvoc(&self) -> Result<(), ErrorCode>; +} + +/// Client for receiving Air Quality readings +pub trait AirQualityClient { + /// Called when the environment specify command has completed. + fn environment_specified(&self, result: Result<(), ErrorCode>); + + /// Called when a CO2 or equivalent CO2 (eCO2) reading has completed. + /// + /// - `value`: will contain the latest CO2 reading in ppm. An example value + /// might be `400`. + fn co2_data_available(&self, value: Result); + + /// Called when a Total Organic Compound (TVOC) reading has completed. + /// + /// - `value`: will contain the latest TVOC reading in ppb. An example value + /// might be `0`. + fn tvoc_data_available(&self, value: Result); +} + /// A basic interface for a proximity sensor pub trait ProximityDriver<'a> { fn set_client(&self, client: &'a dyn ProximityClient); @@ -144,3 +211,25 @@ pub trait SoundPressureClient { /// Signals the sound pressure in dB fn callback(&self, ret: Result<(), ErrorCode>, sound_pressure: u8); } + +/// A Basic interface for a barometer sensor. +pub trait PressureDriver<'a> { + /// Used to initialize a atmospheric pressure reading + /// + /// This function might return the following errors: + /// - `BUSY`: Indicates that the hardware is busy with an existing + /// operation or initialisation/calibration. + /// - `FAIL`: Failed to correctly communicate over communication protocol. + /// - `NOSUPPORT`: Indicates that this data type isn't supported. + fn read_atmospheric_pressure(&self) -> Result<(), ErrorCode>; + + /// Set the client + fn set_client(&self, client: &'a dyn PressureClient); +} + +pub trait PressureClient { + /// Called when a atmospheric pressure reading has completed. + /// + /// Returns the value in hPa. + fn callback(&self, pressure: Result); +} diff --git a/kernel/src/hil/spi.rs b/kernel/src/hil/spi.rs index 124affb637..9731ebbc17 100644 --- a/kernel/src/hil/spi.rs +++ b/kernel/src/hil/spi.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interfaces for SPI controller (master) and peripheral (slave) //! communication. We use the terms master/slave in some situations //! because the term peripheral can also refer to a hardware peripheral @@ -10,7 +14,6 @@ // Author: Amit Aryeh Levy use crate::ErrorCode; -use core::option::Option; /// Data order defines the order of bits sent over the wire: most /// significant first, or least significant first. @@ -93,7 +96,7 @@ pub trait SpiMasterClient { /// pin_b.clear(); // Select B /// write_byte(0xaa); // Uses SampleTrailing /// -pub trait SpiMaster { +pub trait SpiMaster<'a> { /// Chip select is an associated type because different SPI /// buses may have different numbers of chip selects. This /// allows peripheral implementations to define their own type. @@ -109,7 +112,7 @@ pub trait SpiMaster { /// Change the callback handler for `read_write_bytes` /// calls. - fn set_client(&self, client: &'static dyn SpiMasterClient); + fn set_client(&self, client: &'a dyn SpiMasterClient); /// Return whether the SPI peripheral is busy with `read_write_bytes` /// call. @@ -231,9 +234,9 @@ pub trait SpiMaster { /// SPIMasterDevice provides a chip-select-specific interface to the SPI /// Master hardware, such that a client cannot changethe chip select line. -pub trait SpiMasterDevice { +pub trait SpiMasterDevice<'a> { /// Set the callback for read_write operations. - fn set_client(&self, client: &'static dyn SpiMasterClient); + fn set_client(&self, client: &'a dyn SpiMasterClient); /// Configure the bus for this chip select. fn configure(&self, cpol: ClockPolarity, cpal: ClockPhase, rate: u32) -> Result<(), ErrorCode>; @@ -332,7 +335,7 @@ pub trait SpiSlaveClient { /// (master). This is a low-level trait typically implemented by hardware: /// higher level software typically uses the `SpiSlaveDevice` trait, /// which is provided by a virtualizing/multiplexing layer. -pub trait SpiSlave { +pub trait SpiSlave<'a> { /// Initialize the SPI device to be in peripheral mode. /// Return values: /// - Ok(()): the device is in peripheral mode @@ -348,7 +351,7 @@ pub trait SpiSlave { /// Set the callback for slave operations, passing `None` to /// disable peripheral mode. - fn set_client(&self, client: Option<&'static dyn SpiSlaveClient>); + fn set_client(&self, client: Option<&'a dyn SpiSlaveClient>); /// Set a single byte to write in response to a read/write /// operation from a controller. Useful for devices that always @@ -409,9 +412,9 @@ pub trait SpiSlave { /// It is the standard trait used by services within the kernel: /// `SpiSlave` is for lower-level access responsible for initializing /// hardware. -pub trait SpiSlaveDevice { +pub trait SpiSlaveDevice<'a> { /// Specify the callback of `read_write_bytes` operations: - fn set_client(&self, client: &'static dyn SpiSlaveClient); + fn set_client(&self, client: &'a dyn SpiSlaveClient); /// Setup the SPI settings and speed of the bus. fn configure(&self, cpol: ClockPolarity, cpal: ClockPhase) -> Result<(), ErrorCode>; diff --git a/kernel/src/hil/symmetric_encryption.rs b/kernel/src/hil/symmetric_encryption.rs index 72e6a39c00..e32638587b 100644 --- a/kernel/src/hil/symmetric_encryption.rs +++ b/kernel/src/hil/symmetric_encryption.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for symmetric-cipher encryption //! //! see boards/imix/src/aes_test.rs for example usage @@ -139,3 +143,40 @@ pub trait AES128CCM<'a> { encrypting: bool, ) -> Result<(), (ErrorCode, &'static mut [u8])>; } + +pub trait GCMClient { + /// `res` is Ok(()) if the encryption/decryption process succeeded. This + /// does not mean that the message has been verified in the case of + /// decryption. + /// If we are encrypting: `tag_is_valid` is `true` iff `res` is Ok(()). + /// If we are decrypting: `tag_is_valid` is `true` iff `res` is Ok(()) and the + /// message authentication tag is valid. + fn crypt_done(&self, buf: &'static mut [u8], res: Result<(), ErrorCode>, tag_is_valid: bool); +} + +pub trait AES128GCM<'a> { + /// Set the client instance which will receive `crypt_done()` callbacks + fn set_client(&'a self, client: &'a dyn GCMClient); + + /// Set the key to be used for GCM encryption + /// Returns `INVAL` if length is not `AES128_KEY_SIZE` + fn set_key(&self, key: &[u8]) -> Result<(), ErrorCode>; + + /// Set the IV to be used for GCM encryption. The IV should be less + /// or equal to 12 bytes (96 bits) as recommened in NIST-800-38D. + /// Returns `INVAL` if length is greater then 12 bytes + fn set_iv(&self, nonce: &[u8]) -> Result<(), ErrorCode>; + + /// Try to begin the encryption/decryption process + /// The possible ErrorCodes are: + /// - `BUSY`: An operation is already in progress + /// - `SIZE`: The offset and lengths don't fit inside the buffer + fn crypt( + &self, + buf: &'static mut [u8], + aad_offset: usize, + message_offset: usize, + message_len: usize, + encrypting: bool, + ) -> Result<(), (ErrorCode, &'static mut [u8])>; +} diff --git a/kernel/src/hil/text_screen.rs b/kernel/src/hil/text_screen.rs index 2c3a41bbf4..acd8025372 100644 --- a/kernel/src/hil/text_screen.rs +++ b/kernel/src/hil/text_screen.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for text screen and displays. use crate::ErrorCode; diff --git a/kernel/src/hil/time.rs b/kernel/src/hil/time.rs index 694b0ed99a..c2ba877e06 100644 --- a/kernel/src/hil/time.rs +++ b/kernel/src/hil/time.rs @@ -1,8 +1,12 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Hardware agnostic interfaces for time and timers within the Tock //! kernel. //! //! These traits are designed to be able encompass the wide -//! variety of hardare counters in a general yet efficient way. They +//! variety of hardware counters in a general yet efficient way. They //! abstract the frequency of a counter through the `Frequency` trait //! and the width of a time value through the `Ticks` //! trait. Higher-level software abstractions should generally rely on @@ -12,18 +16,58 @@ //! into these more general ones. use crate::ErrorCode; -use core::cmp::{Eq, Ord, Ordering, PartialOrd}; +use core::cmp::Ordering; use core::fmt; /// An integer type defining the width of a time value, which allows /// clients to know when wraparound will occur. pub trait Ticks: Clone + Copy + From + fmt::Debug + Ord + PartialOrd + Eq { + /// Width of the actual underlying timer in bits. + /// + /// The maximum value that *will* be attained by this timer should + /// be `(2 ** width) - 1`. In other words, the timer will wrap at + /// exactly `width` bits, and then continue counting at `0`. + /// + /// The return value is a `u32`, in accordance with the bit widths + /// specified using the BITS associated const on Rust integer + /// types. + fn width() -> u32; + /// Converts the type into a `usize`, stripping the higher bits /// it if it is larger than `usize` and filling the higher bits /// with 0 if it is smaller than `usize`. fn into_usize(self) -> usize; + /// The amount of bits required to left-justify this ticks value + /// range (filling the lower bits with `0`) for it wrap at `(2 ** + /// usize::BITS) - 1` bits. For timers with a `width` larger than + /// usize, this value will be `0` (i.e., they can simply be + /// truncated to usize::BITS bits). + fn usize_padding() -> u32 { + usize::BITS.saturating_sub(Self::width()) + } + + /// Converts the type into a `usize`, left-justified and + /// right-padded with `0` such that it is guaranteed to wrap at + /// `(2 ** usize::BITS) - 1`. If it is larger than usize::BITS + /// bits, any higher bits are stripped. + /// + /// The resulting tick rate will possibly be higher (multiplied by + /// `2 ** usize_padding()`). Use `usize_left_justified_scale_freq` + /// to convert the underlying timer's frequency into the padded + /// ticks frequency in Hertz. + fn into_usize_left_justified(self) -> usize { + self.into_usize() << Self::usize_padding() + } + + /// Convert the generic [`Frequency`] argument into a frequency + /// (Hertz) describing a left-justified ticks value as returned by + /// [`Ticks::into_usize_left_justified`]. + fn usize_left_justified_scale_freq() -> u32 { + F::frequency() << Self::usize_padding() + } + /// Converts the type into a `u32`, stripping the higher bits /// it if it is larger than `u32` and filling the higher bits /// with 0 if it is smaller than `u32`. Included as a simple @@ -31,6 +75,39 @@ pub trait Ticks: Clone + Copy + From + fmt::Debug + Ord + PartialOrd + Eq { /// are 32 bits. fn into_u32(self) -> u32; + /// The amount of bits required to left-justify this ticks value + /// range (filling the lower bits with `0`) for it wrap at `(2 ** + /// 32) - 1` bits. For timers with a `width` larger than 32, this + /// value will be `0` (i.e., they can simply be truncated to + /// 32-bits). + /// + /// The return value is a `u32`, in accordance with the bit widths + /// specified using the BITS associated const on Rust integer + /// types. + fn u32_padding() -> u32 { + u32::BITS.saturating_sub(Self::width()) + } + + /// Converts the type into a `u32`, left-justified and + /// right-padded with `0` such that it is guaranteed to wrap at + /// `(2 ** 32) - 1`. If it is larger than 32-bits, any higher bits + /// are stripped. + /// + /// The resulting tick rate will possibly be higher (multiplied by + /// `2 ** u32_padding()`). Use `u32_left_justified_scale_freq` to + /// convert the underlying timer's frequency into the padded ticks + /// frequency in Hertz. + fn into_u32_left_justified(self) -> u32 { + self.into_u32() << Self::u32_padding() + } + + /// Convert the generic [`Frequency`] argument into a frequency + /// (Hertz) describing a left-justified ticks value as returned by + /// [`Ticks::into_u32_left_justified`]. + fn u32_left_justified_scale_freq() -> u32 { + F::frequency() << Self::u32_padding() + } + /// Add two values, wrapping around on overflow using standard /// unsigned arithmetic. fn wrapping_add(self, other: Self) -> Self; @@ -51,7 +128,7 @@ pub trait Ticks: Clone + Copy + From + fmt::Debug + Ord + PartialOrd + Eq { /// Returns the half the maximum value of this type, which should be (2^width-1). fn half_max_value() -> Self; - /// Coverts the specified val into this type if it fits otherwise the + /// Converts the specified val into this type if it fits otherwise the /// `max_value()` is returned fn from_or_max(val: u64) -> Self; @@ -169,7 +246,7 @@ pub trait Counter<'a>: Time { /// - `Err(ErrorCode::OFF)`: underlying clocks or other hardware resources /// are not on, such that the counter cannot start. /// - `Err(ErrorCode::FAIL)`: unidentified failure, counter is not running. - /// After a successful call to `start`, `is_running` MUST return true. + /// After a successful call to `start`, `is_running` MUST return true. fn start(&self) -> Result<(), ErrorCode>; /// Stops the free-running hardware counter. Valid `Result<(), ErrorCode>` values are: @@ -178,7 +255,7 @@ pub trait Counter<'a>: Time { /// - `Err(ErrorCode::BUSY)`: the counter is in use in a way that means it /// cannot be stopped and is busy. /// - `Err(ErrorCode::FAIL)`: unidentified failure, counter is running. - /// After a successful call to `stop`, `is_running` MUST return false. + /// After a successful call to `stop`, `is_running` MUST return false. fn stop(&self) -> Result<(), ErrorCode>; /// Resets the counter to 0. This may introduce jitter on the counter. @@ -187,7 +264,7 @@ pub trait Counter<'a>: Time { /// call `stop` before `reset`. /// Valid `Result<(), ErrorCode>` values are: /// - `Ok(())`: the counter was reset to 0. - /// - `Err(ErrorCode::FAIL)`: the counter was not reset to 0. + /// - `Err(ErrorCode::FAIL)`: the counter was not reset to 0. fn reset(&self) -> Result<(), ErrorCode>; /// Returns whether the counter is currently running. @@ -235,9 +312,9 @@ pub trait Alarm<'a>: Time { /// Disable the alarm and stop it from firing in the future. /// Valid `Result<(), ErrorCode>` codes are: /// - `Ok(())` the alarm has been disarmed and will not invoke - /// the callback in the future + /// the callback in the future /// - `Err(ErrorCode::FAIL)` the alarm could not be disarmed and will invoke - /// the callback in the future + /// the callback in the future fn disarm(&self) -> Result<(), ErrorCode>; /// Returns whether the alarm is currently armed. Note that this @@ -266,7 +343,7 @@ pub trait TimerClient { /// precisely timed callbacks should use the `Alarm` trait instead. pub trait Timer<'a>: Time { /// Specify the callback to invoke when the timer interval expires. - /// If there was a previously installed callback this call replaces it. + /// If there was a previously installed callback this call replaces it. fn set_timer_client(&self, client: &'a dyn TimerClient); /// Start a one-shot timer that will invoke the callback at least @@ -296,7 +373,7 @@ pub trait Timer<'a>: Time { /// Return how many ticks are remaining until the next callback, /// or None if the timer is disabled. This call is useful because - /// there may be non-neglible delays between when a timer was + /// there may be non-negligible delays between when a timer was /// requested and it was actually scheduled. Therefore, since a /// timer's start might be delayed slightly, the time remaining /// might be slightly higher than one would expect if one @@ -316,57 +393,69 @@ pub trait Timer<'a>: Time { fn cancel(&self) -> Result<(), ErrorCode>; } +// The following "frequencies" are represented as variant-less enums. Because +// they can never be constructed, it forces them to be used purely as +// type-markers which are guaranteed to be elided at runtime. + /// 100MHz `Frequency` #[derive(Debug)] -pub struct Freq100MHz; +pub enum Freq100MHz {} impl Frequency for Freq100MHz { fn frequency() -> u32 { - 100000000 + 100_000_000 } } /// 16MHz `Frequency` #[derive(Debug)] -pub struct Freq16MHz; +pub enum Freq16MHz {} impl Frequency for Freq16MHz { fn frequency() -> u32 { - 16000000 + 16_000_000 + } +} + +/// 10MHz `Frequency` +pub enum Freq10MHz {} +impl Frequency for Freq10MHz { + fn frequency() -> u32 { + 10_000_000 } } /// 1MHz `Frequency` #[derive(Debug)] -pub struct Freq1MHz; +pub enum Freq1MHz {} impl Frequency for Freq1MHz { fn frequency() -> u32 { - 1000000 + 1_000_000 } } -/// 32KHz `Frequency` +/// 32.768KHz `Frequency` #[derive(Debug)] -pub struct Freq32KHz; +pub enum Freq32KHz {} impl Frequency for Freq32KHz { fn frequency() -> u32 { - 32768 + 32_768 } } /// 16KHz `Frequency` #[derive(Debug)] -pub struct Freq16KHz; +pub enum Freq16KHz {} impl Frequency for Freq16KHz { fn frequency() -> u32 { - 16000 + 16_000 } } /// 1KHz `Frequency` #[derive(Debug)] -pub struct Freq1KHz; +pub enum Freq1KHz {} impl Frequency for Freq1KHz { fn frequency() -> u32 { - 1000 + 1_000 } } @@ -381,6 +470,10 @@ impl From for Ticks32 { } impl Ticks for Ticks32 { + fn width() -> u32 { + 32 + } + fn into_usize(self) -> usize { self.0 as usize } @@ -455,13 +548,21 @@ impl Eq for Ticks32 {} #[derive(Clone, Copy, Debug)] pub struct Ticks24(u32); +impl Ticks24 { + pub const MASK: u32 = 0x00FFFFFF; +} + impl From for Ticks24 { fn from(val: u32) -> Self { - Ticks24(val) + Ticks24(val & Self::MASK) } } impl Ticks for Ticks24 { + fn width() -> u32 { + 24 + } + fn into_usize(self) -> usize { self.0 as usize } @@ -471,11 +572,11 @@ impl Ticks for Ticks24 { } fn wrapping_add(self, other: Self) -> Self { - Ticks24(self.0.wrapping_add(other.0) & 0x00FFFFFF) + Ticks24(self.0.wrapping_add(other.0) & Self::MASK) } fn wrapping_sub(self, other: Self) -> Self { - Ticks24(self.0.wrapping_sub(other.0) & 0x00FFFFFF) + Ticks24(self.0.wrapping_sub(other.0) & Self::MASK) } fn within_range(self, start: Self, end: Self) -> bool { @@ -484,7 +585,7 @@ impl Ticks for Ticks24 { /// Returns the maximum value of this type, which should be (2^width)-1. fn max_value() -> Self { - Ticks24(0x00FFFFFF) + Ticks24(Self::MASK) } /// Returns the half the maximum value of this type, which should be (2^width-1). @@ -555,6 +656,10 @@ impl Ticks16 { } impl Ticks for Ticks16 { + fn width() -> u32 { + 16 + } + fn into_usize(self) -> usize { self.0 as usize } @@ -643,11 +748,15 @@ impl From for Ticks64 { impl From for Ticks64 { fn from(val: u64) -> Self { - Ticks64(val as u64) + Ticks64(val) } } impl Ticks for Ticks64 { + fn width() -> u32 { + 64 + } + fn into_usize(self) -> usize { self.0 as usize } diff --git a/kernel/src/hil/touch.rs b/kernel/src/hil/touch.rs index e766ed9e6a..b1fd1a8948 100644 --- a/kernel/src/hil/touch.rs +++ b/kernel/src/hil/touch.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for touch input devices use crate::ErrorCode; diff --git a/kernel/src/hil/uart.rs b/kernel/src/hil/uart.rs index f229f97cb1..4614b1000d 100644 --- a/kernel/src/hil/uart.rs +++ b/kernel/src/hil/uart.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Hardware interface layer (HIL) traits for UART communication. //! //! @@ -54,6 +58,9 @@ pub enum Error { /// UART hardware was reset ResetError, + /// UART hardware was disconnected + BreakError, + /// Read or write was aborted early Aborted, } diff --git a/kernel/src/hil/usb.rs b/kernel/src/hil/usb.rs index 95454c642b..cb68bc2bfd 100644 --- a/kernel/src/hil/usb.rs +++ b/kernel/src/hil/usb.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface to USB controller hardware use crate::utilities::cells::VolatileCell; diff --git a/kernel/src/hil/usb_hid.rs b/kernel/src/hil/usb_hid.rs index 6ba6831a90..30592c2795 100644 --- a/kernel/src/hil/usb_hid.rs +++ b/kernel/src/hil/usb_hid.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for USB HID (Human Interface Device) class use crate::ErrorCode; diff --git a/kernel/src/introspection.rs b/kernel/src/introspection.rs index fe265ff8b8..22f50b9335 100644 --- a/kernel/src/introspection.rs +++ b/kernel/src/introspection.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Mechanism for inspecting the status of the kernel. //! //! In particular this provides functions for getting the status of processes @@ -25,7 +29,7 @@ pub struct KernelInfo { impl KernelInfo { pub fn new(kernel: &'static Kernel) -> KernelInfo { - KernelInfo { kernel: kernel } + KernelInfo { kernel } } /// Returns how many processes have been loaded on this platform. This is diff --git a/kernel/src/ipc.rs b/kernel/src/ipc.rs index 1f8ae156e7..e285577ff2 100644 --- a/kernel/src/ipc.rs +++ b/kernel/src/ipc.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Inter-process communication mechanism for Tock. //! //! This is a special syscall driver that allows userspace applications to @@ -18,8 +22,8 @@ pub const DRIVER_NUM: usize = 0x10000; /// Ids for read-only allow buffers mod ro_allow { pub(super) const SEARCH: usize = 0; - /// The number of allow buffers the kernel stores for this grant - pub(super) const COUNT: usize = 1; + /// The number of allow buffers the kernel stores for this grant. + pub(super) const COUNT: u8 = 1; } /// Enum to mark which type of upcall is scheduled for the IPC mechanism. @@ -38,7 +42,7 @@ pub enum IPCUpcallType { struct IPCData; /// The IPC mechanism struct. -pub struct IPC { +pub struct IPC { /// The grant regions for each process that holds the per-process IPC data. data: Grant< IPCData, @@ -48,7 +52,7 @@ pub struct IPC { >, } -impl IPC { +impl IPC { pub fn new( kernel: &'static Kernel, driver_num: usize, @@ -69,6 +73,13 @@ impl IPC { ) -> Result<(), process::Error> { let schedule_on_id = schedule_on.index().ok_or(process::Error::NoSuchApp)?; let called_from_id = called_from.index().ok_or(process::Error::NoSuchApp)?; + + // Verify that IPC is not trying to share with the same app. If so, this + // will cause a double grant enter if we don't return now. + if schedule_on_id == called_from_id { + return Err(process::Error::AlreadyInUse); + } + self.data.enter(schedule_on, |_, schedule_on_data| { self.data.enter(called_from, |_, called_from_data| { // If the other app shared a buffer with us, make @@ -98,7 +109,7 @@ impl IPC { } } -impl SyscallDriver for IPC { +impl SyscallDriver for IPC { /// command is how notify() is implemented. /// Notifying an IPC service is done by setting client_or_svc to 0, /// and notifying an IPC client is done by setting client_or_svc to 1. @@ -111,7 +122,7 @@ impl SyscallDriver for IPC { /// /// ### `command_num` /// - /// - `0`: Driver check, always returns Ok(()) + /// - `0`: Driver existence check, always returns Ok(()) /// - `1`: Perform discovery on the package name passed to `allow_readonly`. Returns the /// service descriptor if the service is found, otherwise returns an error. /// - `2`: Notify a service previously discovered to have the service descriptor in @@ -125,7 +136,7 @@ impl SyscallDriver for IPC { command_number: usize, target_id: usize, _: usize, - appid: ProcessId, + processid: ProcessId, ) -> CommandReturn { match command_number { 0 => CommandReturn::success(), @@ -133,7 +144,7 @@ impl SyscallDriver for IPC { /* Discover */ { self.data - .enter(appid, |_, kernel_data| { + .enter(processid, |_, kernel_data| { kernel_data .get_readonly_processbuffer(ro_allow::SEARCH) .and_then(|search| { @@ -182,7 +193,7 @@ impl SyscallDriver for IPC { CommandReturn::failure(ErrorCode::INVAL), otherapp, |target| { - let ret = target.enqueue_task(process::Task::IPC((appid, cb_type))); + let ret = target.enqueue_task(process::Task::IPC((processid, cb_type))); match ret { Ok(()) => CommandReturn::success(), Err(e) => { @@ -215,7 +226,7 @@ impl SyscallDriver for IPC { CommandReturn::failure(ErrorCode::INVAL), otherapp, |target| { - let ret = target.enqueue_task(process::Task::IPC((appid, cb_type))); + let ret = target.enqueue_task(process::Task::IPC((processid, cb_type))); match ret { Ok(()) => CommandReturn::success(), Err(e) => { diff --git a/kernel/src/kernel.rs b/kernel/src/kernel.rs index b3d5a87db7..b9a22625ef 100644 --- a/kernel/src/kernel.rs +++ b/kernel/src/kernel.rs @@ -1,9 +1,12 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tock's main kernel loop, scheduler loop, and Scheduler trait. //! -//! This module also includes utility functions that are commonly used -//! by scheduler policy implementations. Scheduling policy (round -//! robin, priority, etc.) is defined in the `sched` subcrate and -//! selected by a board. +//! This module also includes utility functions that are commonly used by +//! scheduler policy implementations. Scheduling policy (round robin, priority, +//! etc.) is defined in the `scheduler` subcrate and selected by a board. use core::cell::Cell; use core::ptr::NonNull; @@ -11,7 +14,7 @@ use core::ptr::NonNull; use crate::capabilities; use crate::config; use crate::debug; -use crate::dynamic_deferred_call::DynamicDeferredCall; +use crate::deferred_call::DeferredCall; use crate::errorcode::ErrorCode; use crate::grant::{AllowRoSize, AllowRwSize, Grant, UpcallSize}; use crate::ipc; @@ -23,9 +26,9 @@ use crate::platform::platform::KernelResources; use crate::platform::platform::{ProcessFault, SyscallDriverLookup, SyscallFilter}; use crate::platform::scheduler_timer::SchedulerTimer; use crate::platform::watchdog::WatchDog; -use crate::process::ProcessId; -use crate::process::{self, Task}; +use crate::process::{self, ProcessId, Task}; use crate::scheduler::{Scheduler, SchedulingDecision}; +use crate::syscall::SyscallDriver; use crate::syscall::{ContextSwitchReason, SyscallReturn}; use crate::syscall::{Syscall, YieldCall}; use crate::syscall_driver::CommandReturn; @@ -39,10 +42,6 @@ pub(crate) const MIN_QUANTA_THRESHOLD_US: u32 = 500; /// Main object for the kernel. Each board will need to create one. pub struct Kernel { - /// How many "to-do" items exist at any given time. These include - /// outstanding upcalls and processes in the Running state. - work: Cell, - /// This holds a pointer to the static array of Process pointers. processes: &'static [Option<&'static dyn process::Process>], @@ -62,31 +61,6 @@ pub struct Kernel { grants_finalized: Cell, } -/// Enum used to inform scheduler why a process stopped executing (aka why -/// `do_process()` returned). -#[derive(PartialEq, Eq)] -pub enum StoppedExecutingReason { - /// The process returned because it is no longer ready to run. - NoWorkLeft, - - /// The process faulted, and the board restart policy was configured such - /// that it was not restarted and there was not a kernel panic. - StoppedFaulted, - - /// The kernel stopped the process. - Stopped, - - /// The process was preempted because its timeslice expired. - TimesliceExpired, - - /// The process returned because it was preempted by the kernel. This can - /// mean that kernel work became ready (most likely because an interrupt - /// fired and the kernel thread needs to execute the bottom half of the - /// interrupt), or because the scheduler no longer wants to execute that - /// process. - KernelPreemption, -} - /// Represents the different outcomes when trying to allocate a grant region enum AllocResult { NoAllocation, @@ -96,30 +70,25 @@ enum AllocResult { /// Tries to allocate the grant region for specified driver and process. /// Returns if a new grant was allocated or not -fn try_allocate_grant, C: Chip>( - resources: &KR, - driver_number: usize, - process: &dyn process::Process, -) -> AllocResult { +fn try_allocate_grant(driver: &dyn SyscallDriver, process: &dyn process::Process) -> AllocResult { let before_count = process.grant_allocated_count().unwrap_or(0); - resources - .syscall_driver_lookup() - .with_driver(driver_number, |driver| match driver { - Some(d) => match d.allocate_grant(process.processid()).is_ok() { - true if before_count == process.grant_allocated_count().unwrap_or(0) => { - AllocResult::SameAllocation - } - true => AllocResult::NewAllocation, - false => AllocResult::NoAllocation, - }, - None => AllocResult::NoAllocation, - }) + match driver.allocate_grant(process.processid()).is_ok() { + true if before_count == process.grant_allocated_count().unwrap_or(0) => { + AllocResult::SameAllocation + } + true => AllocResult::NewAllocation, + false => AllocResult::NoAllocation, + } } impl Kernel { + /// Create the kernel object that knows about the list of processes. + /// + /// Crucially, the processes included in the `processes` array MUST be valid + /// to execute. Any credential checks or validation MUST happen before the + /// `Process` object is included in this array. pub fn new(processes: &'static [Option<&'static dyn process::Process>]) -> Kernel { Kernel { - work: Cell::new(0), processes, process_identifier_max: Cell::new(0), grant_counter: Cell::new(0), @@ -127,63 +96,17 @@ impl Kernel { } } - /// Something was scheduled for a process, so there is more work to do. - /// - /// This is only exposed in the core kernel crate. - pub(crate) fn increment_work(&self) { - self.work.increment(); - } - - /// Something was scheduled for a process, so there is more work to do. - /// - /// This is exposed publicly, but restricted with a capability. The intent - /// is that external implementations of `Process` need to be able to - /// indicate there is more process work to do. - pub fn increment_work_external( - &self, - _capability: &dyn capabilities::ExternalProcessCapability, - ) { - self.increment_work(); - } - - /// Something finished for a process, so we decrement how much work there is - /// to do. - /// - /// This is only exposed in the core kernel crate. - pub(crate) fn decrement_work(&self) { - self.work.decrement(); - } - - /// Something finished for a process, so we decrement how much work there is - /// to do. - /// - /// This is exposed publicly, but restricted with a capability. The intent - /// is that external implementations of `Process` need to be able to - /// indicate that some process work has finished. - pub fn decrement_work_external( - &self, - _capability: &dyn capabilities::ExternalProcessCapability, - ) { - self.decrement_work(); - } - - /// Helper function for determining if we should service processes or go to - /// sleep. - pub(crate) fn processes_blocked(&self) -> bool { - self.work.get() == 0 - } - /// Helper function that moves all non-generic portions of process_map_or /// into a non-generic function to reduce code bloat from monomorphization. pub(crate) fn get_process(&self, processid: ProcessId) -> Option<&dyn process::Process> { - // We use the index in the `appid` so we can do a direct lookup. + // We use the index in the [`ProcessId`] so we can do a direct lookup. // However, we are not guaranteed that the app still exists at that // index in the processes array. To avoid additional overhead, we do the // lookup and check here, rather than calling `.index()`. match self.processes.get(processid.index) { Some(Some(process)) => { // Check that the process stored here matches the identifier - // in the `appid`. + // in the `processid`. if process.processid() == processid { Some(*process) } else { @@ -195,8 +118,8 @@ impl Kernel { } /// Run a closure on a specific process if it exists. If the process with a - /// matching `ProcessId` does not exist at the index specified within the - /// `ProcessId`, then `default` will be returned. + /// matching [`ProcessId`] does not exist at the index specified within the + /// [`ProcessId`], then `default` will be returned. /// /// A match will not be found if the process was removed (and there is a /// `None` in the process array), if the process changed its identifier @@ -204,11 +127,11 @@ impl Kernel { /// different index in the processes array. Note that a match _will_ be /// found if the process still exists in the correct location in the array /// but is in any "stopped" state. - pub(crate) fn process_map_or(&self, default: R, appid: ProcessId, closure: F) -> R + pub(crate) fn process_map_or(&self, default: R, processid: ProcessId, closure: F) -> R where F: FnOnce(&dyn process::Process) -> R, { - match self.get_process(appid) { + match self.get_process(processid) { Some(process) => closure(process), None => default, } @@ -231,14 +154,14 @@ impl Kernel { pub fn process_map_or_external( &self, default: R, - appid: ProcessId, + processid: ProcessId, closure: F, _capability: &dyn capabilities::ProcessManagementCapability, ) -> R where F: FnOnce(&dyn process::Process) -> R, { - match self.get_process(appid) { + match self.get_process(processid) { Some(process) => closure(process), None => default, } @@ -260,7 +183,7 @@ impl Kernel { } } - /// Returns an iterator over all processes loaded by the kernel + /// Returns an iterator over all processes loaded by the kernel. pub(crate) fn get_process_iter( &self, ) -> core::iter::FilterMap< @@ -298,9 +221,9 @@ impl Kernel { } } - /// Run a closure on every process, but only continue if the closure returns `None`. That is, - /// if the closure returns any non-`None` value, iteration stops and the value is returned from - /// this function to the called. + /// Run a closure on every process, but only continue if the closure returns + /// `None`. That is, if the closure returns any non-`None` value, iteration + /// stops and the value is returned from this function to the called. pub(crate) fn process_until(&self, closure: F) -> Option where F: Fn(&dyn process::Process) -> Option, @@ -319,15 +242,15 @@ impl Kernel { None } - /// Checks if the provided `ProcessId` is still valid given the processes stored - /// in the processes array. Returns `true` if the ProcessId still refers to - /// a valid process, and `false` if not. + /// Checks if the provided [`ProcessId`] is still valid given the processes + /// stored in the processes array. Returns `true` if the ProcessId still + /// refers to a valid process, and `false` if not. /// - /// This is needed for `ProcessId` itself to implement the `.index()` command to - /// verify that the referenced app is still at the correct index. - pub(crate) fn processid_is_valid(&self, appid: &ProcessId) -> bool { - self.processes.get(appid.index).map_or(false, |p| { - p.map_or(false, |process| process.processid().id() == appid.id()) + /// This is needed for `ProcessId` itself to implement the `.index()` + /// command to verify that the referenced app is still at the correct index. + pub(crate) fn processid_is_valid(&self, processid: &ProcessId) -> bool { + self.processes.get(processid.index).map_or(false, |p| { + p.map_or(false, |process| process.processid().id() == processid.id()) }) } @@ -394,8 +317,8 @@ impl Kernel { /// Create a new unique identifier for a process and return the identifier. /// - /// Typically we just choose a larger number than we have used for any process - /// before which ensures that the identifier is unique. + /// Typically we just choose a larger number than we have used for any + /// process before which ensures that the identifier is unique. pub(crate) fn create_process_identifier(&self) -> usize { self.process_identifier_max.get_and_increment() } @@ -435,7 +358,7 @@ impl Kernel { /// This function has one configuration option: `no_sleep`. If that argument /// is set to true, the kernel will never attempt to put the chip to sleep, /// and this function can be called again immediately. - pub fn kernel_loop_operation, C: Chip, const NUM_PROCS: usize>( + pub fn kernel_loop_operation, C: Chip, const NUM_PROCS: u8>( &self, resources: &KR, chip: &C, @@ -459,9 +382,9 @@ impl Kernel { } false => { // No kernel work ready, so ask scheduler for a process. - match scheduler.next(self) { - SchedulingDecision::RunProcess((appid, timeslice_us)) => { - self.process_map_or((), appid, |process| { + match scheduler.next() { + SchedulingDecision::RunProcess((processid, timeslice_us)) => { + self.process_map_or((), processid, |process| { let (reason, time_executed) = self.do_process(resources, chip, process, ipc, timeslice_us); scheduler.result(reason, time_executed); @@ -483,9 +406,7 @@ impl Kernel { // starts, the interrupt will not be // serviced and the chip will never wake // from sleep. - if !chip.has_pending_interrupts() - && !DynamicDeferredCall::global_instance_calls_pending() - .unwrap_or(false) + if !chip.has_pending_interrupts() && !DeferredCall::has_tasks() { resources.watchdog().suspend(); chip.sleep(); @@ -502,9 +423,9 @@ impl Kernel { /// Main loop of the OS. /// - /// Most of the behavior of this loop is controlled by the `Scheduler` + /// Most of the behavior of this loop is controlled by the [`Scheduler`] /// implementation in use. - pub fn kernel_loop, C: Chip, const NUM_PROCS: usize>( + pub fn kernel_loop, C: Chip, const NUM_PROCS: u8>( &self, resources: &KR, chip: &C, @@ -512,6 +433,8 @@ impl Kernel { capability: &dyn capabilities::MainLoopCapability, ) -> ! { resources.watchdog().setup(); + // Before we begin, verify that deferred calls were soundly setup. + DeferredCall::verify_setup(); loop { self.kernel_loop_operation(resources, chip, ipc, false, capability); } @@ -548,14 +471,14 @@ impl Kernel { /// cooperatively). Notably, time spent in this function by the kernel, /// executing system calls or merely setting up the switch to/from /// userspace, is charged to the process. - fn do_process, C: Chip, const NUM_PROCS: usize>( + fn do_process, C: Chip, const NUM_PROCS: u8>( &self, resources: &KR, chip: &C, process: &dyn process::Process, ipc: Option<&crate::ipc::IPC>, timeslice_us: Option, - ) -> (StoppedExecutingReason, Option) { + ) -> (process::StoppedExecutingReason, Option) { // We must use a dummy scheduler timer if the process should be executed // without any timeslice restrictions. Note, a chip may not provide a // real scheduler timer implementation even if a timeslice is requested. @@ -570,11 +493,13 @@ impl Kernel { // point, the scheduler timer need not have an interrupt enabled after // `start()`. scheduler_timer.reset(); - timeslice_us.map(|timeslice| scheduler_timer.start(timeslice)); + if let Some(timeslice) = timeslice_us { + scheduler_timer.start(timeslice) + } // Need to track why the process is no longer executing so that we can // inform the scheduler. - let mut return_reason = StoppedExecutingReason::NoWorkLeft; + let mut return_reason = process::StoppedExecutingReason::NoWorkLeft; // Since the timeslice counts both the process's execution time and the // time spent in the kernel on behalf of the process (setting it up and @@ -590,7 +515,7 @@ impl Kernel { if stop_running { // Process ran out of time while the kernel was executing. process.debug_timeslice_expired(); - return_reason = StoppedExecutingReason::TimesliceExpired; + return_reason = process::StoppedExecutingReason::TimesliceExpired; break; } @@ -601,7 +526,7 @@ impl Kernel { .continue_process(process.processid(), chip) }; if !continue_process { - return_reason = StoppedExecutingReason::KernelPreemption; + return_reason = process::StoppedExecutingReason::KernelPreemption; break; } @@ -609,7 +534,7 @@ impl Kernel { // try to run it. This case can happen if a process faults and is // stopped, for example. if !process.ready() { - return_reason = StoppedExecutingReason::NoWorkLeft; + return_reason = process::StoppedExecutingReason::NoWorkLeft; break; } @@ -652,7 +577,7 @@ impl Kernel { if scheduler_timer.get_remaining_us().is_none() { // This interrupt was a timeslice expiration. process.debug_timeslice_expired(); - return_reason = StoppedExecutingReason::TimesliceExpired; + return_reason = process::StoppedExecutingReason::TimesliceExpired; break; } // Go to the beginning of loop to determine whether @@ -669,13 +594,19 @@ impl Kernel { } } } - process::State::Yielded | process::State::Unstarted => { + process::State::Yielded => { // If the process is yielded or hasn't been started it is // waiting for a upcall. If there is a task scheduled for // this process go ahead and set the process to execute it. match process.dequeue_task() { None => break, Some(cb) => match cb { + Task::ReturnValue(_) => { + // Per TRD104, Yield-Wait does not wake the + // process for events that generate Null + // Upcalls. + break; + } Task::FunctionCall(ccb) => { if config::CONFIG.trace_syscalls { debug!( @@ -693,10 +624,7 @@ impl Kernel { Task::IPC((otherapp, ipc_type)) => { ipc.map_or_else( || { - assert!( - false, - "Kernel consistency error: IPC Task with no IPC" - ); + panic!("Kernel consistency error: IPC Task with no IPC"); }, |ipc| { // TODO(alevy): this could error for a variety of reasons. @@ -715,16 +643,60 @@ impl Kernel { }, } } - process::State::Faulted | process::State::Terminated => { - // We should never be scheduling a process in fault. - panic!("Attempted to schedule a faulty process"); + process::State::YieldedFor(upcall_id) => { + // If this process is waiting for a specific upcall, see if + // it is ready. If so, dequeue it and return its values to + // the process without scheduling the callback. + match process.remove_upcall(upcall_id) { + None => break, + Some(task) => { + let (a0, a1, a2) = match task { + // There is no callback function registered, we + // just return the values provided by the driver + Task::ReturnValue(rv) => { + if config::CONFIG.trace_syscalls { + debug!( + "[{:?}] Yield-WaitFor: [NU] ({:#x}, {:#x}, {:#x})", + process.processid(), + rv.argument0, + rv.argument1, + rv.argument2, + ); + } + (rv.argument0, rv.argument1, rv.argument2) + } + // There is a registered callback function, but + // since the process used `Yield-WaitFor`, we do + // not execute it, we just return its arguments + // values to the application. + Task::FunctionCall(ccb) => { + if config::CONFIG.trace_syscalls { + debug!( + "[{:?}] Yield-WaitFor [Suppressed function_call @{:#x}] ({:#x}, {:#x}, {:#x}, {:#x})", + process.processid(), + ccb.pc, + ccb.argument0, + ccb.argument1, + ccb.argument2, + ccb.argument3, + ); + } + (ccb.argument0, ccb.argument1, ccb.argument2) + } + Task::IPC(_) => todo!(), + }; + process + .set_syscall_return_value(SyscallReturn::YieldWaitFor(a0, a1, a2)); + } + } } - process::State::StoppedRunning => { - return_reason = StoppedExecutingReason::Stopped; - break; + process::State::Faulted | process::State::Terminated => { + // We should never be scheduling an unrunnable process. + // This is a potential security flaw: panic. + panic!("Attempted to schedule an unrunnable process"); } - process::State::StoppedYielded => { - return_reason = StoppedExecutingReason::Stopped; + process::State::Stopped(_) => { + return_reason = process::StoppedExecutingReason::Stopped; break; } } @@ -732,16 +704,17 @@ impl Kernel { // Check how much time the process used while it was executing, and // return the value so we can provide it to the scheduler. - let time_executed_us = timeslice_us.map_or(None, |timeslice| { - // Note, we cannot call `.get_remaining_us()` again if it has previously - // returned `None`, so we _must_ check the return reason first. - if return_reason == StoppedExecutingReason::TimesliceExpired { + let time_executed_us = timeslice_us.map(|timeslice| { + // Note, we cannot call `.get_remaining_us()` again if it has + // previously returned `None`, so we _must_ check the return reason + // first. + if return_reason == process::StoppedExecutingReason::TimesliceExpired { // used the whole timeslice - Some(timeslice) + timeslice } else { match scheduler_timer.get_remaining_us() { - Some(remaining) => Some(timeslice - remaining), - None => Some(timeslice), // used whole timeslice + Some(remaining) => timeslice - remaining, + None => timeslice, // used whole timeslice } } }); @@ -777,13 +750,14 @@ impl Kernel { // process. // // Filtering a syscall (i.e. blocking the syscall from running) does not - // cause the process to loose its timeslice. The error will be returned + // cause the process to lose its timeslice. The error will be returned // immediately (assuming the process has not already exhausted its // timeslice) allowing the process to decide how to handle the error. match syscall { Syscall::Yield { which: _, - address: _, + param_a: _, + param_b: _, } => {} // Yield is not filterable. Syscall::Exit { which: _, @@ -828,233 +802,267 @@ impl Kernel { } process.set_syscall_return_value(rval); } - Syscall::Yield { which, address } => { + Syscall::Yield { + which, + param_a, + param_b, + } => { if config::CONFIG.trace_syscalls { debug!("[{:?}] yield. which: {}", process.processid(), which); } - if which > (YieldCall::Wait as usize) { - // Only 0 and 1 are valid, so this is not a valid yield - // system call, Yield does not have a return value because - // it can push a function call onto the stack; just return - // control to the process. - return; - } - let wait = which == (YieldCall::Wait as usize); - // If this is a yield-no-wait AND there are no pending tasks, - // then return immediately. Otherwise, go into the yielded state - // and execute tasks now or when they arrive. - let return_now = !wait && !process.has_tasks(); - if return_now { - // Set the "did I trigger upcalls" flag to be 0, return - // immediately. If address is invalid does nothing. - // - // # Safety - // - // This is fine as long as no references to the process's - // memory exist. We do not have a reference, so we can - // safely call `set_byte()`. - unsafe { - process.set_byte(address, 0); - } - } else { - // There are already enqueued upcalls to execute or we - // should wait for them: handle in the next loop iteration - // and set the "did I trigger upcalls" flag to be 1. If - // address is invalid does nothing. - // - // # Safety - // - // This is fine as long as no references to the process's - // memory exist. We do not have a reference, so we can - // safely call `set_byte()`. - unsafe { - process.set_byte(address, 1); - } - process.set_yielded_state(); - } - } - Syscall::Subscribe { - driver_number, - subdriver_number, - upcall_ptr, - appdata, - } => { - // A upcall is identified as a tuple of the driver number and - // the subdriver number. - let upcall_id = UpcallId { - driver_num: driver_number, - subscribe_num: subdriver_number, - }; + match which.try_into() { + Ok(YieldCall::NoWait) => { + // If this is a `Yield-WaitFor` AND there are no pending + // tasks, then return immediately. Otherwise, go into + // the yielded state and execute tasks now or when they + // arrive. + let has_tasks = process.has_tasks(); + + // Set the "did I trigger upcalls" flag. + // If address is invalid does nothing. + // + // # Safety + // + // This is fine as long as no references to the + // process's memory exist. We do not have a reference, + // so we can safely call `set_byte()`. + unsafe { + let address = param_a as *mut u8; + process.set_byte(address, has_tasks as u8); + } - // First check if `upcall_ptr` is null. A null `upcall_ptr` will - // result in `None` here and represents the special - // "unsubscribe" operation. - let ptr = NonNull::new(upcall_ptr); + if has_tasks { + process.set_yielded_state(); + } + } - // For convenience create an `Upcall` type now. This is just a - // data structure and doesn't do any checking or conversion. - let upcall = Upcall::new(process.processid(), upcall_id, appdata, ptr); + Ok(YieldCall::Wait) => { + process.set_yielded_state(); + } - // If `ptr` is not null, we must first verify that the upcall - // function pointer is within process accessible memory. Per - // TRD104: - // - // > If the passed upcall is not valid (is outside process - // > executable memory...), the kernel...MUST immediately return - // > a failure with a error code of `INVALID`. - let rval1 = ptr.map_or(None, |upcall_ptr_nonnull| { - if !process.is_valid_upcall_function_pointer(upcall_ptr_nonnull) { - Some(ErrorCode::INVAL) - } else { - None + Ok(YieldCall::WaitFor) => { + let upcall_id = UpcallId { + driver_num: param_a, + subscribe_num: param_b, + }; + process.set_yielded_for_state(upcall_id); } - }); - // If the upcall is either null or valid, then we continue - // handling the upcall. - let rval = match rval1 { - Some(err) => upcall.into_subscribe_failure(err), - None => { - // At this point we must save the new upcall and return - // the old. The upcalls are stored by the core kernel in - // the grant region so we can guarantee a correct upcall - // swap. However, we do need help with initially - // allocating the grant if this driver has never been - // used before. + _ => { + // Only 0, 1, and 2 are valid, so this is not a valid + // yield system call, Yield does not have a return value + // because it can push a function call onto the stack; + // just return control to the process. + } + } + } + Syscall::Subscribe { driver_number, .. } + | Syscall::Command { driver_number, .. } + | Syscall::ReadWriteAllow { driver_number, .. } + | Syscall::UserspaceReadableAllow { driver_number, .. } + | Syscall::ReadOnlyAllow { driver_number, .. } => { + resources + .syscall_driver_lookup() + .with_driver(driver_number, |driver| match syscall { + Syscall::Subscribe { + driver_number, + subdriver_number, + upcall_ptr, + appdata, + } => { + // A upcall is identified as a tuple of the driver + // number and the subdriver number. + let upcall_id = UpcallId { + driver_num: driver_number, + subscribe_num: subdriver_number, + }; + + // First check if `upcall_ptr` is null. A null + // `upcall_ptr` will result in `None` here and + // represents the special "unsubscribe" operation. + let ptr = NonNull::new(upcall_ptr); + + // For convenience create an `Upcall` type now. This is + // just a data structure and doesn't do any checking or + // conversion. + let upcall = Upcall::new(process.processid(), upcall_id, appdata, ptr); + + // If `ptr` is not null, we must first verify that the + // upcall function pointer is within process accessible + // memory. Per TRD104: // - // To avoid the overhead with checking for process - // liveness and grant allocation, we assume the grant is - // initially allocated. If it turns out it isn't we ask - // the capsule to allocate the grant. - match crate::grant::subscribe(process, upcall) { - Ok(upcall) => upcall.into_subscribe_success(), - Err((upcall, err @ ErrorCode::NOMEM)) => { - // If we get a memory error, we always try to - // allocate the grant since this could be the - // first time the grant is getting accessed. - match try_allocate_grant(resources, driver_number, process) { - AllocResult::NewAllocation => { - // Now we try again. It is possible that - // the capsule did not actually allocate - // the grant, at which point this will - // fail again and we return an error to - // userspace. + // > If the passed upcall is not valid (is outside + // > process executable memory...), the kernel...MUST + // > immediately return a failure with a error code of + // > `INVALID`. + let rval1 = ptr.and_then(|upcall_ptr_nonnull| { + if !process.is_valid_upcall_function_pointer(upcall_ptr_nonnull) { + Some(ErrorCode::INVAL) + } else { + None + } + }); + + // If the upcall is either null or valid, then we + // continue handling the upcall. + let rval = match rval1 { + Some(err) => upcall.into_subscribe_failure(err), + None => { + match driver { + Some(driver) => { + // At this point we must save the new + // upcall and return the old. The + // upcalls are stored by the core kernel + // in the grant region so we can + // guarantee a correct upcall swap. + // However, we do need help with + // initially allocating the grant if + // this driver has never been used + // before. + // + // To avoid the overhead with checking + // for process liveness and grant + // allocation, we assume the grant is + // initially allocated. If it turns out + // it isn't we ask the capsule to + // allocate the grant. match crate::grant::subscribe(process, upcall) { - // An Ok() returns the previous - // upcall, while Err() returns the - // one that was just passed. Ok(upcall) => upcall.into_subscribe_success(), - Err((upcall, err)) => { - upcall.into_subscribe_failure(err) - } - } - } - alloc_failure => { - // We didn't actually create a new - // alloc, so just error. - match (config::CONFIG.trace_syscalls, alloc_failure) { - (true, AllocResult::NoAllocation) => { - debug!("[{:?}] WARN driver #{:x} did not allocate grant", + Err((upcall, err @ ErrorCode::NOMEM)) => { + // If we get a memory error, we + // always try to allocate the + // grant since this could be the + // first time the grant is + // getting accessed. + match try_allocate_grant(driver, process) { + AllocResult::NewAllocation => { + // Now we try again. It + // is possible that the + // capsule did not + // actually allocate the + // grant, at which point + // this will fail again + // and we return an + // error to userspace. + match crate::grant::subscribe( + process, upcall, + ) { + // An Ok() returns + // the previous + // upcall, while + // Err() returns the + // one that was just + // passed. + Ok(upcall) => { + upcall.into_subscribe_success() + } + Err((upcall, err)) => { + upcall.into_subscribe_failure(err) + } + } + } + alloc_failure => { + // We didn't actually + // create a new alloc, + // so just error. + match ( + config::CONFIG.trace_syscalls, + alloc_failure, + ) { + (true, AllocResult::NoAllocation) => { + debug!("[{:?}] WARN driver #{:x} did not allocate grant", process.processid(), driver_number); - } - (true, AllocResult::SameAllocation) => { - debug!("[{:?}] ERROR driver #{:x} allocated wrong grant counts", + } + (true, AllocResult::SameAllocation) => { + debug!("[{:?}] ERROR driver #{:x} allocated wrong grant counts", process.processid(), driver_number); + } + _ => {} + } + upcall.into_subscribe_failure(err) + } + } + } + Err((upcall, err)) => { + upcall.into_subscribe_failure(err) } - _ => {} } - upcall.into_subscribe_failure(err) } + None => upcall.into_subscribe_failure(ErrorCode::NODEVICE), } } - Err((upcall, err)) => upcall.into_subscribe_failure(err), + }; + + // Per TRD104, we only clear upcalls if the subscribe + // will return success. At this point we know the result + // and clear if necessary. + if rval.is_success() { + // Only one upcall should exist per tuple. To ensure + // that there are no pending upcalls with the same + // identifier but with the old function pointer, we + // clear them now. + process.remove_pending_upcalls(upcall_id); } - } - }; - // Per TRD104, we only clear upcalls if the subscribe will - // return success. At this point we know the result and clear if - // necessary. - if rval.is_success() { - // Only one upcall should exist per tuple. To ensure that - // there are no pending upcalls with the same identifier but - // with the old function pointer, we clear them now. - process.remove_pending_upcalls(upcall_id); - } - - if config::CONFIG.trace_syscalls { - debug!( - "[{:?}] subscribe({:#x}, {}, @{:#x}, {:#x}) = {:?}", - process.processid(), - driver_number, - subdriver_number, - upcall_ptr as usize, - appdata, - rval - ); - } - - process.set_syscall_return_value(rval); - } - Syscall::Command { - driver_number, - subdriver_number, - arg0, - arg1, - } => { - let cres = resources - .syscall_driver_lookup() - .with_driver(driver_number, |driver| match driver { - Some(d) => d.command(subdriver_number, arg0, arg1, process.processid()), - None => CommandReturn::failure(ErrorCode::NODEVICE), - }); - - let res = SyscallReturn::from_command_return(cres); + if config::CONFIG.trace_syscalls { + debug!( + "[{:?}] subscribe({:#x}, {}, @{:#x}, {:#x}) = {:?}", + process.processid(), + driver_number, + subdriver_number, + upcall_ptr as usize, + appdata, + rval + ); + } - if config::CONFIG.trace_syscalls { - debug!( - "[{:?}] cmd({:#x}, {}, {:#x}, {:#x}) = {:?}", - process.processid(), + process.set_syscall_return_value(rval); + } + Syscall::Command { driver_number, subdriver_number, arg0, arg1, - res, - ); - } - process.set_syscall_return_value(res); - } - Syscall::ReadWriteAllow { - driver_number, - subdriver_number, - allow_address, - allow_size, - } => { - // Try to create an appropriate [`ReadWriteProcessBuffer`]. This - // method will ensure that the memory in question is located in - // the process-accessible memory space. - let res = match process.build_readwrite_process_buffer(allow_address, allow_size) { - Ok(rw_pbuf) => { - // Creating the [`ReadWriteProcessBuffer`] worked, try - // to set in grant. - match crate::grant::allow_rw( - process, - driver_number, - subdriver_number, - rw_pbuf, - ) { - Ok(rw_pbuf) => { - let (ptr, len) = rw_pbuf.consume(); - SyscallReturn::AllowReadWriteSuccess(ptr, len) - } - Err((rw_pbuf, err @ ErrorCode::NOMEM)) => { - // If we get a memory error, we always try to - // allocate the grant since this could be the - // first time the grant is getting accessed. - match try_allocate_grant(resources, driver_number, process) { - AllocResult::NewAllocation => { - // If we actually allocated a new grant, - // try again and honor the result. + } => { + let cres = match driver { + Some(d) => d.command(subdriver_number, arg0, arg1, process.processid()), + None => CommandReturn::failure(ErrorCode::NODEVICE), + }; + + let res = SyscallReturn::from_command_return(cres); + + if config::CONFIG.trace_syscalls { + debug!( + "[{:?}] cmd({:#x}, {}, {:#x}, {:#x}) = {:?}", + process.processid(), + driver_number, + subdriver_number, + arg0, + arg1, + res, + ); + } + process.set_syscall_return_value(res); + } + Syscall::ReadWriteAllow { + driver_number, + subdriver_number, + allow_address, + allow_size, + } => { + let res = match driver { + Some(driver) => { + // Try to create an appropriate + // [`ReadWriteProcessBuffer`]. This method will + // ensure that the memory in question is located + // in the process-accessible memory space. + match process + .build_readwrite_process_buffer(allow_address, allow_size) + { + Ok(rw_pbuf) => { + // Creating the + // [`ReadWriteProcessBuffer`] worked, + // try to set in grant. match crate::grant::allow_rw( process, driver_number, @@ -1065,167 +1073,201 @@ impl Kernel { let (ptr, len) = rw_pbuf.consume(); SyscallReturn::AllowReadWriteSuccess(ptr, len) } + Err((rw_pbuf, err @ ErrorCode::NOMEM)) => { + // If we get a memory error, we + // always try to allocate the + // grant since this could be the + // first time the grant is + // getting accessed. + match try_allocate_grant(driver, process) { + AllocResult::NewAllocation => { + // If we actually + // allocated a new + // grant, try again and + // honor the result. + match crate::grant::allow_rw( + process, + driver_number, + subdriver_number, + rw_pbuf, + ) { + Ok(rw_pbuf) => { + let (ptr, len) = rw_pbuf.consume(); + SyscallReturn::AllowReadWriteSuccess( + ptr, len, + ) + } + Err((rw_pbuf, err)) => { + let (ptr, len) = rw_pbuf.consume(); + SyscallReturn::AllowReadWriteFailure( + err, ptr, len, + ) + } + } + } + alloc_failure => { + // We didn't actually + // create a new alloc, + // so just error. + match ( + config::CONFIG.trace_syscalls, + alloc_failure, + ) { + (true, AllocResult::NoAllocation) => { + debug!("[{:?}] WARN driver #{:x} did not allocate grant", + process.processid(), driver_number); + } + (true, AllocResult::SameAllocation) => { + debug!("[{:?}] ERROR driver #{:x} allocated wrong grant counts", + process.processid(), driver_number); + } + _ => {} + } + let (ptr, len) = rw_pbuf.consume(); + SyscallReturn::AllowReadWriteFailure( + err, ptr, len, + ) + } + } + } Err((rw_pbuf, err)) => { let (ptr, len) = rw_pbuf.consume(); SyscallReturn::AllowReadWriteFailure(err, ptr, len) } } } - alloc_failure => { - // We didn't actually create a new - // alloc, so just error. - match (config::CONFIG.trace_syscalls, alloc_failure) { - (true, AllocResult::NoAllocation) => { - debug!("[{:?}] WARN driver #{:x} did not allocate grant", - process.processid(), driver_number); - } - (true, AllocResult::SameAllocation) => { - debug!("[{:?}] ERROR driver #{:x} allocated wrong grant counts", - process.processid(), driver_number); - } - _ => {} - } - let (ptr, len) = rw_pbuf.consume(); - SyscallReturn::AllowReadWriteFailure(err, ptr, len) + Err(allow_error) => { + // There was an error creating the + // [`ReadWriteProcessBuffer`]. Report + // back to the process with the original + // parameters. + SyscallReturn::AllowReadWriteFailure( + allow_error, + allow_address, + allow_size, + ) } } } - Err((rw_pbuf, err)) => { - let (ptr, len) = rw_pbuf.consume(); - SyscallReturn::AllowReadWriteFailure(err, ptr, len) - } + None => SyscallReturn::AllowReadWriteFailure( + ErrorCode::NODEVICE, + allow_address, + allow_size, + ), + }; + + if config::CONFIG.trace_syscalls { + debug!( + "[{:?}] read-write allow({:#x}, {}, @{:#x}, {}) = {:?}", + process.processid(), + driver_number, + subdriver_number, + allow_address as usize, + allow_size, + res + ); } + process.set_syscall_return_value(res); } - Err(allow_error) => { - // There was an error creating the - // [`ReadWriteProcessBuffer`]. Report back to the - // process with the original parameters. - SyscallReturn::AllowReadWriteFailure(allow_error, allow_address, allow_size) - } - }; - - if config::CONFIG.trace_syscalls { - debug!( - "[{:?}] read-write allow({:#x}, {}, @{:#x}, {}) = {:?}", - process.processid(), + Syscall::UserspaceReadableAllow { driver_number, subdriver_number, - allow_address as usize, + allow_address, allow_size, - res - ); - } - process.set_syscall_return_value(res); - } - Syscall::UserspaceReadableAllow { - driver_number, - subdriver_number, - allow_address, - allow_size, - } => { - let res = resources - .syscall_driver_lookup() - .with_driver(driver_number, |driver| match driver { - Some(d) => { - // Try to create an appropriate - // [`UserspaceReadableProcessBuffer`]. This method - // will ensure that the memory in question is - // located in the process-accessible memory space. - match process.build_readwrite_process_buffer(allow_address, allow_size) - { - Ok(rw_pbuf) => { - // Creating the - // [`UserspaceReadableProcessBuffer`] - // worked, provide it to the capsule. - match d.allow_userspace_readable( - process.processid(), - subdriver_number, - rw_pbuf, - ) { - Ok(returned_pbuf) => { - // The capsule has accepted the - // allow operation. Pass the - // previous buffer information back - // to the process. - let (ptr, len) = returned_pbuf.consume(); - SyscallReturn::UserspaceReadableAllowSuccess(ptr, len) - } - Err((rejected_pbuf, err)) => { - // The capsule has rejected the - // allow operation. Pass the new - // buffer information back to the - // process. - let (ptr, len) = rejected_pbuf.consume(); - SyscallReturn::UserspaceReadableAllowFailure( - err, ptr, len, - ) + } => { + let res = match driver { + Some(d) => { + // Try to create an appropriate + // [`UserspaceReadableProcessBuffer`]. This + // method will ensure that the memory in + // question is located in the process-accessible + // memory space. + match process + .build_readwrite_process_buffer(allow_address, allow_size) + { + Ok(rw_pbuf) => { + // Creating the + // [`UserspaceReadableProcessBuffer`] + // worked, provide it to the capsule. + match d.allow_userspace_readable( + process.processid(), + subdriver_number, + rw_pbuf, + ) { + Ok(returned_pbuf) => { + // The capsule has accepted the + // allow operation. Pass the + // previous buffer information + // back to the process. + let (ptr, len) = returned_pbuf.consume(); + SyscallReturn::UserspaceReadableAllowSuccess( + ptr, len, + ) + } + Err((rejected_pbuf, err)) => { + // The capsule has rejected the + // allow operation. Pass the new + // buffer information back to + // the process. + let (ptr, len) = rejected_pbuf.consume(); + SyscallReturn::UserspaceReadableAllowFailure( + err, ptr, len, + ) + } } } - } - Err(allow_error) => { - // There was an error creating the - // [`UserspaceReadableProcessBuffer`]. - // Report back to the process. - SyscallReturn::UserspaceReadableAllowFailure( - allow_error, - allow_address, - allow_size, - ) + Err(allow_error) => { + // There was an error creating the + // [`UserspaceReadableProcessBuffer`]. + // Report back to the process. + SyscallReturn::UserspaceReadableAllowFailure( + allow_error, + allow_address, + allow_size, + ) + } } } - } - - None => SyscallReturn::UserspaceReadableAllowFailure( - ErrorCode::NODEVICE, - allow_address, - allow_size, - ), - }); - if config::CONFIG.trace_syscalls { - debug!( - "[{:?}] userspace readable allow({:#x}, {}, @{:#x}, {}) = {:?}", - process.processid(), + None => SyscallReturn::UserspaceReadableAllowFailure( + ErrorCode::NODEVICE, + allow_address, + allow_size, + ), + }; + + if config::CONFIG.trace_syscalls { + debug!( + "[{:?}] userspace readable allow({:#x}, {}, @{:#x}, {}) = {:?}", + process.processid(), + driver_number, + subdriver_number, + allow_address as usize, + allow_size, + res + ); + } + process.set_syscall_return_value(res); + } + Syscall::ReadOnlyAllow { driver_number, subdriver_number, - allow_address as usize, + allow_address, allow_size, - res - ); - } - process.set_syscall_return_value(res); - } - Syscall::ReadOnlyAllow { - driver_number, - subdriver_number, - allow_address, - allow_size, - } => { - // Try to create an appropriate [`ReadOnlyProcessBuffer`]. This - // method will ensure that the memory in question is located in - // the process-accessible memory space. - let res = match process.build_readonly_process_buffer(allow_address, allow_size) { - Ok(ro_pbuf) => { - // Creating the [`ReadOnlyProcessBuffer`] worked, try to - // set in grant. - match crate::grant::allow_ro( - process, - driver_number, - subdriver_number, - ro_pbuf, - ) { - Ok(ro_pbuf) => { - let (ptr, len) = ro_pbuf.consume(); - SyscallReturn::AllowReadOnlySuccess(ptr, len) - } - Err((ro_pbuf, err @ ErrorCode::NOMEM)) => { - // If we get a memory error, we always try to - // allocate the grant since this could be the - // first time the grant is getting accessed. - match try_allocate_grant(resources, driver_number, process) { - AllocResult::NewAllocation => { - // If we actually allocated a new grant, - // try again and honor the result. + } => { + let res = match driver { + Some(driver) => { + // Try to create an appropriate + // [`ReadOnlyProcessBuffer`]. This method will + // ensure that the memory in question is located + // in the process-accessible memory space. + match process + .build_readonly_process_buffer(allow_address, allow_size) + { + Ok(ro_pbuf) => { + // Creating the + // [`ReadOnlyProcessBuffer`] worked, try + // to set in grant. match crate::grant::allow_ro( process, driver_number, @@ -1236,58 +1278,111 @@ impl Kernel { let (ptr, len) = ro_pbuf.consume(); SyscallReturn::AllowReadOnlySuccess(ptr, len) } + Err((ro_pbuf, err @ ErrorCode::NOMEM)) => { + // If we get a memory error, we + // always try to allocate the + // grant since this could be the + // first time the grant is + // getting accessed. + match try_allocate_grant(driver, process) { + AllocResult::NewAllocation => { + // If we actually + // allocated a new + // grant, try again and + // honor the result. + match crate::grant::allow_ro( + process, + driver_number, + subdriver_number, + ro_pbuf, + ) { + Ok(ro_pbuf) => { + let (ptr, len) = ro_pbuf.consume(); + SyscallReturn::AllowReadOnlySuccess( + ptr, len, + ) + } + Err((ro_pbuf, err)) => { + let (ptr, len) = ro_pbuf.consume(); + SyscallReturn::AllowReadOnlyFailure( + err, ptr, len, + ) + } + } + } + alloc_failure => { + // We didn't actually + // create a new alloc, + // so just error. + match ( + config::CONFIG.trace_syscalls, + alloc_failure, + ) { + (true, AllocResult::NoAllocation) => { + debug!("[{:?}] WARN driver #{:x} did not allocate grant", + process.processid(), driver_number); + } + (true, AllocResult::SameAllocation) => { + debug!("[{:?}] ERROR driver #{:x} allocated wrong grant counts", + process.processid(), driver_number); + } + _ => {} + } + let (ptr, len) = ro_pbuf.consume(); + SyscallReturn::AllowReadOnlyFailure( + err, ptr, len, + ) + } + } + } Err((ro_pbuf, err)) => { let (ptr, len) = ro_pbuf.consume(); SyscallReturn::AllowReadOnlyFailure(err, ptr, len) } } } - alloc_failure => { - // We didn't actually create a new - // alloc, so just error. - match (config::CONFIG.trace_syscalls, alloc_failure) { - (true, AllocResult::NoAllocation) => { - debug!("[{:?}] WARN driver #{:x} did not allocate grant", - process.processid(), driver_number); - } - (true, AllocResult::SameAllocation) => { - debug!("[{:?}] ERROR driver #{:x} allocated wrong grant counts", - process.processid(), driver_number); - } - _ => {} - } - let (ptr, len) = ro_pbuf.consume(); - SyscallReturn::AllowReadOnlyFailure(err, ptr, len) + Err(allow_error) => { + // There was an error creating the + // [`ReadOnlyProcessBuffer`]. Report + // back to the process with the original + // parameters. + SyscallReturn::AllowReadOnlyFailure( + allow_error, + allow_address, + allow_size, + ) } } } - Err((ro_pbuf, err)) => { - let (ptr, len) = ro_pbuf.consume(); - SyscallReturn::AllowReadOnlyFailure(err, ptr, len) - } + None => SyscallReturn::AllowReadOnlyFailure( + ErrorCode::NODEVICE, + allow_address, + allow_size, + ), + }; + + if config::CONFIG.trace_syscalls { + debug!( + "[{:?}] read-only allow({:#x}, {}, @{:#x}, {}) = {:?}", + process.processid(), + driver_number, + subdriver_number, + allow_address as usize, + allow_size, + res + ); } - } - Err(allow_error) => { - // There was an error creating the - // [`ReadOnlyProcessBuffer`]. Report back to the process - // with the original parameters. - SyscallReturn::AllowReadOnlyFailure(allow_error, allow_address, allow_size) - } - }; - if config::CONFIG.trace_syscalls { - debug!( - "[{:?}] read-only allow({:#x}, {}, @{:#x}, {}) = {:?}", - process.processid(), - driver_number, - subdriver_number, - allow_address as usize, - allow_size, - res - ); - } - - process.set_syscall_return_value(res); + process.set_syscall_return_value(res); + } + Syscall::Yield { .. } + | Syscall::Exit { .. } + | Syscall::Memop { .. } => { + // These variants must not be reachable due to the outer + // match statement: + debug_assert!(false, "Kernel system call handling invariant violated!"); + }, + }) } Syscall::Exit { which, diff --git a/kernel/src/lib.rs b/kernel/src/lib.rs index 4429561907..65b8443cd8 100644 --- a/kernel/src/lib.rs +++ b/kernel/src/lib.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Core Tock Kernel //! //! The kernel crate implements the core features of Tock as well as shared @@ -85,20 +89,26 @@ //! this use case. It is likely we will have to create new interfaces as new //! use cases are discovered. -#![feature(core_intrinsics, const_fn_trait_bound)] #![warn(unreachable_pub)] #![no_std] -// Define the kernel major and minor versions -pub const MAJOR: u16 = 2; -pub const MINOR: u16 = 0; +/// Kernel major version. +/// +/// This is compiled with the crate to enable for checking of compatibility with +/// loaded apps. Both major and minor version constants are updated during a +/// release. +pub const KERNEL_MAJOR_VERSION: u16 = 2; +/// Kernel minor version. +/// +/// This is compiled with the crate to enable for checking of compatibility with +/// loaded apps. +pub const KERNEL_MINOR_VERSION: u16 = 1; pub mod capabilities; pub mod collections; pub mod component; pub mod debug; pub mod deferred_call; -pub mod dynamic_deferred_call; pub mod errorcode; pub mod grant; pub mod hil; @@ -106,8 +116,10 @@ pub mod introspection; pub mod ipc; pub mod platform; pub mod process; +pub mod process_checker; pub mod processbuffer; pub mod scheduler; +pub mod storage_permissions; pub mod syscall; pub mod upcall; pub mod utilities; @@ -115,10 +127,11 @@ pub mod utilities; mod config; mod kernel; mod memop; +mod process_binary; +mod process_loading; mod process_policies; mod process_printer; mod process_standard; -mod process_utilities; mod syscall_driver; // Core resources exposed as `kernel::Type`. diff --git a/kernel/src/memop.rs b/kernel/src/memop.rs index 51d89f37c3..35d4cf9177 100644 --- a/kernel/src/memop.rs +++ b/kernel/src/memop.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of the MEMOP family of syscalls. use crate::process::Process; @@ -40,18 +44,16 @@ use crate::ErrorCode; pub(crate) fn memop(process: &dyn Process, op_type: usize, r1: usize) -> SyscallReturn { match op_type { // Op Type 0: BRK - 0 /* BRK */ => { - process.brk(r1 as *const u8) - .map(|_| SyscallReturn::Success) - .unwrap_or(SyscallReturn::Failure(ErrorCode::NOMEM)) - }, + 0 => process + .brk(r1 as *const u8) + .map(|_| SyscallReturn::Success) + .unwrap_or(SyscallReturn::Failure(ErrorCode::NOMEM)), // Op Type 1: SBRK - 1 /* SBRK */ => { - process.sbrk(r1 as isize) - .map(|addr| SyscallReturn::SuccessU32(addr as u32)) - .unwrap_or(SyscallReturn::Failure(ErrorCode::NOMEM)) - }, + 1 => process + .sbrk(r1 as isize) + .map(|addr| SyscallReturn::SuccessU32(addr as u32)) + .unwrap_or(SyscallReturn::Failure(ErrorCode::NOMEM)), // Op Type 2: Process memory start 2 => SyscallReturn::SuccessU32(process.get_addresses().sram_start as u32), diff --git a/kernel/src/platform/chip.rs b/kernel/src/platform/chip.rs index ef0a10d575..235c5262d3 100644 --- a/kernel/src/platform/chip.rs +++ b/kernel/src/platform/chip.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interfaces for implementing microcontrollers in Tock. use crate::platform::mpu; @@ -66,7 +70,7 @@ pub trait Chip { unsafe fn print_state(&self, writer: &mut dyn Write); } -/// Interface for handling interrupts and deferred calls on a hardware chip. +/// Interface for handling interrupts on a hardware chip. /// /// Each board must construct an implementation of this trait to handle specific /// interrupts. When an interrupt (identified by number) has triggered and @@ -102,13 +106,10 @@ pub trait Chip { /// where the kernel instructs the `nrf52` crate to handle interrupts, and if /// there is an interrupt ready then that interrupt is passed through the /// InterruptService objects until something can service it. -pub trait InterruptService { +pub trait InterruptService { /// Service an interrupt, if supported by this chip. If this interrupt /// number is not supported, return false. unsafe fn service_interrupt(&self, interrupt: u32) -> bool; - - /// Service a deferred call. If this task is not supported, return false. - unsafe fn service_deferred_call(&self, task: T) -> bool; } /// Generic operations that clock-like things are expected to support. @@ -130,4 +131,4 @@ impl ClockInterface for NoClockControl { /// Instance of NoClockControl for things that need references to /// `ClockInterface` objects. -pub static mut NO_CLOCK_CONTROL: NoClockControl = NoClockControl {}; +pub const NO_CLOCK_CONTROL: NoClockControl = NoClockControl {}; diff --git a/kernel/src/platform/mod.rs b/kernel/src/platform/mod.rs index 5c24751408..4b81bb9961 100644 --- a/kernel/src/platform/mod.rs +++ b/kernel/src/platform/mod.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Traits for implementing various layers and components in Tock. //! //! Implementations of these traits are used by the core kernel. diff --git a/kernel/src/platform/mpu.rs b/kernel/src/platform/mpu.rs index 5a55f72a2d..2ae36e4555 100644 --- a/kernel/src/platform/mpu.rs +++ b/kernel/src/platform/mpu.rs @@ -1,11 +1,14 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for configuring the Memory Protection Unit. -use crate::process::ProcessId; use core::cmp; use core::fmt::{self, Display}; /// User mode access permissions. -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug)] pub enum Permissions { ReadWriteExecute, ReadWriteOnly, @@ -34,8 +37,8 @@ impl Region { /// Create a new MPU region with a given starting point and length in bytes. pub fn new(start_address: *const u8, size: usize) -> Region { Region { - start_address: start_address, - size: size, + start_address, + size, } } @@ -53,8 +56,7 @@ impl Region { /// Null type for the default type of the `MpuConfig` type in an implementation /// of the `MPU` trait. This custom type allows us to implement `Display` with /// an empty implementation to meet the constraint on `type MpuConfig`. -#[derive(Default)] -pub struct MpuConfigDefault {} +pub struct MpuConfigDefault; impl Display for MpuConfigDefault { fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -88,21 +90,13 @@ pub trait MPU { /// It is `Default` so we can create empty state when the process is /// created, and `Display` so that the `panic!()` output can display the /// current state to help with debugging. - type MpuConfig: Default + Display; - - /// Clears the MPU. - /// - /// This function will clear any access control enforced by the - /// MPU where possible. - /// On some hardware it is impossible to reset the MPU after it has - /// been locked, in this case this function wont change those regions. - fn clear_mpu(&self) {} + type MpuConfig: Display; /// Enables the MPU for userspace apps. /// /// This function must enable the permission restrictions on the various /// regions protected by the MPU. - fn enable_app_mpu(&self) {} + fn enable_app_mpu(&self); /// Disables the MPU for userspace apps. /// @@ -112,12 +106,27 @@ pub trait MPU { /// platforms the MPU rules apply to privileged code as well, and therefore /// some of the MPU configuration must be disabled for the kernel to effectively /// manage processes. - fn disable_app_mpu(&self) {} + fn disable_app_mpu(&self); /// Returns the maximum number of regions supported by the MPU. - fn number_total_regions(&self) -> usize { - 0 - } + fn number_total_regions(&self) -> usize; + + /// Creates a new empty MPU configuration. + /// + /// The returned configuration must not have any userspace-accessible + /// regions pre-allocated. + /// + /// The underlying implementation may only be able to allocate a finite + /// number of MPU configurations. It may return `None` if this resource is + /// exhausted. + fn new_config(&self) -> Option; + + /// Resets an MPU configuration. + /// + /// This method resets an MPU configuration to its initial state, as + /// returned by [`MPU::new_config`]. After invoking this operation, it must + /// not have any userspace-acessible regions pre-allocated. + fn reset_config(&self, config: &mut Self::MpuConfig); /// Allocates a new MPU region. /// @@ -139,7 +148,6 @@ pub trait MPU { /// /// Returns the start and size of the allocated MPU region. If it is /// infeasible to allocate the MPU region, returns None. - #[allow(unused_variables)] fn allocate_region( &self, unallocated_memory_start: *const u8, @@ -147,13 +155,7 @@ pub trait MPU { min_region_size: usize, permissions: Permissions, config: &mut Self::MpuConfig, - ) -> Option { - if min_region_size > unallocated_memory_size { - None - } else { - Some(Region::new(unallocated_memory_start, min_region_size)) - } - } + ) -> Option; /// Removes an MPU region within app-owned memory. /// @@ -170,10 +172,7 @@ pub trait MPU { /// # Return Value /// /// Returns an error if the specified region is not exactly mapped to the process as specified - #[allow(unused_variables)] - fn remove_memory_region(&self, region: Region, config: &mut Self::MpuConfig) -> Result<(), ()> { - Ok(()) - } + fn remove_memory_region(&self, region: Region, config: &mut Self::MpuConfig) -> Result<(), ()>; /// Chooses the location for a process's memory, and allocates an MPU region /// covering the app-owned part. @@ -213,7 +212,6 @@ pub trait MPU { /// chosen for the process. If it is infeasible to find a memory block or /// allocate the MPU region, or if the function has already been called, /// returns None. If None is returned no changes are made. - #[allow(unused_variables)] fn allocate_app_memory_region( &self, unallocated_memory_start: *const u8, @@ -223,17 +221,7 @@ pub trait MPU { initial_kernel_memory_size: usize, permissions: Permissions, config: &mut Self::MpuConfig, - ) -> Option<(*const u8, usize)> { - let memory_size = cmp::max( - min_memory_size, - initial_app_memory_size + initial_kernel_memory_size, - ); - if memory_size > unallocated_memory_size { - None - } else { - Some((unallocated_memory_start, memory_size)) - } - } + ) -> Option<(*const u8, usize)>; /// Updates the MPU region for app-owned memory. /// @@ -253,20 +241,13 @@ pub trait MPU { /// Returns an error if it is infeasible to update the MPU region, or if it /// was never created. If an error is returned no changes are made to the /// configuration. - #[allow(unused_variables)] fn update_app_memory_region( &self, app_memory_break: *const u8, kernel_memory_break: *const u8, permissions: Permissions, config: &mut Self::MpuConfig, - ) -> Result<(), ()> { - if (app_memory_break as usize) > (kernel_memory_break as usize) { - Err(()) - } else { - Ok(()) - } - } + ) -> Result<(), ()>; /// Configures the MPU with the provided region configuration. /// @@ -277,86 +258,84 @@ pub trait MPU { /// # Arguments /// /// - `config`: MPU region configuration - /// - `app_id`: ProcessId of the process that the MPU is configured for - #[allow(unused_variables)] - fn configure_mpu(&self, config: &Self::MpuConfig, app_id: &ProcessId) {} + fn configure_mpu(&self, config: &Self::MpuConfig); } /// Implement default MPU trait for unit. impl MPU for () { type MpuConfig = MpuConfigDefault; -} -/// The generic trait that particular kernel level memory protection unit -/// implementations need to implement. -/// -/// This trait provides generic functionality to extend the MPU trait above -/// to also allow the kernel to protect itself. It is expected that only a -/// limited number of SoCs can support this, which is why it is a seperate -/// implementation. -pub trait KernelMPU { - /// MPU-specific state that defines a particular configuration for the kernel - /// MPU. - /// That is, this should contain all of the required state such that the - /// implementation can be passed an object of this type and it should be - /// able to correctly and entirely configure the MPU. - /// - /// It is `Default` so we can create empty state when the kernel is - /// created, and `Display` so that the `panic!()` output can display the - /// current state to help with debugging. - type KernelMpuConfig: Default + Display; + fn enable_app_mpu(&self) {} - /// Mark a region of memory that the Tock kernel owns. - /// - /// This function will optionally set the MPU to enforce the specified - /// constraints for all accessess (even from the kernel). - /// This should be used to mark read/write/execute areas of the Tock - /// kernel to have the hardware enforce those permissions. - /// - /// If the KernelMPU trait is supported a board should use this function - /// to set permissions for all areas of memory the kernel will use. - /// Once all regions of memory have been allocated, the board must call - /// enable_kernel_mpu(). After enable_kernel_mpu() is called no changes - /// to kernel level code permissions can be made. - /// - /// Note that kernel level permissions also apply to apps, although apps - /// will have more constraints applied on top of the kernel ones as - /// specified by the `MPU` trait. - /// - /// Not all architectures support this, so don't assume this will be - /// implemented. - /// - /// # Arguments - /// - /// - `memory_start`: start of memory region - /// - `memory_size`: size of unallocated memory - /// - `permissions`: permissions for the region - /// - `config`: MPU region configuration - /// - /// # Return Value - /// - /// Returns the start and size of the requested memory region. If it is - /// infeasible to allocate the MPU region, returns None. If None is - /// returned no changes are made. - #[allow(unused_variables)] - fn allocate_kernel_region( + fn disable_app_mpu(&self) {} + + fn number_total_regions(&self) -> usize { + 0 + } + + fn new_config(&self) -> Option { + Some(MpuConfigDefault) + } + + fn reset_config(&self, _config: &mut Self::MpuConfig) {} + + fn allocate_region( &self, - memory_start: *const u8, - memory_size: usize, - permissions: Permissions, - config: &mut Self::KernelMpuConfig, - ) -> Option; + unallocated_memory_start: *const u8, + unallocated_memory_size: usize, + min_region_size: usize, + _permissions: Permissions, + _config: &mut Self::MpuConfig, + ) -> Option { + if min_region_size > unallocated_memory_size { + None + } else { + Some(Region::new(unallocated_memory_start, min_region_size)) + } + } - /// Enables the MPU for the kernel. - /// - /// This function must enable the permission restrictions on the various - /// kernel regions specified by `allocate_kernel_region()` protected by - /// the MPU. - /// - /// It is expected that this function is called in `main()`. - /// - /// Once enabled this cannot be disabled. It is expected there won't be any - /// changes to the kernel regions after this is enabled. - #[allow(unused_variables)] - fn enable_kernel_mpu(&self, config: &mut Self::KernelMpuConfig); + fn remove_memory_region( + &self, + _region: Region, + _config: &mut Self::MpuConfig, + ) -> Result<(), ()> { + Ok(()) + } + + fn allocate_app_memory_region( + &self, + unallocated_memory_start: *const u8, + unallocated_memory_size: usize, + min_memory_size: usize, + initial_app_memory_size: usize, + initial_kernel_memory_size: usize, + _permissions: Permissions, + _config: &mut Self::MpuConfig, + ) -> Option<(*const u8, usize)> { + let memory_size = cmp::max( + min_memory_size, + initial_app_memory_size + initial_kernel_memory_size, + ); + if memory_size > unallocated_memory_size { + None + } else { + Some((unallocated_memory_start, memory_size)) + } + } + + fn update_app_memory_region( + &self, + app_memory_break: *const u8, + kernel_memory_break: *const u8, + _permissions: Permissions, + _config: &mut Self::MpuConfig, + ) -> Result<(), ()> { + if (app_memory_break as usize) > (kernel_memory_break as usize) { + Err(()) + } else { + Ok(()) + } + } + + fn configure_mpu(&self, _config: &Self::MpuConfig) {} } diff --git a/kernel/src/platform/platform.rs b/kernel/src/platform/platform.rs index 2c9f3d634a..06e036a636 100644 --- a/kernel/src/platform/platform.rs +++ b/kernel/src/platform/platform.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interfaces for implementing boards in Tock. use crate::errorcode; @@ -54,6 +58,10 @@ pub trait KernelResources { /// this platform wants the kernel to use. fn process_fault(&self) -> &Self::ProcessFault; + /// Returns a reference to the implementation of the ContextSwitchCallback + /// for this platform. + fn context_switch_callback(&self) -> &Self::ContextSwitchCallback; + /// Returns a reference to the implementation of the Scheduler this platform /// wants the kernel to use. fn scheduler(&self) -> &Self::Scheduler; @@ -65,10 +73,6 @@ pub trait KernelResources { /// Returns a reference to the implementation of the WatchDog on this /// platform. fn watchdog(&self) -> &Self::WatchDog; - - /// Returns a reference to the implementation of the ContextSwitchCallback - /// for this platform. - fn context_switch_callback(&self) -> &Self::ContextSwitchCallback; } /// Configure the system call dispatch mapping. @@ -138,11 +142,10 @@ impl SyscallFilter for () {} /// An allow list system call filter based on the TBF header, with a default /// allow all fallback. /// -/// This will check if the process has TbfHeaderPermissions specified. -/// If the process has TbfHeaderPermissions they will be used to determine -/// access permissions. For details on this see the TockBinaryFormat -/// documentation. -/// If no permissions are specified the default is to allow the syscall. +/// This will check if the process has TbfHeaderPermissions specified. If the +/// process has TbfHeaderPermissions they will be used to determine access +/// permissions. For details on this see the TockBinaryFormat documentation. If +/// no permissions are specified the default is to allow the syscall. pub struct TbfHeaderFilterDefaultAllow {} /// Implement default SyscallFilter trait for filtering based on the TBF header. @@ -231,35 +234,35 @@ pub trait ProcessFault { /// This function is called when an app faults. /// /// This is an optional function that can be implemented by `Platform`s that - /// allows the chip to handle the app fault and not terminate or restart - /// the app. + /// allows the chip to handle the app fault and not terminate or restart the + /// app. /// /// If `Ok(())` is returned by this function then the kernel will not - /// terminate or restart the app, but instead allow it to continue - /// running. NOTE in this case the chip must have fixed the underlying - /// reason for fault otherwise it will re-occur. + /// terminate or restart the app, but instead allow it to continue running. + /// NOTE in this case the chip must have fixed the underlying reason for + /// fault otherwise it will re-occur. /// - /// This can not be used for apps to circumvent Tock's protections. If - /// for example this function just ignored the error and allowed the app - /// to continue the fault would continue to occur. + /// This can not be used for apps to circumvent Tock's protections. If for + /// example this function just ignored the error and allowed the app to + /// continue the fault would continue to occur. /// - /// If `Err(())` is returned then the kernel will set the app as faulted - /// and follow the `FaultResponse` protocol. + /// If `Err(())` is returned then the kernel will set the app as faulted and + /// follow the `FaultResponse` protocol. /// - /// It is unlikey a `Platform` will need to implement this. This should be used - /// for only a handul of use cases. Possible use cases include: - /// - Allowing the kernel to emulate unimplemented instructions - /// This could be used to allow apps to run on hardware that doesn't + /// It is unlikely a `Platform` will need to implement this. This should be + /// used for only a handful of use cases. Possible use cases include: + /// - Allowing the kernel to emulate unimplemented instructions This + /// could be used to allow apps to run on hardware that doesn't /// implement some instructions, for example atomics. /// - Allow the kernel to handle hardware faults, triggered by the app. /// This can allow an app to continue running if it triggers certain /// types of faults. For example if an app triggers a memory parity - /// error the kernel can handle the error and allow the app to - /// continue (or not). - /// - Allow an app to execute from external QSPI. - /// This could be used to allow an app to execute from external QSPI - /// where access faults can be handled by the `Platform` to ensure the - /// QPSI is mapped correctly. + /// error the kernel can handle the error and allow the app to continue + /// (or not). + /// - Allow an app to execute from external QSPI. This could be used to + /// allow an app to execute from external QSPI where access faults can + /// be handled by the `Platform` to ensure the QPSI is mapped + /// correctly. #[allow(unused_variables)] fn process_fault_hook(&self, process: &dyn process::Process) -> Result<(), ()> { Err(()) diff --git a/kernel/src/platform/scheduler_timer.rs b/kernel/src/platform/scheduler_timer.rs index b067b81506..05a0c33d64 100644 --- a/kernel/src/platform/scheduler_timer.rs +++ b/kernel/src/platform/scheduler_timer.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Scheduler Timer for enforcing Process Timeslices //! //! Interface for use by the Kernel to configure timers which can preempt diff --git a/kernel/src/platform/watchdog.rs b/kernel/src/platform/watchdog.rs index c3561f0905..3db5282a38 100644 --- a/kernel/src/platform/watchdog.rs +++ b/kernel/src/platform/watchdog.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for configuring a watchdog /// A trait for implementing a watchdog in the kernel. diff --git a/kernel/src/process.rs b/kernel/src/process.rs index 9424fdc0dc..a0f16c77a7 100644 --- a/kernel/src/process.rs +++ b/kernel/src/process.rs @@ -1,8 +1,12 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Types for Tock-compatible processes. -use core::cell::Cell; use core::fmt; use core::fmt::Write; +use core::num::NonZeroU32; use core::ptr::NonNull; use core::str; @@ -12,26 +16,29 @@ use crate::ipc; use crate::kernel::Kernel; use crate::platform::mpu::{self}; use crate::processbuffer::{ReadOnlyProcessBuffer, ReadWriteProcessBuffer}; +use crate::storage_permissions; use crate::syscall::{self, Syscall, SyscallReturn}; use crate::upcall::UpcallId; use tock_tbf::types::CommandPermissions; // Export all process related types via `kernel::process::`. -pub use crate::process_policies::{ - PanicFaultPolicy, ProcessFaultPolicy, RestartFaultPolicy, StopFaultPolicy, - StopWithDebugFaultPolicy, ThresholdRestartFaultPolicy, ThresholdRestartThenPanicFaultPolicy, -}; -pub use crate::process_printer::{ProcessPrinter, ProcessPrinterContext, ProcessPrinterText}; +pub use crate::process_binary::ProcessBinary; +pub use crate::process_checker::{ProcessCheckerMachine, ProcessCheckerMachineClient}; +pub use crate::process_loading::load_processes; +pub use crate::process_loading::ProcessLoadError; +pub use crate::process_loading::SequentialProcessLoaderMachine; +pub use crate::process_loading::{ProcessLoadingAsync, ProcessLoadingAsyncClient}; +pub use crate::process_policies::ProcessFaultPolicy; +pub use crate::process_printer::{ProcessPrinter, ProcessPrinterContext}; pub use crate::process_standard::ProcessStandard; -pub use crate::process_utilities::{load_processes, load_processes_advanced, ProcessLoadError}; /// Userspace process identifier. /// -/// This should be treated as an opaque type that can be used to represent a -/// process on the board without requiring an actual reference to a `Process` -/// object. Having this `ProcessId` reference type is useful for managing -/// ownership and type issues in Rust, but more importantly `ProcessId` serves -/// as a tool for capsules to hold pointers to applications. +/// This is an opaque type that can be used to represent a running process on +/// the board without requiring an actual reference to a `Process` object. +/// Having this `ProcessId` reference type is useful for managing ownership and +/// type issues in Rust, but more importantly `ProcessId` serves as a tool for +/// capsules to hold pointers to applications. /// /// Since `ProcessId` implements `Copy`, having an `ProcessId` does _not_ ensure /// that the process the `ProcessId` refers to is still valid. The process may @@ -91,8 +98,25 @@ impl PartialEq for ProcessId { impl Eq for ProcessId {} impl fmt::Debug for ProcessId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.identifier) + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + // We handle alignment and width. + if let Some(width) = formatter.width() { + match formatter.align() { + Some(fmt::Alignment::Left) => { + write!(formatter, "{: { + write!(formatter, "{:width$}", self.identifier, width = width) + } + Some(fmt::Alignment::Center) => { + write!(formatter, "{:^width$}", self.identifier, width = width) + } + None => write!(formatter, "{:width$}", self.identifier, width = width), + } + } else { + // Otherwise just do default. + write!(formatter, "{}", self.identifier) + } } } @@ -101,9 +125,9 @@ impl ProcessId { /// index in the processes array. pub(crate) fn new(kernel: &'static Kernel, identifier: usize, index: usize) -> ProcessId { ProcessId { - kernel: kernel, - identifier: identifier, - index: index, + kernel, + index, + identifier, } } @@ -119,9 +143,9 @@ impl ProcessId { _capability: &dyn capabilities::ExternalProcessCapability, ) -> ProcessId { ProcessId { - kernel: kernel, - identifier: identifier, - index: index, + kernel, + index, + identifier, } } @@ -151,12 +175,27 @@ impl ProcessId { /// Note, this will always return the saved unique identifier for the app /// originally referred to, even if that app no longer exists. For example, /// the app may have restarted, or may have been ended or removed by the - /// kernel. Therefore, calling `id()` is _not_ a valid way to check - /// that an application still exists. + /// kernel. Therefore, calling `id()` is _not_ a valid way to check that an + /// application still exists. pub fn id(&self) -> usize { self.identifier } + /// Get the `ShortId` for this application this process is an execution of. + /// + /// The `ShortId` is an identifier for the _application_, not the particular + /// execution (i.e. the currently running process). This makes `ShortId` + /// distinct from `ProcessId`. + /// + /// This function is a helper function as capsules typically use `ProcessId` + /// as a handle to the running process and corresponding app. + pub fn short_app_id(&self) -> ShortId { + self.kernel + .process_map_or(ShortId::LocallyUnique, *self, |process| { + process.short_app_id() + }) + } + /// Returns the full address of the start and end of the flash region that /// the app owns and can write to. This includes the app's code and data and /// any padding at the end of the app. It does not include the TBF header, @@ -167,6 +206,120 @@ impl ProcessId { (addresses.flash_non_protected_start, addresses.flash_end) }) } + + /// Get the storage permissions for the process. These permissions indicate + /// what the process is allowed to read and write. Returns `None` if the + /// process has no storage permissions. + pub fn get_storage_permissions(&self) -> Option { + self.kernel + .process_map_or(None, *self, |process| process.get_storage_permissions()) + } +} + +/// A compressed form of an Application Identifier. +/// +/// ShortIds are useful for more efficient operations with app identifiers +/// within the kernel. They are guaranteed to be unique among all running +/// processes on the same board. However, as they are only 32 bits they are not +/// globally unique. +/// +/// ShortIds are persistent across restarts of the same app (whereas ProcessIDs +/// are not). +/// +/// As ShortIds must be unique for each app on a board, and since not every +/// platform may have a use for ShortIds, the definition of a ShortId provides a +/// convenient mechanism for meeting the uniqueness requirement without actually +/// requiring assigning unique discrete values to each app. This is done with +/// the `LocallyUnique` variant which is an abstract ID that is guaranteed to be +/// unique (i.e. an equality comparison with any other ShortId will always +/// return `false`). Platforms which have a use for an actual number for a +/// `ShortId` should use the `Fixed(NonZeroU32)` variant. Note, for type space +/// efficiency, we disallow using the number 0 as a fixed ShortId. +/// +/// ShortIds are assigned to the app as part of the credential checking process. +/// Specifically, an implementation of the `process_checker::Compress` trait +/// assigns ShortIds. +#[derive(Clone, Copy)] +pub enum ShortId { + /// An abstract `ShortId` that is always guaranteed to be unique. As this is + /// not an actual discrete value, it cannot be used for anything other than + /// meeting the uniqueness requirement. + LocallyUnique, + /// A 32 bit number `ShortId`. This fixed value is guaranteed to be unique + /// among all running processes as the kernel will not start two processes + /// with the same ShortId. + Fixed(core::num::NonZeroU32), +} + +impl PartialEq for ShortId { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (ShortId::Fixed(a), ShortId::Fixed(b)) => a == b, + _ => false, + } + } +} +impl Eq for ShortId {} + +impl core::convert::From> for ShortId { + fn from(id: Option) -> ShortId { + match id { + Some(fixed) => ShortId::Fixed(fixed), + None => ShortId::LocallyUnique, + } + } +} + +impl core::fmt::Display for ShortId { + fn fmt(&self, fmt: &mut core::fmt::Formatter) -> fmt::Result { + match *self { + ShortId::LocallyUnique => { + write!(fmt, "Unique") + } + ShortId::Fixed(id) => { + write!(fmt, "0x{:<8x} ", id) + } + } + } +} + +/// Enum used to inform scheduler why a process stopped executing (aka why +/// `do_process()` returned). +/// +/// This is publicly exported to allow for schedulers implemented outside of the +/// kernel crate. +#[derive(PartialEq, Eq)] +pub enum StoppedExecutingReason { + /// The process returned because it is no longer ready to run. + NoWorkLeft, + + /// The process faulted, and the board restart policy was configured such + /// that it was not restarted and there was not a kernel panic. + StoppedFaulted, + + /// The kernel stopped the process. + Stopped, + + /// The process was preempted because its timeslice expired. + TimesliceExpired, + + /// The process returned because it was preempted by the kernel. This can + /// mean that kernel work became ready (most likely because an interrupt + /// fired and the kernel thread needs to execute the bottom half of the + /// interrupt), or because the scheduler no longer wants to execute that + /// process. + KernelPreemption, +} + +/// The version of a binary. +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)] +pub struct BinaryVersion(NonZeroU32); + +impl BinaryVersion { + /// Creates a new binary version. + pub fn new(value: NonZeroU32) -> Self { + Self(value) + } } /// This trait represents a generic process that the Tock scheduler can @@ -175,26 +328,40 @@ pub trait Process { /// Returns the process's identifier. fn processid(&self) -> ProcessId; + /// Returns the ShortId generated by the application binary checker at + /// loading. + fn short_app_id(&self) -> ShortId; + + /// Returns the version number of the binary in this process, as specified + /// in a TBF Program Header; if the binary has no version assigned, return [None] + fn binary_version(&self) -> Option; + + /// Returns how many times this process has been restarted. + fn get_restart_count(&self) -> usize; + + /// Get the name of the process. Used for IPC. + fn get_process_name(&self) -> &'static str; + + /// Return if there are any Tasks (upcalls/IPC requests) enqueued for the + /// process. + fn has_tasks(&self) -> bool; + + /// Returns the number of pending tasks. If 0 then `dequeue_task()` will + /// return `None` when called. + fn pending_tasks(&self) -> usize; + /// Queue a `Task` for the process. This will be added to a per-process /// buffer and executed by the scheduler. `Task`s are some function the app /// should run, for example a upcall or an IPC call. /// - /// This function returns `Ok(())` if the `Task` was successfully - /// enqueued. If the process is no longer alive, - /// `Err(ErrorCode::NODEVICE)` is returned. If the task could not - /// be enqueued because there is insufficient space in the - /// internal task queue, `Err(ErrorCode::NOMEM)` is - /// returned. Other return values must be treated as - /// kernel-internal errors. + /// This function returns: + /// - `Ok(())` if the `Task` was successfully enqueued. + /// - `Err(ErrorCode::NODEVICE)` if the process is no longer alive. + /// - `Err(ErrorCode::NOMEM)` if the task could not be enqueued because + /// there is insufficient space in the internal task queue. is returned. + /// Other return values must be treated as kernel-internal errors. fn enqueue_task(&self, task: Task) -> Result<(), ErrorCode>; - /// Returns whether this process is ready to execute. - fn ready(&self) -> bool; - - /// Return if there are any Tasks (upcalls/IPC requests) enqueued - /// for the process. - fn has_tasks(&self) -> bool; - /// Remove the scheduled operation from the front of the queue and return it /// to be handled by the scheduler. /// @@ -202,24 +369,36 @@ pub trait Process { /// `None`. fn dequeue_task(&self) -> Option; - /// Returns the number of pending tasks. If 0 then `dequeue_task()` will - /// return `None` when called. - fn pending_tasks(&self) -> usize; + /// Search the work queue for a specific upcall_id. If it is present, + /// return the associated `Task`, otherwise return `None`. + fn remove_upcall(&self, upcall_id: UpcallId) -> Option; - /// Remove all scheduled upcalls for a given upcall id from the task - /// queue. + /// Remove all scheduled upcalls for a given upcall id from the task queue. fn remove_pending_upcalls(&self, upcall_id: UpcallId); /// Returns the current state the process is in. Common states are "running" /// or "yielded". fn get_state(&self) -> State; + /// Returns whether this process is ready to execute. + fn ready(&self) -> bool; + + /// Returns whether the process is running (has active stack frames) or not + /// (has never run, has faulted, or has completed). + fn is_running(&self) -> bool; + /// Move this process from the running state to the yielded state. /// /// This will fail (i.e. not do anything) if the process was not previously /// running. fn set_yielded_state(&self); + /// Move this process from the running state to the yielded-for state. + /// + /// This will fail (i.e. not do anything) if the process was not previously + /// running. + fn set_yielded_for_state(&self, upcall_id: UpcallId); + /// Move this process from running or yielded state into the stopped state. /// /// This will fail (i.e. not do anything) if the process was not either @@ -232,60 +411,37 @@ pub trait Process { /// `StoppedYielded` -> `Yielded`. fn resume(&self); - /// Put this process in the fault state. This will trigger the - /// `FaultResponse` for this process to occur. + /// Put this process in the fault state. The kernel will use its process + /// fault policy to decide what action to take in regards to the faulted + /// process. fn set_fault_state(&self); - /// Returns how many times this process has been restarted. - fn get_restart_count(&self) -> usize; - - /// Get the name of the process. Used for IPC. - fn get_process_name(&self) -> &'static str; - - /// Get the completion code if the process has previously terminated. + /// Start a terminated process. This function can only be called on a + /// terminated process. /// - /// If the process has never terminated then there has been no opportunity - /// for a completion code to be set, and this will return `None`. - /// - /// If the process has previously terminated this will return `Some()`. If - /// the last time the process terminated it did not provide a completion - /// code (e.g. the process faulted), then this will return `Some(None)`. If - /// the last time the process terminated it did provide a completion code, - /// this will return `Some(Some(completion_code))`. - fn get_completion_code(&self) -> Option>; - - /// Stop and clear a process's state, putting it into the `Terminated` - /// state. - /// - /// This will end the process, but does not reset it such that it could be - /// restarted and run again. This function instead frees grants and any - /// queued tasks for this process, but leaves the debug information about - /// the process and other state intact. - /// - /// When a process is terminated, an optional `completion_code` should be - /// stored for the process. If the process provided the completion code - /// (e.g. via the exit syscall), then this function should be called with - /// a completion code of `Some(u32)`. If the kernel is terminating the - /// process and therefore has no completion code from the process, it should - /// provide `None`. - fn terminate(&self, completion_code: Option); + /// The caller MUST verify this process is unique before calling this + /// function. This requires a capability to call to ensure that the caller + /// have verified that this process is unique before trying to start it. + fn start(&self, cap: &dyn crate::capabilities::ProcessStartCapability); /// Terminates and attempts to restart the process. The process and current /// application always terminate. The kernel may, based on its own policy, /// restart the application using the same process, reuse the process for /// another application, or simply terminate the process and application. /// - /// This function can be called when the process is in any state. It - /// attempts to reset all process state and re-initialize it so that it can - /// be reused. + /// This function can be called when the process is in any state except for + /// `Terminated`. It attempts to reset all process state and re-initialize + /// it so that it can be reused. + /// + /// Restarting an application can fail for three general reasons: /// - /// Restarting an application can fail for two general reasons: + /// 1. The process is already terminated. Use `start()` instead. /// - /// 1. The kernel chooses not to restart the application, based on its + /// 2. The kernel chooses not to restart the application, based on its /// policy. /// - /// 2. The kernel decides to restart the application but fails to do so - /// because Some state can no long be configured for the process. For + /// 3. The kernel decides to restart the application but fails to do so + /// because some state can no long be configured for the process. For /// example, the syscall state for the process fails to initialize. /// /// After `restart()` runs the process will either be queued to run its the @@ -300,6 +456,33 @@ pub trait Process { /// with `None`. fn try_restart(&self, completion_code: Option); + /// Stop and clear a process's state and put it into the `Terminated` state. + /// + /// This will end the process, but does not reset it such that it could be + /// restarted and run again. This function instead frees grants and any + /// queued tasks for this process, but leaves the debug information about + /// the process and other state intact. + /// + /// When a process is terminated, an optional `completion_code` should be + /// stored for the process. If the process provided the completion code + /// (e.g. via the exit syscall), then this function should be called with a + /// completion code of `Some(u32)`. If the kernel is terminating the process + /// and therefore has no completion code from the process, it should provide + /// `None`. + fn terminate(&self, completion_code: Option); + + /// Get the completion code if the process has previously terminated. + /// + /// If the process has never terminated then there has been no opportunity + /// for a completion code to be set, and this will return `None`. + /// + /// If the process has previously terminated this will return `Some()`. If + /// the last time the process terminated it did not provide a completion + /// code (e.g. the process faulted), then this will return `Some(None)`. If + /// the last time the process terminated it did provide a completion code, + /// this will return `Some(Some(completion_code))`. + fn get_completion_code(&self) -> Option>; + // memop operations /// Change the location of the program break and reallocate the MPU region @@ -343,11 +526,11 @@ pub trait Process { /// In case of success, this method returns the created /// [`ReadWriteProcessBuffer`]. /// - /// In case of an error, an appropriate ErrorCode is returned: + /// In case of an error, an appropriate `ErrorCode` is returned: /// /// - If the memory is not contained in the process-accessible memory space /// / `buf_start_addr` and `size` are not a valid read-write buffer (any - /// byte in the range is not read/write accessible to the process), + /// byte in the range is not read/write accessible to the process): /// [`ErrorCode::INVAL`]. /// - If the process is not active: [`ErrorCode::FAIL`]. /// - For all other errors: [`ErrorCode::FAIL`]. @@ -369,7 +552,7 @@ pub trait Process { /// /// - If the memory is not contained in the process-accessible memory space /// / `buf_start_addr` and `size` are not a valid read-only buffer (any - /// byte in the range is not read-accessible to the process), + /// byte in the range is not read-accessible to the process): /// [`ErrorCode::INVAL`]. /// - If the process is not active: [`ErrorCode::FAIL`]. /// - For all other errors: [`ErrorCode::FAIL`]. @@ -397,9 +580,15 @@ pub trait Process { /// The returned `CommandPermissions` will indicate if any permissions for /// individual command numbers are specified. If there are permissions set /// they are returned as a 64 bit bitmask for sequential command numbers. - /// The offset indicates the multiple of 64 command numbers to get permissions for. + /// The offset indicates the multiple of 64 command numbers to get + /// permissions for. fn get_command_permissions(&self, driver_num: usize, offset: usize) -> CommandPermissions; + /// Get the storage permissions for the process. + /// + /// Returns `None` if the process has no storage permissions. + fn get_storage_permissions(&self) -> Option; + // mpu /// Configure the MPU to use the process's allocated regions. @@ -421,8 +610,8 @@ pub trait Process { min_region_size: usize, ) -> Option; - /// Removes an MPU region from the process that has been previouly added with - /// `add_mpu_region`. + /// Removes an MPU region from the process that has been previously added + /// with `add_mpu_region`. /// /// It is not valid to call this function when the process is inactive (i.e. /// the process will not run again). @@ -433,13 +622,13 @@ pub trait Process { /// Allocate memory from the grant region and store the reference in the /// proper grant pointer index. /// - /// This function must check that doing the allocation does not cause - /// the kernel memory break to go below the top of the process accessible - /// memory region allowed by the MPU. Note, this can be different from the - /// actual app_brk, as MPU alignment and size constraints may result in the - /// MPU enforced region differing from the app_brk. + /// This function must check that doing the allocation does not cause the + /// kernel memory break to go below the top of the process accessible memory + /// region allowed by the MPU. Note, this can be different from the actual + /// app_brk, as MPU alignment and size constraints may result in the MPU + /// enforced region differing from the app_brk. /// - /// This will return `None` and fail if: + /// This will return `Err(())` and fail if: /// - The process is inactive, or /// - There is not enough available memory to do the allocation, or /// - The grant_num is invalid, or @@ -450,7 +639,7 @@ pub trait Process { driver_num: usize, size: usize, align: usize, - ) -> Option>; + ) -> Result<(), ()>; /// Check if a given grant for this process has been allocated. /// @@ -463,14 +652,14 @@ pub trait Process { /// are not recorded in the grant pointer array, but are useful for capsules /// which need additional process-specific dynamically allocated memory. /// - /// If successful, return a Some() with an identifier that can be used with + /// If successful, return a Ok() with an identifier that can be used with /// `enter_custom_grant()` to get access to the memory and the pointer to /// the memory which must be used to initialize the memory. fn allocate_custom_grant( &self, size: usize, align: usize, - ) -> Option<(ProcessCustomGrantIdentifer, NonNull)>; + ) -> Result<(ProcessCustomGrantIdentifier, NonNull), ()>; /// Enter the grant based on `grant_num` for this process. /// @@ -481,17 +670,19 @@ pub trait Process { /// is invalid, if the grant has not been allocated, or if the grant is /// already entered. If this returns `Ok()` then the pointer points to the /// previously allocated memory for this grant. - fn enter_grant(&self, grant_num: usize) -> Result<*mut u8, Error>; + fn enter_grant(&self, grant_num: usize) -> Result, Error>; /// Enter a custom grant based on the `identifier`. /// /// This retrieves a pointer to the previously allocated custom grant based /// on the identifier returned when the custom grant was allocated. /// - /// This returns an error if the custom grant is no longer accessible, or - /// if the process is inactive. - fn enter_custom_grant(&self, identifier: ProcessCustomGrantIdentifer) - -> Result<*mut u8, Error>; + /// This returns an error if the custom grant is no longer accessible, or if + /// the process is inactive. + fn enter_custom_grant( + &self, + identifier: ProcessCustomGrantIdentifier, + ) -> Result<*mut u8, Error>; /// Opposite of `enter_grant()`. Used to signal that the grant is no longer /// entered. @@ -500,11 +691,19 @@ pub trait Process { /// invalid, this function will do nothing. If the process is inactive then /// grants are invalid and are not entered or not entered, and this function /// will do nothing. - fn leave_grant(&self, grant_num: usize); + /// + /// ### Safety + /// + /// The caller must ensure that no references to the memory inside the grant + /// exist after calling `leave_grant()`. Otherwise, it would be possible to + /// effectively enter the grant twice (once using the existing reference, + /// once with a new call to `enter_grant()`) which breaks the memory safety + /// requirements of grants. + unsafe fn leave_grant(&self, grant_num: usize); /// Return the count of the number of allocated grant pointers if the - /// process is active. This does not count custom grants. This is used - /// to determine if a new grant has been allocated after a call to + /// process is active. This does not count custom grants. This is used to + /// determine if a new grant has been allocated after a call to /// `SyscallDriver::allocate_grant()`. /// /// Useful for debugging/inspecting the system. @@ -516,7 +715,7 @@ pub trait Process { // subscribe - /// Verify that an Upcall function pointer is within process-accessible + /// Verify that an upcall function pointer is within process-accessible /// memory. /// /// Returns `true` if the upcall function pointer is valid for this process, @@ -556,23 +755,23 @@ pub trait Process { /// switched to. fn switch_to(&self) -> Option; - /// Return process state information related to the location in memory - /// of various process data structures. + /// Return process state information related to the location in memory of + /// various process data structures. fn get_addresses(&self) -> ProcessAddresses; /// Return process state information related to the size in memory of /// various process data structures. fn get_sizes(&self) -> ProcessSizes; - /// Write stored state as a binary blob into the `out` slice. Returns the number of bytes - /// written to `out` on success. + /// Write stored state as a binary blob into the `out` slice. Returns the + /// number of bytes written to `out` on success. /// - /// Returns `ErrorCode::SIZE` if `out` is too short to hold the stored state binary - /// representation. Returns `ErrorCode::FAIL` on an internal error. + /// Returns `ErrorCode::SIZE` if `out` is too short to hold the stored state + /// binary representation. Returns `ErrorCode::FAIL` on an internal error. fn get_stored_state(&self, out: &mut [u8]) -> Result; - /// Print out the full state of the process: its memory map, its - /// context, and the state of the memory protection unit (MPU). + /// Print out the full state of the process: its memory map, its context, + /// and the state of the memory protection unit (MPU). fn print_full_process(&self, writer: &mut dyn Write); // debug @@ -612,10 +811,11 @@ pub trait Process { /// The fields of this struct are private so only Process can create this /// identifier. #[derive(Copy, Clone)] -pub struct ProcessCustomGrantIdentifer { +pub struct ProcessCustomGrantIdentifier { pub(crate) offset: usize, } +/// Error types related to processes. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum Error { /// The process has been removed and no longer exists. For example, the @@ -663,10 +863,22 @@ impl From for ErrorCode { } } -/// Various states a process can be in. +/// States a process can be in. /// -/// This is made public in case external implementations of `Process` want -/// to re-use these process states in the external implementation. +/// This is public so external implementations of `Process` can re-use these +/// process states. +/// +/// While a process is running, it transitions between the `Running`, `Yielded`, +/// `YieldedFor`, and `Stopped` states. If an error occurs (e.g., a memory +/// access error), the kernel faults it and either leaves it in the `Faulted` +/// state, restarts it, or takes some other action defined by the kernel fault +/// policy. If the process issues an `exit-terminate` system call, it enters the +/// `Terminated` state. If it issues an `exit-restart` system call, it +/// terminates then tries to back to a runnable state. +/// +/// When a process faults, it enters the `Faulted` state. To be restarted, it +/// must first transition to the `Terminated` state, which means that all of its +/// state has been cleaned up. #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum State { /// Process expects to be running code. The process may not be currently @@ -680,61 +892,46 @@ pub enum State { /// scheduled again. Yielded, - /// The process is stopped, and its previous state was Running. This is used - /// if the kernel forcibly stops a process when it is in the `Running` - /// state. This state indicates to the kernel not to schedule the process, - /// but if the process is to be resumed later it should be put back in the - /// running state so it will execute correctly. - StoppedRunning, - - /// The process is stopped, and it was stopped while it was yielded. If this - /// process needs to be resumed it should be put back in the `Yield` state. - StoppedYielded, - - /// The process faulted and cannot be run. + /// Process stopped executing and returned to the kernel because it called + /// the `WaitFor` variant of the `yield` syscall. The process should not be + /// scheduled until the specified driver attempts to execute the specified + /// upcall. + YieldedFor(UpcallId), + + /// The process is stopped and the previous state the process was in when it + /// was stopped. This is used if the kernel forcibly stops a process. This + /// state indicates to the kernel not to schedule the process, but if the + /// process is to be resumed later it should be put back in its previous + /// state so it will execute correctly. + Stopped(StoppedState), + + /// The process ran, faulted while running, and is no longer runnable. For a + /// faulted process to be made runnable, it must first be terminated (to + /// clean up its state). Faulted, - /// The process exited with the `exit-terminate` system call and cannot be - /// run. + /// The process is not running: it exited with the `exit-terminate` system + /// call or was terminated for some other reason (e.g., by the process + /// console). Processes in the `Terminated` state can be run again. Terminated, - - /// The process has never actually been executed. This of course happens - /// when the board first boots and the kernel has not switched to any - /// processes yet. It can also happen if an process is terminated and all of - /// its state is reset as if it has not been executed yet. - Unstarted, -} - -/// A wrapper around `Cell` is used by `Process` to prevent bugs arising -/// from the state duplication in the kernel work tracking and process state -/// tracking. -pub(crate) struct ProcessStateCell<'a> { - state: Cell, - kernel: &'a Kernel, } -impl<'a> ProcessStateCell<'a> { - pub(crate) fn new(kernel: &'a Kernel) -> Self { - Self { - state: Cell::new(State::Unstarted), - kernel, - } - } - - pub(crate) fn get(&self) -> State { - self.state.get() - } - - pub(crate) fn update(&self, new_state: State) { - let old_state = self.state.get(); - - if old_state == State::Running && new_state != State::Running { - self.kernel.decrement_work(); - } else if new_state == State::Running && old_state != State::Running { - self.kernel.increment_work() - } - self.state.set(new_state); - } +/// States a process could previously have been in when stopped. +/// +/// This is public so external implementations of `Process` can re-use these +/// process stopped states. +/// +/// These are recorded so the process can be returned to its previous state when +/// it is resumed. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum StoppedState { + /// The process was in the running state when it was stopped. + Running, + /// The process was in the yielded state when it was stopped. + Yielded, + /// The process was in the yielded for state when it was stopped with a + /// particular upcall it was waiting for. + YieldedFor(UpcallId), } /// The action the kernel should take when a process encounters a fault. @@ -770,6 +967,10 @@ pub enum Task { /// Function pointer in the process to execute. Generally this is a upcall /// from a capsule. FunctionCall(FunctionCall), + /// Data to return to the process. This is used to resume a suspended + /// process without invoking any callbacks in userspace (e.g., in response + /// to a YieldFor). + ReturnValue(ReturnArguments), /// An IPC operation that needs additional setup to configure memory access. IPC((ProcessId, ipc::IPCUpcallType)), } @@ -798,16 +999,39 @@ pub enum FunctionCallSource { /// that it can be unscheduled when the process unsubscribes from this upcall. #[derive(Copy, Clone, Debug)] pub struct FunctionCall { + /// Whether the kernel called this directly or this is an upcall. pub source: FunctionCallSource, + /// The first argument to the function. pub argument0: usize, + /// The second argument to the function. pub argument1: usize, + /// The third argument to the function. pub argument2: usize, + /// The fourth argument to the function. pub argument3: usize, + /// The PC of the function to execute. pub pc: usize, } -/// Collection of process state information related to the memory addresses -/// of different elements of the process. +/// This is similar to `FunctionCall` but for the special case of the Null +/// Upcall for a subscribe. Because there is no function pointer in a Null +/// Upcall we can only return these values to userspace. This is used to pass +/// around upcall parameters when there is no associated upcall to actually call +/// or userdata. +#[derive(Copy, Clone, Debug)] +pub struct ReturnArguments { + /// Which upcall generates this event. + pub upcall_id: UpcallId, + /// The first argument to return. + pub argument0: usize, + /// The second argument to return. + pub argument1: usize, + /// The third argument to return. + pub argument2: usize, +} + +/// Collection of process state information related to the memory addresses of +/// different elements of the process. pub struct ProcessAddresses { /// The address of the beginning of the process's region in nonvolatile /// memory. @@ -816,10 +1040,14 @@ pub struct ProcessAddresses { /// nonvolatile memory. This is after the TBF header and any other memory /// the kernel has reserved for its own use. pub flash_non_protected_start: usize, + /// The address immediately after the end of part of the process binary that + /// is covered by integrity; the integrity region is [flash_start - + /// flash_integrity_end). Footers are stored in the flash after + /// flash_integrity_end. + pub flash_integrity_end: *const u8, /// The address immediately after the end of the region allocated for this /// process in nonvolatile memory. pub flash_end: usize, - /// The address of the beginning of the process's allocated region in /// memory. pub sram_start: usize, diff --git a/kernel/src/process_binary.rs b/kernel/src/process_binary.rs new file mode 100644 index 0000000000..eea8e1bf04 --- /dev/null +++ b/kernel/src/process_binary.rs @@ -0,0 +1,249 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2024. + +//! Representation of processes stored in flash. +//! +//! A `ProcessBinary` object represents the stored binary for a process before +//! it is loaded into a runnable `Process` object. + +use core::fmt; + +use crate::config; +use crate::debug; + +/// Errors resulting from trying to load a process binary structure from flash. +pub enum ProcessBinaryError { + /// No TBF header was found. + TbfHeaderNotFound, + + /// The TBF header for the process could not be successfully parsed. + TbfHeaderParseFailure(tock_tbf::types::TbfParseError), + + /// Not enough flash remaining to parse a process and its header. + NotEnoughFlash, + + /// A process requires a newer version of the kernel or did not specify a + /// required version. Processes can include the KernelVersion TBF header + /// stating their compatible kernel version (^major.minor). + /// + /// Boards may not require processes to include the KernelVersion TBF + /// header, and the kernel supports ignoring a missing KernelVersion TBF + /// header. In that case, this error will not be returned for a process + /// missing a KernelVersion TBF header. + /// + /// `version` is the `(major, minor)` kernel version the process indicates + /// it requires. If `version` is `None` then the process did not include the + /// KernelVersion TBF header. + IncompatibleKernelVersion { version: Option<(u16, u16)> }, + + /// A process specified that its binary must start at a particular address, + /// and that is not the address the binary is actually placed at. + IncorrectFlashAddress { + actual_address: u32, + expected_address: u32, + }, + + /// The process binary specifies the process is not enabled, and therefore + /// cannot be loaded. + NotEnabledProcess, + + /// This entry in flash is just padding. + Padding, +} + +impl From for ProcessBinaryError { + /// Convert between a TBF Header parse error and a process binary error. + /// + /// We note that the process binary error is because a TBF header failed to + /// parse, and just pass through the parse error. + fn from(error: tock_tbf::types::TbfParseError) -> Self { + ProcessBinaryError::TbfHeaderParseFailure(error) + } +} + +impl fmt::Debug for ProcessBinaryError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ProcessBinaryError::TbfHeaderNotFound => { + write!(f, "Could not find TBF header") + } + + ProcessBinaryError::TbfHeaderParseFailure(tbf_parse_error) => { + writeln!(f, "Error parsing TBF header")?; + write!(f, "{:?}", tbf_parse_error) + } + + ProcessBinaryError::NotEnoughFlash => { + write!(f, "Not enough flash available for TBF") + } + + ProcessBinaryError::IncorrectFlashAddress { + actual_address, + expected_address, + } => write!( + f, + "App flash does not match requested address. Actual:{:#x}, Expected:{:#x}", + actual_address, expected_address + ), + + ProcessBinaryError::IncompatibleKernelVersion { version } => match version { + Some((major, minor)) => write!( + f, + "Process is incompatible with the kernel. Running: {}.{}, Requested: {}.{}", + crate::KERNEL_MAJOR_VERSION, + crate::KERNEL_MINOR_VERSION, + major, + minor + ), + None => write!(f, "Process did not provide a TBF kernel version header"), + }, + + ProcessBinaryError::NotEnabledProcess => { + write!(f, "Process marked not enabled") + } + + ProcessBinaryError::Padding => { + write!(f, "Process item is just padding") + } + } + } +} + +/// A process stored in flash. +pub struct ProcessBinary { + /// Process flash segment. This is the entire region of nonvolatile flash + /// that the process occupies. + pub flash: &'static [u8], + + /// The footers of the process binary (may be zero-sized), which are metadata + /// about the process not covered by integrity. Used, among other things, to + /// store signatures. + pub footers: &'static [u8], + + /// Collection of pointers to the TBF header in flash. + pub header: tock_tbf::types::TbfHeader, +} + +impl ProcessBinary { + pub(crate) fn create( + app_flash: &'static [u8], + header_length: usize, + tbf_version: u16, + require_kernel_version: bool, + ) -> Result { + // Get a slice for just the app header. + let header_flash = app_flash + .get(0..header_length) + .ok_or(ProcessBinaryError::NotEnoughFlash)?; + + // Parse the full TBF header to see if this is a valid app. If the + // header can't parse, we will error right here. + let tbf_header = tock_tbf::parse::parse_tbf_header(header_flash, tbf_version)?; + + // If this isn't an app (i.e. it is padding) then we can skip it and do + // not create a `ProcessBinary` object. + if !tbf_header.is_app() { + if config::CONFIG.debug_load_processes && !tbf_header.is_app() { + debug!( + "Padding in flash={:#010X}-{:#010X}", + app_flash.as_ptr() as usize, + app_flash.as_ptr() as usize + app_flash.len() - 1 + ); + } + // Return no process and the full memory slice we were given. + return Err(ProcessBinaryError::Padding); + } + + // If this is an app but it isn't enabled, then we can return an error. + if !tbf_header.enabled() { + if config::CONFIG.debug_load_processes { + debug!( + "Process not enabled flash={:#010X}-{:#010X} process={:?}", + app_flash.as_ptr() as usize, + app_flash.as_ptr() as usize + app_flash.len() - 1, + tbf_header.get_package_name().unwrap_or("(no name)") + ); + } + return Err(ProcessBinaryError::NotEnabledProcess); + } + + if let Some((major, minor)) = tbf_header.get_kernel_version() { + // If the `KernelVersion` header is present, we read the requested + // kernel version and compare it to the running kernel version. + if crate::KERNEL_MAJOR_VERSION != major || crate::KERNEL_MINOR_VERSION < minor { + // If the kernel major version is different, we prevent the + // process from being loaded. + // + // If the kernel major version is the same, we compare the + // kernel minor version. The current running kernel minor + // version has to be greater or equal to the one that the + // process has requested. If not, we prevent the process from + // loading. + if config::CONFIG.debug_load_processes { + debug!( + "WARN process {} requires kernel>={}.{} and <{}.0, (running kernel {}.{})", + tbf_header.get_package_name().unwrap_or(""), + major, + minor, + (major + 1), + crate::KERNEL_MAJOR_VERSION, + crate::KERNEL_MINOR_VERSION + ); + } + return Err(ProcessBinaryError::IncompatibleKernelVersion { + version: Some((major, minor)), + }); + } + } else if require_kernel_version { + // If enforcing the kernel version is requested, and the + // `KernelVersion` header is not present, we prevent the process + // from loading. + if config::CONFIG.debug_load_processes { + debug!( + "WARN process {} has no kernel version header", + tbf_header.get_package_name().unwrap_or("") + ); + debug!("Please upgrade to elf2tab >= 0.8.0"); + } + return Err(ProcessBinaryError::IncompatibleKernelVersion { version: None }); + } + + let binary_end = tbf_header.get_binary_end() as usize; + let total_size = app_flash.len(); + + // End of the portion of the application binary covered by integrity. + // Now handle footers. + let footer_region = app_flash + .get(binary_end..total_size) + .ok_or(ProcessBinaryError::NotEnoughFlash)?; + + // Check that the process is at the correct location in flash if the TBF + // header specified a fixed address. If there is a mismatch we catch + // that early. + if let Some(fixed_flash_start) = tbf_header.get_fixed_address_flash() { + // The flash address in the header is based on the app binary, so we + // need to take into account the header length. + let actual_address = app_flash.as_ptr() as u32 + tbf_header.get_protected_size(); + let expected_address = fixed_flash_start; + if actual_address != expected_address { + return Err(ProcessBinaryError::IncorrectFlashAddress { + actual_address, + expected_address, + }); + } + } + + Ok(Self { + header: tbf_header, + footers: footer_region, + flash: app_flash, + }) + } + + pub(crate) fn get_integrity_region_slice(&self) -> &'static [u8] { + unsafe { + core::slice::from_raw_parts(self.flash.as_ptr(), self.header.get_binary_end() as usize) + } + } +} diff --git a/kernel/src/process_checker.rs b/kernel/src/process_checker.rs new file mode 100644 index 0000000000..8e6287ab45 --- /dev/null +++ b/kernel/src/process_checker.rs @@ -0,0 +1,463 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Traits and types for application credentials checkers, used to decide +//! whether an application can be loaded. +//! +//! See the [AppID TRD](../../doc/reference/trd-appid.md). + +use core::cell::Cell; +use core::fmt; + +use crate::config; +use crate::debug; +use crate::process::Process; +use crate::process::ShortId; +use crate::process_binary::ProcessBinary; +use crate::utilities::cells::{NumericCellExt, OptionalCell}; +use crate::ErrorCode; +use tock_tbf::types::TbfFooterV2Credentials; +use tock_tbf::types::TbfParseError; + +/// Error from checking process credentials. +pub enum ProcessCheckError { + /// The application checker requires credentials, but the TBF did not + /// include a credentials that meets the checker's requirements. This can be + /// either because the TBF has no credentials or the checker policy did not + /// accept any of the credentials it has. + CredentialsNotAccepted, + + /// The process contained a credentials which was rejected by the verifier. + /// The `u32` indicates which credentials was rejected: the first + /// credentials after the application binary is 0, and each subsequent + /// credentials increments this counter. + CredentialsRejected(u32), + + /// Error in the kernel implementation. + InternalError, +} + +impl fmt::Debug for ProcessCheckError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ProcessCheckError::CredentialsNotAccepted => { + write!(f, "No credentials accepted") + } + + ProcessCheckError::CredentialsRejected(index) => { + write!(f, "Credential {} rejected", index) + } + + ProcessCheckError::InternalError => write!(f, "Error in kernel. Likely a bug."), + } + } +} + +/// What a AppCredentialsChecker decided a particular application's credential +/// indicates about the runnability of an application binary. +#[derive(Debug)] +pub enum CheckResult { + /// Accept the credential and run the binary. + Accept, + /// Go to the next credential or in the case of the last one fall + /// back to the default policy. + Pass, + /// Reject the credential and do not run the binary. + Reject, +} + +/// Receives callbacks on whether a credential was accepted or not. +pub trait AppCredentialsPolicyClient<'a> { + /// The check for a particular credential is complete. Result of the check + /// is in `result`. + fn check_done( + &self, + result: Result, + credentials: TbfFooterV2Credentials, + integrity_region: &'a [u8], + ); +} + +/// Implements a Credentials Checking Policy. +pub trait AppCredentialsPolicy<'a> { + /// Set the client which gets notified after the credential check completes. + fn set_client(&self, client: &'a dyn AppCredentialsPolicyClient<'a>); + + /// Whether credentials are required or not. + /// + /// If this returns `true`, then a process will only be executed if one + /// credential was accepted. If this returns `false` then a process will be + /// executed even if no credentials are accepted. + fn require_credentials(&self) -> bool; + + /// Check a particular credential. + /// + /// If credential checking started successfully then this returns `Ok()`. + fn check_credentials( + &self, + credentials: TbfFooterV2Credentials, + integrity_region: &'a [u8], + ) -> Result<(), (ErrorCode, TbfFooterV2Credentials, &'a [u8])>; +} + +/// Whether two processes have the same Application Identifier; two +/// processes with the same Application Identifier cannot run concurrently. +pub trait AppUniqueness { + /// Returns whether `process_a` and `process_b` have a different identifier, + /// and so can run concurrently. If this returns `false`, the kernel + /// will not run `process_a` and `process_b` at the same time. + fn different_identifier(&self, process_a: &ProcessBinary, process_b: &ProcessBinary) -> bool; + + /// Returns whether `process_a` and `process_b` have a different identifier, + /// and so can run concurrently. If this returns `false`, the kernel + /// will not run `process_a` and `process_b` at the same time. + fn different_identifier_process( + &self, + process_a: &ProcessBinary, + process_b: &dyn Process, + ) -> bool; + + /// Returns whether `process_a` and `process_b` have a different identifier, + /// and so can run concurrently. If this returns `false`, the kernel + /// will not run `process_a` and `process_b` at the same time. + fn different_identifier_processes( + &self, + process_a: &dyn Process, + process_b: &dyn Process, + ) -> bool; +} + +/// Default implementation. +impl AppUniqueness for () { + fn different_identifier(&self, _process_a: &ProcessBinary, _process_b: &ProcessBinary) -> bool { + true + } + + fn different_identifier_process( + &self, + _process_a: &ProcessBinary, + _process_b: &dyn Process, + ) -> bool { + true + } + + fn different_identifier_processes( + &self, + _process_a: &dyn Process, + _process_b: &dyn Process, + ) -> bool { + true + } +} + +/// Transforms Application Credentials into a corresponding ShortId. +pub trait Compress { + /// Create a `ShortId` for `process`. + fn to_short_id(&self, process: &ProcessBinary) -> ShortId; +} + +impl Compress for () { + fn to_short_id(&self, _process: &ProcessBinary) -> ShortId { + ShortId::LocallyUnique + } +} + +pub trait AppIdPolicy: AppUniqueness + Compress {} +impl AppIdPolicy for T {} + +/// Client interface for the outcome of a process credential check. +pub trait ProcessCheckerMachineClient { + /// Check is finished, and the check result is in `result`.0 + /// + /// If `result` is `Ok(())`, the credentials were accepted. If `result` is + /// `Err`, the credentials were not accepted. + fn done(&self, process_binary: ProcessBinary, result: Result<(), ProcessCheckError>); +} + +/// Outcome from checking a single footer credential. +#[derive(Debug)] +enum FooterCheckResult { + /// A check has started + Checking, + /// There are no more footers, no check started + PastLastFooter, + /// The footer isn't a credential, no check started + FooterNotCheckable, + /// The footer is invalid, no check started + BadFooter, + /// An internal error occurred, no check started + Error, +} + +/// Checks the footers for a `ProcessBinary` and decides whether to continue +/// loading the process based on the checking policy in `checker`. +pub struct ProcessCheckerMachine { + /// Client for receiving the outcome of the check. + client: OptionalCell<&'static dyn ProcessCheckerMachineClient>, + /// Policy for checking credentials. + policy: OptionalCell<&'static dyn AppCredentialsPolicy<'static>>, + /// Hold the process binary during checking. + process_binary: OptionalCell, + /// Keep track of which footer is being parsed. + footer_index: Cell, +} + +impl ProcessCheckerMachine { + pub fn new(policy: &'static dyn AppCredentialsPolicy<'static>) -> Self { + Self { + footer_index: Cell::new(0), + policy: OptionalCell::new(policy), + process_binary: OptionalCell::empty(), + client: OptionalCell::empty(), + } + } + + pub fn set_client(&self, client: &'static dyn ProcessCheckerMachineClient) { + self.client.set(client); + } + + pub fn set_policy(&self, policy: &'static dyn AppCredentialsPolicy<'static>) { + self.policy.replace(policy); + } + + /// Check this `process_binary` to see if its credentials are valid. + /// + /// This must be called from a interrupt callback chain. + pub fn check(&self, process_binary: ProcessBinary) -> Result<(), ProcessCheckError> { + self.footer_index.set(0); + self.process_binary.set(process_binary); + self.next() + } + + /// Must be called from a callback context. + fn next(&self) -> Result<(), ProcessCheckError> { + let policy = self.policy.get().ok_or(ProcessCheckError::InternalError)?; + let pb = self + .process_binary + .take() + .ok_or(ProcessCheckError::InternalError)?; + let pb_name = pb.header.get_package_name().unwrap_or(""); + + // Loop over all footers in the footer region. We don't know how many + // footers there are, so we use `loop {}`. + loop { + let footer_index = self.footer_index.get(); + + let check_result = ProcessCheckerMachine::check_footer(&pb, policy, footer_index); + + if config::CONFIG.debug_process_credentials { + debug!( + "Checking: Check status for process {}, footer {}: {:?}", + pb_name, footer_index, check_result + ); + } + match check_result { + FooterCheckResult::Checking => { + self.process_binary.set(pb); + break; + } + FooterCheckResult::PastLastFooter | FooterCheckResult::BadFooter => { + // We reached the end of the footers without any + // credentials or all credentials were Pass: apply + // the checker policy to see if the process + // should be allowed to run. + self.policy.map(|policy| { + let requires = policy.require_credentials(); + + let result = if requires { + Err(ProcessCheckError::CredentialsNotAccepted) + } else { + Ok(()) + }; + + self.client.map(|client| client.done(pb, result)); + }); + break; + } + FooterCheckResult::FooterNotCheckable => { + // Go to next footer + self.footer_index.increment(); + } + FooterCheckResult::Error => { + self.client + .map(|client| client.done(pb, Err(ProcessCheckError::InternalError))); + break; + } + } + } + Ok(()) + } + + // Returns whether a footer is being checked or not, and if not, why. + // Iterates through the footer list until if finds `next_footer` or + // it reached the end of the footer region. + fn check_footer( + process_binary: &ProcessBinary, + policy: &'static dyn AppCredentialsPolicy<'static>, + next_footer: usize, + ) -> FooterCheckResult { + if config::CONFIG.debug_process_credentials { + debug!( + "Checking: Checking {:?} footer {}", + process_binary.header.get_package_name(), + next_footer + ); + } + + let integrity_slice = process_binary.get_integrity_region_slice(); + let mut footer_slice = process_binary.footers; + + if config::CONFIG.debug_process_credentials { + debug!( + "Checking: Integrity region is {:x}-{:x}; footers at {:x}-{:x}", + integrity_slice.as_ptr() as usize, + integrity_slice.as_ptr() as usize + integrity_slice.len(), + footer_slice.as_ptr() as usize, + footer_slice.as_ptr() as usize + footer_slice.len(), + ); + } + + let mut current_footer = 0; + while current_footer <= next_footer { + if config::CONFIG.debug_process_credentials { + debug!( + "Checking: Current footer slice {:x}-{:x}", + footer_slice.as_ptr() as usize, + footer_slice.as_ptr() as usize + footer_slice.len(), + ); + } + + let parse_result = tock_tbf::parse::parse_tbf_footer(footer_slice); + match parse_result { + Err(TbfParseError::NotEnoughFlash) => { + if config::CONFIG.debug_process_credentials { + debug!("Checking: Not enough flash for a footer"); + } + return FooterCheckResult::PastLastFooter; + } + Err(TbfParseError::BadTlvEntry(t)) => { + if config::CONFIG.debug_process_credentials { + debug!("Checking: Bad TLV entry, type: {:?}", t); + } + return FooterCheckResult::BadFooter; + } + Err(e) => { + if config::CONFIG.debug_process_credentials { + debug!("Checking: Error parsing footer: {:?}", e); + } + return FooterCheckResult::BadFooter; + } + Ok((footer, len)) => { + let slice_result = footer_slice.get(len as usize + 4..); + if config::CONFIG.debug_process_credentials { + debug!( + "ProcessCheck: @{:x} found a len {} footer: {:?}", + footer_slice.as_ptr() as usize, + len, + footer.format() + ); + } + match slice_result { + None => { + return FooterCheckResult::BadFooter; + } + Some(slice) => { + footer_slice = slice; + if current_footer == next_footer { + match policy.check_credentials(footer, integrity_slice) { + Ok(()) => { + if config::CONFIG.debug_process_credentials { + debug!("Checking: Found {}, checking", current_footer); + } + return FooterCheckResult::Checking; + } + Err((ErrorCode::NOSUPPORT, _, _)) => { + if config::CONFIG.debug_process_credentials { + debug!( + "Checking: Found {}, not supported", + current_footer + ); + } + return FooterCheckResult::FooterNotCheckable; + } + Err((ErrorCode::ALREADY, _, _)) => { + if config::CONFIG.debug_process_credentials { + debug!("Checking: Found {}, already", current_footer); + } + return FooterCheckResult::FooterNotCheckable; + } + Err(e) => { + if config::CONFIG.debug_process_credentials { + debug!( + "Checking: Found {}, error {:?}", + current_footer, e + ); + } + return FooterCheckResult::Error; + } + } + } + } + } + } + } + current_footer += 1; + } + FooterCheckResult::PastLastFooter + } +} + +impl AppCredentialsPolicyClient<'static> for ProcessCheckerMachine { + fn check_done( + &self, + result: Result, + _credentials: TbfFooterV2Credentials, + _integrity_region: &'static [u8], + ) { + if config::CONFIG.debug_process_credentials { + debug!("Checking: check_done gave result {:?}", result); + } + let cont = match result { + Ok(CheckResult::Accept) => { + self.client.map(|client| { + if let Some(pb) = self.process_binary.take() { + client.done(pb, Ok(())) + } + }); + false + } + Ok(CheckResult::Pass) => { + // Checker ignored the credential, so we try the next one. + self.footer_index.increment(); + true + } + Ok(CheckResult::Reject) => { + self.client.map(|client| { + if let Some(pb) = self.process_binary.take() { + client.done( + pb, + Err(ProcessCheckError::CredentialsRejected( + self.footer_index.get() as u32, + )), + ) + } + }); + false + } + Err(e) => { + if config::CONFIG.debug_process_credentials { + debug!("Checking: error checking footer {:?}", e); + } + self.footer_index.increment(); + true + } + }; + if cont { + // If this errors it is an internal error. We don't have a + // `process_binary` to signal the `client::done()` callback, so we + // cannot signal the error. + let _ = self.next(); + } + } +} diff --git a/kernel/src/process_loading.rs b/kernel/src/process_loading.rs new file mode 100644 index 0000000000..a0de6e7637 --- /dev/null +++ b/kernel/src/process_loading.rs @@ -0,0 +1,951 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Helper functions and machines for loading process binaries into in-memory +//! Tock processes. +//! +//! Process loaders are responsible for parsing the binary formats of Tock +//! processes, checking whether they are allowed to be loaded, and if so +//! initializing a process structure to run it. +//! +//! This module provides multiple process loader options depending on which +//! features a particular board requires. + +use core::cell::Cell; +use core::fmt; + +use crate::capabilities::ProcessManagementCapability; +use crate::config; +use crate::debug; +use crate::deferred_call::{DeferredCall, DeferredCallClient}; +use crate::kernel::Kernel; +use crate::platform::chip::Chip; +use crate::process::{Process, ShortId}; +use crate::process_binary::{ProcessBinary, ProcessBinaryError}; +use crate::process_checker::{AppIdPolicy, ProcessCheckError, ProcessCheckerMachine}; +use crate::process_policies::ProcessFaultPolicy; +use crate::process_standard::ProcessStandard; +use crate::utilities::cells::{MapCell, OptionalCell}; + +/// Errors that can occur when trying to load and create processes. +pub enum ProcessLoadError { + /// Not enough memory to meet the amount requested by a process. Modify the + /// process to request less memory, flash fewer processes, or increase the + /// size of the region your board reserves for process memory. + NotEnoughMemory, + + /// A process was loaded with a length in flash that the MPU does not + /// support. The fix is probably to correct the process size, but this could + /// also be caused by a bad MPU implementation. + MpuInvalidFlashLength, + + /// The MPU configuration failed for some other, unspecified reason. This + /// could be of an internal resource exhaustion, or a mismatch between the + /// (current) MPU constraints and process requirements. + MpuConfigurationError, + + /// A process specified a fixed memory address that it needs its memory + /// range to start at, and the kernel did not or could not give the process + /// a memory region starting at that address. + MemoryAddressMismatch { + actual_address: u32, + expected_address: u32, + }, + + /// There is nowhere in the `PROCESSES` array to store this process. + NoProcessSlot, + + /// Process loading failed because parsing the binary failed. + BinaryError(ProcessBinaryError), + + /// Process loading failed because checking the process failed. + CheckError(ProcessCheckError), + + /// Process loading error due (likely) to a bug in the kernel. If you get + /// this error please open a bug report. + InternalError, +} + +impl fmt::Debug for ProcessLoadError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ProcessLoadError::NotEnoughMemory => { + write!(f, "Not able to provide RAM requested by app") + } + + ProcessLoadError::MpuInvalidFlashLength => { + write!(f, "App flash length not supported by MPU") + } + + ProcessLoadError::MpuConfigurationError => { + write!(f, "Configuring the MPU failed") + } + + ProcessLoadError::MemoryAddressMismatch { + actual_address, + expected_address, + } => write!( + f, + "App memory does not match requested address Actual:{:#x}, Expected:{:#x}", + actual_address, expected_address + ), + + ProcessLoadError::NoProcessSlot => { + write!(f, "Nowhere to store the loaded process") + } + + ProcessLoadError::BinaryError(binary_error) => { + writeln!(f, "Error parsing process binary")?; + write!(f, "{:?}", binary_error) + } + + ProcessLoadError::CheckError(check_error) => { + writeln!(f, "Error checking process")?; + write!(f, "{:?}", check_error) + } + + ProcessLoadError::InternalError => write!(f, "Error in kernel. Likely a bug."), + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// SYNCHRONOUS PROCESS LOADING +//////////////////////////////////////////////////////////////////////////////// + +/// Load processes (stored as TBF objects in flash) into runnable process +/// structures stored in the `procs` array and mark all successfully loaded +/// processes as runnable. This method does not check the cryptographic +/// credentials of TBF objects. Platforms for which code size is tight and do +/// not need to check TBF credentials can call this method because it results in +/// a smaller kernel, as it does not invoke the credential checking state +/// machine. +/// +/// This function is made `pub` so that board files can use it, but loading +/// processes from slices of flash an memory is fundamentally unsafe. Therefore, +/// we require the `ProcessManagementCapability` to call this function. +// Mark inline always to reduce code size. Since this is only called in one +// place (a board's main.rs), by inlining the load_*processes() functions, the +// compiler can elide many checks which reduces code size appreciably. Note, +// however, these functions require a rather large stack frame, which may be an +// issue for boards small kernel stacks. +#[inline(always)] +pub fn load_processes( + kernel: &'static Kernel, + chip: &'static C, + app_flash: &'static [u8], + app_memory: &'static mut [u8], + mut procs: &'static mut [Option<&'static dyn Process>], + fault_policy: &'static dyn ProcessFaultPolicy, + _capability_management: &dyn ProcessManagementCapability, +) -> Result<(), ProcessLoadError> { + load_processes_from_flash( + kernel, + chip, + app_flash, + app_memory, + &mut procs, + fault_policy, + )?; + + if config::CONFIG.debug_process_credentials { + debug!("Checking: no checking, load and run all processes"); + } + for proc in procs.iter() { + proc.map(|p| { + if config::CONFIG.debug_process_credentials { + debug!("Running {}", p.get_process_name()); + } + }); + } + Ok(()) +} + +/// Helper function to load processes from flash into an array of active +/// processes. This is the default template for loading processes, but a board +/// is able to create its own `load_processes()` function and use that instead. +/// +/// Processes are found in flash starting from the given address and iterating +/// through Tock Binary Format (TBF) headers. Processes are given memory out of +/// the `app_memory` buffer until either the memory is exhausted or the +/// allocated number of processes are created. This buffer is a non-static slice, +/// ensuring that this code cannot hold onto the slice past the end of this function +/// (instead, processes store a pointer and length), which necessary for later +/// creation of `ProcessBuffer`s in this memory region to be sound. +/// A reference to each process is stored in the provided `procs` array. +/// How process faults are handled by the +/// kernel must be provided and is assigned to every created process. +/// +/// Returns `Ok(())` if process discovery went as expected. Returns a +/// `ProcessLoadError` if something goes wrong during TBF parsing or process +/// creation. +#[inline(always)] +fn load_processes_from_flash( + kernel: &'static Kernel, + chip: &'static C, + app_flash: &'static [u8], + app_memory: &'static mut [u8], + procs: &mut &'static mut [Option<&'static dyn Process>], + fault_policy: &'static dyn ProcessFaultPolicy, +) -> Result<(), ProcessLoadError> { + if config::CONFIG.debug_load_processes { + debug!( + "Loading processes from flash={:#010X}-{:#010X} into sram={:#010X}-{:#010X}", + app_flash.as_ptr() as usize, + app_flash.as_ptr() as usize + app_flash.len() - 1, + app_memory.as_ptr() as usize, + app_memory.as_ptr() as usize + app_memory.len() - 1 + ); + } + + let mut remaining_flash = app_flash; + let mut remaining_memory = app_memory; + // Try to discover up to `procs.len()` processes in flash. + let mut index = 0; + let num_procs = procs.len(); + while index < num_procs { + let load_binary_result = discover_process_binary(remaining_flash); + + match load_binary_result { + Ok((new_flash, process_binary)) => { + remaining_flash = new_flash; + + let load_result = load_process( + kernel, + chip, + process_binary, + remaining_memory, + ShortId::LocallyUnique, + index, + fault_policy, + ); + match load_result { + Ok((new_mem, proc)) => { + remaining_memory = new_mem; + match proc { + Some(p) => { + if config::CONFIG.debug_load_processes { + debug!("Loaded process {}", p.get_process_name()) + } + procs[index] = proc; + index += 1; + } + None => { + if config::CONFIG.debug_load_processes { + debug!("No process loaded."); + } + } + } + } + Err((new_mem, err)) => { + remaining_memory = new_mem; + if config::CONFIG.debug_load_processes { + debug!("Processes load error: {:?}.", err); + } + } + } + } + Err((new_flash, err)) => { + remaining_flash = new_flash; + match err { + ProcessBinaryError::NotEnoughFlash | ProcessBinaryError::TbfHeaderNotFound => { + if config::CONFIG.debug_load_processes { + debug!("No more processes to load: {:?}.", err); + } + // No more processes to load. + break; + } + + ProcessBinaryError::TbfHeaderParseFailure(_) + | ProcessBinaryError::IncompatibleKernelVersion { .. } + | ProcessBinaryError::IncorrectFlashAddress { .. } + | ProcessBinaryError::NotEnabledProcess + | ProcessBinaryError::Padding => { + // Skip this binary and move to the next one. + continue; + } + } + } + } + } + Ok(()) +} + +//////////////////////////////////////////////////////////////////////////////// +// HELPER FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +/// Find a process binary stored at the beginning of `flash` and create a +/// `ProcessBinary` object if the process is viable to run on this kernel. +fn discover_process_binary( + flash: &'static [u8], +) -> Result<(&'static [u8], ProcessBinary), (&'static [u8], ProcessBinaryError)> { + if config::CONFIG.debug_load_processes { + debug!( + "Loading process binary from flash={:#010X}-{:#010X}", + flash.as_ptr() as usize, + flash.as_ptr() as usize + flash.len() - 1 + ); + } + + // If this fails, not enough remaining flash to check for an app. + let test_header_slice = flash + .get(0..8) + .ok_or((flash, ProcessBinaryError::NotEnoughFlash))?; + + // Pass the first eight bytes to tbfheader to parse out the length of + // the tbf header and app. We then use those values to see if we have + // enough flash remaining to parse the remainder of the header. + // + // Start by converting [u8] to [u8; 8]. + let header = test_header_slice + .try_into() + .or(Err((flash, ProcessBinaryError::NotEnoughFlash)))?; + + let (version, header_length, app_length) = + match tock_tbf::parse::parse_tbf_header_lengths(header) { + Ok((v, hl, el)) => (v, hl, el), + Err(tock_tbf::types::InitialTbfParseError::InvalidHeader(app_length)) => { + // If we could not parse the header, then we want to skip over + // this app and look for the next one. + (0, 0, app_length) + } + Err(tock_tbf::types::InitialTbfParseError::UnableToParse) => { + // Since Tock apps use a linked list, it is very possible the + // header we started to parse is intentionally invalid to signal + // the end of apps. This is ok and just means we have finished + // loading apps. + return Err((flash, ProcessBinaryError::TbfHeaderNotFound)); + } + }; + + // Now we can get a slice which only encompasses the length of flash + // described by this tbf header. We will either parse this as an actual + // app, or skip over this region. + let app_flash = flash + .get(0..app_length as usize) + .ok_or((flash, ProcessBinaryError::NotEnoughFlash))?; + + // Advance the flash slice for process discovery beyond this last entry. + // This will be the start of where we look for a new process since Tock + // processes are allocated back-to-back in flash. + let remaining_flash = flash + .get(app_flash.len()..) + .ok_or((flash, ProcessBinaryError::NotEnoughFlash))?; + + let pb = ProcessBinary::create(app_flash, header_length as usize, version, true) + .map_err(|e| (remaining_flash, e))?; + + Ok((remaining_flash, pb)) +} + +/// Load a process stored as a TBF process binary with `app_memory` as the RAM +/// pool that its RAM should be allocated from. Returns `Ok` if the process +/// object was created, `Err` with a relevant error if the process object could +/// not be created. +fn load_process( + kernel: &'static Kernel, + chip: &'static C, + process_binary: ProcessBinary, + app_memory: &'static mut [u8], + app_id: ShortId, + index: usize, + fault_policy: &'static dyn ProcessFaultPolicy, +) -> Result<(&'static mut [u8], Option<&'static dyn Process>), (&'static mut [u8], ProcessLoadError)> +{ + if config::CONFIG.debug_load_processes { + debug!( + "Loading: process flash={:#010X}-{:#010X} ram={:#010X}-{:#010X}", + process_binary.flash.as_ptr() as usize, + process_binary.flash.as_ptr() as usize + process_binary.flash.len() - 1, + app_memory.as_ptr() as usize, + app_memory.as_ptr() as usize + app_memory.len() - 1 + ); + } + + // Need to reassign remaining_memory in every iteration so the compiler + // knows it will not be re-borrowed. + // If we found an actual app header, try to create a `Process` + // object. We also need to shrink the amount of remaining memory + // based on whatever is assigned to the new process if one is + // created. + + // Try to create a process object from that app slice. If we don't + // get a process and we didn't get a loading error (aka we got to + // this point), then the app is a disabled process or just padding. + let (process_option, unused_memory) = unsafe { + ProcessStandard::create( + kernel, + chip, + process_binary, + app_memory, + fault_policy, + app_id, + index, + ) + .map_err(|(e, memory)| (memory, e))? + }; + + process_option.map(|process| { + if config::CONFIG.debug_load_processes { + debug!( + "Loading: {} [{}] flash={:#010X}-{:#010X} ram={:#010X}-{:#010X}", + process.get_process_name(), + index, + process.get_addresses().flash_start, + process.get_addresses().flash_end, + process.get_addresses().sram_start, + process.get_addresses().sram_end - 1, + ); + } + }); + + Ok((unused_memory, process_option)) +} + +//////////////////////////////////////////////////////////////////////////////// +// ASYNCHRONOUS PROCESS LOADING +//////////////////////////////////////////////////////////////////////////////// + +/// Client for asynchronous process loading. +/// +/// This supports a client that is notified after trying to load each process in +/// flash. Also there is a callback for after all processes have been +/// discovered. +pub trait ProcessLoadingAsyncClient { + /// A process was successfully found in flash, checked, and loaded into a + /// `ProcessStandard` object. + fn process_loaded(&self, result: Result<(), ProcessLoadError>); + + /// There are no more processes in flash to be loaded. + fn process_loading_finished(&self); +} + +/// Asynchronous process loading. +/// +/// Machines which implement this trait perform asynchronous process loading and +/// signal completion through `ProcessLoadingAsyncClient`. +/// +/// Various process loaders may exist. This includes a loader from a MCU's +/// integrated flash, or a loader from an external flash chip. +pub trait ProcessLoadingAsync<'a> { + /// Set the client to receive callbacks about process loading and when + /// process loading has finished. + fn set_client(&self, client: &'a dyn ProcessLoadingAsyncClient); + + /// Set the credential checking policy for the loader. + fn set_policy(&self, policy: &'a dyn AppIdPolicy); + + /// Start the process loading operation. + fn start(&self); +} + +/// Operating mode of the loader. +#[derive(Clone, Copy)] +enum SequentialProcessLoaderMachineState { + /// Phase of discovering `ProcessBinary` objects in flash. + DiscoverProcessBinaries, + /// Phase of loading `ProcessBinary`s into `Process`s. + LoadProcesses, +} + +/// A machine for loading processes stored sequentially in a region of flash. +/// +/// Load processes (stored as TBF objects in flash) into runnable process +/// structures stored in the `procs` array. This machine scans the footers in +/// the TBF for cryptographic credentials for binary integrity, passing them to +/// the checker to decide whether the process has sufficient credentials to run. +pub struct SequentialProcessLoaderMachine<'a, C: Chip + 'static> { + /// Client to notify as processes are loaded and process loading finishes. + client: OptionalCell<&'a dyn ProcessLoadingAsyncClient>, + /// Machine to use to check process credentials. + checker: &'static ProcessCheckerMachine, + /// Array of stored process references for loaded processes. + procs: MapCell<&'static mut [Option<&'static dyn Process>]>, + /// Array to store `ProcessBinary`s after checking credentials. + proc_binaries: MapCell<&'static mut [Option]>, + /// Flash memory region to load processes from. + flash: Cell<&'static [u8]>, + /// Memory available to assign to applications. + app_memory: Cell<&'static mut [u8]>, + /// Mechanism for generating async callbacks. + deferred_call: DeferredCall, + /// Reference to the kernel object for creating Processes. + kernel: &'static Kernel, + /// Reference to the Chip object for creating Processes. + chip: &'static C, + /// The policy to use when determining ShortIds and process uniqueness. + policy: OptionalCell<&'a dyn AppIdPolicy>, + /// The fault policy to assign to each created Process. + fault_policy: &'static dyn ProcessFaultPolicy, + /// Current mode of the loading machine. + state: OptionalCell, +} + +impl<'a, C: Chip> SequentialProcessLoaderMachine<'a, C> { + /// This function is made `pub` so that board files can use it, but loading + /// processes from slices of flash an memory is fundamentally unsafe. + /// Therefore, we require the `ProcessManagementCapability` to call this + /// function. + pub fn new( + checker: &'static ProcessCheckerMachine, + procs: &'static mut [Option<&'static dyn Process>], + proc_binaries: &'static mut [Option], + kernel: &'static Kernel, + chip: &'static C, + flash: &'static [u8], + app_memory: &'static mut [u8], + fault_policy: &'static dyn ProcessFaultPolicy, + policy: &'static dyn AppIdPolicy, + _capability_management: &dyn ProcessManagementCapability, + ) -> Self { + Self { + deferred_call: DeferredCall::new(), + checker, + client: OptionalCell::empty(), + procs: MapCell::new(procs), + proc_binaries: MapCell::new(proc_binaries), + kernel, + chip, + flash: Cell::new(flash), + app_memory: Cell::new(app_memory), + policy: OptionalCell::new(policy), + fault_policy, + state: OptionalCell::empty(), + } + } + + /// Find a slot in the `PROCESSES` array to store this process. + fn find_open_process_slot(&self) -> Option { + self.procs.map_or(None, |procs| { + for (i, p) in procs.iter().enumerate() { + if p.is_none() { + return Some(i); + } + } + None + }) + } + + /// Find a slot in the `PROCESS_BINARIES` array to store this process. + fn find_open_process_binary_slot(&self) -> Option { + self.proc_binaries.map_or(None, |proc_bins| { + for (i, p) in proc_bins.iter().enumerate() { + if p.is_none() { + return Some(i); + } + } + None + }) + } + + fn load_and_check(&self) { + let ret = self.discover_process_binary(); + match ret { + Ok(pb) => match self.checker.check(pb) { + Ok(()) => {} + Err(e) => { + self.client.map(|client| { + client.process_loaded(Err(ProcessLoadError::CheckError(e))); + }); + } + }, + Err(ProcessBinaryError::NotEnoughFlash) + | Err(ProcessBinaryError::TbfHeaderNotFound) => { + // These two errors occur when there are no more app binaries in + // flash. Now we can move to actually loading process binaries + // into full processes. + + self.state + .set(SequentialProcessLoaderMachineState::LoadProcesses); + self.deferred_call.set(); + } + Err(e) => { + if config::CONFIG.debug_load_processes { + debug!("Loading: unable to create ProcessBinary"); + } + + // Other process binary errors indicate the process is not + // compatible. Signal error and try the next item in flash. + self.client.map(|client| { + client.process_loaded(Err(ProcessLoadError::BinaryError(e))); + }); + self.deferred_call.set(); + } + } + } + + /// Try to parse a process binary from flash. + /// + /// Returns the process binary object or an error if a valid process + /// binary could not be extracted. + fn discover_process_binary(&self) -> Result { + let flash = self.flash.get(); + + if config::CONFIG.debug_load_processes { + debug!( + "Loading process binary from flash={:#010X}-{:#010X}", + flash.as_ptr() as usize, + flash.as_ptr() as usize + flash.len() - 1 + ); + } + + // If this fails, not enough remaining flash to check for an app. + let test_header_slice = flash.get(0..8).ok_or(ProcessBinaryError::NotEnoughFlash)?; + + // Pass the first eight bytes to tbfheader to parse out the length of + // the tbf header and app. We then use those values to see if we have + // enough flash remaining to parse the remainder of the header. + // + // Start by converting [u8] to [u8; 8]. + let header = test_header_slice + .try_into() + .or(Err(ProcessBinaryError::NotEnoughFlash))?; + + let (version, header_length, app_length) = + match tock_tbf::parse::parse_tbf_header_lengths(header) { + Ok((v, hl, el)) => (v, hl, el), + Err(tock_tbf::types::InitialTbfParseError::InvalidHeader(app_length)) => { + // If we could not parse the header, then we want to skip over + // this app and look for the next one. + (0, 0, app_length) + } + Err(tock_tbf::types::InitialTbfParseError::UnableToParse) => { + // Since Tock apps use a linked list, it is very possible the + // header we started to parse is intentionally invalid to signal + // the end of apps. This is ok and just means we have finished + // loading apps. + return Err(ProcessBinaryError::TbfHeaderNotFound); + } + }; + + // Now we can get a slice which only encompasses the length of flash + // described by this tbf header. We will either parse this as an actual + // app, or skip over this region. + let app_flash = flash + .get(0..app_length as usize) + .ok_or(ProcessBinaryError::NotEnoughFlash)?; + + // Advance the flash slice for process discovery beyond this last entry. + // This will be the start of where we look for a new process since Tock + // processes are allocated back-to-back in flash. + let remaining_flash = flash + .get(app_flash.len()..) + .ok_or(ProcessBinaryError::NotEnoughFlash)?; + self.flash.set(remaining_flash); + + let pb = ProcessBinary::create(app_flash, header_length as usize, version, true)?; + + Ok(pb) + } + + /// Create process objects from the discovered process binaries. + /// + /// This verifies that the discovered processes are valid to run. + fn load_process_objects(&self) -> Result<(), ()> { + let proc_binaries = self.proc_binaries.take().ok_or(())?; + let proc_binaries_len = proc_binaries.len(); + + // Iterate all process binary entries. + for i in 0..proc_binaries_len { + // We are either going to load this process binary or discard it, so + // we can use `take()` here. + if let Some(process_binary) = proc_binaries[i].take() { + // We assume the process can be loaded. This is not the case + // if there is a conflicting process. + let mut ok_to_load = true; + + // Start by iterating all other process binaries and seeing + // if any are in conflict (same AppID with newer version). + for j in 0..proc_binaries_len { + match &proc_binaries[j] { + Some(other_process_binary) => { + let blocked = self + .is_blocked_from_loading_by(&process_binary, other_process_binary); + + if blocked { + ok_to_load = false; + break; + } + } + None => {} + } + } + + // Go to next ProcessBinary if we cannot load this process. + if !ok_to_load { + continue; + } + + // Now scan the already loaded processes and make sure this + // doesn't conflict with any of those. Since those processes + // are already loaded, we just need to check if this process + // binary has the same AppID as an already loaded process. + self.procs.map(|procs| { + for proc in procs.iter() { + match proc { + Some(p) => { + let blocked = + self.is_blocked_from_loading_by_process(&process_binary, *p); + + if blocked { + ok_to_load = false; + break; + } + } + None => {} + } + } + }); + + if !ok_to_load { + continue; + } + + // If we get here it is ok to load the process. + match self.find_open_process_slot() { + Some(index) => { + // Calculate the ShortId for this new process. + let short_app_id = self.policy.map_or(ShortId::LocallyUnique, |policy| { + policy.to_short_id(&process_binary) + }); + + // Try to create a `Process` object. + let load_result = load_process( + self.kernel, + self.chip, + process_binary, + self.app_memory.take(), + short_app_id, + index, + self.fault_policy, + ); + match load_result { + Ok((new_mem, proc)) => { + self.app_memory.set(new_mem); + match proc { + Some(p) => { + if config::CONFIG.debug_load_processes { + debug!( + "Loading: Loaded process {}", + p.get_process_name() + ) + } + + // Store the `ProcessStandard` object in the `PROCESSES` + // array. + self.procs.map(|procs| { + procs[index] = proc; + }); + // Notify the client the process was loaded + // successfully. + self.client.map(|client| { + client.process_loaded(Ok(())); + }); + } + None => { + if config::CONFIG.debug_load_processes { + debug!("No process loaded."); + } + } + } + } + Err((new_mem, err)) => { + self.app_memory.set(new_mem); + if config::CONFIG.debug_load_processes { + debug!("Could not load process: {:?}.", err); + } + + self.client.map(|client| { + client.process_loaded(Err(err)); + }); + } + } + } + None => { + // Nowhere to store the process. + self.client.map(|client| { + client.process_loaded(Err(ProcessLoadError::NoProcessSlot)); + }); + } + } + } + } + self.proc_binaries.put(proc_binaries); + + // We have iterated all discovered `ProcessBinary`s and loaded what we + // could so now we can signal that process loading is finished. + self.client.map(|client| { + client.process_loading_finished(); + }); + + self.state.clear(); + Ok(()) + } + + /// Check if `pb1` is blocked from running by `pb2`. + /// + /// `pb2` blocks `pb1` if: + /// + /// - They both have the same AppID or they both have the same ShortId, and + /// - `pb2` has a higher version number. + fn is_blocked_from_loading_by(&self, pb1: &ProcessBinary, pb2: &ProcessBinary) -> bool { + let same_app_id = self + .policy + .map_or(false, |policy| !policy.different_identifier(pb1, pb2)); + let same_short_app_id = self.policy.map_or(false, |policy| { + policy.to_short_id(pb1) == policy.to_short_id(pb2) + }); + let other_newer = pb2.header.get_binary_version() > pb1.header.get_binary_version(); + + let blocks = (same_app_id || same_short_app_id) && other_newer; + + if config::CONFIG.debug_process_credentials { + debug!( + "Loading: ProcessBinary {}({:#02x}) does{} block {}({:#02x})", + pb2.header.get_package_name().unwrap_or(""), + pb2.flash.as_ptr() as usize, + if blocks { " not" } else { "" }, + pb1.header.get_package_name().unwrap_or(""), + pb1.flash.as_ptr() as usize, + ); + } + + blocks + } + + /// Check if `pb` is blocked from running by `process`. + /// + /// `process` blocks `pb` if: + /// + /// - They both have the same AppID, or + /// - They both have the same ShortId + /// + /// Since `process` is already loaded, we only have to enforce the AppID and + /// ShortId uniqueness guarantees. + fn is_blocked_from_loading_by_process( + &self, + pb: &ProcessBinary, + process: &dyn Process, + ) -> bool { + let same_app_id = self.policy.map_or(false, |policy| { + !policy.different_identifier_process(pb, process) + }); + let same_short_app_id = self.policy.map_or(false, |policy| { + policy.to_short_id(pb) == process.short_app_id() + }); + + let blocks = same_app_id || same_short_app_id; + + if config::CONFIG.debug_process_credentials { + debug!( + "Loading: Process {}({:#02x}) does{} block {}({:#02x})", + process.get_process_name(), + process.get_addresses().flash_start, + if blocks { " not" } else { "" }, + pb.header.get_package_name().unwrap_or(""), + pb.flash.as_ptr() as usize, + ); + } + + blocks + } +} + +impl<'a, C: Chip> ProcessLoadingAsync<'a> for SequentialProcessLoaderMachine<'a, C> { + fn set_client(&self, client: &'a dyn ProcessLoadingAsyncClient) { + self.client.set(client); + } + + fn set_policy(&self, policy: &'a dyn AppIdPolicy) { + self.policy.replace(policy); + } + + fn start(&self) { + self.state + .set(SequentialProcessLoaderMachineState::DiscoverProcessBinaries); + // Start an asynchronous flow so we can issue a callback on error. + self.deferred_call.set(); + } +} + +impl<'a, C: Chip> DeferredCallClient for SequentialProcessLoaderMachine<'a, C> { + fn handle_deferred_call(&self) { + // We use deferred calls to start the operation in the async loop. + match self.state.get() { + Some(SequentialProcessLoaderMachineState::DiscoverProcessBinaries) => { + self.load_and_check(); + } + Some(SequentialProcessLoaderMachineState::LoadProcesses) => { + let ret = self.load_process_objects(); + match ret { + Ok(()) => {} + Err(()) => { + // If this failed for some reason, we still need to + // signal that process loading has finished. + self.client.map(|client| { + client.process_loading_finished(); + }); + } + } + } + None => {} + } + } + + fn register(&'static self) { + self.deferred_call.register(self); + } +} + +impl<'a, C: Chip> crate::process_checker::ProcessCheckerMachineClient + for SequentialProcessLoaderMachine<'a, C> +{ + fn done( + &self, + process_binary: ProcessBinary, + result: Result<(), crate::process_checker::ProcessCheckError>, + ) { + // Check if this process was approved by the checker. + match result { + Ok(()) => { + if config::CONFIG.debug_load_processes { + debug!( + "Loading: Check succeeded for process {}", + process_binary.header.get_package_name().unwrap_or("") + ); + } + // Save the checked process binary now that we know it is valid. + match self.find_open_process_binary_slot() { + Some(index) => { + self.proc_binaries.map(|proc_binaries| { + proc_binaries[index] = Some(process_binary); + }); + } + None => { + self.client.map(|client| { + client.process_loaded(Err(ProcessLoadError::NoProcessSlot)); + }); + } + } + } + Err(e) => { + if config::CONFIG.debug_load_processes { + debug!( + "Loading: Process {} check failed {:?}", + process_binary.header.get_package_name().unwrap_or(""), + e + ); + } + // Signal error and call try next + self.client.map(|client| { + client.process_loaded(Err(ProcessLoadError::CheckError(e))); + }); + } + } + + // Try to load the next process in flash. + self.deferred_call.set(); + } +} diff --git a/kernel/src/process_policies.rs b/kernel/src/process_policies.rs index 39435eabcb..84791fa358 100644 --- a/kernel/src/process_policies.rs +++ b/kernel/src/process_policies.rs @@ -1,8 +1,12 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Process-related policies in the Tock kernel. //! -//! This file contains definitions and implementations of policies the Tock -//! kernel can use when managing processes. For example, these policies control -//! decisions such as whether a specific process should be restarted. +//! This file contains definitions of policies the Tock kernel can use when +//! managing processes. For example, these policies control decisions such as +//! whether a specific process should be restarted. use crate::process; use crate::process::Process; @@ -16,92 +20,3 @@ pub trait ProcessFaultPolicy { /// faulting. fn action(&self, process: &dyn Process) -> process::FaultAction; } - -/// Simply panic the entire board if a process faults. -pub struct PanicFaultPolicy {} - -impl ProcessFaultPolicy for PanicFaultPolicy { - fn action(&self, _: &dyn Process) -> process::FaultAction { - process::FaultAction::Panic - } -} - -/// Simply stop the process and no longer schedule it if a process faults. -pub struct StopFaultPolicy {} - -impl ProcessFaultPolicy for StopFaultPolicy { - fn action(&self, _: &dyn Process) -> process::FaultAction { - process::FaultAction::Stop - } -} - -/// Stop the process and no longer schedule it if a process faults, but also -/// print a debug message notifying the user that the process faulted and -/// stopped. -pub struct StopWithDebugFaultPolicy {} - -impl ProcessFaultPolicy for StopWithDebugFaultPolicy { - fn action(&self, process: &dyn Process) -> process::FaultAction { - crate::debug!( - "Process {} faulted and was stopped.", - process.get_process_name() - ); - process::FaultAction::Stop - } -} - -/// Always restart the process if it faults. -pub struct RestartFaultPolicy {} - -impl ProcessFaultPolicy for RestartFaultPolicy { - fn action(&self, _: &dyn Process) -> process::FaultAction { - process::FaultAction::Restart - } -} - -/// Implementation of `ProcessFaultPolicy` that uses a threshold to decide -/// whether to restart a process when it faults. If the process has been -/// restarted more times than the threshold then the process will be stopped -/// and no longer scheduled. -pub struct ThresholdRestartFaultPolicy { - threshold: usize, -} - -impl ThresholdRestartFaultPolicy { - pub const fn new(threshold: usize) -> ThresholdRestartFaultPolicy { - ThresholdRestartFaultPolicy { threshold } - } -} - -impl ProcessFaultPolicy for ThresholdRestartFaultPolicy { - fn action(&self, process: &dyn Process) -> process::FaultAction { - if process.get_restart_count() <= self.threshold { - process::FaultAction::Restart - } else { - process::FaultAction::Stop - } - } -} - -/// Implementation of `ProcessFaultPolicy` that uses a threshold to decide -/// whether to restart a process when it faults. If the process has been -/// restarted more times than the threshold then the board will panic. -pub struct ThresholdRestartThenPanicFaultPolicy { - threshold: usize, -} - -impl ThresholdRestartThenPanicFaultPolicy { - pub const fn new(threshold: usize) -> ThresholdRestartThenPanicFaultPolicy { - ThresholdRestartThenPanicFaultPolicy { threshold } - } -} - -impl ProcessFaultPolicy for ThresholdRestartThenPanicFaultPolicy { - fn action(&self, process: &dyn Process) -> process::FaultAction { - if process.get_restart_count() <= self.threshold { - process::FaultAction::Restart - } else { - process::FaultAction::Panic - } - } -} diff --git a/kernel/src/process_printer.rs b/kernel/src/process_printer.rs index 2a89505254..185177f9aa 100644 --- a/kernel/src/process_printer.rs +++ b/kernel/src/process_printer.rs @@ -1,10 +1,11 @@ -//! Tools for displaying process state. +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. -use core::fmt::Write; +//! Tools for displaying process state. use crate::process::Process; use crate::utilities::binary_write::BinaryWrite; -use crate::utilities::binary_write::WriteToBinaryOffsetWrapper; /// A context token that the caller must pass back to us. This allows us to /// track where we are in the print operation. @@ -13,7 +14,7 @@ pub struct ProcessPrinterContext { /// The overall print message is broken in to chunks so that it can be fit /// in a small buffer that is called multiple times. This tracks which byte /// we are at so we can ignore the text before and print the next bytes. - offset: usize, + pub offset: usize, } /// Trait for creating a custom "process printer" that formats process state in @@ -29,8 +30,8 @@ pub struct ProcessPrinterContext { /// state to nonvolatile storage rather than display it immediately. pub trait ProcessPrinter { /// Print a process overview to the `writer`. As `print_overview()` uses a - /// `&dyn Process` to access the process, only state which can be accessed - /// via the `Process` trait can be printed. + /// `&dyn` [`Process`] to access the process, only state which can be + /// accessed via the [`Process`] trait can be printed. /// /// This is a synchronous function which also supports asynchronous /// operation. This function does not issue a callback, but the return value @@ -47,8 +48,8 @@ pub trait ProcessPrinter { /// The return indicates whether `print_overview()` has more printing to do /// and should be called again. If `print_overview()` returns `Some()` then /// the caller should call `print_overview()` again (providing the returned - /// `ProcessPrinterContext` as the `context` argument) once the `writer` is - /// ready to accept more data. If `print_overview()` returns `None`, the + /// [`ProcessPrinterContext`] as the `context` argument) once the `writer` + /// is ready to accept more data. If `print_overview()` returns `None`, the /// `writer` indicated it accepted all output and the caller does not need /// to call `print_overview()` again to finish the printing. fn print_overview( @@ -58,243 +59,3 @@ pub trait ProcessPrinter { context: Option, ) -> Option; } - -/// A Process Printer that displays a process as a human-readable string. -pub struct ProcessPrinterText {} - -impl ProcessPrinterText { - pub fn new() -> ProcessPrinterText { - ProcessPrinterText {} - } -} - -impl ProcessPrinter for ProcessPrinterText { - // `print_overview()` must be synchronous, but does not assume a synchronous - // writer or an infinite (or very large) underlying buffer in the writer. To - // do this, this implementation assumes the underlying writer _is_ - // synchronous. This makes the printing code cleaner, as it does not need to - // be broken up into chunks of some length (which would need to match the - // underlying buffer length). However, not all writers are synchronous, so - // this implementation keeps track of how many bytes were sent on the last - // call, and only prints new bytes on the next call. This works by having - // the function start from the beginning each time, formats the entire - // overview message, and just drops bytes until getting back to where it - // left off on the last call. - // - // ### Assumptions - // - // This implementation makes two assumptions: - // 1. That `print_overview()` is not called in performance-critical code. - // Since each time it formats and "prints" the message starting from the - // beginning, it duplicates a fair bit of formatting work. Since this is - // for debugging, the performance impact of that shouldn't matter. - // 2. That `printer_overview()` will be called in a tight loop, and no - // process state will change between calls. That could change the length - // of the printed message, and lead to gaps or parts of the overview - // being duplicated. However, it does not make sense that the kernel - // would want to run the process while it is displaying debugging - // information about it, so this should be a safe assumption. - fn print_overview( - &self, - process: &dyn Process, - writer: &mut dyn BinaryWrite, - context: Option, - ) -> Option { - let offset = context.map_or(0, |c| c.offset); - - // Process statistics - let events_queued = process.pending_tasks(); - let syscall_count = process.debug_syscall_count(); - let dropped_upcall_count = process.debug_dropped_upcall_count(); - let restart_count = process.get_restart_count(); - - let addresses = process.get_addresses(); - let sizes = process.get_sizes(); - - let process_struct_memory_location = addresses.sram_end - - sizes.grant_pointers - - sizes.upcall_list - - sizes.process_control_block; - let sram_grant_size = process_struct_memory_location - addresses.sram_grant_start; - - let mut bww = WriteToBinaryOffsetWrapper::new(writer); - bww.set_offset(offset); - - let _ = bww.write_fmt(format_args!( - "\ - 𝐀𝐩𝐩: {} - [{:?}]\ - \r\n Events Queued: {} Syscall Count: {} Dropped Upcall Count: {}\ - \r\n Restart Count: {}\ - \r\n", - process.get_process_name(), - process.get_state(), - events_queued, - syscall_count, - dropped_upcall_count, - restart_count, - )); - - let _ = match process.debug_syscall_last() { - Some(syscall) => bww.write_fmt(format_args!(" Last Syscall: {:?}\r\n", syscall)), - None => bww.write_str(" Last Syscall: None\r\n"), - }; - - let _ = match process.get_completion_code() { - Some(opt_cc) => match opt_cc { - Some(cc) => bww.write_fmt(format_args!(" Completion Code: {}\r\n", cc)), - None => bww.write_str(" Completion Code: Faulted\r\n"), - }, - None => bww.write_str(" Completion Code: None\r\n"), - }; - - let _ = bww.write_fmt(format_args!( - "\ - \r\n\ - \r\n ╔═══════════╤══════════════════════════════════════════╗\ - \r\n ║ Address │ Region Name Used | Allocated (bytes) ║\ - \r\n ╚{:#010X}═╪══════════════════════════════════════════╝\ - \r\n │ Grant Ptrs {:6}\ - \r\n │ Upcalls {:6}\ - \r\n │ Process {:6}\ - \r\n {:#010X} ┼───────────────────────────────────────────\ - \r\n │ ▼ Grant {:6}\ - \r\n {:#010X} ┼───────────────────────────────────────────\ - \r\n │ Unused\ - \r\n {:#010X} ┼───────────────────────────────────────────", - addresses.sram_end, - sizes.grant_pointers, - sizes.upcall_list, - sizes.process_control_block, - process_struct_memory_location, - sram_grant_size, - addresses.sram_grant_start, - addresses.sram_app_brk, - )); - - // We check to see if the underlying writer has more work to do. If it - // does, then its buffer is full and any additional writes are just - // going to be dropped. So, we skip doing more printing if there are - // bytes remaining as a slight performance optimization. - if !bww.bytes_remaining() { - match addresses.sram_heap_start { - Some(sram_heap_start) => { - let sram_heap_size = addresses.sram_app_brk - sram_heap_start; - let sram_heap_allocated = addresses.sram_grant_start - sram_heap_start; - - let _ = bww.write_fmt(format_args!( - "\ - \r\n │ ▲ Heap {:6} | {:6}{} S\ - \r\n {:#010X} ┼─────────────────────────────────────────── R", - sram_heap_size, - sram_heap_allocated, - exceeded_check(sram_heap_size, sram_heap_allocated), - sram_heap_start, - )); - } - None => { - let _ = bww.write_str( - "\ - \r\n │ ▲ Heap ? | ? S\ - \r\n ?????????? ┼─────────────────────────────────────────── R", - ); - } - } - } - - if !bww.bytes_remaining() { - match (addresses.sram_heap_start, addresses.sram_stack_top) { - (Some(sram_heap_start), Some(sram_stack_top)) => { - let sram_data_size = sram_heap_start - sram_stack_top; - let sram_data_allocated = sram_data_size as usize; - - let _ = bww.write_fmt(format_args!( - "\ - \r\n │ Data {:6} | {:6} A", - sram_data_size, sram_data_allocated, - )); - } - _ => { - let _ = bww.write_str( - "\ - \r\n │ Data ? | ? A", - ); - } - } - } - - if !bww.bytes_remaining() { - match (addresses.sram_stack_top, addresses.sram_stack_bottom) { - (Some(sram_stack_top), Some(sram_stack_bottom)) => { - let sram_stack_size = sram_stack_top - sram_stack_bottom; - let sram_stack_allocated = sram_stack_top - addresses.sram_start; - - let _ = bww.write_fmt(format_args!( - "\ - \r\n {:#010X} ┼─────────────────────────────────────────── M\ - \r\n │ ▼ Stack {:6} | {:6}{}", - sram_stack_top, - sram_stack_size, - sram_stack_allocated, - exceeded_check(sram_stack_size, sram_stack_allocated), - )); - } - _ => { - let _ = bww.write_str( - "\ - \r\n ?????????? ┼─────────────────────────────────────────── M\ - \r\n │ ▼ Stack ? | ?", - ); - } - } - } - - if !bww.bytes_remaining() { - let flash_protected_size = addresses.flash_non_protected_start - addresses.flash_start; - let flash_app_size = addresses.flash_end - addresses.flash_non_protected_start; - - let _ = bww.write_fmt(format_args!( - "\ - \r\n {:#010X} ┼───────────────────────────────────────────\ - \r\n │ Unused\ - \r\n {:#010X} ┴───────────────────────────────────────────\ - \r\n .....\ - \r\n {:#010X} ┬─────────────────────────────────────────── F\ - \r\n │ App Flash {:6} L\ - \r\n {:#010X} ┼─────────────────────────────────────────── A\ - \r\n │ Protected {:6} S\ - \r\n {:#010X} ┴─────────────────────────────────────────── H\ - \r\n", - addresses.sram_stack_bottom.unwrap_or(0), - addresses.sram_start, - addresses.flash_end, - flash_app_size, - addresses.flash_non_protected_start, - flash_protected_size, - addresses.flash_start - )); - } - - if bww.bytes_remaining() { - // The underlying writer is indicating there are still bytes - // remaining to be sent. That means we want to return a context so - // the caller knows to call us again and we can keep printing until - // we have displayed the entire process overview. - let new_context = ProcessPrinterContext { - offset: bww.get_index(), - }; - Some(new_context) - } else { - None - } - } -} - -/// If `size` is greater than `allocated` then it returns a warning string to -/// help with debugging. -fn exceeded_check(size: usize, allocated: usize) -> &'static str { - if size > allocated { - " EXCEEDED!" - } else { - " " - } -} diff --git a/kernel/src/process_standard.rs b/kernel/src/process_standard.rs index ef17179228..5ded4e414e 100644 --- a/kernel/src/process_standard.rs +++ b/kernel/src/process_standard.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tock default Process implementation. //! //! `ProcessStandard` is an implementation for a userspace process running on @@ -6,6 +10,7 @@ use core::cell::Cell; use core::cmp; use core::fmt::Write; +use core::num::NonZeroU32; use core::ptr::NonNull; use core::{mem, ptr, slice, str}; @@ -17,15 +22,20 @@ use crate::errorcode::ErrorCode; use crate::kernel::Kernel; use crate::platform::chip::Chip; use crate::platform::mpu::{self, MPU}; -use crate::process::{Error, FunctionCall, FunctionCallSource, Process, State, Task}; -use crate::process::{FaultAction, ProcessCustomGrantIdentifer, ProcessId, ProcessStateCell}; -use crate::process::{ProcessAddresses, ProcessSizes}; +use crate::process::BinaryVersion; +use crate::process::ProcessBinary; +use crate::process::{Error, FunctionCall, FunctionCallSource, Process, Task}; +use crate::process::{FaultAction, ProcessCustomGrantIdentifier, ProcessId}; +use crate::process::{ProcessAddresses, ProcessSizes, ShortId}; +use crate::process::{State, StoppedState}; +use crate::process_loading::ProcessLoadError; use crate::process_policies::ProcessFaultPolicy; -use crate::process_utilities::ProcessLoadError; use crate::processbuffer::{ReadOnlyProcessBuffer, ReadWriteProcessBuffer}; +use crate::storage_permissions; use crate::syscall::{self, Syscall, SyscallReturn, UserspaceKernelBoundary}; use crate::upcall::UpcallId; use crate::utilities::cells::{MapCell, NumericCellExt, OptionalCell}; + use tock_tbf::types::CommandPermissions; /// State for helping with debugging apps. @@ -96,6 +106,10 @@ pub struct ProcessStandard<'a, C: 'static + Chip> { /// table. process_id: Cell, + /// An application ShortId, generated from process loading and + /// checking, which denotes the security identity of this process. + app_id: ShortId, + /// Pointer to the main Kernel struct. kernel: &'static Kernel, @@ -164,6 +178,11 @@ pub struct ProcessStandard<'a, C: 'static + Chip> { /// the process occupies. flash: &'static [u8], + /// The footers of the process binary (may be zero-sized), which are metadata + /// about the process not covered by integrity. Used, among other things, to + /// store signatures. + footers: &'static [u8], + /// Collection of pointers to the TBF header in flash. header: tock_tbf::types::TbfHeader, @@ -181,7 +200,7 @@ pub struct ProcessStandard<'a, C: 'static + Chip> { /// `Running` and `Yielded` states. The system can control the process by /// switching it to a "stopped" state to prevent the scheduler from /// scheduling it. - state: ProcessStateCell<'static>, + state: Cell, /// How to respond if this process faults. fault_policy: &'a dyn ProcessFaultPolicy, @@ -211,9 +230,6 @@ pub struct ProcessStandard<'a, C: 'static + Chip> { /// be stored as `Some(completion code)`. completion_code: OptionalCell>, - /// Name of the app. - process_name: &'static str, - /// Values kept so that we can print useful debug messages when apps fault. debug: MapCell, } @@ -223,10 +239,22 @@ impl Process for ProcessStandard<'_, C> { self.process_id.get() } + fn short_app_id(&self) -> ShortId { + self.app_id + } + + fn binary_version(&self) -> Option { + let version = self.header.get_binary_version(); + match NonZeroU32::new(version) { + Some(version_nonzero) => Some(BinaryVersion::new(version_nonzero)), + None => None, + } + } + fn enqueue_task(&self, task: Task) -> Result<(), ErrorCode> { // If this app is in a `Fault` state then we shouldn't schedule // any work for it. - if !self.is_active() { + if !self.is_running() { return Err(ErrorCode::NODEVICE); } @@ -244,9 +272,7 @@ impl Process for ProcessStandard<'_, C> { } }); - if ret.is_ok() { - self.kernel.increment_work(); - } else { + if ret.is_err() { // On any error we were unable to enqueue the task. Record the // error, but importantly do _not_ increment kernel work. self.debug.map(|debug| { @@ -270,14 +296,7 @@ impl Process for ProcessStandard<'_, C> { // to `upcall_id`. Task::FunctionCall(function_call) => match function_call.source { FunctionCallSource::Kernel => true, - FunctionCallSource::Driver(id) => { - if id != upcall_id { - true - } else { - self.kernel.decrement_work(); - false - } - } + FunctionCallSource::Driver(id) => id != upcall_id, }, _ => true, }); @@ -294,28 +313,52 @@ impl Process for ProcessStandard<'_, C> { }); } + fn is_running(&self) -> bool { + match self.state.get() { + State::Running | State::Yielded | State::YieldedFor(_) | State::Stopped(_) => true, + _ => false, + } + } + fn get_state(&self) -> State { self.state.get() } fn set_yielded_state(&self) { if self.state.get() == State::Running { - self.state.update(State::Yielded); + self.state.set(State::Yielded); + } + } + + fn set_yielded_for_state(&self, upcall_id: UpcallId) { + if self.state.get() == State::Running { + self.state.set(State::YieldedFor(upcall_id)); } } fn stop(&self) { match self.state.get() { - State::Running => self.state.update(State::StoppedRunning), - State::Yielded => self.state.update(State::StoppedYielded), - _ => {} // Do nothing + State::Running => self.state.set(State::Stopped(StoppedState::Running)), + State::Yielded => self.state.set(State::Stopped(StoppedState::Yielded)), + State::YieldedFor(upcall_id) => self + .state + .set(State::Stopped(StoppedState::YieldedFor(upcall_id))), + State::Stopped(_stopped_state) => { + // Already stopped, nothing to do. + } + State::Faulted | State::Terminated => { + // Stop has no meaning on a inactive process. + } } } fn resume(&self) { match self.state.get() { - State::StoppedRunning => self.state.update(State::Running), - State::StoppedYielded => self.state.update(State::Yielded), + State::Stopped(stopped_state) => match stopped_state { + StoppedState::Running => self.state.set(State::Running), + StoppedState::Yielded => self.state.set(State::Yielded), + StoppedState::YieldedFor(upcall_id) => self.state.set(State::YieldedFor(upcall_id)), + }, _ => {} // Do nothing } } @@ -324,12 +367,11 @@ impl Process for ProcessStandard<'_, C> { // Use the per-process fault policy to determine what action the kernel // should take since the process faulted. let action = self.fault_policy.action(self); - match action { FaultAction::Panic => { // process faulted. Panic and print status - self.state.update(State::Faulted); - panic!("Process {} had a fault", self.process_name); + self.state.set(State::Faulted); + panic!("Process {} had a fault", self.get_process_name()); } FaultAction::Restart => { self.try_restart(None); @@ -341,30 +383,53 @@ impl Process for ProcessStandard<'_, C> { // clearing all of the grant regions will cause capsules to drop // this app as well. self.terminate(None); - self.state.update(State::Faulted); + self.state.set(State::Faulted); } } } + fn start(&self, _cap: &dyn crate::capabilities::ProcessStartCapability) { + // `start()` can only be called on a terminated process. + if self.get_state() != State::Terminated { + return; + } + + // Reset to start the process. + if let Ok(()) = self.reset() { + self.state.set(State::Yielded); + } + } + fn try_restart(&self, completion_code: Option) { + // `try_restart()` cannot be called if the process is terminated. Only + // `start()` can start a terminated process. + if self.get_state() == State::Terminated { + return; + } + // Terminate the process, freeing its state and removing any // pending tasks from the scheduler's queue. self.terminate(completion_code); // If there is a kernel policy that controls restarts, it should be // implemented here. For now, always restart. - let _res = self.restart(); + if let Ok(()) = self.reset() { + self.state.set(State::Yielded); + } // Decide what to do with res later. E.g., if we can't restart // want to reclaim the process resources. } fn terminate(&self, completion_code: Option) { - // Remove the tasks that were scheduled for the app from the - // amount of work queue. - let tasks_len = self.tasks.map_or(0, |tasks| tasks.len()); - for _ in 0..tasks_len { - self.kernel.decrement_work(); + // A process can be terminated if it is running or in the `Faulted` + // state. Otherwise, you cannot terminate it and this method return + // early. + // + // The kernel can terminate in the `Faulted` state to return the process + // to a state in which it can run again (e.g., reset it). + if !self.is_running() && self.get_state() != State::Faulted { + return; } // And remove those tasks @@ -381,7 +446,7 @@ impl Process for ProcessStandard<'_, C> { self.completion_code.set(completion_code); // Mark the app as stopped so the scheduler won't try to run it. - self.state.update(State::Terminated); + self.state.set(State::Terminated); } fn get_restart_count(&self) -> usize { @@ -393,10 +458,18 @@ impl Process for ProcessStandard<'_, C> { } fn dequeue_task(&self) -> Option { + self.tasks.map_or(None, |tasks| tasks.dequeue()) + } + + fn remove_upcall(&self, upcall_id: UpcallId) -> Option { self.tasks.map_or(None, |tasks| { - tasks.dequeue().map(|cb| { - self.kernel.decrement_work(); - cb + tasks.remove_first_matching(|task| match task { + Task::FunctionCall(fc) => match fc.source { + FunctionCallSource::Driver(upid) => upid == upcall_id, + _ => false, + }, + Task::ReturnValue(rv) => rv.upcall_id == upcall_id, + Task::IPC(_) => false, }) }) } @@ -409,6 +482,23 @@ impl Process for ProcessStandard<'_, C> { self.header.get_command_permissions(driver_num, offset) } + fn get_storage_permissions(&self) -> Option { + let (read_count, read_ids) = self.header.get_storage_read_ids().unwrap_or((0, [0; 8])); + + let (modify_count, modify_ids) = + self.header.get_storage_modify_ids().unwrap_or((0, [0; 8])); + + let write_id = self.header.get_storage_write_id(); + + Some(storage_permissions::StoragePermissions::new( + read_count, + read_ids, + modify_count, + modify_ids, + write_id, + )) + } + fn number_writeable_flash_regions(&self) -> usize { self.header.number_writeable_flash_regions() } @@ -439,7 +529,7 @@ impl Process for ProcessStandard<'_, C> { fn setup_mpu(&self) { self.mpu_config.map(|config| { - self.chip.mpu().configure_mpu(&config, &self.processid()); + self.chip.mpu().configure_mpu(config); }); } @@ -449,23 +539,19 @@ impl Process for ProcessStandard<'_, C> { unallocated_memory_size: usize, min_region_size: usize, ) -> Option { - self.mpu_config.and_then(|mut config| { + self.mpu_config.and_then(|config| { let new_region = self.chip.mpu().allocate_region( unallocated_memory_start, unallocated_memory_size, min_region_size, mpu::Permissions::ReadWriteOnly, - &mut config, - ); - - if new_region.is_none() { - return None; - } + config, + )?; for region in self.mpu_regions.iter() { if region.get().is_none() { - region.set(new_region); - return new_region; + region.set(Some(new_region)); + return Some(new_region); } } @@ -475,7 +561,7 @@ impl Process for ProcessStandard<'_, C> { } fn remove_mpu_region(&self, region: mpu::Region) -> Result<(), ErrorCode> { - self.mpu_config.map_or(Err(ErrorCode::INVAL), |mut config| { + self.mpu_config.map_or(Err(ErrorCode::INVAL), |config| { // Find the existing mpu region that we are removing; it needs to match exactly. if let Some(internal_region) = self .mpu_regions @@ -484,7 +570,7 @@ impl Process for ProcessStandard<'_, C> { { self.chip .mpu() - .remove_memory_region(region, &mut config) + .remove_memory_region(region, config) .or(Err(ErrorCode::FAIL))?; // Remove this region from the tracking cache of mpu_regions @@ -498,7 +584,7 @@ impl Process for ProcessStandard<'_, C> { fn sbrk(&self, increment: isize) -> Result<*const u8, Error> { // Do not modify an inactive process. - if !self.is_active() { + if !self.is_running() { return Err(Error::InactiveApp); } @@ -508,30 +594,29 @@ impl Process for ProcessStandard<'_, C> { fn brk(&self, new_break: *const u8) -> Result<*const u8, Error> { // Do not modify an inactive process. - if !self.is_active() { + if !self.is_running() { return Err(Error::InactiveApp); } - self.mpu_config - .map_or(Err(Error::KernelError), |mut config| { - if new_break < self.allow_high_water_mark.get() || new_break >= self.mem_end() { - Err(Error::AddressOutOfBounds) - } else if new_break > self.kernel_memory_break.get() { - Err(Error::OutOfMemory) - } else if let Err(_) = self.chip.mpu().update_app_memory_region( - new_break, - self.kernel_memory_break.get(), - mpu::Permissions::ReadWriteOnly, - &mut config, - ) { - Err(Error::OutOfMemory) - } else { - let old_break = self.app_break.get(); - self.app_break.set(new_break); - self.chip.mpu().configure_mpu(&config, &self.processid()); - Ok(old_break) - } - }) + self.mpu_config.map_or(Err(Error::KernelError), |config| { + if new_break < self.allow_high_water_mark.get() || new_break >= self.mem_end() { + Err(Error::AddressOutOfBounds) + } else if new_break > self.kernel_memory_break.get() { + Err(Error::OutOfMemory) + } else if let Err(()) = self.chip.mpu().update_app_memory_region( + new_break, + self.kernel_memory_break.get(), + mpu::Permissions::ReadWriteOnly, + config, + ) { + Err(Error::OutOfMemory) + } else { + let old_break = self.app_break.get(); + self.app_break.set(new_break); + self.chip.mpu().configure_mpu(config); + Ok(old_break) + } + }) } #[allow(clippy::not_unsafe_ptr_arg_deref)] @@ -540,7 +625,7 @@ impl Process for ProcessStandard<'_, C> { buf_start_addr: *mut u8, size: usize, ) -> Result { - if !self.is_active() { + if !self.is_running() { // Do not operate on an inactive process return Err(ErrorCode::FAIL); } @@ -562,7 +647,7 @@ impl Process for ProcessStandard<'_, C> { // // ### Safety // - // We specific a zero-length buffer, so the implementation of + // We specify a zero-length buffer, so the implementation of // `ReadWriteProcessBuffer` will handle any safety issues. // Therefore, we can encapsulate the unsafe. Ok(unsafe { ReadWriteProcessBuffer::new(buf_start_addr, 0, self.processid()) }) @@ -570,7 +655,7 @@ impl Process for ProcessStandard<'_, C> { // TODO: Check for buffer aliasing here // Valid buffer, we need to adjust the app's watermark - // note: in_app_owned_memory ensures this offset does not wrap + // note: `in_app_owned_memory` ensures this offset does not wrap let buf_end_addr = buf_start_addr.wrapping_add(size); let new_water_mark = cmp::max(self.allow_high_water_mark.get(), buf_end_addr); self.allow_high_water_mark.set(new_water_mark); @@ -587,7 +672,7 @@ impl Process for ProcessStandard<'_, C> { // sure that we're pointing towards userspace memory (verified using // `in_app_owned_memory`) and respect alignment and other // constraints of the Rust references created by - // ReadWriteProcessBuffer. + // `ReadWriteProcessBuffer`. // // ### Safety // @@ -606,7 +691,7 @@ impl Process for ProcessStandard<'_, C> { buf_start_addr: *const u8, size: usize, ) -> Result { - if !self.is_active() { + if !self.is_running() { // Do not operate on an inactive process return Err(ErrorCode::FAIL); } @@ -628,7 +713,7 @@ impl Process for ProcessStandard<'_, C> { // // ### Safety // - // We specific a zero-length buffer, so the implementation of + // We specify a zero-length buffer, so the implementation of // `ReadOnlyProcessBuffer` will handle any safety issues. Therefore, // we can encapsulate the unsafe. Ok(unsafe { ReadOnlyProcessBuffer::new(buf_start_addr, 0, self.processid()) }) @@ -658,7 +743,7 @@ impl Process for ProcessStandard<'_, C> { // sure that we're pointing towards userspace memory (verified using // `in_app_owned_memory` or `in_app_flash_memory`) and respect // alignment and other constraints of the Rust references created by - // ReadWriteProcessBuffer. + // `ReadWriteProcessBuffer`. // // ### Safety // @@ -685,7 +770,7 @@ impl Process for ProcessStandard<'_, C> { fn grant_is_allocated(&self, grant_num: usize) -> Option { // Do not modify an inactive process. - if !self.is_active() { + if !self.is_running() { return None; } @@ -695,7 +780,7 @@ impl Process for ProcessStandard<'_, C> { // panic. grant_pointers .get(grant_num) - .map_or(None, |grant_entry| Some(!grant_entry.grant_ptr.is_null())) + .map(|grant_entry| !grant_entry.grant_ptr.is_null()) }) } @@ -705,27 +790,27 @@ impl Process for ProcessStandard<'_, C> { driver_num: usize, size: usize, align: usize, - ) -> Option> { + ) -> Result<(), ()> { // Do not modify an inactive process. - if !self.is_active() { - return None; + if !self.is_running() { + return Err(()); } // Verify the grant_num is valid. if grant_num >= self.kernel.get_grant_count_and_finalize() { - return None; + return Err(()); } // Verify that the grant is not already allocated. If the pointer is not // null then the grant is already allocated. if let Some(is_allocated) = self.grant_is_allocated(grant_num) { if is_allocated { - return None; + return Err(()); } } // Verify that there is not already a grant allocated with the same - // driver_num. + // `driver_num`. let exists = self.grant_pointers.map_or(false, |grant_pointers| { // Check our list of grant pointers if the driver number is used. grant_pointers.iter().any(|grant_entry| { @@ -734,33 +819,33 @@ impl Process for ProcessStandard<'_, C> { (!grant_entry.grant_ptr.is_null()) && grant_entry.driver_num == driver_num }) }); - // If we find a match, then the driver_num must already be used and the - // grant allocation fails. + // If we find a match, then the `driver_num` must already be used and + // the grant allocation fails. if exists { - return None; + return Err(()); } // Use the shared grant allocator function to actually allocate memory. // Returns `None` if the allocation cannot be created. if let Some(grant_ptr) = self.allocate_in_grant_region_internal(size, align) { // Update the grant pointer to the address of the new allocation. - self.grant_pointers.map_or(None, |grant_pointers| { + self.grant_pointers.map_or(Err(()), |grant_pointers| { // Implement `grant_pointers[grant_num] = grant_ptr` without a // chance of a panic. grant_pointers .get_mut(grant_num) - .map_or(None, |grant_entry| { + .map_or(Err(()), |grant_entry| { // Actually set the driver num and grant pointer. grant_entry.driver_num = driver_num; - grant_entry.grant_ptr = grant_ptr.as_ptr() as *mut u8; + grant_entry.grant_ptr = grant_ptr.as_ptr(); - // If all of this worked, return the allocated pointer. - Some(grant_ptr) + // If all of this worked, return true. + Ok(()) }) }) } else { // Could not allocate the memory for the grant region. - None + Err(()) } } @@ -768,10 +853,10 @@ impl Process for ProcessStandard<'_, C> { &self, size: usize, align: usize, - ) -> Option<(ProcessCustomGrantIdentifer, NonNull)> { + ) -> Result<(ProcessCustomGrantIdentifier, NonNull), ()> { // Do not modify an inactive process. - if !self.is_active() { - return None; + if !self.is_running() { + return Err(()); } // Use the shared grant allocator function to actually allocate memory. @@ -781,16 +866,16 @@ impl Process for ProcessStandard<'_, C> { // this custom grant in the future. let identifier = self.create_custom_grant_identifier(ptr); - Some((identifier, ptr)) + Ok((identifier, ptr)) } else { // Could not allocate memory for the custom grant. - None + Err(()) } } - fn enter_grant(&self, grant_num: usize) -> Result<*mut u8, Error> { - // Do not try to access the grant region of inactive process. - if !self.is_active() { + fn enter_grant(&self, grant_num: usize) -> Result, Error> { + // Do not try to access the grant region of an inactive process. + if !self.is_running() { return Err(Error::InactiveApp); } @@ -820,7 +905,7 @@ impl Process for ProcessStandard<'_, C> { // And we return the grant pointer to the entered // grant. - Ok(grant_ptr) + Ok(unsafe { NonNull::new_unchecked(grant_ptr) }) } } None => Err(Error::AddressOutOfBounds), @@ -830,10 +915,10 @@ impl Process for ProcessStandard<'_, C> { fn enter_custom_grant( &self, - identifier: ProcessCustomGrantIdentifer, + identifier: ProcessCustomGrantIdentifier, ) -> Result<*mut u8, Error> { - // Do not try to access the grant region of inactive process. - if !self.is_active() { + // Do not try to access the grant region of an inactive process. + if !self.is_running() { return Err(Error::InactiveApp); } @@ -845,40 +930,37 @@ impl Process for ProcessStandard<'_, C> { Ok(custom_grant_address as *mut u8) } - fn leave_grant(&self, grant_num: usize) { + unsafe fn leave_grant(&self, grant_num: usize) { // Do not modify an inactive process. - if !self.is_active() { + if !self.is_running() { return; } self.grant_pointers.map(|grant_pointers| { // Implement `grant_pointers[grant_num]` without a chance of a // panic. - match grant_pointers.get_mut(grant_num) { - Some(grant_entry) => { - // Get a copy of the actual grant pointer. - let grant_ptr = grant_entry.grant_ptr; - - // Now, to mark that the grant has been released, we set the - // lowest bit back to zero and save this as the grant - // pointer. - grant_entry.grant_ptr = (grant_ptr as usize & !0x1) as *mut u8; - } - None => {} + if let Some(grant_entry) = grant_pointers.get_mut(grant_num) { + // Get a copy of the actual grant pointer. + let grant_ptr = grant_entry.grant_ptr; + + // Now, to mark that the grant has been released, we set the + // lowest bit back to zero and save this as the grant + // pointer. + grant_entry.grant_ptr = (grant_ptr as usize & !0x1) as *mut u8; } }); } fn grant_allocated_count(&self) -> Option { // Do not modify an inactive process. - if !self.is_active() { + if !self.is_running() { return None; } self.grant_pointers.map(|grant_pointers| { - // Filter our list of grant pointers into just the non null ones, - // and count those. A grant is allocated if its grant pointer is non - // null. + // Filter our list of grant pointers into just the non-null ones, + // and count those. A grant is allocated if its grant pointer is + // non-null. grant_pointers .iter() .filter(|grant_entry| !grant_entry.grant_ptr.is_null()) @@ -891,7 +973,7 @@ impl Process for ProcessStandard<'_, C> { .map_or(Err(Error::KernelError), |grant_pointers| { // Filter our list of grant pointers into just the non null // ones, and count those. A grant is allocated if its grant - // pointer is non null. + // pointer is non-null. match grant_pointers.iter().position(|grant_entry| { // Only consider allocated grants. (!grant_entry.grant_ptr.is_null()) && grant_entry.driver_num == driver_num @@ -906,16 +988,16 @@ impl Process for ProcessStandard<'_, C> { let ptr = upcall_fn.as_ptr() as *const u8; let size = mem::size_of::<*const u8>(); - // It is ok if this function is in memory or flash. + // It is okay if this function is in memory or flash. self.in_app_flash_memory(ptr, size) || self.in_app_owned_memory(ptr, size) } fn get_process_name(&self) -> &'static str { - self.process_name + self.header.get_package_name().unwrap_or("") } fn get_completion_code(&self) -> Option> { - self.completion_code.extract() + self.completion_code.get() } fn set_syscall_return_value(&self, return_value: SyscallReturn) { @@ -937,6 +1019,13 @@ impl Process for ProcessStandard<'_, C> { }) { Some(Ok(())) => { // If we get an `Ok` we are all set. + + // The process is either already in the running state (having + // just called a nonblocking syscall like command) or needs to + // be moved to the running state having called Yield-WaitFor and + // now needing to be resumed. Either way we can set the state to + // running. + self.state.set(State::Running); } Some(Err(())) => { @@ -981,7 +1070,7 @@ impl Process for ProcessStandard<'_, C> { // Move this process to the "running" state so the scheduler // will schedule it. - self.state.update(State::Running); + self.state.set(State::Running); } Some(Err(())) => { @@ -1003,7 +1092,7 @@ impl Process for ProcessStandard<'_, C> { fn switch_to(&self) -> Option { // Cannot switch to an invalid process - if !self.is_active() { + if !self.is_running() { return None; } @@ -1023,7 +1112,7 @@ impl Process for ProcessStandard<'_, C> { // If the UKB implementation passed us a stack pointer, update our // debugging state. This is completely optional. - stack_pointer.map(|sp| { + if let Some(sp) = stack_pointer { self.debug.map(|debug| { match debug.app_stack_min_pointer { None => debug.app_stack_min_pointer = Some(sp), @@ -1035,7 +1124,7 @@ impl Process for ProcessStandard<'_, C> { } } }); - }); + } switch_reason } @@ -1073,6 +1162,9 @@ impl Process for ProcessStandard<'_, C> { ProcessAddresses { flash_start: self.flash_start() as usize, flash_non_protected_start: self.flash_non_protected_start() as usize, + flash_integrity_end: ((self.flash.as_ptr() as usize) + + (self.header.get_binary_end() as usize)) + as *const u8, flash_end: self.flash_end() as usize, sram_start: self.mem_start() as usize, sram_app_brk: self.app_memory_break() as usize, @@ -1121,7 +1213,7 @@ impl Process for ProcessStandard<'_, C> { let number_grants = self.kernel.get_grant_count_and_finalize(); let _ = writer.write_fmt(format_args!( "\ - \r\n Total number of grant regions defined: {}\r\n", + \r\n Total number of grant regions defined: {}\r\n", self.kernel.get_grant_count_and_finalize() )); let rows = (number_grants + 2) / 3; @@ -1170,8 +1262,8 @@ impl Process for ProcessStandard<'_, C> { // Fixed addresses, can just run `make lst`. let _ = writer.write_fmt(format_args!( "\ - \r\nTo debug, run `make lst` in the app's folder\ - \r\nand open the arch.{:#x}.{:#x}.lst file.\r\n\r\n", + \r\nTo debug libtock-c apps, run `make lst` in the app's\ + \r\nfolder and open the arch.{:#x}.{:#x}.lst file.\r\n\r\n", debug.fixed_address_flash.unwrap_or(0), debug.fixed_address_ram.unwrap_or(0) )); @@ -1183,8 +1275,9 @@ impl Process for ProcessStandard<'_, C> { let _ = writer.write_fmt(format_args!( "\ - \r\nTo debug, run `make debug RAM_START={:#x} FLASH_INIT={:#x}`\ - \r\nin the app's folder and open the .lst file.\r\n\r\n", + \r\nTo debug libtock-c apps, run\ + \r\n`make debug RAM_START={:#x} FLASH_INIT={:#x}`\ + \r\nin the app's folder and open the .lst file.\r\n\r\n", sram_start, flash_init_fn )); } @@ -1210,117 +1303,33 @@ impl ProcessStandard<'_, C> { // Memory offset to make room for this process's metadata. const PROCESS_STRUCT_OFFSET: usize = mem::size_of::>(); + /// Create a `ProcessStandard` object based on the found `ProcessBinary`. pub(crate) unsafe fn create<'a>( kernel: &'static Kernel, chip: &'static C, - app_flash: &'static [u8], - header_length: usize, - app_version: u16, + pb: ProcessBinary, remaining_memory: &'a mut [u8], fault_policy: &'static dyn ProcessFaultPolicy, - require_kernel_version: bool, + app_id: ShortId, index: usize, - ) -> Result<(Option<&'static dyn Process>, &'a mut [u8]), ProcessLoadError> { - // Get a slice for just the app header. - let header_flash = app_flash - .get(0..header_length as usize) - .ok_or(ProcessLoadError::NotEnoughFlash)?; - - // Parse the full TBF header to see if this is a valid app. If the - // header can't parse, we will error right here. - let tbf_header = tock_tbf::parse::parse_tbf_header(header_flash, app_version)?; - - let process_name = tbf_header.get_package_name(); - - // If this isn't an app (i.e. it is padding) or it is an app but it - // isn't enabled, then we can skip it and do not create a `Process` - // object. - if !tbf_header.is_app() || !tbf_header.enabled() { - if config::CONFIG.debug_load_processes { - if !tbf_header.is_app() { - debug!( - "Padding in flash={:#010X}-{:#010X}", - app_flash.as_ptr() as usize, - app_flash.as_ptr() as usize + app_flash.len() - 1 - ); - } - if !tbf_header.enabled() { - debug!( - "Process not enabled flash={:#010X}-{:#010X} process={:?}", - app_flash.as_ptr() as usize, - app_flash.as_ptr() as usize + app_flash.len() - 1, - process_name.unwrap_or("(no name)") - ); - } - } - // Return no process and the full memory slice we were given. - return Ok((None, remaining_memory)); - } - - if let Some((major, minor)) = tbf_header.get_kernel_version() { - // If the `KernelVersion` header is present, we read the requested - // kernel version and compare it to the running kernel version. - if crate::MAJOR != major || crate::MINOR < minor { - // If the kernel major version is different, we prevent the - // process from being loaded. - // - // If the kernel major version is the same, we compare the - // kernel minor version. The current running kernel minor - // version has to be greater or equal to the one that the - // process has requested. If not, we prevent the process from - // loading. - if config::CONFIG.debug_load_processes { - debug!("WARN process {:?} not loaded as it requires kernel version >= {}.{} and < {}.0, (running kernel {}.{})", process_name.unwrap_or("(no name)"), major, minor, (major+1), crate::MAJOR, crate::MINOR); - } - return Err(ProcessLoadError::IncompatibleKernelVersion { - version: Some((major, minor)), - }); - } - } else { - if require_kernel_version { - // If enforcing the kernel version is requested, and the - // `KernelVersion` header is not present, we prevent the process - // from loading. - if config::CONFIG.debug_load_processes { - debug!("WARN process {:?} not loaded as it has no kernel version header, please upgrade to elf2tab >= 0.8.0", - process_name.unwrap_or ("(no name")); - } - return Err(ProcessLoadError::IncompatibleKernelVersion { version: None }); - } - } - - // Check that the process is at the correct location in - // flash if the TBF header specified a fixed address. If there is a - // mismatch we catch that early. - if let Some(fixed_flash_start) = tbf_header.get_fixed_address_flash() { - // The flash address in the header is based on the app binary, - // so we need to take into account the header length. - let actual_address = app_flash.as_ptr() as u32 + tbf_header.get_protected_size(); - let expected_address = fixed_flash_start; - if actual_address != expected_address { - return Err(ProcessLoadError::IncorrectFlashAddress { - actual_address, - expected_address, - }); - } - } - - // Otherwise, actually load the app. - let process_ram_requested_size = tbf_header.get_minimum_app_ram_size() as usize; - let init_fn = app_flash - .as_ptr() - .offset(tbf_header.get_init_function_offset() as isize) as usize; + ) -> Result<(Option<&'static dyn Process>, &'a mut [u8]), (ProcessLoadError, &'a mut [u8])> + { + let process_name = pb.header.get_package_name(); + let process_ram_requested_size = pb.header.get_minimum_app_ram_size() as usize; // Initialize MPU region configuration. - let mut mpu_config: <::MPU as MPU>::MpuConfig = Default::default(); + let mut mpu_config = match chip.mpu().new_config() { + Some(mpu_config) => mpu_config, + None => return Err((ProcessLoadError::MpuConfigurationError, remaining_memory)), + }; // Allocate MPU region for flash. if chip .mpu() .allocate_region( - app_flash.as_ptr(), - app_flash.len(), - app_flash.len(), + pb.flash.as_ptr(), + pb.flash.len(), + pb.flash.len(), mpu::Permissions::ReadExecuteOnly, &mut mpu_config, ) @@ -1328,13 +1337,13 @@ impl ProcessStandard<'_, C> { { if config::CONFIG.debug_load_processes { debug!( - "[!] flash={:#010X}-{:#010X} process={:?} - couldn't allocate MPU region for flash", - app_flash.as_ptr() as usize, - app_flash.as_ptr() as usize + app_flash.len() - 1, - process_name - ); + "[!] flash={:#010X}-{:#010X} process={:?} - couldn't allocate MPU region for flash", + pb.flash.as_ptr() as usize, + pb.flash.as_ptr() as usize + pb.flash.len() - 1, + process_name + ); } - return Err(ProcessLoadError::MpuInvalidFlashLength); + return Err((ProcessLoadError::MpuInvalidFlashLength, remaining_memory)); } // Determine how much space we need in the application's memory space @@ -1349,8 +1358,17 @@ impl ProcessStandard<'_, C> { // Initial size of the kernel-owned part of process memory can be // calculated directly based on the initial size of all kernel-owned // data structures. - let initial_kernel_memory_size = - grant_ptrs_offset + Self::CALLBACKS_OFFSET + Self::PROCESS_STRUCT_OFFSET; + // + // We require our kernel memory break (located at the end of the + // MPU-returned allocated memory region) to be word-aligned. However, we + // don't have any explicit alignment constraints from the MPU. To ensure + // that the below kernel-owned data structures still fit into the + // kernel-owned memory even with padding for alignment, add an extra + // `sizeof(usize)` bytes. + let initial_kernel_memory_size = grant_ptrs_offset + + Self::CALLBACKS_OFFSET + + Self::PROCESS_STRUCT_OFFSET + + core::mem::size_of::(); // By default we start with the initial size of process-accessible // memory set to 0. This maximizes the flexibility that processes have @@ -1385,8 +1403,7 @@ impl ProcessStandard<'_, C> { // Right now, we only support skipping some RAM and leaving a chunk // unused so that the memory region starts where the process needs it // to. - let remaining_memory = if let Some(fixed_memory_start) = tbf_header.get_fixed_address_ram() - { + let remaining_memory = if let Some(fixed_memory_start) = pb.header.get_fixed_address_ram() { // The process does have a fixed address. if fixed_memory_start == remaining_memory.as_ptr() as u32 { // Address already matches. @@ -1400,34 +1417,48 @@ impl ProcessStandard<'_, C> { let actual_address = remaining_memory.as_ptr() as u32 + remaining_memory.len() as u32 - 1; let expected_address = fixed_memory_start; - return Err(ProcessLoadError::MemoryAddressMismatch { - actual_address, - expected_address, - }); + return Err(( + ProcessLoadError::MemoryAddressMismatch { + actual_address, + expected_address, + }, + remaining_memory, + )); } else { // Change the memory range to start where the process - // requested it. - remaining_memory - .get_mut(diff..) - .ok_or(ProcessLoadError::InternalError)? + // requested it. Because of the if statement above we know this should + // work. Doing it more cleanly would be good but was a bit beyond my borrow + // ken; calling get_mut has a mutable borrow.-pal + &mut remaining_memory[diff..] } } else { // Address is earlier in memory, nothing we can do. let actual_address = remaining_memory.as_ptr() as u32; let expected_address = fixed_memory_start; - return Err(ProcessLoadError::MemoryAddressMismatch { - actual_address, - expected_address, - }); + return Err(( + ProcessLoadError::MemoryAddressMismatch { + actual_address, + expected_address, + }, + remaining_memory, + )); } } else { remaining_memory }; - // Determine where process memory will go and allocate MPU region for - // app-owned memory. - let (app_memory_start, app_memory_size) = match chip.mpu().allocate_app_memory_region( - remaining_memory.as_ptr() as *const u8, + // Determine where process memory will go and allocate an MPU region. + // + // `[allocation_start, allocation_size)` will cover both + // + // - the app-owned `min_process_memory_size`-long part of memory (at + // some offset within `remaining_memory`), as well as + // + // - the kernel-owned allocation growing downward starting at the end + // of this allocation, `initial_kernel_memory_size` bytes long. + // + let (allocation_start, allocation_size) = match chip.mpu().allocate_app_memory_region( + remaining_memory.as_ptr(), remaining_memory.len(), min_total_memory_size, min_process_memory_size, @@ -1440,71 +1471,141 @@ impl ProcessStandard<'_, C> { // Failed to load process. Insufficient memory. if config::CONFIG.debug_load_processes { debug!( - "[!] flash={:#010X}-{:#010X} process={:?} - couldn't allocate memory region of size >= {:#X}", - app_flash.as_ptr() as usize, - app_flash.as_ptr() as usize + app_flash.len() - 1, - process_name, - min_total_memory_size - ); + "[!] flash={:#010X}-{:#010X} process={:?} - couldn't allocate memory region of size >= {:#X}", + pb.flash.as_ptr() as usize, + pb.flash.as_ptr() as usize + pb.flash.len() - 1, + process_name, + min_total_memory_size + ); } - return Err(ProcessLoadError::NotEnoughMemory); + return Err((ProcessLoadError::NotEnoughMemory, remaining_memory)); } }; - // Get a slice for the memory dedicated to the process. This can fail if - // the MPU returns a region of memory that is not inside of the - // `remaining_memory` slice passed to `create()` to allocate the - // process's memory out of. - let memory_start_offset = app_memory_start as usize - remaining_memory.as_ptr() as usize; - // First split the remaining memory into a slice that contains the - // process memory and a slice that will not be used by this process. - let (app_memory_oversize, unused_memory) = - remaining_memory.split_at_mut(memory_start_offset + app_memory_size); - // Then since the process's memory need not start at the beginning of - // the remaining slice given to create(), get a smaller slice as needed. - let app_memory = app_memory_oversize - .get_mut(memory_start_offset..) - .ok_or(ProcessLoadError::InternalError)?; + // Determine the offset of the app-owned part of the above memory + // allocation. An MPU may not place it at the very start of + // `remaining_memory` for internal alignment constraints. This can only + // overflow if the MPU implementation is incorrect; a compliant + // implementation must return a memory allocation within the + // `remaining_memory` slice. + let app_memory_start_offset = + allocation_start as usize - remaining_memory.as_ptr() as usize; // Check if the memory region is valid for the process. If a process // included a fixed address for the start of RAM in its TBF header (this // field is optional, processes that are position independent do not // need a fixed address) then we check that we used the same address // when we allocated it in RAM. - if let Some(fixed_memory_start) = tbf_header.get_fixed_address_ram() { - let actual_address = app_memory.as_ptr() as u32; + if let Some(fixed_memory_start) = pb.header.get_fixed_address_ram() { + let actual_address = remaining_memory.as_ptr() as u32 + app_memory_start_offset as u32; let expected_address = fixed_memory_start; if actual_address != expected_address { - return Err(ProcessLoadError::MemoryAddressMismatch { - actual_address, - expected_address, - }); + return Err(( + ProcessLoadError::MemoryAddressMismatch { + actual_address, + expected_address, + }, + remaining_memory, + )); } } - // Set the initial process-accessible memory to the amount specified by - // the context switch implementation. - let initial_app_brk = app_memory.as_ptr().add(min_process_memory_size); + // With our MPU allocation, we can begin to divide up the + // `remaining_memory` slice into individual regions for the process and + // kernel, as follows: + // + // + // +----------------------------------------------------------------- + // | remaining_memory + // +----------------------------------------------------+------------ + // v v + // +----------------------------------------------------+ + // | allocated_padded_memory | + // +--+-------------------------------------------------+ + // v v + // +-------------------------------------------------+ + // | allocated_memory | + // +-------------------------------------------------+ + // v v + // +-----------------------+-------------------------+ + // | app_accessible_memory | allocated_kernel_memory | + // +-----------------------+-------------------+-----+ + // v + // kernel memory break + // \---+/ + // v + // optional padding + // + // + // First split the `remaining_memory` into two slices: + // + // - `allocated_padded_memory`: the allocated memory region, containing + // + // 1. optional padding at the start of the memory region of + // `app_memory_start_offset` bytes, + // + // 2. the app accessible memory region of `min_process_memory_size`, + // + // 3. optional unallocated memory, and + // + // 4. kernel-reserved memory, growing downward starting at + // `app_memory_padding`. + // + // - `unused_memory`: the rest of the `remaining_memory`, not assigned + // to this app. + // + let (allocated_padded_memory, unused_memory) = + remaining_memory.split_at_mut(app_memory_start_offset + allocation_size); + + // Now, slice off the (optional) padding at the start: + let (_padding, allocated_memory) = + allocated_padded_memory.split_at_mut(app_memory_start_offset); + + // We continue to sub-slice the `allocated_memory` into + // process-accessible and kernel-owned memory. Prior to that, store the + // start and length ofthe overall allocation: + let allocated_memory_start = allocated_memory.as_ptr(); + let allocated_memory_len = allocated_memory.len(); + + // Slice off the process-accessible memory: + let (app_accessible_memory, allocated_kernel_memory) = + allocated_memory.split_at_mut(min_process_memory_size); + + // Set the initial process-accessible memory: + let initial_app_brk = app_accessible_memory + .as_ptr() + .add(app_accessible_memory.len()); // Set the initial allow high water mark to the start of process memory // since no `allow` calls have been made yet. - let initial_allow_high_water_mark = app_memory.as_ptr(); + let initial_allow_high_water_mark = app_accessible_memory.as_ptr(); // Set up initial grant region. - let mut kernel_memory_break = app_memory.as_mut_ptr().add(app_memory.len()); + // + // `kernel_memory_break` is set to the end of kernel-accessible memory + // and grows downward. + // + // We require the `kernel_memory_break` to be aligned to a + // word-boundary, as we rely on this during offset calculations to + // kernel-accessed structs (e.g. the grant pointer table) below. As it + // moves downward in the address space, we can't use the `align_offset` + // convenience functions. + // + // Calling `wrapping_sub` is safe here, as we've factored in an optional + // padding of at most `sizeof(usize)` bytes in the calculation of + // `initial_kernel_memory_size` above. + let mut kernel_memory_break = allocated_kernel_memory + .as_ptr() + .add(allocated_kernel_memory.len()); + + kernel_memory_break = kernel_memory_break + .wrapping_sub(kernel_memory_break as usize % core::mem::size_of::()); - // Now that we know we have the space we can setup the grant - // pointers. + // Now that we know we have the space we can setup the grant pointers. kernel_memory_break = kernel_memory_break.offset(-(grant_ptrs_offset as isize)); - // This is safe today, as MPU constraints ensure that `memory_start` - // will always be aligned on at least a word boundary, and that - // memory_size will be aligned on at least a word boundary, and - // `grant_ptrs_offset` is a multiple of the word size. Thus, - // `kernel_memory_break` must be word aligned. While this is unlikely to - // change, it should be more proactively enforced. - // - // TODO: https://github.com/tock/tock/issues/1739 + // This is safe, `kernel_memory_break` is aligned to a word-boundary, + // and `grant_ptrs_offset` is a multiple of the word size. #[allow(clippy::cast_ptr_alignment)] // Set all grant pointers to null. let grant_pointers = slice::from_raw_parts_mut( @@ -1539,7 +1640,9 @@ impl ProcessStandard<'_, C> { let process_struct_memory_location = kernel_memory_break; // Create the Process struct in the app grant region. - let mut process: &mut ProcessStandard = + // Note that this requires every field be explicitly initialized, as + // we are just transforming a pointer into a structure. + let process: &mut ProcessStandard = &mut *(process_struct_memory_location as *mut ProcessStandard<'static, C>); // Ask the kernel for a unique identifier for this process that is being @@ -1548,27 +1651,29 @@ impl ProcessStandard<'_, C> { // Save copies of these in case the app was compiled for fixed addresses // for later debugging. - let fixed_address_flash = tbf_header.get_fixed_address_flash(); - let fixed_address_ram = tbf_header.get_fixed_address_ram(); + let fixed_address_flash = pb.header.get_fixed_address_flash(); + let fixed_address_ram = pb.header.get_fixed_address_ram(); process .process_id .set(ProcessId::new(kernel, unique_identifier, index)); + process.app_id = app_id; process.kernel = kernel; process.chip = chip; process.allow_high_water_mark = Cell::new(initial_allow_high_water_mark); - process.memory_start = app_memory.as_ptr(); - process.memory_len = app_memory.len(); - process.header = tbf_header; + process.memory_start = allocated_memory_start; + process.memory_len = allocated_memory_len; + process.header = pb.header; process.kernel_memory_break = Cell::new(kernel_memory_break); process.app_break = Cell::new(initial_app_brk); process.grant_pointers = MapCell::new(grant_pointers); - process.flash = app_flash; + process.footers = pb.footers; + process.flash = pb.flash; process.stored_state = MapCell::new(Default::default()); - // Mark this process as unstarted - process.state = ProcessStateCell::new(process.kernel); + // Mark this process as approved and leave it to the kernel to start it. + process.state = Cell::new(State::Yielded); process.fault_policy = fault_policy; process.restart_count = Cell::new(0); process.completion_code = OptionalCell::empty(); @@ -1583,11 +1688,10 @@ impl ProcessStandard<'_, C> { Cell::new(None), ]; process.tasks = MapCell::new(tasks); - process.process_name = process_name.unwrap_or(""); process.debug = MapCell::new(ProcessStandardDebug { - fixed_address_flash: fixed_address_flash, - fixed_address_ram: fixed_address_ram, + fixed_address_flash, + fixed_address_ram, app_heap_start_pointer: None, app_stack_start_pointer: None, app_stack_min_pointer: None, @@ -1597,20 +1701,6 @@ impl ProcessStandard<'_, C> { timeslice_expiration_count: 0, }); - let flash_protected_size = process.header.get_protected_size() as usize; - let flash_app_start_addr = app_flash.as_ptr() as usize + flash_protected_size; - - process.tasks.map(|tasks| { - tasks.enqueue(Task::FunctionCall(FunctionCall { - source: FunctionCallSource::Kernel, - pc: init_fn, - argument0: flash_app_start_addr, - argument1: process.memory_start as usize, - argument2: process.memory_len, - argument3: process.app_break.get() as usize, - })); - }); - // Handle any architecture-specific requirements for a new process. // // NOTE! We have to ensure that the start of process-accessible memory @@ -1621,7 +1711,7 @@ impl ProcessStandard<'_, C> { // TODO: https://github.com/tock/tock/issues/1739 match process.stored_state.map(|stored_state| { chip.userspace_kernel_boundary().initialize_process( - app_memory_start, + app_accessible_memory.as_ptr(), initial_app_brk, stored_state, ) @@ -1631,26 +1721,44 @@ impl ProcessStandard<'_, C> { if config::CONFIG.debug_load_processes { debug!( "[!] flash={:#010X}-{:#010X} process={:?} - couldn't initialize process", - app_flash.as_ptr() as usize, - app_flash.as_ptr() as usize + app_flash.len() - 1, + pb.flash.as_ptr() as usize, + pb.flash.as_ptr() as usize + pb.flash.len() - 1, process_name ); } - return Err(ProcessLoadError::InternalError); + // Note that since remaining_memory was split by split_at_mut into + // application memory and unused_memory, a failure here will leak + // the application memory. Not leaking it requires being able to + // reconstitute the original memory slice. + return Err((ProcessLoadError::InternalError, unused_memory)); } }; - kernel.increment_work(); + let flash_start = process.flash.as_ptr(); + let app_start = + flash_start.wrapping_add(process.header.get_app_start_offset() as usize) as usize; + let init_fn = + flash_start.wrapping_add(process.header.get_init_function_offset() as usize) as usize; + + process.tasks.map(|tasks| { + tasks.enqueue(Task::FunctionCall(FunctionCall { + source: FunctionCallSource::Kernel, + pc: init_fn, + argument0: app_start, + argument1: process.memory_start as usize, + argument2: process.memory_len, + argument3: process.app_break.get() as usize, + })); + }); // Return the process object and a remaining memory for processes slice. Ok((Some(process), unused_memory)) } - /// Restart the process, resetting all of its state and re-initializing it - /// to start running. Assumes the process is not running but is still in - /// flash and still has its memory region allocated to it. This implements - /// the mechanism of restart. - fn restart(&self) -> Result<(), ErrorCode> { + /// Reset the process, resetting all of its state and re-initializing it so + /// it can start running. Assumes the process is not running but is still in + /// flash and still has its memory region allocated to it. + fn reset(&self) -> Result<(), ErrorCode> { // We need a new process identifier for this process since the restarted // version is in effect a new process. This is also necessary to // invalidate any stored `ProcessId`s that point to the old version of @@ -1669,22 +1777,20 @@ impl ProcessStandard<'_, C> { debug.timeslice_expiration_count = 0; }); - // FLASH - - // We are going to start this process over again, so need the init_fn - // location. - let app_flash_address = self.flash_start(); - let init_fn = unsafe { - app_flash_address.offset(self.header.get_init_function_offset() as isize) as usize - }; - // Reset MPU region configuration. // // TODO: ideally, this would be moved into a helper function used by // both create() and reset(), but process load debugging complicates // this. We just want to create new config with only flash and memory // regions. - let mut mpu_config: <::MPU as MPU>::MpuConfig = Default::default(); + // + // We must have a previous MPU configuration stored, fault the + // process if this invariant is violated. We avoid allocating + // a new MPU configuration, as this may eventually exhaust the + // number of available MPU configurations. + let mut mpu_config = self.mpu_config.take().ok_or(ErrorCode::FAIL)?; + self.chip.mpu().reset_config(&mut mpu_config); + // Allocate MPU region for flash. let app_mpu_flash = self.chip.mpu().allocate_region( self.flash.as_ptr(), @@ -1756,7 +1862,7 @@ impl ProcessStandard<'_, C> { // process's memory region. self.allow_high_water_mark.set(app_mpu_mem_start); - // Drop the old config and use the clean one + // Store the adjusted MPU configuration: self.mpu_config.replace(mpu_config); // Handle any architecture-specific requirements for a process when it @@ -1770,7 +1876,7 @@ impl ProcessStandard<'_, C> { }); match ukb_init_process { Ok(()) => {} - Err(_) => { + Err(()) => { // We couldn't initialize the architecture-specific state for // this process. This shouldn't happen since the app was able to // be started before, but at this point the app is no longer @@ -1780,32 +1886,26 @@ impl ProcessStandard<'_, C> { } }; - // And queue up this app to be restarted. - let flash_protected_size = self.header.get_protected_size() as usize; - let flash_app_start = app_flash_address as usize + flash_protected_size; - - // Mark the state as `Unstarted` for the scheduler. - self.state.update(State::Unstarted); - - // Mark that we restarted this process. self.restart_count.increment(); - // Enqueue the initial function. - self.tasks.map(|tasks| { - tasks.enqueue(Task::FunctionCall(FunctionCall { - source: FunctionCallSource::Kernel, - pc: init_fn, - argument0: flash_app_start, - argument1: self.mem_start() as usize, - argument2: self.memory_len, - argument3: self.app_break.get() as usize, - })); - }); - - // Mark that the process is ready to run. - self.kernel.increment_work(); + // Mark the state as `Yielded` for the scheduler. + self.state.set(State::Yielded); - Ok(()) + // And queue up this app to be restarted. + let flash_start = self.flash_start(); + let app_start = + flash_start.wrapping_add(self.header.get_app_start_offset() as usize) as usize; + let init_fn = + flash_start.wrapping_add(self.header.get_init_function_offset() as usize) as usize; + + self.enqueue_task(Task::FunctionCall(FunctionCall { + source: FunctionCallSource::Kernel, + pc: init_fn, + argument0: app_start, + argument1: self.memory_start as usize, + argument2: self.memory_len, + argument3: self.app_break.get() as usize, + })) } /// Checks if the buffer represented by the passed in base pointer and size @@ -1852,7 +1952,7 @@ impl ProcessStandard<'_, C> { /// accessible region from the new kernel memory break after doing the /// allocation, then this will return `None`. fn allocate_in_grant_region_internal(&self, size: usize, align: usize) -> Option> { - self.mpu_config.and_then(|mut config| { + self.mpu_config.and_then(|config| { // First, compute the candidate new pointer. Note that at this point // we have not yet checked whether there is space for this // allocation or that it meets alignment requirements. @@ -1874,15 +1974,15 @@ impl ProcessStandard<'_, C> { // Verify there is space for this allocation if new_break < self.app_break.get() { None - // Verify it didn't wrap around + // Verify it didn't wrap around } else if new_break > self.kernel_memory_break.get() { None - // Verify this is compatible with the MPU. - } else if let Err(_) = self.chip.mpu().update_app_memory_region( + // Verify this is compatible with the MPU. + } else if let Err(()) = self.chip.mpu().update_app_memory_region( self.app_break.get(), new_break, mpu::Permissions::ReadWriteOnly, - &mut config, + config, ) { None } else { @@ -1910,20 +2010,20 @@ impl ProcessStandard<'_, C> { /// /// We create this identifier by calculating the number of bytes between /// where the custom grant starts and the end of the process memory. - fn create_custom_grant_identifier(&self, ptr: NonNull) -> ProcessCustomGrantIdentifer { + fn create_custom_grant_identifier(&self, ptr: NonNull) -> ProcessCustomGrantIdentifier { let custom_grant_address = ptr.as_ptr() as usize; let process_memory_end = self.mem_end() as usize; - ProcessCustomGrantIdentifer { + ProcessCustomGrantIdentifier { offset: process_memory_end - custom_grant_address, } } - /// Use a ProcessCustomGrantIdentifer to find the address of the custom - /// grant. + /// Use a `ProcessCustomGrantIdentifier` to find the address of the + /// custom grant. /// /// This reverses `create_custom_grant_identifier()`. - fn get_custom_grant_address(&self, identifier: ProcessCustomGrantIdentifer) -> usize { + fn get_custom_grant_address(&self, identifier: ProcessCustomGrantIdentifier) -> usize { let process_memory_end = self.mem_end() as usize; // Subtract the offset in the identifier from the end of the process @@ -1931,21 +2031,6 @@ impl ProcessStandard<'_, C> { process_memory_end - identifier.offset } - /// Check if the process is active. - /// - /// "Active" is defined as the process can resume executing in the future. - /// This means its state in the `Process` struct is still valid, and that - /// the kernel could resume its execution without completely restarting and - /// resetting its state. - /// - /// A process is inactive if the kernel cannot resume its execution, such as - /// if the process faults and is in an invalid state, or if the process - /// explicitly exits. - fn is_active(&self) -> bool { - let current_state = self.state.get(); - current_state != State::Terminated && current_state != State::Faulted - } - /// The start address of allocated RAM for this process. fn mem_start(&self) -> *const u8 { self.memory_start diff --git a/kernel/src/process_utilities.rs b/kernel/src/process_utilities.rs deleted file mode 100644 index 0a79d5654c..0000000000 --- a/kernel/src/process_utilities.rs +++ /dev/null @@ -1,311 +0,0 @@ -//! Helper functions related to Tock processes. - -use core::convert::TryInto; -use core::fmt; - -use crate::capabilities::ProcessManagementCapability; -use crate::config; -use crate::debug; -use crate::kernel::Kernel; -use crate::platform::chip::Chip; -use crate::process::Process; -use crate::process_policies::ProcessFaultPolicy; -use crate::process_standard::ProcessStandard; - -/// Errors that can occur when trying to load and create processes. -pub enum ProcessLoadError { - /// The TBF header for the process could not be successfully parsed. - TbfHeaderParseFailure(tock_tbf::types::TbfParseError), - - /// Not enough flash remaining to parse a process and its header. - NotEnoughFlash, - - /// Not enough memory to meet the amount requested by a process. Modify the - /// process to request less memory, flash fewer processes, or increase the - /// size of the region your board reserves for process memory. - NotEnoughMemory, - - /// A process was loaded with a length in flash that the MPU does not - /// support. The fix is probably to correct the process size, but this could - /// also be caused by a bad MPU implementation. - MpuInvalidFlashLength, - - /// A process specified a fixed memory address that it needs its memory - /// range to start at, and the kernel did not or could not give the process - /// a memory region starting at that address. - MemoryAddressMismatch { - actual_address: u32, - expected_address: u32, - }, - - /// A process specified that its binary must start at a particular address, - /// and that is not the address the binary is actually placed at. - IncorrectFlashAddress { - actual_address: u32, - expected_address: u32, - }, - - /// A process requires a newer version of the kernel or did not specify - /// a required version. Processes can include the KernelVersion TBF header stating - /// their compatible kernel version (^major.minor). - /// - /// Boards may not require processes to include the KernelVersion TBF header, and - /// the kernel supports ignoring a missing KernelVersion TBF header. In that case, - /// this error will not be returned for a process missing a KernelVersion TBF - /// header. - /// - /// `version` is the `(major, minor)` kernel version the process indicates it - /// requires. If `version` is `None` then the process did not include the - /// KernelVersion TBF header. - IncompatibleKernelVersion { version: Option<(u16, u16)> }, - - /// Process loading error due (likely) to a bug in the kernel. If you get - /// this error please open a bug report. - InternalError, -} - -impl From for ProcessLoadError { - /// Convert between a TBF Header parse error and a process load error. - /// - /// We note that the process load error is because a TBF header failed to - /// parse, and just pass through the parse error. - fn from(error: tock_tbf::types::TbfParseError) -> Self { - ProcessLoadError::TbfHeaderParseFailure(error) - } -} - -impl fmt::Debug for ProcessLoadError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - ProcessLoadError::TbfHeaderParseFailure(tbf_parse_error) => { - write!(f, "Error parsing TBF header\n")?; - write!(f, "{:?}", tbf_parse_error) - } - - ProcessLoadError::NotEnoughFlash => { - write!(f, "Not enough flash available for app linked list") - } - - ProcessLoadError::NotEnoughMemory => { - write!(f, "Not able to meet memory requirements requested by apps") - } - - ProcessLoadError::MpuInvalidFlashLength => { - write!(f, "App flash length not supported by MPU") - } - - ProcessLoadError::MemoryAddressMismatch { - actual_address, - expected_address, - } => write!( - f, - "App memory does not match requested address Actual:{:#x}, Expected:{:#x}", - actual_address, expected_address - ), - - ProcessLoadError::IncorrectFlashAddress { - actual_address, - expected_address, - } => write!( - f, - "App flash does not match requested address. Actual:{:#x}, Expected:{:#x}", - actual_address, expected_address - ), - - ProcessLoadError::IncompatibleKernelVersion { version } => match version { - Some((major, minor)) => write!( - f, - "Process is incompatible with the kernel. Running: {}.{}, Requested: {}.{}", - crate::MAJOR, - crate::MINOR, - major, - minor - ), - None => write!(f, "Process did not provide a TBF kernel version header"), - }, - - ProcessLoadError::InternalError => write!(f, "Error in kernel. Likely a bug."), - } - } -} - -/// Helper function to load processes from flash into an array of active -/// processes. This is the default template for loading processes, but a board -/// is able to create its own `load_processes()` function and use that instead. -/// -/// Processes are found in flash starting from the given address and iterating -/// through Tock Binary Format (TBF) headers. Processes are given memory out of -/// the `app_memory` buffer until either the memory is exhausted or the -/// allocated number of processes are created. This buffer is a non-static slice, -/// ensuring that this code cannot hold onto the slice past the end of this function -/// (instead, processes store a pointer and length), which necessary for later -/// creation of `ProcessBuffer`s in this memory region to be sound. -/// A reference to each process is stored in the provided `procs` array. -/// How process faults are handled by the -/// kernel must be provided and is assigned to every created process. -/// -/// This function is made `pub` so that board files can use it, but loading -/// processes from slices of flash an memory is fundamentally unsafe. Therefore, -/// we require the `ProcessManagementCapability` to call this function. -/// -/// Returns `Ok(())` if process discovery went as expected. Returns a -/// `ProcessLoadError` if something goes wrong during TBF parsing or process -/// creation. -#[inline(always)] -pub fn load_processes_advanced( - kernel: &'static Kernel, - chip: &'static C, - app_flash: &'static [u8], - app_memory: &mut [u8], // not static, so that process.rs cannot hold on to slice w/o unsafe - procs: &'static mut [Option<&'static dyn Process>], - fault_policy: &'static dyn ProcessFaultPolicy, - require_kernel_version: bool, - _capability: &dyn ProcessManagementCapability, -) -> Result<(), ProcessLoadError> { - if config::CONFIG.debug_load_processes { - debug!( - "Loading processes from flash={:#010X}-{:#010X} into sram={:#010X}-{:#010X}", - app_flash.as_ptr() as usize, - app_flash.as_ptr() as usize + app_flash.len() - 1, - app_memory.as_ptr() as usize, - app_memory.as_ptr() as usize + app_memory.len() - 1 - ); - } - - let mut remaining_flash = app_flash; - let mut remaining_memory = app_memory; - - // Try to discover up to `procs.len()` processes in flash. - let mut index = 0; - while index < procs.len() { - // Get the first eight bytes of flash to check if there is another - // app. - let test_header_slice = match remaining_flash.get(0..8) { - Some(s) => s, - None => { - // Not enough flash to test for another app. This just means - // we are at the end of flash, and there are no more apps to - // load. - return Ok(()); - } - }; - - // Pass the first eight bytes to tbfheader to parse out the length of - // the tbf header and app. We then use those values to see if we have - // enough flash remaining to parse the remainder of the header. - let (version, header_length, entry_length) = match tock_tbf::parse::parse_tbf_header_lengths( - test_header_slice - .try_into() - .or(Err(ProcessLoadError::InternalError))?, - ) { - Ok((v, hl, el)) => (v, hl, el), - Err(tock_tbf::types::InitialTbfParseError::InvalidHeader(entry_length)) => { - // If we could not parse the header, then we want to skip over - // this app and look for the next one. - (0, 0, entry_length) - } - Err(tock_tbf::types::InitialTbfParseError::UnableToParse) => { - // Since Tock apps use a linked list, it is very possible the - // header we started to parse is intentionally invalid to signal - // the end of apps. This is ok and just means we have finished - // loading apps. - return Ok(()); - } - }; - - // Now we can get a slice which only encompasses the length of flash - // described by this tbf header. We will either parse this as an actual - // app, or skip over this region. - let entry_flash = remaining_flash - .get(0..entry_length as usize) - .ok_or(ProcessLoadError::NotEnoughFlash)?; - - // Advance the flash slice for process discovery beyond this last entry. - // This will be the start of where we look for a new process since Tock - // processes are allocated back-to-back in flash. - remaining_flash = remaining_flash - .get(entry_flash.len()..) - .ok_or(ProcessLoadError::NotEnoughFlash)?; - - // Need to reassign remaining_memory in every iteration so the compiler - // knows it will not be re-borrowed. - remaining_memory = if header_length > 0 { - // If we found an actual app header, try to create a `Process` - // object. We also need to shrink the amount of remaining memory - // based on whatever is assigned to the new process if one is - // created. - - // Try to create a process object from that app slice. If we don't - // get a process and we didn't get a loading error (aka we got to - // this point), then the app is a disabled process or just padding. - let (process_option, unused_memory) = unsafe { - ProcessStandard::create( - kernel, - chip, - entry_flash, - header_length as usize, - version, - remaining_memory, - fault_policy, - require_kernel_version, - index, - )? - }; - process_option.map(|process| { - if config::CONFIG.debug_load_processes { - let addresses = process.get_addresses(); - debug!( - "Loaded process[{}] from flash={:#010X}-{:#010X} into sram={:#010X}-{:#010X} = {:?}", - index, - entry_flash.as_ptr() as usize, - entry_flash.as_ptr() as usize + entry_flash.len() - 1, - addresses.sram_start, - addresses.sram_end - 1, - process.get_process_name() - ); - } - - // Save the reference to this process in the processes array. - procs[index] = Some(process); - // Can now increment index to use the next spot in the processes - // array. Padding apps mean we might detect valid headers but - // not actually insert a new process in the array. - index += 1; - }); - unused_memory - } else { - // We are just skipping over this region of flash, so we have the - // same amount of process memory to allocate from. - remaining_memory - }; - } - - Ok(()) -} - -/// This is a wrapper function for `load_processes_advanced` that uses -/// the default arguments that mainstream boards should provide. -/// -/// Default arguments are: -/// - `require_kernel_version`: prevent loading processes that do not provide a `KernelVersion` -#[inline(always)] -pub fn load_processes( - kernel: &'static Kernel, - chip: &'static C, - app_flash: &'static [u8], - app_memory: &mut [u8], // not static, so that process.rs cannot hold on to slice w/o unsafe - procs: &'static mut [Option<&'static dyn Process>], - fault_policy: &'static dyn ProcessFaultPolicy, - capability: &dyn ProcessManagementCapability, -) -> Result<(), ProcessLoadError> { - load_processes_advanced( - kernel, - chip, - app_flash, - app_memory, - procs, - fault_policy, - true, - capability, - ) -} diff --git a/kernel/src/processbuffer.rs b/kernel/src/processbuffer.rs index f3b5a79c05..6ad9b00e30 100644 --- a/kernel/src/processbuffer.rs +++ b/kernel/src/processbuffer.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Data structures for passing application memory to the kernel. //! //! A Tock process can pass read-write or read-only buffers into the @@ -29,7 +33,7 @@ use crate::process::{self, ProcessId}; use crate::ErrorCode; /// Convert a process buffer's internal representation to a -/// ReadableProcessSlice. +/// [`ReadableProcessSlice`]. /// /// This function will automatically convert zero-length process /// buffers into valid zero-sized Rust slices regardless of the value @@ -80,7 +84,7 @@ unsafe fn raw_processbuf_to_roprocessslice<'a>( } /// Convert an process buffers's internal representation to a -/// WriteableProcessSlice. +/// [`WriteableProcessSlice`]. /// /// This function will automatically convert zero-length process /// buffers into valid zero-sized Rust slices regardless of the value @@ -214,7 +218,7 @@ pub trait WriteableProcessBuffer: ReadableProcessBuffer { F: FnOnce(&WriteableProcessSlice) -> R; } -/// Read-only buffer shared by a userspace process +/// Read-only buffer shared by a userspace process. /// /// This struct is provided to capsules when a process `allow`s a /// particular section of its memory to the kernel and gives the @@ -305,19 +309,25 @@ impl ReadOnlyProcessBuffer { } impl ReadableProcessBuffer for ReadOnlyProcessBuffer { + /// Return the length of the buffer in bytes. fn len(&self) -> usize { self.process_id .map_or(0, |pid| pid.kernel.process_map_or(0, pid, |_| self.len)) } + /// Return the pointer to the start of the buffer. fn ptr(&self) -> *const u8 { if self.len == 0 { - 0x0 as *const u8 + core::ptr::null::() } else { self.ptr } } + /// Access the contents of the buffer in a closure. + /// + /// This verifies the process is still valid before accessing the underlying + /// memory. fn enter(&self, fun: F) -> Result where F: FnOnce(&ReadableProcessSlice) -> R, @@ -354,14 +364,14 @@ impl ReadableProcessBuffer for ReadOnlyProcessBuffer { impl Default for ReadOnlyProcessBuffer { fn default() -> Self { ReadOnlyProcessBuffer { - ptr: 0x0 as *mut u8, + ptr: core::ptr::null_mut::(), len: 0, process_id: None, } } } -/// Provides access to a ReadOnlyProcessBuffer with a restricted lifetime. +/// Provides access to a [`ReadOnlyProcessBuffer`] with a restricted lifetime. /// This automatically dereferences into a ReadOnlyProcessBuffer pub struct ReadOnlyProcessBufferRef<'a> { buf: ReadOnlyProcessBuffer, @@ -393,7 +403,7 @@ impl Deref for ReadOnlyProcessBufferRef<'_> { } } -/// Read-writable buffer shared by a userspace process +/// Read-writable buffer shared by a userspace process. /// /// This struct is provided to capsules when a process `allows` a /// particular section of its memory to the kernel and gives the @@ -506,19 +516,25 @@ impl ReadWriteProcessBuffer { } impl ReadableProcessBuffer for ReadWriteProcessBuffer { + /// Return the length of the buffer in bytes. fn len(&self) -> usize { self.process_id .map_or(0, |pid| pid.kernel.process_map_or(0, pid, |_| self.len)) } + /// Return the pointer to the start of the buffer. fn ptr(&self) -> *const u8 { if self.len == 0 { - 0x0 as *const u8 + core::ptr::null::() } else { self.ptr } } + /// Access the contents of the buffer in a closure. + /// + /// This verifies the process is still valid before accessing the underlying + /// memory. fn enter(&self, fun: F) -> Result where F: FnOnce(&ReadableProcessSlice) -> R, @@ -592,7 +608,7 @@ impl Default for ReadWriteProcessBuffer { } } -/// Provides access to a ReadWriteProcessBuffer with a restricted lifetime. +/// Provides access to a [`ReadWriteProcessBuffer`] with a restricted lifetime. /// This automatically dereferences into a ReadWriteProcessBuffer pub struct ReadWriteProcessBufferRef<'a> { buf: ReadWriteProcessBuffer, @@ -661,7 +677,7 @@ impl ReadableProcessByte { } } -/// Readable and accessible slice of memory of a process buffer +/// Readable and accessible slice of memory of a process buffer. /// /// /// The only way to obtain this struct is through a @@ -676,9 +692,7 @@ pub struct ReadableProcessSlice { slice: [ReadableProcessByte], } -fn cast_byte_slice_to_process_slice<'a>( - byte_slice: &'a [ReadableProcessByte], -) -> &'a ReadableProcessSlice { +fn cast_byte_slice_to_process_slice(byte_slice: &[ReadableProcessByte]) -> &ReadableProcessSlice { // As ReadableProcessSlice is a transparent wrapper around its inner type, // [ReadableProcessByte], we can safely transmute a reference to the inner // type as a reference to the outer type with the same lifetime. @@ -766,14 +780,17 @@ impl ReadableProcessSlice { } } + /// Return the length of the slice in bytes. pub fn len(&self) -> usize { self.slice.len() } + /// Return an iterator over the bytes of the slice. pub fn iter(&self) -> core::slice::Iter<'_, ReadableProcessByte> { self.slice.iter() } + /// Iterate the slice in chunks. pub fn chunks( &self, chunk_size: usize, @@ -783,6 +800,8 @@ impl ReadableProcessSlice { .map(cast_byte_slice_to_process_slice) } + /// Access a portion of the slice with bounds checking. If the access is not + /// within the slice then `None` is returned. pub fn get(&self, range: Range) -> Option<&ReadableProcessSlice> { if let Some(slice) = self.slice.get(range) { Some(cast_byte_slice_to_process_slice(slice)) @@ -791,6 +810,8 @@ impl ReadableProcessSlice { } } + /// Access a portion of the slice with bounds checking. If the access is not + /// within the slice then `None` is returned. pub fn get_from(&self, range: RangeFrom) -> Option<&ReadableProcessSlice> { if let Some(slice) = self.slice.get(range) { Some(cast_byte_slice_to_process_slice(slice)) @@ -799,6 +820,8 @@ impl ReadableProcessSlice { } } + /// Access a portion of the slice with bounds checking. If the access is not + /// within the slice then `None` is returned. pub fn get_to(&self, range: RangeTo) -> Option<&ReadableProcessSlice> { if let Some(slice) = self.slice.get(range) { Some(cast_byte_slice_to_process_slice(slice)) @@ -863,7 +886,7 @@ pub struct WriteableProcessSlice { slice: [Cell], } -fn cast_cell_slice_to_process_slice<'a>(cell_slice: &'a [Cell]) -> &'a WriteableProcessSlice { +fn cast_cell_slice_to_process_slice(cell_slice: &[Cell]) -> &WriteableProcessSlice { // # Safety // // As WriteableProcessSlice is a transparent wrapper around its inner type, @@ -960,7 +983,7 @@ impl WriteableProcessSlice { #[track_caller] fn len_mismatch_fail(dst_len: usize, src_len: usize) -> ! { panic!( - "source slice length ({}) does not match destination slice length ({})", + "src slice len ({}) != dest slice len ({})", src_len, dst_len, ); } @@ -994,14 +1017,17 @@ impl WriteableProcessSlice { } } + /// Return the length of the slice in bytes. pub fn len(&self) -> usize { self.slice.len() } + /// Return an iterator over the slice. pub fn iter(&self) -> core::slice::Iter<'_, Cell> { self.slice.iter() } + /// Iterate over the slice in chunks. pub fn chunks( &self, chunk_size: usize, @@ -1011,6 +1037,8 @@ impl WriteableProcessSlice { .map(cast_cell_slice_to_process_slice) } + /// Access a portion of the slice with bounds checking. If the access is not + /// within the slice then `None` is returned. pub fn get(&self, range: Range) -> Option<&WriteableProcessSlice> { if let Some(slice) = self.slice.get(range) { Some(cast_cell_slice_to_process_slice(slice)) @@ -1019,6 +1047,8 @@ impl WriteableProcessSlice { } } + /// Access a portion of the slice with bounds checking. If the access is not + /// within the slice then `None` is returned. pub fn get_from(&self, range: RangeFrom) -> Option<&WriteableProcessSlice> { if let Some(slice) = self.slice.get(range) { Some(cast_cell_slice_to_process_slice(slice)) @@ -1027,6 +1057,8 @@ impl WriteableProcessSlice { } } + /// Access a portion of the slice with bounds checking. If the access is not + /// within the slice then `None` is returned. pub fn get_to(&self, range: RangeTo) -> Option<&WriteableProcessSlice> { if let Some(slice) = self.slice.get(range) { Some(cast_cell_slice_to_process_slice(slice)) diff --git a/kernel/src/scheduler.rs b/kernel/src/scheduler.rs index c07a361249..3d3e37e31b 100644 --- a/kernel/src/scheduler.rs +++ b/kernel/src/scheduler.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interface for Tock kernel schedulers. pub mod cooperative; @@ -5,11 +9,10 @@ pub mod mlfq; pub mod priority; pub mod round_robin; -use crate::dynamic_deferred_call::DynamicDeferredCall; -use crate::kernel::StoppedExecutingReason; +use crate::deferred_call::DeferredCall; use crate::platform::chip::Chip; use crate::process::ProcessId; -use crate::Kernel; +use crate::process::StoppedExecutingReason; /// Trait which any scheduler must implement. pub trait Scheduler { @@ -24,7 +27,7 @@ pub trait Scheduler { /// process. If the timeslice is `None`, the process will be run /// cooperatively (i.e. without preemption). Otherwise the process will run /// with a timeslice set to the specified length. - fn next(&self, kernel: &Kernel) -> SchedulingDecision; + fn next(&self) -> SchedulingDecision; /// Inform the scheduler of why the last process stopped executing, and how /// long it executed for. Notably, `execution_time_us` will be `None` @@ -47,7 +50,9 @@ pub trait Scheduler { /// as this function is called in the core kernel loop. unsafe fn execute_kernel_work(&self, chip: &C) { chip.service_pending_interrupts(); - DynamicDeferredCall::call_global_instance_while(|| !chip.has_pending_interrupts()); + while DeferredCall::has_tasks() && !chip.has_pending_interrupts() { + DeferredCall::service_next_pending(); + } } /// Ask the scheduler whether to take a break from executing userspace @@ -55,8 +60,7 @@ pub trait Scheduler { /// implementation, which always prioritizes kernel work, but schedulers /// that wish to defer interrupt handling may reimplement it. unsafe fn do_kernel_work_now(&self, chip: &C) -> bool { - chip.has_pending_interrupts() - || DynamicDeferredCall::global_instance_calls_pending().unwrap_or(false) + chip.has_pending_interrupts() || DeferredCall::has_tasks() } /// Ask the scheduler whether to continue trying to execute a process. @@ -76,8 +80,7 @@ pub trait Scheduler { /// /// `id` is the identifier of the currently active process. unsafe fn continue_process(&self, _id: ProcessId, chip: &C) -> bool { - !(chip.has_pending_interrupts() - || DynamicDeferredCall::global_instance_calls_pending().unwrap_or(false)) + !(chip.has_pending_interrupts() || DeferredCall::has_tasks()) } } diff --git a/kernel/src/scheduler/cooperative.rs b/kernel/src/scheduler/cooperative.rs index 8733981438..25ddfbefbd 100644 --- a/kernel/src/scheduler/cooperative.rs +++ b/kernel/src/scheduler/cooperative.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Cooperative Scheduler for Tock //! //! This scheduler runs all processes in a round-robin fashion, but does not use @@ -12,9 +16,9 @@ //! that was executing. use crate::collections::list::{List, ListLink, ListNode}; -use crate::kernel::{Kernel, StoppedExecutingReason}; use crate::platform::chip::Chip; use crate::process::Process; +use crate::process::StoppedExecutingReason; use crate::scheduler::{Scheduler, SchedulingDecision}; /// A node in the linked list the scheduler uses to track processes @@ -52,33 +56,40 @@ impl<'a> CooperativeSched<'a> { } impl<'a, C: Chip> Scheduler for CooperativeSched<'a> { - fn next(&self, kernel: &Kernel) -> SchedulingDecision { - if kernel.processes_blocked() { - // No processes ready - SchedulingDecision::TrySleep - } else { - let mut next = None; // This will be replaced, bc a process is guaranteed - // to be ready if processes_blocked() is false + fn next(&self) -> SchedulingDecision { + let mut first_head = None; + let mut next = None; - // Find next ready process. Place any *empty* process slots, or not-ready - // processes, at the back of the queue. - for node in self.processes.iter() { - match node.proc { - Some(proc) => { - if proc.ready() { - next = Some(proc.processid()); - break; - } - self.processes.push_tail(self.processes.pop_head().unwrap()); + // Find the first ready process in the queue. Place any *empty* process slots, + // or not-ready processes, at the back of the queue. + while let Some(node) = self.processes.head() { + // Ensure we do not loop forever if all processes are not not ready + match first_head { + None => first_head = Some(node), + Some(first_head) => { + // We make a full iteration and nothing was ready. Try to sleep instead + if core::ptr::eq(first_head, node) { + return SchedulingDecision::TrySleep; } - None => { - self.processes.push_tail(self.processes.pop_head().unwrap()); + } + } + match node.proc { + Some(proc) => { + if proc.ready() { + next = Some(proc.processid()); + break; } + self.processes.push_tail(self.processes.pop_head().unwrap()); + } + None => { + self.processes.push_tail(self.processes.pop_head().unwrap()); } } - - SchedulingDecision::RunProcess((next.unwrap(), None)) } + + // next will not be None, because if we make a full iteration and nothing + // is ready we return early + SchedulingDecision::RunProcess((next.unwrap(), None)) } fn result(&self, result: StoppedExecutingReason, _: Option) { diff --git a/kernel/src/scheduler/mlfq.rs b/kernel/src/scheduler/mlfq.rs index 59f0f7f5a5..0c10cb0479 100644 --- a/kernel/src/scheduler/mlfq.rs +++ b/kernel/src/scheduler/mlfq.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Multilevel feedback queue scheduler for Tock //! //! Based on the MLFQ rules described in "Operating Systems: Three Easy Pieces" @@ -21,10 +25,10 @@ use core::cell::Cell; use crate::collections::list::{List, ListLink, ListNode}; use crate::hil::time::{self, ConvertTicks, Ticks}; -use crate::kernel::{Kernel, StoppedExecutingReason}; use crate::platform::chip::Chip; use crate::process::Process; use crate::process::ProcessId; +use crate::process::StoppedExecutingReason; use crate::scheduler::{Scheduler, SchedulingDecision}; #[derive(Default)] @@ -51,7 +55,7 @@ impl<'a> MLFQProcessNode<'a> { } impl<'a> ListNode<'a, MLFQProcessNode<'a>> for MLFQProcessNode<'a> { - fn next(&'a self) -> &'static ListLink<'a, MLFQProcessNode<'a>> { + fn next(&'a self) -> &'a ListLink<'a, MLFQProcessNode<'a>> { &self.next } } @@ -91,12 +95,7 @@ impl<'a, A: 'static + time::Alarm<'static>> MLFQSched<'a, A> { } fn redeem_all_procs(&self) { - let mut first = true; - for queue in self.processes.iter() { - if first { - continue; - } - first = false; + for queue in self.processes.iter().skip(1) { match queue.pop_head() { Some(proc) => self.processes[0].push_tail(proc), None => continue, @@ -116,17 +115,14 @@ impl<'a, A: 'static + time::Alarm<'static>> MLFQSched<'a, A> { // pop procs to back until we get to match loop { let cur = queue.pop_head(); - match cur { - Some(node) => { - if node as *const _ == next.unwrap() as *const _ { - queue.push_head(node); - // match! Put back on front - return (next, idx); - } else { - queue.push_tail(node); - } + if let Some(node) = cur { + if core::ptr::eq(node, next.unwrap()) { + queue.push_head(node); + // match! Put back on front + return (next, idx); + } else { + queue.push_tail(node); } - None => {} } } } @@ -136,35 +132,31 @@ impl<'a, A: 'static + time::Alarm<'static>> MLFQSched<'a, A> { } impl<'a, A: 'static + time::Alarm<'static>, C: Chip> Scheduler for MLFQSched<'a, A> { - fn next(&self, kernel: &Kernel) -> SchedulingDecision { - if kernel.processes_blocked() { - // No processes ready - SchedulingDecision::TrySleep - } else { - let now = self.alarm.now(); - let next_reset = self.next_reset.get(); - let last_reset_check = self.last_reset_check.get(); - - // storing last reset check is necessary to avoid missing a reset when the underlying - // alarm wraps around - if !now.within_range(last_reset_check, next_reset) { - // Promote all processes to highest priority queue - self.next_reset.set( - now.wrapping_add(self.alarm.ticks_from_ms(Self::PRIORITY_REFRESH_PERIOD_MS)), - ); - self.redeem_all_procs(); - } - self.last_reset_check.set(now); - let (node_ref_opt, queue_idx) = self.get_next_ready_process_node(); - let node_ref = node_ref_opt.unwrap(); // Panic if fail bc processes_blocked()! - let timeslice = - self.get_timeslice_us(queue_idx) - node_ref.state.us_used_this_queue.get(); - let next = node_ref.proc.unwrap().processid(); // Panic if fail bc processes_blocked()! - self.last_queue_idx.set(queue_idx); - self.last_timeslice.set(timeslice); - - SchedulingDecision::RunProcess((next, Some(timeslice))) + fn next(&self) -> SchedulingDecision { + let now = self.alarm.now(); + let next_reset = self.next_reset.get(); + let last_reset_check = self.last_reset_check.get(); + + // storing last reset check is necessary to avoid missing a reset when the underlying + // alarm wraps around + if !now.within_range(last_reset_check, next_reset) { + // Promote all processes to highest priority queue + self.next_reset + .set(now.wrapping_add(self.alarm.ticks_from_ms(Self::PRIORITY_REFRESH_PERIOD_MS))); + self.redeem_all_procs(); + } + self.last_reset_check.set(now); + let (node_ref_opt, queue_idx) = self.get_next_ready_process_node(); + if node_ref_opt.is_none() { + return SchedulingDecision::TrySleep; } + let node_ref = node_ref_opt.unwrap(); + let timeslice = self.get_timeslice_us(queue_idx) - node_ref.state.us_used_this_queue.get(); + let next = node_ref.proc.unwrap().processid(); + self.last_queue_idx.set(queue_idx); + self.last_timeslice.set(timeslice); + + SchedulingDecision::RunProcess((next, Some(timeslice))) } fn result(&self, result: StoppedExecutingReason, execution_time_us: Option) { diff --git a/kernel/src/scheduler/priority.rs b/kernel/src/scheduler/priority.rs index fd35504820..c6b6bb8b39 100644 --- a/kernel/src/scheduler/priority.rs +++ b/kernel/src/scheduler/priority.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Fixed Priority Scheduler for Tock //! //! This scheduler assigns priority to processes based on their order in the @@ -10,10 +14,11 @@ //! is running. The only way for a process to longer be the highest priority is //! for an interrupt to occur, which will cause the process to stop running. -use crate::dynamic_deferred_call::DynamicDeferredCall; -use crate::kernel::{Kernel, StoppedExecutingReason}; +use crate::deferred_call::DeferredCall; +use crate::kernel::Kernel; use crate::platform::chip::Chip; use crate::process::ProcessId; +use crate::process::StoppedExecutingReason; use crate::scheduler::{Scheduler, SchedulingDecision}; use crate::utilities::cells::OptionalCell; @@ -33,23 +38,20 @@ impl PrioritySched { } impl Scheduler for PrioritySched { - fn next(&self, kernel: &Kernel) -> SchedulingDecision { - if kernel.processes_blocked() { - // No processes ready - SchedulingDecision::TrySleep - } else { - // Iterates in-order through the process array, always running the - // first process it finds that is ready to run. This enforces the - // priorities of all processes. - let next = self - .kernel - .get_process_iter() - .find(|&proc| proc.ready()) - .map_or(None, |proc| Some(proc.processid())); - self.running.insert(next); + fn next(&self) -> SchedulingDecision { + // Iterates in-order through the process array, always running the + // first process it finds that is ready to run. This enforces the + // priorities of all processes. + let next = self + .kernel + .get_process_iter() + .find(|&proc| proc.ready()) + .map(|proc| proc.processid()); + self.running.insert(next); - SchedulingDecision::RunProcess((next.unwrap(), None)) - } + next.map_or(SchedulingDecision::TrySleep, |next| { + SchedulingDecision::RunProcess((next, None)) + }) } unsafe fn continue_process(&self, _: ProcessId, chip: &C) -> bool { @@ -58,7 +60,7 @@ impl Scheduler for PrioritySched { // a system call by this process could make another process ready, if // this app is communicating via IPC with a higher priority app. !(chip.has_pending_interrupts() - || DynamicDeferredCall::global_instance_calls_pending().unwrap_or(false) + || DeferredCall::has_tasks() || self .kernel .get_process_iter() diff --git a/kernel/src/scheduler/round_robin.rs b/kernel/src/scheduler/round_robin.rs index 6d17ee4bf5..464c3f7382 100644 --- a/kernel/src/scheduler/round_robin.rs +++ b/kernel/src/scheduler/round_robin.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Round Robin Scheduler for Tock //! //! This scheduler is specifically a Round Robin Scheduler with Interrupts. @@ -17,9 +21,9 @@ use core::cell::Cell; use crate::collections::list::{List, ListLink, ListNode}; -use crate::kernel::{Kernel, StoppedExecutingReason}; use crate::platform::chip::Chip; use crate::process::Process; +use crate::process::StoppedExecutingReason; use crate::scheduler::{Scheduler, SchedulingDecision}; /// A node in the linked list the scheduler uses to track processes @@ -47,6 +51,7 @@ impl<'a> ListNode<'a, RoundRobinProcessNode<'a>> for RoundRobinProcessNode<'a> { /// Round Robin Scheduler pub struct RoundRobinSched<'a> { time_remaining: Cell, + timeslice_length: u32, pub processes: List<'a, RoundRobinProcessNode<'a>>, last_rescheduled: Cell, } @@ -55,8 +60,13 @@ impl<'a> RoundRobinSched<'a> { /// How long a process can run before being pre-empted const DEFAULT_TIMESLICE_US: u32 = 10000; pub const fn new() -> RoundRobinSched<'a> { + Self::new_with_time(Self::DEFAULT_TIMESLICE_US) + } + + pub const fn new_with_time(time_us: u32) -> RoundRobinSched<'a> { RoundRobinSched { - time_remaining: Cell::new(Self::DEFAULT_TIMESLICE_US), + time_remaining: Cell::new(time_us), + timeslice_length: time_us, processes: List::new(), last_rescheduled: Cell::new(false), } @@ -64,41 +74,55 @@ impl<'a> RoundRobinSched<'a> { } impl<'a, C: Chip> Scheduler for RoundRobinSched<'a> { - fn next(&self, kernel: &Kernel) -> SchedulingDecision { - if kernel.processes_blocked() { - // No processes ready - SchedulingDecision::TrySleep - } else { - let mut next = None; // This will be replaced, bc a process is guaranteed - // to be ready if processes_blocked() is false + fn next(&self) -> SchedulingDecision { + let mut first_head = None; + let mut next = None; - // Find next ready process. Place any *empty* process slots, or not-ready - // processes, at the back of the queue. - for node in self.processes.iter() { - match node.proc { - Some(proc) => { - if proc.ready() { - next = Some(proc.processid()); - break; - } - self.processes.push_tail(self.processes.pop_head().unwrap()); + // Find the first ready process in the queue. Place any *empty* process slots, + // or not-ready processes, at the back of the queue. + while let Some(node) = self.processes.head() { + // Ensure we do not loop forever if all processes are not ready + match first_head { + None => first_head = Some(node), + Some(first_head) => { + // We made a full iteration and nothing was ready. Try to sleep instead + if core::ptr::eq(first_head, node) { + return SchedulingDecision::TrySleep; } - None => { - self.processes.push_tail(self.processes.pop_head().unwrap()); + } + } + match node.proc { + Some(proc) => { + if proc.ready() { + next = Some(proc.processid()); + break; } + self.processes.push_tail(self.processes.pop_head().unwrap()); + } + None => { + self.processes.push_tail(self.processes.pop_head().unwrap()); } } - let timeslice = if self.last_rescheduled.get() { - self.time_remaining.get() - } else { - // grant a fresh timeslice - self.time_remaining.set(Self::DEFAULT_TIMESLICE_US); - Self::DEFAULT_TIMESLICE_US - }; - assert!(timeslice != 0); - - SchedulingDecision::RunProcess((next.unwrap(), Some(timeslice))) } + + let next = match next { + Some(p) => p, + None => { + // No processes on the system + return SchedulingDecision::TrySleep; + } + }; + + let timeslice = if self.last_rescheduled.get() { + self.time_remaining.get() + } else { + // grant a fresh timeslice + self.time_remaining.set(self.timeslice_length); + self.timeslice_length + }; + assert!(timeslice != 0); + + SchedulingDecision::RunProcess((next, Some(timeslice))) } fn result(&self, result: StoppedExecutingReason, execution_time_us: Option) { diff --git a/kernel/src/storage_permissions.rs b/kernel/src/storage_permissions.rs new file mode 100644 index 0000000000..c18783cd11 --- /dev/null +++ b/kernel/src/storage_permissions.rs @@ -0,0 +1,129 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Mechanism for managing storage read & write permissions. +//! +//! These permissions are intended for userspace applications so the kernel can +//! restrict which stored elements the apps have access to. + +use core::cmp; +use core::num::NonZeroU32; + +use crate::capabilities; + +/// List of storage permissions for a storage user. +/// +/// These identifiers signify what permissions a storage user has. The storage +/// mechanism defines how the identifiers are assigned and how they relate to +/// stored objects. +/// +/// For simplicity, a we store to eight read and eight write permissions. The +/// first `count` `u32` values in `permissions` are valid. +/// +/// Mar, 2022: This interface is considered experimental and for initial +/// prototyping. As we learn more about how these permissions are set and used +/// we may want to revamp this interface. +#[derive(Clone, Copy)] +pub struct StoragePermissions { + /// How many entries in the `read_permissions` slice are valid, starting at + /// index 0. + read_count: usize, + /// Up to eight 32 bit identifiers of storage items the process has read + /// access to. + read_permissions: [u32; 8], + /// How many entries in the `modify_permissions` slice are valid, starting + /// at index 0. + modify_count: usize, + /// Up to eight 32 bit identifiers of storage items the process has modify + /// (update) access to. + modify_permissions: [u32; 8], + /// The identifier for this storage user when creating new objects. If + /// `None` there is no `write_id` for these permissions. + write_id: Option, + /// If `kerneluser` is true, this permission grants access to all objects + /// stored stored with `write_id` 0. New items created with `kerneluser == + /// true` will use the specified ID if `write_id.is_some()`, otherwise new + /// items will be created with the reserved ID (i.e., 0). + kerneluser: bool, +} + +impl StoragePermissions { + pub(crate) fn new( + read_count: usize, + read_permissions: [u32; 8], + modify_count: usize, + modify_permissions: [u32; 8], + write_id: Option, + ) -> Self { + let read_count_capped = cmp::min(read_count, 8); + let modify_count_capped = cmp::min(modify_count, 8); + StoragePermissions { + read_count: read_count_capped, + read_permissions, + modify_count: modify_count_capped, + modify_permissions, + write_id, + kerneluser: false, + } + } + + /// Create superuser permissions suitable for the kernel. This allows the + /// kernel to read/update any stored item, and allows the kernel to write + /// items that will not be accessible to any clients without superuser + /// permissions. + pub fn new_kernel_permissions(_cap: &dyn capabilities::KerneluserStorageCapability) -> Self { + let read_permissions: [u32; 8] = [0; 8]; + let modify_permissions: [u32; 8] = [0; 8]; + StoragePermissions { + read_count: 0, + read_permissions, + modify_count: 0, + modify_permissions, + write_id: None, + kerneluser: true, + } + } + + /// Check if this permission object grants read access to the specified + /// `storage_id`. Returns `true` if access is permitted, `false` otherwise. + pub fn check_read_permission(&self, storage_id: u32) -> bool { + if storage_id == 0 { + // Only kerneluser can read ID 0. + self.kerneluser + } else { + // Otherwise check if given storage_id is in read permissions + // array. + self.read_permissions + .get(0..self.read_count) + .unwrap_or(&[]) + .contains(&storage_id) + } + } + + /// Check if this permission object grants modify access to the specified + /// `storage_id`. Returns `true` if access is permitted, `false` otherwise. + pub fn check_write_permission(&self, storage_id: u32) -> bool { + if storage_id == 0 { + // Only kerneluser can access ID 0. + self.kerneluser + } else { + // Otherwise check if given storage_id is in read permissions + // array. + self.modify_permissions + .get(0..self.modify_count) + .unwrap_or(&[]) + .contains(&storage_id) + } + } + + /// Get the `write_id` for saving items to the storage. + pub fn get_write_id(&self) -> Option { + if self.kerneluser { + // If kerneluser, write_id is 0 unless specifically set. + Some(self.write_id.map_or(0, |wid| wid.get())) + } else { + self.write_id.map(|wid| wid.get()) + } + } +} diff --git a/kernel/src/syscall.rs b/kernel/src/syscall.rs index 5e6f2bbfc3..e8975f4a7c 100644 --- a/kernel/src/syscall.rs +++ b/kernel/src/syscall.rs @@ -1,6 +1,71 @@ -//! Tock syscall number definitions and arch-agnostic interface trait. +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Mechanisms for handling and defining system calls. +//! +//! # System Call Overview +//! +//! Tock supports six system calls. The `allow_readonly`, `allow_readwrite`, +//! `subscribe`, `yield`, and `memop` system calls are handled by the core +//! kernel, while `command` is implemented by drivers. The main system calls: +//! +//! - `subscribe` passes a upcall to the driver which it can invoke on the +//! process later, when an event has occurred or data of interest is +//! available. +//! - `command` tells the driver to do something immediately. +//! - `allow_readwrite` provides the driver read-write access to an application +//! buffer. +//! - `allow_userspace_readable` provides the driver read-write access to an +//! application buffer that is still shared with the app. +//! - `allow_readonly` provides the driver read-only access to an application +//! buffer. +//! +//! ## Mapping system-calls to drivers +//! +//! Each of these three system calls takes at least two parameters. The first is +//! a _driver identifier_ and tells the scheduler which driver to forward the +//! system call to. The second parameters is a __syscall number_ and is used by +//! the driver to differentiate instances of the call with different +//! driver-specific meanings (e.g. `subscribe` for "data received" vs +//! `subscribe` for "send completed"). The mapping between _driver identifiers_ +//! and drivers is determined by a particular platform, while the _syscall +//! number_ is driver-specific. +//! +//! One convention in Tock is that _driver minor number_ 0 for the `command` +//! syscall can always be used to determine if the driver is supported by the +//! running kernel by checking the return code. If the return value is greater +//! than or equal to zero then the driver is present. Typically this is +//! implemented by a null command that only returns 0, but in some cases the +//! command can also return more information, like the number of supported +//! devices (useful for things like the number of LEDs). +//! +//! # The `yield` system call class +//! +//! While drivers do not handle `yield` system calls, it is important to +//! understand them and how they interact with `subscribe`, which registers +//! upcall functions with the kernel. When a process calls a `yield` system +//! call, the kernel checks if there are any pending upcalls for the process. If +//! there are pending upcalls, it pushes one upcall onto the process stack. If +//! there are no pending upcalls, `yield-wait` will cause the process to sleep +//! until a upcall is triggered, while `yield-no-wait` returns immediately. +//! +//! # Method result types +//! +//! Each driver method has a limited set of valid return types. Every method has +//! a single return type corresponding to success and a single return type +//! corresponding to failure. For the `subscribe` and `allow` system calls, +//! these return types are the same for every instance of those calls. Each +//! instance of the `command` system call, however, has its own specified return +//! types. A command that requests a timestamp, for example, might return a +//! 32-bit number on success and an error code on failure, while a command that +//! requests time of day in microsecond granularity might return a 64-bit number +//! and a 32-bit timezone encoding on success, and an error code on failure. +//! +//! These result types are represented as safe Rust types. The core kernel (the +//! scheduler and syscall dispatcher) is responsible for encoding these types +//! into the Tock system call ABI specification. -use core::convert::TryFrom; use core::fmt::Write; use crate::errorcode::ErrorCode; @@ -8,10 +73,9 @@ use crate::process; pub use crate::syscall_driver::{CommandReturn, SyscallDriver}; -/// Helper function to split a u64 into a higher and lower u32. +/// Helper function to split a [`u64`] into a higher and lower [`u32`]. /// -/// Used in encoding 64-bit wide system call return values on 32-bit -/// platforms. +/// Used in encoding 64-bit wide system call return values on 32-bit platforms. #[inline] fn u64_to_be_u32s(src: u64) -> (u32, u32) { let src_bytes = src.to_be_bytes(); @@ -23,11 +87,11 @@ fn u64_to_be_u32s(src: u64) -> (u32, u32) { // ---------- SYSTEMCALL ARGUMENT DECODING ---------- -/// Enumeration of the system call classes based on the identifiers -/// specified in the Tock ABI. +/// Enumeration of the system call classes based on the identifiers specified in +/// the Tock ABI. /// -/// These are encoded as 8 bit values as on some architectures the value can -/// be encoded in the instruction itself. +/// These are encoded as 8 bit values as on some architectures the value can be +/// encoded in the instruction itself. #[repr(u8)] #[derive(Copy, Clone, Debug)] pub enum SyscallClass { @@ -47,11 +111,25 @@ pub enum SyscallClass { pub enum YieldCall { NoWait = 0, Wait = 1, + WaitFor = 2, +} + +impl TryFrom for YieldCall { + type Error = usize; + + fn try_from(yield_variant: usize) -> Result { + match yield_variant { + 0 => Ok(YieldCall::NoWait), + 1 => Ok(YieldCall::Wait), + 2 => Ok(YieldCall::WaitFor), + i => Err(i), + } + } } // Required as long as no solution to // https://github.com/rust-lang/rfcs/issues/2783 is integrated into -// the standard library +// the standard library. impl TryFrom for SyscallClass { type Error = u8; @@ -70,90 +148,107 @@ impl TryFrom for SyscallClass { } } -/// Decoded system calls as defined in TRD 104. +/// Decoded system calls as defined in TRD104. #[derive(Copy, Clone, Debug, PartialEq)] pub enum Syscall { - /// Structure representing an invocation of the Yield system call class. - /// `which` is the Yield identifier value and `address` is the no wait field. - Yield { which: usize, address: *mut u8 }, - - /// Structure representing an invocation of the Subscribe system call - /// class. `driver_number` is the driver identifier, `subdriver_number` - /// is the subscribe identifier, `upcall_ptr` is upcall pointer, - /// and `appdata` is the application data. + /// Structure representing an invocation of the [`SyscallClass::Yield`] + /// system call class. `which` is the Yield identifier value and `address` + /// is the no wait field. + Yield { + which: usize, + param_a: usize, + param_b: usize, + }, + + /// Structure representing an invocation of the Subscribe system call class. Subscribe { + /// The driver identifier. driver_number: usize, + /// The subscribe identifier. subdriver_number: usize, + /// Upcall pointer to the upcall function. upcall_ptr: *mut (), + /// Userspace application data. appdata: usize, }, /// Structure representing an invocation of the Command system call class. - /// `driver_number` is the driver identifier and `subdriver_number` is - /// the command identifier. Command { + /// The driver identifier. driver_number: usize, + /// The command identifier. subdriver_number: usize, + /// Value passed to the `Command` implementation. arg0: usize, + /// Value passed to the `Command` implementation. arg1: usize, }, /// Structure representing an invocation of the ReadWriteAllow system call - /// class. `driver_number` is the driver identifier, `subdriver_number` is - /// the buffer identifier, `allow_address` is the address, and `allow_size` - /// is the size. + /// class. ReadWriteAllow { + /// The driver identifier. driver_number: usize, + /// The buffer identifier. subdriver_number: usize, + /// The address where the buffer starts. allow_address: *mut u8, + /// The size of the buffer in bytes. allow_size: usize, }, - /// Structure representing an invocation of the ReadWriteAllow system call - /// class, but with shared kernel and app access. `driver_number` is the - /// driver identifier, `subdriver_number` is the buffer identifier, - // `allow_address` is the address, and `allow_size` is the size. + /// Structure representing an invocation of the UserspaceReadableAllow + /// system call class that allows shared kernel and app access. UserspaceReadableAllow { + /// The driver identifier. driver_number: usize, + /// The buffer identifier. subdriver_number: usize, + /// The address where the buffer starts. allow_address: *mut u8, + /// The size of the buffer in bytes. allow_size: usize, }, /// Structure representing an invocation of the ReadOnlyAllow system call - /// class. `driver_number` is the driver identifier, `subdriver_number` is - /// the buffer identifier, `allow_address` is the address, and `allow_size` - /// is the size. + /// class. ReadOnlyAllow { + /// The driver identifier. driver_number: usize, + /// The buffer identifier. subdriver_number: usize, + /// The address where the buffer starts. allow_address: *const u8, + /// The size of the buffer in bytes. allow_size: usize, }, - /// Structure representing an invocation of the Memop system call - /// class. `operand` is the operation and `arg0` is the operation - /// argument. - Memop { operand: usize, arg0: usize }, + /// Structure representing an invocation of the Memop system call class. + Memop { + /// The operation. + operand: usize, + /// The operation argument. + arg0: usize, + }, - /// Structure representing an invocation of the Exit system call - /// class. `which` is the exit identifier and `completion_code` is - /// the completion code passed into the kernel. + /// Structure representing an invocation of the Exit system call class. Exit { + /// The exit identifier. which: usize, + /// The completion code passed into the kernel. completion_code: usize, }, } impl Syscall { - /// Helper function for converting raw values passed back from an application - /// into a `Syscall` type in Tock, representing an typed version of a system - /// call invocation. The method returns None if the values do not specify - /// a valid system call. + /// Helper function for converting raw values passed back from an + /// application into a `Syscall` type in Tock, representing an typed version + /// of a system call invocation. The method returns None if the values do + /// not specify a valid system call. /// /// Different architectures have different ABIs for a process and the kernel - /// to exchange data. The 32-bit ABI for CortexM and RISCV microcontrollers is - /// specified in TRD104. + /// to exchange data. The 32-bit ABI for CortexM and RISCV microcontrollers + /// is specified in TRD104. pub fn from_register_arguments( syscall_number: u8, r0: usize, @@ -164,7 +259,8 @@ impl Syscall { match SyscallClass::try_from(syscall_number) { Ok(SyscallClass::Yield) => Some(Syscall::Yield { which: r0, - address: r1 as *mut u8, + param_a: r1, + param_b: r2, }), Ok(SyscallClass::Subscribe) => Some(Syscall::Subscribe { driver_number: r0, @@ -207,6 +303,81 @@ impl Syscall { Err(_) => None, } } + + /// Get the `driver_number` for the syscall classes that use driver numbers. + pub fn driver_number(&self) -> Option { + match *self { + Syscall::Subscribe { + driver_number, + subdriver_number: _, + upcall_ptr: _, + appdata: _, + } => Some(driver_number), + Syscall::Command { + driver_number, + subdriver_number: _, + arg0: _, + arg1: _, + } => Some(driver_number), + Syscall::ReadWriteAllow { + driver_number, + subdriver_number: _, + allow_address: _, + allow_size: _, + } => Some(driver_number), + Syscall::UserspaceReadableAllow { + driver_number, + subdriver_number: _, + allow_address: _, + allow_size: _, + } => Some(driver_number), + Syscall::ReadOnlyAllow { + driver_number, + subdriver_number: _, + allow_address: _, + allow_size: _, + } => Some(driver_number), + _ => None, + } + } + + /// Get the `subdriver_number` for the syscall classes that use sub driver + /// numbers. + pub fn subdriver_number(&self) -> Option { + match *self { + Syscall::Subscribe { + driver_number: _, + subdriver_number, + upcall_ptr: _, + appdata: _, + } => Some(subdriver_number), + Syscall::Command { + driver_number: _, + subdriver_number, + arg0: _, + arg1: _, + } => Some(subdriver_number), + Syscall::ReadWriteAllow { + driver_number: _, + subdriver_number, + allow_address: _, + allow_size: _, + } => Some(subdriver_number), + Syscall::UserspaceReadableAllow { + driver_number: _, + subdriver_number, + allow_address: _, + allow_size: _, + } => Some(subdriver_number), + Syscall::ReadOnlyAllow { + driver_number: _, + subdriver_number, + allow_address: _, + allow_size: _, + } => Some(subdriver_number), + _ => None, + } + } } // ---------- SYSCALL RETURN VALUE ENCODING ---------- @@ -214,8 +385,8 @@ impl Syscall { /// Enumeration of the system call return type variant identifiers described /// in TRD104. /// -/// Each variant is associated with the respective variant identifier -/// that would be passed along with the return value to userspace. +/// Each variant is associated with the respective variant identifier that would +/// be passed along with the return value to userspace. #[repr(u32)] #[derive(Copy, Clone, Debug)] pub enum SyscallReturnVariant { @@ -228,25 +399,21 @@ pub enum SyscallReturnVariant { SuccessU32U32 = 130, SuccessU64 = 131, SuccessU32U32U32 = 132, - SuccessU64U32 = 133, + SuccessU32U64 = 133, } -/// Enumeration of the possible system call return variants specified -/// in TRD104. +/// Enumeration of the possible system call return variants specified in TRD104. /// -/// This struct operates over primitive types such as integers of -/// fixed length and pointers. It is constructed by the scheduler and -/// passed down to the architecture to be encoded into registers, -/// using the provided -/// [`encode_syscall_return`](SyscallReturn::encode_syscall_return) -/// method. +/// This struct operates over primitive types such as integers of fixed length +/// and pointers. It is constructed by the scheduler and passed down to the +/// architecture to be encoded into registers, using the provided +/// [`encode_syscall_return`](SyscallReturn::encode_syscall_return) method. /// -/// Capsules do not use this struct. Capsules use higher level Rust -/// types -/// (e.g. [`ReadWriteProcessBuffer`](crate::processbuffer::ReadWriteProcessBuffer) -/// and `GrantKernelData`) or wrappers around this struct -/// ([`CommandReturn`](crate::syscall_driver::CommandReturn)) which limit the -/// available constructors to safely constructable variants. +/// Capsules do not use this struct. Capsules use higher level Rust types (e.g. +/// [`ReadWriteProcessBuffer`](crate::processbuffer::ReadWriteProcessBuffer) and +/// [`GrantKernelData`](crate::grant::GrantKernelData)) or wrappers around this +/// struct ([`CommandReturn`]) which limit the available constructors to safely +/// constructable variants. #[derive(Copy, Clone, Debug)] pub enum SyscallReturn { /// Generic error case @@ -267,61 +434,67 @@ pub enum SyscallReturn { SuccessU32U32U32(u32, u32, u32), /// Generic success case, with an additional 64-bit data field SuccessU64(u64), - /// Generic success case, with an additional 32-bit and 64-bit - /// data field - SuccessU64U32(u64, u32), - - // These following types are used by the scheduler so that it can - // return values to userspace in an architecture (pointer-width) - // independent way. The kernel passes these types (rather than - // ProcessBuffer or Upcall) for two reasons. First, since the - // kernel/scheduler makes promises about the lifetime and safety - // of these types, it does not want to leak them to other - // code. Second, if subscribe or allow calls pass invalid values + /// Generic success case, with an additional 32-bit and 64-bit data field + SuccessU32U64(u32, u64), + + // These following types are used by the scheduler so that it can return + // values to userspace in an architecture (pointer-width) independent way. + // The kernel passes these types (rather than ProcessBuffer or Upcall) for + // two reasons. First, since the kernel/scheduler makes promises about the + // lifetime and safety of these types, it does not want to leak them to + // other code. Second, if subscribe or allow calls pass invalid values // (pointers out of valid memory), the kernel cannot construct an - // ProcessBuffer or Upcall type but needs to be able to return a - // failure. -pal 11/24/20 - /// Read/Write allow success case, returns the previous allowed - /// buffer and size to the process. + // ProcessBuffer or Upcall type but needs to be able to return a failure. + // -pal 11/24/20 + /// Read/Write allow success case, returns the previous allowed buffer and + /// size to the process. AllowReadWriteSuccess(*mut u8, usize), - /// Read/Write allow failure case, returns the passed allowed - /// buffer and size to the process. + /// Read/Write allow failure case, returns the passed allowed buffer and + /// size to the process. AllowReadWriteFailure(ErrorCode, *mut u8, usize), /// Shared Read/Write allow success case, returns the previous allowed /// buffer and size to the process. UserspaceReadableAllowSuccess(*mut u8, usize), - /// Shared Read/Write allow failure case, returns the passed allowed - /// buffer and size to the process. + /// Shared Read/Write allow failure case, returns the passed allowed buffer + /// and size to the process. UserspaceReadableAllowFailure(ErrorCode, *mut u8, usize), - /// Read only allow success case, returns the previous allowed - /// buffer and size to the process. + /// Read only allow success case, returns the previous allowed buffer and + /// size to the process. AllowReadOnlySuccess(*const u8, usize), - /// Read only allow failure case, returns the passed allowed - /// buffer and size to the process. + /// Read only allow failure case, returns the passed allowed buffer and size + /// to the process. AllowReadOnlyFailure(ErrorCode, *const u8, usize), - /// Subscribe success case, returns the previous upcall function - /// pointer and application data. + /// Subscribe success case, returns the previous upcall function pointer and + /// application data. SubscribeSuccess(*const (), usize), - /// Subscribe failure case, returns the passed upcall function - /// pointer and application data. + /// Subscribe failure case, returns the passed upcall function pointer and + /// application data. SubscribeFailure(ErrorCode, *const (), usize), + + /// Yield-WaitFor return value. These arguments match the arguments to an + /// upcall, where the kernel does not define an error field. Therefore this + /// does not have success/failure versions because the kernel cannot know if + /// the upcall (i.e. Yield-WaitFor return value) represents success or + /// failure. + YieldWaitFor(usize, usize, usize), } impl SyscallReturn { - /// Transforms a CommandReturn, which is wrapper around a subset of - /// SyscallReturn, into a SyscallReturn. + /// Transforms a [`CommandReturn`], which is wrapper around a subset of + /// [`SyscallReturn`], into a [`SyscallReturn`]. /// - /// This allows CommandReturn to include only the variants of SyscallReturn - /// that can be returned from a Command, while having an inexpensive way to - /// handle it as a SyscallReturn for more generic code paths. + /// This allows [`CommandReturn`] to include only the variants of + /// [`SyscallReturn`] that can be returned from a Command, while having an + /// inexpensive way to handle it as a [`SyscallReturn`] for more generic + /// code paths. pub(crate) fn from_command_return(res: CommandReturn) -> Self { res.into_inner() } - /// Returns true if the `SyscallReturn` is any success type. + /// Returns true if the [`SyscallReturn`] is any success type. pub(crate) fn is_success(&self) -> bool { match self { SyscallReturn::Success => true, @@ -329,7 +502,7 @@ impl SyscallReturn { SyscallReturn::SuccessU32U32(_, _) => true, SyscallReturn::SuccessU32U32U32(_, _, _) => true, SyscallReturn::SuccessU64(_) => true, - SyscallReturn::SuccessU64U32(_, _) => true, + SyscallReturn::SuccessU32U64(_, _) => true, SyscallReturn::AllowReadWriteSuccess(_, _) => true, SyscallReturn::UserspaceReadableAllowSuccess(_, _) => true, SyscallReturn::AllowReadOnlySuccess(_, _) => true, @@ -342,155 +515,161 @@ impl SyscallReturn { SyscallReturn::UserspaceReadableAllowFailure(_, _, _) => false, SyscallReturn::AllowReadOnlyFailure(_, _, _) => false, SyscallReturn::SubscribeFailure(_, _, _) => false, + SyscallReturn::YieldWaitFor(_, _, _) => true, } } - /// Encode the system call return value into 4 registers, following - /// the encoding specified in TRD104. Architectures which do not follow - /// TRD104 are free to define their own encoding. + /// Encode the system call return value into 4 registers, following the + /// encoding specified in TRD104. Architectures which do not follow TRD104 + /// are free to define their own encoding. pub fn encode_syscall_return(&self, a0: &mut u32, a1: &mut u32, a2: &mut u32, a3: &mut u32) { - match self { - &SyscallReturn::Failure(e) => { + match *self { + SyscallReturn::Failure(e) => { *a0 = SyscallReturnVariant::Failure as u32; *a1 = usize::from(e) as u32; } - &SyscallReturn::FailureU32(e, data0) => { + SyscallReturn::FailureU32(e, data0) => { *a0 = SyscallReturnVariant::FailureU32 as u32; *a1 = usize::from(e) as u32; *a2 = data0; } - &SyscallReturn::FailureU32U32(e, data0, data1) => { + SyscallReturn::FailureU32U32(e, data0, data1) => { *a0 = SyscallReturnVariant::FailureU32U32 as u32; *a1 = usize::from(e) as u32; *a2 = data0; *a3 = data1; } - &SyscallReturn::FailureU64(e, data0) => { + SyscallReturn::FailureU64(e, data0) => { let (data0_msb, data0_lsb) = u64_to_be_u32s(data0); *a0 = SyscallReturnVariant::FailureU64 as u32; *a1 = usize::from(e) as u32; *a2 = data0_lsb; *a3 = data0_msb; } - &SyscallReturn::Success => { + SyscallReturn::Success => { *a0 = SyscallReturnVariant::Success as u32; } - &SyscallReturn::SuccessU32(data0) => { + SyscallReturn::SuccessU32(data0) => { *a0 = SyscallReturnVariant::SuccessU32 as u32; *a1 = data0; } - &SyscallReturn::SuccessU32U32(data0, data1) => { + SyscallReturn::SuccessU32U32(data0, data1) => { *a0 = SyscallReturnVariant::SuccessU32U32 as u32; *a1 = data0; *a2 = data1; } - &SyscallReturn::SuccessU32U32U32(data0, data1, data2) => { + SyscallReturn::SuccessU32U32U32(data0, data1, data2) => { *a0 = SyscallReturnVariant::SuccessU32U32U32 as u32; *a1 = data0; *a2 = data1; *a3 = data2; } - &SyscallReturn::SuccessU64(data0) => { + SyscallReturn::SuccessU64(data0) => { let (data0_msb, data0_lsb) = u64_to_be_u32s(data0); *a0 = SyscallReturnVariant::SuccessU64 as u32; *a1 = data0_lsb; *a2 = data0_msb; } - &SyscallReturn::SuccessU64U32(data0, data1) => { - let (data0_msb, data0_lsb) = u64_to_be_u32s(data0); + SyscallReturn::SuccessU32U64(data0, data1) => { + let (data1_msb, data1_lsb) = u64_to_be_u32s(data1); - *a0 = SyscallReturnVariant::SuccessU64U32 as u32; - *a1 = data0_lsb; - *a2 = data0_msb; - *a3 = data1; + *a0 = SyscallReturnVariant::SuccessU32U64 as u32; + *a1 = data0; + *a2 = data1_lsb; + *a3 = data1_msb; } - &SyscallReturn::AllowReadWriteSuccess(ptr, len) => { + SyscallReturn::AllowReadWriteSuccess(ptr, len) => { *a0 = SyscallReturnVariant::SuccessU32U32 as u32; *a1 = ptr as u32; *a2 = len as u32; } - &SyscallReturn::UserspaceReadableAllowSuccess(ptr, len) => { + SyscallReturn::UserspaceReadableAllowSuccess(ptr, len) => { *a0 = SyscallReturnVariant::SuccessU32U32 as u32; *a1 = ptr as u32; *a2 = len as u32; } - &SyscallReturn::AllowReadWriteFailure(err, ptr, len) => { + SyscallReturn::AllowReadWriteFailure(err, ptr, len) => { *a0 = SyscallReturnVariant::FailureU32U32 as u32; *a1 = usize::from(err) as u32; *a2 = ptr as u32; *a3 = len as u32; } - &SyscallReturn::UserspaceReadableAllowFailure(err, ptr, len) => { + SyscallReturn::UserspaceReadableAllowFailure(err, ptr, len) => { *a0 = SyscallReturnVariant::FailureU32U32 as u32; *a1 = usize::from(err) as u32; *a2 = ptr as u32; *a3 = len as u32; } - &SyscallReturn::AllowReadOnlySuccess(ptr, len) => { + SyscallReturn::AllowReadOnlySuccess(ptr, len) => { *a0 = SyscallReturnVariant::SuccessU32U32 as u32; *a1 = ptr as u32; *a2 = len as u32; } - &SyscallReturn::AllowReadOnlyFailure(err, ptr, len) => { + SyscallReturn::AllowReadOnlyFailure(err, ptr, len) => { *a0 = SyscallReturnVariant::FailureU32U32 as u32; *a1 = usize::from(err) as u32; *a2 = ptr as u32; *a3 = len as u32; } - &SyscallReturn::SubscribeSuccess(ptr, data) => { + SyscallReturn::SubscribeSuccess(ptr, data) => { *a0 = SyscallReturnVariant::SuccessU32U32 as u32; *a1 = ptr as u32; *a2 = data as u32; } - &SyscallReturn::SubscribeFailure(err, ptr, data) => { + SyscallReturn::SubscribeFailure(err, ptr, data) => { *a0 = SyscallReturnVariant::FailureU32U32 as u32; *a1 = usize::from(err) as u32; *a2 = ptr as u32; *a3 = data as u32; } + SyscallReturn::YieldWaitFor(data0, data1, data2) => { + *a0 = data0 as u32; + *a1 = data1 as u32; + *a2 = data2 as u32; + } } } } // ---------- USERSPACE KERNEL BOUNDARY ---------- -/// `ContentSwitchReason` specifies why the process stopped executing and +/// [`ContextSwitchReason`] specifies why the process stopped executing and /// execution returned to the kernel. #[derive(PartialEq, Copy, Clone)] pub enum ContextSwitchReason { /// Process called a syscall. Also returns the syscall and relevant values. SyscallFired { syscall: Syscall }, - /// Process triggered the hardfault handler. - /// The implementation should still save registers in the event that the - /// `Platform` can handle the fault and allow the app to continue running. - /// For more details on this see `Platform::process_fault_hook()`. + /// Process triggered the hardfault handler. The implementation should still + /// save registers in the event that the platform can handle the fault and + /// allow the app to continue running. For more details on this see + /// [`ProcessFault`](crate::platform::ProcessFault). Fault, - /// Process interrupted (e.g. by a hardware event) + /// Process was interrupted (e.g. by a hardware event). Interrupted, } -/// The `UserspaceKernelBoundary` trait is implemented by the -/// architectural component of the chip implementation of Tock. This -/// trait allows the kernel to switch to and from processes -/// in an architecture-independent manner. +/// The [`UserspaceKernelBoundary`] trait is implemented by the architectural +/// component of the chip implementation of Tock. This trait allows the kernel +/// to switch to and from processes in an architecture-independent manner. /// -/// Exactly how upcalls and return values are passed between -/// kernelspace and userspace is architecture specific. The -/// architecture may use process memory to store state when -/// switching. Therefore, functions in this trait are passed the -/// bounds of process-accessible memory so that the architecture -/// implementation can verify it is reading and writing memory that -/// the process has valid access to. These bounds are passed through +/// Exactly how upcalls and return values are passed between kernelspace and +/// userspace is architecture specific. The architecture may use process memory +/// to store state when switching. Therefore, functions in this trait are passed +/// the bounds of process-accessible memory so that the architecture +/// implementation can verify it is reading and writing memory that the process +/// has valid access to. These bounds are passed through /// `accessible_memory_start` and `app_brk` pointers. pub trait UserspaceKernelBoundary { /// Some architecture-specific struct containing per-process state that must /// be kept while the process is not running. For example, for keeping CPU /// registers that aren't stored on the stack. /// - /// Implementations should **not** rely on the `Default` constructor (custom - /// or derived) for any initialization of a process's stored state. The - /// initialization must happen in the `initialize_process()` function. + /// Implementations should **not** rely on the [`Default`] constructor + /// (custom or derived) for any initialization of a process's stored state. + /// The initialization must happen in the + /// [`initialize_process()`](UserspaceKernelBoundary::initialize_process()) + /// function. type StoredState: Default; /// Called by the kernel during process creation to inform the kernel of the @@ -612,8 +791,8 @@ pub trait UserspaceKernelBoundary { /// /// This returns two values in a tuple. /// - /// 1. A `ContextSwitchReason` indicating why the process stopped executing - /// and switched back to the kernel. + /// 1. A [`ContextSwitchReason`] indicating why the process stopped + /// executing and switched back to the kernel. /// 2. Optionally, the current stack pointer used by the process. This is /// optional because it is only for debugging in process.rs. By sharing /// the process's stack pointer with process.rs users can inspect the diff --git a/kernel/src/syscall_driver.rs b/kernel/src/syscall_driver.rs index ba66584d04..e37f3f239b 100644 --- a/kernel/src/syscall_driver.rs +++ b/kernel/src/syscall_driver.rs @@ -1,79 +1,10 @@ -//! System call interface for userspace processes. +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! System call interface for userspace processes implemented by capsules. //! //! Drivers implement these interfaces to expose operations to processes. -//! -//! # System-call Overview -//! -//! Tock supports six system calls. The `allow_readonly`, `allow_readwrite`, -//! `subscribe`, `yield`, and `memop` system calls are handled by the core -//! kernel, while `command`is implemented by drivers. The main system calls: -//! -//! * `subscribe` passes a upcall to the driver which it can -//! invoke on the process later, when an event has occurred or data -//! of interest is available. -//! -//! * `command` tells the driver to do something immediately. -//! -//! * `allow_readwrite` provides the driver read-write access to an -//! application buffer. -//! -//! * `allow_userspace_readable` provides the driver read-write access to an -//! application buffer that is still shared with the app. -//! -//! * `allow_readonly` provides the driver read-only access to an -//! application buffer. -//! -//! ## Mapping system-calls to drivers -//! -//! Each of these three system calls takes at least two -//! parameters. The first is a _driver identifier_ and tells the -//! scheduler which driver to forward the system call to. The second -//! parameters is a __syscall number_ and is used by the driver to -//! differentiate instances of the call with different driver-specific -//! meanings (e.g. `subscribe` for "data received" vs `subscribe` for -//! "send completed"). The mapping between _driver identifiers_ and -//! drivers is determined by a particular platform, while the _syscall -//! number_ is driver-specific. -//! -//! One convention in Tock is that _driver minor number_ 0 for the `command` -//! syscall can always be used to determine if the driver is supported by -//! the running kernel by checking the return code. If the return value is -//! greater than or equal to zero then the driver is present. Typically this is -//! implemented by a null command that only returns 0, but in some cases the -//! command can also return more information, like the number of supported -//! devices (useful for things like the number of LEDs). -//! -//! # The `yield` system call class -//! -//! While drivers do not handle `yield` system calls, it is important -//! to understand them and how they interact with `subscribe`, which -//! registers upcall functions with the kernel. When a process calls -//! a `yield` system call, the kernel checks if there are any pending -//! upcalls for the process. If there are pending upcalls, it -//! pushes one upcall onto the process stack. If there are no -//! pending upcalls, `yield-wait` will cause the process to sleep -//! until a upcall is trigered, while `yield-no-wait` returns -//! immediately. -//! -//! # Method result types -//! -//! Each driver method has a limited set of valid return types. Every -//! method has a single return type corresponding to success and a -//! single return type corresponding to failure. For the `subscribe` -//! and `allow` system calls, these return types are the same for -//! every instance of those calls. Each instance of the `command` -//! system call, however, has its own specified return types. A -//! command that requests a timestamp, for example, might return a -//! 32-bit number on success and an error code on failure, while a -//! command that requests time of day in microsecond granularity might -//! return a 64-bit number and a 32-bit timezone encoding on success, -//! and an error code on failure. -//! -//! These result types are represented as safe Rust types. The core -//! kernel (the scheduler and syscall dispatcher) is responsible for -//! encoding these types into the Tock system call ABI specification. - -use core::convert::TryFrom; use crate::errorcode::ErrorCode; use crate::process; @@ -81,22 +12,18 @@ use crate::process::ProcessId; use crate::processbuffer::UserspaceReadableProcessBuffer; use crate::syscall::SyscallReturn; -/// Possible return values of a `command` driver method, as specified -/// in TRD104. +/// Possible return values of a `command` driver method, as specified in TRD104. /// -/// This is just a wrapper around -/// [`SyscallReturn`](SyscallReturn) since a -/// `command` driver method may only return primitve integer types as -/// payload. +/// This is just a wrapper around [`SyscallReturn`] since a `command` driver +/// method may only return primitive integer types as payload. /// -/// It is important for this wrapper to only be constructable over -/// variants of -/// [`SyscallReturn`](SyscallReturn) that are -/// deemed safe for a capsule to construct and return to an -/// application (e.g. not -/// [`SubscribeSuccess`](crate::syscall::SyscallReturn::SubscribeSuccess)). -/// This means that the inner value **must** remain private. +/// It is important for this wrapper to only be constructable over variants of +/// [`SyscallReturn`] that are deemed safe for a capsule to construct and return +/// to an application (e.g. not +/// [`SubscribeSuccess`](crate::syscall::SyscallReturn::SubscribeSuccess)). This +/// means that the inner value **must** remain private. pub struct CommandReturn(SyscallReturn); + impl CommandReturn { pub(crate) fn into_inner(self) -> SyscallReturn { self.0 @@ -148,8 +75,8 @@ impl CommandReturn { } /// Successful command with an additional 64-bit and 32-bit data field - pub fn success_u64_u32(data0: u64, data1: u32) -> Self { - CommandReturn(SyscallReturn::SuccessU64U32(data0, data1)) + pub fn success_u32_u64(data0: u32, data1: u64) -> Self { + CommandReturn(SyscallReturn::SuccessU32U64(data0, data1)) } } @@ -168,27 +95,25 @@ impl From for CommandReturn { } } -/// Trait for capsules implementing peripheral driver system calls -/// specified in TRD104. The kernel translates the values passed from -/// userspace into Rust types and includes which process is making the -/// call. All of these system calls perform very little synchronous work; -/// long running computations or I/O should be split-phase, with an upcall -/// indicating their completion. +/// Trait for capsules implementing peripheral driver system calls specified in +/// TRD104. The kernel translates the values passed from userspace into Rust +/// types and includes which process is making the call. All of these system +/// calls perform very little synchronous work; long running computations or I/O +/// should be split-phase, with an upcall indicating their completion. /// /// The exact instances of each of these methods (which identifiers are valid -/// and what they represents) are specific to the peripheral system call -/// driver. +/// and what they represents) are specific to the peripheral system call driver. /// -/// Note about subscribe: upcall subscriptions are handled entirely by the core -/// kernel, and therefore there is no subscribe function for capsules to -/// implement. +/// Note about `subscribe`, `read-only allow`, and `read-write allow` syscalls: +/// those are handled entirely by the core kernel, and there is no corresponding +/// function for capsules to implement. #[allow(unused_variables)] pub trait SyscallDriver { - /// System call for a process to perform a short synchronous operation - /// or start a long-running split-phase operation (whose completion - /// is signaled with an upcall). Command 0 is a reserved command to - /// detect if a peripheral system call driver is installed and must - /// always return a CommandReturn::Success. + /// System call for a process to perform a short synchronous operation or + /// start a long-running split-phase operation (whose completion is signaled + /// with an upcall). Command 0 is a reserved command to detect if a + /// peripheral system call driver is installed and must always return a + /// [`CommandReturn::success`]. fn command( &self, command_num: usize, @@ -200,14 +125,15 @@ pub trait SyscallDriver { } /// System call for a process to pass a buffer (a - /// UserspaceReadableProcessBuffer) to the kernel that the kernel can either - /// read or write. The kernel calls this method only after it checks that - /// the entire buffer is within memory the process can both read and write. + /// [`UserspaceReadableProcessBuffer`]) to the kernel that the kernel can + /// either read or write. The kernel calls this method only after it checks + /// that the entire buffer is within memory the process can both read and + /// write. /// - /// This is different to `allow_readwrite()` in that the app is allowed - /// to read the buffer once it has been passed to the kernel. - /// For more details on how this can be done safely see the userspace - /// readable allow syscalls TRDXXX. + /// This is different to `allow_readwrite` in that the app is allowed to + /// read the buffer once it has been passed to the kernel. For more details + /// on how this can be done safely see the userspace readable allow syscalls + /// TRDXXX. fn allow_userspace_readable( &self, app: ProcessId, @@ -222,7 +148,7 @@ pub trait SyscallDriver { /// The core kernel uses this function to instruct a capsule to ensure its /// grant (if it has one) is allocated for a specific process. The core /// kernel needs the capsule to initiate the allocation because only the - /// capsule knows the type T (and therefore the size of T) that will be + /// capsule knows the type `T` (and therefore the size of `T`) that will be /// stored in the grant. /// /// The typical implementation will look like: @@ -236,7 +162,7 @@ pub trait SyscallDriver { /// forgetting to implement this function. /// /// If a capsule fails to successfully implement this function, subscribe - /// calls from userspace for the Driver may fail. + /// calls from userspace for the [`SyscallDriver`] may fail. // // The inclusion of this function originates from the method for ensuring // correct upcall swapping semantics in the kernel starting with Tock 2.0. @@ -283,7 +209,7 @@ pub trait SyscallDriver { // drivers in the grant region. When each grant is created it could tell // the kernel how many upcalls it will use and the kernel could easily // keep track of the total. Then, when a process's memory is allocated - // the kernel would reserve rooom for that many upcalls. There are two + // the kernel would reserve room for that many upcalls. There are two // issues, however. The kernel would not know how many upcalls each // driver individually requires, so it would not be able to index into // this array properly to store each upcall. Second, the upcall array @@ -302,10 +228,10 @@ pub trait SyscallDriver { // together. // // Based on the available options, the Tock developers decided go with - // option 4 and add the `allocate_grant` method to the `Driver` trait. This - // mechanism may find more uses in the future if the kernel needs to store - // additional state on a per-driver basis and therefore needs a mechanism to - // force a grant allocation. + // option 4 and add the `allocate_grant` method to the `SyscallDriver` + // trait. This mechanism may find more uses in the future if the kernel + // needs to store additional state on a per-driver basis and therefore needs + // a mechanism to force a grant allocation. // // This same mechanism was later extended to handle allow calls as well. // Capsules that do not need upcalls but do use process buffers must also diff --git a/kernel/src/upcall.rs b/kernel/src/upcall.rs index f7795e204f..a719f3d37d 100644 --- a/kernel/src/upcall.rs +++ b/kernel/src/upcall.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Data structure for storing an upcall from the kernel to a process. use core::ptr::NonNull; @@ -12,9 +16,14 @@ use crate::ErrorCode; /// Type to uniquely identify an upcall subscription across all drivers. /// /// This contains the driver number and the subscribe number within the driver. -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] pub struct UpcallId { + /// The [`SyscallDriver`](crate::syscall_driver::SyscallDriver) + /// implementation this upcall corresponds to. pub driver_num: usize, + /// The subscription index the upcall corresponds to. Subscribe numbers + /// start at 0 and increment for each upcall defined for a particular + /// [`SyscallDriver`](crate::syscall_driver::SyscallDriver). pub subscribe_num: usize, } @@ -27,33 +36,35 @@ pub struct UpcallId { /// benefit that no task is inserted in the process' task queue. #[derive(Copy, Clone, Debug)] pub enum UpcallError { - /// The passed `subscribe_num` exceeds the number of Upcalls - /// available for this process. + /// The passed `subscribe_num` exceeds the number of Upcalls available for + /// this process. /// - /// For a [`Grant`](crate::grant::Grant) with `n` upcalls, - /// this error is returned when - /// `GrantKernelData::schedule_upcall` is invoked with - /// `subscribe_num >= n`. + /// For a [`Grant`](crate::grant::Grant) with `n` upcalls, this error is + /// returned when + /// [`GrantKernelData::schedule_upcall`](crate::grant::GrantKernelData::schedule_upcall) + /// is invoked with `subscribe_num >= n`. /// /// No Upcall has been scheduled, the call to - /// `GrantKernelData::schedule_upcall` had no observable effects. + /// [`GrantKernelData::schedule_upcall`](crate::grant::GrantKernelData::schedule_upcall) + /// had no observable effects. /// InvalidSubscribeNum, /// The process' task queue is full. /// - /// This error can occur when too many tasks (for example, - /// Upcalls) have been scheduled for a process, without that - /// process yielding or having a chance to resume execution. + /// This error can occur when too many tasks (for example, Upcalls) have + /// been scheduled for a process, without that process yielding or having a + /// chance to resume execution. /// /// No Upcall has been scheduled, the call to - /// `GrantKernelData::schedule_upcall` had no observable effects. + /// [`GrantKernelData::schedule_upcall`](crate::grant::GrantKernelData::schedule_upcall) + /// had no observable effects. QueueFull, /// A kernel-internal invariant has been violated. /// - /// This error should never happen. It can be returned if the - /// process is inactive (which should be caught by - /// [`Grant::enter`](crate::grant::Grant::enter)) or - /// `process.tasks` was taken. + /// This error should never happen. It can be returned if the process is + /// inactive (which should be caught by + /// [`Grant::enter`](crate::grant::Grant::enter)) or `process.tasks` was + /// taken. /// /// These cases cannot be reasonably handled. KernelError, @@ -61,21 +72,21 @@ pub enum UpcallError { /// Type for calling an upcall in a process. /// -/// This is essentially a wrapper around a function pointer with -/// associated process data. +/// This is essentially a wrapper around a function pointer with associated +/// process data. pub(crate) struct Upcall { - /// The ProcessId of the process this upcall is for. + /// The [`ProcessId`] of the process this upcall is for. pub(crate) process_id: ProcessId, /// A unique identifier of this particular upcall, representing the /// driver_num and subdriver_num used to submit it. pub(crate) upcall_id: UpcallId, - /// The application data passed by the app when `subscribe()` was called + /// The application data passed by the app when `subscribe()` was called. pub(crate) appdata: usize, - /// A pointer to the first instruction of a function in the app - /// associated with app_id. + /// A pointer to the first instruction of the function in the app that + /// corresponds to this upcall. /// /// If this value is `None`, this is a null upcall, which cannot actually be /// scheduled. An `Upcall` can be null when it is first created, or after an @@ -100,70 +111,74 @@ impl Upcall { /// Schedule the upcall. /// - /// This will queue the [`Upcall`] for the given process. It - /// returns `false` if the queue for the process is full and the - /// upcall could not be scheduled or this is a null upcall. + /// This will queue the [`Upcall`] for the given process. It returns `false` + /// if the queue for the process is full and the upcall could not be + /// scheduled or this is a null upcall. /// /// The arguments (`r0-r2`) are the values passed back to the process and /// are specific to the individual `Driver` interfaces. /// /// This function also takes `process` as a parameter (even though we have - /// process_id in our struct) to avoid a search through the processes array - /// to schedule the upcall. Currently, it is convenient to pass this + /// `process_id` in our struct) to avoid a search through the processes + /// array to schedule the upcall. Currently, it is convenient to pass this /// parameter so we take advantage of it. If in the future that is not the /// case we could have `process` be an Option and just do the search with - /// the stored process_id. + /// the stored [`ProcessId`]. pub(crate) fn schedule( - &mut self, + &self, process: &dyn process::Process, r0: usize, r1: usize, r2: usize, ) -> Result<(), UpcallError> { - let res = self.fn_ptr.map_or( - // A null-Upcall is treated as being delivered to - // the process and ignored - Ok(()), + let enqueue_res = self.fn_ptr.map_or_else( + || { + process.enqueue_task(process::Task::ReturnValue(process::ReturnArguments { + upcall_id: self.upcall_id, + argument0: r0, + argument1: r1, + argument2: r2, + })) + }, |fp| { - let enqueue_res = - process.enqueue_task(process::Task::FunctionCall(process::FunctionCall { - source: process::FunctionCallSource::Driver(self.upcall_id), - argument0: r0, - argument1: r1, - argument2: r2, - argument3: self.appdata, - pc: fp.as_ptr() as usize, - })); - - match enqueue_res { - Ok(()) => Ok(()), - Err(ErrorCode::NODEVICE) => { - // There should be no code path to schedule an - // Upcall on a process that is no longer - // alive. Indicate a kernel-internal error. - Err(UpcallError::KernelError) - } - Err(ErrorCode::NOMEM) => { - // No space left in the process' task queue. - Err(UpcallError::QueueFull) - } - Err(_) => { - // All other errors returned by - // `Process::enqueue_task` must be treated as - // kernel-internal errors - Err(UpcallError::KernelError) - } - } + process.enqueue_task(process::Task::FunctionCall(process::FunctionCall { + source: process::FunctionCallSource::Driver(self.upcall_id), + argument0: r0, + argument1: r1, + argument2: r2, + argument3: self.appdata, + pc: fp.as_ptr() as usize, + })) }, ); + let res = match enqueue_res { + Ok(()) => Ok(()), + Err(ErrorCode::NODEVICE) => { + // There should be no code path to schedule an Upcall on a + // process that is no longer alive. Indicate a kernel-internal + // error. + Err(UpcallError::KernelError) + } + Err(ErrorCode::NOMEM) => { + // No space left in the process' task queue. + Err(UpcallError::QueueFull) + } + Err(_) => { + // All other errors returned by `Process::enqueue_task` must be + // treated as kernel-internal errors + Err(UpcallError::KernelError) + } + }; + if config::CONFIG.trace_syscalls { debug!( "[{:?}] schedule[{:#x}:{}] @{:#x}({:#x}, {:#x}, {:#x}, {:#x}) = {:?}", self.process_id, self.upcall_id.driver_num, self.upcall_id.subscribe_num, - self.fn_ptr.map_or(0x0 as *mut (), |fp| fp.as_ptr()) as usize, + self.fn_ptr + .map_or(core::ptr::null_mut::<()>(), |fp| fp.as_ptr()) as usize, r0, r1, r2, @@ -185,7 +200,7 @@ impl Upcall { pub(crate) fn into_subscribe_success(self) -> SyscallReturn { match self.fn_ptr { Some(fp) => SyscallReturn::SubscribeSuccess(fp.as_ptr(), self.appdata), - None => SyscallReturn::SubscribeSuccess(0 as *const (), self.appdata), + None => SyscallReturn::SubscribeSuccess(core::ptr::null::<()>(), self.appdata), } } @@ -201,7 +216,7 @@ impl Upcall { pub(crate) fn into_subscribe_failure(self, err: ErrorCode) -> SyscallReturn { match self.fn_ptr { Some(fp) => SyscallReturn::SubscribeFailure(err, fp.as_ptr(), self.appdata), - None => SyscallReturn::SubscribeFailure(err, 0 as *const (), self.appdata), + None => SyscallReturn::SubscribeFailure(err, core::ptr::null::<()>(), self.appdata), } } } diff --git a/kernel/src/utilities/binary_write.rs b/kernel/src/utilities/binary_write.rs index a6b3fe3ef9..c9d3051f5c 100644 --- a/kernel/src/utilities/binary_write.rs +++ b/kernel/src/utilities/binary_write.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Basic binary write interface and supporting structs. //! //! This simple trait provides a synchronous interface for printing an arbitrary @@ -45,7 +49,7 @@ pub trait BinaryWrite { /// // Nothing left to print, we're done! /// } /// ``` -pub(crate) struct WriteToBinaryOffsetWrapper<'a> { +pub struct WriteToBinaryOffsetWrapper<'a> { /// Binary writer implementation that is asynchronous and has a fixed sized /// buffer. binary_writer: &'a mut dyn BinaryWrite, @@ -61,7 +65,7 @@ pub(crate) struct WriteToBinaryOffsetWrapper<'a> { } impl<'a> WriteToBinaryOffsetWrapper<'a> { - pub(crate) fn new(binary_writer: &'a mut dyn BinaryWrite) -> WriteToBinaryOffsetWrapper { + pub fn new(binary_writer: &'a mut dyn BinaryWrite) -> WriteToBinaryOffsetWrapper { WriteToBinaryOffsetWrapper { binary_writer, index: 0, @@ -72,19 +76,19 @@ impl<'a> WriteToBinaryOffsetWrapper<'a> { /// Set the byte to start printing from on this iteration. Call this before /// calling `Write`. - pub(crate) fn set_offset(&mut self, offset: usize) { + pub fn set_offset(&mut self, offset: usize) { self.offset = offset; } /// After printing, get the index we left off on to use as the offset for /// the next iteration. - pub(crate) fn get_index(&self) -> usize { + pub fn get_index(&self) -> usize { self.index } /// After printing, check if there is more to print that the binary_writer /// did not print. - pub(crate) fn bytes_remaining(&self) -> bool { + pub fn bytes_remaining(&self) -> bool { self.bytes_remaining } } diff --git a/kernel/src/utilities/copy_slice.rs b/kernel/src/utilities/copy_slice.rs index 49d7f90eb1..03471990f9 100644 --- a/kernel/src/utilities/copy_slice.rs +++ b/kernel/src/utilities/copy_slice.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use crate::ErrorCode; use core::ptr; diff --git a/kernel/src/utilities/helpers.rs b/kernel/src/utilities/helpers.rs index bd2959e716..6fe2575367 100644 --- a/kernel/src/utilities/helpers.rs +++ b/kernel/src/utilities/helpers.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Helper macros. /// Create an object with the given capability. @@ -19,12 +23,13 @@ macro_rules! create_capability { #[allow(unsafe_code)] unsafe impl $T for Cap {} Cap - };}; + }}; } /// Count the number of passed expressions. +/// /// Useful for constructing variable sized arrays in other macros. -/// Taken from the Little Book of Rust Macros +/// Taken from the Little Book of Rust Macros. /// /// ```ignore /// use kernel:count_expressions; @@ -37,3 +42,23 @@ macro_rules! count_expressions { ($head:expr $(,)?) => (1usize); ($head:expr, $($tail:expr),* $(,)?) => (1usize + count_expressions!($($tail),*)); } + +/// Compute a POSIX-style CRC32 checksum of a slice. +/// +/// Online calculator: +pub fn crc32_posix(b: &[u8]) -> u32 { + let mut crc: u32 = 0; + + for c in b { + crc ^= (*c as u32) << 24; + + for _i in 0..8 { + if crc & (0b1 << 31) > 0 { + crc = (crc << 1) ^ 0x04c11db7; + } else { + crc <<= 1; + } + } + } + !crc +} diff --git a/kernel/src/utilities/leasable_buffer.rs b/kernel/src/utilities/leasable_buffer.rs index a48b5336d8..3a74a536bc 100644 --- a/kernel/src/utilities/leasable_buffer.rs +++ b/kernel/src/utilities/leasable_buffer.rs @@ -1,23 +1,155 @@ -//! Defines a LeasableBuffer type which can be used to pass a section of a larger -//! buffer but still get the entire buffer back in a callback +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Defines a SubSlice type to implement leasable buffers. //! -//! Author: Amit Levy +//! A leasable buffer decouples maintaining a reference to a buffer from the +//! presentation of the accessible buffer. This allows layers to operate on +//! "windows" of the buffer while enabling the original reference (and in effect +//! the entire buffer) to be passed back in a callback. +//! +//! Challenge with Normal Rust Slices +//! --------------------------------- +//! +//! Commonly in Tock we want to partially fill a static buffer with some data, +//! call an asynchronous operation on that data, and then retrieve that buffer +//! via a callback. In common Rust code, that might look something like this +//! (for this example we are transmitting data using I2C). +//! +//! ```rust,ignore +//! // Statically declare the buffer. Make sure it is long enough to handle all +//! // I2C operations we need to perform. +//! let buffer = static_init!([u8; 64], [0; 64]); +//! +//! // Populate the buffer with our current operation. +//! buffer[0] = OPERATION_SET; +//! buffer[1] = REGISTER; +//! buffer[2] = 0x7; // Value to set the register to. +//! +//! // Call the I2C hardware to transmit the data, passing the slice we actually +//! // want to transmit and not the full buffer. +//! i2c.write(buffer[0..3]); +//! ``` +//! +//! The issue with this is that within the I2C driver, `buffer` is now only +//! three bytes long. When the I2C driver issues the callback to return the +//! buffer after the transmission completes, the returned buffer will have a +//! length of three. Effectively, the full static buffer is lost. +//! +//! To avoid this, in Tock we always call operations with both the buffer and a +//! separate length. We now have two lengths, the provided `length` parameter +//! which is the size of the buffer actually in use, and `buffer.len()` which is +//! the full size of the static memory. +//! +//! ```rust,ignore +//! // Call the I2C hardware with a reference to the full buffer and the length +//! // of that buffer it should actually consider. +//! i2c.write(buffer, 3); +//! ``` +//! +//! Now the I2C driver has a reference to the full buffer, and so when it +//! returns the buffer via callback the client will have access to the full +//! static buffer. +//! +//! Challenge with Buffers + Length +//! ------------------------------- +//! +//! Using a reference to the buffer and a separate length parameter is +//! sufficient to address the challenge of needing variable size buffers when +//! using static buffers and complying with Rust's memory management. However, +//! it still has two drawbacks. +//! +//! First, all code in Tock that operates on buffers must correctly handle the +//! separate buffer and length values as though the `buffer` is a `*u8` pointer +//! (as in more traditional C code). We lose many of the benefits of the higher +//! level slice primitive in Rust. For example, calling `buffer.len()` when +//! using data from the buffer is essentially meaningless, as the correct length +//! is the `length` parameter. When copying data _to_ the buffer, however, not +//! overflowing the buffer is critical, and using `buffer.len()` _is_ correct. +//! With separate reference and length managing this is left to the programmer. +//! +//! Second, using only a reference and length assumes that the contents of the +//! buffer will always start at the first entry in the buffer (i.e., +//! `buffer[0]`). To support more generic use of the buffer, we might want to +//! pass a reference, length, _and offset_, so that we can use arbitrary regions +//! of the buffer, while again retaining a reference to the original buffer to +//! use in callbacks. +//! +//! For example, in networking code it is common to parse headers and then pass +//! the payload to upper layers. With slices, that might look something like: +//! +//! ```rust,ignore +//! // Check for a valid header of size 10. +//! if (valid_header(buffer)) { +//! self.client.payload_callback(buffer[10..]); +//! } +//! ``` +//! +//! The issue is that again the client loses access to the beginning of the +//! buffer and that memory is lost. +//! +//! We might also want to do this when calling lower-layer operations to avoid +//! moving and copying data around. Consider a networking layer that needs to +//! add a header, we might want to do something like: +//! +//! ```rust,ignore +//! buffer[11] = PAYLOAD; +//! network_layer_send(buffer, 11, 1); +//! +//! fn network_layer_send(buffer: &'static [u8], offset: usize, length: usize) { +//! buffer[0..11] = header; +//! lower_layer_send(buffer); +//! } +//! ``` +//! +//! Now we have to keep track of two parameters which are both redundant with +//! the API provided by Rust slices. +//! +//! Leasable Buffers +//! ---------------- +//! +//! A leasable buffer is a data structure that addresses these challenges. +//! Simply, it provides the Rust slice API while internally always retaining a +//! reference to the full underlying buffer. To narrow a buffer, the leasable +//! buffer can be "sliced". To retrieve the full original memory, a leasable +//! buffer can be "reset". +//! +//! A leasable buffer can be sliced multiple times. For example, as a buffer is +//! parsed in a networking stack, each layer can call slice on the leasable +//! buffer to remove that layer's header before passing the buffer to the upper +//! layer. +//! +//! Supporting Mutable and Immutable Buffers +//! ---------------------------------------- +//! +//! One challenge with implementing leasable buffers in rust is preserving the +//! mutability of the underlying buffer. If a mutable buffer is passed as an +//! immutable slice, the mutability of that buffer is "lost" (i.e., when passed +//! back in a callback the buffer will be immutable). To address this, we must +//! implement two versions of a leasable buffer: mutable and immutable. That way +//! a mutable buffer remains mutable. +//! +//! Since in Tock most buffers are mutable, the mutable version is commonly +//! used. However, in cases where size is a concern, immutable buffers from +//! flash storage may be preferable. In those cases the immutable version may +//! be used. //! //! Usage //! ----- //! -//! `slice()` is used to set the portion of the `LeasableBuffer` that is accessbile. -//! `reset()` makes the entire `LeasableBuffer` accessible again. -//! Typically, `slice()` will be called prior to passing the buffer down to lower layers, -//! and `reset()` will be called once the `LeasableBuffer` is returned via a callback +//! `slice()` is used to set the portion of the `SubSlice` that is accessible. +//! `reset()` makes the entire `SubSlice` accessible again. Typically, `slice()` +//! will be called prior to passing the buffer down to lower layers, and +//! `reset()` will be called once the `SubSlice` is returned via a callback. //! //! ```rust -//! # use kernel::utilities::leasable_buffer::LeasableBuffer; +//! # use kernel::utilities::leasable_buffer::SubSlice; //! //! let mut internal = ['a', 'b', 'c', 'd']; //! let original_base_addr = internal.as_ptr(); //! -//! let mut buffer = LeasableBuffer::new(&mut internal); +//! let mut buffer = SubSlice::new(&mut internal); //! //! buffer.slice(1..3); //! @@ -32,60 +164,157 @@ //! assert_eq!((buffer[0], buffer[1]), ('a', 'b')); //! //! ``` +//! +//! Author: Amit Levy use core::ops::{Bound, Range, RangeBounds}; use core::ops::{Index, IndexMut}; use core::slice::SliceIndex; -/// Leasable Buffer which can be used to pass a section of a larger buffer but still -/// get the entire buffer back in a callback -pub struct LeasableBuffer<'a, T> { +/// A mutable leasable buffer implementation. +/// +/// A leasable buffer can be used to pass a section of a larger mutable buffer +/// but still get the entire buffer back in a callback. +#[derive(Debug, PartialEq)] +pub struct SubSliceMut<'a, T> { internal: &'a mut [T], active_range: Range, } -impl<'a, T> LeasableBuffer<'a, T> { - /// Create a leasable buffer from a passed reference to a raw buffer +/// An immutable leasable buffer implementation. +/// +/// A leasable buffer can be used to pass a section of a larger mutable buffer +/// but still get the entire buffer back in a callback. +#[derive(Debug, PartialEq)] +pub struct SubSlice<'a, T> { + internal: &'a [T], + active_range: Range, +} + +/// Holder for either a mutable or immutable SubSlice. +/// +/// In cases where code needs to support either a mutable or immutable SubSlice, +/// `SubSliceMutImmut` allows the code to store a single type which can +/// represent either option. +pub enum SubSliceMutImmut<'a, T> { + Immutable(SubSlice<'a, T>), + Mutable(SubSliceMut<'a, T>), +} + +impl<'a, T> SubSliceMutImmut<'a, T> { + pub fn reset(&mut self) { + match *self { + SubSliceMutImmut::Immutable(ref mut buf) => buf.reset(), + SubSliceMutImmut::Mutable(ref mut buf) => buf.reset(), + } + } + + /// Returns the length of the currently accessible portion of the + /// SubSlice. + pub fn len(&self) -> usize { + match *self { + SubSliceMutImmut::Immutable(ref buf) => buf.len(), + SubSliceMutImmut::Mutable(ref buf) => buf.len(), + } + } + + pub fn slice>(&mut self, range: R) { + match *self { + SubSliceMutImmut::Immutable(ref mut buf) => buf.slice(range), + SubSliceMutImmut::Mutable(ref mut buf) => buf.slice(range), + } + } +} + +impl<'a, T, I> Index for SubSliceMutImmut<'a, T> +where + I: SliceIndex<[T]>, +{ + type Output = >::Output; + + fn index(&self, idx: I) -> &Self::Output { + match *self { + SubSliceMutImmut::Immutable(ref buf) => &buf[idx], + SubSliceMutImmut::Mutable(ref buf) => &buf[idx], + } + } +} + +impl<'a, T> SubSliceMut<'a, T> { + /// Create a SubSlice from a passed reference to a raw buffer. pub fn new(buffer: &'a mut [T]) -> Self { let len = buffer.len(); - LeasableBuffer { + SubSliceMut { internal: buffer, active_range: 0..len, } } - /// Retrieve the raw buffer used to create the LeasableBuffer. Consumes - /// the LeasableBuffer. + fn active_slice(&self) -> &[T] { + &self.internal[self.active_range.clone()] + } + + /// Retrieve the raw buffer used to create the SubSlice. Consumes the + /// SubSlice. pub fn take(self) -> &'a mut [T] { self.internal } - /// Resets the LeasableBuffer to its full size, making the entire buffer - /// accessible again. Typically this would be called once a sliced - /// LeasableBuffer is returned through a callback + /// Resets the SubSlice to its full size, making the entire buffer + /// accessible again. + /// + /// This should only be called by layer that created the SubSlice, and not + /// layers that were passed a SubSlice. Layers which are using a SubSlice + /// should treat the SubSlice as a traditional Rust slice and not consider + /// any additional size to the underlying buffer. + /// + /// Most commonly, this is called once a sliced leasable buffer is returned + /// through a callback. pub fn reset(&mut self) { self.active_range = 0..self.internal.len(); } - fn active_slice(&self) -> &[T] { - &self.internal[self.active_range.clone()] - } - - /// Returns the length of the currently accessible portion of the LeasableBuffer + /// Returns the length of the currently accessible portion of the SubSlice. pub fn len(&self) -> usize { self.active_slice().len() } - /// Returns a pointer to the currently accessible portion of the LeasableBuffer + /// Returns a pointer to the currently accessible portion of the SubSlice. pub fn as_ptr(&self) -> *const T { self.active_slice().as_ptr() } - /// Reduces the range of the LeasableBuffer that is accessible. This should be called - /// whenever an upper layer wishes to pass only a portion of a larger buffer down to - /// a lower layer. For example: if the application layer has a 1500 byte packet - /// buffer, but wishes to send a 250 byte packet, the upper layer should slice the - /// LeasableBuffer down to its first 250 bytes before passing it down. + /// Returns a slice of the currently accessible portion of the + /// LeasableBuffer. + pub fn as_slice(&mut self) -> &mut [T] { + &mut self.internal[self.active_range.clone()] + } + + /// Returns `true` if the LeasableBuffer is sliced internally. + /// + /// This is a useful check when switching between code that uses + /// LeasableBuffers and code that uses traditional slice-and-length. Since + /// slice-and-length _only_ supports using the entire buffer it is not valid + /// to try to use a sliced LeasableBuffer. + pub fn is_sliced(&self) -> bool { + self.internal.len() != self.len() + } + + /// Reduces the range of the SubSlice that is accessible. + /// + /// This should be called whenever a layer wishes to pass only a portion of + /// a larger buffer to another layer. + /// + /// For example, if the application layer has a 1500 byte packet buffer, but + /// wishes to send a 250 byte packet, the upper layer should slice the + /// SubSlice down to its first 250 bytes before passing it down: + /// + /// ```rust,ignore + /// let buffer = static_init!([u8; 1500], [0; 1500]); + /// let s = SubSliceMut::new(buffer); + /// s.slice(0..250); + /// network.send(s); + /// ``` pub fn slice>(&mut self, range: R) { let start = match range.start_bound() { Bound::Included(s) => *s, @@ -96,7 +325,7 @@ impl<'a, T> LeasableBuffer<'a, T> { let end = match range.end_bound() { Bound::Included(e) => *e + 1, Bound::Excluded(e) => *e, - Bound::Unbounded => self.internal.len(), + Bound::Unbounded => self.active_range.end - self.active_range.start, }; let new_start = self.active_range.start + start; @@ -109,7 +338,7 @@ impl<'a, T> LeasableBuffer<'a, T> { } } -impl<'a, T, I> Index for LeasableBuffer<'a, T> +impl<'a, T, I> Index for SubSliceMut<'a, T> where I: SliceIndex<[T]>, { @@ -120,7 +349,7 @@ where } } -impl<'a, T, I> IndexMut for LeasableBuffer<'a, T> +impl<'a, T, I> IndexMut for SubSliceMut<'a, T> where I: SliceIndex<[T]>, { @@ -128,3 +357,114 @@ where &mut self.internal[self.active_range.clone()][idx] } } + +impl<'a, T> SubSlice<'a, T> { + /// Create a SubSlice from a passed reference to a raw buffer. + pub fn new(buffer: &'a [T]) -> Self { + let len = buffer.len(); + SubSlice { + internal: buffer, + active_range: 0..len, + } + } + + fn active_slice(&self) -> &[T] { + &self.internal[self.active_range.clone()] + } + + /// Retrieve the raw buffer used to create the SubSlice. Consumes the + /// SubSlice. + pub fn take(self) -> &'a [T] { + self.internal + } + + /// Resets the SubSlice to its full size, making the entire buffer + /// accessible again. + /// + /// This should only be called by layer that created the SubSlice, and not + /// layers that were passed a SubSlice. Layers which are using a SubSlice + /// should treat the SubSlice as a traditional Rust slice and not consider + /// any additional size to the underlying buffer. + /// + /// Most commonly, this is called once a sliced leasable buffer is returned + /// through a callback. + pub fn reset(&mut self) { + self.active_range = 0..self.internal.len(); + } + + /// Returns the length of the currently accessible portion of the SubSlice. + pub fn len(&self) -> usize { + self.active_slice().len() + } + + /// Returns a pointer to the currently accessible portion of the SubSlice. + pub fn as_ptr(&self) -> *const T { + self.active_slice().as_ptr() + } + + /// Returns a slice of the currently accessible portion of the + /// LeasableBuffer. + pub fn as_slice(&self) -> &[T] { + &self.internal[self.active_range.clone()] + } + + /// Returns `true` if the LeasableBuffer is sliced internally. + /// + /// This is a useful check when switching between code that uses + /// LeasableBuffers and code that uses traditional slice-and-length. Since + /// slice-and-length _only_ supports using the entire buffer it is not valid + /// to try to use a sliced LeasableBuffer. + pub fn is_sliced(&self) -> bool { + self.internal.len() != self.len() + } + + /// Reduces the range of the SubSlice that is accessible. + /// + /// This should be called whenever a layer wishes to pass only a portion of + /// a larger buffer to another layer. + /// + /// For example, if the application layer has a 1500 byte packet buffer, but + /// wishes to send a 250 byte packet, the upper layer should slice the + /// SubSlice down to its first 250 bytes before passing it down: + /// + /// ```rust,ignore + /// let buffer = unsafe { + /// core::slice::from_raw_parts(core::ptr::addr_of!(_ptr_in_flash), 1500) + /// }; + /// let s = SubSlice::new(buffer); + /// s.slice(0..250); + /// network.send(s); + /// ``` + pub fn slice>(&mut self, range: R) { + let start = match range.start_bound() { + Bound::Included(s) => *s, + Bound::Excluded(s) => *s + 1, + Bound::Unbounded => 0, + }; + + let end = match range.end_bound() { + Bound::Included(e) => *e + 1, + Bound::Excluded(e) => *e, + Bound::Unbounded => self.active_range.end - self.active_range.start, + }; + + let new_start = self.active_range.start + start; + let new_end = new_start + (end - start); + + self.active_range = Range { + start: new_start, + end: new_end, + }; + } +} + +impl<'a, T, I> Index for SubSlice<'a, T> +where + I: SliceIndex<[T]>, +{ + type Output = >::Output; + + fn index(&self, idx: I) -> &Self::Output { + &self.internal[self.active_range.clone()][idx] + } +} diff --git a/kernel/src/utilities/math.rs b/kernel/src/utilities/math.rs index 46e664d17a..041aef07cb 100644 --- a/kernel/src/utilities/math.rs +++ b/kernel/src/utilities/math.rs @@ -1,6 +1,9 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Helper functions for common mathematical operations. -use core::convert::{From, Into}; use core::f32; /// Get closest power of two greater than the given number. @@ -111,7 +114,7 @@ fn ln_1to2_series_approximation(x: f32) -> f32 { //https://en.wikipedia.org/wiki/Remez_algorithm let ln_1to2_polynomial: f32 = -1.741_793_9_f32 + (2.821_202_6_f32 - + (-1.469_956_8_f32 + (0.447_179_55_f32 - 0.056_570_851_f32 * x_working) * x_working) + + (-1.469_956_8_f32 + (0.447_179_55_f32 - 0.056_570_85_f32 * x_working) * x_working) * x_working) * x_working; // ln(2) * n + ln(y) diff --git a/kernel/src/utilities/mod.rs b/kernel/src/utilities/mod.rs index d5d428ee90..5fc700c0fe 100644 --- a/kernel/src/utilities/mod.rs +++ b/kernel/src/utilities/mod.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Utility functions and macros provided by the kernel crate. pub mod binary_write; diff --git a/kernel/src/utilities/mut_imut_buffer.rs b/kernel/src/utilities/mut_imut_buffer.rs index b547b47405..1c1f47a556 100644 --- a/kernel/src/utilities/mut_imut_buffer.rs +++ b/kernel/src/utilities/mut_imut_buffer.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! An enum that can contain a reference to either a mutable or //! an immutable buffer. //! diff --git a/kernel/src/utilities/peripheral_management.rs b/kernel/src/utilities/peripheral_management.rs index 6454915d76..ac581456ce 100644 --- a/kernel/src/utilities/peripheral_management.rs +++ b/kernel/src/utilities/peripheral_management.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Peripheral Management //! //! Most peripherals are implemented as memory mapped I/O (MMIO). diff --git a/kernel/src/utilities/static_init.rs b/kernel/src/utilities/static_init.rs index c6e7c915ec..cf583b960c 100644 --- a/kernel/src/utilities/static_init.rs +++ b/kernel/src/utilities/static_init.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Support for statically initializing objects in memory. /// Allocates a statically-sized global array of memory and initializes the @@ -6,6 +10,9 @@ /// This macro creates the static buffer, ensures it is initialized to the /// proper type, and then returns a `&'static mut` reference to it. /// +/// Note: Because this instantiates a static object, you generally cannot pass +/// a type with generic parameters. github.com/tock/tock/issues/2995 for detail. +/// /// # Safety /// /// As this macro will write directly to a global area without acquiring a lock @@ -17,12 +24,38 @@ macro_rules! static_init { ($T:ty, $e:expr $(,)?) => {{ let mut buf = $crate::static_buf!($T); - buf.initialize($e) + buf.write($e) }}; } -/// Allocates a statically-sized global array of memory for data structures but -/// does not initialize the memory. +/// An `#[inline(never)]` function that panics internally if the passed reference +/// is `true`. This function is intended for use within +/// the `static_buf!()` macro, which removes the size bloat of track_caller +/// saving the location of every single call to `static_init!()`. +/// If you hit this panic, you are either calling `static_buf!()` in +/// a loop or calling a function multiple times which internally +/// contains a call to `static_buf!()`. Typically, calls to +/// `static_buf!()` are hidden within calls to `static_init!()` or +/// component helper macros, so start your search there. +#[inline(never)] +pub fn static_buf_check_used(used: &mut bool) { + // Check if this `BUF` has already been declared and initialized. If it + // has, then this is a repeated `static_buf!()` call which is an error + // as it will alias the same `BUF`. + if *used { + // panic, this buf has already been declared and initialized. + // NOTE: To save 144 bytes of code size, use loop {} instead of this + // panic. + panic!("Error! Single static_buf!() called twice."); + } else { + // Otherwise, mark our uninitialized buffer as used. + *used = true; + } +} + +/// Allocates a statically-sized global region of memory for data structures but +/// does not initialize the memory. Checks that the buffer is not aliased and is +/// only used once. /// /// This macro creates the static buffer, and returns a /// `StaticUninitializedBuffer` wrapper containing the buffer. The memory is @@ -46,105 +79,21 @@ macro_rules! static_init { #[macro_export] macro_rules! static_buf { ($T:ty $(,)?) => {{ - // Statically allocate a read-write buffer for the value, write our - // initial value into it (without dropping the initial zeros) and - // return a reference to it. - static mut BUF: $crate::utilities::static_init::UninitializedBuffer<$T> = - $crate::utilities::static_init::UninitializedBuffer::new(); - $crate::utilities::static_init::StaticUninitializedBuffer::new(&mut BUF) - }}; -} + // Statically allocate a read-write buffer for the value without + // actually writing anything, as well as a flag to track if + // this memory has been initialized yet. + static mut BUF: (core::mem::MaybeUninit<$T>, bool) = + (core::mem::MaybeUninit::uninit(), false); -use core::mem::MaybeUninit; + // To minimize the amount of code duplicated across every invocation + // of this macro, all of the logic for checking if the buffer has been + // used is contained within the static_buf_check_used function, + // which panics if the passed boolean has been used and sets the + // boolean to true otherwise. + $crate::utilities::static_init::static_buf_check_used(&mut BUF.1); -/// The `UninitializedBuffer` type is designed to be statically allocated -/// as a global buffer to hold data structures in Tock. As a static, global -/// buffer the data structure can then be shared in the Tock kernel. -/// -/// This type is implemented as a wrapper around a `MaybeUninit` buffer. -/// To enforce that the global static buffer is initialized exactly once, -/// this wrapper type ensures that the underlying memory is uninitialized -/// so that an `UninitializedBuffer` does not contain an initialized value. -/// -/// The only way to initialize this buffer is to create a -/// `StaticUninitializedBuffer`, pass the `UninitializedBuffer` to it, and call -/// `initialize()`. This structure ensures that: -/// -/// 1. The static buffer is not used while uninitialized. Since the only way to -/// get the necessary `&'static mut T` is to call `initialize()`, the memory -/// is guaranteed to be initialized. -/// -/// 2. A static buffer is not initialized twice. Since the underlying memory is -/// owned by `UninitializedBuffer` nothing else can initialize it. Also, once -/// the memory is initialized via `StaticUninitializedBuffer.initialize()`, -/// the internal buffer is consumed and `initialize()` cannot be called -/// again. -#[repr(transparent)] -pub struct UninitializedBuffer(MaybeUninit); - -impl UninitializedBuffer { - /// The only way to construct an `UninitializedBuffer` is via this function, - /// which initializes it to `MaybeUninit::uninit()`. This guarantees the - /// invariant that `UninitializedBuffer` does not contain an initialized - /// value. - pub const fn new() -> Self { - UninitializedBuffer(MaybeUninit::uninit()) - } -} - -/// The `StaticUninitializedBuffer` type represents a statically allocated -/// buffer that can be converted to another type once it has been initialized. -/// Upon initialization, a static mutable reference is returned and the -/// `StaticUninitializedBuffer` is consumed. -/// -/// This type is implemented as a wrapper containing a static mutable reference to -/// an `UninitializedBuffer`. This guarantees that the memory pointed to by the -/// reference has not already been initialized. -/// -/// `StaticUninitializedBuffer` provides one operation: `initialize()` that returns a -/// `&'static mut T` reference. This is the only way to get the reference, and -/// ensures that the underlying uninitialized buffer is properly initialized. -/// The wrapper is also consumed when `initialize()` is called, ensuring that -/// the underlying memory cannot be subsequently re-initialized. -pub struct StaticUninitializedBuffer { - buf: &'static mut UninitializedBuffer, -} - -impl StaticUninitializedBuffer { - /// This function is not intended to be called publicly. It's only meant to - /// be called within `static_buf!` macro, but Rust's visibility rules - /// require it to be public, so that the macro's body can be instantiated. - pub fn new(buf: &'static mut UninitializedBuffer) -> Self { - Self { buf } - } - - /// This function consumes an uninitialized static buffer, initializes it - /// to some value, and returns a static mutable reference to it. This - /// allows for runtime initialization of `static` values that do not have a - /// `const` constructor. - pub unsafe fn initialize(self, value: T) -> &'static mut T { - self.buf.0.as_mut_ptr().write(value); - // TODO: use MaybeUninit::get_mut() once that is stabilized (see - // https://github.com/rust-lang/rust/issues/63568). - &mut *self.buf.0.as_mut_ptr() as &'static mut T - } -} - -/// This macro is deprecated. You should migrate to using `static_buf!` -/// followed by a call to `StaticUninitializedBuffer::initialize()`. -/// -/// Same as `static_init!()` but without actually creating the static buffer. -/// The static buffer must be passed in. -#[macro_export] -macro_rules! static_init_half { - ($B:expr, $T:ty, $e:expr $(,)?) => { - { - use core::mem::MaybeUninit; - let buf: &'static mut MaybeUninit<$T> = $B; - buf.as_mut_ptr().write($e); - // TODO: use MaybeUninit::get_mut() once that is stabilized (see - // https://github.com/rust-lang/rust/issues/63568). - &mut *buf.as_mut_ptr() as &'static mut $T - } - }; + // If we get to this point we can wrap our buffer to be eventually + // initialized. + &mut BUF.0 + }}; } diff --git a/kernel/src/utilities/static_ref.rs b/kernel/src/utilities/static_ref.rs index 710ca7a50d..d72196454c 100644 --- a/kernel/src/utilities/static_ref.rs +++ b/kernel/src/utilities/static_ref.rs @@ -1,6 +1,11 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Wrapper type for safe pointers to static memory. use core::ops::Deref; +use core::ptr::NonNull; /// A pointer to statically allocated mutable data such as memory mapped I/O /// registers. @@ -10,9 +15,14 @@ use core::ops::Deref; /// given a raw address and acts similarly to `extern` definitions, except /// `StaticRef` is subject to module and crate boundaries, while `extern` /// definitions can be imported anywhere. +/// +/// Because this defers the actual dereference, this can be put in a `const`, +/// whereas `const I32_REF: &'static i32 = unsafe { &*(0x1000 as *const i32) };` +/// will always fail to compile since `0x1000` doesn't have an allocation at +/// compile time, even if it's known to be a valid MMIO address. #[derive(Debug)] pub struct StaticRef { - ptr: *const T, + ptr: NonNull, } impl StaticRef { @@ -20,16 +30,19 @@ impl StaticRef { /// /// ## Safety /// - /// Callers must pass in a reference to statically allocated memory which - /// does not overlap with other values. + /// - `ptr` must be aligned, non-null, and dereferencable as `T`. + /// - `*ptr` must be valid for the program duration. pub const unsafe fn new(ptr: *const T) -> StaticRef { - StaticRef { ptr: ptr } + // SAFETY: `ptr` is non-null as promised by the caller. + StaticRef { + ptr: NonNull::new_unchecked(ptr.cast_mut()), + } } } impl Clone for StaticRef { fn clone(&self) -> Self { - StaticRef { ptr: self.ptr } + *self } } @@ -37,7 +50,9 @@ impl Copy for StaticRef {} impl Deref for StaticRef { type Target = T; - fn deref(&self) -> &'static T { - unsafe { &*self.ptr } + fn deref(&self) -> &T { + // SAFETY: `ptr` is aligned and dereferencable for the program + // duration as promised by the caller of `StaticRef::new`. + unsafe { self.ptr.as_ref() } } } diff --git a/kernel/src/utilities/storage_volume.rs b/kernel/src/utilities/storage_volume.rs index c00825bf9f..9954101afc 100644 --- a/kernel/src/utilities/storage_volume.rs +++ b/kernel/src/utilities/storage_volume.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Utility for creating non-volatile storage regions. /// Allocates space in the kernel image for on-chip non-volatile storage. diff --git a/libraries/enum_primitive/Cargo.toml b/libraries/enum_primitive/Cargo.toml index af743eb17f..88c04121fe 100644 --- a/libraries/enum_primitive/Cargo.toml +++ b/libraries/enum_primitive/Cargo.toml @@ -1,3 +1,11 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "enum_primitive" version = "0.1.0" +edition = "2021" + +[lints] +workspace = true diff --git a/libraries/enum_primitive/src/cast.rs b/libraries/enum_primitive/src/cast.rs index dbd7e7f8b8..51e8beb93a 100644 --- a/libraries/enum_primitive/src/cast.rs +++ b/libraries/enum_primitive/src/cast.rs @@ -1,9 +1,10 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use core::mem::size_of; use core::num::Wrapping; -use core::{i16, i32, i64, i8, isize}; -use core::{u16, u32, u64, u8, usize}; - /// A generic trait for converting a value to a number. pub trait ToPrimitive { /// Converts the value of `self` to an `isize`. @@ -101,8 +102,6 @@ macro_rules! impl_to_primitive_int { fn to_i16 -> i16; fn to_i32 -> i32; fn to_i64 -> i64; - #[cfg(has_i128)] - fn to_i128 -> i128; } impl_to_primitive_int_to_uint! { $T: @@ -111,8 +110,6 @@ macro_rules! impl_to_primitive_int { fn to_u16 -> u16; fn to_u32 -> u32; fn to_u64 -> u64; - #[cfg(has_i128)] - fn to_u128 -> u128; } } }; diff --git a/libraries/enum_primitive/src/lib.rs b/libraries/enum_primitive/src/lib.rs index e2410f4c56..e2d2ebd91c 100644 --- a/libraries/enum_primitive/src/lib.rs +++ b/libraries/enum_primitive/src/lib.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Helper library for converting numbers to enums. // copied from https://github.com/andersk/enum_primitive-rs which did not with with nostd out of box diff --git a/libraries/riscv-csr/Cargo.toml b/libraries/riscv-csr/Cargo.toml index 260e5dc3c0..e223fecf60 100644 --- a/libraries/riscv-csr/Cargo.toml +++ b/libraries/riscv-csr/Cargo.toml @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "riscv-csr" version = "0.1.0" @@ -16,3 +20,6 @@ travis-ci = { repository = "tock/tock", branch = "master" } [dependencies] tock-registers = { path = "../tock-register-interface", default-features = false } + +[lints] +workspace = true diff --git a/libraries/riscv-csr/src/csr.rs b/libraries/riscv-csr/src/csr.rs index 62716496c0..3b583c7f24 100644 --- a/libraries/riscv-csr/src/csr.rs +++ b/libraries/riscv-csr/src/csr.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! `ReadWriteRiscvCsr` type for RISC-V CSRs. use core::marker::PhantomData; @@ -146,6 +150,7 @@ impl ReadWriteRiscvCsr { ))] #[inline] pub fn atomic_replace(&self, val_to_set: usize) -> usize { + use core::arch::asm; let r: usize; unsafe { asm!("csrrw {rd}, {csr}, {rs1}", @@ -166,7 +171,10 @@ impl ReadWriteRiscvCsr { /// instruction where `rs1 = in(reg) value_to_set` and `rd = /// out(reg) `. // Mock implementations for tests on Travis-CI. - #[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64", target_os = "none")))] + #[cfg(not(all( + any(target_arch = "riscv32", target_arch = "riscv64"), + target_os = "none" + )))] pub fn atomic_replace(&self, _value_to_set: usize) -> usize { unimplemented!("RISC-V CSR {} Atomic Read/Write", V) } @@ -182,6 +190,7 @@ impl ReadWriteRiscvCsr { ))] #[inline] pub fn read_and_set_bits(&self, bitmask: usize) -> usize { + use core::arch::asm; let r: usize; unsafe { asm!("csrrs {rd}, {csr}, {rs1}", @@ -198,7 +207,10 @@ impl ReadWriteRiscvCsr { /// instruction where `rs1 = in(reg) bitmask` and `rd = out(reg) /// `. // Mock implementations for tests on Travis-CI. - #[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64", target_os = "none")))] + #[cfg(not(all( + any(target_arch = "riscv32", target_arch = "riscv64"), + target_os = "none" + )))] pub fn read_and_set_bits(&self, bitmask: usize) -> usize { unimplemented!( "RISC-V CSR {} Atomic Read and Set Bits, bitmask {:04x}", @@ -218,6 +230,7 @@ impl ReadWriteRiscvCsr { ))] #[inline] pub fn read_and_clear_bits(&self, bitmask: usize) -> usize { + use core::arch::asm; let r: usize; unsafe { asm!("csrrc {rd}, {csr}, {rs1}", @@ -234,7 +247,10 @@ impl ReadWriteRiscvCsr { /// instruction where `rs1 = in(reg) bitmask` and `rd = out(reg) /// `. // Mock implementations for tests on Travis-CI. - #[cfg(not(any(target_arch = "riscv32", target_arch = "riscv64", target_os = "none")))] + #[cfg(not(all( + any(target_arch = "riscv32", target_arch = "riscv64"), + target_os = "none" + )))] pub fn read_and_clear_bits(&self, bitmask: usize) -> usize { unimplemented!( "RISC-V CSR {} Atomic Read and Clear Bits, bitmask {:04x}", @@ -278,6 +294,7 @@ impl Readable for ReadWriteRiscvCsr usize { + use core::arch::asm; let r: usize; unsafe { asm!("csrr {rd}, {csr}", rd = out(reg) r, csr = const V); @@ -286,7 +303,10 @@ impl Readable for ReadWriteRiscvCsr usize { unimplemented!("reading RISC-V CSR {}", V) } @@ -302,12 +322,16 @@ impl Writeable for ReadWriteRiscvCsr"] edition = "2021" readme = "README.md" +keywords = ["flash", "key-value-store"] +categories = ["database-implementations", "no-std"] + +[lints] +workspace = true diff --git a/libraries/tickv/README.md b/libraries/tickv/README.md index e990465929..2e0c2aeeae 100644 --- a/libraries/tickv/README.md +++ b/libraries/tickv/README.md @@ -8,7 +8,7 @@ on flash. It was written to be generic though, so other Rust applications can use it if they want. TicKV is based on similar concepts as -[Yaffs1](https://yaffs.net/documents/how-yaffs-works). +[Yaffs1](https://yaffs.net/sites/yaffs.net/files/HowYaffsWorks.pdf). ## Goals of TicKV @@ -94,9 +94,9 @@ committed. This is the durability guarantee as part of the ACID semantics. The only data that can be lost in the event of a power loss is the data which hasn't been write to flash yet. -If a power loss occurs after calling `append_key()` or `invalidate_key()` -before it has completed then the operation probably did not complete and -that data is lost. +If a power loss occurs after calling `append_key()`, `invalidate_key()` or +`zeroize_key()` before it has completed then the operation probably did not +complete and that data is lost. ### Security @@ -115,9 +115,7 @@ erase operations. TicKV stores the version when adding objects to the flash storage. -TicKV is currently version 0. +TicKV is currently version 1. - * Version 0 - * Version 0 is a draft version. It should NOT be used for important data! - Version 0 maintains no backwards compatible support and could change at - any time. + * Version 1 + * Initial release diff --git a/libraries/tickv/SPEC.md b/libraries/tickv/SPEC.md index 36ba405fe5..d69c042510 100644 --- a/libraries/tickv/SPEC.md +++ b/libraries/tickv/SPEC.md @@ -10,9 +10,9 @@ to add such features. TicKV allows writing new key/value pairs (by appending them) and removing old key/value pairs. -Similar to (Yaffs1)[https://yaffs.net/documents/how-yaffs-works] TicKV uses a -log structure and circles over the flash data. This means that the file -system is inherently wear leveling as we don't regularly write to the same +Similar to [Yaffs1](https://yaffs.net/sites/yaffs.net/files/HowYaffsWorks.pdf) +TicKV uses a log structure and circles over the flash data. This means that the +file system is inherently wear leveling as we don't regularly write to the same flash region. ## Storage Format @@ -175,6 +175,23 @@ If all the objects in a region are no longer valid then that region will be erased when `garbage_collect()` is called. Note that even if the flash is full `garbage_collect()` will not be called automatically. +### Zeroising keys + +This is similar to the `invalidate_key()` function, but instead will +change all `1`s in the value and checksum to `0`s. This does not remove +the header, as that is required for garbage collection later on, so the +length and hashed key will still be preserved. + +The values will be changed by a single write operation to the flash. +The values are not securley overwritten to make restoring data +difficult. + +Users will need to check with the hardware specifications to determine +if this is cryptographically secure for their use case. + +As this data is marked as invalid, `garbage_collect()` will function as normal +removing both zeroised keys as well as invalid keys. + ### Initialisation When setting up a block of flash for the first time the entire size of flash @@ -400,6 +417,42 @@ flash will be the `valid` flag. The object header for ONE will now look like: No changes will happen in flash until key TWO has also been invalidated. At which point `garbage_collect()` can erase the region. +### Zeroising a key + +When the key ONE is zeroised with `zeroise_key()`, the only change in +the header will be the `valid` flag. The rest of the object will become +zeros. The object ONE will now look like: + +``` +0x400 0x42C +-------------------------------------------------------------------------------------------------------- +||||| version|len/flag| len | hashed_key | +||||| | | | | | | | | | | | +||||| 0x00|00000000| 0x34| 0xed| 0xa1| 0x00| 0x78| 0x88| 0x61| 0x93| 0xbb| +-------------------------------------------------------------------------------------------------------| +``` + +``` +0x42C 0x52C +-------------------------------------------------------------------------------------------------------- +||||| value | +||||| | +|||||00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000| +-------------------------------------------------------------------------------------------------------| +``` + +``` +0x52C 0x53C +----------------------------------------- +||||| checksum | +||||| | | | | +||||| 0x00| 0x00| 0x00| 0x00| +----------------------------------------| +``` + +No changes will happen in flash until key TWO has also been invalidated. +At which point `garbage_collect()` can erase the region once it is run. + ## Limitations of TicKV ### Fragmentation diff --git a/libraries/tickv/src/async_ops.rs b/libraries/tickv/src/async_ops.rs index b16dba8526..c553ae212a 100644 --- a/libraries/tickv/src/async_ops.rs +++ b/libraries/tickv/src/async_ops.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! TicKV can be used asynchronously. This module provides documentation and //! tests for using it with an async `FlashController` interface. //! @@ -41,7 +45,6 @@ //! fn read_region( //! &self, //! region_number: usize, -//! offset: usize, //! buf: &mut [u8; 1024], //! ) -> Result<(), ErrorCode> { //! // We aren't ready yet, launch the async operation @@ -80,7 +83,7 @@ //! let tickv = AsyncTicKV::::new(FlashCtrl::new(), //! &mut read_buf, 0x1000); //! -//! let mut ret = tickv.initalise(hash_function.finish()); +//! let mut ret = tickv.initialise(hash_function.finish()); //! while ret.is_err() { //! // There is no actual delay here, in a real implementation wait on some event //! ret = tickv.continue_operation().0; @@ -100,11 +103,11 @@ //! // when appending a key: //! //! // Add a key -//! static VALUE: [u8; 32] = [0x23; 32]; -//! let ret = unsafe { tickv.append_key(get_hashed_key(b"ONE"), &VALUE) }; +//! static mut VALUE: [u8; 32] = [0x23; 32]; +//! let ret = unsafe { tickv.append_key(get_hashed_key(b"ONE"), &mut VALUE, 32) }; //! //! match ret { -//! Err(ErrorCode::ReadNotReady(reg)) => { +//! Err((_buf, ErrorCode::ReadNotReady(reg))) => { //! // There is no actual delay in the test, just continue now //! tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg]); //! tickv @@ -135,6 +138,8 @@ type ContinueReturn = ( Result, // Buf Buffer Option<&'static mut [u8]>, + // Length of valid data inside of the buffer. + usize, ); /// The struct storing all of the TicKV information for the async implementation. @@ -142,8 +147,8 @@ pub struct AsyncTicKV<'a, C: FlashController, const S: usize> { /// The main TicKV struct pub tickv: TicKV<'a, C, S>, key: Cell>, - value: Cell>, - buf: Cell>, + value: Cell>, + value_length: Cell, } impl<'a, C: FlashController, const S: usize> AsyncTicKV<'a, C, S> { @@ -158,12 +163,12 @@ impl<'a, C: FlashController, const S: usize> AsyncTicKV<'a, C, S> { tickv: TicKV::::new(controller, read_buffer, flash_size), key: Cell::new(None), value: Cell::new(None), - buf: Cell::new(None), + value_length: Cell::new(0), } } /// This function setups the flash region to be used as a key-value store. - /// If the region is already initalised this won't make any changes. + /// If the region is already initialised this won't make any changes. /// /// `hashed_main_key`: The u64 hash of the const string `MAIN_KEY`. /// @@ -172,9 +177,9 @@ impl<'a, C: FlashController, const S: usize> AsyncTicKV<'a, C, S> { /// /// On success a `SuccessCode` will be returned. /// On error a `ErrorCode` will be returned. - pub fn initalise(&self, hashed_main_key: u64) -> Result { + pub fn initialise(&self, hashed_main_key: u64) -> Result { self.key.replace(Some(hashed_main_key)); - self.tickv.initalise(hashed_main_key) + self.tickv.initialise(hashed_main_key) } /// Appends the key/value pair to flash storage. @@ -185,14 +190,35 @@ impl<'a, C: FlashController, const S: usize> AsyncTicKV<'a, C, S> { /// /// On success nothing will be returned. /// On error a `ErrorCode` will be returned. - pub fn append_key(&self, hash: u64, value: &'static [u8]) -> Result { - match self.tickv.append_key(hash, value) { - Ok(code) => Ok(code), - Err(e) => { - self.key.replace(Some(hash)); - self.value.replace(Some(value)); - Err(e) + pub fn append_key( + &self, + hash: u64, + value: &'static mut [u8], + length: usize, + ) -> Result { + match self.tickv.append_key(hash, &value[0..length]) { + Ok(_code) => { + // Ok is a problem, since that means no asynchronous operations + // were called, which means our client will never get a + // callback. We need to error. + Err((value, ErrorCode::WriteFail)) } + Err(e) => match e { + ErrorCode::ReadNotReady(_) + | ErrorCode::EraseNotReady(_) + | ErrorCode::WriteNotReady(_) => { + // This is what we expect, since it means we are going + // an asynchronous operation which this interface expects. + self.key.replace(Some(hash)); + self.value.replace(Some(value)); + self.value_length.set(length); + Ok(SuccessCode::Queued) + } + _ => { + // On any other error we report the error. + Err((value, e)) + } + }, } } @@ -210,18 +236,23 @@ impl<'a, C: FlashController, const S: usize> AsyncTicKV<'a, C, S> { &self, hash: u64, buf: &'static mut [u8], - ) -> Result, ErrorCode)> { + ) -> Result { match self.tickv.get_key(hash, buf) { - Ok(code) => Ok(code), + Ok(_code) => { + // Ok is a problem, since that means no asynchronous operations + // were called, which means our client will never get a + // callback. We need to error. + Err((buf, ErrorCode::ReadFail)) + } Err(e) => match e { ErrorCode::ReadNotReady(_) | ErrorCode::EraseNotReady(_) | ErrorCode::WriteNotReady(_) => { self.key.replace(Some(hash)); - self.buf.replace(Some(buf)); - Err((None, e)) + self.value.replace(Some(buf)); + Ok(SuccessCode::Queued) } - _ => Err((Some(buf), e)), + _ => Err((buf, e)), }, } } @@ -238,20 +269,43 @@ impl<'a, C: FlashController, const S: usize> AsyncTicKV<'a, C, S> { /// assumed to be lost. pub fn invalidate_key(&self, hash: u64) -> Result { match self.tickv.invalidate_key(hash) { - Ok(code) => Ok(code), - Err(e) => { + Ok(_code) => Err(ErrorCode::WriteFail), + Err(_e) => { + self.key.replace(Some(hash)); + Ok(SuccessCode::Queued) + } + } + } + + /// Zeroizes the key in flash storage + /// + /// `hash`: A hashed key. + /// `key`: A unhashed key. This will be hashed internally. + /// + /// On success a `SuccessCode` will be returned. + /// On error a `ErrorCode` will be returned. + /// + /// If a power loss occurs before success is returned the data is + /// assumed to be lost. + pub fn zeroise_key(&self, hash: u64) -> Result { + match self.tickv.zeroise_key(hash) { + Ok(_code) => Err(ErrorCode::WriteFail), + Err(_e) => { self.key.replace(Some(hash)); - Err(e) + Ok(SuccessCode::Queued) } } } /// Perform a garbage collection on TicKV /// - /// On success the number of bytes freed will be returned. + /// On success a `SuccessCode` will be returned. /// On error a `ErrorCode` will be returned. - pub fn garbage_collect(&self) -> Result { - self.tickv.garbage_collect() + pub fn garbage_collect(&self) -> Result { + match self.tickv.garbage_collect() { + Ok(_code) => Err(ErrorCode::EraseFail), + Err(_e) => Ok(SuccessCode::Queued), + } } /// Copy data from `read_buffer` argument to the internal read_buffer. @@ -264,18 +318,6 @@ impl<'a, C: FlashController, const S: usize> AsyncTicKV<'a, C, S> { self.tickv.read_buffer.replace(Some(buf)); } - /// Get the `value` buffer that was passed in by previous - /// commands. - pub fn get_stored_value_buffer(&self) -> Option<&'static [u8]> { - self.value.take() - } - - /// Get the `buf` buffer that was passed in by previous - /// commands. - pub fn get_stored_buffer(&self) -> Option<&'static mut [u8]> { - self.buf.take() - } - /// Continue the last operation after the async operation has completed. /// This should be called from a read/erase complete callback. /// NOTE: If called from a read callback, `set_read_buffer` should be @@ -284,29 +326,41 @@ impl<'a, C: FlashController, const S: usize> AsyncTicKV<'a, C, S> { /// `hash_function`: Hash function with no previous state. This is /// usually a newly created hash. /// - /// Returns a tuple of 4 values + /// Returns a tuple of 3 values /// Result: /// On success a `SuccessCode` will be returned. /// On error a `ErrorCode` will be returned. /// Buf Buffer: /// An option of the buf buffer used + /// Length usize: + /// The number of valid bytes in the buffer. 0 if Buf is None. /// The buffers will only be returned on a non async error or on success. pub fn continue_operation(&self) -> ContinueReturn { - let ret = match self.tickv.state.get() { - State::Init(_) => self.tickv.initalise(self.key.get().unwrap()), - State::AppendKey(_) => self - .tickv - .append_key(self.key.get().unwrap(), self.value.get().unwrap()), + let (ret, length) = match self.tickv.state.get() { + State::Init(_) => (self.tickv.initialise(self.key.get().unwrap()), 0), + State::AppendKey(_) => { + let value = self.value.take().unwrap(); + let value_length = self.value_length.get(); + let ret = self + .tickv + .append_key(self.key.get().unwrap(), &value[0..value_length]); + self.value.replace(Some(value)); + (ret, value_length) + } State::GetKey(_) => { - let buf = self.buf.take().unwrap(); + let buf = self.value.take().unwrap(); let ret = self.tickv.get_key(self.key.get().unwrap(), buf); - self.buf.replace(Some(buf)); - ret + self.value.replace(Some(buf)); + match ret { + Ok((s, len)) => (Ok(s), len), + Err(e) => (Err(e), 0), + } } - State::InvalidateKey(_) => self.tickv.invalidate_key(self.key.get().unwrap()), + State::InvalidateKey(_) => (self.tickv.invalidate_key(self.key.get().unwrap()), 0), + State::ZeroiseKey(_) => (self.tickv.zeroise_key(self.key.get().unwrap()), 0), State::GarbageCollect(_) => match self.tickv.garbage_collect() { - Ok(_) => Ok(SuccessCode::Complete), - Err(e) => Err(e), + Ok(bytes_freed) => (Ok(SuccessCode::Complete), bytes_freed), + Err(e) => (Err(e), 0), }, _ => unreachable!(), }; @@ -314,552 +368,824 @@ impl<'a, C: FlashController, const S: usize> AsyncTicKV<'a, C, S> { match ret { Ok(_) => { self.tickv.state.set(State::None); - (ret, self.buf.take()) + (ret, self.value.take(), length) } Err(e) => match e { - ErrorCode::ReadNotReady(_) | ErrorCode::EraseNotReady(_) => (ret, None), + ErrorCode::ReadNotReady(_) | ErrorCode::EraseNotReady(_) => (ret, None, 0), ErrorCode::WriteNotReady(_) => { self.tickv.state.set(State::None); - (ret, None) + (ret, None, 0) } _ => { self.tickv.state.set(State::None); - (ret, self.buf.take()) + (ret, self.value.take(), length) } }, } } } -/// Tests using a flash controller that can store data #[cfg(test)] -mod store_flast_ctrl { - use crate::async_ops::AsyncTicKV; - use crate::error_codes::ErrorCode; - use crate::flash_controller::FlashController; - use crate::tickv::{HASH_OFFSET, LEN_OFFSET, MAIN_KEY, VERSION, VERSION_OFFSET}; - use core::hash::{Hash, Hasher}; - use std::cell::Cell; - use std::cell::RefCell; - use std::collections::hash_map::DefaultHasher; - - fn check_region_main(buf: &[u8]) { - // Check the version - assert_eq!(buf[VERSION_OFFSET], VERSION); - - // Check the length - assert_eq!(buf[LEN_OFFSET], 0x80); - assert_eq!(buf[LEN_OFFSET + 1], 15); - - // Check the hash - assert_eq!(buf[HASH_OFFSET + 0], 0x7b); - assert_eq!(buf[HASH_OFFSET + 1], 0xc9); - assert_eq!(buf[HASH_OFFSET + 2], 0xf7); - assert_eq!(buf[HASH_OFFSET + 3], 0xff); - assert_eq!(buf[HASH_OFFSET + 4], 0x4f); - assert_eq!(buf[HASH_OFFSET + 5], 0x76); - assert_eq!(buf[HASH_OFFSET + 6], 0xf2); - assert_eq!(buf[HASH_OFFSET + 7], 0x44); - - // Check the check hash - assert_eq!(buf[HASH_OFFSET + 8], 0x55); - assert_eq!(buf[HASH_OFFSET + 9], 0xb5); - assert_eq!(buf[HASH_OFFSET + 10], 0xd8); - assert_eq!(buf[HASH_OFFSET + 11], 0xe4); - } +mod tests { + #![allow(unsafe_code)] + + /// Tests using a flash controller that can store data + mod store_flast_ctrl { + use crate::async_ops::AsyncTicKV; + use crate::error_codes::ErrorCode; + use crate::flash_controller::FlashController; + use crate::success_codes::SuccessCode; + use crate::tickv::{HASH_OFFSET, LEN_OFFSET, MAIN_KEY, VERSION, VERSION_OFFSET}; + use core::hash::{Hash, Hasher}; + use core::ptr::addr_of_mut; + use std::cell::Cell; + use std::cell::RefCell; + use std::collections::hash_map::DefaultHasher; + + fn check_region_main(buf: &[u8]) { + // Check the version + assert_eq!(buf[VERSION_OFFSET], VERSION); + + // Check the length + assert_eq!(buf[LEN_OFFSET], 0x80); + assert_eq!(buf[LEN_OFFSET + 1], 15); + + // Check the hash + assert_eq!(buf[HASH_OFFSET + 0], 0x7b); + assert_eq!(buf[HASH_OFFSET + 1], 0xc9); + assert_eq!(buf[HASH_OFFSET + 2], 0xf7); + assert_eq!(buf[HASH_OFFSET + 3], 0xff); + assert_eq!(buf[HASH_OFFSET + 4], 0x4f); + assert_eq!(buf[HASH_OFFSET + 5], 0x76); + assert_eq!(buf[HASH_OFFSET + 6], 0xf2); + assert_eq!(buf[HASH_OFFSET + 7], 0x44); + + // Check the check hash + assert_eq!(buf[HASH_OFFSET + 8], 0xbb); + assert_eq!(buf[HASH_OFFSET + 9], 0x32); + assert_eq!(buf[HASH_OFFSET + 10], 0x74); + assert_eq!(buf[HASH_OFFSET + 11], 0x1d); + } - fn check_region_one(buf: &[u8]) { - // Check the version - assert_eq!(buf[VERSION_OFFSET], VERSION); - - // Check the length - assert_eq!(buf[LEN_OFFSET], 0x80); - assert_eq!(buf[LEN_OFFSET + 1], 47); - - // Check the hash - assert_eq!(buf[HASH_OFFSET + 0], 0x81); - assert_eq!(buf[HASH_OFFSET + 1], 0x13); - assert_eq!(buf[HASH_OFFSET + 2], 0x7e); - assert_eq!(buf[HASH_OFFSET + 3], 0x95); - assert_eq!(buf[HASH_OFFSET + 4], 0x9e); - assert_eq!(buf[HASH_OFFSET + 5], 0x93); - assert_eq!(buf[HASH_OFFSET + 6], 0xaa); - assert_eq!(buf[HASH_OFFSET + 7], 0x3d); - - // Check the value - assert_eq!(buf[HASH_OFFSET + 8], 0x23); - assert_eq!(buf[28], 0x23); - assert_eq!(buf[42], 0x23); - - // Check the check hash - assert_eq!(buf[43], 0xf7); - assert_eq!(buf[44], 0x1d); - assert_eq!(buf[45], 0xb3); - assert_eq!(buf[46], 0xe9); - } + fn check_region_one(buf: &[u8]) { + // Check the version + assert_eq!(buf[VERSION_OFFSET], VERSION); + + // Check the length + assert_eq!(buf[LEN_OFFSET], 0x80); + assert_eq!(buf[LEN_OFFSET + 1], 47); + + // Check the hash + assert_eq!(buf[HASH_OFFSET + 0], 0x81); + assert_eq!(buf[HASH_OFFSET + 1], 0x13); + assert_eq!(buf[HASH_OFFSET + 2], 0x7e); + assert_eq!(buf[HASH_OFFSET + 3], 0x95); + assert_eq!(buf[HASH_OFFSET + 4], 0x9e); + assert_eq!(buf[HASH_OFFSET + 5], 0x93); + assert_eq!(buf[HASH_OFFSET + 6], 0xaa); + assert_eq!(buf[HASH_OFFSET + 7], 0x3d); + + // Check the value + assert_eq!(buf[HASH_OFFSET + 8], 0x23); + assert_eq!(buf[28], 0x23); + assert_eq!(buf[42], 0x23); + + // Check the check hash + assert_eq!(buf[43], 0xfd); + assert_eq!(buf[44], 0x24); + assert_eq!(buf[45], 0xf0); + assert_eq!(buf[46], 0x07); + } - fn check_region_two(buf: &[u8]) { - // Check the version - assert_eq!(buf[VERSION_OFFSET], VERSION); - - // Check the length - assert_eq!(buf[LEN_OFFSET], 0x80); - assert_eq!(buf[LEN_OFFSET + 1], 47); - - // Check the hash - assert_eq!(buf[HASH_OFFSET + 0], 0x9d); - assert_eq!(buf[HASH_OFFSET + 1], 0xd3); - assert_eq!(buf[HASH_OFFSET + 2], 0x71); - assert_eq!(buf[HASH_OFFSET + 3], 0x45); - assert_eq!(buf[HASH_OFFSET + 4], 0x05); - assert_eq!(buf[HASH_OFFSET + 5], 0xc2); - assert_eq!(buf[HASH_OFFSET + 6], 0xf8); - assert_eq!(buf[HASH_OFFSET + 7], 0x66); - - // Check the value - assert_eq!(buf[HASH_OFFSET + 8], 0x23); - assert_eq!(buf[28], 0x23); - assert_eq!(buf[42], 0x23); - - // Check the check hash - assert_eq!(buf[43], 0x11); - assert_eq!(buf[44], 0x6a); - assert_eq!(buf[45], 0xba); - assert_eq!(buf[46], 0xba); - } + fn check_region_two(buf: &[u8]) { + // Check the version + assert_eq!(buf[VERSION_OFFSET], VERSION); + + // Check the length + assert_eq!(buf[LEN_OFFSET], 0x80); + assert_eq!(buf[LEN_OFFSET + 1], 47); + + // Check the hash + assert_eq!(buf[HASH_OFFSET + 0], 0x9d); + assert_eq!(buf[HASH_OFFSET + 1], 0xd3); + assert_eq!(buf[HASH_OFFSET + 2], 0x71); + assert_eq!(buf[HASH_OFFSET + 3], 0x45); + assert_eq!(buf[HASH_OFFSET + 4], 0x05); + assert_eq!(buf[HASH_OFFSET + 5], 0xc2); + assert_eq!(buf[HASH_OFFSET + 6], 0xf8); + assert_eq!(buf[HASH_OFFSET + 7], 0x66); + + // Check the value + assert_eq!(buf[HASH_OFFSET + 8], 0x23); + assert_eq!(buf[28], 0x23); + assert_eq!(buf[42], 0x23); + + // Check the check hash + assert_eq!(buf[43], 0x1b); + assert_eq!(buf[44], 0x53); + assert_eq!(buf[45], 0xf9); + assert_eq!(buf[46], 0x54); + } - fn get_hashed_key(unhashed_key: &[u8]) -> u64 { - let mut hash_function = DefaultHasher::new(); - unhashed_key.hash(&mut hash_function); - hash_function.finish() - } + fn get_hashed_key(unhashed_key: &[u8]) -> u64 { + let mut hash_function = DefaultHasher::new(); + unhashed_key.hash(&mut hash_function); + hash_function.finish() + } - // An example FlashCtrl implementation - struct FlashCtrl { - buf: RefCell<[[u8; 1024]; 64]>, - run: Cell, - async_read_region: Cell, - async_erase_region: Cell, - } + #[derive(Clone, Copy)] + enum FlashCtrlAction { + Idle, + Read, + Write, + Erase, + } + + // An example FlashCtrl implementation + struct FlashCtrl { + buf: RefCell<[[u8; S]; 64]>, + run: Cell, + async_read_region: Cell, + async_erase_region: Cell, + waiting_on: Cell, + check_write_contents: bool, + } + + impl FlashCtrl { + fn new(check_write_contents: bool) -> Self { + Self { + buf: RefCell::new([[0xFF; S]; 64]), + run: Cell::new(0), + async_read_region: Cell::new(100), + async_erase_region: Cell::new(100), + waiting_on: Cell::new(FlashCtrlAction::Idle), + check_write_contents, + } + } - impl FlashCtrl { - fn new() -> Self { - Self { - buf: RefCell::new([[0xFF; 1024]; 64]), - run: Cell::new(0), - async_read_region: Cell::new(100), - async_erase_region: Cell::new(100), + fn get_waiting_action(&self) -> FlashCtrlAction { + self.waiting_on.get() } } - } - impl FlashController<1024> for FlashCtrl { - fn read_region( - &self, - region_number: usize, - offset: usize, - buf: &mut [u8; 1024], - ) -> Result<(), ErrorCode> { - println!("Read from region: {}", region_number); + impl FlashController for FlashCtrl { + fn read_region( + &self, + region_number: usize, + _buf: &mut [u8; S], + ) -> Result<(), ErrorCode> { + println!("Read from region: {}", region_number); - if self.async_read_region.get() != region_number { // Pretend that we aren't ready self.async_read_region.set(region_number); println!(" Not ready"); - return Err(ErrorCode::ReadNotReady(region_number)); - } - for (i, b) in buf.iter_mut().enumerate() { - *b = self.buf.borrow()[region_number][offset + i] - } + self.waiting_on.set(FlashCtrlAction::Read); - // println!(" buf: {:#x?}", self.buf.borrow()[region_number]); - - Ok(()) - } + Err(ErrorCode::ReadNotReady(region_number)) + } - fn write(&self, address: usize, buf: &[u8]) -> Result<(), ErrorCode> { - println!( - "Write to address: {:#x}, region: {}", - address, - address / 1024 - ); + fn write(&self, address: usize, buf: &[u8]) -> Result<(), ErrorCode> { + println!( + "Write to address: {:#x}, region: {}", + address % S, + address / S + ); - for (i, d) in buf.iter().enumerate() { - self.buf.borrow_mut()[address / 1024][(address % 1024) + i] = *d; - } + for (i, d) in buf.iter().enumerate() { + self.buf.borrow_mut()[address / S][(address % S) + i] = *d; + } - // Check to see if we are adding a key - if buf.len() > 1 { - if self.run.get() == 0 { - println!("Writing main key: {:#x?}", buf); - check_region_main(buf); - } else if self.run.get() == 1 { - println!("Writing key ONE: {:#x?}", buf); - check_region_one(buf); - } else if self.run.get() == 2 { - println!("Writing key TWO: {:#x?}", buf); - check_region_two(buf); + // Check to see if we are adding a key + if buf.len() > 1 && self.check_write_contents { + if self.run.get() == 0 { + println!("Writing main key: {:#x?}", buf); + check_region_main(buf); + } else if self.run.get() == 1 { + println!("Writing key ONE: {:#x?}", buf); + check_region_one(buf); + } else if self.run.get() == 2 { + println!("Writing key TWO: {:#x?}", buf); + check_region_two(buf); + } } + + self.run.set(self.run.get() + 1); + self.waiting_on.set(FlashCtrlAction::Write); + Err(ErrorCode::WriteNotReady(address)) } - self.run.set(self.run.get() + 1); + fn erase_region(&self, region_number: usize) -> Result<(), ErrorCode> { + println!("Erase region: {}", region_number); - Ok(()) - } + let mut local_buf = self.buf.borrow_mut()[region_number]; - fn erase_region(&self, region_number: usize) -> Result<(), ErrorCode> { - println!("Erase region: {}", region_number); + for d in local_buf.iter_mut() { + *d = 0xFF; + } - if self.async_erase_region.get() != region_number { // Pretend that we aren't ready self.async_erase_region.set(region_number); - return Err(ErrorCode::EraseNotReady(region_number)); - } - - let mut local_buf = self.buf.borrow_mut()[region_number]; - for d in local_buf.iter_mut() { - *d = 0xFF; + self.waiting_on.set(FlashCtrlAction::Erase); + Err(ErrorCode::EraseNotReady(region_number)) } - - Ok(()) } - } - #[test] - fn test_simple_append() { - let mut read_buf: [u8; 1024] = [0; 1024]; - let mut hash_function = DefaultHasher::new(); - MAIN_KEY.hash(&mut hash_function); + /// This function implements what would happen in the callback function + /// triggered by the underlying flash hardware. In effect, it is the + /// callback handler, but since we don't actually have an async flash + /// implementation, this is just called by each test after starting the + /// flash operation. + fn flash_ctrl_callback(tickv: &AsyncTicKV, S>) { + match tickv.tickv.controller.get_waiting_action() { + FlashCtrlAction::Read => { + // This mimics a read is complete, and we provide the buffer + // with the newly read data to the tickv layer. + tickv.set_read_buffer( + &tickv.tickv.controller.buf.borrow() + [tickv.tickv.controller.async_read_region.get()], + ); + } + _ => { + // For write an erase all of the operation already occurred + // in the original operation, and nothing needs to be done + // in the simulated callback. + } + } + } - let tickv = AsyncTicKV::::new(FlashCtrl::new(), &mut read_buf, 0x1000); + #[test] + fn test_simple_append() { + let mut read_buf: [u8; 1024] = [0; 1024]; + let mut hash_function = DefaultHasher::new(); + MAIN_KEY.hash(&mut hash_function); - let mut ret = tickv.initalise(hash_function.finish()); - while ret.is_err() { - // There is no actual delay in the test, just continue now - let (r, _buf) = tickv.continue_operation(); - ret = r; - } + let tickv = AsyncTicKV::, 1024>::new( + FlashCtrl::new(true), + &mut read_buf, + 0x1000, + ); - static VALUE: [u8; 32] = [0x23; 32]; + let mut ret = tickv.initialise(hash_function.finish()); + while ret.is_err() { + flash_ctrl_callback(&tickv); - let ret = tickv.append_key(get_hashed_key(b"ONE"), &VALUE); - match ret { - Err(ErrorCode::ReadNotReady(reg)) => { // There is no actual delay in the test, just continue now - tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg]); - tickv.continue_operation().0.unwrap(); + let (r, _buf, _len) = tickv.continue_operation(); + ret = r; + } + + static mut VALUE: [u8; 32] = [0x23; 32]; + + println!("HASHED KEY {:?}", get_hashed_key(b"ONE")); + + let ret = + unsafe { tickv.append_key(get_hashed_key(b"ONE"), &mut *addr_of_mut!(VALUE), 32) }; + match ret { + Ok(SuccessCode::Queued) => { + // There is no actual delay in the test, just continue now + flash_ctrl_callback(&tickv); + tickv.continue_operation().0.unwrap(); + } + Err(_) => {} + _ => unreachable!(), + } + + let ret = + unsafe { tickv.append_key(get_hashed_key(b"TWO"), &mut *addr_of_mut!(VALUE), 32) }; + match ret { + Ok(SuccessCode::Queued) => { + // There is no actual delay in the test, just continue now + flash_ctrl_callback(&tickv); + tickv.continue_operation().0.unwrap(); + } + Err(_) => {} + _ => unreachable!(), } - Ok(_) => {} - _ => unreachable!(), } - let ret = tickv.append_key(get_hashed_key(b"TWO"), &VALUE); - match ret { - Err(ErrorCode::ReadNotReady(reg)) => { + #[test] + fn test_double_append() { + let mut read_buf: [u8; 1024] = [0; 1024]; + let mut hash_function = DefaultHasher::new(); + MAIN_KEY.hash(&mut hash_function); + + let tickv = AsyncTicKV::, 1024>::new( + FlashCtrl::new(true), + &mut read_buf, + 0x10000, + ); + + let mut ret = tickv.initialise(hash_function.finish()); + while ret.is_err() { + flash_ctrl_callback(&tickv); + // There is no actual delay in the test, just continue now - tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg]); - tickv.continue_operation().0.unwrap(); + let (r, _buf, _len) = tickv.continue_operation(); + ret = r; } - Ok(_) => {} - _ => unreachable!(), - } - } - #[test] - fn test_double_append() { - let mut read_buf: [u8; 1024] = [0; 1024]; - let mut hash_function = DefaultHasher::new(); - MAIN_KEY.hash(&mut hash_function); + static mut VALUE: [u8; 32] = [0x23; 32]; + static mut BUF: [u8; 32] = [0; 32]; - let tickv = AsyncTicKV::::new(FlashCtrl::new(), &mut read_buf, 0x10000); + println!("Add key ONE"); + let ret = + unsafe { tickv.append_key(get_hashed_key(b"ONE"), &mut *addr_of_mut!(VALUE), 32) }; + match ret { + Ok(SuccessCode::Queued) => { + // There is no actual delay in the test, just continue now + flash_ctrl_callback(&tickv); + tickv.continue_operation().0.unwrap(); + } + Err(_) => {} + _ => unreachable!(), + } - let mut ret = tickv.initalise(hash_function.finish()); - while ret.is_err() { - // There is no actual delay in the test, just continue now - let (r, _buf) = tickv.continue_operation(); - ret = r; - } + println!("Get key ONE"); - static VALUE: [u8; 32] = [0x23; 32]; - static mut BUF: [u8; 32] = [0; 32]; + let ret = unsafe { tickv.get_key(get_hashed_key(b"ONE"), &mut *addr_of_mut!(BUF)) }; + match ret { + Ok(SuccessCode::Queued) => { + flash_ctrl_callback(&tickv); + tickv.continue_operation().0.unwrap(); + } + Err(_) => {} + _ => unreachable!(), + } - println!("Add key ONE"); - let ret = tickv.append_key(get_hashed_key(b"ONE"), &VALUE); - match ret { - Err(ErrorCode::ReadNotReady(reg)) => { - // There is no actual delay in the test, just continue now - tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg]); - tickv.continue_operation().0.unwrap(); + println!("Get non-existent key TWO"); + let ret = unsafe { tickv.get_key(get_hashed_key(b"TWO"), &mut *addr_of_mut!(BUF)) }; + match ret { + Ok(SuccessCode::Queued) => { + // There is no actual delay in the test, just continue now + flash_ctrl_callback(&tickv); + assert_eq!(tickv.continue_operation().0, Err(ErrorCode::KeyNotFound)); + } + Err((_, ErrorCode::KeyNotFound)) => {} + _ => unreachable!(), } - Ok(_) => {} - _ => unreachable!(), - } - println!("Get key ONE"); - #[allow(unsafe_code)] - unsafe { - tickv.get_key(get_hashed_key(b"ONE"), &mut BUF).unwrap(); - } + println!("Add key ONE again"); + let ret = + unsafe { tickv.append_key(get_hashed_key(b"ONE"), &mut *addr_of_mut!(VALUE), 32) }; + match ret { + Ok(SuccessCode::Queued) => { + // There is no actual delay in the test, just continue now + flash_ctrl_callback(&tickv); + assert_eq!( + tickv.continue_operation().0, + Err(ErrorCode::KeyAlreadyExists) + ); + } + Err((_buf, ErrorCode::KeyAlreadyExists)) => {} + _ => unreachable!(), + } - println!("Get non-existant key TWO"); - #[allow(unsafe_code)] - let ret = unsafe { tickv.get_key(get_hashed_key(b"TWO"), &mut BUF) }; - match ret { - Err((_, ErrorCode::ReadNotReady(reg))) => { - // There is no actual delay in the test, just continue now - tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg]); - assert_eq!(tickv.continue_operation().0, Err(ErrorCode::KeyNotFound)); + println!("Add key TWO"); + let ret = + unsafe { tickv.append_key(get_hashed_key(b"TWO"), &mut *addr_of_mut!(VALUE), 32) }; + match ret { + Ok(SuccessCode::Queued) => { + // There is no actual delay in the test, just continue now + flash_ctrl_callback(&tickv); + tickv.continue_operation().0.unwrap(); + } + Err(_) => {} + _ => unreachable!(), } - Err((_, ErrorCode::KeyNotFound)) => {} - _ => unreachable!(), - } - println!("Add key ONE again"); - let ret = tickv.append_key(get_hashed_key(b"ONE"), &VALUE); - match ret { - Err(ErrorCode::ReadNotReady(reg)) => { - // There is no actual delay in the test, just continue now - tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg]); - assert_eq!( - tickv.continue_operation().0, - Err(ErrorCode::KeyAlreadyExists) - ); + println!("Get key ONE"); + let ret = unsafe { tickv.get_key(get_hashed_key(b"ONE"), &mut *addr_of_mut!(BUF)) }; + match ret { + Ok(SuccessCode::Queued) => { + // There is no actual delay in the test, just continue now + flash_ctrl_callback(&tickv); + tickv.continue_operation().0.unwrap(); + } + Err(_) => {} + _ => unreachable!(), } - Err(ErrorCode::KeyAlreadyExists) => {} - _ => unreachable!(), - } - println!("Add key TWO"); - let ret = tickv.append_key(get_hashed_key(b"TWO"), &VALUE); - match ret { - Err(ErrorCode::ReadNotReady(reg)) => { - // There is no actual delay in the test, just continue now - tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg]); - tickv.continue_operation().0.unwrap(); + println!("Get key TWO"); + let ret = unsafe { tickv.get_key(get_hashed_key(b"TWO"), &mut *addr_of_mut!(BUF)) }; + match ret { + Ok(SuccessCode::Queued) => { + // There is no actual delay in the test, just continue now + flash_ctrl_callback(&tickv); + tickv.continue_operation().0.unwrap(); + } + Err(_) => {} + _ => unreachable!(), } - Ok(_) => {} - _ => unreachable!(), - } - println!("Get key ONE"); - #[allow(unsafe_code)] - let ret = unsafe { tickv.get_key(get_hashed_key(b"ONE"), &mut BUF) }; - match ret { - Err((_, ErrorCode::ReadNotReady(reg))) => { - // There is no actual delay in the test, just continue now - tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg]); - tickv.continue_operation().0.unwrap(); + println!("Get non-existent key THREE"); + let ret = unsafe { tickv.get_key(get_hashed_key(b"THREE"), &mut *addr_of_mut!(BUF)) }; + match ret { + Ok(SuccessCode::Queued) => { + // There is no actual delay in the test, just continue now + flash_ctrl_callback(&tickv); + assert_eq!(tickv.continue_operation().0, Err(ErrorCode::KeyNotFound)); + } + _ => unreachable!(), } - Ok(_) => {} - _ => unreachable!(), - } - println!("Get key TWO"); - #[allow(unsafe_code)] - let ret = unsafe { tickv.get_key(get_hashed_key(b"TWO"), &mut BUF) }; - match ret { - Err((_, ErrorCode::ReadNotReady(reg))) => { - // There is no actual delay in the test, just continue now - tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg]); - tickv.continue_operation().0.unwrap(); + let ret = unsafe { tickv.get_key(get_hashed_key(b"THREE"), &mut *addr_of_mut!(BUF)) }; + match ret { + Ok(SuccessCode::Queued) => { + flash_ctrl_callback(&tickv); + assert_eq!(tickv.continue_operation().0, Err(ErrorCode::KeyNotFound)); + } + Err(_) => {} + _ => unreachable!(), } - Ok(_) => {} - _ => unreachable!(), } - println!("Get non-existant key THREE"); - #[allow(unsafe_code)] - let ret = unsafe { tickv.get_key(get_hashed_key(b"THREE"), &mut BUF) }; - match ret { - Err((_, ErrorCode::ReadNotReady(reg))) => { + #[test] + fn test_spread_pages() { + let mut read_buf: [u8; 64] = [0; 64]; + let mut hash_function = DefaultHasher::new(); + MAIN_KEY.hash(&mut hash_function); + + let tickv = + AsyncTicKV::, 64>::new(FlashCtrl::new(false), &mut read_buf, 64 * 64); + + let mut ret = tickv.initialise(hash_function.finish()); + while ret.is_err() { + tickv.set_read_buffer( + &tickv.tickv.controller.buf.borrow() + [tickv.tickv.controller.async_read_region.get()], + ); + // There is no actual delay in the test, just continue now - tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg]); - assert_eq!(tickv.continue_operation().0, Err(ErrorCode::KeyNotFound)); + let (r, _buf, _len) = tickv.continue_operation(); + ret = r; } - _ => unreachable!(), - } - #[allow(unsafe_code)] - unsafe { - match tickv.get_key(get_hashed_key(b"THREE"), &mut BUF) { - Err((_, ErrorCode::KeyNotFound)) => {} - _ => { - panic!("Expected ErrorCode::KeyNotFound"); + static mut VALUE: [u8; 32] = [0x23; 32]; + static mut BUF: [u8; 32] = [0; 32]; + + println!("Add key 0x1000"); + let ret = unsafe { tickv.append_key(0x1000, &mut *addr_of_mut!(VALUE), 32) }; + match ret { + Ok(SuccessCode::Queued) => { + // There is no actual delay in the test, just continue now + flash_ctrl_callback(&tickv); + tickv.continue_operation().0.unwrap(); } + Err(e) => panic!("Unable to add key 0x100: {e:?}"), + _ => unreachable!(), } - } - } - #[test] - fn test_append_and_delete() { - let mut read_buf: [u8; 1024] = [0; 1024]; - let mut hash_function = DefaultHasher::new(); - MAIN_KEY.hash(&mut hash_function); + println!("Add key 0x2000"); + let ret = unsafe { tickv.append_key(0x2000, &mut *addr_of_mut!(VALUE), 32) }; + match ret { + Ok(SuccessCode::Queued) => { + // There is no actual delay in the test, just continue now + flash_ctrl_callback(&tickv); - let tickv = AsyncTicKV::::new(FlashCtrl::new(), &mut read_buf, 0x10000); + assert_eq!( + tickv.continue_operation().0, + Err(ErrorCode::ReadNotReady(1)) + ); + flash_ctrl_callback(&tickv); - let mut ret = tickv.initalise(hash_function.finish()); - while ret.is_err() { - // There is no actual delay in the test, just continue now - let (r, _buf) = tickv.continue_operation(); - ret = r; - } + tickv.continue_operation().0.unwrap(); + } + Err(e) => panic!("Unable to add key 0x200: {e:?}"), + _ => unreachable!(), + } - static VALUE: [u8; 32] = [0x23; 32]; - static mut BUF: [u8; 32] = [0; 32]; + println!("Add key 0x3000"); + let ret = unsafe { tickv.append_key(0x3000, &mut *addr_of_mut!(VALUE), 32) }; + match ret { + Ok(SuccessCode::Queued) => { + // There is no actual delay in the test, just continue now + flash_ctrl_callback(&tickv); + } + Err(e) => panic!("Unable to add key 0x3000: {e:?}"), + _ => unreachable!(), + } - println!("Add key ONE"); - let ret = tickv.append_key(get_hashed_key(b"ONE"), &VALUE); - match ret { - Err(ErrorCode::ReadNotReady(reg)) => { - // There is no actual delay in the test, just continue now - tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg]); - tickv.continue_operation().0.unwrap(); + loop { + let ret = tickv.continue_operation().0; + match ret { + Err(ErrorCode::ReadNotReady(_reg)) => { + // There is no actual delay in the test, just continue now + flash_ctrl_callback(&tickv); + } + Err(e) => panic!("Unable to add key 0x3000: {e:?}"), + Ok(_) => break, + } } - Ok(_) => {} - _ => unreachable!(), - } - println!("Get key ONE"); - #[allow(unsafe_code)] - unsafe { - tickv.get_key(get_hashed_key(b"ONE"), &mut BUF).unwrap(); - } + println!("Get key 0x1000"); + let ret = unsafe { tickv.get_key(0x1000, &mut *addr_of_mut!(BUF)) }; + match ret { + Ok(SuccessCode::Queued) => { + // There is no actual delay in the test, just continue now + flash_ctrl_callback(&tickv); + } + _ => unreachable!(), + } - println!("Delete Key ONE"); - tickv.invalidate_key(get_hashed_key(b"ONE")).unwrap(); + loop { + let ret = tickv.continue_operation().0; + match ret { + Err(ErrorCode::ReadNotReady(_reg)) => { + // There is no actual delay in the test, just continue now + flash_ctrl_callback(&tickv); + } + Err(e) => panic!("Unable to get key 0x1000: {e:?}"), + Ok(_) => break, + } + } - println!("Get non-existant key ONE"); - #[allow(unsafe_code)] - unsafe { - match tickv.get_key(get_hashed_key(b"ONE"), &mut BUF) { - Err((_, ErrorCode::KeyNotFound)) => {} - _ => { - panic!("Expected ErrorCode::KeyNotFound"); + println!("Get key 0x3000"); + let ret = unsafe { tickv.get_key(0x3000, &mut *addr_of_mut!(BUF)) }; + match ret { + Ok(_) => flash_ctrl_callback(&tickv), + Err(_) => unreachable!(), + } + + loop { + let ret = tickv.continue_operation().0; + match ret { + Err(ErrorCode::ReadNotReady(reg)) => { + // There is no actual delay in the test, just continue now + tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg]); + } + Err(e) => panic!("Unable to get key 0x1000: {e:?}"), + Ok(_) => break, } } } - println!("Try to delete Key ONE Again"); - assert_eq!( - tickv.invalidate_key(get_hashed_key(b"ONE")), - Err(ErrorCode::KeyNotFound) - ); - } + #[test] + fn test_append_and_delete() { + let mut read_buf: [u8; 1024] = [0; 1024]; + let mut hash_function = DefaultHasher::new(); + MAIN_KEY.hash(&mut hash_function); - #[test] - fn test_garbage_collect() { - let mut read_buf: [u8; 1024] = [0; 1024]; - let mut hash_function = DefaultHasher::new(); - MAIN_KEY.hash(&mut hash_function); + let tickv = AsyncTicKV::, 1024>::new( + FlashCtrl::new(true), + &mut read_buf, + 0x10000, + ); - let tickv = AsyncTicKV::::new(FlashCtrl::new(), &mut read_buf, 0x10000); + let mut ret = tickv.initialise(hash_function.finish()); + while ret.is_err() { + flash_ctrl_callback(&tickv); - let mut ret = tickv.initalise(hash_function.finish()); - while ret.is_err() { - // There is no actual delay in the test, just continue now - let (r, _buf) = tickv.continue_operation(); - ret = r; - } + // There is no actual delay in the test, just continue now + let (r, _buf, _len) = tickv.continue_operation(); + ret = r; + } - static VALUE: [u8; 32] = [0x23; 32]; - static mut BUF: [u8; 32] = [0; 32]; - - println!("Garbage collect empty flash"); - let mut ret = tickv.garbage_collect(); - while ret.is_err() { - // There is no actual delay in the test, just continue now - ret = match tickv.continue_operation().0 { - Ok(_) => Ok(0), - Err(e) => Err(e), - }; - } + static mut VALUE: [u8; 32] = [0x23; 32]; + static mut BUF: [u8; 32] = [0; 32]; - println!("Add key ONE"); - let ret = tickv.append_key(get_hashed_key(b"ONE"), &VALUE); - match ret { - Err(ErrorCode::ReadNotReady(reg)) => { - // There is no actual delay in the test, just continue now - tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg]); - tickv.continue_operation().0.unwrap(); + println!("Add key ONE"); + let ret = + unsafe { tickv.append_key(get_hashed_key(b"ONE"), &mut *addr_of_mut!(VALUE), 32) }; + match ret { + Ok(SuccessCode::Queued) => { + // There is no actual delay in the test, just continue now + flash_ctrl_callback(&tickv); + tickv.continue_operation().0.unwrap(); + } + Err(_) => {} + _ => unreachable!(), } - Ok(_) => {} - _ => unreachable!(), - } - println!("Garbage collect flash with valid key"); - let mut ret = tickv.garbage_collect(); - while ret.is_err() { + println!("Get key ONE"); + let ret = unsafe { tickv.get_key(get_hashed_key(b"ONE"), &mut *addr_of_mut!(BUF)) }; match ret { - Err(ErrorCode::ReadNotReady(reg)) => { + Ok(SuccessCode::Queued) => { // There is no actual delay in the test, just continue now - tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg]); - ret = match tickv.continue_operation().0 { - Ok(_) => Ok(0), - Err(e) => Err(e), - }; + flash_ctrl_callback(&tickv); + tickv.continue_operation().0.unwrap(); } - Ok(num) => { - assert_eq!(num, 0); + Err(_) => {} + _ => unreachable!(), + } + + println!("Delete Key ONE"); + let ret = tickv.invalidate_key(get_hashed_key(b"ONE")); + match ret { + Ok(SuccessCode::Queued) => { + flash_ctrl_callback(&tickv); + tickv.continue_operation().0.unwrap(); } + Err(_) => {} _ => unreachable!(), } + + println!("Get non-existent key ONE"); + unsafe { + match tickv.get_key(get_hashed_key(b"ONE"), &mut *addr_of_mut!(BUF)) { + Ok(SuccessCode::Queued) => { + flash_ctrl_callback(&tickv); + + assert_eq!( + tickv.continue_operation().0, + Err(ErrorCode::ReadNotReady(62)) + ); + flash_ctrl_callback(&tickv); + + match tickv.continue_operation().0 { + Err(ErrorCode::ReadNotReady(reg)) => { + panic!("Searching too far for keys: {reg}"); + } + Err(ErrorCode::KeyNotFound) => {} + e => { + panic!("Expected ErrorCode::KeyNotFound, got {e:?}"); + } + } + } + _ => unreachable!(), + } + } + + println!("Try to delete Key ONE Again"); + match tickv.invalidate_key(get_hashed_key(b"ONE")) { + Ok(SuccessCode::Queued) => { + let reg = tickv.tickv.controller.async_read_region.get(); + + flash_ctrl_callback(&tickv); + assert_eq!( + tickv.continue_operation().0, + Err(ErrorCode::ReadNotReady(reg + 1)) + ); + + // In normal operation we will read region `reg`, determine + // that it isn't full and stop looking for the key + // + // The following test is a hack to continue testing. + // We don't fill the read buffer with new data. So + // the read buffer will continue to provide the data from + // `reg`, which means TicKV will continue searching for + // an empty region. + // + // In normal operation this isn't correct, but for the test + // case it's a good check to test region searching + assert_eq!( + tickv.continue_operation().0, + Err(ErrorCode::ReadNotReady(reg - 1)) + ); + + assert_eq!( + tickv.continue_operation().0, + Err(ErrorCode::ReadNotReady(reg + 2)) + ); + + assert_eq!( + tickv.continue_operation().0, + Err(ErrorCode::ReadNotReady(reg - 2)) + ); + + assert_eq!( + tickv.continue_operation().0, + Err(ErrorCode::ReadNotReady(reg - 3)) + ); + + // Now set the read buffer and end the search + tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg - 1]); + + match tickv.continue_operation().0 { + Err(ErrorCode::ReadNotReady(reg)) => { + panic!("Searching too far for keys: {reg}"); + } + Err(ErrorCode::KeyNotFound) => {} + e => { + panic!("Expected ErrorCode::KeyNotFound, got {e:?}"); + } + } + } + e => { + panic!("Expected ErrorCode::KeyNotFound, got {e:?}"); + } + } } - println!("Delete Key ONE"); - let ret = tickv.invalidate_key(get_hashed_key(b"ONE")); - match ret { - Err(ErrorCode::ReadNotReady(reg)) => { + #[test] + fn test_garbage_collect() { + let mut read_buf: [u8; 1024] = [0; 1024]; + let mut hash_function = DefaultHasher::new(); + MAIN_KEY.hash(&mut hash_function); + + let tickv = AsyncTicKV::, 1024>::new( + FlashCtrl::new(true), + &mut read_buf, + 0x10000, + ); + + let mut ret = tickv.initialise(hash_function.finish()); + while ret.is_err() { + flash_ctrl_callback(&tickv); + // There is no actual delay in the test, just continue now - tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg]); - tickv.continue_operation().0.unwrap(); + let (r, _buf, _len) = tickv.continue_operation(); + ret = r; } - Ok(_) => {} - _ => unreachable!("ret: {:?}", ret), - } - println!("Garbage collect flash with deleted key"); - let mut ret = tickv.garbage_collect(); - while ret.is_err() { + static mut VALUE: [u8; 32] = [0x23; 32]; + static mut BUF: [u8; 32] = [0; 32]; + + println!("Garbage collect empty flash"); + let ret = tickv.garbage_collect(); match ret { - Err(ErrorCode::ReadNotReady(reg)) => { + Ok(SuccessCode::Queued) => loop { + flash_ctrl_callback(&tickv); + let (res, _buf, len) = tickv.continue_operation(); + if res.is_ok() { + assert_eq!(len, 0); + break; + } + }, + Ok(_) => {} + _ => unreachable!(), + } + + println!("Add key ONE"); + let ret = + unsafe { tickv.append_key(get_hashed_key(b"ONE"), &mut *addr_of_mut!(VALUE), 32) }; + match ret { + Ok(SuccessCode::Queued) => { // There is no actual delay in the test, just continue now - tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg]); - ret = match tickv.continue_operation().0 { - Ok(_) => Ok(0), - Err(e) => Err(e), - }; + flash_ctrl_callback(&tickv); + tickv.continue_operation().0.unwrap(); } - Err(ErrorCode::EraseNotReady(_reg)) => { - // There is no actual delay in the test, just continue now - ret = match tickv.continue_operation().0 { - Ok(_) => Ok(0), - Err(e) => Err(e), - }; + Ok(_) => {} + _ => unreachable!(), + } + + println!("Garbage collect flash with valid key"); + let ret = tickv.garbage_collect(); + match ret { + Ok(SuccessCode::Queued) => loop { + flash_ctrl_callback(&tickv); + let (res, _buf, len) = tickv.continue_operation(); + if res.is_ok() { + assert_eq!(len, 0); + break; + } + }, + Ok(_) => {} + _ => unreachable!(), + } + + println!("Delete Key ONE"); + let ret = tickv.invalidate_key(get_hashed_key(b"ONE")); + match ret { + Ok(SuccessCode::Queued) => { + flash_ctrl_callback(&tickv); + tickv.continue_operation().0.unwrap(); } - Ok(num) => { - assert_eq!(num, 1024); + Err(_) => {} + _ => unreachable!(), + } + + println!("Garbage collect flash with deleted key"); + let ret = tickv.garbage_collect(); + match ret { + Ok(SuccessCode::Queued) => loop { + flash_ctrl_callback(&tickv); + let (res, _buf, len) = tickv.continue_operation(); + if res.is_ok() { + assert_eq!(len, 1024); + break; + } + }, + Ok(_) => {} + _ => unreachable!(), + } + + println!("Get non-existent key ONE"); + match unsafe { tickv.get_key(get_hashed_key(b"ONE"), &mut *addr_of_mut!(BUF)) } { + Ok(SuccessCode::Queued) => { + flash_ctrl_callback(&tickv); + assert_eq!( + tickv.continue_operation().0, + Err(ErrorCode::ReadNotReady(62)) + ); + flash_ctrl_callback(&tickv); + assert_eq!(tickv.continue_operation().0, Err(ErrorCode::KeyNotFound)); } - _ => unreachable!("ret: {:?}", ret), + _ => unreachable!(), } - } - println!("Get non-existant key ONE"); - #[allow(unsafe_code)] - let ret = unsafe { tickv.get_key(get_hashed_key(b"ONE"), &mut BUF) }; - match ret { - Err((_, ErrorCode::ReadNotReady(reg))) => { - // There is no actual delay in the test, just continue now - tickv.set_read_buffer(&tickv.tickv.controller.buf.borrow()[reg]); - assert_eq!(tickv.continue_operation().0, Err(ErrorCode::KeyNotFound)); + println!("Add Key ONE"); + let ret = + unsafe { tickv.append_key(get_hashed_key(b"ONE"), &mut *addr_of_mut!(VALUE), 32) }; + match ret { + Ok(SuccessCode::Queued) => { + // There is no actual delay in the test, just continue now + flash_ctrl_callback(&tickv); + tickv.continue_operation().0.unwrap(); + } + _ => unreachable!("ret: {:?}", ret), } - Err((_, ErrorCode::KeyNotFound)) => {} - _ => unreachable!("ret: {:?}", ret), } - - println!("Add Key ONE"); - tickv.append_key(get_hashed_key(b"ONE"), &VALUE).unwrap(); } } diff --git a/libraries/tickv/src/crc32.rs b/libraries/tickv/src/crc32.rs index 8ef68998cd..da47e8d4a7 100644 --- a/libraries/tickv/src/crc32.rs +++ b/libraries/tickv/src/crc32.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! A standalone CRC32 implementation //! //! This is based on the CRC32 implementation @@ -15,6 +19,8 @@ //! MIT License (LICENSE-MIT ) //! at your option. +use core::cell::Cell; + const CRC_TABLE: [u32; 256] = [ crc32(0x04c11db7, 0), crc32(0x04c11db7, 1), @@ -307,15 +313,21 @@ const fn crc32(poly: u32, mut byte: u8) -> u32 { [value, reflect_32(value)][0] } -pub struct Crc {} - -pub struct Digest<'a> { - crc: &'a Crc, - value: u32, -} +/// Internal `Crc` implementation +/// +/// This doesn't store any state, but just performs operations +/// +/// This is called from the public `Crc32` struct and functions +/// which store the state. +/// +/// We keep this seperate to clearly show the CRC implementation +/// details (such as initial state and xorout). +#[derive(Default)] +struct Crc {} impl Crc { - pub const fn new() -> Self { + /// Create a new instance for performing CRC32 operations. + const fn new() -> Self { Self {} } @@ -339,23 +351,50 @@ impl Crc { const fn finalise(&self, crc: u32) -> u32 { crc ^ 0xffffffff } +} - pub const fn digest(&self) -> Digest { - Digest::new(self) - } +/// A standalone CRC32 implementation +/// +/// This is based on the CRC32 implementation +/// from: crc-rs +/// +/// This implemented the CRC-32 checksum +/// poly: 0x04c11db7 +/// init: 0x00000000 +/// refin: false +/// refout: false +/// xorout: 0xffffffff +pub struct Crc32 { + crc: Crc, + value: Cell, } -impl<'a> Digest<'a> { - const fn new(crc: &'a Crc) -> Self { +impl Crc32 { + /// Create a new instance of the CRC implementation. + /// + /// This correctly initalises the CRC + pub const fn new() -> Self { + let crc = Crc::new(); let value = crc.init(); - Digest { crc, value } + Crc32 { + crc, + value: Cell::new(value), + } } - pub fn update(&mut self, bytes: &[u8]) { - self.value = self.crc.update(self.value, bytes); + /// Update the CRC based on the data in `bytes` + pub fn update(&self, bytes: &[u8]) { + self.value.set(self.crc.update(self.value.get(), bytes)); } - pub const fn finalise(self) -> u32 { - self.crc.finalise(self.value) + /// Complete the CRC operation and return the final value + /// + /// This will reset the CRC instance in preperation for new data + pub fn finalise(self) -> u32 { + let ret = self.crc.finalise(self.value.get()); + + self.value.set(self.crc.init()); + + ret } } diff --git a/libraries/tickv/src/error_codes.rs b/libraries/tickv/src/error_codes.rs index 8a2459f56b..858175cebc 100644 --- a/libraries/tickv/src/error_codes.rs +++ b/libraries/tickv/src/error_codes.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! The standard error codes used by TicKV. /// Standard error codes. diff --git a/libraries/tickv/src/flash_controller.rs b/libraries/tickv/src/flash_controller.rs index 4be500f69d..5d78a51c49 100644 --- a/libraries/tickv/src/flash_controller.rs +++ b/libraries/tickv/src/flash_controller.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! The Flash Controller interface with hardware use crate::error_codes::ErrorCode; @@ -36,7 +40,7 @@ use crate::error_codes::ErrorCode; /// } /// /// impl FlashController<1024> for FlashCtrl { -/// fn read_region(&self, region_number: usize, offset: usize, buf: &mut [u8; 1024]) -> Result<(), ErrorCode> { +/// fn read_region(&self, region_number: usize, buf: &mut [u8; 1024]) -> Result<(), ErrorCode> { /// unimplemented!() /// } /// @@ -52,8 +56,7 @@ use crate::error_codes::ErrorCode; pub trait FlashController { /// This function must read the data from the flash region specified by /// `region_number` into `buf`. The length of the data read should be the - /// same length as buf. `offset` indicates an offset into the region that - /// should be read. + /// same length as buf. /// /// On success it should return nothing, on failure it /// should return ErrorCode::ReadFail. @@ -67,12 +70,7 @@ pub trait FlashController { /// `read_region()` has returned `ErrorCode::ReadNotReady(region_number)` /// the `read_region()` function will be called again and this time should /// return the data. - fn read_region( - &self, - region_number: usize, - offset: usize, - buf: &mut [u8; S], - ) -> Result<(), ErrorCode>; + fn read_region(&self, region_number: usize, buf: &mut [u8; S]) -> Result<(), ErrorCode>; /// This function must write the length of `buf` to the specified address /// in flash. diff --git a/libraries/tickv/src/lib.rs b/libraries/tickv/src/lib.rs index 44f9cc784d..59070cda82 100644 --- a/libraries/tickv/src/lib.rs +++ b/libraries/tickv/src/lib.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! # TicKV //! //! TicKV (Tiny Circular Key Value) is a small file system allowing @@ -118,10 +122,10 @@ //! } //! //! impl FlashController<1024> for FlashCtrl { -//! fn read_region(&self, region_number: usize, offset: usize, buf: &mut [u8; 1024]) -> Result<(), ErrorCode> { +//! fn read_region(&self, region_number: usize, buf: &mut [u8; 1024]) -> Result<(), ErrorCode> { //! // TODO: Read the specified flash region //! for (i, b) in buf.iter_mut().enumerate() { -//! *b = self.buf.borrow()[region_number][offset + i] +//! *b = self.buf.borrow()[region_number][i] //! } //! Ok(()) //! } @@ -148,7 +152,7 @@ //! let tickv = TicKV::::new(FlashCtrl::new(), //! &mut read_buf, 0x1000); //! tickv -//! .initalise(hash_function.finish()) +//! .initialise(hash_function.finish()) //! .unwrap(); //! //! // Add a key @@ -208,7 +212,7 @@ #![deny(missing_docs)] pub mod async_ops; -mod crc32; +pub mod crc32; pub mod error_codes; pub mod flash_controller; pub mod success_codes; diff --git a/libraries/tickv/src/success_codes.rs b/libraries/tickv/src/success_codes.rs index 021f351309..e5d5846c16 100644 --- a/libraries/tickv/src/success_codes.rs +++ b/libraries/tickv/src/success_codes.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! The standard success codes used by TicKV. /// Standard success codes. diff --git a/libraries/tickv/src/tests.rs b/libraries/tickv/src/tests.rs index 2c89498965..03964d02c6 100644 --- a/libraries/tickv/src/tests.rs +++ b/libraries/tickv/src/tests.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use crate::error_codes::ErrorCode; use crate::flash_controller::FlashController; use crate::tickv::{TicKV, HASH_OFFSET, LEN_OFFSET, MAIN_KEY, VERSION, VERSION_OFFSET}; @@ -25,10 +29,10 @@ fn check_region_main(buf: &[u8]) { assert_eq!(buf[HASH_OFFSET + 7], 0x44); // Check the check hash - assert_eq!(buf[HASH_OFFSET + 8], 0x55); - assert_eq!(buf[HASH_OFFSET + 9], 0xb5); - assert_eq!(buf[HASH_OFFSET + 10], 0xd8); - assert_eq!(buf[HASH_OFFSET + 11], 0xe4); + assert_eq!(buf[HASH_OFFSET + 8], 0xbb); + assert_eq!(buf[HASH_OFFSET + 9], 0x32); + assert_eq!(buf[HASH_OFFSET + 10], 0x74); + assert_eq!(buf[HASH_OFFSET + 11], 0x1d); } fn check_region_one(buf: &[u8]) { @@ -55,10 +59,44 @@ fn check_region_one(buf: &[u8]) { assert_eq!(buf[42], 0x23); // Check the check hash - assert_eq!(buf[43], 0xf7); - assert_eq!(buf[44], 0x1d); - assert_eq!(buf[45], 0xb3); - assert_eq!(buf[46], 0xe9); + assert_eq!(buf[43], 0xfd); + assert_eq!(buf[44], 0x24); + assert_eq!(buf[45], 0xf0); + assert_eq!(buf[46], 0x07); +} + +fn check_region_one_zeroed(buf: &[u8]) { + // Check the version + assert_eq!(buf[VERSION_OFFSET], VERSION); + + // Check the length + // The valid bit should be 0 + assert_eq!(buf[LEN_OFFSET], 0x00); + assert_eq!(buf[LEN_OFFSET + 1], 47); + + // Check the hash + assert_eq!(buf[HASH_OFFSET + 0], 0x81); + assert_eq!(buf[HASH_OFFSET + 1], 0x13); + assert_eq!(buf[HASH_OFFSET + 2], 0x7e); + assert_eq!(buf[HASH_OFFSET + 3], 0x95); + assert_eq!(buf[HASH_OFFSET + 4], 0x9e); + assert_eq!(buf[HASH_OFFSET + 5], 0x93); + assert_eq!(buf[HASH_OFFSET + 6], 0xaa); + assert_eq!(buf[HASH_OFFSET + 7], 0x3d); + + // Check the value + assert_eq!(buf[HASH_OFFSET + 8], 0x00); + assert_eq!(buf[28], 0x00); + assert_eq!(buf[42], 0x00); + + // Check the check hash + assert_eq!(buf[43], 0x00); + assert_eq!(buf[44], 0x00); + assert_eq!(buf[45], 0x00); + assert_eq!(buf[46], 0x00); + + // Make sure we don't overwrite valid data + assert_eq!(buf.len(), 47); } fn check_region_two(buf: &[u8]) { @@ -85,10 +123,10 @@ fn check_region_two(buf: &[u8]) { assert_eq!(buf[42], 0x23); // Check the check hash - assert_eq!(buf[43], 0x11); - assert_eq!(buf[44], 0x6a); - assert_eq!(buf[45], 0xba); - assert_eq!(buf[46], 0xba); + assert_eq!(buf[43], 0x1b); + assert_eq!(buf[44], 0x53); + assert_eq!(buf[45], 0xf9); + assert_eq!(buf[46], 0x54); } fn get_hashed_key(unhashed_key: &[u8]) -> u64 { @@ -113,7 +151,6 @@ mod simple_flash_ctrl { fn read_region( &self, _region_number: usize, - _offset: usize, buf: &mut [u8; 2048], ) -> Result<(), ErrorCode> { for b in buf.iter_mut() { @@ -142,7 +179,7 @@ mod simple_flash_ctrl { let hash = hash_function.finish(); let tickv = TicKV::::new(FlashCtrl::new(), &mut read_buf, 0x20000); - tickv.initalise(hash).unwrap(); + tickv.initialise(hash).unwrap(); } } @@ -164,7 +201,6 @@ mod single_erase_flash_ctrl { fn read_region( &self, _region_number: usize, - _offset: usize, buf: &mut [u8; 2048], ) -> Result<(), ErrorCode> { for b in buf.iter_mut() { @@ -197,11 +233,11 @@ mod single_erase_flash_ctrl { let hash = hash_function.finish(); let tickv1 = TicKV::::new(FlashCtrl::new(), &mut read_buf1, 0x20000); - tickv1.initalise(hash).unwrap(); + tickv1.initialise(hash).unwrap(); let mut read_buf2: [u8; 2048] = [0; 2048]; let tickv2 = TicKV::::new(FlashCtrl::new(), &mut read_buf2, 0x20000); - tickv2.initalise(hash).unwrap(); + tickv2.initialise(hash).unwrap(); } } @@ -224,16 +260,11 @@ mod store_flast_ctrl { } impl FlashController<1024> for FlashCtrl { - fn read_region( - &self, - region_number: usize, - offset: usize, - buf: &mut [u8; 1024], - ) -> Result<(), ErrorCode> { + fn read_region(&self, region_number: usize, buf: &mut [u8; 1024]) -> Result<(), ErrorCode> { println!("Read from region: {}", region_number); for (i, b) in buf.iter_mut().enumerate() { - *b = self.buf.borrow()[region_number][offset + i] + *b = self.buf.borrow()[region_number][i] } Ok(()) @@ -261,6 +292,9 @@ mod store_flast_ctrl { } else if self.run.get() == 2 { println!("Writing key TWO: {:#x?}", buf); check_region_two(buf); + } else if self.run.get() == 99 { + println!("Checking the data is zeroed: {:#x?}", buf); + check_region_one_zeroed(buf); } } @@ -289,7 +323,7 @@ mod store_flast_ctrl { let hash = hash_function.finish(); let tickv = TicKV::::new(FlashCtrl::new(), &mut read_buf, 0x10000); - tickv.initalise(hash).unwrap(); + tickv.initialise(hash).unwrap(); let value: [u8; 32] = [0x23; 32]; @@ -305,7 +339,7 @@ mod store_flast_ctrl { let hash = hash_function.finish(); let tickv = TicKV::::new(FlashCtrl::new(), &mut read_buf, 0x10000); - tickv.initalise(hash).unwrap(); + tickv.initialise(hash).unwrap(); let value: [u8; 32] = [0x23; 32]; let mut buf: [u8; 32] = [0; 32]; @@ -350,7 +384,7 @@ mod store_flast_ctrl { let hash = hash_function.finish(); let tickv = TicKV::::new(FlashCtrl::new(), &mut read_buf, 0x10000); - tickv.initalise(hash).unwrap(); + tickv.initialise(hash).unwrap(); let value: [u8; 32] = [0x23; 32]; let mut buf: [u8; 32] = [0; 32]; @@ -377,6 +411,44 @@ mod store_flast_ctrl { ); } + #[test] + fn test_append_and_delete_zeroise() { + let mut read_buf: [u8; 1024] = [0; 1024]; + let mut hash_function = DefaultHasher::new(); + MAIN_KEY.hash(&mut hash_function); + let hash = hash_function.finish(); + + let tickv = TicKV::::new(FlashCtrl::new(), &mut read_buf, 0x10000); + tickv.initialise(hash).unwrap(); + + let value: [u8; 32] = [0x23; 32]; + let mut buf: [u8; 32] = [0; 32]; + + println!("Add Key ONE"); + tickv.append_key(get_hashed_key(b"ONE"), &value).unwrap(); + + println!("Get key ONE"); + tickv.get_key(get_hashed_key(b"ONE"), &mut buf).unwrap(); + + // Set an invalid value here to skip checking the key + tickv.controller.run.set(99); + + println!("Zeroise Key ONE"); + tickv.zeroise_key(get_hashed_key(b"ONE")).unwrap(); + + println!("Get non-existant key ONE"); + assert_eq!( + tickv.get_key(get_hashed_key(b"ONE"), &mut buf), + Err(ErrorCode::KeyNotFound) + ); + + println!("Try to zeroise Key ONE Again"); + assert_eq!( + tickv.zeroise_key(get_hashed_key(b"ONE")), + Err(ErrorCode::KeyNotFound) + ); + } + #[test] fn test_garbage_collect() { let mut read_buf: [u8; 1024] = [0; 1024]; @@ -385,7 +457,7 @@ mod store_flast_ctrl { let hash = hash_function.finish(); let tickv = TicKV::::new(FlashCtrl::new(), &mut read_buf, 0x10000); - tickv.initalise(hash).unwrap(); + tickv.initialise(hash).unwrap(); let value: [u8; 32] = [0x23; 32]; let mut buf: [u8; 32] = [0; 32]; @@ -414,6 +486,47 @@ mod store_flast_ctrl { println!("Add Key ONE"); tickv.append_key(get_hashed_key(b"ONE"), &value).unwrap(); } + + #[test] + fn test_garbage_collect_zeroise() { + let mut read_buf: [u8; 1024] = [0; 1024]; + let mut hash_function = DefaultHasher::new(); + MAIN_KEY.hash(&mut hash_function); + let hash = hash_function.finish(); + + let tickv = TicKV::::new(FlashCtrl::new(), &mut read_buf, 0x10000); + tickv.initialise(hash).unwrap(); + + let value: [u8; 32] = [0x23; 32]; + let mut buf: [u8; 32] = [0; 32]; + + println!("Garbage collect empty flash"); + assert_eq!(tickv.garbage_collect(), Ok(0)); + + println!("Add Key ONE"); + tickv.append_key(get_hashed_key(b"ONE"), &value).unwrap(); + + println!("Garbage collect flash with valid key"); + assert_eq!(tickv.garbage_collect(), Ok(0)); + + // Set an invalid value here to skip checking the key + tickv.controller.run.set(99); + + println!("Zeroise Key ONE"); + tickv.zeroise_key(get_hashed_key(b"ONE")).unwrap(); + + println!("Garbage collect flash with deleted key"); + assert_eq!(tickv.garbage_collect(), Ok(1024)); + + println!("Get non-existant key ONE"); + assert_eq!( + tickv.get_key(get_hashed_key(b"ONE"), &mut buf), + Err(ErrorCode::KeyNotFound) + ); + + println!("Add Key ONE"); + tickv.append_key(get_hashed_key(b"ONE"), &value).unwrap(); + } } mod no_check_store_flast_ctrl { @@ -432,16 +545,11 @@ mod no_check_store_flast_ctrl { } impl FlashController<256> for FlashCtrl { - fn read_region( - &self, - region_number: usize, - offset: usize, - buf: &mut [u8; 256], - ) -> Result<(), ErrorCode> { + fn read_region(&self, region_number: usize, buf: &mut [u8; 256]) -> Result<(), ErrorCode> { println!("Read from region: {}", region_number); for (i, b) in buf.iter_mut().enumerate() { - *b = self.buf.borrow()[region_number][offset + i] + *b = self.buf.borrow()[region_number][i] } Ok(()) @@ -480,7 +588,7 @@ mod no_check_store_flast_ctrl { let hash = hash_function.finish(); let tickv = TicKV::::new(FlashCtrl::new(), &mut read_buf, 0x200); - tickv.initalise(hash).unwrap(); + tickv.initialise(hash).unwrap(); let value: [u8; 64] = [0x23; 64]; let mut buf: [u8; 64] = [0; 64]; diff --git a/libraries/tickv/src/tickv.rs b/libraries/tickv/src/tickv.rs index 433e2836f8..2ac42c3df8 100644 --- a/libraries/tickv/src/tickv.rs +++ b/libraries/tickv/src/tickv.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! The TicKV implementation. use crate::crc32; @@ -7,7 +11,7 @@ use crate::success_codes::SuccessCode; use core::cell::Cell; /// The current version of TicKV -pub const VERSION: u8 = 0; +pub const VERSION: u8 = 1; #[derive(Clone, Copy, PartialEq)] pub(crate) enum InitState { @@ -29,8 +33,8 @@ pub(crate) enum KeyState { #[derive(Clone, Copy, PartialEq)] pub(crate) enum RubbishState { - ReadRegion(usize), - EraseRegion(usize), + ReadRegion(usize, usize), + EraseRegion(usize, usize), } #[derive(Clone, Copy, PartialEq)] @@ -47,6 +51,8 @@ pub(crate) enum State { GetKey(KeyState), /// Invalidating a key InvalidateKey(KeyState), + /// Zeroizing a key + ZeroiseKey(KeyState), /// Running garbage collection GarbageCollect(RubbishState), } @@ -92,7 +98,7 @@ pub(crate) const HEADER_LENGTH: usize = HASH_OFFSET + 8; pub(crate) const CHECK_SUM_LEN: usize = 4; /// The main key. A hashed version of this should be passed to -/// `initalise()`. +/// `initialise()`. pub const MAIN_KEY: &[u8; 15] = b"tickv-super-key"; /// This is the main TicKV struct. @@ -113,7 +119,7 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { } /// This function setups the flash region to be used as a key-value store. - /// If the region is already initalised this won't make any changes. + /// If the region is already initialised this won't make any changes. /// /// `hashed_main_key`: The u64 hash of the const string `MAIN_KEY`. /// @@ -122,7 +128,7 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { /// /// On success nothing will be returned. /// On error a `ErrorCode` will be returned. - pub fn initalise(&self, hashed_main_key: u64) -> Result { + pub fn initialise(&self, hashed_main_key: u64) -> Result { let mut buf: [u8; 0] = [0; 0]; let key_ret = match self.state.get() { @@ -135,7 +141,7 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { }; match key_ret { - Ok(ret) => Ok(ret), + Ok((ret, _len)) => Ok(ret), Err(e) => { match e { ErrorCode::ReadNotReady(reg) => { @@ -211,26 +217,41 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { } // Determine the new region offset to try. + // + // `region` is the base region. This is the default region + // for the object, this won't change per key. + // `region_offset` is the current region offset are trying to use + // If multiple attempts are required this value will be different + // on each iteration. This should be the previous return value of + // this function, or zero on the first iteration. + // + // This function will return an offset that can be applied to + // region to determine a new flash region // Returns None if there aren't any more in range. - fn increment_region_offset(&self, region_offset: isize) -> Option { + fn increment_region_offset(&self, region: usize, region_offset: isize) -> Option { let mut too_big = false; let mut too_small = false; + let mut new_offset = region_offset; + // Loop until we find a region we can use - while !too_big && !too_small { - let new_offset = match region_offset { + while !too_big || !too_small { + new_offset = match new_offset { + // If this is the first iteration, just try the next region 0 => 1, - region_offset if region_offset > 0 => -region_offset, - region_offset if region_offset < 0 => -region_offset + 1, + // If the offset is positive, return the negative value + new_offset if new_offset > 0 => -new_offset, + // If the offset is negative, convert to positive and increment by 1 + new_offset if new_offset < 0 => -new_offset + 1, _ => unreachable!(), }; // Make sure our new offset is valid - if new_offset as usize > ((self.flash_size / S) - 1) { + if (region as isize + new_offset) > ((self.flash_size / S) - 1) as isize { too_big = true; continue; } - if new_offset < 0 { + if (region as isize + new_offset) < 0 { too_small = true; continue; } @@ -267,18 +288,33 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { } // Check to see if we have data - if region_data[offset + VERSION_OFFSET] != 0xFF { + if *region_data + .get(offset + VERSION_OFFSET) + .ok_or((false, ErrorCode::KeyNotFound))? + != 0xFF + { // Mark that this region isn't empty empty = false; // We found a version, check that we support it - if region_data[offset + VERSION_OFFSET] != VERSION { + if *region_data + .get(offset + VERSION_OFFSET) + .ok_or((false, ErrorCode::KeyNotFound))? + != VERSION + { return Err((false, ErrorCode::UnsupportedVersion)); } // Find this entries length - let total_length = ((region_data[offset + LEN_OFFSET] as u16) & !0xF0) << 8 - | region_data[offset + LEN_OFFSET + 1] as u16; + let total_length = ((*region_data + .get(offset + LEN_OFFSET) + .ok_or((false, ErrorCode::CorruptData))? + as u16) + & !0xF0) + << 8 + | *region_data + .get(offset + LEN_OFFSET + 1) + .ok_or((false, ErrorCode::CorruptData))? as u16; // Check to see if all fields are just 0 if total_length == 0 { @@ -287,21 +323,50 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { } // Check to see if the entry has been deleted - if region_data[offset + LEN_OFFSET] & 0x80 != 0x80 { + if *region_data + .get(offset + LEN_OFFSET) + .ok_or((false, ErrorCode::CorruptData))? + & 0x80 + != 0x80 + { // Increment our offset by the length and repeat the loop offset += total_length as usize; continue; } // We have found a valid entry, see if it is ours. - if region_data[offset + HASH_OFFSET] != hash[7] - || region_data[offset + HASH_OFFSET + 1] != hash[6] - || region_data[offset + HASH_OFFSET + 2] != hash[5] - || region_data[offset + HASH_OFFSET + 3] != hash[4] - || region_data[offset + HASH_OFFSET + 4] != hash[3] - || region_data[offset + HASH_OFFSET + 5] != hash[2] - || region_data[offset + HASH_OFFSET + 6] != hash[1] - || region_data[offset + HASH_OFFSET + 7] != hash[0] + if *region_data + .get(offset + HASH_OFFSET) + .ok_or((false, ErrorCode::CorruptData))? + != *hash.get(7).ok_or((false, ErrorCode::CorruptData))? + || *region_data + .get(offset + HASH_OFFSET + 1) + .ok_or((false, ErrorCode::CorruptData))? + != *hash.get(6).ok_or((false, ErrorCode::CorruptData))? + || *region_data + .get(offset + HASH_OFFSET + 2) + .ok_or((false, ErrorCode::CorruptData))? + != *hash.get(5).ok_or((false, ErrorCode::CorruptData))? + || *region_data + .get(offset + HASH_OFFSET + 3) + .ok_or((false, ErrorCode::CorruptData))? + != *hash.get(4).ok_or((false, ErrorCode::CorruptData))? + || *region_data + .get(offset + HASH_OFFSET + 4) + .ok_or((false, ErrorCode::CorruptData))? + != *hash.get(3).ok_or((false, ErrorCode::CorruptData))? + || *region_data + .get(offset + HASH_OFFSET + 5) + .ok_or((false, ErrorCode::CorruptData))? + != *hash.get(2).ok_or((false, ErrorCode::CorruptData))? + || *region_data + .get(offset + HASH_OFFSET + 6) + .ok_or((false, ErrorCode::CorruptData))? + != *hash.get(1).ok_or((false, ErrorCode::CorruptData))? + || *region_data + .get(offset + HASH_OFFSET + 7) + .ok_or((false, ErrorCode::CorruptData))? + != *hash.first().ok_or((false, ErrorCode::CorruptData))? { // Increment our offset by the length and repeat the loop offset += total_length as usize; @@ -327,8 +392,7 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { /// On error a `ErrorCode` will be returned. pub fn append_key(&self, hash: u64, value: &[u8]) -> Result { let region = self.get_region(hash); - let crc = crc32::Crc::new(); - let mut check_sum = crc.digest(); + let check_sum = crc32::Crc32::new(); // Length not including check sum let package_length = HEADER_LENGTH + value.len(); @@ -345,32 +409,27 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { loop { let new_region = match self.state.get() { - State::None => region as isize + region_offset, + State::None => (region as isize + region_offset) as usize, State::Init(state) => { match state { - InitState::AppendKeyReadRegion(reg) => reg as isize, + InitState::AppendKeyReadRegion(reg) => reg, _ => { // Get the data from that region - region as isize + region_offset + (region as isize + region_offset) as usize } } } State::AppendKey(key_state) => match key_state { - KeyState::ReadRegion(reg) => reg as isize, + KeyState::ReadRegion(reg) => reg, }, - State::GarbageCollect(RubbishState::ReadRegion(reg)) => reg as isize, _ => unreachable!(), }; - let mut region_data = self.read_buffer.take().unwrap(); - if self.state.get() != State::AppendKey(KeyState::ReadRegion(new_region as usize)) - && self.state.get() - != State::Init(InitState::AppendKeyReadRegion(new_region as usize)) + let region_data = self.read_buffer.take().unwrap(); + if self.state.get() != State::AppendKey(KeyState::ReadRegion(new_region)) + && self.state.get() != State::Init(InitState::AppendKeyReadRegion(new_region)) { - match self - .controller - .read_region(new_region as usize, 0, &mut region_data) - { + match self.controller.read_region(new_region, region_data) { Ok(()) => {} Err(e) => { self.read_buffer.replace(Some(region_data)); @@ -398,9 +457,11 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { // Replace the buffer self.read_buffer.replace(Some(region_data)); - match self.increment_region_offset(new_region) { + region_offset = new_region as isize - region as isize; + match self.increment_region_offset(region, region_offset) { Some(o) => { region_offset = o; + self.state.set(State::None); } None => { return Err(ErrorCode::FlashFull); @@ -410,16 +471,31 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { } // Check to see if we have data - if region_data[offset + VERSION_OFFSET] != 0xFF { + if *region_data + .get(offset + VERSION_OFFSET) + .ok_or(ErrorCode::KeyNotFound)? + != 0xFF + { // We found a version, check that we support it - if region_data[offset + VERSION_OFFSET] != VERSION { + if *region_data + .get(offset + VERSION_OFFSET) + .ok_or(ErrorCode::KeyNotFound)? + != VERSION + { self.read_buffer.replace(Some(region_data)); return Err(ErrorCode::UnsupportedVersion); } // Find this entries length - let total_length = ((region_data[offset + LEN_OFFSET] as u16) & !0xF0) << 8 - | region_data[offset + LEN_OFFSET + 1] as u16; + let total_length = ((*region_data + .get(offset + LEN_OFFSET) + .ok_or(ErrorCode::CorruptData)? + as u16) + & !0xF0) + << 8 + | *region_data + .get(offset + LEN_OFFSET + 1) + .ok_or(ErrorCode::CorruptData)? as u16; // Increment our offset by the length and repeat the loop offset += total_length as usize; @@ -431,35 +507,67 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { // Check to see if the entire header is 0xFFFF_FFFF_FFFF_FFFF // To avoid operating on 64-bit values check every 8 bytes at a time - if region_data[offset + HASH_OFFSET] != 0xFF { + if *region_data + .get(offset + HASH_OFFSET) + .ok_or(ErrorCode::CorruptData)? + != 0xFF + { self.read_buffer.replace(Some(region_data)); return Err(ErrorCode::CorruptData); } - if region_data[offset + HASH_OFFSET + 1] != 0xFF { + if *region_data + .get(offset + HASH_OFFSET + 1) + .ok_or(ErrorCode::CorruptData)? + != 0xFF + { self.read_buffer.replace(Some(region_data)); return Err(ErrorCode::CorruptData); } - if region_data[offset + HASH_OFFSET + 2] != 0xFF { + if *region_data + .get(offset + HASH_OFFSET + 2) + .ok_or(ErrorCode::CorruptData)? + != 0xFF + { self.read_buffer.replace(Some(region_data)); return Err(ErrorCode::CorruptData); } - if region_data[offset + HASH_OFFSET + 3] != 0xFF { + if *region_data + .get(offset + HASH_OFFSET + 3) + .ok_or(ErrorCode::CorruptData)? + != 0xFF + { self.read_buffer.replace(Some(region_data)); return Err(ErrorCode::CorruptData); } - if region_data[offset + HASH_OFFSET + 4] != 0xFF { + if *region_data + .get(offset + HASH_OFFSET + 4) + .ok_or(ErrorCode::CorruptData)? + != 0xFF + { self.read_buffer.replace(Some(region_data)); return Err(ErrorCode::CorruptData); } - if region_data[offset + HASH_OFFSET + 5] != 0xFF { + if *region_data + .get(offset + HASH_OFFSET + 5) + .ok_or(ErrorCode::CorruptData)? + != 0xFF + { self.read_buffer.replace(Some(region_data)); return Err(ErrorCode::CorruptData); } - if region_data[offset + HASH_OFFSET + 6] != 0xFF { + if *region_data + .get(offset + HASH_OFFSET + 6) + .ok_or(ErrorCode::CorruptData)? + != 0xFF + { self.read_buffer.replace(Some(region_data)); return Err(ErrorCode::CorruptData); } - if region_data[offset + HASH_OFFSET + 7] != 0xFF { + if *region_data + .get(offset + HASH_OFFSET + 7) + .ok_or(ErrorCode::CorruptData)? + != 0xFF + { self.read_buffer.replace(Some(region_data)); return Err(ErrorCode::CorruptData); } @@ -468,24 +576,52 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { // Copy in new header // This is a little painful, but avoids any unsafe Rust - region_data[offset + VERSION_OFFSET] = header.version; - region_data[offset + LEN_OFFSET] = + *region_data + .get_mut(offset + VERSION_OFFSET) + .ok_or(ErrorCode::RegionFull)? = header.version; + *region_data + .get_mut(offset + LEN_OFFSET) + .ok_or(ErrorCode::RegionFull)? = (header.len >> 8) as u8 & 0x0F | (header.flags << 4) & 0xF0; - region_data[offset + LEN_OFFSET + 1] = (header.len & 0xFF) as u8; - region_data[offset + HASH_OFFSET] = (header.hashed_key >> 56) as u8; - region_data[offset + HASH_OFFSET + 1] = (header.hashed_key >> 48) as u8; - region_data[offset + HASH_OFFSET + 2] = (header.hashed_key >> 40) as u8; - region_data[offset + HASH_OFFSET + 3] = (header.hashed_key >> 32) as u8; - region_data[offset + HASH_OFFSET + 4] = (header.hashed_key >> 24) as u8; - region_data[offset + HASH_OFFSET + 5] = (header.hashed_key >> 16) as u8; - region_data[offset + HASH_OFFSET + 6] = (header.hashed_key >> 8) as u8; - region_data[offset + HASH_OFFSET + 7] = (header.hashed_key) as u8; + *region_data + .get_mut(offset + LEN_OFFSET + 1) + .ok_or(ErrorCode::RegionFull)? = (header.len & 0xFF) as u8; + *region_data + .get_mut(offset + HASH_OFFSET) + .ok_or(ErrorCode::RegionFull)? = (header.hashed_key >> 56) as u8; + *region_data + .get_mut(offset + HASH_OFFSET + 1) + .ok_or(ErrorCode::RegionFull)? = (header.hashed_key >> 48) as u8; + *region_data + .get_mut(offset + HASH_OFFSET + 2) + .ok_or(ErrorCode::RegionFull)? = (header.hashed_key >> 40) as u8; + *region_data + .get_mut(offset + HASH_OFFSET + 3) + .ok_or(ErrorCode::RegionFull)? = (header.hashed_key >> 32) as u8; + *region_data + .get_mut(offset + HASH_OFFSET + 4) + .ok_or(ErrorCode::RegionFull)? = (header.hashed_key >> 24) as u8; + *region_data + .get_mut(offset + HASH_OFFSET + 5) + .ok_or(ErrorCode::RegionFull)? = (header.hashed_key >> 16) as u8; + *region_data + .get_mut(offset + HASH_OFFSET + 6) + .ok_or(ErrorCode::RegionFull)? = (header.hashed_key >> 8) as u8; + *region_data + .get_mut(offset + HASH_OFFSET + 7) + .ok_or(ErrorCode::RegionFull)? = (header.hashed_key) as u8; // Hash the new header data - check_sum.update(®ion_data[offset + VERSION_OFFSET..=offset + HASH_OFFSET + 7]); + check_sum.update( + region_data + .get(offset + VERSION_OFFSET..=offset + HASH_OFFSET + 7) + .ok_or(ErrorCode::CorruptData)?, + ); // Copy the value - let slice = &mut region_data[(offset + HEADER_LENGTH)..(offset + package_length)]; + let slice = region_data + .get_mut((offset + HEADER_LENGTH)..(offset + package_length)) + .ok_or(ErrorCode::ObjectTooLarge)?; slice.copy_from_slice(value); // Include the value in the hash @@ -493,14 +629,17 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { // Append a Check Hash let check_sum = check_sum.finalise(); - let slice = &mut region_data - [(offset + package_length)..(offset + package_length + CHECK_SUM_LEN)]; + let slice = region_data + .get_mut((offset + package_length)..(offset + package_length + CHECK_SUM_LEN)) + .ok_or(ErrorCode::ObjectTooLarge)?; slice.copy_from_slice(&check_sum.to_ne_bytes()); // Write the data back to the region if let Err(e) = self.controller.write( - S * new_region as usize + offset, - ®ion_data[offset..(offset + package_length + CHECK_SUM_LEN)], + S * new_region + offset, + region_data + .get(offset..(offset + package_length + CHECK_SUM_LEN)) + .ok_or(ErrorCode::ObjectTooLarge)?, ) { self.read_buffer.replace(Some(region_data)); match e { @@ -517,48 +656,44 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { /// Retrieves the value from flash storage. /// - /// `hash`: A hashed key. - /// `buf`: A buffer to store the value to. + /// - `hash`: A hashed key. + /// - `buf`: A buffer to store the value to. /// - /// On success nothing will be returned. - /// On error a `ErrorCode` will be returned. + /// On success a `SuccessCode` will be returned and the length of the value + /// for the corresponding key. On error a `ErrorCode` will be returned. /// - /// If a power loss occurs before success is returned the data is - /// assumed to be lost. - pub fn get_key(&self, hash: u64, buf: &mut [u8]) -> Result { + /// If a power loss occurs before success is returned the data is assumed to + /// be lost. + pub fn get_key(&self, hash: u64, buf: &mut [u8]) -> Result<(SuccessCode, usize), ErrorCode> { let region = self.get_region(hash); let mut region_offset: isize = 0; loop { - let crc = crc32::Crc::new(); - let mut check_sum = crc.digest(); + let check_sum = crc32::Crc32::new(); let new_region = match self.state.get() { - State::None => region as isize + region_offset, + State::None => (region as isize + region_offset) as usize, State::Init(state) => { match state { - InitState::GetKeyReadRegion(reg) => reg as isize, + InitState::GetKeyReadRegion(reg) => reg, _ => { // Get the data from that region - region as isize + region_offset + (region as isize + region_offset) as usize } } } State::GetKey(key_state) => match key_state { - KeyState::ReadRegion(reg) => reg as isize, + KeyState::ReadRegion(reg) => reg, }, _ => unreachable!(), }; // Get the data from that region - let mut region_data = self.read_buffer.take().unwrap(); - if self.state.get() != State::GetKey(KeyState::ReadRegion(new_region as usize)) - && self.state.get() != State::Init(InitState::GetKeyReadRegion(new_region as usize)) + let region_data = self.read_buffer.take().unwrap(); + if self.state.get() != State::GetKey(KeyState::ReadRegion(new_region)) + && self.state.get() != State::Init(InitState::GetKeyReadRegion(new_region)) { - match self - .controller - .read_region(new_region as usize, 0, &mut region_data) - { + match self.controller.read_region(new_region, region_data) { Ok(()) => {} Err(e) => { self.read_buffer.replace(Some(region_data)); @@ -573,45 +708,76 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { match self.find_key_offset(hash, region_data) { Ok((offset, total_length)) => { // Add the header data to the check hash - check_sum.update(®ion_data[offset..(HEADER_LENGTH + offset)]); + check_sum.update( + region_data + .get(offset..(HEADER_LENGTH + offset)) + .ok_or(ErrorCode::ObjectTooLarge)?, + ); + + // The size of the stored object's actual data; + let value_length = total_length as usize - HEADER_LENGTH - CHECK_SUM_LEN; // Make sure if will fit in the buffer - if buf.len() < (total_length as usize - HEADER_LENGTH - CHECK_SUM_LEN) { + if buf.len() < value_length { + // The entire value is not going to fit, + // Let's still copy in what we can and return an error + for i in 0..buf.len() { + *buf.get_mut(i) + .ok_or(ErrorCode::BufferTooSmall(value_length))? = *region_data + .get(offset + HEADER_LENGTH + i) + .ok_or(ErrorCode::BufferTooSmall(value_length))?; + } + self.read_buffer.replace(Some(region_data)); - return Err(ErrorCode::BufferTooSmall( - total_length as usize - HEADER_LENGTH - CHECK_SUM_LEN, - )); + return Err(ErrorCode::BufferTooSmall(value_length)); } // Copy in the value - for i in 0..(total_length as usize - HEADER_LENGTH - CHECK_SUM_LEN) { - buf[i] = region_data[offset + HEADER_LENGTH + i]; - check_sum.update(&[buf[i]]) + for i in 0..value_length { + *buf.get_mut(i) + .ok_or(ErrorCode::BufferTooSmall(value_length))? = *region_data + .get(offset + HEADER_LENGTH + i) + .ok_or(ErrorCode::CorruptData)?; + check_sum.update(&[*buf.get(i).ok_or(ErrorCode::CorruptData)?]) } // Check the hash let check_sum = check_sum.finalise(); let check_sum = check_sum.to_ne_bytes(); - if check_sum[3] != region_data[offset + total_length as usize - 1] - || check_sum[2] != region_data[offset + total_length as usize - 2] - || check_sum[1] != region_data[offset + total_length as usize - 3] - || check_sum[0] != region_data[offset + total_length as usize - 4] + if *check_sum.get(3).ok_or(ErrorCode::InvalidCheckSum)? + != *region_data + .get(offset + total_length as usize - 1) + .ok_or(ErrorCode::InvalidCheckSum)? + || *check_sum.get(2).ok_or(ErrorCode::InvalidCheckSum)? + != *region_data + .get(offset + total_length as usize - 2) + .ok_or(ErrorCode::InvalidCheckSum)? + || *check_sum.get(1).ok_or(ErrorCode::InvalidCheckSum)? + != *region_data + .get(offset + total_length as usize - 3) + .ok_or(ErrorCode::InvalidCheckSum)? + || *check_sum.first().ok_or(ErrorCode::InvalidCheckSum)? + != *region_data + .get(offset + total_length as usize - 4) + .ok_or(ErrorCode::InvalidCheckSum)? { self.read_buffer.replace(Some(region_data)); return Err(ErrorCode::InvalidCheckSum); } self.read_buffer.replace(Some(region_data)); - return Ok(SuccessCode::Complete); + return Ok((SuccessCode::Complete, value_length)); } Err((cont, e)) => { self.read_buffer.replace(Some(region_data)); if cont { - match self.increment_region_offset(new_region) { + region_offset = new_region as isize - region as isize; + match self.increment_region_offset(region, region_offset) { Some(o) => { region_offset = o; + self.state.set(State::None); } None => { return Err(e); @@ -642,20 +808,17 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { loop { // Get the data from that region let new_region = match self.state.get() { - State::None => region as isize + region_offset, + State::None => (region as isize + region_offset) as usize, State::InvalidateKey(key_state) => match key_state { - KeyState::ReadRegion(reg) => reg as isize, + KeyState::ReadRegion(reg) => reg, }, _ => unreachable!(), }; // Get the data from that region - let mut region_data = self.read_buffer.take().unwrap(); - if self.state.get() != State::InvalidateKey(KeyState::ReadRegion(new_region as usize)) { - match self - .controller - .read_region(new_region as usize, 0, &mut region_data) - { + let region_data = self.read_buffer.take().unwrap(); + if self.state.get() != State::InvalidateKey(KeyState::ReadRegion(new_region)) { + match self.controller.read_region(new_region, region_data) { Ok(()) => {} Err(e) => { self.read_buffer.replace(Some(region_data)); @@ -671,11 +834,15 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { match self.find_key_offset(hash, region_data) { Ok((offset, _data_len)) => { // We found a key, let's delete it - region_data[offset + LEN_OFFSET] &= !0x80; + *region_data + .get_mut(offset + LEN_OFFSET) + .ok_or(ErrorCode::CorruptData)? &= !0x80; if let Err(e) = self.controller.write( - S * new_region as usize + offset + LEN_OFFSET, - ®ion_data[offset + LEN_OFFSET..offset + LEN_OFFSET + 1], + S * new_region + offset + LEN_OFFSET, + region_data + .get(offset + LEN_OFFSET..offset + LEN_OFFSET + 1) + .ok_or(ErrorCode::ObjectTooLarge)?, ) { self.read_buffer.replace(Some(region_data)); match e { @@ -691,9 +858,11 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { self.read_buffer.replace(Some(region_data)); if cont { - match self.increment_region_offset(new_region) { + region_offset = new_region as isize - region as isize; + match self.increment_region_offset(region, region_offset) { Some(o) => { region_offset = o; + self.state.set(State::None); } None => { return Err(e); @@ -707,17 +876,132 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { } } - fn garbage_collect_region(&self, region: usize) -> Result { + /// Zeroises the key in flash storage. + /// + /// This is similar to the `invalidate_key()` function, but instead will + /// change all `1`s in the value and checksum to `0`s. This does + /// not remove the header, as that is required for garbage collection + /// later on, so the length and hashed key will still be preserved. + /// + /// The values will be changed by a single write operation to the flash. + /// The values are not securley overwritten to make restoring data + /// difficult. + /// + /// Users will need to check with the hardware specifications to determine + /// if this is cryptographically secure for their use case. + /// + /// + /// + /// `hash`: A hashed key. + /// + /// On success nothing will be returned. + /// On error a `ErrorCode` will be returned. + /// + /// If a power loss occurs before success is returned the data is + /// assumed to be lost. + pub fn zeroise_key(&self, hash: u64) -> Result { + let region = self.get_region(hash); + + let mut region_offset: isize = 0; + + loop { + // Get the data from that region + let new_region = match self.state.get() { + State::None => (region as isize + region_offset) as usize, + State::ZeroiseKey(key_state) => match key_state { + KeyState::ReadRegion(reg) => reg, + }, + _ => unreachable!(), + }; + + // Get the data from that region + let region_data = self.read_buffer.take().unwrap(); + if self.state.get() != State::ZeroiseKey(KeyState::ReadRegion(new_region)) { + match self.controller.read_region(new_region, region_data) { + Ok(()) => {} + Err(e) => { + self.read_buffer.replace(Some(region_data)); + if let ErrorCode::ReadNotReady(reg) = e { + self.state.set(State::ZeroiseKey(KeyState::ReadRegion(reg))); + } + return Err(e); + } + }; + } + + match self.find_key_offset(hash, region_data) { + Ok((offset, data_len)) => { + // We found a key, let's delete it + *region_data + .get_mut(offset + LEN_OFFSET) + .ok_or(ErrorCode::CorruptData)? &= !0x80; + + // Replace Value with 0s + for i in HEADER_LENGTH..(data_len as usize + HEADER_LENGTH) { + *region_data + .get_mut(offset + i) + .ok_or(ErrorCode::RegionFull)? = 0; + } + + let write_len = data_len as usize; + + if let Err(e) = self.controller.write( + S * new_region + offset, + region_data + .get(offset..offset + write_len) + .ok_or(ErrorCode::ObjectTooLarge)?, + ) { + self.read_buffer.replace(Some(region_data)); + match e { + ErrorCode::WriteNotReady(_) => return Ok(SuccessCode::Queued), + _ => return Err(e), + } + } + + self.read_buffer.replace(Some(region_data)); + return Ok(SuccessCode::Written); + } + Err((cont, e)) => { + self.read_buffer.replace(Some(region_data)); + + if cont { + region_offset = new_region as isize - region as isize; + match self.increment_region_offset(region, region_offset) { + Some(o) => { + region_offset = o; + self.state.set(State::None); + } + None => { + return Err(e); + } + } + } else { + return Err(e); + } + } + } + } + } + + fn garbage_collect_region( + &self, + region: usize, + flash_freed: usize, + ) -> Result { // Get the data from that region - let mut region_data = self.read_buffer.take().unwrap(); - if self.state.get() != State::GarbageCollect(RubbishState::ReadRegion(region)) { - match self.controller.read_region(region, 0, &mut region_data) { + let region_data = self.read_buffer.take().unwrap(); + if self.state.get() != State::GarbageCollect(RubbishState::ReadRegion(region, flash_freed)) + { + match self.controller.read_region(region, region_data) { Ok(()) => {} Err(e) => { self.read_buffer.replace(Some(region_data)); if let ErrorCode::ReadNotReady(reg) = e { self.state - .set(State::GarbageCollect(RubbishState::ReadRegion(reg))); + .set(State::GarbageCollect(RubbishState::ReadRegion( + reg, + flash_freed, + ))); } return Err(e); } @@ -735,9 +1019,17 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { } // Check to see if we have data - if region_data[offset + VERSION_OFFSET] != 0xFF { + if *region_data + .get(offset + VERSION_OFFSET) + .ok_or(ErrorCode::KeyNotFound)? + != 0xFF + { // We found a version, check that we support it - if region_data[offset + VERSION_OFFSET] != VERSION { + if *region_data + .get(offset + VERSION_OFFSET) + .ok_or(ErrorCode::KeyNotFound)? + != VERSION + { self.read_buffer.replace(Some(region_data)); return Err(ErrorCode::UnsupportedVersion); } @@ -745,11 +1037,22 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { entry_found = true; // Find this entries length - let total_length = ((region_data[offset + LEN_OFFSET] as u16) & !0xF0) << 8 - | region_data[offset + LEN_OFFSET + 1] as u16; + let total_length = ((*region_data + .get(offset + LEN_OFFSET) + .ok_or(ErrorCode::CorruptData)? as u16) + & !0xF0) + << 8 + | *region_data + .get(offset + LEN_OFFSET + 1) + .ok_or(ErrorCode::CorruptData)? as u16; // Check to see if the entry has been deleted - if region_data[offset + LEN_OFFSET] & 0x80 != 0x80 { + if *region_data + .get(offset + LEN_OFFSET) + .ok_or(ErrorCode::CorruptData)? + & 0x80 + != 0x80 + { // The entry has been deleted, this region might be ready // for erasure. // Increment our offset by the length and repeat the loop @@ -783,7 +1086,10 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { if let Err(e) = self.controller.erase_region(region) { if let ErrorCode::EraseNotReady(reg) = e { self.state - .set(State::GarbageCollect(RubbishState::EraseRegion(reg))); + .set(State::GarbageCollect(RubbishState::EraseRegion( + reg, + flash_freed + S, + ))); } return Err(e); } @@ -801,15 +1107,21 @@ impl<'a, C: FlashController, const S: usize> TicKV<'a, C, S> { let start = match self.state.get() { State::None => 0, State::GarbageCollect(state) => match state { - RubbishState::ReadRegion(reg) => reg, + RubbishState::ReadRegion(reg, ff) => { + flash_freed += ff; + reg + } // We already erased region reg, so move to the next one - RubbishState::EraseRegion(reg) => reg + 1, + RubbishState::EraseRegion(reg, ff) => { + flash_freed += ff; + reg + 1 + } }, _ => unreachable!(), }; for i in start..num_region { - match self.garbage_collect_region(i) { + match self.garbage_collect_region(i, flash_freed) { Ok(freed) => flash_freed += freed, Err(e) => return Err(e), } diff --git a/libraries/tock-cells/Cargo.toml b/libraries/tock-cells/Cargo.toml index 353cc8d274..882e8540f0 100644 --- a/libraries/tock-cells/Cargo.toml +++ b/libraries/tock-cells/Cargo.toml @@ -1,5 +1,13 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "tock-cells" version = "0.1.0" authors = ["Tock Project Developers "] +edition = "2021" + +[lints] +workspace = true diff --git a/libraries/tock-cells/src/lib.rs b/libraries/tock-cells/src/lib.rs index e7565ad01b..dcfa2a5be7 100644 --- a/libraries/tock-cells/src/lib.rs +++ b/libraries/tock-cells/src/lib.rs @@ -1,16 +1,9 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tock Cell types. -#![feature(const_fn_trait_bound)] -// Feature used to opt-in the new `core::Option::contains()` API. -// -// This feature can be removed if needed by manually reimplementing the -// `contains` logic for `Option`. -// -// Tock expects this feature to stabilize in the near future. -// Tracking: https://github.com/rust-lang/rust/issues/62358 -#![feature(option_result_contains)] -// Feature required with newer versions of rustc (at least 2020-10-25). -#![feature(const_mut_refs)] #![no_std] pub mod map_cell; diff --git a/libraries/tock-cells/src/map_cell.rs b/libraries/tock-cells/src/map_cell.rs index 85a65dd7eb..25fd158929 100644 --- a/libraries/tock-cells/src/map_cell.rs +++ b/libraries/tock-cells/src/map_cell.rs @@ -1,62 +1,184 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tock specific `MapCell` type for sharing references. use core::cell::{Cell, UnsafeCell}; use core::mem::MaybeUninit; -use core::ptr; +use core::ptr::drop_in_place; + +#[derive(Clone, Copy, PartialEq)] +enum MapCellState { + Uninit, + Init, + Borrowed, +} + +#[inline(never)] +#[cold] +fn access_panic() { + panic!("`MapCell` accessed while borrowed"); +} -/// A mutable memory location that enforces borrow rules at runtime without -/// possible panics. +macro_rules! debug_assert_not_borrowed { + ($slf:ident) => { + if cfg!(debug_assertions) && $slf.occupied.get() == MapCellState::Borrowed { + access_panic(); + } + }; +} + +/// A mutable, possibly unset, memory location that provides checked `&mut` access +/// to its contents via a closure. +/// +/// A `MapCell` provides checked shared access to its mutable memory. Borrow +/// rules are enforced by forcing clients to either move the memory out of the +/// cell or operate on a `&mut` within a closure. You can think of a `MapCell` +/// as a `Cell>` with an extra "in-use" state to prevent `map` from invoking +/// undefined behavior when called re-entrantly. +/// +/// # Examples +/// ``` +/// # use tock_cells::map_cell::MapCell; +/// let cell: MapCell = MapCell::empty(); /// -/// A `MapCell` is a potential reference to mutable memory. Borrow rules are -/// enforced by forcing clients to either move the memory out of the cell or -/// operate on a borrow within a closure. You can think of a `MapCell` as an -/// `Option` wrapped in a `RefCell` --- attempts to take the value from inside a -/// `MapCell` may fail by returning `None`. +/// assert!(cell.is_none()); +/// cell.map(|_| unreachable!("The cell is empty; map does not call the closure")); +/// assert_eq!(cell.take(), None); +/// cell.put(10); +/// assert_eq!(cell.take(), Some(10)); +/// assert_eq!(cell.replace(20), None); +/// assert_eq!(cell.get(), Some(20)); +/// +/// cell.map(|x| { +/// assert_eq!(x, &mut 20); +/// // `map` provides a `&mut` to the contents inside the closure +/// *x = 30; +/// }); +/// assert_eq!(cell.replace(60), Some(30)); +/// ``` pub struct MapCell { // Since val is potentially uninitialized memory, we must be sure to check // `.occupied` before calling `.val.get()` or `.val.assume_init()`. See // [mem::MaybeUninit](https://doc.rust-lang.org/core/mem/union.MaybeUninit.html). val: UnsafeCell>, - occupied: Cell, + + // Safety invariants: + // - The contents of `val` must be initialized if this is `Init` or `InsideMap`. + // - It must be sound to mutate `val` behind a shared reference if this is `Uninit` or `Init`. + // No outside mutation can occur while a `&mut` to the contents of `val` exist. + occupied: Cell, +} + +impl Drop for MapCell { + fn drop(&mut self) { + let state = self.occupied.get(); + debug_assert_not_borrowed!(self); // This should be impossible + if state == MapCellState::Init { + unsafe { + // SAFETY: + // - `occupied` is `Init`; `val` is initialized as an invariant. + // - Even though this violates the `occupied` invariant, by causing `val` + // to be no longer valid, `self` is immediately dropped. + drop_in_place(self.val.get_mut().as_mut_ptr()) + } + } + } +} + +impl MapCell { + /// Gets the contents of the cell, if any. + /// + /// Returns `None` if the cell is empty. + /// + /// This requires the held type be `Copy` for the same reason [`Cell::get`] does: + /// it leaves the contents of `self` intact and so it can't have drop glue. + /// + /// This returns `None` in release mode if the `MapCell`'s contents are already borrowed. + /// + /// # Examples + /// ``` + /// # use tock_cells::map_cell::MapCell; + /// let cell: MapCell = MapCell::empty(); + /// assert_eq!(cell.get(), None); + /// + /// cell.put(20); + /// assert_eq!(cell.get(), Some(20)); + /// ``` + /// + /// # Panics + /// If debug assertions are enabled, this panics if the `MapCell`'s contents are already borrowed. + pub fn get(&self) -> Option { + debug_assert_not_borrowed!(self); + // SAFETY: + // - `Init` means that `val` is initialized and can be read + // - `T: Copy` so there is no drop glue + (self.occupied.get() == MapCellState::Init) + .then(|| unsafe { self.val.get().read().assume_init() }) + } } impl MapCell { - /// Creates an empty `MapCell` + /// Creates an empty `MapCell`. pub const fn empty() -> MapCell { MapCell { val: UnsafeCell::new(MaybeUninit::uninit()), - occupied: Cell::new(false), + occupied: Cell::new(MapCellState::Uninit), } } - /// Creates a new `MapCell` containing `value` + /// Creates a new `MapCell` containing `value`. pub const fn new(value: T) -> MapCell { MapCell { - val: UnsafeCell::new(MaybeUninit::::new(value)), - occupied: Cell::new(true), + val: UnsafeCell::new(MaybeUninit::new(value)), + occupied: Cell::new(MapCellState::Init), } } - /// Returns a boolean which indicates if the MapCell is unoccupied. + /// Returns `true` if the `MapCell` contains no value. + /// + /// # Examples + /// ``` + /// # use tock_cells::map_cell::MapCell; + /// let x: MapCell = MapCell::empty(); + /// assert!(x.is_none()); + /// + /// x.put(10); + /// x.map(|_| assert!(!x.is_none())); + /// assert!(!x.is_none()); + /// ``` pub fn is_none(&self) -> bool { !self.is_some() } - /// Returns a boolean which indicates if the MapCell is occupied. + /// Returns `true` if the `MapCell` contains a value. + /// + /// # Examples + /// ``` + /// # use tock_cells::map_cell::MapCell; + /// let x: MapCell = MapCell::new(10); + /// assert!(x.is_some()); + /// x.map(|_| assert!(x.is_some())); + /// + /// x.take(); + /// assert!(!x.is_some()); + /// ``` pub fn is_some(&self) -> bool { - self.occupied.get() + self.occupied.get() != MapCellState::Uninit } - /// Takes the value out of the `MapCell` leaving it empty. If - /// the value has already been taken elsewhere (and not `replace`ed), the - /// returned `Option` will be `None`. + /// Takes the value out of the `MapCell`, leaving it empty. + /// + /// Returns `None` if the cell is empty. + /// + /// To save size, this has no effect and returns `None` in release mode + /// if the `MapCell`'s contents are already borrowed. /// /// # Examples /// /// ``` - /// extern crate tock_cells; - /// use tock_cells::map_cell::MapCell; - /// + /// # use tock_cells::map_cell::MapCell; /// let cell = MapCell::new(1234); /// let x = &cell; /// let y = &cell; @@ -64,53 +186,74 @@ impl MapCell { /// assert_eq!(x.take(), Some(1234)); /// assert_eq!(y.take(), None); /// ``` + /// + /// # Panics + /// If debug assertions are enabled, this panics if the `MapCell`'s contents are already borrowed. pub fn take(&self) -> Option { - if self.is_none() { - None - } else { - self.occupied.set(false); + debug_assert_not_borrowed!(self); + (self.occupied.get() == MapCellState::Init).then(|| { + // SAFETY: Since `occupied` is `Init`, `val` is initialized and can be mutated + // behind a shared reference. `result` is therefore initialized. unsafe { - let result: MaybeUninit = - ptr::replace(self.val.get(), MaybeUninit::::uninit()); - // `result` is _initialized_ and now `self.val` is now a new uninitialized value - Some(result.assume_init()) + let result: MaybeUninit = self.val.get().replace(MaybeUninit::uninit()); + self.occupied.set(MapCellState::Uninit); + result.assume_init() } - } + }) } - /// Puts a value into the `MapCell`. + /// Puts a value into the `MapCell` without returning the old value. + /// + /// To save size, this has no effect in release mode if `map` is invoking + /// a closure for this cell. + /// + /// # Panics + /// If debug assertions are enabled, this panics if the `MapCell`'s contents are already borrowed. pub fn put(&self, val: T) { - self.occupied.set(true); - unsafe { - ptr::write(self.val.get(), MaybeUninit::::new(val)); - } + debug_assert_not_borrowed!(self); + // This will ensure the value as dropped + self.replace(val); } - /// Replaces the contents of the `MapCell` with `val`. If the cell was not - /// empty, the previous value is returned, otherwise `None` is returned. + /// Replaces the contents of the `MapCell`, returning the old value if available. + /// + /// To save size, this has no effect and returns `None` in release mode + /// if the `MapCell`'s contents are already borrowed. + /// + /// # Panics + /// If debug assertions are enabled, this panics if the `MapCell`'s contents are already borrowed. pub fn replace(&self, val: T) -> Option { - if self.is_none() { - self.put(val); - None - } else { - unsafe { - let result: MaybeUninit = ptr::replace(self.val.get(), MaybeUninit::new(val)); - // `result` is _initialized_ and now `self.val` is now a new uninitialized value - Some(result.assume_init()) - } + let occupied = self.occupied.get(); + debug_assert_not_borrowed!(self); + if occupied == MapCellState::Borrowed { + return None; } + self.occupied.set(MapCellState::Init); + + // SAFETY: + // - Since `occupied` is `Init` or `Uninit`, no `&mut` to the `val` exists, meaning it + // is safe to mutate the `get` pointer. + // - If occupied is `Init`, `maybe_uninit_val` must be initialized. + let maybe_uninit_val = unsafe { self.val.get().replace(MaybeUninit::new(val)) }; + (occupied == MapCellState::Init).then(|| unsafe { maybe_uninit_val.assume_init() }) } - /// Allows `closure` to borrow the contents of the `MapCell` if-and-only-if - /// it is not `take`n already. The state of the `MapCell` is unchanged - /// after the closure completes. + /// Calls `closure` with a `&mut` of the contents of the `MapCell`, if available. + /// + /// The closure is only called if the `MapCell` has a value. + /// The state of the `MapCell` is unchanged after the closure completes. + /// + /// # Re-entrancy + /// + /// This borrows the contents of the cell while the closure is executing. + /// Be careful about calling methods on `&self` inside of that closure! + /// To save size, this has no effect in release mode, but if debug assertions + /// are enabled, this panics to indicate a likely bug. /// /// # Examples /// /// ``` - /// extern crate tock_cells; - /// use tock_cells::map_cell::MapCell; - /// + /// # use tock_cells::map_cell::MapCell; /// let cell = MapCell::new(1234); /// let x = &cell; /// let y = &cell; @@ -124,22 +267,33 @@ impl MapCell { /// // but potentially changed. /// assert_eq!(y.take(), Some(1235)); /// ``` + /// + /// # Panics + /// If debug assertions are enabled, this panics if the `MapCell`'s contents are already borrowed. + #[inline(always)] pub fn map(&self, closure: F) -> Option where F: FnOnce(&mut T) -> R, { - if self.is_some() { - self.occupied.set(false); - let valref = unsafe { &mut *self.val.get() }; - // TODO: change to valref.get_mut() once stabilized [#53491](https://github.com/rust-lang/rust/issues/53491) - let res = closure(unsafe { &mut *valref.as_mut_ptr() }); - self.occupied.set(true); - Some(res) - } else { - None - } + debug_assert_not_borrowed!(self); + (self.occupied.get() == MapCellState::Init).then(move || { + self.occupied.set(MapCellState::Borrowed); + // `occupied` is reset to initialized at the end of scope, + // even if a panic occurs in `closure`. + struct ResetToInit<'a>(&'a Cell); + impl Drop for ResetToInit<'_> { + #[inline(always)] + fn drop(&mut self) { + self.0.set(MapCellState::Init); + } + } + let _reset_to_init = ResetToInit(&self.occupied); + unsafe { closure(&mut *self.val.get().cast::()) } + }) } + /// Behaves like `map`, but returns `default` if there is no value present. + #[inline(always)] pub fn map_or(&self, default: R, closure: F) -> R where F: FnOnce(&mut T) -> R, @@ -149,22 +303,17 @@ impl MapCell { /// Behaves the same as `map`, except the closure is allowed to return /// an `Option`. + #[inline(always)] pub fn and_then(&self, closure: F) -> Option where F: FnOnce(&mut T) -> Option, { - if self.is_some() { - self.occupied.set(false); - let valref = unsafe { &mut *self.val.get() }; - // TODO: change to valref.get_mut() once stabilized [#53491](https://github.com/rust-lang/rust/issues/53491) - let res = closure(unsafe { &mut *valref.as_mut_ptr() }); - self.occupied.set(true); - res - } else { - None - } + self.map(closure).flatten() } + /// If a value is present `modify` is called with a borrow. + /// Otherwise, the value is set with `G`. + #[inline(always)] pub fn modify_or_replace(&self, modify: F, mkval: G) where F: FnOnce(&mut T), @@ -175,3 +324,81 @@ impl MapCell { } } } + +#[cfg(test)] +mod tests { + use super::MapCell; + + struct DropCheck<'a> { + flag: &'a mut bool, + } + impl<'a> Drop for DropCheck<'a> { + fn drop(&mut self) { + *self.flag = true; + } + } + + #[test] + fn test_drop() { + let mut dropped_after_drop = false; + let mut dropped_after_put = false; + { + let cell = MapCell::new(DropCheck { + flag: &mut dropped_after_put, + }); + cell.put(DropCheck { + flag: &mut dropped_after_drop, + }); + } + assert!(dropped_after_drop); + assert!(dropped_after_put); + } + + #[test] + fn test_replace() { + let a_cell = MapCell::new(1); + let old = a_cell.replace(2); + assert_eq!(old, Some(1)); + assert_eq!(a_cell.take(), Some(2)); + assert_eq!(a_cell.take(), None); + } + + #[test] + #[should_panic = "`MapCell` accessed while borrowed"] + fn test_map_in_borrow() { + let cell = MapCell::new(1); + let borrow1 = &cell; + let borrow2 = &cell; + borrow1.map(|_| borrow2.map(|_| ())); + } + + #[test] + #[should_panic = "`MapCell` accessed while borrowed"] + fn test_replace_in_borrow() { + let my_cell = MapCell::new(55); + my_cell.map(|_ref1: &mut i32| { + // Should fail + my_cell.replace(56); + }); + } + + #[test] + #[should_panic = "`MapCell` accessed while borrowed"] + fn test_put_in_borrow() { + let my_cell = MapCell::new(55); + my_cell.map(|_ref1: &mut i32| { + // Should fail + my_cell.put(56); + }); + } + + #[test] + #[should_panic = "`MapCell` accessed while borrowed"] + fn test_get_in_borrow() { + let my_cell = MapCell::new(55); + my_cell.map(|_ref1: &mut i32| { + // Should fail + my_cell.get(); + }); + } +} diff --git a/libraries/tock-cells/src/numeric_cell_ext.rs b/libraries/tock-cells/src/numeric_cell_ext.rs index 076ecd3d12..49a42d2cd3 100644 --- a/libraries/tock-cells/src/numeric_cell_ext.rs +++ b/libraries/tock-cells/src/numeric_cell_ext.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! `NumericCellExt` extension trait for `Cell`s. //! //! Adds a suite of convenience functions to `Cell`s that contain numeric @@ -13,7 +17,6 @@ //! ``` use core::cell::Cell; -use core::marker::Copy; use core::ops::{Add, Sub}; pub trait NumericCellExt @@ -52,22 +55,22 @@ where } fn increment(&self) { - self.set(self.get() + T::from(1 as usize)); + self.set(self.get() + T::from(1_usize)); } fn decrement(&self) { - self.set(self.get() - T::from(1 as usize)); + self.set(self.get() - T::from(1_usize)); } fn get_and_increment(&self) -> T { let ret = self.get(); - self.set(ret + T::from(1 as usize)); + self.set(ret + T::from(1_usize)); ret } fn get_and_decrement(&self) -> T { let ret = self.get(); - self.set(ret - T::from(1 as usize)); + self.set(ret - T::from(1_usize)); ret } } diff --git a/libraries/tock-cells/src/optional_cell.rs b/libraries/tock-cells/src/optional_cell.rs index 5916ed7fc4..b0e15e5c11 100644 --- a/libraries/tock-cells/src/optional_cell.rs +++ b/libraries/tock-cells/src/optional_cell.rs @@ -1,10 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! `OptionalCell` convenience type use core::cell::Cell; /// `OptionalCell` is a `Cell` that wraps an `Option`. This is helper type /// that makes keeping types that can be `None` a little cleaner. -#[derive(Default)] pub struct OptionalCell { value: Cell>, } @@ -71,7 +74,10 @@ impl OptionalCell { T: PartialEq, { let value = self.value.take(); - let out = value.contains(x); + let out = match &value { + Some(y) => y == x, + None => false, + }; self.value.set(value); out } @@ -149,7 +155,35 @@ impl OptionalCell { impl OptionalCell { /// Returns a copy of the contained [`Option`]. - pub fn extract(&self) -> Option { + // + // This was originally introduced in PR #2531 [1], then renamed to `extract` + // in PR #2533 [2], and finally renamed back in PR #3536 [3]. + // + // The rationale for including a `get` method is to allow developers to + // treat an `OptionalCell` as what it is underneath: a `Cell>`. + // This is useful to be interoperable with APIs that take an `Option`, or + // to use an *if-let* or *match* expression to perform case-analysis on the + // `OptionalCell`'s state: this avoids using a closure and can thus allow + // Rust to deduce that only a single branch will ever be entered (either the + // `Some(_)` or `None`) branch, avoiding lifetime & move restrictions. + // + // However, there was pushback for that name, as an `OptionalCell`'s `get` + // method might indicate that it should directly return a `T` -- given that + // `OptionalCell` presents itself as to be a wrapper around + // `T`. Furthermore, adding `.get()` might have developers use + // `.get().map(...)` instead, which defeats the purpose of having the + // `OptionalCell` convenience wrapper in the first place. For these reasons, + // `get` was renamed to `extract`. + // + // Unfortunately, `extract` turned out to be a confusing name, as it is not + // an idiomatic method name as found on Rust's standard library types, and + // further suggests that it actually removes a value from the `OptionalCell` + // (as the `take` method does). Thus, it has been renamed back to `get`. + // + // [1]: https://github.com/tock/tock/pull/2531 + // [2]: https://github.com/tock/tock/pull/2533 + // [3]: https://github.com/tock/tock/pull/3536 + pub fn get(&self) -> Option { self.value.get() } @@ -179,20 +213,18 @@ impl OptionalCell { /// Call a closure on the value if the value exists. pub fn map(&self, closure: F) -> Option where - F: FnOnce(&mut T) -> R, + F: FnOnce(T) -> R, { - self.value.get().map(|mut val| closure(&mut val)) + self.value.get().map(|val| closure(val)) } /// Call a closure on the value if the value exists, or return the /// default if the value is `None`. pub fn map_or(&self, default: R, closure: F) -> R where - F: FnOnce(&mut T) -> R, + F: FnOnce(T) -> R, { - self.value - .get() - .map_or(default, |mut val| closure(&mut val)) + self.value.get().map_or(default, |val| closure(val)) } /// If the cell contains a value, call a closure supplied with the @@ -201,11 +233,9 @@ impl OptionalCell { pub fn map_or_else(&self, default: D, closure: F) -> U where D: FnOnce() -> U, - F: FnOnce(&mut T) -> U, + F: FnOnce(T) -> U, { - self.value - .get() - .map_or_else(default, |mut val| closure(&mut val)) + self.value.get().map_or_else(default, |val| closure(val)) } /// If the cell is empty, return `None`. Otherwise, call a closure @@ -214,3 +244,12 @@ impl OptionalCell { self.value.get().and_then(f) } } + +// Manual implementation of the [`Default`] trait, as +// `#[derive(Default)]` incorrectly constraints `T: Default`. +impl Default for OptionalCell { + /// Returns an empty [`OptionalCell`]. + fn default() -> Self { + OptionalCell::empty() + } +} diff --git a/libraries/tock-cells/src/take_cell.rs b/libraries/tock-cells/src/take_cell.rs index 482b97bbbb..d52e01d5ae 100644 --- a/libraries/tock-cells/src/take_cell.rs +++ b/libraries/tock-cells/src/take_cell.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tock specific `TakeCell` type for sharing references. use core::cell::Cell; @@ -16,10 +20,12 @@ pub struct TakeCell<'a, T: 'a + ?Sized> { } impl<'a, T: ?Sized> TakeCell<'a, T> { + const EMPTY: Self = Self { + val: Cell::new(None), + }; + pub const fn empty() -> TakeCell<'a, T> { - TakeCell { - val: Cell::new(None), - } + Self::EMPTY } /// Creates a new `TakeCell` containing `value` @@ -111,8 +117,8 @@ impl<'a, T: ?Sized> TakeCell<'a, T> { F: FnOnce(&mut T) -> R, { let maybe_val = self.take(); - maybe_val.map(|mut val| { - let res = closure(&mut val); + maybe_val.map(|val| { + let res = closure(val); self.replace(val); res }) @@ -124,8 +130,8 @@ impl<'a, T: ?Sized> TakeCell<'a, T> { F: FnOnce(&mut T) -> R, { let maybe_val = self.take(); - maybe_val.map_or(default, |mut val| { - let res = closure(&mut val); + maybe_val.map_or(default, |val| { + let res = closure(val); self.replace(val); res }) @@ -141,8 +147,8 @@ impl<'a, T: ?Sized> TakeCell<'a, T> { let maybe_val = self.take(); maybe_val.map_or_else( || default(), - |mut val| { - let res = f(&mut val); + |val| { + let res = f(val); self.replace(val); res }, @@ -156,8 +162,8 @@ impl<'a, T: ?Sized> TakeCell<'a, T> { F: FnOnce(&mut T) -> Option, { let maybe_val = self.take(); - maybe_val.and_then(|mut val| { - let res = closure(&mut val); + maybe_val.and_then(|val| { + let res = closure(val); self.replace(val); res }) @@ -172,8 +178,8 @@ impl<'a, T: ?Sized> TakeCell<'a, T> { G: FnOnce() -> &'a mut T, { let val = match self.take() { - Some(mut val) => { - modify(&mut val); + Some(val) => { + modify(val); val } None => mkval(), diff --git a/libraries/tock-cells/src/volatile_cell.rs b/libraries/tock-cells/src/volatile_cell.rs index 8651f102a8..26e6bf2c08 100644 --- a/libraries/tock-cells/src/volatile_cell.rs +++ b/libraries/tock-cells/src/volatile_cell.rs @@ -1,29 +1,95 @@ -//! Implementation of a type for accessing MCU registers. +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. +// This file was vendored into Tock. It comes from +// https://github.com/japaric/vcell, commit +// ef556474203e93b3f45f0f8cd8dea3210aa7f844, path src/lib.rs. + +//! A type for accessing MMIO registers. `VolatileCell` is just like [`Cell`] +//! but with [volatile] read / write operations +//! +//! [`Cell`]: https://doc.rust-lang.org/std/cell/struct.Cell.html +//! [volatile]: https://doc.rust-lang.org/std/ptr/fn.read_volatile.html + +#![deny(missing_docs)] +#![deny(warnings)] + +use core::cell::UnsafeCell; use core::ptr; -/// `VolatileCell` provides a wrapper around unsafe volatile pointer reads -/// and writes. This is particularly useful for accessing microcontroller -/// registers. -// Source: https://github.com/hackndev/zinc/tree/master/volatile_cell -#[derive(Copy, Clone, Default)] -#[repr(C)] +/// `VolatileCell` provides a wrapper around unsafe volatile pointer reads and +/// writes. This is particularly useful for accessing microcontroller registers +/// by (unsafely) casting a pointer to the register into a `VolatileCell`. +/// +/// ``` +/// use tock_cells::volatile_cell::VolatileCell; +/// let myptr: *const usize = 0xdeadbeef as *const usize; +/// let myregister: &VolatileCell = unsafe { core::mem::transmute(myptr) }; +/// ``` +#[derive(Default)] +#[repr(transparent)] pub struct VolatileCell { - value: T, + value: UnsafeCell, } impl VolatileCell { + /// Creates a new `VolatileCell` containing the given value pub const fn new(value: T) -> Self { - VolatileCell { value } + VolatileCell { + value: UnsafeCell::new(value), + } } - #[inline] - pub fn get(&self) -> T { - unsafe { ptr::read_volatile(&self.value) } + /// Performs a memory read and returns a copy of the value represented by + /// the cell. + /// + /// # Side-Effects + /// + /// `get` _always_ performs a memory read on the underlying location. If + /// this location is a memory-mapped I/O register, the side-effects of + /// performing the read are register-specific. + /// + /// # Examples + /// + /// ``` + /// use tock_cells::volatile_cell::VolatileCell; + /// + /// let vc = VolatileCell::new(5); + /// let five = vc.get(); + /// ``` + #[inline(always)] + pub fn get(&self) -> T + where + T: Copy, + { + unsafe { ptr::read_volatile(self.value.get()) } } - #[inline] - pub fn set(&self, value: T) { - unsafe { ptr::write_volatile(&self.value as *const T as *mut T, value) } + /// Performs a memory write with the provided value. + /// + /// # Side-Effects + /// + /// `set` _always_ performs a memory write on the underlying location. If + /// this location is a memory-mapped I/O register, the side-effects of + /// performing the write are register-specific. + /// + /// # Examples + /// + /// ``` + /// use tock_cells::volatile_cell::VolatileCell; + /// + /// let vc = VolatileCell::new(123); + /// vc.set(432); + /// ``` + #[inline(always)] + pub fn set(&self, value: T) + where + T: Copy, + { + unsafe { ptr::write_volatile(self.value.get(), value) } } } + +// NOTE implicit because of `UnsafeCell` +// unsafe impl !Sync for VolatileCell {} diff --git a/libraries/tock-register-interface/CHANGELOG.md b/libraries/tock-register-interface/CHANGELOG.md index 0688ab4898..3569d1741c 100644 --- a/libraries/tock-register-interface/CHANGELOG.md +++ b/libraries/tock-register-interface/CHANGELOG.md @@ -2,6 +2,67 @@ ## master +## v0.9 + +There is a small breaking change, described below, which addresses semantic +confusion around `matches_any()`. The interface has changed, so this should +result in compile-time awareness rather than any silent behavioral changes. + +### **Breaking Changes** + +https://github.com/tock/tock/pull/3336 +480ee65139f1e51c149236a1b3e47cd61832ac80 +Rename matches_any() to any_matching_bits_set(), implement matches_any() + +> The current implementation of matches_any() does not implement the +> functionality the name implies. This commit renames the existing +> implementation to a name which better describes its functionality, +> and introduces a new matches_any() function (with a different interface) +> that actually correctly implements the functionality suggested by the +> name. This commit also adds several tests to the tock-registers test +> suite to verify the new version works as expected, and removes a feature +> gate on a feature that no longer exists for the crate which was +> preventing some of the tock-registers teste suite from being run as part +> of `cargo test`. + +### Other changes: + + - #3687: derive `Debug` on `Value` + - #3582: Update rust-toolchain to nightly of 2023-07-30 + +## v0.8.1 + +A point release to allow multiple invocations of `register_fields!` in +the same module. See the PR and linked Issue for details. + + - #3230: don't encapsulate test_fields! tests in mod + +## v0.8 + +`tock-registers` now supports stable Rust! + +There is a small breaking change, documented below, required to support +Rust 2021 edition. Most of the remaining changes are improvements to the +internal self-testing infrastructure and documentation. There are also +some additions to `FieldValue` to improve ergonomics. + +### **Breaking Changes** + + - #2842: tock-registers: rename TryFromValue::try_from to try_from_value + - #2838: Update to Rust 2021 edition + +### Other Changes + + - #3126: [trivial] tock-registers: mark two methods as `const` + - #3088: tock_registers/test_fields: respect struct size padding w/ alignment + - #3072: Update Rust nightly version + Expose virtual-function-elimination + - libraries/tock-register-interface: Fixup register_structs documentation + - #2988: Remove const_fn_trait_bound feature and update nightly (Mar 2022) + - #3014: tock-registers: Implement From field enum value type for FieldValue + - #3013: tock-register-interface: Provide none method for FieldValue type + - #2916: tock-register-interface: improve read_as_enum documentation + - #2922: tock-register-interface: replace register tests by const assertions + ## v0.7 - #2642: Rename `IntLike` to `UIntLike` to match semantics diff --git a/libraries/tock-register-interface/Cargo.toml b/libraries/tock-register-interface/Cargo.toml index 99d563e48c..90c7665313 100644 --- a/libraries/tock-register-interface/Cargo.toml +++ b/libraries/tock-register-interface/Cargo.toml @@ -1,6 +1,10 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "tock-registers" -version = "0.7.0" +version = "0.9.0" authors = ["Tock Project Developers "] description = "Memory-Mapped I/O and register interface developed for Tock." homepage = "https://www.tockos.org/" @@ -11,18 +15,13 @@ categories = ["data-structures", "embedded", "no-std"] license = "MIT/Apache-2.0" edition = "2021" -[badges] -travis-ci = { repository = "tock/tock", branch = "master" } - [features] -default = [ "register_types", "std_unit_tests" ] +default = [ "register_types" ] # Include actual register types (except LocalRegisterCopy). Disabling # the feature makes this an interface-only library and removes all # usage of unsafe code register_types = [] -# Feature flag to enable generation of unit tests for the -# registers. Enabling this may break compilation in -# `custom-test-frameworks` environments. -std_unit_tests = [] +[lints] +workspace = true diff --git a/libraries/tock-register-interface/README.md b/libraries/tock-register-interface/README.md index 13f2e19524..4c58a27983 100644 --- a/libraries/tock-register-interface/README.md +++ b/libraries/tock-register-interface/README.md @@ -90,22 +90,16 @@ struct Registers { } ``` -By default, `std` unit tests for the struct are generated as well (that is, -tests attributed with `#[test]`). The unit tests make sure that the offsets and -padding are consistent with the actual fields in the struct, and that alignment -is correct. - -Since those tests would break compilation in `custom-test-frameworks` -environments, it is possible to opt out of the test generation. To do -so, disable the default feature set containing the `std_unit_tests` -feature: - -```toml -[dependencies.tock-registers] -version = "0.4.x" -default-features = false -features = ["register_types"] -``` +This crate will generate additional, compile time (`const`) assertions +to validate various invariants of the register structs, such as + +- proper start offset of padding fields, +- proper start and end offsets of actual fields, +- invalid alignment of field types, +- the `@END` marker matching the size of the struct. + +For more information on the generated assertions, check out the [`test_fields!` +macro documentation](https://docs.tockos.org/tock_registers/macro.test_fields.html). By default, the visibility of the generated structs and fields is private. You can make them public using the `pub` keyword, just before the struct name or the @@ -209,8 +203,9 @@ ReadOnly: Readable .read(field: Field) -> T // Read the value of the given field .read_as_enum(field: Field) -> Option // Read value of the given field as a enum member .is_set(field: Field) -> bool // Check if one or more bits in a field are set -.matches_any(value: FieldValue) -> bool // Check if any specified parts of a field match +.any_matching_bits_set(value: FieldValue) -> bool // Check if any bits corresponding to the mask in the passed field are set .matches_all(value: FieldValue) -> bool // Check if all specified parts of a field match +.matches_any(&self, fields: &[FieldValue]) -> bool // Check if any specified parts of a field match .extract() -> LocalRegisterCopy // Make local copy of register WriteOnly: Writeable @@ -230,8 +225,9 @@ ReadWrite: Readable + Writeable + ReadWri original: LocalRegisterCopy, // leaving other fields unchanged, but pass in value: FieldValue) // the original value, instead of doing a register read .is_set(field: Field) -> bool // Check if one or more bits in a field are set -.matches_any(value: FieldValue) -> bool // Check if any specified parts of a field match +.any_matching_bits_set(value: FieldValue) -> bool // Check if any bits corresponding to the mask in the passed field are set .matches_all(value: FieldValue) -> bool // Check if all specified parts of a field match +.matches_any(&self, fields: &[FieldValue]) -> bool // Check if any specified parts of a field match .extract() -> LocalRegisterCopy // Make local copy of register Aliased: Readable + Writeable @@ -242,8 +238,9 @@ Aliased: Readab .write(value: FieldValue) // Write the value of one or more fields, // overwriting other fields to zero .is_set(field: Field) -> bool // Check if one or more bits in a field are set -.matches_any(value: FieldValue) -> bool // Check if any specified parts of a field match +.any_matching_bits_set(value: FieldValue) -> bool // Check if any bits corresponding to the mask in the passed field are set .matches_all(value: FieldValue) -> bool // Check if all specified parts of a field match +.matches_any(&self, fields: &[FieldValue]) -> bool // Check if any specified parts of a field match .extract() -> LocalRegisterCopy // Make local copy of register ``` @@ -300,6 +297,9 @@ registers.cr.modify(Control::RANGE.val(2)); // Leaves EN, INT unchanged // Named constants can be used instead of the raw values: registers.cr.modify(Control::RANGE::VeryHigh); +// Enum values can also be used: +registers.cr.modify(Control::RANGE::Value::VeryHigh.into()) + // Another example of writing a field with a raw value: registers.cr.modify(Control::EN.val(0)); // Leaves RANGE, INT unchanged @@ -338,7 +338,8 @@ let txcomplete: bool = registers.s.is_set(Status::TXCOMPLETE); // MATCHING // ----------------------------------------------------------------------------- -// You can also query a specific register state easily with `matches_[any|all]`: +// You can also query a specific register state easily with `matches_all` or +// `any_matching_bits_set` or `matches_any`: // Doesn't care about the state of any field except TXCOMPLETE and MODE: let ready: bool = registers.s.matches_all(Status::TXCOMPLETE:SET + @@ -350,14 +351,23 @@ while !registers.s.matches_all(Status::TXCOMPLETE::SET + Status::TXINTERRUPT::CLEAR) {} // Or for checking whether any interrupts are enabled: -let any_ints = registers.s.matches_any(Status::TXINTERRUPT + Status::RXINTERRUPT); +let any_ints = registers.s.any_matching_bits_set(Status::TXINTERRUPT + Status::RXINTERRUPT); + +// Or for checking whether any completion states are cleared: +let any_cleared = registers.s.matches_any(&[Status::TXCOMPLETE::CLEAR, Status::RXCOMPLETE::CLEAR]); + +// Or for checking if a multi-bit field matches one of several modes: +let sub_word_size = registers.s.matches_any(&[Size::Halfword, Size::Word]); + +// Or for checking if any of several fields exactly match in the register: +let not_supported_mode = registers.s.matches_any(&[Status::Mode::HalfDuplex, Status::Mode::VARSYNC, Status::MODE::NOPARITY]); // Also you can read a register with set of enumerated values as a enum and `match` over it: let mode = registers.cr.read_as_enum(Status::MODE); match mode { - Some(Status::MODE::FullDuplex) => { /* ... */ } - Some(Status::MODE::HalfDuplex) => { /* ... */ } + Some(Status::MODE::Value::FullDuplex) => { /* ... */ } + Some(Status::MODE::Value::HalfDuplex) => { /* ... */ } None => unreachable!("invalid value") } @@ -458,6 +468,43 @@ register_bitfields! [ ] ``` +## Debug trait + +By default, if you print the value of a register, you will get the raw value as a number. + +How ever, you can use the `debug` method to get a more human readable output. + +This is implemented in `LocalRegisterCopy` and in using `Debuggable` registers which is auto implemented with `Readable`. + +Example: + +```rust +// Create a copy of the register value as a local variable. +let local = registers.cr.extract(); + +println!("cr: {:#?}", local.debug()); +``` + +For example, if the value of the `Control` register is `0b0000_0100`, the output will be: + +```rust +cr: Control { + RANGE: VeryHigh, + EN: 0, + INT: 1 +} +``` + +Similarly it works directly on the register: + +```rust +// require `Debuggable` trait +use tock_registers::interfaces::Debuggable; + +println!("cr: {:#?}", registers.cr.debug()); +``` +> Do note this will issue a read to the register once. + ## Implementing custom register types The `Readable`, `Writeable` and `ReadWriteable` traits make it diff --git a/libraries/tock-register-interface/src/debug.rs b/libraries/tock-register-interface/src/debug.rs new file mode 100644 index 0000000000..e0369f7910 --- /dev/null +++ b/libraries/tock-register-interface/src/debug.rs @@ -0,0 +1,202 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +//! Register Debug Support Infrastructure +//! +//! This module provides optional infrastructure to query debug information from +//! register types implementing the [`RegisterDebugInfo`] trait. This +//! information can then be used by the [`RegisterDebugValue`] type to produce a +//! human-readable representation of a register's fields and values. + +use core::fmt; +use core::marker::PhantomData; + +use crate::{ + fields::{Field, TryFromValue}, + RegisterLongName, UIntLike, +}; + +/// `FieldValueEnumSeq` is a debug helper trait representing a sequence of +/// [field enum types](crate::fields::Field::read_as_enum). It provides methods +/// to recurse through this sequence of types and thus call methods on them, +/// such as [`try_from_value`](crate::fields::TryFromValue::try_from_value). +/// +/// Its primary use lies in the [`RegisterDebugInfo`] trait. This trait provides +/// facilities useful for providing information on the layout of a register +/// (such as, which fields it has and which known values those fields can +/// assume). Such information is usually only available at compile time. This +/// trait makes runtime-representable data available at runtime. However, +/// information encoded solely in types can't simply be used at runtime, which +/// is where this trait comes in. +pub trait FieldValueEnumSeq { + /// Iterates over the sequence of types and performs the following steps: + /// + /// 1. Invokes the `data` function argument. This is expected to provide the + /// numeric (`UIntLike`) value of the register field that the current type + /// corresponds to. + /// + /// 2. Invoke [`try_from_value`](crate::fields::TryFromValue::try_from_value) + /// on the current [field enum type](crate::fields::Field::read_as_enum), + /// passing the value returned by `data`. + /// + /// 3. Provide the returned value to the `f` function argument. This is + /// either the enum representation (if the field value belongs to a known + /// variant, or the numeric field value returned by `data`. + /// + /// In practice, this method should be used to iterate over types and other + /// runtime-accessible information in tandem, to produce a human-readable + /// register dump. + /// + /// Importantly, `data` is invoked for every type in the sequence, and + /// every invocation of `data` is followed by a single invocation of `f`. + fn recurse_try_from_value(data: &mut impl FnMut() -> U, f: &mut impl FnMut(&dyn fmt::Debug)); +} + +/// End-of-list type for the [`FieldValueEnumSeq`] sequence. +pub enum FieldValueEnumNil {} +impl FieldValueEnumSeq for FieldValueEnumNil { + fn recurse_try_from_value(_data: &mut impl FnMut() -> U, _f: &mut impl FnMut(&dyn fmt::Debug)) { + } +} + +/// List element for the [`FieldValueEnumSeq`] sequence. +pub enum FieldValueEnumCons< + U: UIntLike, + H: TryFromValue + fmt::Debug, + T: FieldValueEnumSeq, +> { + // This variant can never be constructed, as `Infallible` can't be: + _Impossible( + core::convert::Infallible, + PhantomData, + PhantomData, + PhantomData, + ), +} +impl + fmt::Debug, T: FieldValueEnumSeq> + FieldValueEnumSeq for FieldValueEnumCons +{ + fn recurse_try_from_value(data: &mut impl FnMut() -> U, f: &mut impl FnMut(&dyn fmt::Debug)) { + // Query debug information from first type, then recurse into the next. + + // It's imprtant that we call `data` _exactly_ once here. + let extracted_value = data(); + + // It's important that we call `f` _exactly_ once here. + match H::try_from_value(extracted_value) { + Some(v) => f(&v), + None => f(&extracted_value), + } + + // Continue the recursion: + T::recurse_try_from_value(data, f) + } +} + +/// [`RegisterDebugInfo`] exposes debugging information from register types. +/// +/// The exposed information is composed of both types (such as the individual +/// [field enum types](crate::fields::Field::read_as_enum) generated by the +/// [`crate::register_bitfields`] macro), as well as runtime-queryable +/// information in the form of data. +/// +/// Where applicable, the index of type information and runtime-queryable data +/// match. For instance, the `i`th element of the +/// [`RegisterDebugInfo::FieldValueEnumTypes`] associated type sequence +/// corresponds to the `i`th element of the array returned by the +/// [`RegisterDebugInfo::field_names`] method. +pub trait RegisterDebugInfo: RegisterLongName { + /// Associated type representing a sequence of all field-value enum types of + /// this [`RegisterLongName`] register. + /// + /// See [`FieldValueEnumSeq`]. The index of types in this sequence + /// correspond to indices of values returned from the [`fields`] and + /// [`field_names`] methods. + /// + /// [`field_names`]: RegisterDebugInfo::field_names + /// [`fields`]: RegisterDebugInfo::fields + type FieldValueEnumTypes: FieldValueEnumSeq; + + /// The name of the register. + fn name() -> &'static str; + + /// The names of the fields in the register. + /// + /// The length of the returned slice is identical to the length of the + /// [`FieldValueEnumTypes`] sequence and [`fields`] return value. For every + /// index `i`, the element of this slice corresponds to the type at the + /// `i`th position in the [`FieldValueEnumTypes`] sequence and element at + /// the `i`th position in the [`fields`] return value. + /// + /// [`FieldValueEnumTypes`]: RegisterDebugInfo::FieldValueEnumTypes + /// [`fields`]: RegisterDebugInfo::fields + fn field_names() -> &'static [&'static str]; + + /// The fields of a register. + /// + /// The length of the returned slice is identical to the length of the + /// [`FieldValueEnumTypes`] sequence and [`field_names`] return value. For + /// every index `i`, the element of this slice corresponds to the type at + /// the `i`th position in the [`FieldValueEnumTypes`] sequence and element + /// at the `i`th position in the [`field_names`] return value. + /// + /// [`FieldValueEnumTypes`]: RegisterDebugInfo::FieldValueEnumTypes + /// [`field_names`]: RegisterDebugInfo::field_names + fn fields() -> &'static [Field] + where + Self: Sized; +} + +/// `RegisterDebugValue` captures a register's value and implements +/// [`fmt::Debug`] to provide a human-readable representation of the register +/// state. +/// +/// Its usage incurs the inclusion of additional data into the final binary, +/// such as the names of all register fields and defined field value variants +/// (see [`crate::fields::Field::read_as_enum`]). +/// +/// This type contains a local copy of the register value used for providing +/// debug information. It will not access the actual backing register. +pub struct RegisterDebugValue +where + T: UIntLike, + E: RegisterDebugInfo, +{ + pub(crate) data: T, + pub(crate) _reg: core::marker::PhantomData, +} + +impl fmt::Debug for RegisterDebugValue +where + T: UIntLike + 'static, + E: RegisterDebugInfo + 'static, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // This is using the core library's formatting facilities to produce an + // output similar to Rust's own derive-Debug implementation on structs. + // + // We start by printing the struct's name and opening braces: + let mut debug_struct = f.debug_struct(E::name()); + + // Now, obtain iterators over both the struct's field types and + // names. They are guaranteed to match up: + let mut names = E::field_names().iter(); + let mut fields = E::fields().iter(); + + // To actually resolve the field's known values (encoded in the field + // enum type's variants), we need to recurse through those field + // types. Their ordering is guaranteed to match up with the above + // calls. For more information on what these closures do and how they + // are invoked, consult the documentation of `recurse_try_from_value`. + let mut data = || fields.next().unwrap().read(self.data); + let mut debug_field = |f: &dyn fmt::Debug| { + debug_struct.field(names.next().unwrap(), f); + }; + + // Finally, recurse through all the fields: + E::FieldValueEnumTypes::recurse_try_from_value(&mut data, &mut debug_field); + + debug_struct.finish() + } +} diff --git a/libraries/tock-register-interface/src/fields.rs b/libraries/tock-register-interface/src/fields.rs index e4f651af52..87575c5290 100644 --- a/libraries/tock-register-interface/src/fields.rs +++ b/libraries/tock-register-interface/src/fields.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Register bitfield types and macros //! //! To conveniently access and manipulate fields of a register, this @@ -9,7 +13,7 @@ //! //! A specific section (bitfield) in a register is described by the //! [`Field`] type, consisting of an unshifted bitmask over the base -//! register [`UIntLike`](crate::UIntLike) type, and a shift +//! register [`UIntLike`] type, and a shift //! parameter. It is further associated with a specific //! [`RegisterLongName`], which can prevent its use with incompatible //! registers. @@ -23,7 +27,7 @@ //! ## `register_bitfields` macro //! //! For defining register layouts with an associated -//! [`RegisterLongName`](crate::RegisterLongName), along with +//! [`RegisterLongName`], along with //! [`Field`]s and matching [`FieldValue`]s, a convenient macro-based //! interface can be used. //! @@ -57,6 +61,12 @@ //! assert!(reg.read(Uart::ENABLE) == 0x00000000); //! reg.modify(Uart::ENABLE::ON); //! assert!(reg.get() == 0x00000008); +//! +//! use tock_registers::interfaces::Debuggable; +//! assert!( +//! &format!("{:?}", reg.debug()) +//! == "Uart { ENABLE: ON }" +//! ); //! ``` // The register interface uses `+` in a way that is fine for bitfields, but @@ -82,8 +92,8 @@ pub struct Field { impl Field { pub const fn new(mask: T, shift: usize) -> Field { Field { - mask: mask, - shift: shift, + mask, + shift, associated_register: PhantomData, } } @@ -101,6 +111,42 @@ impl Field { #[inline] /// Read value of the field as an enum member + /// + /// This method expects to be passed the unasked and unshifted register + /// value, extracts the field value by calling [`Field::read`] and + /// subsequently passes that value to the [`TryFromValue`] implementation on + /// the passed enum type. + /// + /// The [`register_bitfields!`](crate::register_bitfields) macro will + /// generate an enum containing the various named field variants and + /// implementing the required [`TryFromValue`] trait. It is accessible as + /// `$REGISTER_NAME::$FIELD_NAME::Value`. + /// + /// This method can be useful to symbolically represent read register field + /// states throughout the codebase and to enforce exhaustive matches over + /// all defined valid register field values. + /// + /// ## Usage Example + /// + /// ```rust + /// # use tock_registers::interfaces::Readable; + /// # use tock_registers::registers::InMemoryRegister; + /// # use tock_registers::register_bitfields; + /// register_bitfields![u8, + /// EXAMPLEREG [ + /// TESTFIELD OFFSET(3) NUMBITS(3) [ + /// Foo = 2, + /// Bar = 3, + /// Baz = 6, + /// ], + /// ], + /// ]; + /// + /// assert_eq!( + /// EXAMPLEREG::TESTFIELD.read_as_enum::(0x9C).unwrap(), + /// EXAMPLEREG::TESTFIELD::Value::Bar + /// ); + /// ``` pub fn read_as_enum>(self, val: T) -> Option { E::try_from_value(self.read(val)) } @@ -124,11 +170,7 @@ impl Field { // Relevant Rust issue: https://github.com/rust-lang/rust/issues/26925 impl Clone for Field { fn clone(&self) -> Self { - Field { - mask: self.mask, - shift: self.shift, - associated_register: self.associated_register, - } + *self } } impl Copy for Field {} @@ -136,7 +178,7 @@ impl Copy for Field {} macro_rules! Field_impl_for { ($type:ty) => { impl Field<$type, R> { - pub fn val(&self, value: $type) -> FieldValue<$type, R> { + pub const fn val(&self, value: $type) -> FieldValue<$type, R> { FieldValue::<$type, R>::new(self.mask, self.shift, value) } } @@ -194,9 +236,18 @@ FieldValue_impl_for!(u128); FieldValue_impl_for!(usize); impl FieldValue { + #[inline] + pub fn none() -> Self { + Self { + mask: T::zero(), + value: T::zero(), + associated_register: PhantomData, + } + } + /// Get the raw bitmask represented by this FieldValue. #[inline] - pub fn mask(&self) -> T { + pub const fn mask(&self) -> T { self.mask as T } @@ -211,10 +262,12 @@ impl FieldValue { (val & !self.mask) | self.value } - /// Check if any specified parts of a field match + /// Check if any of the bits covered by the mask for this + /// `FieldValue` and set in the `FieldValue` are also set + /// in the passed value #[inline] - pub fn matches_any(&self, val: T) -> bool { - val & self.mask != T::zero() + pub fn any_matching_bits_set(&self, val: T) -> bool { + val & self.mask & self.value != T::zero() } /// Check if all specified parts of a field match @@ -269,40 +322,44 @@ macro_rules! register_bitmasks { { // BITFIELD_NAME OFFSET(x) $(#[$outer:meta])* - $valtype:ident, $reg_desc:ident, [ + $valtype:ident, $reg_mod:ident, $reg_desc:ident, [ $( $(#[$inner:meta])* $field:ident OFFSET($offset:expr)),+ $(,)? ] } => { $(#[$outer])* $( $crate::register_bitmasks!($valtype, $reg_desc, $(#[$inner])* $field, $offset, 1, []); )* + $crate::register_bitmasks!(@debug $valtype, $reg_mod, $reg_desc, [$($field),*]); }; + { // BITFIELD_NAME OFFSET // All fields are 1 bit $(#[$outer:meta])* - $valtype:ident, $reg_desc:ident, [ + $valtype:ident, $reg_mod:ident, $reg_desc:ident, [ $( $(#[$inner:meta])* $field:ident $offset:expr ),+ $(,)? ] } => { $(#[$outer])* $( $crate::register_bitmasks!($valtype, $reg_desc, $(#[$inner])* $field, $offset, 1, []); )* + $crate::register_bitmasks!(@debug $valtype, $reg_mod, $reg_desc, [$($field),*]); }; { // BITFIELD_NAME OFFSET(x) NUMBITS(y) $(#[$outer:meta])* - $valtype:ident, $reg_desc:ident, [ + $valtype:ident, $reg_mod:ident, $reg_desc:ident, [ $( $(#[$inner:meta])* $field:ident OFFSET($offset:expr) NUMBITS($numbits:expr) ),+ $(,)? ] } => { $(#[$outer])* $( $crate::register_bitmasks!($valtype, $reg_desc, $(#[$inner])* $field, $offset, $numbits, []); )* + $crate::register_bitmasks!(@debug $valtype, $reg_mod, $reg_desc, [$($field),*]); }; { // BITFIELD_NAME OFFSET(x) NUMBITS(y) [] $(#[$outer:meta])* - $valtype:ident, $reg_desc:ident, [ + $valtype:ident, $reg_mod:ident, $reg_desc:ident, [ $( $(#[$inner:meta])* $field:ident OFFSET($offset:expr) NUMBITS($numbits:expr) $values:tt ),+ $(,)? ] @@ -310,7 +367,9 @@ macro_rules! register_bitmasks { $(#[$outer])* $( $crate::register_bitmasks!($valtype, $reg_desc, $(#[$inner])* $field, $offset, $numbits, $values); )* + $crate::register_bitmasks!(@debug $valtype, $reg_mod, $reg_desc, [$($field),*]); }; + { $valtype:ident, $reg_desc:ident, $(#[$outer:meta])* $field:ident, $offset:expr, $numbits:expr, @@ -354,6 +413,7 @@ macro_rules! register_bitmasks { #[allow(dead_code)] #[allow(non_camel_case_types)] + #[derive(Copy, Clone, Debug, Eq, PartialEq)] #[repr($valtype)] // so that values larger than isize::MAX can be stored $(#[$outer])* pub enum Value { @@ -377,6 +437,12 @@ macro_rules! register_bitmasks { } } } + + impl From for FieldValue<$valtype, $reg_desc> { + fn from(v: Value) -> Self { + Self::new($crate::bitmask!($numbits), $offset, v as $valtype) + } + } } }; { @@ -411,6 +477,7 @@ macro_rules! register_bitmasks { #[allow(dead_code)] #[allow(non_camel_case_types)] + #[derive(Debug)] $(#[$outer])* pub enum Value {} @@ -423,6 +490,68 @@ macro_rules! register_bitmasks { } } }; + + // Implement the `RegisterDebugInfo` trait for the register. Refer to its + // documentation for more information on the individual types and fields. + ( + // final implementation of the macro + @debug $valtype:ident, $reg_mod:ident, $reg_desc:ident, [$($field:ident),*] + ) => { + impl $crate::debug::RegisterDebugInfo<$valtype> for $reg_desc { + // Sequence of field value enum types (implementing `TryFromValue`, + // produced above), generated by recursing over the fields: + type FieldValueEnumTypes = $crate::register_bitmasks!( + @fv_enum_type_seq $valtype, $($field::Value),* + ); + + fn name() -> &'static str { + stringify!($reg_mod) + } + + fn field_names() -> &'static [&'static str] { + &[ + $( + stringify!($field) + ),* + ] + } + + fn fields() -> &'static [Field<$valtype, Self>] { + &[ + $( + $field + ),* + ] + } + } + }; + + // Build the recursive `FieldValueEnumSeq` type sequence. This will generate + // a type signature of the form: + // + // ``` + // FieldValueEnumCons + // > + // > + // ``` + ( + @fv_enum_type_seq $valtype:ident, $enum_val:path $(, $($rest:path),+)? + ) => { + $crate::debug::FieldValueEnumCons< + $valtype, + $enum_val, + $crate::register_bitmasks!(@fv_enum_type_seq $valtype $(, $($rest),*)*) + > + }; + ( + @fv_enum_type_seq $valtype:ident $(,)? + ) => { + $crate::debug::FieldValueEnumNil + }; } /// Define register types and fields. @@ -446,13 +575,12 @@ macro_rules! register_bitfields { use $crate::fields::Field; - $crate::register_bitmasks!( $valtype, Register, $fields ); + $crate::register_bitmasks!( $valtype, $reg, Register, $fields ); } )* } } -#[cfg(feature = "std_unit_tests")] #[cfg(test)] mod tests { #[derive(Debug, PartialEq, Eq)] @@ -627,22 +755,20 @@ mod tests { } #[test] - fn test_matches_any() { + fn test_any_matching_bits_set() { let field = Field::::new(0xFF, 4); - assert_eq!(field.val(0x23).matches_any(0x1234), true); - assert_eq!(field.val(0x23).matches_any(0x5678), true); - assert_eq!(field.val(0x23).matches_any(0x5008), false); + assert_eq!(field.val(0x23).any_matching_bits_set(0x1234), true); + assert_eq!(field.val(0x23).any_matching_bits_set(0x5678), true); + assert_eq!(field.val(0x23).any_matching_bits_set(0x5008), false); for shift in 0..24 { let field = Field::::new(0xFF, shift); - for x in 0..=0xFF { - let field_value = field.val(x); - for y in 1..=0xFF { - assert_eq!(field_value.matches_any(y << shift), true); - } - assert_eq!(field_value.matches_any(0), false); - assert_eq!(field_value.matches_any(!(0xFF << shift)), false); + let field_value = field.val(0xff); + for y in 1..=0xff { + assert_eq!(field_value.any_matching_bits_set(y << shift), true,); } + assert_eq!(field_value.any_matching_bits_set(0), false); + assert_eq!(field_value.any_matching_bits_set(!(0xFF << shift)), false); } } @@ -661,6 +787,35 @@ mod tests { } } + #[test] + fn test_matches_any() { + register_bitfields! { + u32, + + TEST [ + FLAG OFFSET(18) NUMBITS(1) [], + SIZE OFFSET(0) NUMBITS(2) [ + Byte = 0, + Halfword = 1, + Word = 2 + ], + ] + } + + let value: crate::LocalRegisterCopy = + crate::LocalRegisterCopy::new(2); + assert!(value.matches_any(&[TEST::SIZE::Word])); + assert!(!value.matches_any(&[TEST::SIZE::Halfword])); + assert!(!value.matches_any(&[TEST::SIZE::Byte])); + assert!(value.matches_any(&[TEST::SIZE::Word, TEST::FLAG::SET])); + assert!(value.matches_any(&[TEST::SIZE::Halfword, TEST::FLAG::CLEAR])); + assert!(!value.matches_any(&[TEST::SIZE::Halfword, TEST::FLAG::SET])); + let value: crate::LocalRegisterCopy = + crate::LocalRegisterCopy::new(266241); + assert!(value.matches_any(&[TEST::FLAG::SET])); + assert!(!value.matches_any(&[TEST::FLAG::CLEAR])); + } + #[test] fn test_add_disjoint_fields() { let field1 = Field::::new(0xFF, 24); diff --git a/libraries/tock-register-interface/src/interfaces.rs b/libraries/tock-register-interface/src/interfaces.rs index b5f08c0db6..e13253bb77 100644 --- a/libraries/tock-register-interface/src/interfaces.rs +++ b/libraries/tock-register-interface/src/interfaces.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Interfaces (traits) to register types //! //! This module contains traits which reflect standardized interfaces @@ -7,14 +11,14 @@ //! //! Each trait has two associated type parameters, namely: //! -//! - `T`: [`UIntLike`](crate::UIntLike), indicating the underlying +//! - `T`: [`UIntLike`], indicating the underlying //! integer type used to represent the register's raw contents. //! -//! - `R`: [`RegisterLongName`](crate::RegisterLongName), functioning +//! - `R`: [`RegisterLongName`], functioning //! as a type to identify this register's descriptive name and //! semantic meaning. It is further used to impose type constraints //! on values passed through the API, such as -//! [`FieldValue`](crate::fields::FieldValue). +//! [`FieldValue`]. //! //! Registers can have different access levels, which are mapped to //! different traits respectively: @@ -35,7 +39,7 @@ //! writing and receive when reading. //! //! If a type implements both [`Readable`] and [`Writeable`], and -//! the associated [`RegisterLongName`](crate::RegisterLongName) +//! the associated [`RegisterLongName`] //! type parameters are identical, it will automatically implement //! [`ReadWriteable`]. In particular, for //! [`Aliased`](crate::registers::Aliased) this is -- in general -- @@ -50,8 +54,9 @@ //! DUMMY OFFSET(0) NUMBITS(1) [], //! ], //! ]; +//! let mut register_memory: u8 = 0; //! let read_write_reg: &ReadWrite = unsafe { -//! core::mem::transmute(Box::leak(Box::new(0_u8))) +//! core::mem::transmute(&mut register_memory) //! }; //! ReadWriteable::modify(read_write_reg, A::DUMMY::SET); //! ``` @@ -76,6 +81,15 @@ //! ReadWriteable::modify(aliased_reg, A::DUMMY::SET); //! ``` //! +//! - [`Debuggable`]: indicates that the register supports producing +//! human-readable debug output using the `RegisterDebugValue` type. +//! This type can be produced with the +//! [`debug`](crate::interfaces::Debuggable::debug) method. This +//! will return a value that implements [`Debug`](core::fmt::Debug). +//! It is automticaly implemented for any register implementing +//! [`Readable`]. +//! +//! //! ## Example: implementing a custom register type //! //! These traits can be used to implement custom register types, which @@ -173,8 +187,44 @@ pub trait Readable { field.read(self.get()) } - #[inline] /// Set the raw register value + /// + /// The [`register_bitfields!`](crate::register_bitfields) macro will + /// generate an enum containing the various named field variants and + /// implementing the required [`TryFromValue`] trait. It is accessible as + /// `$REGISTER_NAME::$FIELD_NAME::Value`. + /// + /// This method can be useful to symbolically represent read register field + /// states throughout the codebase and to enforce exhaustive matches over + /// all defined valid register field values. + /// + /// ## Usage Example + /// + /// ```rust + /// # use tock_registers::interfaces::Readable; + /// # use tock_registers::registers::InMemoryRegister; + /// # use tock_registers::register_bitfields; + /// register_bitfields![u8, + /// EXAMPLEREG [ + /// TESTFIELD OFFSET(0) NUMBITS(2) [ + /// Foo = 0, + /// Bar = 1, + /// Baz = 2, + /// ], + /// ], + /// ]; + /// + /// let reg: InMemoryRegister = + /// InMemoryRegister::new(2); + /// + /// match reg.read_as_enum(EXAMPLEREG::TESTFIELD) { + /// Some(EXAMPLEREG::TESTFIELD::Value::Foo) => "Tock", + /// Some(EXAMPLEREG::TESTFIELD::Value::Bar) => "is", + /// Some(EXAMPLEREG::TESTFIELD::Value::Baz) => "awesome!", + /// None => panic!("boo!"), + /// }; + /// ``` + #[inline] fn read_as_enum>( &self, field: Field, @@ -194,10 +244,13 @@ pub trait Readable { field.is_set(self.get()) } + /// Check if any bits corresponding to the mask in the passed `FieldValue` are set. + /// This function is identical to `is_set()` but operates on a `FieldValue` rather + /// than a `Field`, allowing for checking if any bits are set across multiple, + /// non-contiguous portions of a bitfield. #[inline] - /// Check if any specified parts of a field match - fn matches_any(&self, field: FieldValue) -> bool { - field.matches_any(self.get()) + fn any_matching_bits_set(&self, field: FieldValue) -> bool { + field.any_matching_bits_set(self.get()) } #[inline] @@ -205,8 +258,46 @@ pub trait Readable { fn matches_all(&self, field: FieldValue) -> bool { field.matches_all(self.get()) } + + /// Check if any of the passed parts of a field exactly match the contained + /// value. This allows for matching on unset bits, or matching on specific values + /// in multi-bit fields. + #[inline] + fn matches_any(&self, fields: &[FieldValue]) -> bool { + fields + .iter() + .any(|field| self.get() & field.mask() == field.value) + } } +/// [`Debuggable`] is a trait for registers that support human-readable debug +/// output with [`core::fmt::Debug`]. It extends the [`Readable`] trait and +/// doesn't require manual implementation. +/// +/// This is implemented for the register when using the [`register_bitfields`] macro. +/// +/// The `debug` method returns a value that implements [`core::fmt::Debug`]. +/// +/// [`register_bitfields`]: crate::register_bitfields +pub trait Debuggable: Readable { + /// Returns a [`RegisterDebugValue`](crate::debug::RegisterDebugValue) that + /// implements [`core::fmt::Debug`], the debug information is extracted from + /// `::DebugInfo`. + #[inline] + fn debug(&self) -> crate::debug::RegisterDebugValue + where + Self::R: crate::debug::RegisterDebugInfo, + { + crate::debug::RegisterDebugValue { + data: self.get(), + _reg: core::marker::PhantomData, + } + } +} + +// pass Readable implementation to Debuggable +impl Debuggable for T {} + /// Writeable register /// /// Register which at least supports setting a value. Only diff --git a/libraries/tock-register-interface/src/lib.rs b/libraries/tock-register-interface/src/lib.rs index 0ce1a1eadb..2bd5e2cb6e 100644 --- a/libraries/tock-register-interface/src/lib.rs +++ b/libraries/tock-register-interface/src/lib.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tock Register Interface //! //! Provides efficient mechanisms to express and use type-checked @@ -53,7 +57,6 @@ //! ------ //! - Shane Leonard -#![feature(const_fn_trait_bound)] #![no_std] // If we don't build any actual register types, we don't need unsafe // code in this crate @@ -66,9 +69,12 @@ pub mod macros; #[cfg(feature = "register_types")] pub mod registers; +pub mod debug; + mod local_register; pub use local_register::LocalRegisterCopy; +use core::fmt::Debug; use core::ops::{BitAnd, BitOr, BitOrAssign, Not, Shl, Shr}; /// Trait representing the base type of registers. @@ -90,6 +96,7 @@ pub trait UIntLike: + Shl + Copy + Clone + + Debug { /// Return the representation of the value `0` in the implementing /// type. diff --git a/libraries/tock-register-interface/src/local_register.rs b/libraries/tock-register-interface/src/local_register.rs index 15bf7d4ebb..e7f6bfd5f5 100644 --- a/libraries/tock-register-interface/src/local_register.rs +++ b/libraries/tock-register-interface/src/local_register.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Module containing the [`LocalRegisterCopy`] type. Please refer to //! its documentation. @@ -36,7 +40,7 @@ pub struct LocalRegisterCopy { impl LocalRegisterCopy { pub const fn new(value: T) -> Self { LocalRegisterCopy { - value: value, + value, associated_register: PhantomData, } } @@ -83,10 +87,10 @@ impl LocalRegisterCopy { field.is_set(self.get()) } - /// Check if any specified parts of a field match + /// Check if any bits corresponding to the mask in the passed `FieldValue` are set. #[inline] - pub fn matches_any(&self, field: FieldValue) -> bool { - field.matches_any(self.get()) + pub fn any_matching_bits_set(&self, field: FieldValue) -> bool { + field.any_matching_bits_set(self.get()) } /// Check if all specified parts of a field match @@ -95,12 +99,33 @@ impl LocalRegisterCopy { field.matches_all(self.get()) } + /// Check if any of the passed parts of a field exactly match the contained + /// value. This allows for matching on unset bits, or matching on specific values + /// in multi-bit fields. + #[inline] + pub fn matches_any(&self, fields: &[FieldValue]) -> bool { + fields + .iter() + .any(|field| self.get() & field.mask() == field.value) + } + /// Do a bitwise AND operation of the stored value and the passed in value /// and return a new LocalRegisterCopy. #[inline] pub fn bitand(&self, rhs: T) -> LocalRegisterCopy { LocalRegisterCopy::new(self.value & rhs) } + + #[inline] + pub fn debug(&self) -> crate::debug::RegisterDebugValue + where + R: crate::debug::RegisterDebugInfo, + { + crate::debug::RegisterDebugValue { + data: self.get(), + _reg: core::marker::PhantomData, + } + } } impl fmt::Debug for LocalRegisterCopy { diff --git a/libraries/tock-register-interface/src/macros.rs b/libraries/tock-register-interface/src/macros.rs index b2fc941a6d..f65d8b78cc 100644 --- a/libraries/tock-register-interface/src/macros.rs +++ b/libraries/tock-register-interface/src/macros.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Macros for cleanly defining peripheral registers. #[macro_export] @@ -79,44 +83,152 @@ macro_rules! register_fields { }; } +// TODO: All of the rustdoc tests below use a `should_fail` attribute instead of +// `should_panic` because a const panic will result in a failure to evaluate a +// constant value, and thus a compiler error. However, this means that these +// examples could break for unrelated reasons, trigger a compiler error, but not +// test the desired assertion any longer. This should be switched to a +// `should_panic`-akin attribute which works for const panics, once that is +// available. +/// Statically validate the size and offsets of the fields defined +/// within the register struct through the `register_structs!()` +/// macro. +/// +/// This macro expands to an expression which contains static +/// assertions about various parameters of the individual fields in +/// the register struct definition. It will test for: +/// +/// - Proper start offset of padding fields. It will fail in cases +/// such as +/// +/// ```should_fail +/// # #[macro_use] +/// # extern crate tock_registers; +/// # use tock_registers::register_structs; +/// # use tock_registers::registers::ReadWrite; +/// register_structs! { +/// UartRegisters { +/// (0x04 => _reserved), +/// (0x08 => foo: ReadWrite), +/// (0x0C => @END), +/// } +/// } +/// # // This is required for rustdoc to not place this code snipped into an +/// # // fn main() {...} function. +/// # fn main() { } +/// ``` +/// +/// In this example, the start offset of `_reserved` should have been `0x00` +/// instead of `0x04`. +/// +/// - Correct start offset and end offset (start offset of next field) in actual +/// fields. It will fail in cases such as +/// +/// ```should_fail +/// # #[macro_use] +/// # extern crate tock_registers; +/// # use tock_registers::register_structs; +/// # use tock_registers::registers::ReadWrite; +/// register_structs! { +/// UartRegisters { +/// (0x00 => foo: ReadWrite), +/// (0x05 => bar: ReadWrite), +/// (0x08 => @END), +/// } +/// } +/// # // This is required for rustdoc to not place this code snipped into an +/// # // fn main() {...} function. +/// # fn main() { } +/// ``` +/// +/// In this example, the start offset of `bar` and thus the end offset of +/// `foo` should have been `0x04` instead of `0x05`. +/// +/// - Invalid alignment of fields. +/// +/// - That the end marker matches the actual generated struct size. This will +/// fail in cases such as +/// +/// ```should_fail +/// # #[macro_use] +/// # extern crate tock_registers; +/// # use tock_registers::register_structs; +/// # use tock_registers::registers::ReadWrite; +/// register_structs! { +/// UartRegisters { +/// (0x00 => foo: ReadWrite), +/// (0x04 => bar: ReadWrite), +/// (0x10 => @END), +/// } +/// } +/// # // This is required for rustdoc to not place this code snipped into an +/// # // fn main() {...} function. +/// # fn main() { } +/// ``` #[macro_export] macro_rules! test_fields { + // This macro works by iterating over all defined fields, until it hits an + // ($size:expr => @END) field. Each iteration generates an expression which, + // when evaluated, yields the current byte offset in the fields. Thus, when + // reading a field or padding, the field or padding length must be added to + // the returned size. + // + // By feeding this expression recursively into the macro, deeper invocations + // can continue validating fields through knowledge of the current offset + // and the remaining fields. + // + // The nested expression returned by this macro is guaranteed to be + // const-evaluable. + // Macro entry point. (@root $struct:ident $(<$life:lifetime>)? { $($input:tt)* } ) => { - $crate::test_fields!(@munch $struct $(<$life>)? sum ($($input)*) -> {}); + // Start recursion at offset 0. + $crate::test_fields!(@munch $struct $(<$life>)? ($($input)*) : (0, 0)); }; - // Print the tests once all fields have been munched. - // We wrap the tests in a "detail" function that potentially takes a lifetime parameter, so that - // the lifetime is declared inside it - therefore all types using the lifetime are well-defined. - (@munch $struct:ident $(<$life:lifetime>)? $sum:ident + // Consume the ($size:expr => @END) field, which MUST be the last field in + // the register struct. + (@munch $struct:ident $(<$life:lifetime>)? ( $(#[$attr_end:meta])* ($size:expr => @END), ) - -> {$($stmts:block)*} + : $stmts:expr ) => { - { - fn detail $(<$life>)? () - { - let mut $sum: usize = 0; - $($stmts)* - let size = core::mem::size_of::<$struct $(<$life>)?>(); + const _: () = { + // We've reached the end! Normally it is sufficient to compare the + // struct's size to the reported end offet. However, we must + // evaluate the previous iterations' expressions for them to have an + // effect anyways, so we can perform an internal sanity check on + // this value as well. + const SUM_MAX_ALIGN: (usize, usize) = $stmts; + const SUM: usize = SUM_MAX_ALIGN.0; + const MAX_ALIGN: usize = SUM_MAX_ALIGN.1; + + // Internal sanity check. If we have reached this point and + // correctly iterated over the struct's fields, the current offset + // and the claimed end offset MUST be equal. + assert!(SUM == $size); + + const STRUCT_SIZE: usize = core::mem::size_of::<$struct $(<$life>)?>(); + const ALIGNMENT_CORRECTED_SIZE: usize = if $size % MAX_ALIGN != 0 { $size + (MAX_ALIGN - ($size % MAX_ALIGN)) } else { $size }; + assert!( - size == $size, - "Invalid size for struct {} (expected {:#X} but was {:#X})", - stringify!($struct), - $size, - size + STRUCT_SIZE == ALIGNMENT_CORRECTED_SIZE, + "{}", + concat!( + "Invalid size for struct ", + stringify!($struct), + " (expected ", + stringify!($size), + ", actual struct size differs)", + ), ); - } - - detail(); - } + }; }; - // Munch field. - (@munch $struct:ident $(<$life:lifetime>)? $sum:ident + // Consume a proper ($offset:expr => $field:ident: $ty:ty) field. + (@munch $struct:ident $(<$life:lifetime>)? ( $(#[$attr:meta])* ($offset_start:expr => $vis:vis $field:ident: $ty:ty), @@ -124,46 +236,84 @@ macro_rules! test_fields { ($offset_end:expr => $($next:tt)*), $($after:tt)* ) - -> {$($output:block)*} + : $output:expr ) => { $crate::test_fields!( - @munch $struct $(<$life>)? $sum ( + @munch $struct $(<$life>)? ( $(#[$attr_next])* ($offset_end => $($next)*), $($after)* - ) -> { - $($output)* - { - assert!( - $sum == $offset_start, - "Invalid start offset for field {} (expected {:#X} but was {:#X})", - stringify!($field), - $offset_start, - $sum - ); - let align = core::mem::align_of::<$ty>(); - assert!( - $sum & (align - 1) == 0, - "Invalid alignment for field {} (expected alignment of {:#X} but offset was {:#X})", + ) : { + // Evaluate the previous iterations' expression to determine the + // current offset. + const SUM_MAX_ALIGN: (usize, usize) = $output; + const SUM: usize = SUM_MAX_ALIGN.0; + const MAX_ALIGN: usize = SUM_MAX_ALIGN.1; + + // Validate the start offset of the current field. This check is + // mostly relevant for when this is the first field in the + // struct, as any subsequent start offset error will be detected + // by an end offset error of the previous field. + assert!( + SUM == $offset_start, + "{}", + concat!( + "Invalid start offset for field ", stringify!($field), - align, - $sum - ); - $sum += core::mem::size_of::<$ty>(); + " (expected ", + stringify!($offset_start), + " but actual value differs)", + ), + ); + + // Validate that the start offset of the current field within + // the struct matches the type's minimum alignment constraint. + const ALIGN: usize = core::mem::align_of::<$ty>(); + // Clippy can tell that (align - 1) is zero for some fields, so + // we allow this lint and further encapsule the assert! as an + // expression, such that the allow attr can apply. + #[allow(clippy::bad_bit_mask)] + { assert!( - $sum == $offset_end, - "Invalid end offset for field {} (expected {:#X} but was {:#X})", - stringify!($field), - $offset_end, - $sum + SUM & (ALIGN - 1) == 0, + "{}", + concat!( + "Invalid alignment for field ", + stringify!($field), + " (offset differs from expected)", + ), ); } + + // Add the current field's length to the offset and validate the + // end offset of the field based on the next field's claimed + // start offset. + const NEW_SUM: usize = SUM + core::mem::size_of::<$ty>(); + assert!( + NEW_SUM == $offset_end, + "{}", + concat!( + "Invalid end offset for field ", + stringify!($field), + " (expected ", + stringify!($offset_end), + " but actual value differs)", + ), + ); + + // Determine the new maximum alignment. core::cmp::max(ALIGN, + // MAX_ALIGN) does not work here, as the function is not const. + const NEW_MAX_ALIGN: usize = if ALIGN > MAX_ALIGN { ALIGN } else { MAX_ALIGN }; + + // Provide the updated offset and alignment to the next + // iteration. + (NEW_SUM, NEW_MAX_ALIGN) } ); }; - // Munch padding. - (@munch $struct:ident $(<$life:lifetime>)? $sum:ident + // Consume a padding ($offset:expr => $padding:ident) field. + (@munch $struct:ident $(<$life:lifetime>)? ( $(#[$attr:meta])* ($offset_start:expr => $padding:ident), @@ -171,60 +321,43 @@ macro_rules! test_fields { ($offset_end:expr => $($next:tt)*), $($after:tt)* ) - -> {$($output:block)*} + : $output:expr ) => { $crate::test_fields!( - @munch $struct $(<$life>)? $sum ( + @munch $struct $(<$life>)? ( $(#[$attr_next])* ($offset_end => $($next)*), $($after)* - ) -> { - $($output)* - { - assert!( - $sum == $offset_start, - "Invalid start offset for padding {} (expected {:#X} but was {:#X})", - stringify!($padding), - $offset_start, - $sum - ); - $sum = $offset_end; - } - } - ); - }; -} + ) : { + // Evaluate the previous iterations' expression to determine the + // current offset. + const SUM_MAX_ALIGN: (usize, usize) = $output; + const SUM: usize = SUM_MAX_ALIGN.0; + const MAX_ALIGN: usize = SUM_MAX_ALIGN.1; -#[cfg(feature = "std_unit_tests")] -#[macro_export] -macro_rules! register_structs { - { - $( - $(#[$attr:meta])* - $vis_struct:vis $name:ident $(<$life:lifetime>)? { - $( $fields:tt )* - } - ),* - } => { - $( $crate::register_fields!(@root $(#[$attr])* $vis_struct $name $(<$life>)? { $($fields)* } ); )* + // Validate the start offset of the current padding field. This + // check is mostly relevant for when this is the first field in + // the struct, as any subsequent start offset error will be + // detected by an end offset error of the previous field. + assert!( + SUM == $offset_start, + concat!( + "Invalid start offset for padding ", + stringify!($padding), + " (expected ", + stringify!($offset_start), + " but actual value differs)", + ), + ); - #[cfg(test)] - mod test_register_structs { - $( - #[allow(non_snake_case)] - mod $name { - use super::super::*; - #[test] - fn test_offsets() { - $crate::test_fields!(@root $name $(<$life>)? { $($fields)* } ) - } + // The padding field is automatically sized. Provide the start + // offset of the next field to the next iteration. + ($offset_end, MAX_ALIGN) } - )* - } + ); }; } -#[cfg(not(feature = "std_unit_tests"))] #[macro_export] macro_rules! register_structs { { @@ -236,5 +369,6 @@ macro_rules! register_structs { ),* } => { $( $crate::register_fields!(@root $(#[$attr])* $vis_struct $name $(<$life>)? { $($fields)* } ); )* + $( $crate::test_fields!(@root $name $(<$life>)? { $($fields)* } ); )* }; } diff --git a/libraries/tock-register-interface/src/registers.rs b/libraries/tock-register-interface/src/registers.rs index c11a979f0a..b72983102f 100644 --- a/libraries/tock-register-interface/src/registers.rs +++ b/libraries/tock-register-interface/src/registers.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Implementation of included register types. //! //! This module provides a standard set of register types, which can @@ -29,7 +33,15 @@ use crate::{RegisterLongName, UIntLike}; /// [`ReadWriteable`](crate::interfaces::ReadWriteable) traits are /// implemented. // To successfully alias this structure onto hardware registers in memory, this -// struct must be exactly the size of the `T`. +// struct must be exactly the size of the `T` and is thus marked +// `repr(transparent)` over an `UnsafeCell`, which itself is +// `repr(transparent)` over `T`. +// +// This struct is constructed by casting a pointer to it (or, implicitly, by +// casting a pointer to a larger struct that containts this type). As such, it +// does not have a public constructor and Rust thinks it's dead code and should +// be removed. We `allow(dead_code)` here to suppress this warning. +#[allow(dead_code)] #[repr(transparent)] pub struct ReadWrite { value: UnsafeCell, @@ -59,7 +71,15 @@ impl Writeable for ReadWrite { /// For accessing the register contents the [`Readable`] trait is /// implemented. // To successfully alias this structure onto hardware registers in memory, this -// struct must be exactly the size of the `T`. +// struct must be exactly the size of the `T` and is thus marked +// `repr(transparent)` over an `UnsafeCell`, which itself is +// `repr(transparent)` over `T`. +// +// This struct is constructed by casting a pointer to it (or, implicitly, by +// casting a pointer to a larger struct that containts this type). As such, it +// does not have a public constructor and Rust thinks it's dead code and should +// be removed. We `allow(dead_code)` here to suppress this warning. +#[allow(dead_code)] #[repr(transparent)] pub struct ReadOnly { value: T, @@ -80,7 +100,15 @@ impl Readable for ReadOnly { /// For setting the register contents the [`Writeable`] trait is /// implemented. // To successfully alias this structure onto hardware registers in memory, this -// struct must be exactly the size of the `T`. +// struct must be exactly the size of the `T` and is thus marked +// `repr(transparent)` over an `UnsafeCell`, which itself is +// `repr(transparent)` over `T`. +// +// This struct is constructed by casting a pointer to it (or, implicitly, by +// casting a pointer to a larger struct that containts this type). As such, it +// does not have a public constructor and Rust thinks it's dead code and should +// be removed. We `allow(dead_code)` here to suppress this warning. +#[allow(dead_code)] #[repr(transparent)] pub struct WriteOnly { value: UnsafeCell, @@ -110,7 +138,15 @@ impl Writeable for WriteOnly { /// type parameters `R` and `W` are identical, in which case a /// [`ReadWrite`] register might be a better choice). // To successfully alias this structure onto hardware registers in memory, this -// struct must be exactly the size of the `T`. +// struct must be exactly the size of the `T` and is thus marked +// `repr(transparent)` over an `UnsafeCell`, which itself is +// `repr(transparent)` over `T`. +// +// This struct is constructed by casting a pointer to it (or, implicitly, by +// casting a pointer to a larger struct that containts this type). As such, it +// does not have a public constructor and Rust thinks it's dead code and should +// be removed. We `allow(dead_code)` here to suppress this warning. +#[allow(dead_code)] #[repr(transparent)] pub struct Aliased { value: UnsafeCell, diff --git a/libraries/tock-tbf/Cargo.toml b/libraries/tock-tbf/Cargo.toml index a755eb8416..fa7e69855c 100644 --- a/libraries/tock-tbf/Cargo.toml +++ b/libraries/tock-tbf/Cargo.toml @@ -1,5 +1,12 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "tock-tbf" version = "0.1.0" authors = ["Tock Project Developers "] edition = "2021" + +[lints] +workspace = true diff --git a/libraries/tock-tbf/src/lib.rs b/libraries/tock-tbf/src/lib.rs index c32b572736..6eaa08c23b 100644 --- a/libraries/tock-tbf/src/lib.rs +++ b/libraries/tock-tbf/src/lib.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tock Binary Format (TBF) header parsing library. // Parsing the headers does not require any unsafe operations. diff --git a/libraries/tock-tbf/src/parse.rs b/libraries/tock-tbf/src/parse.rs index 843ac69484..a20a53f31e 100644 --- a/libraries/tock-tbf/src/parse.rs +++ b/libraries/tock-tbf/src/parse.rs @@ -1,18 +1,13 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Tock Binary Format parsing code. -use core::convert::TryInto; -use core::iter::Iterator; use core::{mem, str}; use crate::types; -/// Takes a value and rounds it up to be aligned % 4 -macro_rules! align4 { - ($e:expr $(,)?) => { - ($e) + ((4 - (($e) % 4)) % 4) - }; -} - /// Parse the TBF header length and the entire length of the TBF binary. /// /// ## Return @@ -29,7 +24,7 @@ macro_rules! align4 { /// we can skip over it and check for the next app. /// - Err(InitialTbfParseError::InvalidHeader(app_length)) pub fn parse_tbf_header_lengths( - app: &'static [u8; 8], + app: &[u8; 8], ) -> Result<(u16, u16, u32), types::InitialTbfParseError> { // Version is the first 16 bits of the app TBF contents. We need this to // correctly parse the other lengths. @@ -126,19 +121,23 @@ pub fn parse_tbf_header( // Places to save fields that we parse out of the header // options. let mut main_pointer: Option = None; - let mut wfr_pointer: [Option; 4] = - Default::default(); + let mut program_pointer: Option = None; + let mut wfr_pointer: Option<&'static [u8]> = None; let mut app_name_str = ""; - let mut fixed_address_pointer: Option = None; - let mut permissions_pointer: Option> = None; - let mut persistent_acls_pointer: Option> = None; + let mut fixed_address_pointer: Option<&'static [u8]> = None; + let mut permissions_pointer: Option<&'static [u8]> = None; + let mut storage_permissions_pointer: Option<&'static [u8]> = None; let mut kernel_version: Option = None; + let mut short_id: Option = None; // Iterate the remainder of the header looking for TLV entries. while remaining.len() > 0 { // Get the T and L portions of the next header (if it is // there). - let tlv_header: types::TbfHeaderTlv = remaining.try_into()?; + let tlv_header: types::TbfTlv = remaining + .get(0..4) + .ok_or(types::TbfParseError::NotEnoughFlash)? + .try_into()?; remaining = remaining .get(4..) .ok_or(types::TbfParseError::NotEnoughFlash)?; @@ -146,52 +145,52 @@ pub fn parse_tbf_header( match tlv_header.tipe { types::TbfHeaderTypes::TbfHeaderMain => { let entry_len = mem::size_of::(); - - // Check that the size of the TLV entry matches the - // size of the Main TLV. If so we can store it. - // Otherwise, we fail to parse this TBF header and - // throw an error. - if tlv_header.length as usize == entry_len { - main_pointer = Some(remaining.try_into()?); - } else { - return Err(types::TbfParseError::BadTlvEntry( - tlv_header.tipe as usize, - )); + // If there is already a header do nothing: if this is a second Main + // keep the first one, if it's a Program we ignore the Main + if main_pointer.is_none() { + if tlv_header.length as usize == entry_len { + main_pointer = Some( + remaining + .get(0..entry_len) + .ok_or(types::TbfParseError::NotEnoughFlash)? + .try_into()?, + ); + } else { + return Err(types::TbfParseError::BadTlvEntry( + tlv_header.tipe as usize, + )); + } + } + } + types::TbfHeaderTypes::TbfHeaderProgram => { + let entry_len = mem::size_of::(); + if program_pointer.is_none() { + if tlv_header.length as usize == entry_len { + program_pointer = Some( + remaining + .get(0..entry_len) + .ok_or(types::TbfParseError::NotEnoughFlash)? + .try_into()?, + ); + } else { + return Err(types::TbfParseError::BadTlvEntry( + tlv_header.tipe as usize, + )); + } } } - types::TbfHeaderTypes::TbfHeaderWriteableFlashRegions => { // Length must be a multiple of the size of a region definition. if tlv_header.length as usize % mem::size_of::() == 0 { - // Calculate how many writeable flash regions - // there are specified in this header. - let wfr_len = - mem::size_of::(); - let mut number_regions = tlv_header.length as usize / wfr_len; - // Capture a slice with just the wfr information. let wfr_slice = remaining .get(0..tlv_header.length as usize) .ok_or(types::TbfParseError::NotEnoughFlash)?; - // To enable a static buffer, we only support up - // to four writeable flash regions. - if number_regions > 4 { - number_regions = 4; - } - - // Convert and store each wfr. - for i in 0..number_regions { - wfr_pointer[i] = Some( - wfr_slice - .get(i * wfr_len..(i + 1) * wfr_len) - .ok_or(types::TbfParseError::NotEnoughFlash)? - .try_into()?, - ); - } + wfr_pointer = Some(wfr_slice); } else { return Err(types::TbfParseError::BadTlvEntry( tlv_header.tipe as usize, @@ -212,9 +211,13 @@ pub fn parse_tbf_header( } types::TbfHeaderTypes::TbfHeaderFixedAddresses => { - let entry_len = 8; + let entry_len = mem::size_of::(); if tlv_header.length as usize == entry_len { - fixed_address_pointer = Some(remaining.try_into()?); + fixed_address_pointer = Some( + remaining + .get(0..entry_len) + .ok_or(types::TbfParseError::NotEnoughFlash)?, + ); } else { return Err(types::TbfParseError::BadTlvEntry( tlv_header.tipe as usize, @@ -223,17 +226,46 @@ pub fn parse_tbf_header( } types::TbfHeaderTypes::TbfHeaderPermissions => { - permissions_pointer = Some(remaining.try_into()?); + permissions_pointer = Some( + remaining + .get(0..tlv_header.length as usize) + .ok_or(types::TbfParseError::NotEnoughFlash)?, + ); } - types::TbfHeaderTypes::TbfHeaderPersistentAcl => { - persistent_acls_pointer = Some(remaining.try_into()?); + types::TbfHeaderTypes::TbfHeaderStoragePermissions => { + storage_permissions_pointer = Some( + remaining + .get(0..tlv_header.length as usize) + .ok_or(types::TbfParseError::NotEnoughFlash)?, + ); } types::TbfHeaderTypes::TbfHeaderKernelVersion => { - let entry_len = 4; + let entry_len = mem::size_of::(); + if tlv_header.length as usize == entry_len { + kernel_version = Some( + remaining + .get(0..entry_len) + .ok_or(types::TbfParseError::NotEnoughFlash)? + .try_into()?, + ); + } else { + return Err(types::TbfParseError::BadTlvEntry( + tlv_header.tipe as usize, + )); + } + } + + types::TbfHeaderTypes::TbfHeaderShortId => { + let entry_len = mem::size_of::(); if tlv_header.length as usize == entry_len { - kernel_version = Some(remaining.try_into()?); + short_id = Some( + remaining + .get(0..entry_len) + .ok_or(types::TbfParseError::NotEnoughFlash)? + .try_into()?, + ); } else { return Err(types::TbfParseError::BadTlvEntry( tlv_header.tipe as usize, @@ -246,7 +278,9 @@ pub fn parse_tbf_header( // All TLV blocks are padded to 4 bytes, so we need to skip // more if the length is not a multiple of 4. - let skip_len: usize = align4!(tlv_header.length as usize); + let skip_len: usize = (tlv_header.length as usize) + .checked_next_multiple_of(4) + .ok_or(types::TbfParseError::InternalError)?; remaining = remaining .get(skip_len..) .ok_or(types::TbfParseError::NotEnoughFlash)?; @@ -255,12 +289,14 @@ pub fn parse_tbf_header( let tbf_header = types::TbfHeaderV2 { base: tbf_header_base, main: main_pointer, + program: program_pointer, package_name: Some(app_name_str), - writeable_regions: Some(wfr_pointer), + writeable_regions: wfr_pointer, fixed_addresses: fixed_address_pointer, permissions: permissions_pointer, - persistent_acls: persistent_acls_pointer, - kernel_version: kernel_version, + storage_permissions: storage_permissions_pointer, + kernel_version, + short_id, }; Ok(types::TbfHeader::TbfHeaderV2(tbf_header)) @@ -269,3 +305,28 @@ pub fn parse_tbf_header( _ => Err(types::TbfParseError::UnsupportedVersion(version)), } } + +pub fn parse_tbf_footer( + footers: &'static [u8], +) -> Result<(types::TbfFooterV2Credentials, u32), types::TbfParseError> { + let mut remaining = footers; + let tlv_header: types::TbfTlv = remaining + .get(0..4) + .ok_or(types::TbfParseError::NotEnoughFlash)? + .try_into()?; + remaining = remaining + .get(4..) + .ok_or(types::TbfParseError::NotEnoughFlash)?; + match tlv_header.tipe { + types::TbfHeaderTypes::TbfFooterCredentials => { + let credential: types::TbfFooterV2Credentials = remaining + .get(0..tlv_header.length as usize) + .ok_or(types::TbfParseError::NotEnoughFlash)? + .try_into()?; + // Check length here + let length = tlv_header.length; + Ok((credential, length as u32)) + } + _ => Err(types::TbfParseError::BadTlvEntry(tlv_header.tipe as usize)), + } +} diff --git a/libraries/tock-tbf/src/types.rs b/libraries/tock-tbf/src/types.rs index a60638e481..c12ac77937 100644 --- a/libraries/tock-tbf/src/types.rs +++ b/libraries/tock-tbf/src/types.rs @@ -1,9 +1,16 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! Types and Data Structures for TBFs. -use core::convert::TryInto; use core::fmt; use core::mem::size_of; +/// We only support up to a fixed number of storage permissions for each of read +/// and modify. This simplification enables us to use fixed sized buffers. +const NUM_STORAGE_PERMISSIONS: usize = 8; + /// Error when parsing just the beginning of the TBF header. This is only used /// when establishing the linked list structure of apps installed in flash. pub enum InitialTbfParseError { @@ -120,8 +127,11 @@ pub enum TbfHeaderTypes { TbfHeaderPackageName = 3, TbfHeaderFixedAddresses = 5, TbfHeaderPermissions = 6, - TbfHeaderPersistentAcl = 7, + TbfHeaderStoragePermissions = 7, TbfHeaderKernelVersion = 8, + TbfHeaderProgram = 9, + TbfHeaderShortId = 10, + TbfFooterCredentials = 128, /// Some field in the header that we do not understand. Since the TLV format /// specifies the length of each section, if we get a field we do not @@ -131,20 +141,41 @@ pub enum TbfHeaderTypes { /// The TLV header (T and L). #[derive(Clone, Copy, Debug)] -pub struct TbfHeaderTlv { +pub struct TbfTlv { pub(crate) tipe: TbfHeaderTypes, pub(crate) length: u16, } -/// The v2 main section for apps. +/// The v2 Main Header for apps. /// -/// All apps must have a main section. Without it, the header is considered as -/// only padding. +/// All apps must have either a Main Header or a Program Header. Without +/// either, the TBF object is considered padding. Main and Program Headers +/// differ in whether they specify the endpoint of the process binary; Main +/// Headers do not, while Program Headers do. A TBF with a Main Header cannot +/// have any Credentials Footers, while a TBF with a Program Header can. #[derive(Clone, Copy, Debug)] pub struct TbfHeaderV2Main { init_fn_offset: u32, - protected_size: u32, + protected_trailer_size: u32, + minimum_ram_size: u32, +} + +/// The v2 Program Header for apps. +/// +/// All apps must have either a Main Header or a Program Header. Without +/// either, the TBF object is considered padding. Main and Program Headers +/// differ in whether they specify the endpoint of the process binary; Main +/// Headers do not, while Program Headers do. A Program Header includes +/// the binary end offset so that a Verifier knows where Credentials Headers +/// start. The region between the end of the binary and the end of the TBF +/// is reserved for Credentials Footers. +#[derive(Clone, Copy, Debug)] +pub struct TbfHeaderV2Program { + init_fn_offset: u32, + protected_trailer_size: u32, minimum_ram_size: u32, + binary_end_offset: u32, + version: u32, } /// Writeable flash regions only need an offset and size. @@ -195,14 +226,14 @@ pub struct TbfHeaderV2Permissions { perms: [TbfHeaderDriverPermission; L], } -/// A list of persistent access permissions +/// A list of storage (read/write/modify) permissions for this app. #[derive(Clone, Copy, Debug)] -pub struct TbfHeaderV2PersistentAcl { - write_id: u32, +pub struct TbfHeaderV2StoragePermissions { + write_id: Option, read_length: u16, read_ids: [u32; L], - access_length: u16, - access_ids: [u32; L], + modify_length: u16, + modify_ids: [u32; L], } #[derive(Clone, Copy, Debug)] @@ -211,12 +242,49 @@ pub struct TbfHeaderV2KernelVersion { minor: u16, } +/// The v2 ShortId for apps. +/// +/// Header to specify a fixed ShortID for an app. +#[derive(Clone, Copy, Debug)] +pub struct TbfHeaderV2ShortId { + short_id: Option, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum TbfFooterV2CredentialsType { + Reserved = 0, + Rsa3072Key = 1, + Rsa4096Key = 2, + SHA256 = 3, + SHA384 = 4, + SHA512 = 5, +} + +#[derive(Clone, Copy, Debug)] +pub struct TbfFooterV2Credentials { + format: TbfFooterV2CredentialsType, + data: &'static [u8], +} + +impl TbfFooterV2Credentials { + pub fn format(&self) -> TbfFooterV2CredentialsType { + self.format + } + + pub fn data(&self) -> &'static [u8] { + self.data + } +} + // Conversion functions from slices to the various TBF fields. impl core::convert::TryFrom<&[u8]> for TbfHeaderV2Base { type Error = TbfParseError; fn try_from(b: &[u8]) -> Result { + if b.len() < 16 { + return Err(TbfParseError::InternalError); + } Ok(TbfHeaderV2Base { version: u16::from_le_bytes( b.get(0..2) @@ -257,18 +325,21 @@ impl core::convert::TryFrom for TbfHeaderTypes { 3 => Ok(TbfHeaderTypes::TbfHeaderPackageName), 5 => Ok(TbfHeaderTypes::TbfHeaderFixedAddresses), 6 => Ok(TbfHeaderTypes::TbfHeaderPermissions), - 7 => Ok(TbfHeaderTypes::TbfHeaderPersistentAcl), + 7 => Ok(TbfHeaderTypes::TbfHeaderStoragePermissions), 8 => Ok(TbfHeaderTypes::TbfHeaderKernelVersion), + 9 => Ok(TbfHeaderTypes::TbfHeaderProgram), + 10 => Ok(TbfHeaderTypes::TbfHeaderShortId), + 128 => Ok(TbfHeaderTypes::TbfFooterCredentials), _ => Ok(TbfHeaderTypes::Unknown), } } } -impl core::convert::TryFrom<&[u8]> for TbfHeaderTlv { +impl core::convert::TryFrom<&[u8]> for TbfTlv { type Error = TbfParseError; - fn try_from(b: &[u8]) -> Result { - Ok(TbfHeaderTlv { + fn try_from(b: &[u8]) -> Result { + Ok(TbfTlv { tipe: u16::from_le_bytes( b.get(0..2) .ok_or(TbfParseError::InternalError)? @@ -288,13 +359,17 @@ impl core::convert::TryFrom<&[u8]> for TbfHeaderV2Main { type Error = TbfParseError; fn try_from(b: &[u8]) -> Result { + // For 3 or more fields, this shortcut check reduces code size + if b.len() < 12 { + return Err(TbfParseError::InternalError); + } Ok(TbfHeaderV2Main { init_fn_offset: u32::from_le_bytes( b.get(0..4) .ok_or(TbfParseError::InternalError)? .try_into()?, ), - protected_size: u32::from_le_bytes( + protected_trailer_size: u32::from_le_bytes( b.get(4..8) .ok_or(TbfParseError::InternalError)? .try_into()?, @@ -308,6 +383,43 @@ impl core::convert::TryFrom<&[u8]> for TbfHeaderV2Main { } } +impl core::convert::TryFrom<&[u8]> for TbfHeaderV2Program { + type Error = TbfParseError; + fn try_from(b: &[u8]) -> Result { + // For 3 or more fields, this shortcut check reduces code size + if b.len() < 20 { + return Err(TbfParseError::InternalError); + } + Ok(TbfHeaderV2Program { + init_fn_offset: u32::from_le_bytes( + b.get(0..4) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + protected_trailer_size: u32::from_le_bytes( + b.get(4..8) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + minimum_ram_size: u32::from_le_bytes( + b.get(8..12) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + binary_end_offset: u32::from_le_bytes( + b.get(12..16) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + version: u32::from_le_bytes( + b.get(16..20) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ), + }) + } +} + impl core::convert::TryFrom<&[u8]> for TbfHeaderV2WriteableFlashRegion { type Error = TbfParseError; @@ -350,6 +462,10 @@ impl core::convert::TryFrom<&[u8]> for TbfHeaderDriverPermission { type Error = TbfParseError; fn try_from(b: &[u8]) -> Result { + // For 3 or more fields, this shortcut check reduces code size + if b.len() < 16 { + return Err(TbfParseError::InternalError); + } Ok(TbfHeaderDriverPermission { driver_number: u32::from_le_bytes( b.get(0..4) @@ -370,64 +486,21 @@ impl core::convert::TryFrom<&[u8]> for TbfHeaderDriverPermission { } } -impl core::convert::TryFrom<&[u8]> for TbfHeaderV2Permissions { +impl core::convert::TryFrom<&[u8]> for TbfHeaderV2StoragePermissions { type Error = TbfParseError; - fn try_from(b: &[u8]) -> Result, Self::Error> { - let length = u16::from_le_bytes( - b.get(0..2) - .ok_or(TbfParseError::BadTlvEntry( - TbfHeaderTypes::TbfHeaderPermissions as usize, - ))? - .try_into()?, - ); - - let mut perms: [TbfHeaderDriverPermission; L] = [TbfHeaderDriverPermission { - driver_number: 0, - offset: 0, - allowed_commands: 0, - }; L]; - - for i in 0..length as usize { - let start = 2 + (i * size_of::()); - let end = start + size_of::(); - if let Some(perm) = perms.get_mut(i) { - *perm = b - .get(start..end as usize) - .ok_or(TbfParseError::BadTlvEntry( - TbfHeaderTypes::TbfHeaderPermissions as usize, - ))? - .try_into()?; - } else { - return Err(TbfParseError::BadTlvEntry( - TbfHeaderTypes::TbfHeaderPermissions as usize, - )); - } - } - - Ok(TbfHeaderV2Permissions { length, perms }) - } -} - -impl core::convert::TryFrom<&[u8]> for TbfHeaderV2PersistentAcl { - type Error = TbfParseError; - - fn try_from(b: &[u8]) -> Result, Self::Error> { + fn try_from(b: &[u8]) -> Result, Self::Error> { let mut read_end = 6; - let write_id = u32::from_le_bytes( + let write_id = core::num::NonZeroU32::new(u32::from_le_bytes( b.get(0..4) - .ok_or(TbfParseError::BadTlvEntry( - TbfHeaderTypes::TbfHeaderPersistentAcl as usize, - ))? + .ok_or(TbfParseError::NotEnoughFlash)? .try_into()?, - ); + )); let read_length = u16::from_le_bytes( b.get(4..6) - .ok_or(TbfParseError::BadTlvEntry( - TbfHeaderTypes::TbfHeaderPersistentAcl as usize, - ))? + .ok_or(TbfParseError::NotEnoughFlash)? .try_into()?, ); @@ -437,52 +510,46 @@ impl core::convert::TryFrom<&[u8]> for TbfHeaderV2PersistentAcl< read_end = start + size_of::(); if let Some(read_id) = read_ids.get_mut(i) { *read_id = u32::from_le_bytes( - b.get(start..read_end as usize) - .ok_or(TbfParseError::BadTlvEntry( - TbfHeaderTypes::TbfHeaderPersistentAcl as usize, - ))? + b.get(start..read_end) + .ok_or(TbfParseError::NotEnoughFlash)? .try_into()?, ); } else { return Err(TbfParseError::BadTlvEntry( - TbfHeaderTypes::TbfHeaderPersistentAcl as usize, + TbfHeaderTypes::TbfHeaderStoragePermissions as usize, )); } } - let access_length = u16::from_le_bytes( + let modify_length = u16::from_le_bytes( b.get(read_end..(read_end + 2)) - .ok_or(TbfParseError::BadTlvEntry( - TbfHeaderTypes::TbfHeaderPersistentAcl as usize, - ))? + .ok_or(TbfParseError::NotEnoughFlash)? .try_into()?, ); - let mut access_ids: [u32; L] = [0; L]; - for i in 0..access_length as usize { + let mut modify_ids: [u32; L] = [0; L]; + for i in 0..modify_length as usize { let start = read_end + 2 + (i * size_of::()); - let access_end = start + size_of::(); - if let Some(access_id) = access_ids.get_mut(i) { - *access_id = u32::from_le_bytes( - b.get(start..access_end as usize) - .ok_or(TbfParseError::BadTlvEntry( - TbfHeaderTypes::TbfHeaderPersistentAcl as usize, - ))? + let modify_end = start + size_of::(); + if let Some(modify_id) = modify_ids.get_mut(i) { + *modify_id = u32::from_le_bytes( + b.get(start..modify_end) + .ok_or(TbfParseError::NotEnoughFlash)? .try_into()?, ); } else { return Err(TbfParseError::BadTlvEntry( - TbfHeaderTypes::TbfHeaderPersistentAcl as usize, + TbfHeaderTypes::TbfHeaderStoragePermissions as usize, )); } } - Ok(TbfHeaderV2PersistentAcl { + Ok(TbfHeaderV2StoragePermissions { write_id, read_length, read_ids, - access_length, - access_ids, + modify_length, + modify_ids, }) } } @@ -506,6 +573,60 @@ impl core::convert::TryFrom<&[u8]> for TbfHeaderV2KernelVersion { } } +impl core::convert::TryFrom<&[u8]> for TbfHeaderV2ShortId { + type Error = TbfParseError; + + fn try_from(b: &[u8]) -> Result { + Ok(TbfHeaderV2ShortId { + short_id: core::num::NonZeroU32::new(u32::from_le_bytes( + b.get(0..4) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + )), + }) + } +} + +impl core::convert::TryFrom<&'static [u8]> for TbfFooterV2Credentials { + type Error = TbfParseError; + + fn try_from(b: &'static [u8]) -> Result { + let format = u32::from_le_bytes( + b.get(0..4) + .ok_or(TbfParseError::InternalError)? + .try_into()?, + ); + let ftype = match format { + 0 => TbfFooterV2CredentialsType::Reserved, + 1 => TbfFooterV2CredentialsType::Rsa3072Key, + 2 => TbfFooterV2CredentialsType::Rsa4096Key, + 3 => TbfFooterV2CredentialsType::SHA256, + 4 => TbfFooterV2CredentialsType::SHA384, + 5 => TbfFooterV2CredentialsType::SHA512, + _ => { + return Err(TbfParseError::BadTlvEntry( + TbfHeaderTypes::TbfFooterCredentials as usize, + )); + } + }; + let length = match ftype { + TbfFooterV2CredentialsType::Reserved => 0, + TbfFooterV2CredentialsType::Rsa3072Key => 768, + TbfFooterV2CredentialsType::Rsa4096Key => 1024, + TbfFooterV2CredentialsType::SHA256 => 32, + TbfFooterV2CredentialsType::SHA384 => 48, + TbfFooterV2CredentialsType::SHA512 => 64, + }; + let data = &b + .get(4..(length + 4)) + .ok_or(TbfParseError::NotEnoughFlash)?; + Ok(TbfFooterV2Credentials { + format: ftype, + data, + }) + } +} + /// The command permissions specified by the TBF header. /// /// Use the `get_command_permissions()` function to retrieve these. @@ -529,12 +650,14 @@ pub enum CommandPermissions { pub struct TbfHeaderV2 { pub(crate) base: TbfHeaderV2Base, pub(crate) main: Option, + pub(crate) program: Option, pub(crate) package_name: Option<&'static str>, - pub(crate) writeable_regions: Option<[Option; 4]>, - pub(crate) fixed_addresses: Option, - pub(crate) permissions: Option>, - pub(crate) persistent_acls: Option>, + pub(crate) writeable_regions: Option<&'static [u8]>, + pub(crate) fixed_addresses: Option<&'static [u8]>, + pub(crate) permissions: Option<&'static [u8]>, + pub(crate) storage_permissions: Option<&'static [u8]>, pub(crate) kernel_version: Option, + pub(crate) short_id: Option, } /// Type that represents the fields of the Tock Binary Format header. @@ -550,6 +673,14 @@ pub enum TbfHeader { } impl TbfHeader { + /// Return the length of the header. + pub fn length(&self) -> u16 { + match *self { + TbfHeader::TbfHeaderV2(hd) => hd.base.header_size, + TbfHeader::Padding(base) => base.header_size, + } + } + /// Return whether this is an app or just padding between apps. pub fn is_app(&self) -> bool { match *self { @@ -575,7 +706,15 @@ impl TbfHeader { /// needed for this app. pub fn get_minimum_app_ram_size(&self) -> u32 { match *self { - TbfHeader::TbfHeaderV2(hd) => hd.main.map_or(0, |m| m.minimum_ram_size), + TbfHeader::TbfHeaderV2(hd) => { + if hd.program.is_some() { + hd.program.map_or(0, |p| p.minimum_ram_size) + } else if hd.main.is_some() { + hd.main.map_or(0, |m| m.minimum_ram_size) + } else { + 0 + } + } _ => 0, } } @@ -585,18 +724,45 @@ impl TbfHeader { pub fn get_protected_size(&self) -> u32 { match *self { TbfHeader::TbfHeaderV2(hd) => { - hd.main.map_or(0, |m| m.protected_size) + (hd.base.header_size as u32) + if hd.program.is_some() { + hd.program.map_or(0, |p| { + (hd.base.header_size as u32) + p.protected_trailer_size + }) + } else if hd.main.is_some() { + hd.main.map_or(0, |m| { + (hd.base.header_size as u32) + m.protected_trailer_size + }) + } else { + 0 + } } _ => 0, } } + /// Get the start offset of the application binary from the beginning + /// of the process binary (start of the TBF header). Only valid if this + /// is an app. + pub fn get_app_start_offset(&self) -> u32 { + // The application binary starts after the header plus any + // additional protected space. + self.get_protected_size() + } + /// Get the offset from the beginning of the app's flash region where the /// app should start executing. pub fn get_init_function_offset(&self) -> u32 { match *self { TbfHeader::TbfHeaderV2(hd) => { - hd.main.map_or(0, |m| m.init_fn_offset) + (hd.base.header_size as u32) + if hd.program.is_some() { + hd.program + .map_or(0, |p| p.init_fn_offset + (hd.base.header_size as u32)) + } else if hd.main.is_some() { + hd.main + .map_or(0, |m| m.init_fn_offset + (hd.base.header_size as u32)) + } else { + 0 + } } _ => 0, } @@ -613,9 +779,9 @@ impl TbfHeader { /// Get the number of flash regions this app has specified in its header. pub fn number_writeable_flash_regions(&self) -> usize { match *self { - TbfHeader::TbfHeaderV2(hd) => hd.writeable_regions.map_or(0, |wrs| { - wrs.iter() - .fold(0, |acc, wr| if wr.is_some() { acc + 1 } else { acc }) + TbfHeader::TbfHeaderV2(hd) => hd.writeable_regions.map_or(0, |wr_slice| { + let wfr_len = size_of::(); + wr_slice.len() / wfr_len }), _ => 0, } @@ -624,13 +790,28 @@ impl TbfHeader { /// Get the offset and size of a given flash region. pub fn get_writeable_flash_region(&self, index: usize) -> (u32, u32) { match *self { - TbfHeader::TbfHeaderV2(hd) => hd.writeable_regions.map_or((0, 0), |wrs| { - wrs.get(index).unwrap_or(&None).map_or((0, 0), |wr| { - ( + TbfHeader::TbfHeaderV2(hd) => hd.writeable_regions.map_or((0, 0), |wr_slice| { + fn get_region( + wr_slice: &'static [u8], + index: usize, + ) -> Result { + let wfr_len = size_of::(); + + let wfr = wr_slice + .get(index * wfr_len..(index + 1) * wfr_len) + .ok_or(())? + .try_into() + .or(Err(()))?; + Ok(wfr) + } + + match get_region(wr_slice, index) { + Ok(wr) => ( wr.writeable_flash_region_offset, wr.writeable_flash_region_size, - ) - }) + ), + Err(()) => (0, 0), + } }), _ => (0, 0), } @@ -643,7 +824,8 @@ impl TbfHeader { TbfHeader::TbfHeaderV2(hd) => hd, _ => return None, }; - match hd.fixed_addresses.as_ref()?.start_process_ram { + let fixed_addresses: TbfHeaderV2FixedAddresses = hd.fixed_addresses?.try_into().ok()?; + match fixed_addresses.start_process_ram { 0xFFFFFFFF => None, start => Some(start), } @@ -656,7 +838,8 @@ impl TbfHeader { TbfHeader::TbfHeaderV2(hd) => hd, _ => return None, }; - match hd.fixed_addresses.as_ref()?.start_process_flash { + let fixed_addresses: TbfHeaderV2FixedAddresses = hd.fixed_addresses?.try_into().ok()?; + match fixed_addresses.start_process_flash { 0xFFFFFFFF => None, start => Some(start), } @@ -677,24 +860,55 @@ impl TbfHeader { pub fn get_command_permissions(&self, driver_num: usize, offset: usize) -> CommandPermissions { match self { TbfHeader::TbfHeaderV2(hd) => match hd.permissions { - Some(permissions) => { - let mut found_driver_num: bool = false; - for perm in permissions.perms { - if perm.driver_number == driver_num as u32 { - found_driver_num = true; - if perm.offset == offset as u32 { - return CommandPermissions::Mask(perm.allowed_commands); + Some(permissions_tlv_slice) => { + // Helper function to wrap the return in a Result. + fn get_command_permissions_result( + permissions_tlv_slice: &'static [u8], + driver_num: usize, + offset: usize, + ) -> Result { + let mut found_driver_num: bool = false; + let perm_len = size_of::(); + + // Read the number of stored permissions. + let number_perms = u16::from_le_bytes( + permissions_tlv_slice + .get(0..2) + .ok_or(())? + .try_into() + .or(Err(()))?, + ); + // Get the remaining slice of just the permissions. + let permissions_slice = permissions_tlv_slice.get(2..).ok_or(())?; + + // Iterate the permissions to find a match. + for i in 0..number_perms as usize { + let perm: TbfHeaderDriverPermission = permissions_slice + .get((i * perm_len)..((i + 1) * perm_len)) + .ok_or(())? + .try_into() + .or(Err(()))?; + + if perm.driver_number == driver_num as u32 { + found_driver_num = true; + if perm.offset == offset as u32 { + return Ok(CommandPermissions::Mask(perm.allowed_commands)); + } } } + + if found_driver_num { + // We found this driver number but nothing matched the + // requested offset. Since permissions are default off, + // we can return a mask of all zeros. + Ok(CommandPermissions::Mask(0)) + } else { + Ok(CommandPermissions::NoPermsThisDriver) + } } - if found_driver_num { - // We found this driver number but nothing matched the - // requested offset. Since permissions are default off, - // we can return a mask of all zeros. - CommandPermissions::Mask(0) - } else { - CommandPermissions::NoPermsThisDriver - } + + get_command_permissions_result(permissions_tlv_slice, driver_num, offset) + .unwrap_or(CommandPermissions::NoPermsAtAll) } _ => CommandPermissions::NoPermsAtAll, }, @@ -702,6 +916,68 @@ impl TbfHeader { } } + /// Get the process `write_id`. + /// + /// Returns `None` if a `write_id` is not included. This indicates the TBF + /// does not have the ability to store new items. + pub fn get_storage_write_id(&self) -> Option { + match self { + TbfHeader::TbfHeaderV2(hd) => match hd.storage_permissions { + Some(storage_permissions_tlv_slice) => { + let write_id = core::num::NonZeroU32::new(u32::from_le_bytes( + storage_permissions_tlv_slice.get(0..4)?.try_into().ok()?, + )); + + write_id + } + _ => None, + }, + _ => None, + } + } + + /// Get the number of valid `read_ids` and the `read_ids`. + /// Returns `None` if a `read_ids` is not included. + pub fn get_storage_read_ids(&self) -> Option<(usize, [u32; NUM_STORAGE_PERMISSIONS])> { + match self { + TbfHeader::TbfHeaderV2(hd) => match hd.storage_permissions { + Some(storage_permissions_tlv_slice) => { + let storage_permissions: TbfHeaderV2StoragePermissions< + NUM_STORAGE_PERMISSIONS, + > = storage_permissions_tlv_slice.try_into().ok()?; + + Some(( + storage_permissions.read_length.into(), + storage_permissions.read_ids, + )) + } + _ => None, + }, + _ => None, + } + } + + /// Get the number of valid `access_ids` and the `access_ids`. + /// Returns `None` if a `access_ids` is not included. + pub fn get_storage_modify_ids(&self) -> Option<(usize, [u32; NUM_STORAGE_PERMISSIONS])> { + match self { + TbfHeader::TbfHeaderV2(hd) => match hd.storage_permissions { + Some(storage_permissions_tlv_slice) => { + let storage_permissions: TbfHeaderV2StoragePermissions< + NUM_STORAGE_PERMISSIONS, + > = storage_permissions_tlv_slice.try_into().ok()?; + + Some(( + storage_permissions.modify_length.into(), + storage_permissions.modify_ids, + )) + } + _ => None, + }, + _ => None, + } + } + /// Get the minimum compatible kernel version this process requires. /// Returns `None` if the kernel compatibility header is not included. pub fn get_kernel_version(&self) -> Option<(u16, u16)> { @@ -713,4 +989,34 @@ impl TbfHeader { _ => None, } } + + /// Return the offset where the binary ends in the TBF or 0 if there + /// is no binary. If there is a Main header the end offset is the size + /// of the TBF, while if there is a Program header it can be smaller. + pub fn get_binary_end(&self) -> u32 { + match self { + TbfHeader::TbfHeaderV2(hd) => hd + .program + .map_or(hd.base.total_size, |p| p.binary_end_offset), + _ => 0, + } + } + + /// Return the version number of the Userspace Binary in this TBF + /// Object, or 0 if there is no binary or no version number. + pub fn get_binary_version(&self) -> u32 { + match self { + TbfHeader::TbfHeaderV2(hd) => hd.program.map_or(0, |p| p.version), + _ => 0, + } + } + + /// Return the fixed ShortId of the application if it was specified in the + /// TBF header. + pub fn get_fixed_short_id(&self) -> Option { + match self { + TbfHeader::TbfHeaderV2(hd) => hd.short_id.map_or(None, |si| si.short_id), + _ => None, + } + } } diff --git a/netlify.toml b/netlify.toml index 53f8909014..d5c7ad3a51 100644 --- a/netlify.toml +++ b/netlify.toml @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [[plugins]] package = "./tools/netlify-cache" diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index db56ccbd24..0000000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -nightly-2021-12-04 diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000000..00e595944e --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,8 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +[toolchain] +channel = "nightly-2024-07-08" +components = ["miri", "llvm-tools", "rust-src", "rustfmt", "clippy"] +targets = ["thumbv6m-none-eabi", "thumbv7em-none-eabi", "thumbv7em-none-eabihf", "riscv32imc-unknown-none-elf", "riscv32imac-unknown-none-elf"] diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000000..99d0080f4c --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,16 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +# Tock uses rustfmt's default configuration for formatting style. + +# Configure rustfmt to error when it cannot format a file. This helps avoid +# issues where rust code is silently not formatted because rustfmt encounters +# something it cannot format. Typically, this happens when a comment is inserted +# somewhere that rustfmt doesn't handle. +error_on_unformatted = true + +# This configuration file is important so that when rustfmt is run within this +# repository, it does not search parent directories for a rustfmt.toml file. +# This allows projects with their own rustfmt.toml file to include tock as a +# submodule without changing the behavior of `make prepush`. diff --git a/shell.nix b/shell.nix index 0573ee441c..cf994f17a8 100644 --- a/shell.nix +++ b/shell.nix @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # Shell expression for the Nix package manager # # This nix expression creates an environment with necessary packages installed: @@ -10,60 +14,35 @@ # $ nix-shell # -{ pkgs ? import {} }: +{ pkgs ? import {}, withUnfreePkgs ? false }: with builtins; let inherit (pkgs) stdenv lib; - pythonPackages = lib.fix' (self: with self; pkgs.python3Packages // - { - - tockloader = buildPythonPackage rec { - pname = "tockloader"; - version = "1.8.0"; - name = "${pname}-${version}"; + # Tockloader v1.12.0 + tockloader = import (pkgs.fetchFromGitHub { + owner = "tock"; + repo = "tockloader"; + rev = "v1.12.0"; + sha256 = "sha256-VgbAKDY/7ZVINDkqSHF7C0zRzVgtk8YG6O/ZmUpsh/g="; + }) { inherit pkgs withUnfreePkgs; }; - propagatedBuildInputs = [ argcomplete colorama crcmod pyserial pytoml tqdm ]; + rust_overlay = import "${pkgs.fetchFromGitHub { + owner = "nix-community"; + repo = "fenix"; + rev = "1a92c6d75963fd594116913c23041da48ed9e020"; + sha256 = "sha256-L3vZfifHmog7sJvzXk8qiKISkpyltb+GaThqMJ7PU9Y="; + }}/overlay.nix"; - src = fetchPypi { - inherit pname version; - sha256 = "0qniwkhgiwm9bayf1l9s3i83k0f7qm0iqgvjljdj4pf86lqllbb7"; - }; - }; - }); - - moz_overlay = import (builtins.fetchTarball https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz); - nixpkgs = import { overlays = [ moz_overlay ]; }; + nixpkgs = import { overlays = [ rust_overlay ]; }; # Get a custom cross-compile capable Rust install of a specific channel and # build. Tock expects a specific version of Rust with a selection of targets # and components to be present. rustBuild = ( - nixpkgs.rustChannelOf ( - let - # Read the ./rust-toolchain (and trim whitespace) so we can extrapolate - # the channel and date information. This makes it more convenient to - # update the Rust toolchain used. - rustToolchain = builtins.replaceStrings ["\n" "\r" " " "\t"] ["" "" "" ""] ( - builtins.readFile ./rust-toolchain - ); - in - { - channel = lib.head (lib.splitString "-" rustToolchain); - date = lib.concatStringsSep "-" (lib.tail (lib.splitString "-" rustToolchain)); - } - ) - ).rust.override { - targets = [ - "thumbv7em-none-eabi" "thumbv7em-none-eabihf" "thumbv6m-none-eabi" - "riscv32imac-unknown-none-elf" "riscv32imc-unknown-none-elf" "riscv32i-unknown-none-elf" - ]; - extensions = [ - "rust-src" # required to compile the core library - "llvm-tools-preview" # currently required to support recently added flags - ]; - }; + nixpkgs.fenix.fromToolchainFile { file = ./rust-toolchain.toml; } + ); in pkgs.mkShell { @@ -72,17 +51,30 @@ in buildInputs = with pkgs; [ # --- Toolchains --- rustBuild + openocd # --- Convenience and support packages --- python3Full - pythonPackages.tockloader + tockloader # Required for tools/print_tock_memory_usage.py - pythonPackages.cxxfilt + python3Packages.cxxfilt # --- CI support packages --- qemu + + # --- Flashing tools --- + # If your board requires J-Link to flash and you are on NixOS, + # add these lines to your system wide configuration. + + # Enable udev rules from segger-jlink package + # services.udev.packages = [ + # pkgs.segger-jlink + # ]; + + # Add "segger-jlink" to your system packages and accept the EULA: + # nixpkgs.config.segger-jlink.acceptLicense = true; ]; LD_LIBRARY_PATH="${stdenv.cc.cc.lib}/lib64:$LD_LIBRARY_PATH"; diff --git a/tools/.gitignore b/tools/.gitignore index e182eb6382..64e7933370 100644 --- a/tools/.gitignore +++ b/tools/.gitignore @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + local_cargo/ *.svd .format_fresh diff --git a/tools/Cargo.toml b/tools/Cargo.toml new file mode 100644 index 0000000000..ab9134b00d --- /dev/null +++ b/tools/Cargo.toml @@ -0,0 +1,21 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + +[workspace] +members = [ + "alert_codes", + "board-runner", + "license-checker", + "litex-ci-runner", + "qemu-runner", + "sha256sum", + "usb/bulk-echo", + "usb/bulk-test", + "usb/control-test", +] +resolver = "2" + +[workspace.package] +authors = ["Tock Project Developers "] +edition = "2021" diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 0000000000..ef25576737 --- /dev/null +++ b/tools/README.md @@ -0,0 +1,5 @@ +Tock Tools +========== + +This folder contains various scripts, testing infrastructure, and helpers +related to Tock. diff --git a/tools/alert_codes/Cargo.toml b/tools/alert_codes/Cargo.toml index e457dda7b8..df72c10dfb 100644 --- a/tools/alert_codes/Cargo.toml +++ b/tools/alert_codes/Cargo.toml @@ -1,7 +1,11 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "alert_codes" version = "0.1.0" -authors = ["jrvanwhy "] -edition = "2021" +authors.workspace = true +edition.workspace = true [dependencies] diff --git a/tools/alert_codes/README.md b/tools/alert_codes/README.md new file mode 100644 index 0000000000..3f1e94dad3 --- /dev/null +++ b/tools/alert_codes/README.md @@ -0,0 +1,5 @@ +Alert Codes - Low Level Debug +============================= + +Host-side listener for alert codes transmitted via the low level debug syscall +interface. diff --git a/tools/alert_codes/src/main.rs b/tools/alert_codes/src/main.rs index f9c3477a29..5db13fd7cc 100644 --- a/tools/alert_codes/src/main.rs +++ b/tools/alert_codes/src/main.rs @@ -1,4 +1,8 @@ -/// Prints an error message and usage string. Used to report command line +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +/// Prints an error message and usage string. Used to report command line /// argument errors. fn usage_error(message: &str) { println!( diff --git a/tools/board-runner/Cargo.toml b/tools/board-runner/Cargo.toml index 3cd6c42089..7362df901d 100644 --- a/tools/board-runner/Cargo.toml +++ b/tools/board-runner/Cargo.toml @@ -1,8 +1,12 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "board-runner" version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +authors.workspace = true +edition.workspace = true [dependencies] rexpect = "0.4" diff --git a/tools/board-runner/README.md b/tools/board-runner/README.md index 46e085508f..33c3e7a233 100644 --- a/tools/board-runner/README.md +++ b/tools/board-runner/README.md @@ -38,3 +38,19 @@ LIBTOCK_C_TREE= TARGET=artemis_nano make board-release-test Where `libtock_c_repo` points to the top level directory of the corresponding libtock-c repo. + +### ESP32-C3 + +This can be used to perform Tock release testing on the Espressif ESP32-C3 +board. + +This assumes that the ESP32-C3 serial console is available on the machine's +first serial port (`/dev/ttyUSB0` for Unix systems). The tests can be run from +the top level of the Tock directory with the following command + +```shell +LIBTOCK_C_TREE= TARGET=esp32_c3 make board-release-test +``` + +Where `libtock_c_repo` points to the top level directory of the corresponding +libtock-c repo. diff --git a/tools/board-runner/src/artemis_nano.rs b/tools/board-runner/src/artemis_nano.rs index 806dcae1b9..64378c9e90 100644 --- a/tools/board-runner/src/artemis_nano.rs +++ b/tools/board-runner/src/artemis_nano.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use rexpect::errors::Error; use rexpect::spawn_stream; use serialport::prelude::*; @@ -23,7 +27,7 @@ fn artemis_nano_flash( // Flash the app let mut build = Command::new("make") .arg("-C") - .arg("../../boards/redboard_artemis_nano") + .arg("../../boards/apollo3/redboard_artemis_nano") .arg(format!("APP={}", app_name)) .arg("flash-app") .stdout(Stdio::null()) diff --git a/tools/board-runner/src/earlgrey_cw310.rs b/tools/board-runner/src/earlgrey_cw310.rs index d66b2a5cf8..340946447e 100644 --- a/tools/board-runner/src/earlgrey_cw310.rs +++ b/tools/board-runner/src/earlgrey_cw310.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use rexpect::errors::Error; use rexpect::spawn_stream; use serialport::prelude::*; @@ -50,7 +54,7 @@ fn earlgrey_cw310_flash( p.exp_string("Processing frame #13, expecting #13")?; p.exp_string("Processing frame #67, expecting #67")?; - p.exp_string("Boot ROM initialisation has completed, jump into flash")?; + p.exp_string("Test ROM complete, jumping to flash!")?; Ok(p) } diff --git a/tools/board-runner/src/earlgrey_nexysvideo.rs b/tools/board-runner/src/earlgrey_nexysvideo.rs deleted file mode 100644 index 39f9204e3f..0000000000 --- a/tools/board-runner/src/earlgrey_nexysvideo.rs +++ /dev/null @@ -1,484 +0,0 @@ -use rexpect::errors::Error; -use rexpect::spawn_stream; -use serialport::prelude::*; -use serialport::SerialPortSettings; -use std::env; -use std::fs::OpenOptions; -use std::process::{Command, Stdio}; -use std::time::Duration; -use std::{thread, time}; - -fn earlgrey_nexysvideo_flash( - app_name: &str, -) -> Result>, Error> { - let s = SerialPortSettings { - baud_rate: 115200, - data_bits: DataBits::Eight, - flow_control: FlowControl::None, - parity: Parity::None, - stop_bits: StopBits::One, - timeout: Duration::from_millis(1000), - }; - - // Open the first serialport available. - let port_name = &serialport::available_ports().expect("No serial port")[1].port_name; - println!("Connecting to OpenTitan port: {:?}", port_name); - let port = serialport::open_with_settings(port_name, &s).expect("Failed to open serial port"); - - // Clone the port - let port_clone = port.try_clone().expect("Failed to clone"); - - // Create the Rexpect instance - let mut p = spawn_stream(port, port_clone, Some(2000)); - - // Flash the Tock kernel and app - let mut build = Command::new("make") - .arg("-C") - .arg("../../boards/opentitan/earlgrey-nexysvideo") - .arg(format!( - "OPENTITAN_TREE={}", - env::var("OPENTITAN_TREE").unwrap() - )) - .arg(format!("APP={}", app_name)) - .arg("flash-app") - .stdout(Stdio::null()) - .spawn() - .expect("failed to spawn build"); - assert!(build.wait().unwrap().success()); - - // Make sure the image is flashed - p.exp_string("Processing frame #13, expecting #13")?; - p.exp_string("Processing frame #67, expecting #67")?; - - p.exp_string("Boot ROM initialisation has completed, jump into flash")?; - - Ok(p) -} - -fn earlgrey_nexysvideo_c_hello() -> Result<(), Error> { - let app = format!( - "{}/{}", - env::var("LIBTOCK_C_TREE").unwrap(), - "examples/c_hello/build/rv32imc/rv32imc.0x20030080.0x10005000.tbf" - ); - let mut p = earlgrey_nexysvideo_flash(&app).unwrap(); - - p.exp_string("Hello World!")?; - - Ok(()) -} - -fn earlgrey_nexysvideo_blink() -> Result<(), Error> { - let app = format!( - "{}/{}", - env::var("LIBTOCK_C_TREE").unwrap(), - "examples/blink/build/rv32imc/rv32imc.0x20030080.0x10005000.tbf" - ); - let _p = earlgrey_nexysvideo_flash(&app).unwrap(); - - println!("Make sure the LEDs are blinking"); - - let timeout = time::Duration::from_secs(10); - thread::sleep(timeout); - - Ok(()) -} - -fn earlgrey_nexysvideo_c_hello_and_printf_long() -> Result<(), Error> { - let app = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open("app") - .unwrap(); - - let mut build = Command::new("cat") - .arg(format!( - "{}/{}", - env::var("LIBTOCK_C_TREE").unwrap(), - "examples/c_hello/build/rv32imc/rv32imc.0x20030080.0x10005000.tbf" - )) - .stdout(app) - .spawn() - .expect("failed to spawn build"); - assert!(build.wait().unwrap().success()); - - let app = OpenOptions::new() - .append(true) - .create(false) - .open("app") - .unwrap(); - - let mut build = Command::new("cat") - .arg(format!( - "{}/{}", - env::var("LIBTOCK_C_TREE").unwrap(), - "examples/tests/printf_long/build/rv32imc/rv32imc.0x20030880.0x10008000.tbf" - )) - .stdout(app) - .spawn() - .expect("failed to spawn build"); - assert!(build.wait().unwrap().success()); - - let mut p = earlgrey_nexysvideo_flash("../../../tools/board-runner/app").unwrap(); - - p.exp_string("Hello World!")?; - p.exp_string("Hi welcome to Tock. This test makes sure that a greater than 64 byte message can be printed.")?; - p.exp_string("And a short message.")?; - - Ok(()) -} - -fn earlgrey_nexysvideo_recv_short_and_recv_long() -> Result<(), Error> { - let app = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open("app") - .unwrap(); - - let mut build = Command::new("cat") - .arg(format!( - "{}/{}", - env::var("LIBTOCK_C_TREE").unwrap(), - "examples/tests/console_recv_short/build/rv32imc/rv32imc.0x20030080.0x10005000.tbf" - )) - .stdout(app) - .spawn() - .expect("failed to spawn build"); - assert!(build.wait().unwrap().success()); - - let app = OpenOptions::new() - .append(true) - .create(false) - .open("app") - .unwrap(); - - let mut build = Command::new("cat") - .arg(format!( - "{}/{}", - env::var("LIBTOCK_C_TREE").unwrap(), - "examples/tests/console_recv_long/build/rv32imc/rv32imc.0x20034080.0x10008000.tbf" - )) - .stdout(app) - .spawn() - .expect("failed to spawn build"); - assert!(build.wait().unwrap().success()); - - let mut p = earlgrey_nexysvideo_flash("../../../tools/board-runner/app").unwrap(); - - p.exp_string("Error doing UART receive: -2")?; - - Ok(()) -} - -fn earlgrey_nexysvideo_blink_and_c_hello_and_buttons() -> Result<(), Error> { - let app = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open("app") - .unwrap(); - - let mut build = Command::new("cat") - .arg(format!( - "{}/{}", - env::var("LIBTOCK_C_TREE").unwrap(), - "examples/blink/build/rv32imc/rv32imc.0x20030080.0x10005000.tbf" - )) - .stdout(app) - .spawn() - .expect("failed to spawn build"); - assert!(build.wait().unwrap().success()); - - let app = OpenOptions::new() - .append(true) - .create(false) - .open("app") - .unwrap(); - - let mut build = Command::new("cat") - .arg(format!( - "{}/{}", - env::var("LIBTOCK_C_TREE").unwrap(), - "examples/c_hello/build/rv32imc/rv32imc.0x20030880.0x10008000.tbf" - )) - .stdout(app) - .spawn() - .expect("failed to spawn build"); - assert!(build.wait().unwrap().success()); - - let app = OpenOptions::new() - .append(true) - .create(false) - .open("app") - .unwrap(); - - let mut build = Command::new("cat") - .arg(format!( - "{}/{}", - env::var("LIBTOCK_C_TREE").unwrap(), - "examples/buttons/build/rv32imc/rv32imc.0x20034080.0x10008000.tbf" - )) - .stdout(app) - .spawn() - .expect("failed to spawn build"); - assert!(build.wait().unwrap().success()); - - let mut p = earlgrey_nexysvideo_flash("../../../tools/board-runner/app").unwrap(); - - p.exp_string("Hello World!")?; - - println!("Make sure the LEDs are flashing"); - - let timeout = time::Duration::from_secs(10); - thread::sleep(timeout); - - Ok(()) -} - -fn earlgrey_nexysvideo_console_recv_short() -> Result<(), Error> { - let app = format!( - "{}/{}", - env::var("LIBTOCK_C_TREE").unwrap(), - "examples/tests/console_recv_short/build/rv32imc/rv32imc.0x20030080.0x10005000.tbf" - ); - let mut p = earlgrey_nexysvideo_flash(&app).unwrap(); - - p.send_line("Short recv")?; - - // Check the message - p.exp_string("console_recv_short: Short recv")?; - - Ok(()) -} - -fn earlgrey_nexysvideo_console_timeout() -> Result<(), Error> { - let app = format!( - "{}/{}", - env::var("LIBTOCK_C_TREE").unwrap(), - "examples/tests/console_timeout/build/rv32imc/rv32imc.0x20030080.0x10005000.tbf" - ); - let mut p = earlgrey_nexysvideo_flash(&app).unwrap(); - - // Wait 5 seconds - let timeout = time::Duration::from_secs(5); - thread::sleep(timeout); - - // Send a 60 charecter message - p.send_line("This is a test message that we are sending. Look at us go...")?; - - // Check the message - p.exp_string("Userspace call to read console returned: This is a test message that we are sending. Look at us go...")?; - - Ok(()) -} - -#[allow(dead_code)] -fn earlgrey_nexysvideo_malloc_test1() -> Result<(), Error> { - let app = format!( - "{}/{}", - env::var("LIBTOCK_C_TREE").unwrap(), - "examples/tests/malloc_test01/build/rv32imc/rv32imc.0x20030080.0x10005000.tbf" - ); - let mut p = earlgrey_nexysvideo_flash(&app).unwrap(); - - p.exp_string("malloc01: success")?; - - Ok(()) -} - -#[allow(dead_code)] -fn earlgrey_nexysvideo_stack_size_test1() -> Result<(), Error> { - let app = format!( - "{}/{}", - env::var("LIBTOCK_C_TREE").unwrap(), - "examples/tests/stack_size_test01/build/rv32imc/rv32imc.0x20030080.0x10005000.tbf" - ); - let mut p = earlgrey_nexysvideo_flash(&app).unwrap(); - - p.exp_string("Stack Test App")?; - p.exp_string("Current stack pointer: 0x100")?; - - Ok(()) -} - -#[allow(dead_code)] -fn earlgrey_nexysvideo_stack_size_test2() -> Result<(), Error> { - let app = format!( - "{}/{}", - env::var("LIBTOCK_C_TREE").unwrap(), - "examples/tests/stack_size_test02/build/rv32imc/rv32imc.0x20030080.0x10005000.tbf" - ); - let mut p = earlgrey_nexysvideo_flash(&app).unwrap(); - - p.exp_string("Stack Test App")?; - p.exp_string("Current stack pointer: 0x100")?; - - Ok(()) -} - -fn earlgrey_nexysvideo_mpu_stack_growth() -> Result<(), Error> { - let app = format!( - "{}/{}", - env::var("LIBTOCK_C_TREE").unwrap(), - "examples/tests/mpu_stack_growth/build/rv32imc/rv32imc.0x20030080.0x10005000.tbf" - ); - let mut p = earlgrey_nexysvideo_flash(&app).unwrap(); - - p.exp_string("This test should recursively add stack frames until exceeding")?; - p.exp_string("panicked at 'Process mpu_stack_growth had a fault'")?; - p.exp_string("Store/AMO access fault")?; - - Ok(()) -} - -#[allow(dead_code)] -fn earlgrey_nexysvideo_mpu_walk_region() -> Result<(), Error> { - let app = format!( - "{}/{}", - env::var("LIBTOCK_C_TREE").unwrap(), - "examples/tests/mpu_walk_region/build/rv32imc/rv32imc.0x20030080.0x10005000.tbf" - ); - let mut p = earlgrey_nexysvideo_flash(&app).unwrap(); - - p.exp_string("MPU Walk Regions")?; - p.exp_string("Walking flash")?; - p.exp_string("Will overrun")?; - p.exp_string("0x2003ba00")?; - p.exp_string("panicked at 'Process mpu_walk_region had a fault'")?; - - Ok(()) -} - -fn earlgrey_nexysvideo_multi_alarm_test() -> Result<(), Error> { - let app = format!( - "{}/{}", - env::var("LIBTOCK_C_TREE").unwrap(), - "examples/tests/multi_alarm_test/build/rv32imc/rv32imc.0x20030080.0x10005000.tbf" - ); - let _p = earlgrey_nexysvideo_flash(&app).unwrap(); - - println!("Make sure the LEDs are blinking"); - - let timeout = time::Duration::from_secs(10); - thread::sleep(timeout); - - Ok(()) -} - -fn earlgrey_nexysvideo_sha_hmac_test() -> Result<(), Error> { - let app = OpenOptions::new() - .write(true) - .create(true) - .truncate(true) - .open("app") - .unwrap(); - - let mut build = Command::new("cat") - .arg(format!( - "{}/{}", - env::var("LIBTOCK_C_TREE").unwrap(), - "examples/tests/hmac/build/rv32imc/rv32imc.0x20030080.0x10005000.tbf" - )) - .stdout(app) - .spawn() - .expect("failed to spawn build"); - assert!(build.wait().unwrap().success()); - - let app = OpenOptions::new() - .append(true) - .create(false) - .open("app") - .unwrap(); - - let mut build = Command::new("cat") - .arg(format!( - "{}/{}", - env::var("LIBTOCK_C_TREE").unwrap(), - "examples/tests/sha/build/rv32imc/rv32imc.0x20034080.0x10008000.tbf" - )) - .stdout(app) - .spawn() - .expect("failed to spawn build"); - assert!(build.wait().unwrap().success()); - - let mut p = earlgrey_nexysvideo_flash("../../../tools/board-runner/app").unwrap(); - - p.exp_string("HMAC Example Test")?; - p.exp_string("SHA Example Test")?; - - p.exp_string("Running HMAC...")?; - p.exp_string("0: 0xeb")?; - p.exp_string("10: 0xde")?; - - p.exp_string("Running SHA...")?; - p.exp_string("0: 0x68")?; - p.exp_string("10: 0x8f")?; - p.exp_string("31: 0x15")?; - - let timeout = time::Duration::from_secs(10); - thread::sleep(timeout); - - Ok(()) -} - -pub fn all_earlgrey_nexysvideo_tests() { - println!("Tock board-runner starting..."); - println!(); - println!("Running earlgrey_nexysvideo tests..."); - - earlgrey_nexysvideo_c_hello() - .unwrap_or_else(|e| panic!("earlgrey_nexysvideo_c_hello job failed with {}", e)); - earlgrey_nexysvideo_blink() - .unwrap_or_else(|e| panic!("earlgrey_nexysvideo_blink job failed with {}", e)); - earlgrey_nexysvideo_c_hello_and_printf_long().unwrap_or_else(|e| { - panic!( - "earlgrey_nexysvideo_c_hello_and_printf_long job failed with {}", - e - ) - }); - earlgrey_nexysvideo_recv_short_and_recv_long().unwrap_or_else(|e| { - panic!( - "earlgrey_nexysvideo_recv_short_and_recv_long job failed with {}", - e - ) - }); - earlgrey_nexysvideo_blink_and_c_hello_and_buttons().unwrap_or_else(|e| { - panic!( - "earlgrey_nexysvideo_blink_and_c_hello_and_buttons job failed with {}", - e - ) - }); - earlgrey_nexysvideo_console_recv_short().unwrap_or_else(|e| { - panic!( - "earlgrey_nexysvideo_console_recv_short job failed with {}", - e - ) - }); - earlgrey_nexysvideo_console_timeout() - .unwrap_or_else(|e| panic!("earlgrey_nexysvideo_console_timeout job failed with {}", e)); - - earlgrey_nexysvideo_malloc_test1() - .unwrap_or_else(|e| panic!("earlgrey_nexysvideo_malloc_test1 job failed with {}", e)); - - earlgrey_nexysvideo_stack_size_test1() - .unwrap_or_else(|e| panic!("earlgrey_nexysvideo_stack_size_test1 job failed with {}", e)); - - earlgrey_nexysvideo_stack_size_test2() - .unwrap_or_else(|e| panic!("earlgrey_nexysvideo_stack_size_test2 job failed with {}", e)); - - earlgrey_nexysvideo_mpu_stack_growth() - .unwrap_or_else(|e| panic!("earlgrey_nexysvideo_mpu_stack_growth job failed with {}", e)); - - // earlgrey_nexysvideo_mpu_walk_region() - // .unwrap_or_else(|e| panic!("earlgrey_nexysvideo_mpu_walk_region job failed with {}", e)); - - earlgrey_nexysvideo_multi_alarm_test() - .unwrap_or_else(|e| panic!("earlgrey_nexysvideo_multi_alarm_test job failed with {}", e)); - - earlgrey_nexysvideo_sha_hmac_test() - .unwrap_or_else(|e| panic!("earlgrey_nexysvideo_sha_hmac_test job failed with {}", e)); - - println!("earlgrey_nexysvideo SUCCESS."); -} diff --git a/tools/board-runner/src/esp32_c3.rs b/tools/board-runner/src/esp32_c3.rs new file mode 100644 index 0000000000..6b8127a729 --- /dev/null +++ b/tools/board-runner/src/esp32_c3.rs @@ -0,0 +1,332 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + +use rexpect::errors::Error; +use rexpect::spawn_stream; +use serialport::prelude::*; +use serialport::SerialPortSettings; +use std::env; +use std::fs::OpenOptions; +use std::process::{Command, Stdio}; +use std::time::Duration; +use std::{thread, time}; + +fn esp32_c3_flash( + app_name: &str, +) -> Result>, Error> { + let s = SerialPortSettings { + baud_rate: 115200, + data_bits: DataBits::Eight, + flow_control: FlowControl::None, + parity: Parity::None, + stop_bits: StopBits::One, + timeout: Duration::from_millis(1000), + }; + + // Flash the app + let mut build = Command::new("make") + .arg("-C") + .arg("../../boards/esp32-c3-devkitM-1") + .arg(format!("APP={}", app_name)) + .arg("flash-app") + .stdout(Stdio::null()) + .spawn() + .expect("failed to spawn build"); + assert!(build.wait().unwrap().success()); + + // Open the first serialport available. + let port_name = &serialport::available_ports().expect("No serial port")[0].port_name; + println!("Connecting to redboard_esp32_c3 port: {:?}", port_name); + let port = serialport::open_with_settings(port_name, &s).expect("Failed to open serial port"); + + // Clone the port + let port_clone = port.try_clone().expect("Failed to clone"); + + // Create the Rexpect instance + let mut p = spawn_stream(port, port_clone, Some(2000)); + + // Make sure the image is flashed + p.exp_string("ESP32-C3 initialisation complete.")?; + + Ok(p) +} + +fn esp32_c3_c_hello() -> Result<(), Error> { + let app = format!( + "{}/{}", + env::var("LIBTOCK_C_TREE").unwrap(), + "examples/c_hello/build/rv32imac/rv32imac.0x403B0060.0x3FCC0000.tbf" + ); + let mut p = esp32_c3_flash(&app).unwrap(); + + p.exp_string("Hello World!")?; + + Ok(()) +} + +fn esp32_c3_blink() -> Result<(), Error> { + let app = format!( + "{}/{}", + env::var("LIBTOCK_C_TREE").unwrap(), + "examples/blink/build/rv32imac/rv32imac.0x403B0060.0x3FCC0000.tbf" + ); + let _p = esp32_c3_flash(&app).unwrap(); + + println!("Make sure the LEDs are blinking"); + + let timeout = time::Duration::from_secs(10); + thread::sleep(timeout); + + Ok(()) +} + +#[allow(dead_code)] +fn esp32_c3_sensors() -> Result<(), Error> { + let app = format!( + "{}/{}", + env::var("LIBTOCK_C_TREE").unwrap(), + "examples/sensors/build/rv32imac/rv32imac.0x403B0060.0x3FCC0000.tbf" + ); + let mut p = esp32_c3_flash(&app).unwrap(); + + p.exp_string("All available sensors on the platform will be sampled.")?; + + Ok(()) +} + +#[allow(dead_code)] +fn esp32_c3_c_hello_and_printf_long() -> Result<(), Error> { + let app = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open("app") + .unwrap(); + + let mut build = Command::new("cat") + .arg(format!( + "{}/{}", + env::var("LIBTOCK_C_TREE").unwrap(), + "examples/c_hello/build/rv32imac/rv32imac.0x403B0060.0x3FCC0000.tbf" + )) + .stdout(app) + .spawn() + .expect("failed to spawn build"); + assert!(build.wait().unwrap().success()); + + let app = OpenOptions::new() + .append(true) + .create(false) + .open("app") + .unwrap(); + + let mut build = Command::new("cat") + .arg(format!( + "{}/{}", + env::var("LIBTOCK_C_TREE").unwrap(), + "examples/tests/printf_long/build/rv32imac/rv32imac.0x403B0060.0x3FCC0000.tbf" + )) + .stdout(app) + .spawn() + .expect("failed to spawn build"); + assert!(build.wait().unwrap().success()); + + let mut p = esp32_c3_flash("../../tools/board-runner/app").unwrap(); + + p.exp_string("Hi welcome to Tock. This test makes sure that a greater than 64 byte message can be printed.")?; + p.exp_string("Hello World!")?; + p.exp_string("And a short message.")?; + + Ok(()) +} + +fn esp32_c3_blink_and_c_hello_and_buttons() -> Result<(), Error> { + let app = OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open("app") + .unwrap(); + + let mut build = Command::new("cat") + .arg(format!( + "{}/{}", + env::var("LIBTOCK_C_TREE").unwrap(), + "examples/blink/build/rv32imac/rv32imac.0x403B0060.0x3FCC0000.tbf" + )) + .stdout(app) + .spawn() + .expect("failed to spawn build"); + assert!(build.wait().unwrap().success()); + + let app = OpenOptions::new() + .append(true) + .create(false) + .open("app") + .unwrap(); + + let mut build = Command::new("cat") + .arg(format!( + "{}/{}", + env::var("LIBTOCK_C_TREE").unwrap(), + "examples/c_hello/build/rv32imac/rv32imac.0x403B0060.0x3FCC0000.tbf" + )) + .stdout(app) + .spawn() + .expect("failed to spawn build"); + assert!(build.wait().unwrap().success()); + + let app = OpenOptions::new() + .append(true) + .create(false) + .open("app") + .unwrap(); + + let mut build = Command::new("cat") + .arg(format!( + "{}/{}", + env::var("LIBTOCK_C_TREE").unwrap(), + "examples/buttons/build/rv32imac/rv32imac.0x403B0060.0x3FCC0000.tbf" + )) + .stdout(app) + .spawn() + .expect("failed to spawn build"); + assert!(build.wait().unwrap().success()); + + let mut p = esp32_c3_flash("../../tools/board-runner/app").unwrap(); + + p.exp_string("Hello World!")?; + + println!("Make sure the LEDs are flashing"); + + let timeout = time::Duration::from_secs(10); + thread::sleep(timeout); + + Ok(()) +} + +fn esp32_c3_malloc_test1() -> Result<(), Error> { + let app = format!( + "{}/{}", + env::var("LIBTOCK_C_TREE").unwrap(), + "examples/tests/malloc_test01/build/rv32imac/rv32imac.0x403B0060.0x3FCC0000.tbf" + ); + let mut p = esp32_c3_flash(&app).unwrap(); + + p.exp_string("malloc01: success")?; + + Ok(()) +} + +#[allow(dead_code)] +fn esp32_c3_stack_size_test1() -> Result<(), Error> { + let app = format!( + "{}/{}", + env::var("LIBTOCK_C_TREE").unwrap(), + "examples/tests/stack_size_test01/build/rv32imac/rv32imac.0x403B0060.0x3FCC0000.tbf" + ); + let mut p = esp32_c3_flash(&app).unwrap(); + + p.exp_string("Stack Test App")?; + p.exp_string("Current stack pointer: 0x3fcc0368")?; + + Ok(()) +} + +#[allow(dead_code)] +fn esp32_c3_stack_size_test2() -> Result<(), Error> { + let app = format!( + "{}/{}", + env::var("LIBTOCK_C_TREE").unwrap(), + "examples/tests/stack_size_test02/build/rv32imac/rv32imac.0x403B0060.0x3FCC0000.tbf" + ); + let mut p = esp32_c3_flash(&app).unwrap(); + + p.exp_string("Stack Test App")?; + p.exp_string("Current stack pointer: 0x3fcc2310")?; + + Ok(()) +} + +#[allow(dead_code)] +fn esp32_c3_mpu_stack_growth() -> Result<(), Error> { + let app = format!( + "{}/{}", + env::var("LIBTOCK_C_TREE").unwrap(), + "examples/tests/mpu_stack_growth/build/rv32imac/rv32imac.0x403B0060.0x3FCC0000.tbf" + ); + let mut p = esp32_c3_flash(&app).unwrap(); + + p.exp_string("This test should recursively add stack frames until exceeding")?; + p.exp_string("panicked at 'Process mpu_stack_growth had a fault'")?; + p.exp_string("EXCEEDED!")?; + + Ok(()) +} + +#[allow(dead_code)] +fn esp32_c3_mpu_walk_region() -> Result<(), Error> { + let app = format!( + "{}/{}", + env::var("LIBTOCK_C_TREE").unwrap(), + "examples/tests/mpu_walk_region/build/rv32imac/rv32imac.0x403B0060.0x3FCC0000.tbf" + ); + let mut p = esp32_c3_flash(&app).unwrap(); + + p.exp_string("MPU Walk Regions")?; + p.exp_string("Walking flash")?; + // p.exp_string("Will overrun")?; + // p.exp_string("panicked at 'Process mpu_walk_region had a fault'")?; + + Ok(()) +} + +fn esp32_c3_multi_alarm_test() -> Result<(), Error> { + let app = format!( + "{}/{}", + env::var("LIBTOCK_C_TREE").unwrap(), + "examples/tests/multi_alarm_test/build/rv32imac/rv32imac.0x403B0060.0x3FCC0000.tbf" + ); + let _p = esp32_c3_flash(&app).unwrap(); + + println!("Make sure the LEDs are blinking"); + + let timeout = time::Duration::from_secs(10); + thread::sleep(timeout); + + Ok(()) +} + +pub fn all_tests() { + println!("Tock board-runner starting..."); + println!(); + println!("Running esp32_c3 tests..."); + + esp32_c3_c_hello().unwrap_or_else(|e| panic!("esp32_c3_c_hello job failed with {}", e)); + esp32_c3_blink().unwrap_or_else(|e| panic!("esp32_c3_blink job failed with {}", e)); + esp32_c3_sensors().unwrap_or_else(|e| panic!("esp32_c3_sensors job failed with {}", e)); + esp32_c3_c_hello_and_printf_long() + .unwrap_or_else(|e| panic!("esp32_c3_c_hello_and_printf_long job failed with {}", e)); + esp32_c3_blink_and_c_hello_and_buttons().unwrap_or_else(|e| { + panic!( + "esp32_c3_blink_and_c_hello_and_buttons job failed with {}", + e + ) + }); + + esp32_c3_malloc_test1() + .unwrap_or_else(|e| panic!("esp32_c3_malloc_test1 job failed with {}", e)); + esp32_c3_stack_size_test1() + .unwrap_or_else(|e| panic!("esp32_c3_stack_size_test1 job failed with {}", e)); + esp32_c3_stack_size_test2() + .unwrap_or_else(|e| panic!("esp32_c3_stack_size_test2 job failed with {}", e)); + esp32_c3_mpu_stack_growth() + .unwrap_or_else(|e| panic!("esp32_c3_mpu_stack_growth job failed with {}", e)); + esp32_c3_mpu_walk_region() + .unwrap_or_else(|e| panic!("esp32_c3_mpu_walk_region job failed with {}", e)); + esp32_c3_multi_alarm_test() + .unwrap_or_else(|e| panic!("esp32_c3_multi_alarm_test job failed with {}", e)); + + println!("esp32_c3 SUCCESS."); +} diff --git a/tools/board-runner/src/main.rs b/tools/board-runner/src/main.rs index 189dd8b514..dc8d43b93f 100644 --- a/tools/board-runner/src/main.rs +++ b/tools/board-runner/src/main.rs @@ -1,8 +1,12 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use std::env; pub mod artemis_nano; pub mod earlgrey_cw310; -pub mod earlgrey_nexysvideo; +pub mod esp32_c3; fn main() { let args: Vec = env::args().collect(); @@ -15,16 +19,16 @@ fn main() { println!("Running earlgrey_cw310 tests..."); earlgrey_cw310::all_earlgrey_cw310_tests(); println!("earlgrey_cw310 SUCCESS."); - } else if arg == "earlgrey_nexysvideo" { - println!(); - println!("Running earlgrey_nexysvideo tests..."); - earlgrey_nexysvideo::all_earlgrey_nexysvideo_tests(); - println!("earlgrey_nexysvideo SUCCESS."); } else if arg == "artemis_nano" { println!(); println!("Running Redboard tests..."); artemis_nano::all_artemis_nano_tests(); println!("artemis_nano SUCCESS."); + } else if arg == "esp32_c3" { + println!(); + println!("Running ESP32-C3 tests..."); + esp32_c3::all_tests(); + println!("esp32_c3 SUCCESS."); } } } diff --git a/tools/build-all-docs.sh b/tools/build-all-docs.sh index d05596a704..42a6fa217d 100755 --- a/tools/build-all-docs.sh +++ b/tools/build-all-docs.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + # Builds all of the board documentation into doc/rustdoc. set -e diff --git a/tools/check-all-links.sh b/tools/check-all-links.sh index ef47f3bdf2..af2c147afb 100755 --- a/tools/check-all-links.sh +++ b/tools/check-all-links.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + ### Scan all markdown files and check for broken links. ### ### Requirements: diff --git a/tools/check-for-readmes.sh b/tools/check-for-readmes.sh index af0aee894d..291d5440d6 100755 --- a/tools/check-for-readmes.sh +++ b/tools/check-for-readmes.sh @@ -1,5 +1,11 @@ #!/usr/bin/env bash +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +FAIL=0 + # Find folders with Cargo.toml files but no README.md file. for f in $(find . | grep Cargo.toml); do dir=$(dirname $f) @@ -7,5 +13,8 @@ for f in $(find . | grep Cargo.toml); do if [ ! -f "$readme" ]; then echo "$readme does not exist!" + let FAIL=FAIL+1 fi done + +exit $FAIL diff --git a/tools/check_boards_readme.py b/tools/check_boards_readme.py new file mode 100755 index 0000000000..f42ee16099 --- /dev/null +++ b/tools/check_boards_readme.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 + +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +""" +Check if all of the available boards are documented in the README. +""" + +import os +import re +import sys + +SKIP = [ + "boards/components", + "boards/nordic/nrf52_components", + "boards/configurations", + "boards/tutorials", +] + + +documented_boards = [] +implemented_boards = [] + +# Find all documented capsules +with open("boards/README.md") as f: + for l in f: + items = re.findall(r".*\((.*?/README.md)\).*", l) + if len(items) > 0: + for item in items: + documented_boards.append("boards/{}".format(item)) + + +# Find all capsule source files. +for subdir, dirs, files in os.walk(os.fsencode("boards/")): + # Skip some directories we do not consider. + for skip in SKIP: + if skip in os.fsdecode(subdir): + break + else: + for file in files: + if os.fsdecode(file) == "Cargo.toml": + # Create the filepath to the board readme. + filepath = os.fsdecode( + os.path.join(subdir, "README.md".encode("utf-8")) + ) + implemented_boards.append(filepath) + + +# Calculate what doesn't seem to be documented. +missing = list(set(implemented_boards) - set(documented_boards)) + +# Calculate what has been removed +removed = list(set(documented_boards) - set(implemented_boards)) + + +if len(missing) > 0: + print("The following boards do not seem to be documented:") + for m in sorted(missing): + print(" - {}".format(m)) + +if len(removed) > 0: + print("The following boards seem to have been removed:") + for m in sorted(removed): + print(" - {}".format(m)) + + +if len(missing) > 0: + print("ERROR: Boards missing documentation in the main boards/README.md") + sys.exit(-1) + +if len(removed) > 0: + print("ERROR: Boards that do not exist are documented in boards/README.md ") + sys.exit(-1) + +print("Board documentation up to date.") diff --git a/tools/check_capsule_readme.py b/tools/check_capsule_readme.py index 148b053129..a5a5a5fdc2 100755 --- a/tools/check_capsule_readme.py +++ b/tools/check_capsule_readme.py @@ -1,47 +1,86 @@ #!/usr/bin/env python3 -''' +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +""" Check if all of the available capsules are documented in the README. -''' +""" import os import re +import sys -SKIP = ['/mod.rs', - 'src/lib.rs', - '/test', - 'src/driver.rs', - 'src/rf233_const.rs'] +SKIP = [ + "README.md", + "src/lib.rs", + "/test", + "src/rf233_const.rs", + "extra/src/tutorials", +] documented_capsules = [] implemented_capsules = [] -# Find all documented capsules -with open('capsules/README.md') as f: - for l in f: - items = re.findall(r".*\((src/.*?)\).*", l) - if len(items) > 0: - for item in items: - documented_capsules.append('capsules/{}'.format(item)) - - -# Find all capsule source files. -for subdir, dirs, files in os.walk(os.fsencode('capsules/src/')): - for file in files: - filepath = os.fsdecode(os.path.join(subdir, file)) - - # Get just the part after /src, effectively. - folders = filepath.split('/') - filepath = '/'.join(folders[0:3]) - - # Skip some noise. - for skip in SKIP: - if skip in filepath: - break - else: - implemented_capsules.append(filepath) +def parse_capsule_readme(readme_filename, documented_list): + root = "/".join(readme_filename.split("/")[:-1]) + + # Find all documented capsules + with open(readme_filename) as f: + for l in f: + items = re.findall(r".*\((src/.*?)\).*", l) + if len(items) > 0: + for item in items: + documented_capsules.append("{}/{}".format(root, item)) + + +def find_implemented_capsules(root_path, implemented_list): + # Find all capsule source files. + for subdir, dirs, files in os.walk(os.fsencode(root_path)): + for file in files: + filepath = os.fsdecode(os.path.join(subdir, file)) + + # Include the directory on behalf of `mod.rs` files. + if os.fsdecode(file) == "mod.rs": + # If we document any file in this folder, we must document them + # all. + document_within_this_folder = False + for doc_capsule in documented_capsules: + fp = os.fsdecode(subdir) + if doc_capsule.startswith(fp) and doc_capsule != fp: + document_within_this_folder = True + break + if document_within_this_folder: + # Skip the mod.rs + continue + else: + # Use the mod.rs as the entire folder + filepath = os.fsdecode(subdir) + + # Skip some noise. + for skip in SKIP: + if skip in filepath: + break + else: + # Skip files where the directory (e.g. extra/src/net) is + # documented. + for doc_capsule in documented_capsules: + if filepath.startswith(doc_capsule) and filepath != doc_capsule: + break + else: + implemented_list.append(filepath) + + +# Find links in readmes. +parse_capsule_readme("capsules/core/README.md", documented_capsules) +parse_capsule_readme("capsules/extra/README.md", documented_capsules) + +# Find actual files of capsules. +find_implemented_capsules("capsules/core/src", implemented_capsules) +find_implemented_capsules("capsules/extra/src", implemented_capsules) # Calculate what doesn't seem to be documented. missing = list(set(implemented_capsules) - set(documented_capsules)) @@ -50,12 +89,23 @@ removed = list(set(documented_capsules) - set(implemented_capsules)) -print('The following capsules do not seem to be documented:') -for m in sorted(missing): - print(' - {}'.format(m)) +if len(missing) > 0: + print("The following capsules do not seem to be documented:") + for m in sorted(missing): + print(" - {}".format(m)) + +if len(removed) > 0: + print("The following capsules seem to have been removed:") + for m in sorted(removed): + print(" - {}".format(m)) + +if len(missing) > 0: + print("ERROR: Capsules missing documentation") + sys.exit(-1) -print('The following capsules seem to have been removed:') -for m in sorted(removed): - print(' - {}'.format(m)) +if len(removed) > 0: + print("ERROR: Capsules documented that are missing") + sys.exit(-1) +print("Capsule documentation up to date.") diff --git a/tools/check_format.sh b/tools/check_format.sh new file mode 100755 index 0000000000..143a2f6ed5 --- /dev/null +++ b/tools/check_format.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. +# +# Runs `rustfmt --check` on the workspace and check for tabs in Rust files. +# Must be run from root Tock directory. +# +# Author: Leon Schuermann +# Author: Pat Pannuto +# Author: Brad Campbell +# +set -e + +# Verify that we're running in the base directory +if [ ! -x tools/check_format.sh ]; then + echo ERROR: $0 must be run from the tock repository root. + echo "" + exit 1 +fi + +set +e +let FAIL=0 +set -e + +# Run `cargo fmt --check` in the workspace root, which will check all +# crates in the workspace. +echo "Running \`cargo fmt --check\`..." +CARGO_FMT_CHECK_EXIT_CODE=0 +cargo fmt --check || CARGO_FMT_CHECK_EXIT_CODE=$? +if [[ $CARGO_FMT_CHECK_EXIT_CODE -ne 0 ]]; then + let FAIL=FAIL+1 +else + echo "\`cargo fmt --check\` suceeded." +fi +printf "\n" + +# Check for tab characters in Rust source files that haven't been +# removed by rustfmt +echo "Checking for Rust source files with tab characters..." +RUST_FILES_WITH_TABS="$(git grep --files-with-matches $'\t' -- '*.rs' || grep -lr --include '*.rs' $'\t' . || true)" +if [ "$RUST_FILES_WITH_TABS" != "" ]; then + echo "ERROR: The following files contain tab characters, please use spaces instead:" + echo "$RUST_FILES_WITH_TABS" | sed 's/^/ -> /' + let FAIL=FAIL+1 +else + echo "No Rust source file containing tab characters found." +fi + +if [[ $FAIL -ne 0 ]]; then + printf "\n" + echo "$(tput bold)Formatting errors.$(tput sgr0)" + echo "See above for details. You can try running \`cargo fmt\` to correct them." +fi +exit $FAIL diff --git a/tools/check_process_console.py b/tools/check_process_console.py new file mode 100755 index 0000000000..f0826552f0 --- /dev/null +++ b/tools/check_process_console.py @@ -0,0 +1,599 @@ +#!/usr/bin/env python3 + +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +''' +Script to test basic functionalities of the process console. + +In order to run this script on your board, first find the +serial port name of the board. + +Make sure you have flashed your board and pressed several times +the "RESET"/"REBOOT" button from the board. + +If you want to re-run the script make sure you again +press the "RESET"/"REBOOT" button, in order to clear +the command history and the current output + +If you have multiple serial ports to the same board +run the script for every serial port +''' + +from serial import Serial, SerialException +from time import sleep + + +test_output_line_size = 40 +tests = { + "open_serial_port": (-1, "not_executed"), + "fill_command_history": (-1, "not_executed"), + "command_history_api": (-1, "not_executed"), + "inserting_at_end": (-1, "not_executed"), + "inserting_at_start": (-1, "not_executed"), + "inserting_in_middle": (-1, "not_executed"), + "inserting": (-1, "not_executed"), + "deleting": (-1, "not_executed"), + "cariage_return": (-1, "not_executed"), + "newline_return": (-1, "not_executed"), + "command_history_edit": (-1, "not_executed"), +} + +colors = ["\033[92m", "\033[91m", "\033[00m"] +commands = { + "left": "\x1B[D", + "right": "\x1B[C", + "home": "\x1B[H", + "end": "\x1B[F", + "delete": "\x1B[3~", + "delete-ascii": "\x7F", + "backspace": "\x08 \x08", + "up": "\x1B[A", + "down": "\x1B[B", +} + + +class SerialPort: + ''' Serial Port class utils for sending and receiving data to the board ''' + + def __init__(self, serportname="/dev/tock"): + self.serport = Serial(serportname, 115200, timeout=0.050) + + def send_input(self, payload): + ''' Send input to the board encoded using ascii ''' + sleep(1) + if self.serport.is_open: + self.serport.write(payload.encode("ascii")) + + def recv_output(self): + ''' Receiving output from the board which is return as a normal string ''' + sleep(1) + if self.serport.is_open and self.serport.in_waiting: + return self.__evaluate_encoded(self.serport.readline().decode("ascii")) + else: + return "" + + def is_alive(self): + ''' Check if the serial port is still open ''' + return self.serport.is_open + + def clear_input(self): + ''' Clears the receiving buffer from the board ''' + if self.serport.is_open: + sleep(1) + while self.serport.in_waiting: + self.serport.read() + + def finish(self): + ''' Closes the serial port ''' + if self.serport.is_open: + self.serport.close() + + def __evaluate_encoded(self, encoded): + ''' + Evaluates a string encoded using ascii, + this is a simple parser for escape sequences. + + The function will evaluate just Backspace and Left + escape sequences + ''' + + # delete previous character + encoded = encoded.replace("\x08 \x08", "@") + + # deletes leftover character from previous message. + # As such we can ignore it + # ! This check must go after the '@' replacement ! + encoded = encoded.replace(" \x08", "") + + decoded = "" + count = 0 + + for ch in encoded: + if ch == '\x08': + count += 1 + elif count > 0: + decoded = decoded[:-count] + count = 0 + decoded += ch + elif ch == '@': + if decoded != "": + decoded = decoded[:-1] + count = 0 + else: + decoded += ch + + return decoded + + +def pass_test(test): + ''' Marks a test as passed ''' + tests[test] = (0, "passed") + + +def fail_test(test): + ''' Marks a test as failed ''' + tests[test] = (1, "failed") + + +def exit_if_condition(condition, message): + ''' Exit the main thread if condition is true and print checker results ''' + if condition: + print(message) + print_test_results() + quit() + + +def print_title(title): + ''' Prints the title of the test ''' + print(colors[0], "\n" + title, colors[-1]) + + +def print_test_results(): + ''' Prints all the checker results ''' + print("") + print("RESULTS") + print("-------") + for test in tests: + test_color, test_status = tests.get(test) + dots = test_output_line_size - len(test) - len(test_status) + print(test, end=" ") + print('.' * dots, end=" ") + print(colors[test_color] + test_status + colors[-1]) + + +def test_open_serial_port(serport): + ''' Opens a serial port to the board and checks if it is opened ''' + print_title("Openning the serial port to the board") + fail_test("open_serial_port") + + try: + port = SerialPort(serport) + pass_test("open_serial_port") + + port.send_input("\r\n") + port.clear_input() + + return port + except SerialException: + print("[ERROR] Could not open the serial port") + print_test_results() + quit() + + +def test_fill_command_history(port: SerialPort): + ''' + Fills the command history with dummy commands in order to test + it's functionality. + + For this test case the size of the command history must be greater or + equal to the default size. + ''' + print_title("Filling command history with dummy commands:") + fail_test("fill_command_history") + + for i in range(0, 9): + dummy_text = "Dummy Text" + str(i) + print("Inserting '" + dummy_text + "'") + port.send_input(dummy_text + "\r\n") + + port.clear_input() + pass_test("fill_command_history") + + +def test_command_history_api(port: SerialPort): + ''' Tests basic functionality of command history like scrolling up and down ''' + print_title("Testing basic command history functionalities:") + fail_test("command_history_api") + + print("Moving up in history 4 times") + port.send_input(commands["up"] * 4) + + out = port.recv_output() + exit_if_condition(out != "Dummy Text5", + "[ERROR] Command does not match") + + print("Moving up in history 4 times") + port.send_input(commands["up"] * 4) + + out = port.recv_output() + exit_if_condition(out != "Dummy Text1", + "[ERROR] Command does not match") + + print("Moving down in history 2 times") + port.send_input(commands["down"] * 2) + + out = port.recv_output() + exit_if_condition(out != "Dummy Text3", + "[ERROR] Command does not match") + + print("Moving all the way up") + port.send_input(commands["up"] * 10) + + out = port.recv_output() + exit_if_condition(out != "Dummy Text0", + "[ERROR] Command does not match") + + print("Moving all the way down") + port.send_input(commands["down"] * 13) + + print("Moving up in history 1 time") + port.send_input(commands["up"]) + + out = port.recv_output() + exit_if_condition(out != "Dummy Text8", + "[ERROR] Command does not match") + + out = port.send_input("\r\n") + port.clear_input() + pass_test("command_history_api") + + +def test_inserting_at_end(port: SerialPort): + ''' Inserts a text and then tries to add more text to the end of the command ''' + print_title("Inserting additional text to the end of a command:") + fail_test("inserting_at_end") + + print("Typing 'FooBar'") + port.send_input("FooBar") + + print("Insert to the end 'Dummy'") + port.send_input("Dummy") + + out = port.recv_output() + port.send_input("\r\n") + exit_if_condition(out != "FooBarDummy", + "[ERROR] Command does not match") + + port.clear_input() + pass_test("inserting_at_end") + + +def test_inserting_at_start(port: SerialPort): + ''' Inserts a text and then tries to add more text to the beginning of the command ''' + print_title("Inserting additional text to the start of a command:") + fail_test("inserting_at_start") + + print("Typing 'FooBar'") + port.send_input("FooBar") + + print("Insert to the start 'Dummy'") + port.send_input(commands["home"]) + port.send_input("Dummy") + + out = port.recv_output() + port.send_input("\r\n") + exit_if_condition(out != "DummyFooBar", + "[ERROR] Command does not match") + + port.clear_input() + pass_test("inserting_at_start") + + +def test_inserting_in_middle(port: SerialPort): + ''' Inserts a text and then tries to add more text in the middle of the command ''' + print_title("Inserting additional text in the middle of a command:") + fail_test("inserting_in_middle") + + print("Typing 'FooBar'") + port.send_input("FooBar") + + print("Insert in the middle 'Dummy'") + port.send_input(commands["left"] * 3) + + port.send_input("Dummy") + + out = port.recv_output() + port.send_input("\r\n") + exit_if_condition(out != "FooDummyBar", + "[ERROR] Command does not match") + + port.clear_input() + pass_test("inserting_in_middle") + + +def test_inserting(port: SerialPort): + ''' Performs a series of insertions in different places of the command ''' + print_title("Inserting additional text in the command:") + fail_test("inserting") + + print("Typing 'FooBar'") + port.send_input("FooBar") + + print("Typing 'Dummy'") + port.send_input("Dummy") + + print("Insert to the start 'Dummy'") + port.send_input(commands["home"]) + port.send_input("Dummy") + + print("Insert in the middle 'Dummy'") + port.send_input(commands["right"] * 3) + port.send_input("Dummy") + + out = port.recv_output() + port.send_input("\r\n") + exit_if_condition(out != "DummyFooDummyBarDummy", + "[ERROR] Command does not match") + + port.clear_input() + pass_test("inserting") + + +def test_deleting(port: SerialPort): + ''' Performs a series of deletions from different places of the command ''' + print_title("Deleting from a command using backspace and delete:") + fail_test("deleting") + + def test_deleting_with(delete_char: str): + print("Typing 'DummyFooDummyBarDummy'") + port.send_input("DummyFooDummyBarDummy") + + print("Moving to the start of the command") + port.send_input(commands["home"]) + + print("Delete first 'Dummy'") + port.send_input(delete_char * 5) + + print("Moving to the end of the command") + port.send_input(commands["end"]) + + print("Delete last 'Dummy'") + port.send_input(commands["backspace"] * 5) + + print("Moving to the begining of the middle 'Dummy'") + port.send_input(commands["left"] * 8) + + print("Delete middle 'Dummy'") + port.send_input(delete_char * 5) + + out = port.recv_output() + port.send_input("\r\n") + exit_if_condition(out != "FooBar", + "[ERROR] Command does not match") + + print("Testing with ANSI Escpae Sequence...") + test_deleting_with(commands["delete"]) + port.clear_input() + + print("Testing with ASCII character...") + test_deleting_with(commands["delete-ascii"]) + port.clear_input() + + pass_test("deleting") + + +def test_cariage_return(port: SerialPort): + ''' + Checks the command history api test with cariage return + and performs basic actions like typing a command and + deleting it + ''' + print_title("Sending commands terminated by \\r") + fail_test("cariage_return") + + for i in range(0, 9): + dummy_text = "Dummy Text" + str(i) + print("Inserting '" + dummy_text + "'") + port.send_input(dummy_text + "\r") + + port.clear_input() + test_command_history_api(port) + + print("Test if cursor is reseting") + + print("Insert command 'help' and return") + port.send_input("help\r") + + print("Move up in the command history") + port.send_input(commands["up"]) + + for _ in range(0, 4): + print("Send 25 backspaces") + port.send_input(commands["backspace"] * 25) + + print("Move down in the command history") + port.send_input(commands["down"]) + + for _ in range(0, 4): + print("Send 25 backspaces") + port.send_input(commands["backspace"] * 25) + + port.clear_input() + port.send_input("FooBar") + + out = port.recv_output() + exit_if_condition(out != "FooBar", + "[ERROR] Command does not match") + + port.send_input("\r\n") + + pass_test("cariage_return") + + +def test_newline_return(port: SerialPort): + ''' + Checks the command history api test with newline return + and performs basic actions like typing a command and + deleting it + ''' + print_title("Sending commands terminated by \\n") + fail_test("newline_return") + + for i in range(0, 9): + dummy_text = "Dummy Text" + str(i) + print("Inserting '" + dummy_text + "'") + port.send_input(dummy_text + "\r") + + port.clear_input() + + test_command_history_api(port) + + print("Test if cursor is reseting") + + print("Insert command 'help' and return") + port.send_input("help\r") + + print("Move up in the command history") + port.send_input(commands["up"]) + + for _ in range(0, 4): + print("Send 25 backspaces") + port.send_input(commands["backspace"] * 25) + + print("Move down in the command history") + port.send_input(commands["down"]) + + for _ in range(0, 4): + print("Send 25 backspaces") + port.send_input(commands["backspace"] * 25) + + port.clear_input() + port.send_input("FooBar") + + out = port.recv_output() + exit_if_condition(out != "FooBar", + "[ERROR] Command does not match") + + port.send_input("\r\n") + + pass_test("newline_return") + +def test_command_history_edit(port: SerialPort): + ''' Tests basic editing of commands in history ''' + print_title("Testing basic command history editing:") + fail_test("command_history_edit") + + def test_inserting(): + print("Inserting 'FooBar'") + port.send_input("FooBar\r\n") + port.clear_input() + + print("Moving up in history 1 time") + port.send_input(commands["up"]) + + print("Moving to the start of the command") + port.send_input(commands["home"]) + + print("Insert to the start 'Dummy'") + port.send_input("Dummy") + + print("Moving to the end of the command") + port.send_input(commands["end"]) + + print("Insert to the end 'Dummy'") + port.send_input("Dummy") + + print("Moving to the beginning of the middle 'Dummy'") + port.send_input(commands["left"] * 8) + + print("Insert in the middle 'Dummy'") + port.send_input("Dummy") + + print("Moving down in history 1 time") + port.send_input(commands["down"]) + + out = port.recv_output() + + exit_if_condition(out != "DummyFooDummyBarDummy", + "[ERROR] Command does not match") + + + def test_deleting_with(delete_char: str): + print("Moving up in history 1 time") + port.send_input(commands["up"]) + + print("Moving to the start of the command") + port.send_input(commands["home"]) + + print("Delete first 'Dummy'") + port.send_input(delete_char * 5) + + print("Moving to the end of the command") + port.send_input(commands["end"]) + + print("Delete last 'Dummy'") + port.send_input(commands["backspace"] * 5) + + print("Moving to the begining of the middle 'Dummy'") + port.send_input(commands["left"] * 8) + + print("Delete middle 'Dummy'") + port.send_input(delete_char * 5) + + print("Moving down in history 1 time") + port.send_input(commands["down"]) + + out = port.recv_output() + + exit_if_condition(out != "FooBar", + "[ERROR] Command does not match") + + test_inserting() + out = port.send_input("\r\n") + port.clear_input() + + print("Testing with ANSI Escpae Sequence...") + test_deleting_with(commands["delete"]) + port.clear_input() + + print("Testing with ASCII character...") + test_deleting_with(commands["delete-ascii"]) + + out = port.send_input("\r\n") + port.clear_input() + pass_test("command_history_edit") + + +def read_serial_port_name(): + ''' Fetches the serial port name from the user ''' + serial_port_name = "" + + while serial_port_name == "": + serial_port_name = input("Enter the serial port name: ") + + return serial_port_name + + +def main(): + ''' Main loop to run all test cases ''' + port = test_open_serial_port(read_serial_port_name()) + + test_fill_command_history(port) + test_command_history_api(port) + test_inserting_at_end(port) + test_inserting_at_start(port) + test_inserting_in_middle(port) + test_inserting(port) + test_deleting(port) + test_cariage_return(port) + test_newline_return(port) + test_command_history_edit(port) + + port.finish() + + print_test_results() + + +main() diff --git a/tools/diff_memory_usage.py b/tools/diff_memory_usage.py index 90b4e53c0f..f0fd39be09 100755 --- a/tools/diff_memory_usage.py +++ b/tools/diff_memory_usage.py @@ -1,5 +1,9 @@ #!/usr/bin/env python3 +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + import argparse import sys @@ -17,20 +21,23 @@ def main(): prev_RAM = -1 cur_flash = -1 cur_RAM = -1 - with open(args.prev_bench, "r") as f: - for line in f: - if "Kernel occupies" in line: - if "flash" in line: - prev_flash = int(line.split()[2]) - elif "RAM" in line: - prev_RAM = int(line.split()[2]) - elif "Applications allocated" in line: - if "RAM" in line and prev_RAM > 0: + try: + with open(args.prev_bench, "r") as f: + for line in f: + if "Kernel occupies" in line: + if "flash" in line: + prev_flash = int(line.split()[2]) + elif "RAM" in line: + prev_RAM = int(line.split()[2]) + elif "Applications allocated" in line: + if "RAM" in line and prev_RAM > 0: + prev_RAM -= int(line.split()[2]) + elif "Total of" in line and "wasted" in line and "RAM" in line: + # Don't count wasted RAM as contributing to the count prev_RAM -= int(line.split()[2]) - elif "Total of" in line and "wasted" in line and "RAM" in line: - # Don't count wasted RAM as contributing to the count - prev_RAM -= int(line.split()[2]) - break + break + except FileNotFoundError: + sys.exit("No prior benchmark available for board: {}".format(board)) if prev_flash == -1 or prev_RAM == -1: sys.exit("Failed to parse prev_bench for board: {}".format(board)) diff --git a/tools/embedded_data_analyzer.py b/tools/embedded_data_analyzer.py new file mode 100755 index 0000000000..c178507837 --- /dev/null +++ b/tools/embedded_data_analyzer.py @@ -0,0 +1,324 @@ +#! /usr/bin/env python3 + +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +from audioop import add +from embedded_data_visualizer import create_html_file +from print_tock_memory_usage import parse_mangled_name +import argparse +from collections import defaultdict, namedtuple +import os +import sys +import re +HELP_STRING = ''' +Tock embedded data analysis tool + +Generates a report of a Tock binary's embedded data usage in the form of a static HTML webpage. + +Prerequisite: + - The binary must be compiled for the RISC-V architecture. + - Turn off LTO. To do so, set lto to false in the top-level Cargo.toml file. + +To run the tool, in the Tock home directory invoke: + + $ tools/embedded_data_analyzer.py -e -n -o [-d ] + +arguments: + elf_path -- the relative path of the compiled elf file + name -- a name for the binary to be displayed in the HTML page + output_directory -- where the user wishes the output to be stored + objdump -- (optional) path of the riscv objdump binary to use + +You can also run the script with -h flag for help on what the arguments are. +''' + + +def get_args(): + # To allow for a custom help message, the default help option is turned off, + # this means that required arguments must be checked manually + ap = argparse.ArgumentParser(add_help=False) + ap.add_argument('-e', '--elf_path', required=False) + ap.add_argument('-o', '--output_directory', required=False) + ap.add_argument('-n', '--name', required=False) + ap.add_argument('-h', '--help', required=False, + action='store_true', default=False) + ap.add_argument('-d', '--objdump', required=False) + return vars(ap.parse_args()) + + +def find_objdump(): + objdump = os.popen('which riscv32-unknown-elf-objdump').read().strip() + if objdump != '': + return objdump + + objdump = os.popen('which riscv64-unknown-elf-objdump').read().strip() + if objdump != '': + return objdump + + objdump = os.popen('which riscv32-none-elf-objdump').read().strip() + if objdump != '': + return objdump + + objdump = os.popen('which riscv64-none-elf-objdump').read().strip() + if objdump != '': + return objdump + + critical_error( + 'Objdump not found, please specify a riscv objdump path manually with -d or --objdump.') + + +FunctionInfo = namedtuple( + 'FunctionInfo', 'embedded_data_size_actual embedded_data_size_estimated embedded_data_count addresses') +SymbolInfo = namedtuple('SymbolInfo', 'actual_size estimated_size name') + + +def get_srodata_address(disasm): + pattern = '<_srodata>:' + for i in range(len(disasm)): + match = re.findall(pattern, disasm[i]) + if match: + return disasm[i].split()[0], i + critical_error('Invalid binary, cannot find <_srodata> section') + + +def map_adress_to_data(disasm): + _, start_index = get_srodata_address(disasm) + sro_address, sro_end = get_sro_range(disasm) + address_pattern = r'^\s*([0-9a-f]+):' + data_pattern = r'[ |\t][0-9a-f]{4}' + + sro_data = "" + for i in range(start_index, len(disasm)): + match = re.search(address_pattern, disasm[i]) + if match: + address = int(match.group(1), 16) + if (sro_address <= address < sro_end): + data = re.findall(data_pattern, disasm[i]) + if data: + # remove the \t character from first byte + data[0] = re.sub('\s+', '', data[0]) + for j in range(len(data)): + data[j] = data[j].strip() + # seperate the bytes + sro_data += bytes.fromhex(data[j][2:] + ).decode("utf-8", errors='replace') + sro_data += bytes.fromhex(data[j][0:2] + ).decode("utf-8", errors='replace') + + return sro_data + + +def get_sro_range(disasm): + data_pattern = r'[ |\t][0-9a-f]{4}' + address_pattern = r'^[0-9a-f]{1,16}' + sro_string, start_index = get_srodata_address(disasm) + sro_address = int(sro_string, 16) + previous_blank = False + last_address_seen = 0 + last_line = -1 + for i in range(start_index, len(disasm)): + if (disasm[i] == '\n'): + previous_blank = True + else: + match = re.findall(address_pattern, disasm[i]) + if match: + last_address_seen = int(match[0], 16) + last_line = i + previous_blank = False + elif previous_blank: + # if the last line was blank and current line has no address data has ended + break + + # address points to the beginning of the line of data + # must also add the length of data in the line itself + if last_line != -1: + data = re.findall(data_pattern, disasm[last_line]) + last_address_seen += len(data)*2 + + return sro_address, last_address_seen + + +def filter_symbol_table(disasm, symbols_table): + """ + Given the path to a symbols table, + trim the table to only contain embedded data objects, + save the trimeed table to a file, + and also return the trimmed table as a list of strings. + """ + sro_start, sro_end = get_sro_range(disasm) + + # trim garbage lines at the beginning and end + trimmed_lines = symbols_table[4:-4] + filtered = [] + for line in trimmed_lines: + # if it is an object, and it's in the code, + # and the address is within the range of srodata + address = int(line[:8], 16) + if address >= sro_start and address < sro_end: + filtered.append(line) + + return filtered + + +def estimate_empty_symbols(symbols_table): + items_list = list(symbols_table.items()) + items_list.sort() + res = {} + for i in range(len(items_list)): + address = items_list[i][0] + actual_size = items_list[i][1][0] + name = items_list[i][1][1] + estimated_size = actual_size + if (estimated_size == 0) and (i != len(items_list) - 1): + next_address = items_list[i+1][0] + estimated_size = next_address - address + res[address] = SymbolInfo( + actual_size=actual_size, estimated_size=estimated_size, name=name) + return res + + +def build_symbols_dict(symbols_table): + mapping = {} # address of symbol -> (size, name) + for line in symbols_table: + if '.debug' in line: + continue + + matches = re.search( + '^([0-9a-f]+)\s+[lg]\s+O?\s+\.text\s+([0-9a-f]+)\s+(.*)', line) + address = int(matches.group(1), 16) + size = int(matches.group(2), 16) + name = matches.group(3) + + if address not in mapping: + mapping[address] = (size, name) + else: + # replace zero-sized with non-zero-sized + if size == 0: + continue + if mapping[address][0] == 0: + mapping[address] = (size, name) + continue + + critical_error( + f'address 0x{address:x} mapped already: {mapping[address]}') + + return estimate_empty_symbols(mapping) + + +def trace_function(disasm, line_num): + # pattern to detect if there's a function in a line + func_pattern = r'^[0-9a-f]{1,16} <.*>:\n' + for i in range(line_num, -1, -1): + match = re.findall(func_pattern, disasm[i]) + if match: + func_name = disasm[i][10:-3] + if func_name[0] == '_': + return parse_mangled_name(func_name) + + +def account_symbols(disasm, symbols): + total_actual = 0 + total_estimated = 0 + found = set() + func_to_address = defaultdict(set) + address_to_func = defaultdict(set) + + # pattern to detect if there's an embedded data reference in a line + line_pattern = r'# [0-9a-f]{1,16} <.*>' + # pattern to extract address from a line + address_pattern = r' [0-9a-f]{1,16} ' + + for i in range(len(disasm)): + match = re.findall(line_pattern, disasm[i]) + if match: + address = int(re.findall(address_pattern, match[0])[0].strip(), 16) + if address in symbols: + func_name = trace_function(disasm, i) + func_to_address[func_name].add(address) + address_to_func[address].add(func_name) + if address not in found: + total_actual += symbols[address].actual_size + total_estimated += symbols[address].estimated_size + found.add(address) + + return total_actual, total_estimated, func_to_address, address_to_func + + +def add_size_information(func_to_address, symbols): + for func in func_to_address.keys(): + addresses = func_to_address[func] + actual_size = 0 + estimated_size = 0 + for address in addresses: + actual_size += symbols[address].actual_size + estimated_size += symbols[address].estimated_size + func_to_address[func] = FunctionInfo(embedded_data_size_actual=actual_size, + embedded_data_size_estimated=estimated_size, + embedded_data_count=len( + addresses), + addresses=addresses) + + +def colored_string(msg, color): + RED = '\033[31m' + YELLOW = '\033[93m' + END_COLOR = '\033[0m' + if color == 'red': + return RED + msg + END_COLOR + return YELLOW + msg + END_COLOR + + +def critical_error(msg): + print(colored_string(msg, 'red')) + print('use option --help or -h for more information') + sys.exit() + + +def check_architecture(elf_path): + readelf = os.popen(f'readelf -h {elf_path} | grep Machine').read() + if 'RISC-V' not in readelf: + critical_error('Error: elf file must be compiled for RISC-V') + + +def main(): + args = get_args() + + if args['help']: + print(HELP_STRING) + sys.exit() + if args['objdump'] is None: + objdump = find_objdump() + else: + objdump = args['objdump'] + if args['elf_path'] is None: + critical_error( + 'please provide an elf file with the --elf_path/-e option') + if args['name'] is None: + critical_error( + 'please provide a name to be displayed in the HTML page with the --name/-n option') + if args['output_directory'] is None: + critical_error( + 'please provide an output directory with the --output_directory/-o option') + + check_architecture(args['elf_path']) + + disasm = os.popen(f'{objdump} -d {args["elf_path"]}').readlines() + symbols = os.popen(f'{objdump} -t {args["elf_path"]}').readlines() + + symbols_table = filter_symbol_table(disasm, symbols) + symbols_dict = build_symbols_dict(symbols_table) + total_actual, total_estimated, func_to_address, address_to_func = account_symbols( + disasm, symbols_dict) + add_size_information(func_to_address, symbols_dict) + + sro_data = map_adress_to_data(disasm) + sro_start, sro_end = get_sro_range(disasm) + + create_html_file(args['name'], func_to_address, symbols_dict, + args['output_directory'], sro_data, sro_start) + + +if __name__ == '__main__': + main() diff --git a/tools/embedded_data_visualizer.py b/tools/embedded_data_visualizer.py new file mode 100644 index 0000000000..85be808b2c --- /dev/null +++ b/tools/embedded_data_visualizer.py @@ -0,0 +1,252 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +import binascii +import os +from collections import defaultdict + +HTML_HEADER = ''' + + + + + + Tock Embedded Data + + + +''' + +HTML_FOOTER = ''' + + + + + +''' + +ENTRY_TABLE_HEADER = ''' + + + + + + + + + + +''' + +FUNC_TABLE_HEADER = ''' +
De-mangled Function NameEmbedded Data Size (Estimated)Embedded Data Size (Reported)Embedded Data Count
+ + + + + + + + + +''' + + +def create_function_page(func_name, addresses, symbols_dict, sro_data, sro_start): + symbol_infos = [] + for address in addresses: + estimated_size = symbols_dict[address].estimated_size + name = symbols_dict[address].name + actual_size = symbols_dict[address].actual_size + start_index = address - sro_start + data = sro_data[start_index:start_index+estimated_size] + symbol_infos.append(( + name, + estimated_size, + actual_size, + data + )) + + symbol_infos.sort(key=lambda x: x[1], reverse=True) + + rows = [] + for i in range(len(symbol_infos)): + info = symbol_infos[i] + + ascii_data = info[3] + hex_data = binascii.hexlify( + bytes(ascii_data, encoding='utf-8')).decode('utf-8') + formatted_hex_data = '' + for j in range(0, len(hex_data), 4): + formatted_hex_data += f'{hex_data[j:j+4]} ' + + initial_hex = ascii_data.count(u'\uFFFD') > (len(ascii_data) // 2) + + hex_font = ' style=\"font-family: monospace, monospace;\"' + + escaped_ascii_data = ascii_data.replace('`', '\`') + + data_html = f''' + + ''' + + entry_string = f''' + + + + + {data_html} + + ''' + rows.append(entry_string) + + header = HTML_HEADER.replace( + 'Tock Embedded Data', f'{func_name}') + + name_header = f''' +

{func_name}

+ ''' + + return header + FUNC_TABLE_HEADER + name_header + ''.join(rows) + HTML_FOOTER + + +def get_table_entry_string(function_name, info, index): + html_string = f''' + + + + + + + ''' + return html_string + + +def sort_functions(func_to_address_list): + grouped_by_crate_dict = defaultdict(list) + for func_to_address in func_to_address_list: + func_name = func_to_address[0] + found = func_name.find(':') + crate = '' if found == -1 else func_name[:found] + grouped_by_crate_dict[crate].append(func_to_address) + + grouped_by_crate_list = [] + for crate in grouped_by_crate_dict.keys(): + func_list = grouped_by_crate_dict[crate] + crate_size = sum(x[1].embedded_data_size_estimated for x in func_list) + func_list.sort( + key=lambda x: x[1].embedded_data_size_estimated, reverse=True) + grouped_by_crate_list.append((crate_size, func_list)) + + grouped_by_crate_list.sort(key=lambda x: x[0], reverse=True) + + return [func for crate in grouped_by_crate_list for func in crate[1]] + + +def create_html_strings(name, func_to_address, symbols_dict, sro_data, sro_start): + func_to_address_list = list(func_to_address.items()) + func_to_address_list = sort_functions(func_to_address_list) + + table_rows = [] + function_pages = [] + for i in range(len(func_to_address_list)): + function_name, info = func_to_address_list[i] + html = get_table_entry_string(function_name, info, i) + table_rows.append(html) + function_pages.append(create_function_page( + function_name, info.addresses, symbols_dict, sro_data, sro_start)) + + header = HTML_HEADER.replace( + 'Tock Embedded Data', f'{name}') + + name_header = f''' +

{name}

+ ''' + + index_page = header + name_header + ENTRY_TABLE_HEADER + \ + ''.join(table_rows) + HTML_FOOTER + + return index_page, function_pages + + +def write_file(path, str): + with open(path, 'w') as f: + f.truncate(0) + f.write(str) + + +def create_html_file(name, func_to_address, symbols_dict, file_path, sro_data, sro_start): + funcs_path = os.path.join(file_path, 'funcs') + os.makedirs(funcs_path, exist_ok=True) + + index, funcs = create_html_strings( + name, func_to_address, symbols_dict, sro_data, sro_start) + write_file(os.path.join(file_path, f'{name}.html'), index) + for i in range(len(funcs)): + write_file(os.path.join(funcs_path, f'{i}.html'), funcs[i]) + + print( + f'html file containing embedded data information written to {file_path}{os.sep}{name}.html') diff --git a/tools/find_panics.py b/tools/find_panics.py index d4af3cb8e5..f4bad3b35f 100755 --- a/tools/find_panics.py +++ b/tools/find_panics.py @@ -1,5 +1,9 @@ #!/usr/bin/env python3 +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + # Prints out the source locations of panics in a Tock kernel ELF # # This tool attempts to trace all panic locations in a Tock kernel ELF by diff --git a/tools/github_actions_size_changes.sh b/tools/github_actions_size_changes.sh index 75f3097a2c..2c56e2efcd 100755 --- a/tools/github_actions_size_changes.sh +++ b/tools/github_actions_size_changes.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + # Post github check indicating how a PR affects flash and RAM use for different boards. # This script is run by Github actions after successful PR builds. It reports resource differences between # the target branch before and after merging in the PR. diff --git a/tools/license-checker/Cargo.toml b/tools/license-checker/Cargo.toml new file mode 100644 index 0000000000..3d7c1d6f04 --- /dev/null +++ b/tools/license-checker/Cargo.toml @@ -0,0 +1,21 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. +# Copyright Google LLC 2022. + +[package] +name = "license-checker" +version = "0.1.0" +authors.workspace = true +edition.workspace = true + +[dependencies] +clap = { features = ["derive"], version = "4.0.29" } +colored = "2.0.1" +ignore = "0.4" +thiserror = "1.0.44" + +[dependencies.syntect] +default-features = false +features = ["default-syntaxes", "regex-onig", "yaml-load"] +version = "5.2.0" diff --git a/tools/license-checker/README.md b/tools/license-checker/README.md new file mode 100644 index 0000000000..57c2673c38 --- /dev/null +++ b/tools/license-checker/README.md @@ -0,0 +1,5 @@ +Tock License Checker +==================== + +Tool that verifies that all files in the Tock repository have a valid license +header. diff --git a/tools/license-checker/src/fallback_syntax.yaml b/tools/license-checker/src/fallback_syntax.yaml new file mode 100644 index 0000000000..5913cbc6d2 --- /dev/null +++ b/tools/license-checker/src/fallback_syntax.yaml @@ -0,0 +1,72 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. +# Copyright Google LLC 2023. + +# This syntax definition is used by the license checker to parse files that +# syntect does not recognize. It contains generic support for a handful of +# common comment types. + +scope: source.fallback + +contexts: + # The main context looks for the first not-entirely-whitespace line, and uses + # it to detect what type of comment the header uses. Once detected, it + # switches to one of the has_*_comments contexts, and that context will remain + # for the remainder of the file. + main: + # When checking for number sign comments, allow either the first line of a + # comment or a shebang line. + - match: ^#(!| ) + scope: punctuation.definition.comment.fallback + push: [has_number_comments, number_comment] + + - match: "^// " + scope: punctuation.definition.comment.fallback + push: [has_slashes_comments, slashes_comment] + + - match: "^/\\*" + scope: punctuation.definition.comment.fallback + push: [has_slashstar_comments, slashstar_comment] + + # If none of the above matchers match, and this line is not entirely + # whitespace, then assume the filetype does not support comments. + - match: "\\S" + push: has_no_comments + + has_number_comments: + - match: "^#" + scope: punctuation.definition.comment.fallback + push: number_comment + + number_comment: + - meta_scope: comment.line.number-sign.fallback + - match: $\n? + pop: true + + has_slashes_comments: + - match: "^// " + scope: punctuation.definition.comment.fallback + push: slashes_comment + + slashes_comment: + - meta_scope: comment.line.double-slash.fallback + - match: $\n? + pop: true + + # /* */ -style comments. This does not allow for nested comments. + has_slashstar_comments: + - match: "^/\\*" + scope: punctuation.definition.comment.fallback + push: slashstar_comment + + slashstar_comment: + - meta_scope: comment.block.fallback + - match: "\\*/" + scope: punctuation.definition.comment.fallback + pop: true + + # Context used for files that do not have comments, e.g. plain text files. For + # these, we consider every line to be a comment. + has_no_comments: + - meta_scope: comment.block.fallback diff --git a/tools/license-checker/src/main.rs b/tools/license-checker/src/main.rs new file mode 100644 index 0000000000..54dacf0646 --- /dev/null +++ b/tools/license-checker/src/main.rs @@ -0,0 +1,356 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. +// Copyright Google LLC 2022. + +//! License-header checking tool for the Tock project. +//! +//! # Description +//! This tool recursively traverses through the current working directory, +//! verifying that every source code file inside has a Tock project license +//! header. See `/doc/reference/trd-legal.md` for a description of the Tock +//! license header format. +//! +//! # Ignore files +//! This tool respects gitignore files with the following names (ordered from +//! highest-precedence to lowest-precedence): +//! 1. .lcignore +//! 2. .ignore +//! 3. .gitignore +//! +//! This ignore file handling is modelled after the `ripgrep` tool. Unlike +//! `ripgrep`, however, the license checker looks at hidden files by default. +//! +//! # Comment styles supported +//! This tool supports various types of line comments: +//! ```text +//! # Hash-style comments +//! // C++-style line comments +//! /* Block comments. */ +//! ``` +//! Plain-text files can have license headers without comment punctuation: +//! ```text +//! Licensed under the Apache License, Version 2.0 or the MIT License. +//! SPDX-License-Identifier: Apache-2.0 OR MIT +//! Copyright Tock Contributors 2023. +//! +//! This is an example plain-text file. +//! ``` +//! Note however that the license checker does not support the following comment +//! styles: +//! ``` +//! /// Doc comments +//! /** of any type **/ +//! /* Block comments with leading asterisks on each line. This style is +//! * difficult to support in a filetype-agnostic way. +//! */ +//! ``` +//! A license header can be followed by an empty comment line, but not preceded +//! by one. In other words, the following is acceptable: +//! ``` +//! // Licensed under the Apache License, Version 2.0 or the MIT License. +//! // SPDX-License-Identifier: Apache-2.0 OR MIT +//! // Copyright Tock Contributors 2023. +//! // +//! // The foobar crate does ... +//! ``` +//! and the following is not accepted: +//! ``` +//! // The foobar crate does ... +//! // +//! // Licensed under the Apache License, Version 2.0 or the MIT License. +//! // SPDX-License-Identifier: Apache-2.0 OR MIT +//! // Copyright Tock Contributors 2023. +//! ``` +//! This does prevent the following comment style from working: +//! ``` +//! /* +//! Licensed under the Apache License, Version 2.0 or the MIT License. +//! SPDX-License-Identifier: Apache-2.0 OR MIT +//! Copyright Tock Contributors 2023. +//! */ +//! ``` +//! because it is preceded by an empty comment, not a blank line. Instead, in +//! languages that lack a line comment syntax, use the following style: +//! ``` +//! /* Licensed under the Apache License, Version 2.0 or the MIT License. */ +//! /* SPDX-License-Identifier: Apache-2.0 OR MIT */ +//! /* Copyright Tock Contributors 2023. */ +//! ``` +//! +//! # Design philosophy +//! This license checker is designed to catch easy-to-make and hard-to-catch +//! mistakes, such as forgetting to add a license header, or putting the header +//! lines in an incorrect order. It does not attempt to enforce every detail of +//! the Tock license header format. It makes a compromise between catching most +//! common errors and simplicity of implementation. + +#![allow(rustdoc::invalid_rust_codeblocks)] + +use colored::ColoredString; +use colored::Colorize; +use ignore::WalkBuilder; +use std::path::{Path, PathBuf}; +use std::process::exit; + +mod parser; +use parser::{Cache, LineContents, ParseError, Parser}; + +const LICENSED_LINE: &str = "Licensed under the Apache License, Version 2.0 or the MIT License."; +const SPDX_LINE: &str = "SPDX-License-Identifier: Apache-2.0 OR MIT"; + +fn is_first(comment: &str) -> bool { + comment.starts_with("Licensed under ") +} +fn is_spdx(comment: &str) -> bool { + comment.starts_with("SPDX-License-Identifier:") +} +fn is_copyright(comment: &str) -> bool { + comment.starts_with("Copyright ") +} + +#[derive(clap::Parser)] +/// See the comment at the top of src/main.rs for documentation. +struct Args { + /// Enable verbose debugging output + #[arg(long, short)] + verbose: bool, +} + +fn error_prefix() -> ColoredString { + "error:".bright_red().bold() +} + +#[derive(Debug, thiserror::Error, PartialEq)] +enum LicenseError { + #[error("{} {}", error_prefix(), "license header missing")] + Missing, + + #[error("{} {}", error_prefix(), "missing blank line after header")] + MissingBlank, + + #[error("{} {}", error_prefix(), "missing copyright line")] + MissingCopyright, + + #[error("{} {}", error_prefix(), "missing SPDX line")] + MissingSpdx, + + #[error("{} {}", error_prefix(), "incorrect first line")] + WrongFirst, + + #[error("{} {}", error_prefix(), "wrong SPDX line")] + WrongSpdx, +} + +#[derive(Debug, PartialEq)] +struct ErrorInfo { + file: PathBuf, + line_num: u64, + error: LicenseError, +} + +#[derive(PartialEq)] +enum State { + /// We need a blank line before the header can start. This state is entered + /// if there is a non-blank, non-license-header line before the license + /// header. + NeedBlank, + + /// We have not yet found a header, and are ready for one. This is the + /// starting (top-of-file) state, and is re-entered after a blank line if a + /// header has not been found. + ReadyForHeader, + + /// We have found the first line of the header and the next must be the SPDX + /// line. + NeedSpdx, + + /// We have found the first and SPDX line and now need a copyright line. + NeedCopyright, + + /// We have found at least one copyright and are now waiting for the header + /// to end. + WaitForEnd, + + /// The complete header (with or without errors) has been found, and we do + /// not need to continue processing this file. + Done, +} + +fn check_file(cache: &Cache, path: &Path) -> Vec { + use LicenseError::*; + use LineContents::*; + use State::*; + + let mut license_errors = vec![]; + let mut parser = match Parser::new(cache, path) { + Err(ParseError::Binary) => return vec![], + Err(error) => panic!("{}: {}", path.display(), error), + Ok(parser) => parser, + }; + let mut line_num = 0; + let mut state = ReadyForHeader; + while state != Done { + line_num += 1; + let line_contents = match parser.next() { + Err(ParseError::Binary) => return vec![], + // Coerce end-of-file into Other, as they are treated identically. + Err(ParseError::Eof) => Other, + Err(error) => panic!("Parse error at {}:{}: {}", path.display(), line_num, error), + Ok(contents) => contents, + }; + let (new_state, error) = match (state, line_contents) { + (NeedBlank, Comment(_)) => (NeedBlank, None), + (NeedBlank, Whitespace) => (ReadyForHeader, None), + (NeedBlank, Other) => (Done, Some(Missing)), + (ReadyForHeader, Comment(comment)) if !is_first(comment) => (NeedBlank, None), + (ReadyForHeader, Comment(comment)) if comment == LICENSED_LINE => (NeedSpdx, None), + (ReadyForHeader, Comment(_)) => (NeedSpdx, Some(WrongFirst)), + (ReadyForHeader, Whitespace) => (ReadyForHeader, None), + (ReadyForHeader, Other) => (Done, Some(Missing)), + (NeedSpdx, Comment(comment)) if comment == SPDX_LINE => (NeedCopyright, None), + (NeedSpdx, Comment(comment)) if is_spdx(comment) => (NeedCopyright, Some(WrongSpdx)), + (NeedSpdx, _) => (Done, Some(MissingSpdx)), + (NeedCopyright, Comment(comment)) if is_copyright(comment) => (WaitForEnd, None), + (NeedCopyright, _) => (Done, Some(MissingCopyright)), + (WaitForEnd, Comment(comment)) if is_copyright(comment) => (WaitForEnd, None), + (WaitForEnd, Comment("")) => (Done, None), + (WaitForEnd, Whitespace) => (Done, None), + (WaitForEnd, _) => (Done, Some(MissingBlank)), + (Done, _) => unreachable!("Loop didn't end at EOF"), + }; + state = new_state; + if let Some(error) = error { + license_errors.push(ErrorInfo { + file: path.to_owned(), + line_num, + error, + }); + } + } + license_errors +} + +fn main() { + use clap::Parser as _; + let args = Args::parse(); + let cache = &Cache::default(); + let fs_walk = WalkBuilder::new("./") + .add_custom_ignore_filename(".lcignore") + .git_exclude(false) + .git_global(false) + .hidden(false) + .require_git(false) + .build(); + + let mut failed = false; + for result in fs_walk { + let dir_entry = result.expect("Directory walk failed"); + let file_type = dir_entry.file_type().expect("File type read failed"); + if !file_type.is_file() { + continue; + } + if args.verbose { + println!("Checking {}", dir_entry.path().display()); + } + for error_info in check_file(cache, dir_entry.path()) { + failed = true; + eprintln!( + "{}:{}: {}", + error_info.file.display(), + error_info.line_num, + error_info.error + ); + } + } + + if !failed { + println!("License check passed."); + return; + } + exit(1); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn empty_trailing_comment() { + assert_eq!( + check_file(&Cache::default(), Path::new("testdata/blank_is_comment.rs")), + [] + ); + } + + #[test] + fn many_errors() { + assert_eq!( + check_file(&Cache::default(), Path::new("testdata/many_errors.rs")), + [ + ErrorInfo { + file: "testdata/many_errors.rs".into(), + line_num: 1, + error: LicenseError::WrongFirst, + }, + ErrorInfo { + file: "testdata/many_errors.rs".into(), + line_num: 2, + error: LicenseError::WrongSpdx, + }, + ErrorInfo { + file: "testdata/many_errors.rs".into(), + line_num: 5, + error: LicenseError::MissingBlank, + }, + ] + ); + } + + #[test] + fn missing() { + assert_eq!( + check_file(&Cache::default(), Path::new("testdata/error_missing.rs")), + [ErrorInfo { + file: "testdata/error_missing.rs".into(), + line_num: 1, + error: LicenseError::Missing + }] + ); + } + + #[test] + fn missing_copyright() { + assert_eq!( + check_file(&Cache::default(), Path::new("testdata/no_copyright.rs")), + [ErrorInfo { + file: "testdata/no_copyright.rs".into(), + line_num: 3, + error: LicenseError::MissingCopyright + }] + ); + } + + #[test] + fn missing_spdx() { + assert_eq!( + check_file(&Cache::default(), Path::new("testdata/no_spdx.rs")), + [ErrorInfo { + file: "testdata/no_spdx.rs".into(), + line_num: 2, + error: LicenseError::MissingSpdx + }] + ); + } + + /// Run check_file on a file that should have a valid header. Note this file + /// has a shebang line, so it will have to search past the first line to + /// find the header. + #[test] + fn successful() { + assert_eq!( + check_file(&Cache::default(), Path::new("testdata/by_first_line")), + [] + ); + } +} diff --git a/tools/license-checker/src/parser.rs b/tools/license-checker/src/parser.rs new file mode 100644 index 0000000000..c9cf7e0c3b --- /dev/null +++ b/tools/license-checker/src/parser.rs @@ -0,0 +1,426 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. +// Copyright Google LLC 2022. + +//! A partial parser that parses source code files *just well enough* to find +//! license headers. `Parser` needs some slow-to-initialize resources, so each +//! `Parser` must be initialized with a reference to a `Cache` instance. +//! +//! It is built on top of the [`syntect`](https://crates.io/crates/syntect) +//! crate. `syntect` is designed to perform syntax highlighting for text +//! editors, and can therefore parse a variety of common languages. However, it +//! cannot parse every language present in the Tock project. For languages that +//! `syntect` does not have a definition for, we use a fallback syntax, defined +//! in `fallback_syntax.yaml`. + +use std::fs::File; +use std::io::{self, BufRead, BufReader, ErrorKind}; +use std::ops::Range; +use std::path::Path; +use std::str::FromStr; +use syntect::easy::ScopeRangeIterator; +use syntect::highlighting::ScopeSelector; +use syntect::parsing::syntax_definition::SyntaxDefinition; +use syntect::parsing::{ParseState, ParsingError, ScopeError, ScopeStack, SyntaxSet}; + +const FALLBACK_NAME: &str = "fallback"; + +pub struct Cache { + is_comment: ScopeSelector, + is_punctuation: ScopeSelector, + // Syntect's plain text syntax does not mark anything as comments, which + // causes the license check to fail. Instead, plain text files should use + // the fallback parser. This stores the name of the plain text syntax, so + // that Parser can detect when syntect has returned the plain text syntax + // for a file. + plain_text_name: String, + syntax_set: SyntaxSet, +} + +impl Default for Cache { + fn default() -> Self { + const COMMENT_SELECTOR: &str = + "comment - comment.block.documentation - comment.line.documentation"; + const FALLBACK_SYNTAX: &str = include_str!("fallback_syntax.yaml"); + let mut builder = SyntaxSet::load_defaults_newlines().into_builder(); + builder.add( + SyntaxDefinition::load_from_str(FALLBACK_SYNTAX, true, Some(FALLBACK_NAME)) + .expect("Failed to parse fallback syntax"), + ); + let syntax_set = builder.build(); + Self { + is_comment: ScopeSelector::from_str(COMMENT_SELECTOR).unwrap(), + is_punctuation: ScopeSelector::from_str("punctuation.definition.comment").unwrap(), + plain_text_name: syntax_set.find_syntax_plain_text().name.clone(), + syntax_set, + } + } +} + +#[derive(Debug, thiserror::Error)] +#[rustfmt::skip] +pub enum ParseError { + #[error("binary file")] Binary, + #[error("end-of-file")] Eof, + #[error("bad byte span")] BadSpan, + #[error("io error {0}")] IoError(#[from] io::Error), + #[error("parse error {0}")] ParsingError(#[from] ParsingError), + #[error("scope error {0}")] ScopeError(#[from] ScopeError), +} + +impl PartialEq for ParseError { + fn eq(&self, rhs: &Self) -> bool { + use ParseError::*; + match (self, rhs) { + (Binary, Binary) => true, + (Eof, Eof) => true, + (BadSpan, BadSpan) => true, + _ => false, + } + } +} + +/// Indicates what a particular line of source code contains. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum LineContents<'l> { + /// This line of code consists only of whitespace and one comment. The + /// contained string slice points to the contents of the comment with + /// whitespace trimmed away. + Comment(&'l str), + + /// This line is not a comment and contains only whitespace. + Whitespace, + + /// This line contains more than one comment (e.g. /* A */ /* B */) or text + /// that is neither whitespace nor part of a comment. + Other, +} + +pub struct Parser<'cache> { + cache: &'cache Cache, + line: String, + parse_state: ParseState, + reader: BufReader, + scopes: ScopeStack, +} + +impl<'cache> Parser<'cache> { + /// Creates a new `Parser` that reads the specified file. Will return None + /// if the file is binary. + pub fn new(cache: &'cache Cache, path: &Path) -> Result { + let syntax = match cache.syntax_set.find_syntax_for_file(path) { + Err(error) if error.kind() == ErrorKind::InvalidData => return Err(ParseError::Binary), + Err(error) => return Err(error.into()), + Ok(Some(syntax)) if syntax.name != cache.plain_text_name => syntax, + Ok(_) => cache.syntax_set.find_syntax_by_name(FALLBACK_NAME).unwrap(), + }; + + Ok(Self { + cache, + line: String::new(), + parse_state: ParseState::new(syntax), + reader: BufReader::new(File::open(path)?), + scopes: ScopeStack::new(), + }) + } + + /// Parses the next line and returns its contents. Returns + /// Err(ParseError::Eof) at EOF. + pub fn next(&mut self) -> Result { + self.line.clear(); + let bytes_read = match self.reader.read_line(&mut self.line) { + Err(error) if error.kind() == ErrorKind::InvalidData => return Err(ParseError::Binary), + Err(error) => return Err(error.into()), + Ok(bytes) => bytes, + }; + if bytes_read == 0 { + // End of file. + return Err(ParseError::Eof); + } + + // Comments are assumed to take the following overall form, using SGML + // as a pathological example: + // + // + // |--Opening punctuation--|--Body text--|--Closing punctuation--| + // + // The opening punctuation and closing punctuation are both optional -- + // e.g. line comments do not have closing punctuation. The opening and + // closing punctuation consist of a mix of whitespace and non-whitespace + // punctuation characters. + // + // Yes, this means that the boundary between opening punctuation and the + // body text is vague if that boundary consists of whitespace. That is + // fine because the comment body will be trimmed, so the final return is + // deterministic. + enum State { + // All text processed so far has been whitespace and not part of a + // comment. + WhitespaceOnly, + // The loop has processed comment-begin punctuation (#, //, /*) + // and/or comment whitespace but no comment text. + CommentStart, + // The loop has processed comment body text. The range is the byte + // span of the comment body, possibly with some whitespace trimmed + // off. + CommentBody(Range), + // The loop has processed comment-end punctuation (*/). The range is + // the same as for CommentBody. + CommentEnd(Range), + // This line fits the criteria of LineContents::Other. + Other, + } + + let cache = self.cache; + let ops = self.parse_state.parse_line(&self.line, &cache.syntax_set)?; + let mut state = match cache.is_comment.does_match(self.scopes.as_slice()) { + None => State::WhitespaceOnly, + Some(_) => State::CommentStart, + }; + + for (span, op) in ScopeRangeIterator::new(&ops, &self.line) { + self.scopes.apply(op)?; + // Syntaxes sometimes push and pop spurious scopes; don't bother + // checking the scopes until we have a non-empty span. + if span.is_empty() { + continue; + } + + // Classification applied to each span based on what scopes apply. + enum FragmentType { + Whitespace, // Whitespace OUTSIDE a comment. + CommentPunctuation, + CommentText, + Other, + } + let scopes = self.scopes.as_slice(); + let span_str = self.line.get(span.clone()).ok_or(ParseError::BadSpan)?; + let fragment_type = match cache.is_comment.does_match(scopes) { + None => match span_str.chars().all(char::is_whitespace) { + true => FragmentType::Whitespace, + false => FragmentType::Other, + }, + Some(_) => match cache.is_punctuation.does_match(scopes) { + Some(_) => FragmentType::CommentPunctuation, + None => FragmentType::CommentText, + }, + }; + state = match (state, fragment_type) { + (_, FragmentType::Other) => State::Other, + (State::WhitespaceOnly, FragmentType::Whitespace) => State::WhitespaceOnly, + (State::WhitespaceOnly, FragmentType::CommentPunctuation) => State::CommentStart, + (State::WhitespaceOnly, FragmentType::CommentText) => State::CommentBody(span), + (State::CommentStart, FragmentType::CommentText) => State::CommentBody(span), + (State::CommentStart, _) => State::CommentStart, + (State::CommentBody(old_span), FragmentType::CommentPunctuation) => { + State::CommentEnd(old_span) + } + (State::CommentBody(old_span), _) if span.start == old_span.end => { + State::CommentBody(old_span.start..span.end) + } + (State::CommentBody(_), _) => State::Other, // Disjointed comments + (State::CommentEnd(_), FragmentType::CommentText) => State::Other, + (State::CommentEnd(old_span), _) => State::CommentEnd(old_span), + (State::Other, _) => State::Other, + }; + } + + Ok(match state { + State::WhitespaceOnly => LineContents::Whitespace, + State::CommentStart => LineContents::Comment(""), + State::CommentBody(span) => LineContents::Comment(self.line[span].trim()), + State::CommentEnd(span) => LineContents::Comment(self.line[span].trim()), + State::Other => LineContents::Other, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use LineContents::*; + + // Test function that confirms the parser produces a particular sequence of + // LineContents. + #[track_caller] + fn assert_produces(parser: Result, sequence: &[LineContents]) { + let mut parser = parser.unwrap(); + for &expected in sequence { + assert_eq!(parser.next(), Ok(expected)); + } + assert_eq!(parser.next(), Err(ParseError::Eof)); + } + + // Test with a file that causes SyntaxSet::find_syntax_for_file to return an + // invalid data error (which binary files can cause). + #[test] + fn binary() { + use ParseError::Binary; + let binary = Path::new("testdata/binary"); + let cache = &Cache::default(); + assert!(matches!(Parser::new(cache, binary), Err(Binary))); + } + + // Confirm Parser correctly processes files identified by their shebang + // lines if their extension is unknown. + #[test] + fn by_first_line() { + const EXPECTED: &[LineContents] = &[ + Comment("!/bin/bash"), + Whitespace, + Comment("Licensed under the Apache License, Version 2.0 or the MIT License."), + Comment("SPDX-License-Identifier: Apache-2.0 OR MIT"), + Comment("Copyright Tock Contributors 2022."), + Comment("Copyright Google LLC 2022."), + Whitespace, + Other, + Other, + ]; + let by_first_line = Path::new("testdata/by_first_line"); + assert_produces(Parser::new(&Cache::default(), by_first_line), EXPECTED); + } + + #[test] + fn fallback_block_comments() { + const EXPECTED: &[LineContents] = &[ + Comment("Licensed under the Apache License, Version 2.0 or the MIT License."), + Comment("SPDX-License-Identifier: Apache-2.0 OR MIT"), + Comment("Copyright Tock Contributors 2023."), + Comment("Copyright Google LLC 2023."), + Whitespace, + Comment(""), + Comment("This file contains two styles of block comment."), + Comment(""), + ]; + let path = Path::new("testdata/block_comments.ld"); + assert_produces(Parser::new(&Cache::default(), path), EXPECTED); + } + + #[test] + fn fallback_number_signs() { + const EXPECTED: &[LineContents] = &[ + Comment("Licensed under the Apache License, Version 2.0 or the MIT License."), + Comment("SPDX-License-Identifier: Apache-2.0 OR MIT"), + Comment("Copyright Tock Contributors 2023."), + Comment("Copyright Google LLC 2023."), + Whitespace, + Other, + Other, + Other, + Comment("Lines starting with a '#' without a space are comments too."), + ]; + let path = Path::new("testdata/number_signs.fallback"); + assert_produces(Parser::new(&Cache::default(), path), EXPECTED); + } + + #[test] + fn fallback_shebang() { + const EXPECTED: &[LineContents] = &[ + Comment("Shebang line here"), + Whitespace, + Comment("Licensed under the Apache License, Version 2.0 or the MIT License."), + Comment("SPDX-License-Identifier: Apache-2.0 OR MIT"), + Comment("Copyright Tock Contributors 2023."), + Comment("Copyright Google LLC 2023."), + Whitespace, + Other, + Other, + Other, + ]; + let path = Path::new("testdata/shebang.fallback"); + assert_produces(Parser::new(&Cache::default(), path), EXPECTED); + } + + #[test] + fn fallback_slashes() { + const EXPECTED: &[LineContents] = &[ + Comment("Licensed under the Apache License, Version 2.0 or the MIT License."), + Comment("SPDX-License-Identifier: Apache-2.0 OR MIT"), + Comment("Copyright Tock Contributors 2023."), + Comment("Copyright Google LLC 2023."), + Whitespace, + Other, + Other, + Other, + ]; + let path = Path::new("testdata/slashes.fallback"); + assert_produces(Parser::new(&Cache::default(), path), EXPECTED); + } + + #[test] + fn plain_text_no_extension() { + const EXPECTED: &[LineContents] = &[ + Comment("Licensed under the Apache License, Version 2.0 or the MIT License."), + Comment("SPDX-License-Identifier: Apache-2.0 OR MIT"), + Comment("Copyright Tock Contributors 2023."), + Comment("Copyright Google LLC 2023."), + Comment(""), + Comment("This is a plain text file; the license checker should recognize that it does"), + Comment("# not use a common comment syntax."), + ]; + let path = Path::new("testdata/plain_text_no_extension"); + assert_produces(Parser::new(&Cache::default(), path), EXPECTED); + } + + #[test] + fn plain_text_txt() { + const EXPECTED: &[LineContents] = &[ + Comment("Licensed under the Apache License, Version 2.0 or the MIT License."), + Comment("SPDX-License-Identifier: Apache-2.0 OR MIT"), + Comment("Copyright Tock Contributors 2024."), + Comment("Copyright Google LLC 2024."), + Comment(""), + Comment("This is a plain text file with the .txt extension. The license checker"), + Comment("should use the fallback parser, even though syntect may recognize it as a"), + Comment("plain text file."), + ]; + let path = Path::new("testdata/plain_text.txt"); + assert_produces(Parser::new(&Cache::default(), path), EXPECTED); + } + + // Test Parser with a variety of line types. This also confirms that syntect + // can identify a file type by extension. + #[test] + fn various_line_types() { + const EXPECTED: &[LineContents] = &[ + Comment("Licensed under the Apache License, Version 2.0 or the MIT License."), + Comment("SPDX-License-Identifier: Apache-2.0 OR MIT"), + Comment("Copyright Tock Contributors 2022."), + Comment("Copyright Google LLC 2022."), + Whitespace, + Comment("Syntect should recognize this file's type by extension."), + Whitespace, + Other, + Comment(""), + Whitespace, + Comment("Multi-line comment. The next comment contains only whitespace."), + Comment(""), + Comment(""), + Whitespace, + Other, + Other, + Whitespace, + Whitespace, + Other, + ]; + let path = Path::new("testdata/variety.rs"); + assert_produces(Parser::new(&Cache::default(), path), EXPECTED); + } + + #[test] + fn xml_block_comments() { + const EXPECTED: &[LineContents] = &[ + Comment("Licensed under the Apache License, Version 2.0 or the MIT License."), + Comment("SPDX-License-Identifier: Apache-2.0 OR MIT"), + Comment("Copyright Tock Contributors 2023."), + Comment("Copyright Google LLC 2023."), + Whitespace, + Comment(""), + Comment("This file contains two styles of block comment."), + Comment(""), + ]; + let path = Path::new("testdata/block_comments.xml"); + assert_produces(Parser::new(&Cache::default(), path), EXPECTED); + } +} diff --git a/tools/license-checker/testdata/binary b/tools/license-checker/testdata/binary new file mode 100644 index 0000000000..b47bbce93d --- /dev/null +++ b/tools/license-checker/testdata/binary @@ -0,0 +1,10 @@ +� + +Licensed under the Apache License, Version 2.0 or the MIT License. +Copyright Tock Contributors 2022. +Copyright Google LLC 2022. +SPDX-License-Identifier: Apache-2.0 OR MIT + +The first line of this file is invalid UTF-8, which results in +find_syntax_for_file returning an error. The license checker should treat this +file as binary. diff --git a/tools/license-checker/testdata/blank_is_comment.rs b/tools/license-checker/testdata/blank_is_comment.rs new file mode 100644 index 0000000000..70e6786e85 --- /dev/null +++ b/tools/license-checker/testdata/blank_is_comment.rs @@ -0,0 +1,8 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. +// Copyright Google LLC 2023. +// +// This file has an empty comment line rather than a truly blank line after the +// header. This should be accepted as well, as it makes sense when the Tock +// license header is followed by an "Author: " comment. diff --git a/tools/license-checker/testdata/block_comments.ld b/tools/license-checker/testdata/block_comments.ld new file mode 100644 index 0000000000..6a8ec117cd --- /dev/null +++ b/tools/license-checker/testdata/block_comments.ld @@ -0,0 +1,8 @@ +/* Licensed under the Apache License, Version 2.0 or the MIT License. */ +/* SPDX-License-Identifier: Apache-2.0 OR MIT */ +/* Copyright Tock Contributors 2023. */ +/* Copyright Google LLC 2023. */ + +/* +This file contains two styles of block comment. +*/ diff --git a/tools/license-checker/testdata/block_comments.xml b/tools/license-checker/testdata/block_comments.xml new file mode 100644 index 0000000000..1f8e0c03c5 --- /dev/null +++ b/tools/license-checker/testdata/block_comments.xml @@ -0,0 +1,8 @@ + + + + + + diff --git a/tools/license-checker/testdata/by_first_line b/tools/license-checker/testdata/by_first_line new file mode 100644 index 0000000000..606df3a6fb --- /dev/null +++ b/tools/license-checker/testdata/by_first_line @@ -0,0 +1,9 @@ +#!/bin/bash + +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. +# Copyright Google LLC 2022. + +echo Syntect should be able to recognize this as a Bash script, even though it \ + lacks an extension. diff --git a/tools/license-checker/testdata/error_missing.rs b/tools/license-checker/testdata/error_missing.rs new file mode 100644 index 0000000000..3af05aa588 --- /dev/null +++ b/tools/license-checker/testdata/error_missing.rs @@ -0,0 +1,7 @@ +//! This test file should result in a "license missing" error, as the tool sees +//! a doc comment before the license header. + +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. +// Copyright Google LLC 2022. diff --git a/tools/license-checker/testdata/many_errors.rs b/tools/license-checker/testdata/many_errors.rs new file mode 100644 index 0000000000..8c754295de --- /dev/null +++ b/tools/license-checker/testdata/many_errors.rs @@ -0,0 +1,6 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. [*] +// SPDX-License-Identifier: Apache-2.0 OR MIT [*] +// Copyright Tock Contributors 2022. +// Copyright Google LLC 2022. +// [*] This file is designed to generate the following errors in the license +// checker: MissingBlank, WrongFirst, WrongSpdx. diff --git a/tools/license-checker/testdata/no_copyright.rs b/tools/license-checker/testdata/no_copyright.rs new file mode 100644 index 0000000000..1c0c957695 --- /dev/null +++ b/tools/license-checker/testdata/no_copyright.rs @@ -0,0 +1,8 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT + +// Copyright Tock Contributors 2022. +// Copyright Google LLC 2022. + +// This file should generate a "missing copyright" error with the license +// checker (the blank line should be interpreted as an early end to the header). diff --git a/tools/license-checker/testdata/no_spdx.rs b/tools/license-checker/testdata/no_spdx.rs new file mode 100644 index 0000000000..103c572aa4 --- /dev/null +++ b/tools/license-checker/testdata/no_spdx.rs @@ -0,0 +1,8 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. + +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. +// Copyright Google LLC 2022. + +// This file should generate a "missing spdx line" error in the license checker +// (the blank line should be interpreted as an early end to the header). diff --git a/tools/license-checker/testdata/number_signs.fallback b/tools/license-checker/testdata/number_signs.fallback new file mode 100644 index 0000000000..2d28e5c854 --- /dev/null +++ b/tools/license-checker/testdata/number_signs.fallback @@ -0,0 +1,9 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. +# Copyright Google LLC 2023. + +syntect should not recognize this file type. Instead, the license checker +// should use the fallback parser, which should identify that this file uses +/* number signs for its comments. */ +#Lines starting with a '#' without a space are comments too. diff --git a/tools/license-checker/testdata/plain_text.txt b/tools/license-checker/testdata/plain_text.txt new file mode 100644 index 0000000000..12561e8d46 --- /dev/null +++ b/tools/license-checker/testdata/plain_text.txt @@ -0,0 +1,8 @@ +Licensed under the Apache License, Version 2.0 or the MIT License. +SPDX-License-Identifier: Apache-2.0 OR MIT +Copyright Tock Contributors 2024. +Copyright Google LLC 2024. + +This is a plain text file with the .txt extension. The license checker +should use the fallback parser, even though syntect may recognize it as a +plain text file. diff --git a/tools/license-checker/testdata/plain_text_no_extension b/tools/license-checker/testdata/plain_text_no_extension new file mode 100644 index 0000000000..975eaabdc0 --- /dev/null +++ b/tools/license-checker/testdata/plain_text_no_extension @@ -0,0 +1,7 @@ +Licensed under the Apache License, Version 2.0 or the MIT License. +SPDX-License-Identifier: Apache-2.0 OR MIT +Copyright Tock Contributors 2023. +Copyright Google LLC 2023. + +This is a plain text file; the license checker should recognize that it does +# not use a common comment syntax. diff --git a/tools/license-checker/testdata/shebang.fallback b/tools/license-checker/testdata/shebang.fallback new file mode 100644 index 0000000000..deed0141bf --- /dev/null +++ b/tools/license-checker/testdata/shebang.fallback @@ -0,0 +1,10 @@ +#!Shebang line here + +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. +# Copyright Google LLC 2023. + +syntect should not recognize this file type. Instead, the license checker +// should use the fallback parser, which should identify that this file has a +/* shebang line and uses number signs for its comments. */ diff --git a/tools/license-checker/testdata/slashes.fallback b/tools/license-checker/testdata/slashes.fallback new file mode 100644 index 0000000000..490ea560e5 --- /dev/null +++ b/tools/license-checker/testdata/slashes.fallback @@ -0,0 +1,8 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2023. +// Copyright Google LLC 2023. + +syntect should not recognize this file type. Instead, the license checker +# should use the fallback parser, which should identify that this file uses +/* double slashes for its comments. */ diff --git a/tools/license-checker/testdata/variety.rs b/tools/license-checker/testdata/variety.rs new file mode 100644 index 0000000000..496937c752 --- /dev/null +++ b/tools/license-checker/testdata/variety.rs @@ -0,0 +1,19 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. +// Copyright Google LLC 2022. + +// Syntect should recognize this file's type by extension. + +/// Single-line doc comment. The next line is a comment with no contents. +// + +/* Multi-line comment. The next comment contains only whitespace. + */ +// + +/** Multi-line doc comment. The line after this comment contains whitespace. + */ + + +#![rustfmt::skip] // This line should be considered "other" (i.e. code) diff --git a/tools/list_archs.sh b/tools/list_archs.sh index c3e248b1a0..8205f6a5ae 100755 --- a/tools/list_archs.sh +++ b/tools/list_archs.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + # Find archs based on folders with Cargo.toml for b in $(find arch -maxdepth 4 -name 'Cargo.toml'); do b1=${b#arch/} diff --git a/tools/list_boards.sh b/tools/list_boards.sh index 90f951f80b..a5cf8fb22c 100755 --- a/tools/list_boards.sh +++ b/tools/list_boards.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + # Find boards based on folders with Makefiles for b in $(find boards -maxdepth 4 | egrep 'Makefile$'); do b1=${b#boards/} diff --git a/tools/list_chips.sh b/tools/list_chips.sh index 40a324173b..68a7dfbda8 100755 --- a/tools/list_chips.sh +++ b/tools/list_chips.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + # Find chips based on folders with Cargo.toml for b in $(find chips -maxdepth 4 -name 'Cargo.toml'); do b1=${b#chips/} diff --git a/tools/list_lock.sh b/tools/list_lock.sh index 0359a333cc..695fa8752b 100755 --- a/tools/list_lock.sh +++ b/tools/list_lock.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + # Find crates based on folders with Cargo.lock files for b in $(find . -maxdepth 4 | egrep 'Cargo.lock$'); do b2=${b%/*} diff --git a/tools/list_tools.sh b/tools/list_tools.sh deleted file mode 100755 index 919fd3fe4a..0000000000 --- a/tools/list_tools.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -# Find tools built in rust based on folders with Cargo.toml -for b in $(find tools -maxdepth 4 -name 'Cargo.toml'); do - b1=${b#tools/} - b2=${b1%/*} - echo $b2 -done diff --git a/tools/litex-ci-runner/Cargo.toml b/tools/litex-ci-runner/Cargo.toml index bc38873744..1a1ebfd163 100644 --- a/tools/litex-ci-runner/Cargo.toml +++ b/tools/litex-ci-runner/Cargo.toml @@ -1,8 +1,12 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "litex-ci-runner" version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2018" +authors.workspace = true +edition.workspace = true [dependencies] rexpect = "0.4.0" diff --git a/tools/litex-ci-runner/README.md b/tools/litex-ci-runner/README.md new file mode 100644 index 0000000000..cd54b62a33 --- /dev/null +++ b/tools/litex-ci-runner/README.md @@ -0,0 +1,4 @@ +Litex Runner for CI +=================== + +Run the virtual Litex core using `litex_sim` to run tests in CI. diff --git a/tools/litex-ci-runner/src/main.rs b/tools/litex-ci-runner/src/main.rs index 86b91beba4..b1d3cd8625 100644 --- a/tools/litex-ci-runner/src/main.rs +++ b/tools/litex-ci-runner/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use rexpect::errors::Error; use rexpect::process::signal::Signal; use rexpect::session::PtySession; @@ -315,7 +319,7 @@ fn run() -> Result<(), Error> { // Test: rot13_client and rot13_service // // Tests IPC and IPC service discovery. - libtock_c_examples_test(&["rot13_service", "rot13_client"], |p, _| { + libtock_c_examples_test(&["rot13_client", "rot13_service"], |p, _| { // Let run for a few cycles to make sure it doesn't crash // after the first few messages for _ in 0..10 { diff --git a/tools/netlify-build.sh b/tools/netlify-build.sh index 19673f2f25..22abdc8405 100755 --- a/tools/netlify-build.sh +++ b/tools/netlify-build.sh @@ -1,8 +1,13 @@ #!/usr/bin/env bash + +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. # # Script used to install additional requirements to the base Netlify image. # -# Should not be used or relied on outside of Netlify context. +# Should not be used or relied on outside of Netlify context +# (exception: the docs-ci GitHub actions workflow, see issue #3428). # # Author: Pat Pannuto @@ -12,7 +17,7 @@ set -u set -x # Install rust stuff that we need -curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly-2021-12-04 +curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly-2024-07-08 # And fixup path for the newly installed rust stuff export PATH="$PATH:$HOME/.cargo/bin" diff --git a/tools/netlify-cache/index.js b/tools/netlify-cache/index.js index 8fee23d752..905b6e336d 100644 --- a/tools/netlify-cache/index.js +++ b/tools/netlify-cache/index.js @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + const os = require('os'); const path = require('path'); diff --git a/tools/netlify-cache/manifest.yml b/tools/netlify-cache/manifest.yml index b3b0051295..faf7b045d7 100644 --- a/tools/netlify-cache/manifest.yml +++ b/tools/netlify-cache/manifest.yml @@ -1 +1,5 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + name: tock-netlify-cache diff --git a/tools/post_size_changes_to_github.sh b/tools/post_size_changes_to_github.sh index 22c9a3ef0b..a45c8c782c 100755 --- a/tools/post_size_changes_to_github.sh +++ b/tools/post_size_changes_to_github.sh @@ -1,12 +1,16 @@ #!/usr/bin/env bash +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + # Post commit statuses to github indicating how a PR affects flash and RAM use for different boards. # This script is run by Travis after successful PR builds. It reports resource differences between # the target branch before and after merging in the PR. # This script also prints more detailed size analysis to the Travis build log. # This script only reports updates for boards whose size have changed as a result of the PR being # tested, and does not currently support analyzing size differences in RISC-V boards. -# This file relies on a travis enviroment variable to post to github, which is the value of a +# This file relies on a travis environment variable to post to github, which is the value of a # Github OAuth personal token associated with @hudson-ayers Github identity. set -e diff --git a/tools/print_tock_memory_usage.py b/tools/print_tock_memory_usage.py index d8887467a2..5f178d3162 100755 --- a/tools/print_tock_memory_usage.py +++ b/tools/print_tock_memory_usage.py @@ -1,5 +1,9 @@ #!/usr/bin/env python3 +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + # Prints out the memory usage of a Tock kernel binary ELF. # Currently only works on ARM binaries. # @@ -8,7 +12,7 @@ # Author: Philip Levis # pylint: disable=superfluous-parens -''' +""" Script to print out the memory usage of a Tock kernel binary ELF. Usage: print_memory_usage.py ELF @@ -18,194 +22,274 @@ together. Default: 1 -v, --verbose Print verbose output. -s, --show-waste Show where RAM is wasted (due to padding) -''' +""" import os import re import sys import getopt -import cxxfilt # Demangling C++/Rust symbol names - +import copy OBJDUMP = "llvm-objdump" +print_all = False verbose = False show_waste = False symbol_depth = 1 -sort_by_size = False # Otherwise lexicographic order +sort_by_size = False # Otherwise lexicographic order # A map of section name -> size sections = {} -# These lists store 4-tuples: -# (name, start address, length of function, total size) -# The "length of function" is the size of the symbol as reported in -# objdump, which is the executable code. "Total size" includes any -# constants embedded, including constant strings, or padding. -# Initially the lists are populated with total_size=0; it is later -# computed by sorting the symbols and calculating their spacing. +# These lists store 5-tuples: +# (name, start address, reported size, total size, type) +# The "reported size" is the size that the symbol table says this +# symbol has. As a symbol table can have overlapping symbols (e.g., +# two different symbols with the same address and size) and have space +# that is not covered by symbols (e.g., data embedded after a symbol), +# the reported size is not an accurate accounting of how a symbol impacts +# the size of a binary. +# +# The script calculates the "real" size of symbol and stores it in +# "total size", with the rule that the sum of the total size of all of +# the symbols in a section equal the size of the section. The "total +# size" of a symbol is defined to be the space until the next +# symbol. If there are multiple symbols with the same address, the +# script considers the one with the largest reported size as the last +# one. This means that all of these symbols except the one with the +# largest reported size will have total size 0 (the next symbol has +# the same address). The one with the largest reported size will have +# a total size of the space until the next (different) symbol address. +# +# The "type" field is used to distinguish between variables and +# functions. This is useful in code sections as it allows the script +# to distinguish constants and embedded data from instructions. As Rust +# can insert a lot of embedded data (e.g., panic strings), distinguishing +# the two is useful. Three types are used: +# - "variable" denotes an initialized or uninitialized variable. +# - "data" denotes embedded/constant data in a text section. +# - "function" denotes instructions/executable code in a text section. + +# Uninitialized variables are zeros and zeroed out at kernel boot; they +# exist solely in RAM (the .bss section). kernel_uninitialized = [] +# Initialized variables start with non-zero values and are initialized +# at kernel boot by copying their initial values from flash into RAM +# (the .data section). kernel_initialized = [] +# Kernel symbols are first stored in kernel_text to perform whole +# kernel text calculations. Then they are split into kernel_functions +# and kernel_data to separately account for symbols of these types. +kernel_text = [] kernel_functions = [] +kernel_data = [] + def usage(message): """Prints out an error message and usage""" if message != "": print("error: " + message) - print("""Usage: print_memory_usage.py ELF + print( + """Usage: print_memory_usage.py ELF Options: + -a Print all symbols (overrides -d) -dn, --depth=n Group symbols at depth n or greater. E.g., depth=2 will group all h1b::uart:: symbols together. Default: 1 -s, --size Sort symbols by size (normally lexicographic) -v, --verbose Print verbose output (RAM waste and embedded flash data) -w, --show-waste Show where RAM is wasted (due to padding) - -Note: depends on llvm-objdump to extract symbols""") - + --objdump Path to the llvm-objdump executable + +Note: depends on llvm-objdump to extract symbols""" + ) + +def is_private_symbol(symbol): + """Returns whether a symbol is a private symbol. Private symbols + are inserted by the compiler and denote internal structure: they + are not included when attributing space to symbols.""" + if symbol[0:4] == ".LBB": + return True + elif symbol[0:4] == ".LBE": + return True + elif symbol[0:5] == ".Ltmp": + return True + else: + return False def process_section_line(line): """Parses a line from the Sections: header of an ELF objdump, - inserting it into a data structure keeping track of the sections.""" + inserting it into a data structure keeping track of the sections.""" # pylint: disable=anomalous-backslash-in-string,line-too-long - match = re.search('^\S+\s+\.(text|relocate|sram|stack|app_memory)\s+(\S+).+', line) + match = re.search(r"^\S+\s+\.(text|relocate|sram|stack|app_memory)\s+(\S+).+", line) if match != None: sections[match.group(1)] = int(match.group(2), 16) - # Take a Rust-style symbol of '::' delineated names and trim the last - # one if it is a hash. Many symbols have hashes appended which just - # hurt readability; they take the form of h[16-digit hex number]. -def trim_hash_from_symbol(symbol): - """If the passed symbol ends with a hash of the form h[16-hex number] - trim this and return the trimmed symbol.""" - # Remove the hash off the end - tokens = symbol.split('::') - last = tokens[-1] - if last[0] == 'h': - tokens = tokens[:-1] # Trim off hash if it exists - trimmed_name = "::".join(tokens) # reassemble - return trimmed_name +def trim_vendor_suffix_from_symbol(symbol): + """Take a Rust symbol and strip away vendor-specific suffixes. + + Mangled symbols can contain '.' followed vendor-specific suffixes, e.g. + ".71" or ".llvm". + + LLVM demangler will mangle the parts before the dot and put the rest in a + parenthesis after the demangled symbol. + E.g. `_RNvNtCsefdtF19kgsV_6kernel13deferred_call3CTR.0` becomes `kernel::deferred_call::CTR (.0)`""" + return symbol.split(" (.")[0] + +def parse_angle_bracket(string): + """Parse balanced angle brackets and split the string into parts. + + This method also takes care of trait in UFCS syntax, so + if the string is of the form `::rest`, it is split into ("T", "Trait", "Rest"); + if the string is of the form `::rest`, it is split into ("T", "", "Rest"); + + The first character of the string is assumed to be an opening angle bracket. + """ + nesting = 0 + as_pos = None + for i in range(len(string)): + if nesting == 1 and string[i:i+4] == " as ": + as_pos = i + elif string[i] == "<": + nesting += 1 + elif string[i] == ">": + nesting -= 1 + if nesting == 0: + break + assert nesting == 0, "Unbalanced angle brackets" + + if as_pos == None: + return (string[1:i], "", string[i+1:]) else: - return symbol - -escape_sequences = [ - ["$C$", ","], - ["$SP$", "@"], - ["$BP$", "*"], - ["$RF$", "&"], - ["$LT,GT$", "<>"], - ["$LT$", "<"], - ["$GT$", ">"], - ["$LP$", "("], - ["$RP$", ")"], - ["$u20$", " "], - ["$u27$", "\'"], - ["$u5b$", "["], - ["$u5d$", "]"], - ["..", "::"], - [".", "-"] -] - -def parse_mangled_name(name): - """Take a potentially mangled symbol name and demangle it to its - name, removing the trailing hash. This is not just a simple - demangling: for methods, it outputs the structure + method - as a :: separated name, eliding the trait (if any).""" - - # Trim a trailing . number (e.g., ".71") which breaks demangling - match = re.search('\.\d+$', name) - if match != None: - name = name[:match.start()] + return (string[1:as_pos], string[as_pos+4:i], string[i+1:]) - # Trim a trailing ".llvm", which breaks demangling - match = re.search('\.llvm', name) - if match != None: - name = name[:match.start()] +def parse_symbol_name(name): + """Take a symbol name demangled by LLVM and further process it. - demangled = "" - try: - demangled = cxxfilt.demangle(name, external_only=False) - except cxxfilt.InvalidName: - demangled = name - - corrected_name = trim_hash_from_symbol(demangled) - for escape in escape_sequences: - corrected_name = corrected_name.replace(escape[0], escape[1]) - - # Need to separate the name of the structure from the name of - # the method. If it starts with a _, then it's of the form - # _::method otherwise it's - # structure::method. So first carve off the method name, then - # figure out the structure. - - structure_end = corrected_name.rfind("::") - full_structure_name = "" - - if structure_end >= 0: - method = corrected_name[structure_end + 2:] - full_structure_name = corrected_name[0:structure_end] - else: - method = corrected_name + For methods, it outputs the structure + method + as a :: separated name, eliding the trait (if any).""" + + # Not a Rust symbol name, just return it unchanged. + if not "::" in name: + return name + + prefix = "" + if name[0:8] == ".hidden ": + name = name[8:] + prefix = ".hidden " - structure = full_structure_name - if corrected_name[0:1] == "_": - split = full_structure_name.split(" as ") - structure = split[0] - # trim the _< - structure = structure[2:] + # Trim a trailing vendor suffixes. + name = trim_vendor_suffix_from_symbol(name) - symbol = structure - if len(symbol) > 0: - symbol = symbol + "::" + method - else : - # No structure, just a method - symbol = method + # Unmangled method names will use the UFCS syntax, so it looks like + # ::method for inherent methods and + # ::method for trait methods. We want to + # separate the structure from the method name. + if name[0] == "<": + structure, trait, trailing = parse_angle_bracket(name) + else: + structure = name + trait = "" + trailing = "" + + # This can happen for closures or types that are defined inside an impl. Remove the angle bracket + while structure[0:1] == "<": + structure, _, t = parse_angle_bracket(structure) + structure += t + + # Methods on primitives types are defined in libcore. + if structure[0:1] == "&": + structure = "core::primitive::ref" + elif structure[0:1] == "*": + structure = "core::primitive::ptr" + elif structure[0:1] == "[": + structure = "core::primitive::slice" + elif structure[0:1] == "(": + structure = "core::primitive::tuple" + elif structure == "bool" or structure == "char" or structure == "str" \ + or structure == "u8" or structure == "u16" or structure == "u32" \ + or structure == "u64" or structure == "u128" or structure == "usize" \ + or structure == "i8" or structure == "i16" or structure == "i32" \ + or structure == "i64" or structure == "i128" or structure == "isize": + structure = "core::primitive::" + structure + + # For trait methods on libcore types, they must be defined in the crate that + # defines on the trait. So as an exception account them to the trait instead. + if trait != "" and structure[0:4] == "core" and trait[0:4] != "core": + structure = trait + + symbol = structure + trailing + + # For grouping we want to strip away all generics + while "<" in symbol: + angle_start = symbol.find("<") + _, _, trailing = parse_angle_bracket(symbol[angle_start:]) + # For turbofish also remove the :: before the angle brackets + if symbol[angle_start - 2:angle_start] == "::": + symbol = symbol[0:angle_start - 2] + trailing + else: + symbol = symbol[0:angle_start] + trailing - if symbol[0:2] == "-L" or symbol[0:2] == "-l" or symbol[0:4] == "anon": - symbol = "Anonymous" - if symbol[0:7] == "-hidden": - symbol = "Hidden" + return prefix + symbol - return symbol def process_symbol_line(line): """Parse a line the SYMBOL TABLE section of the objdump output and - insert its data into one of the three kernel_ symbol lists. - Because Tock executables have a variety of symbol formats, - first try to demangle it; if that fails, use it as is.""" + insert its data into one of the three kernel_ symbol lists. + Because Tock executables have a variety of symbol formats, + first try to demangle it; if that fails, use it as is.""" # pylint: disable=line-too-long,anomalous-backslash-in-string - match = re.search('^(\S+)\s+\w+\s+\w*\s+\.(text|relocate|sram|stack|app_memory)\s+(\S+)\s+(.+)', line) + global kernel_text + global kernel_initialized + global kernel_uninitialized + match = re.search( + r"^(\S+)\s+(\w*)\s+(\w*)\s+\.(text|relocate|sram|stack|app_memory)\s+(\S+)\s+(.+)", + line, + ) if match != None: addr = int(match.group(1), 16) - segment = match.group(2) - size = int(match.group(3), 16) - name = match.group(4) + linkage = match.group(2) + symbol_type = match.group(3) + segment = match.group(4) + size = int(match.group(5), 16) + name = match.group(6) + + # Compiler embeds these symbols, ignore them + if name[0:3] == "$t." or name[0:3] == "$d.": + return + + # Special case end of kernel RAM, given that there is padding + # between it and application RAM. + if name == "_ezero": + name = "Padding at end of kernel RAM" # Initialized data: part of the flash image, then copied into RAM # on start. The .data section in normal hosted C. if segment == "relocate": - demangled = parse_mangled_name(name) - kernel_initialized.append((demangled, addr, size, 0)) + demangled = parse_symbol_name(name) + kernel_initialized.append((demangled, addr, size, 0, "variable")) # Uninitialized data, stored in a zeroed RAM section. The # .bss section in normal hosted C. elif segment == "sram": - demangled = parse_mangled_name(name) - kernel_uninitialized.append((demangled, addr, size, 0)) + demangled = parse_symbol_name(name) + kernel_uninitialized.append((demangled, addr, size, 0, "variable")) # Code and embedded data. elif segment == "text": - match = re.search('\$(((\w+\.\.)+)(\w+))\$', name) - if match != None: - symbol = parse_mangled_name(name) - kernel_functions.append((symbol, addr, size, 0)) + match = re.search(r"\$(((\w+\.\.)+)(\w+))\$", name) + # It's a function + if is_private_symbol(name): + # Skip this symbol + return + if symbol_type == "F" or symbol_type == "f": + symbol = parse_symbol_name(name) + kernel_text.append((symbol, addr, size, 0, "function")) else: - try: - symbol = parse_mangled_name(name) - kernel_functions.append((symbol, addr, size, 0)) - except cxxfilt.InvalidName: - kernel_functions.append((name, addr, size, 0)) + symbol = parse_symbol_name(name) + kernel_text.append((symbol, addr, size, 0, "data")) + def print_section_information(): """Print out the ELF's section information (RAM and Flash use).""" @@ -216,8 +300,8 @@ def print_section_information(): app_size = 0 if "app_memory" in sections: # H1B-style linker file, static app section app_size = sections["app_memory"] - else: # Mainline Tock-style linker file, using APP_MEMORY - for (name, addr, size, tsize) in kernel_uninitialized: + else: # Mainline Tock-style linker file, using APP_MEMORY + for (name, addr, size, tsize, desc) in kernel_uninitialized: if name.find("APP_MEMORY") >= 0: app_size = size @@ -234,6 +318,7 @@ def print_section_information(): print(" " + "{:>6}".format(sram_size + relocate_size) + "\tvariables total") print("Applications allocated " + str(app_size) + " bytes of RAM") + # Take a list of 'symbols' and group them into in 'groups' as aggregates # for condensing. Names are '::' delimited hierarchies. The aggregate # sizes are determined by the global symbol depth, which indicates how @@ -250,35 +335,59 @@ def print_section_information(): # as a string to it can be later output. def group_symbols(groups, symbols, waste, section): """Take a list of symbols and group them into 'groups' for reporting - aggregate flash/RAM use.""" + aggregate flash/RAM use.""" global symbol_depth output = "" expected_addr = 0 waste_sum = 0 prev_symbol = "" - for (symbol, addr, size, _) in symbols: - if size == 0: + for (symbol, addr, size, real_size, _) in symbols: + if addr < expected_addr: + # We have some overlapping symbols. This can happen due to merging + # of functions and constants with non-significant address. + # Since we sort symbols with larger sizes to the front, the entire + # span of this symbol has been processed and thus we can ignore this. continue + # If we find a gap between symbol+size and the next symbol, we might # have waste. But this is only true if it's not the first symbol and # this is actually a variable and just just a symbol (e.g., _estart) - if addr != expected_addr and expected_addr != 0 and size != 0 and (waste or verbose): - output = output + " ! " + str(addr - expected_addr) + " bytes of data or padding after " + prev_symbol + "\n" + if ( + addr != expected_addr + and expected_addr != 0 + and size != 0 + and (waste or verbose) + ): + output = ( + output + + " ! " + + str(addr - expected_addr) + + " bytes of data or padding after " + + prev_symbol + + "\n" + ) waste_sum = waste_sum + (addr - expected_addr) tokens = symbol.split("::") - key = symbol[0] # Default to first character (_) if not a proper symbol + key = symbol[0] # Default to first character (_) if not a proper symbol name = symbol if len(tokens) == 1: # The symbol isn't a standard mangled Rust name. These rules are # based on observation. # .Lanon* and str.* are embedded string. - if symbol[0:6] == '.Lanon' or symbol[0:5] == "anon." or symbol[0:4] == 'str.': + if ( + symbol[0:6] == ".Lanon" + or symbol[0:5] == "anon." + or symbol[0:4] == "str." + ): key = "Constant strings" elif symbol[0:8] == ".hidden ": key = "ARM aeabi support" - elif symbol[0:3] == "_ZN": + elif symbol[0:3] == "_RN": key = "Unidentified auto-generated" + elif symbol == "Padding at end of kernel RAM": + key = symbol + name = symbol else: key = "Unmangled globals (C-like code)" name = symbol @@ -288,50 +397,55 @@ def group_symbols(groups, symbols, waste, section): # in printing. key = "::".join(tokens[0:symbol_depth]) name = "" - + if len(tokens[symbol_depth:]) > 0: key = key + "::" name = "::".join(tokens[symbol_depth:]) - - if key in groups.keys(): - groups[key].append((name, size)) - else: - groups[key] = [(name, size)] + if key in groups.keys(): + groups[key].append((name, real_size)) + else: + groups[key] = [(name, real_size)] # Set state for next iteration expected_addr = addr + size prev_symbol = symbol if waste and waste_sum > 0: - output = output + "Total of " + str(waste_sum) + " bytes wasted in " + section + "\n" + output = ( + output + "Total of " + str(waste_sum) + " bytes wasted in " + section + "\n" + ) return output + def string_for_group(key, padding_size, group_size, num_elements): """Return the string for a group of variables, with padding added on the - right; decides whether to add a * or not based on the name of the group - and number of elements in it.""" + right; decides whether to add a * or not based on the name of the group + and number of elements in it.""" if key[-2:] == "::": key = key + "*" - key = key.ljust(padding_size + 2, ' ') - return (" " + key + str(group_size) + " bytes\n") + key = key.ljust(padding_size + 2, " ") + return " " + key + str(group_size) + " bytes\n" else: key = key + "" - key = key.ljust(padding_size + 2, ' ') - return (" " + key + str(group_size) + " bytes\n") + key = key.ljust(padding_size + 2, " ") + return " " + key + str(group_size) + " bytes\n" + def print_groups(title, groups): """Print title, then all of the variable groups in groups.""" group_sum = 0 output = "" - max_string_len = len(max(groups.keys(), key=len)) + max_string_len = 0 + if len(groups.keys()) > 0: + max_string_len = len(max(groups.keys(), key=len)) group_sizes = {} for key in groups.keys(): symbols = groups[key] group_size = 0 - for (_, size) in symbols: + for (name, size) in symbols: group_size = group_size + size group_sizes[key] = group_size @@ -339,92 +453,192 @@ def print_groups(title, groups): for k, v in sorted(group_sizes.items(), key=lambda item: item[1], reverse=True): group_size = v symbols = groups[key] - output = output + string_for_group(k, max_string_len, group_size, len(symbols)) + output = output + string_for_group( + k, max_string_len, group_size, len(symbols) + ) group_sum = group_sum + group_size else: for key in sorted(group_sizes.keys()): group_size = group_sizes[key] symbols = groups[key] - output = output + string_for_group(key, max_string_len, group_size, len(symbols)) + output = output + string_for_group( + key, max_string_len, group_size, len(symbols) + ) group_sum = group_sum + group_size print(title + ": " + str(group_sum) + " bytes") - print(output, end = '') + print(output, end="") + +def split_text_into_data_and_functions(): + global kernel_text + global kernel_functions + global kernel_data + for (name, addr, reported_size, real_size, desc) in kernel_text: + if desc == "function": + kernel_functions.append((name, addr, reported_size, real_size, desc)) + elif desc == "data": + kernel_data.append((name, addr, reported_size, real_size, desc)) + def print_symbol_information(): """Print out all of the variable and function groups with their flash/RAM - use.""" - variable_groups = {} - gaps = group_symbols(variable_groups, kernel_initialized, show_waste, "Flash+RAM") - gaps = gaps + group_symbols(variable_groups, kernel_uninitialized, show_waste, "RAM") - print_groups("Variable groups (RAM)", variable_groups) - print(gaps) + use.""" + split_text_into_data_and_functions() + if print_all: + print_all_symbol_information() + else: + print_grouped_symbol_information() + +def print_all_symbols(title, symbols): + """Print out all of the symbols passed as a list of 4-tuples, + prefaced by the title and total size of the of symbols.""" + max_string_len = 0 + max_byte_len = 5 + if len(symbols) > 0: + max_string_len = max(len(s) for (s, _, _, _, _) in symbols) +# max_byte_len = max(len(str(size)) for (_, _, _, size, _) in symbols) + output = "" + symbol_sum = 0 + if sort_by_size: + symbols = sorted(symbols, key=lambda item: item[3], reverse=True) + for (name, addr, reported_size, real_size, desc) in symbols: + name = name.ljust(max_string_len + 2, " ") + symbol_sum = symbol_sum + real_size + size_str = str(real_size).rjust(max_byte_len, " ") + output = output + " " + name + size_str + " bytes\n" + print(title + ": " + str(symbol_sum) + " bytes") + print(output, end="") + + +def print_all_symbol_information(): + """Print out the size of every symbol.""" + print_all_symbols("Initialized variable groups (Flash+RAM)", kernel_initialized) + print() + print_all_symbols("Variable groups (RAM)", kernel_uninitialized) + print() + print_all_symbols("Function groups (flash)", kernel_functions) + print() + print_all_symbols("Embedded data (flash)", kernel_data) + print() + print_all_symbols("Padding within functions and embedded data (flash)", padding_text) + print() - print("Embedded data (flash): " + str(padding_text) + " bytes") + +def print_grouped_symbol_information(): + """Print out the size taken up by symbols, with symbols grouped + by their names""" + initialized_groups = {} + gaps = group_symbols(initialized_groups, kernel_initialized, show_waste, "Flash+RAM") + print_groups("Initialized variable groups (Flash+RAM)", initialized_groups) print() - function_groups = {} - # Embedded constants in code (e.g., after functions) aren't counted - # in the symbol's size, so detecting waste in code has too many false - # positives. - gaps = group_symbols(function_groups, kernel_functions, False, "Flash") + uninitialized_groups = {} + gaps = gaps + group_symbols( + uninitialized_groups, kernel_uninitialized, show_waste, "RAM" + ) + print_groups("Variable groups (RAM)", uninitialized_groups) + print(gaps) + + function_groups = {} + gaps = group_symbols(function_groups, kernel_functions, show_waste, "Flash") print_groups("Function groups (flash)", function_groups) print(gaps) + embedded_data = {} + gaps = group_symbols(embedded_data, kernel_data, show_waste, "Flash") + print_groups("Embedded data (flash)", embedded_data) + print(gaps) + + padding = {} + gaps = group_symbols(padding, padding_text, False, "Flash") + print_groups("Padding within functions and embedded padding (flash)", padding) + print(gaps) + + +def sort_value(symbol_entry): + """Helper function for sorting symbols by start address and size. + + We sort symbols with smaller start address to the front, and if their starting address is the same, + the larger symbols comes first then the smaller ones. + """ + value = (symbol_entry[1] << 16) - symbol_entry[2] + return value + def get_addr(symbol_entry): """Helper function for sorting symbols by start address.""" return symbol_entry[1] -def compute_padding(symbols): +def get_name(symbol_entry): + """Helper function for fetching symbol names to calculate longest name.""" + return symbol_entry[0] + +text_total = 0 +def compute_padding(symbols, text): """Calculate how much padding is in a list of symbols by comparing their - reporting size with the spacing with the next function and return - the total differences.""" - symbols.sort(key=get_addr) + reporting size with the spacing with the next function and return + the total differences.""" + global text_total + symbols.sort(key=sort_value) + elements = [] func_count = len(symbols) diff = 0 + size_sum = 0 for i in range(1, func_count): - (esymbol, eaddr, esize, _) = symbols[i - 1] - (_, laddr, _, _) = symbols[i] + (esymbol, eaddr, esize, _, edesc) = symbols[i - 1] + (symbol, laddr, _, _, desc) = symbols[i] total_size = laddr - eaddr - symbols[i - 1] = (esymbol, eaddr, esize, total_size) - if total_size != esize: - diff = diff + (total_size - esize) +# print("PROCESSED: ", esymbol, "has size", esize, "but real size is", total_size, "total is", text_total, "comes before", symbol) + symbols[i - 1] = (esymbol, eaddr, esize, total_size, edesc) + + size_sum = size_sum + total_size + padding_size = (total_size - esize) + # Padding represents when there is space after the end of a symbol's + # defined size. Sometimes, when there is embedded data, it is in space + # after a symbol, i.e., there is space after symbol+length and the + # next symbol. Other times, the embedded data is within a symbol's + # region. + if total_size != esize and total_size > 0 and padding_size > 0: + elements.append((esymbol, 0, padding_size, padding_size, edesc)) + diff = diff + padding_size + #if total_size != 0: + # print(esymbol, total_size) + return elements - return diff def parse_options(opts): """Parse command line options.""" - global symbol_depth, verbose, show_waste, sort_by_size - valid = 'd:vsw' - long_valid = ['depth=', 'verbose', 'show-waste', 'size'] + global print_all, symbol_depth, verbose, show_waste, sort_by_size, OBJDUMP + valid = "ad:vsw" + long_valid = ["depth=", "verbose", "show-waste", "size", "objdump="] optlist, leftover = getopt.getopt(opts, valid, long_valid) for (opt, val) in optlist: - if opt == '-d' or opt == '--depth': + if opt == "-a": + print_all = True + elif opt == "-d" or opt == "--depth": symbol_depth = int(val) - elif opt == '-v' or opt == '--verbose': + elif opt == "-v" or opt == "--verbose": verbose = True - elif opt == '-w' or opt == '--show-waste': + elif opt == "-w" or opt == "--show-waste": show_waste = True - elif opt == '-s' or opt == '--size': - print("sorting by size") + elif opt == "-s" or opt == "--size": sort_by_size = True + elif opt == "--objdump": + OBJDUMP = val else: usage("unrecognized option: " + opt) return [] return leftover - - - # Script starts here ###################################### +# Script starts here ###################################### if __name__ == "__main__": arguments = sys.argv[1:] if len(arguments) < 1: usage("no ELF specified") sys.exit(-1) - # The ELF is always the last argument; pull it out, then parse - # the others. + # The ELF is always the last argument; pull it out, then parse + # the others. elf_name = "" options = arguments try: @@ -435,17 +649,17 @@ def parse_options(opts): else: elf_name = remaining[0] except getopt.GetoptError as err: - usage(str(err)) - sys.exit(-1) + usage(str(err)) + sys.exit(-1) - header_lines = os.popen(OBJDUMP + ' -section-headers ' + elf_name).readlines() + header_lines = os.popen(OBJDUMP + " --section-headers " + elf_name).readlines() print("Tock memory usage report for " + elf_name) arch = "UNKNOWN" for hline in header_lines: # pylint: disable=anomalous-backslash-in-string - hmatch = re.search('file format (\S+)', hline) + hmatch = re.search(r"file format (\S+)", hline) if hmatch != None: arch = hmatch.group(1) @@ -453,7 +667,7 @@ def parse_options(opts): usage("could not detect architecture of ELF") sys.exit(-1) - objdump_lines = os.popen(OBJDUMP + ' -t -section-headers ' + elf_name).readlines() + objdump_lines = os.popen(OBJDUMP + " --demangle -t --section-headers " + elf_name).readlines() objdump_output_section = "start" for oline in objdump_lines: @@ -471,9 +685,9 @@ def parse_options(opts): elif objdump_output_section == "symbol_table": process_symbol_line(oline) - padding_init = compute_padding(kernel_initialized) - padding_uninit = compute_padding(kernel_uninitialized) - padding_text = compute_padding(kernel_functions) + padding_init = compute_padding(kernel_initialized, False) + padding_uninit = compute_padding(kernel_uninitialized, False) + padding_text = compute_padding(kernel_text, True) print_section_information() print() diff --git a/tools/qemu-runner/Cargo.toml b/tools/qemu-runner/Cargo.toml index 46776daf36..3b304bcc96 100644 --- a/tools/qemu-runner/Cargo.toml +++ b/tools/qemu-runner/Cargo.toml @@ -1,8 +1,12 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "qemu-runner" version = "0.1.0" -authors = ["Tock Project Developers "] -edition = "2021" +authors.workspace = true +edition.workspace = true [dependencies] rexpect = "0.4.0" diff --git a/tools/qemu-runner/README.md b/tools/qemu-runner/README.md new file mode 100644 index 0000000000..c70c1cf4b6 --- /dev/null +++ b/tools/qemu-runner/README.md @@ -0,0 +1,4 @@ +QEMU Runner +=========== + +Tool to run QEMU with supported Tock boards in CI. diff --git a/tools/qemu-runner/src/main.rs b/tools/qemu-runner/src/main.rs index 83fab84b7b..3013871519 100644 --- a/tools/qemu-runner/src/main.rs +++ b/tools/qemu-runner/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + use std::process::Command; use rexpect::errors::Error; @@ -35,42 +39,6 @@ fn hifive1() -> Result<(), Error> { Ok(()) } -fn earlgrey_nexysvideo() -> Result<(), Error> { - // First, build the board if needed - // n.b. rexpect's `exp_eof` does not actually block main thread, so use - // the standard Rust process library mechanism instead. - let mut build = Command::new("make") - .arg("-C") - .arg("../../boards/opentitan/earlgrey-nexysvideo") - .spawn() - .expect("failed to spawn build"); - assert!(build.wait().unwrap().success()); - - // Get canonicalized path to opentitan rom - let mut rom_path = std::env::current_exe().unwrap(); - rom_path.pop(); // strip exe file - rom_path.pop(); // strip /debug - rom_path.pop(); // strip /target - rom_path.push("opentitan-boot-rom.elf"); - - let mut p = spawn( - &format!( - "make OPENTITAN_BOOT_ROM={} qemu -C ../../boards/opentitan/earlgrey-nexysvideo", - rom_path.to_str().unwrap() - ), - Some(10_000), - )?; - - p.exp_string("Boot ROM initialisation has completed, jump into flash!")?; - p.exp_string("OpenTitan initialisation complete. Entering main loop")?; - - // Test completed, kill QEMU - kill_qemu(&mut p)?; - - p.exp_string("QEMU: Terminated")?; - Ok(()) -} - fn earlgrey_cw310() -> Result<(), Error> { // First, build the board if needed // n.b. rexpect's `exp_eof` does not actually block main thread, so use @@ -90,14 +58,10 @@ fn earlgrey_cw310() -> Result<(), Error> { rom_path.push("opentitan-boot-rom.elf"); let mut p = spawn( - &format!( - "make OPENTITAN_BOOT_ROM={} qemu -C ../../boards/opentitan/earlgrey-cw310", - rom_path.to_str().unwrap() - ), + "make qemu -C ../../boards/opentitan/earlgrey-cw310", Some(10_000), )?; - p.exp_string("Boot ROM initialisation has completed, jump into flash!")?; p.exp_string("OpenTitan initialisation complete. Entering main loop")?; // Test completed, kill QEMU @@ -114,10 +78,6 @@ fn main() { hifive1().unwrap_or_else(|e| panic!("hifive1 job failed with {}", e)); println!("hifive1 SUCCESS."); println!(""); - println!("Running earlgrey_nexysvideo tests..."); - earlgrey_nexysvideo().unwrap_or_else(|e| panic!("earlgrey_nexysvideo job failed with {}", e)); - println!("earlgrey_nexysvideo SUCCESS."); - println!(""); println!("Running earlgrey_cw310 tests..."); earlgrey_cw310().unwrap_or_else(|e| panic!("earlgrey_cw310 job failed with {}", e)); println!("earlgrey_cw310 SUCCESS."); diff --git a/tools/run_cargo_fix.sh b/tools/run_cargo_fix.sh index 8188b7316c..6b7554c5b1 100755 --- a/tools/run_cargo_fix.sh +++ b/tools/run_cargo_fix.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + # Runs cargo fix (which removes lint warnings) on every subdirectory from . # that has a Cargo.toml file. # diff --git a/tools/run_cargo_fmt.sh b/tools/run_cargo_fmt.sh deleted file mode 100755 index 43e9078c53..0000000000 --- a/tools/run_cargo_fmt.sh +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/env bash -# -# Runs rustfmt on all subdirectories with a Cargo.toml file. Must be run from -# root Tock directory. Protects user from inadvertently overwriting files. -# -# Author: Pat Pannuto -# Author: Brad Campbell -# -set -e - -# Verify that we're running in the base directory -if [ ! -x tools/run_cargo_fmt.sh ]; then - echo ERROR: $0 must be run from the tock repository root. - echo "" - exit 1 -fi - -# Add the rustfmt component if needed. -if ! rustup component list | grep 'rustfmt.*(installed)' -q; then - # Some versions of OS X want the -preview version, retry that on failure - rustup component add rustfmt || rustup component add rustfmt-preview -fi - -# Format overwrites changes, which is probably good, but it's nice to see -# what it has done -# -# `git status --porcelain` formats things for scripting -# | M changed file, unstaged -# |M changed file, staged (git add has run) -# |MM changed file, some staged and some unstaged changes (git add then changes) -# |?? untracked file -if [ "$1" != "diff" ]; then - if git status --porcelain | grep '^.M.*\.rs' -q; then - echo "$(tput bold)Warning: Formatting will overwrite files in place.$(tput sgr0)" - echo "While this is probably what you want, it's often useful to" - echo "stage all of your changes (git add ...) before format runs," - echo "just so you can double-check everything." - echo "" - echo "$(tput bold)git status:$(tput sgr0)" - git status - echo "" - read -p "Continue formatting with unstaged changes? [y/N] " response - if [[ ! ( "$(echo "$response" | tr :upper: :lower:)" == "y" ) ]]; then - exit 0 - fi - fi -fi - -set +e -let FAIL=0 -set -e - -# Get the list of files in the workspace to avoid formatting -# them twice -csplit Cargo.toml '/exclude = \[/' > /dev/null -rm xx01 - -# Find folders with Cargo.toml files in them and run `cargo fmt`. -for f in $(find . | grep Cargo.toml); do - if [ $f == './Cargo.toml' ]; then - printf "\rFormatting Workspace" - dir='.' - else - dir=$(dirname $f) - dir=${dir:2} # strip leading ./ - if grep -q '"'$dir'"' xx00; then - continue - fi - - printf "\rFormatting %-$((39))s" $dir - fi - - pushd $dir > /dev/null - if [ "$1" == "diff" ]; then - # If diff mode, two-pass the check to make pretty-print work - if ! cargo-fmt -q -- --check; then - printf "<- Contains formatting errors!\n" - cargo-fmt -- --check || let FAIL=FAIL+1 - printf "\n" - fi - else - cargo-fmt - fi - popd > /dev/null -done -rm xx00 -printf "\rFormatting complete. %-$((39))s\n" "" - -# Check for tab characters in Rust source files that haven't been -# removed by rustfmt -RUST_FILES_WITH_TABS="$(git grep --files-with-matches $'\t' -- '*.rs' || grep -lr --include '*.rs' $'\t' . || true)" -if [ "$RUST_FILES_WITH_TABS" != "" ]; then - echo "ERROR: The following files contain tab characters, please use spaces instead:" - echo "$RUST_FILES_WITH_TABS" | sed 's/^/ -> /' - let FAIL=FAIL+1 -fi - -if [[ $FAIL -ne 0 ]]; then - echo - echo "$(tput bold)Formatting errors.$(tput sgr0)" - echo "See above for details" -fi -exit $FAIL diff --git a/tools/run_cargo_generate-lockfile.sh b/tools/run_cargo_generate-lockfile.sh index c9ba93a37d..080b956919 100755 --- a/tools/run_cargo_generate-lockfile.sh +++ b/tools/run_cargo_generate-lockfile.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + # Runs `cargo generate-lockfile` (which generates and/or updates the Cargo.lock # file for a crate) on every subdirectory from . that has a Cargo.toml file. # diff --git a/tools/run_clippy.sh b/tools/run_clippy.sh deleted file mode 100755 index cac0b086a6..0000000000 --- a/tools/run_clippy.sh +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env bash - -# Install clippy if it is not already preset. -if ! rustup component list | grep 'clippy.*(installed)' -q; then - rustup component add clippy || rustup component add clippy-preview -fi - -# Notably, this runs clippy on the workspace from which it is called. When invoked -# from the root folder, as is done in CI or by invoking `make ci-job-clippy`, -# this code is not run on the rust code in tools/, as that code is in a -# separate cargo workspace. - -# We start by turning most lints off (by -A with most of the categories), then -# specifically turn on lints that make sense. We do keep `clippy::correctness` -# on. -# -# There are some lints we specifically do not want: -# -# - `clippy::if_same_then_else`: There are often good reasons to enumerate -# different states that have the same effect. - -CLIPPY_ARGS=" --A clippy::complexity --A clippy::pedantic --A clippy::nursery --A clippy::style --A clippy::perf --A clippy::cargo --A clippy::restriction - --A clippy::if_same_then_else - --D clippy::needless_return --D clippy::unnecessary_mut_passed --D clippy::empty_line_after_outer_attr --D clippy::default_trait_access --D clippy::map_unwrap_or --D clippy::wildcard_imports -" - -cargo clippy -- $CLIPPY_ARGS diff --git a/tools/semver.sh b/tools/semver.sh index 08a7f074ca..06d4e5ea5b 100755 --- a/tools/semver.sh +++ b/tools/semver.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + # Semantic version comparison. This function compares two # strings of the form X{.Y}* and returns which is larger or # whether they are equal. This is not a simple text comparison diff --git a/tools/sha256sum/Cargo.toml b/tools/sha256sum/Cargo.toml index 821f6b89a8..f0f019faec 100644 --- a/tools/sha256sum/Cargo.toml +++ b/tools/sha256sum/Cargo.toml @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "sha256sum" version = "0.1.0" diff --git a/tools/sha256sum/README.md b/tools/sha256sum/README.md new file mode 100644 index 0000000000..f2899e6ae0 --- /dev/null +++ b/tools/sha256sum/README.md @@ -0,0 +1,7 @@ +sha256sum +========= + +Tool for computing a SHA-256 hash of a file. + +In general we use the host's `sha256sum`, but if one doesn't exist we fallback +to this to avoid an error and another dependency. diff --git a/tools/stack_analysis.sh b/tools/stack_analysis.sh index ca5d644632..d89d2390f9 100755 --- a/tools/stack_analysis.sh +++ b/tools/stack_analysis.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + # This script requires that the .elf under analysis includes stack # size information, and is thus most easily called using the `make stack-analysis` # rule. @@ -8,7 +12,7 @@ bold=$(tput bold) normal=$(tput sgr0) # Get a list of all stack frames and their sizes. -frames=`$(find $(rustc --print sysroot) -name llvm-readobj) --elf-output-style GNU --stack-sizes $1` +frames=`$(find $(rustc --print sysroot) -name llvm-readobj) --demangle --elf-output-style GNU --stack-sizes $1` # Print the stack frame size of `main` printf " main stack frame: \n" diff --git a/tools/svd2regs.nix b/tools/svd2regs.nix index 2d385b096b..c52cd136cf 100644 --- a/tools/svd2regs.nix +++ b/tools/svd2regs.nix @@ -1,3 +1,7 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + # # Nix environment to run svd2regs.py # diff --git a/tools/svd2regs.py b/tools/svd2regs.py index f38868f0ca..50e5d6bdf6 100755 --- a/tools/svd2regs.py +++ b/tools/svd2regs.py @@ -1,4 +1,8 @@ #!/usr/bin/env python + +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. # # usage: svd2regs.py [-h] [--group] (--mcu VENDOR MCU | --svd [SVD]) # [--save FILE] [--fmt ['ARG ..']] [--path PATH] @@ -90,8 +94,8 @@ def fields(*args): class Includes(CodeBlock): TEMPLATE = """ -use kernel::common::StaticRef; -use kernel::common::registers::{{self, register_bitfields, register_structs, ReadOnly, ReadWrite, WriteOnly}}; +use kernel::utilities::StaticRef; +use kernel::utilities::registers::{{self, register_bitfields, register_structs, ReadOnly, ReadWrite, WriteOnly}}; """ @@ -103,6 +107,7 @@ class PeripheralBaseDeclaration(CodeBlock): @staticmethod def fields(base, peripheral): + assert peripheral.base_address != 0, "Cannot create a `StaticRef` to address 0" return { "name": peripheral.name, "title": base.title(), diff --git a/tools/toc.sh b/tools/toc.sh index 37969c032d..0e3218cb9c 100755 --- a/tools/toc.sh +++ b/tools/toc.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + # Run `markdown-toc` on all Markdown files with tables of contents # to see if any of them need an updated TOC. # diff --git a/tools/tockbot/maint_nightly.yaml b/tools/tockbot/maint_nightly.yaml new file mode 100644 index 0000000000..358208deed --- /dev/null +++ b/tools/tockbot/maint_nightly.yaml @@ -0,0 +1,54 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +_anchors: + core_wg_users: &core_wg_users + - hudson-ayers + - bradjc + - brghena + - phil-levis + - alevy + - ppannuto + - lschuermann + - jrvanwhy + + active_reviewers: &active_reviewers + - alexandruradovici + - hudson-ayers + - bradjc + - brghena + - alevy + - lschuermann + - jrvanwhy + +repo: + owner: tock + name: tock + +# Ignore all PRs and issues that have the tockbot-ignore label: +ignored_labels: + - tockbot-ignore + +tasks: + - type: stale_pr_assign + label: Assign Active Reviewers to Stale PRs + + # For how long the PR must have not received any comments: + staleness_time: 0 # assign immediately after PR is opened + + # Any such PRs must not already have a review by a core team + # member (i.e., have been triaged) + no_reviews_by: *core_wg_users + + # Assign one active reviewer at random: + assignee_cnt: 1 + assignee_candidates: *active_reviewers + + # Ignore PRs that are marked as "blocked" or "blocked-upstream". Those + # should not necessarily be assigned a reviewer, but be staged for + # "check-in" in a core-WG call after being stale for some time (e.g., a + # month). + ignored_labels: + - blocked + - blocked-upstream diff --git a/tools/tockbot/requirements.txt b/tools/tockbot/requirements.txt new file mode 100644 index 0000000000..b12b599beb --- /dev/null +++ b/tools/tockbot/requirements.txt @@ -0,0 +1,26 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +attrs==23.2.0 +cattrs==23.2.3 +certifi==2024.7.4 +cffi==1.16.0 +charset-normalizer==3.3.2 +cryptography==42.0.4 +Deprecated==1.2.14 +idna==3.7 +platformdirs==4.2.0 +pycparser==2.21 +PyGithub==2.1.1 +PyJWT==2.8.0 +PyNaCl==1.5.0 +python-dateutil==2.8.2 +PyYAML==6.0.1 +requests==2.32.0 +requests-cache==1.2.0 +six==1.16.0 +typing_extensions==4.9.0 +url-normalize==1.4.3 +urllib3==2.2.2 +wrapt==1.16.0 diff --git a/tools/tockbot/shell.nix b/tools/tockbot/shell.nix new file mode 100644 index 0000000000..f4c4cafa8a --- /dev/null +++ b/tools/tockbot/shell.nix @@ -0,0 +1,15 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +with import {}; + +mkShell { + name = "mirrorcheck-shell"; + buildInputs = [ + (python3.withPackages (pypkgs: with pypkgs; [ + requests-cache pygithub pyyaml + ])) + ]; +} + diff --git a/tools/tockbot/tockbot.py b/tools/tockbot/tockbot.py new file mode 100755 index 0000000000..2798278384 --- /dev/null +++ b/tools/tockbot/tockbot.py @@ -0,0 +1,269 @@ +#!/usr/bin/env python3 + +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2024. + +import os, sys +import random +import argparse +import logging +from datetime import datetime, timedelta, timezone +import yaml +from github import Github, Auth + +# Cache GitHub API requests aggressively: +from requests_cache import NEVER_EXPIRE, DO_NOT_CACHE, get_cache, install_cache +install_cache( + cache_control=True, + urls_expire_after={ + '*.github.com': NEVER_EXPIRE, + '*': DO_NOT_CACHE, + }, +) + +class CallbackFilter: + def __init__(self, function, filtered_cb, sequence, passthrough_cb=None): + self.function = function + self.sequence = sequence + self.filtered_cb = filtered_cb + self.passthrough_cb = passthrough_cb + + def __iter__(self): + return self + + def __next__(self): + # Let any StopIteration exception bubble up the call stack + while True: + item = next(self.sequence) + if self.function(item): + if self.passthrough_cb is not None: + self.passthrough_cb(item) + return item + else: + self.filtered_cb(item) + +def ignore_prs_filter(config, task_config, prs, logger): + filtered = prs + + def build_filter(ignored_label, sequence): + return CallbackFilter( + lambda pr: not any(map( + lambda l: l.name == ignored_label, + pr.get_labels() + )), + lambda ignored: logger.debug( + f"-> Filtered #{ignored.number}, is ignored by label " + + f"\"{ignored_label}\"." + ), + filtered, + passthrough_cb=lambda pr: logger.debug( + f"-> Passing through #{pr.number}, does not have label " + + f"\"{ignored_label}\"." + ), + ) + + # Build a chain of filters over each of the labels: + for ignored_label in ( + config.get("ignored_labels", []) + + task_config.get("ignored_labels", []) + ): + filtered = build_filter(ignored_label, filtered) + + return filtered + +def verbose_pr_stream(prs, log): + def verbose_pr_stream_log(pr, log): + log.debug(f"Processing PR #{pr.number} (\"{pr.title}\")") + return pr + return map(lambda pr: verbose_pr_stream_log(pr, log), prs) + +# Assign maintainers to stale PRs when they haven't seen any review / +# reviewer activity after a given amount of time: +def task_stale_pr_assign(config, task_config, gh, repo, rand, log, dry_run): + # Get the list of open PRs: + prs = verbose_pr_stream(repo.get_pulls(state="open"), log) + + # Ignore all draft PRs: + prs = CallbackFilter( + lambda pr: pr.draft == False, + lambda filtered: log.debug( + f"-> Filtered #{filtered.number}, is a draft PR."), + prs, + ) + + # Filter out PRs that are marked as ignored by this tool: + prs = ignore_prs_filter(config, task_config, prs, log) + + # Filter out PRs that are assigned to one or more users: + prs = CallbackFilter( + lambda pr: len(pr.assignees) == 0, + lambda filtered: log.debug( + f"-> Filtered #{filtered.number}, has assignees."), + prs, + ) + + # Filter out PRs which have received reviews that are not dismissed + # (optionally filted by a designated group of people, if the config is not + # an empty list): + no_reviews_cond = task_config.get("no_reviews_by", None) + if no_reviews_cond is not None: + prs = CallbackFilter( + lambda pr: not any(map( + lambda review: ( + # Only keep PRs that do not have any review where the + # reviewer is in the `no_reviews_cond` list, ... + review.user.login in no_reviews_cond \ + # ... not counting dismissed reviews: + and review.state != "DISMISSED" \ + # ... and not comment reviews (won't be dismissed): + and review.state != "COMMENTED" + ), + pr.get_reviews(), + )), + lambda filtered: log.debug( + f"-> Filtered #{filtered.number}, has current reviews."), + prs + ) + + # Filter our PRs that have seen a comment be updated in the last + # task_config["staleness_time"] seconds: + if task_config.get("staleness_time", None) is not None: + comments_since = datetime.now(timezone.utc) \ + - timedelta(seconds=task_config["staleness_time"]) + + prs = CallbackFilter( + lambda pr: ( + ( + # Keep PRs that do _not_ have at least one review comment or + # at least one issue comment since `comments_since`, + pr.get_review_comments(since=comments_since).totalCount == 0 and \ + pr.as_issue().get_comments(since=comments_since).totalCount == 0 + ) and ( + # ... except if the PR is less than `staleness_time` old: + pr.created_at < comments_since + ) + ), + lambda filtered: log.debug( + f"-> Filtered #{filtered.number}, not stale."), + prs + ) + + # Now, add an assignee to all remaining PRs randomly: + assignee_cnt = task_config.get("assignee_cnt", 1) + for pr in prs: + assignees = list(map( + lambda login: gh.get_user(login), + rand.sample( + list(filter( + # Avoid assigning the PR creator: + lambda login: pr.user.login != login, + task_config["assignee_candidates"])), + assignee_cnt + ) + )) + + log.info(( + "Would assign user(s) {} to PR #{} (\"{}\")" + if dry_run else + "Assigning user(s) {} to PR #{} (\"{}\")" + ).format( + ", ".join(map(lambda a: a.login, assignees)), + pr.number, + pr.title, + )) + + if not dry_run: + pr.add_to_assignees(*assignees) + + +def cmd_maint_nightly(config, log, dry_run, gh_token = None): + rand = random.SystemRandom() + + # Instantiate the GitHub client library: + if gh_token is not None: + auth_args = { "auth": Auth.Token(gh_token) } + else: + log.warning("Running without GitHub auth token.") + auth_args = {} + + gh = Github(**auth_args) + + repo = gh.get_repo("{}/{}".format( + config["repo"]["owner"], + config["repo"]["name"])) + + # Perform the various maintenance tasks + task_handlers = { + "stale_pr_assign": task_stale_pr_assign, + } + + for task in config["tasks"]: + if task["type"] not in task_handlers: + log.error("Unknown task type \"{}\", skipping!".format(task["type"])) + continue + + log.info("Running task \"{}\" (type \"{}\")...".format( + task.get("label", ""), task["type"])) + log.debug(f"Starting task with rate limits: {str(gh.get_rate_limit())}") + + handler = task_handlers[task["type"]] + handler( + config = config, + task_config = task, + gh = gh, + repo = repo, + rand = rand, + log = log, + dry_run = dry_run, + ) + + log.debug(f"Finished all tasks with rate limits: {str(gh.get_rate_limit())}") + +def main(): + parser = argparse.ArgumentParser(prog = "tockbot") + + # Global options: + parser.add_argument("-n", "--dry-run", action="store_true") + parser.add_argument("-v", "--verbose", action="store_true") + + # Subcommands: + subparsers = parser.add_subparsers(dest="subcommand", required=True) + + # Nightly project maintenance command: + maint_nightly_parser = subparsers.add_parser("maint-nightly") + maint_nightly_parser.add_argument( + "-c", "--config", required=True, + help="YAML configuration for nightly maintenance job") + + args = parser.parse_args() + + # Initialize the logging facility: + ch = logging.StreamHandler() + fmt = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + ch.setFormatter(fmt) + log = logging.getLogger('tockbot') + log.addHandler(ch) + if args.verbose: + log.setLevel(logging.DEBUG) + else: + log.setLevel(logging.INFO) + + # Load the YAML configuration for commands that require it: + if args.subcommand in ["maint-nightly"]: + with open(args.config, "r") as f: + config = yaml.safe_load(f) + + # Check if we're being passed a GitHub access token in an environment var: + gh_token = os.environ.get("GITHUB_TOKEN", None) + gh_token = gh_token if gh_token != "" else None + + if args.subcommand == "maint-nightly": + return cmd_maint_nightly( + config, log, dry_run=args.dry_run, gh_token=gh_token) + else: + log.critical(f"Unhandled subcommand: {args.subcommand}") + return 1 + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tools/update_chip_support.py b/tools/update_chip_support.py index 665fc789cc..a4671044c5 100755 --- a/tools/update_chip_support.py +++ b/tools/update_chip_support.py @@ -1,6 +1,10 @@ #!/usr/bin/env python3 -''' +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + +""" Script to generate a table of which HILs each Tock chip supports. Adds it to the `chips/README.md` file. @@ -13,145 +17,156 @@ | AES128Ctr | | | ✓ | ✓ | | | Adc | | | ✓ | ✓ | | ``` -''' +""" import os import re # Static info of chip crates that just support other chips. -SUBSUMES = {'nrf52': ['nrf5x'], - 'e310x': ['sifive'], - 'arty_e21': ['sifive'], - 'nrf52840': ['nrf52', 'nrf5x'], - 'nrf52832': ['nrf52', 'nrf5x'], - 'lowrisc': ['ibex'], - } - +SUBSUMES = { + "nrf52": ["nrf5x"], + "e310_g002": ["e310x", "sifive"], + "e310_g003": ["e310x", "sifive"], + "esp32-c3": ["esp32"], + "arty_e21_chip": ["sifive"], + "nrf52832": ["nrf52", "nrf5x"], + "nrf52833": ["nrf52", "nrf5x"], + "nrf52840": ["nrf52", "nrf5x"], + "litex_vexriscv": ["litex"], + "lowrisc": ["ibex"], + "qemu_rv32_virt_chip": ["sifive", "virtio"], + "stm32f401cc": ["stm32f4xx"], + "stm32f412g": ["stm32f4xx"], + "stm32f429zi": ["stm32f4xx"], + "stm32f446re": ["stm32f4xx"], + "swervolf-eh1": ["swerv"], +} hils = {} # Get the name of all HILs -for subdir, dirs, files in os.walk(os.fsencode('kernel/src/hil/')): - for file in files: - filepath = os.fsdecode(os.path.join(subdir, file)) +for subdir, dirs, files in os.walk(os.fsencode("kernel/src/hil/")): + for file in files: + filepath = os.fsdecode(os.path.join(subdir, file)) - if filepath.endswith('.rs'): - with open(filepath) as f: - mod = os.path.splitext(os.path.basename(filepath))[0] - for l in f: - if l.startswith('pub trait'): - items = re.findall(r"[A-Za-z0-9]+|\S", l) + if filepath.endswith(".rs"): + with open(filepath) as f: + mod = os.path.splitext(os.path.basename(filepath))[0] + for l in f: + if l.startswith("pub trait"): + items = re.findall(r"[A-Za-z0-9]+|\S", l) - hil_name = items[2] - if not 'Client' in hil_name: - hils[hil_name] = {'module': mod, 'chips': []} + hil_name = items[2] + if not "Client" in hil_name: + hils[hil_name] = {"module": mod, "chips": []} chips = [] # Get each chip and all HILs that chip implements. -for subdir, dirs, files in os.walk(os.fsencode('chips/')): - for file in files: - filepath = os.fsdecode(os.path.join(subdir, file)) - - if '/src/' in filepath and filepath.endswith('.rs'): - chip = filepath.split('/')[1] - chips.append(chip) - - with open(filepath) as f: - for l in f: - # Find any line with `impl` - if l.startswith('impl') and ' for ' in l: - - # Get the text before " for " - half = l.split(' for ')[0] - # Split strings apart from all other symbols - items = re.findall(r"[A-Za-z0-9]+|\S", half) - - # Check each HIL to see if this `impl` line implements - # that HIL. - for hil in hils.keys(): - for item in items: - if item == hil: - hils[hil]['chips'].append(chip) - break +for subdir, dirs, files in os.walk(os.fsencode("chips/")): + for file in files: + filepath = os.fsdecode(os.path.join(subdir, file)) + + if "/src/" in filepath and filepath.endswith(".rs"): + chip = filepath.split("/")[1] + chips.append(chip) + + with open(filepath) as f: + for l in f: + # Find any line with `impl` + if l.startswith("impl") and " for " in l: + # Get the text before " for " + half = l.split(" for ")[0] + # Split strings apart from all other symbols + items = re.findall(r"[A-Za-z0-9]+|\S", half) + + # Check each HIL to see if this `impl` line implements + # that HIL. + for hil in hils.keys(): + for item in items: + if item == hil: + hils[hil]["chips"].append(chip) + break # Calculate chips that should be ignored since they only support other chips. subsumed = [] -for k,v in SUBSUMES.items(): - subsumed += v +for k, v in SUBSUMES.items(): + subsumed += v # Get only proper chips that are not just crates that support other chips. chips = set(chips).difference(subsumed) # Setup table and add the header row. table = [] -table.append(['HIL', *sorted(chips)]) +table.append(["HIL", *sorted(chips)]) # Add rows to the table, one row for each HIL. -for k,v in sorted(hils.items(), key=lambda x: '{}::{}'.format(x[1]['module'], x[0])): - row = ['{}::{}'.format(v['module'], k)] - - # Skip any HILs that have no chip support. These are likely not HILs - # that hardware chips implement. - at_least_one = False - - for chip in sorted(chips): - # Check if this chip or if any chip it subsumes implements this HIL. - if chip in v['chips'] or (chip in SUBSUMES and len(set(SUBSUMES[chip]).intersection(set(v['chips'])))): - at_least_one = True - row.append('✓') - else: - row.append(' ') - - if at_least_one: - table.append(row) +for k, v in sorted(hils.items(), key=lambda x: "{}::{}".format(x[1]["module"], x[0])): + row = ["{}::{}".format(v["module"], k)] + + # Skip any HILs that have no chip support. These are likely not HILs + # that hardware chips implement. + at_least_one = False + + for chip in sorted(chips): + # Check if this chip or if any chip it subsumes implements this HIL. + if chip in v["chips"] or ( + chip in SUBSUMES and len(set(SUBSUMES[chip]).intersection(set(v["chips"]))) + ): + at_least_one = True + row.append("✓") + else: + row.append(" ") + + if at_least_one: + table.append(row) # Calculate the max widths of each column. -widths = [0]*len(table[0]) +widths = [0] * len(table[0]) for row in table: - widths[0] = max(len(row[0]), widths[0]) + widths[0] = max(len(row[0]), widths[0]) # The other columns we just need to look at the header -for i,item in enumerate(table[0]): - widths[i] = max(len(item), widths[i]) +for i, item in enumerate(table[0]): + widths[i] = max(len(item), widths[i]) # Generate the output table. -out = '' -for i,row in enumerate(table): - # Use the widths to pad each item in each row. - for j,item in enumerate(row): - out += '| ' - out += '{1:<{0}s}'.format(widths[j]+1, item) - - out += '|\n' - - # After the first row add the "----" header marking row. - if i == 0: - for width in widths: - out += '|' - out += '-'*(width+2) - out += '|\n' +out = "" +for i, row in enumerate(table): + # Use the widths to pad each item in each row. + for j, item in enumerate(row): + out += "| " + out += "{1:<{0}s}".format(widths[j] + 1, item) + + out += "|\n" + + # After the first row add the "----" header marking row. + if i == 0: + for width in widths: + out += "|" + out += "-" * (width + 2) + out += "|\n" # Update the chips README with the newly calculate table. -readme_first = '' -readme_second = '' -readme_state = 'start' -with open('chips/README.md') as f: - for l in f: - if readme_state == 'end': - readme_second += l - elif '' in l: - readme_state = 'end' - readme_second += '\n' - readme_second += l - elif '' in l: - readme_state = 'skip' - readme_first += l - readme_first += '\n' - elif readme_state == 'start': - readme_first += l - -with open('chips/README.md', 'w') as f: - f.write(readme_first) - f.write(out) - f.write(readme_second) +readme_first = "" +readme_second = "" +readme_state = "start" +with open("chips/README.md") as f: + for l in f: + if readme_state == "end": + readme_second += l + elif "" in l: + readme_state = "end" + readme_second += "\n" + readme_second += l + elif "" in l: + readme_state = "skip" + readme_first += l + readme_first += "\n" + elif readme_state == "start": + readme_first += l + +with open("chips/README.md", "w") as f: + f.write(readme_first) + f.write(out) + f.write(readme_second) diff --git a/tools/update_rust_version.sh b/tools/update_rust_version.sh index 67c1ece13a..867b839e4a 100755 --- a/tools/update_rust_version.sh +++ b/tools/update_rust_version.sh @@ -1,10 +1,13 @@ #!/usr/bin/env bash +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. # Ask rustup to pick the latest version that will work. # This requires rustup >= 1.20.0. echo "Updating rustc to latest compatible version..." -rustup toolchain install nightly --allow-downgrade --component cargo --component clippy --component llvm-tools-preview --component miri --component rls --component rust-analysis --component rust-docs --component rust-src --component rust-std --component rustc --component rustfmt +rustup toolchain install nightly --allow-downgrade --component cargo --component clippy --component llvm-tools --component miri --component rust-analysis --component rust-docs --component rust-src --component rust-std --component rustc --component rustfmt # # Rerun the command so that it prints out the version it installed. We then have # # to extract that from the output. If there is a better way to do this then we @@ -27,15 +30,12 @@ NIGHTLY=nightly-$BEST_DATE echo Updating Rust to $NIGHTLY -# Set the Rust version in rust-toolchain file. -echo $NIGHTLY > rust-toolchain - # Update all relevant files with the new version string. # Note, x-platform `sed -i` has odd, but particular syntax # https://stackoverflow.com/questions/5694228/sed-in-place-flag-that-works-both-on-mac-bsd-and-linux +sed -i._SED_HACK "s/nightly-[0-9]*-[0-9]*-[0-9]*/${NIGHTLY}/g" rust-toolchain.toml sed -i._SED_HACK "s/nightly-[0-9]*-[0-9]*-[0-9]*/${NIGHTLY}/g" .vscode/settings.json sed -i._SED_HACK "s/nightly-[0-9]*-[0-9]*-[0-9]*/${NIGHTLY}/g" doc/Getting_Started.md -sed -i._SED_HACK "s/nightly-[0-9]*-[0-9]*-[0-9]*/${NIGHTLY}/g" rust-toolchain sed -i._SED_HACK "s/nightly-[0-9]*-[0-9]*-[0-9]*/${NIGHTLY}/g" tools/netlify-build.sh find . -name '*._SED_HACK' -delete diff --git a/tools/usb/bulk-echo-fast/build.sh b/tools/usb/bulk-echo-fast/build.sh index ec9dbf7806..d0ec76ac4a 100755 --- a/tools/usb/bulk-echo-fast/build.sh +++ b/tools/usb/bulk-echo-fast/build.sh @@ -1,5 +1,9 @@ #!/bin/sh +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + OPTIONS= # Uncomment the line below to enable logging # OPTIONS="-DLOGGING" diff --git a/tools/usb/bulk-echo-fast/main.c b/tools/usb/bulk-echo-fast/main.c index d33016fcf4..7475379971 100644 --- a/tools/usb/bulk-echo-fast/main.c +++ b/tools/usb/bulk-echo-fast/main.c @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + /* * This testing utility is designed to communicate with a device * running Tock. In particular, it interacts with the usbc_client diff --git a/tools/usb/bulk-echo-fast/test.sh b/tools/usb/bulk-echo-fast/test.sh index afc3fe1678..44e9d7a5ec 100755 --- a/tools/usb/bulk-echo-fast/test.sh +++ b/tools/usb/bulk-echo-fast/test.sh @@ -1,5 +1,9 @@ #!/bin/sh +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + dd if=/dev/urandom of=input.dat bs=1 count=99999 time ./a.out output.dat diff --git a/tools/usb/bulk-echo/Cargo.toml b/tools/usb/bulk-echo/Cargo.toml index c610d72f0d..0239ba6c51 100644 --- a/tools/usb/bulk-echo/Cargo.toml +++ b/tools/usb/bulk-echo/Cargo.toml @@ -1,7 +1,12 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "bulk-echo" version = "0.1.0" authors = ["Daniel B Giffin "] +edition.workspace = true [dependencies] libusb = { git ="https://github.com/tock/libusb-rs" } diff --git a/tools/usb/bulk-echo/README.md b/tools/usb/bulk-echo/README.md new file mode 100644 index 0000000000..147c0c2876 --- /dev/null +++ b/tools/usb/bulk-echo/README.md @@ -0,0 +1,5 @@ +USB: Bulk Echo +============== + +Test program for a USB host to connect to a Tock board using bulk endpoints and +echo the data. diff --git a/tools/usb/bulk-echo/src/main.rs b/tools/usb/bulk-echo/src/main.rs index 9376fdafbe..a4ec70c987 100644 --- a/tools/usb/bulk-echo/src/main.rs +++ b/tools/usb/bulk-echo/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! This testing utility is designed to communicate with a device //! running Tock. In particular, it interacts with the usbc_client //! capsule. The USB controller and the client capsule must be enabled @@ -12,10 +16,12 @@ //! USB device, which then echos all data back to the PC via a //! Bulk IN endpoint, and this utility will then send it to stdout: //! +//! ``` //! stdin >___ ___< Bulk IN endpoint <--\ //! \ / | Tock usbc_client //! [this utility] | capsule echoes data //! stdout <___/ \___> Bulk OUT endpoint -->/ +//! ``` //! //! Thus, a useful test of the USB software on Tock is to pipe a file of data //! through the path show above, and confirm that the output is the same as the input. diff --git a/tools/usb/bulk-echo/test.sh b/tools/usb/bulk-echo/test.sh index 4791f669a9..639d20f842 100755 --- a/tools/usb/bulk-echo/test.sh +++ b/tools/usb/bulk-echo/test.sh @@ -1,5 +1,9 @@ #!/bin/sh +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + bin=target/debug dd if=/dev/urandom of=input.dat bs=1 count=99999 diff --git a/tools/usb/bulk-test/Cargo.toml b/tools/usb/bulk-test/Cargo.toml index c8094a4b3b..0a68a74aeb 100644 --- a/tools/usb/bulk-test/Cargo.toml +++ b/tools/usb/bulk-test/Cargo.toml @@ -1,7 +1,12 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "bulk-test" version = "0.1.0" authors = ["Daniel B. Giffin "] +edition.workspace = true [dependencies] libusb = { git ="https://github.com/tock/libusb-rs" } diff --git a/tools/usb/bulk-test/README.md b/tools/usb/bulk-test/README.md new file mode 100644 index 0000000000..67a4d2e8c4 --- /dev/null +++ b/tools/usb/bulk-test/README.md @@ -0,0 +1,5 @@ +USB: Bulk Test +============== + +Simple test program for a USB host to connect to a Tock board using bulk +endpoints. diff --git a/tools/usb/bulk-test/src/main.rs b/tools/usb/bulk-test/src/main.rs index 3d4462cfd7..a76d1d2f4c 100644 --- a/tools/usb/bulk-test/src/main.rs +++ b/tools/usb/bulk-test/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! This utility performs a simple test of usb functionality in Tock: //! It writes bulk data into an eight-byte buffer on a connected //! device and reads that data back from it, using nonstandard diff --git a/tools/usb/control-test/Cargo.toml b/tools/usb/control-test/Cargo.toml index e0862aa916..266a94ca69 100644 --- a/tools/usb/control-test/Cargo.toml +++ b/tools/usb/control-test/Cargo.toml @@ -1,7 +1,12 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2022. + [package] name = "control-test" version = "0.1.0" authors = ["Daniel B. Giffin "] +edition.workspace = true [dependencies] libusb = { git ="https://github.com/tock/libusb-rs" } diff --git a/tools/usb/control-test/README.md b/tools/usb/control-test/README.md new file mode 100644 index 0000000000..aa3eca5d74 --- /dev/null +++ b/tools/usb/control-test/README.md @@ -0,0 +1,6 @@ +USB: Control Test +================= + +This utility performs a simple test of usb functionality in Tock: It reads +control data from a connected device and writes control data back to it, using +nonstandard "vendor" codes. diff --git a/tools/usb/control-test/src/main.rs b/tools/usb/control-test/src/main.rs index cfcab6be41..f41917887c 100644 --- a/tools/usb/control-test/src/main.rs +++ b/tools/usb/control-test/src/main.rs @@ -1,3 +1,7 @@ +// Licensed under the Apache License, Version 2.0 or the MIT License. +// SPDX-License-Identifier: Apache-2.0 OR MIT +// Copyright Tock Contributors 2022. + //! This utility performs a simple test of usb functionality in Tock: //! It reads control data from a connected device and writes control //! data back to it, using nonstandard "vendor" codes. diff --git a/vagrant/.gitignore b/vagrant/.gitignore index 8000dd9db4..2d23a331f6 100644 --- a/vagrant/.gitignore +++ b/vagrant/.gitignore @@ -1 +1,5 @@ +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + .vagrant diff --git a/vagrant/Vagrantfile b/vagrant/Vagrantfile index b2ced7d98f..4cddf4c18e 100644 --- a/vagrant/Vagrantfile +++ b/vagrant/Vagrantfile @@ -1,6 +1,10 @@ # -*- mode: ruby -*- # vi: set ft=ruby : +# Licensed under the Apache License, Version 2.0 or the MIT License. +# SPDX-License-Identifier: Apache-2.0 OR MIT +# Copyright Tock Contributors 2023. + Vagrant.configure("2") do |config| # The most common configuration options are documented and commented below. # For a complete reference, please see the online documentation at
Symbol NameSize (Estimated)Size (Reported)Data
+ + +
{formatted_hex_data if initial_hex else ascii_data}
+
{info[0]}{info[1]}{info[2]}
{function_name}{info.embedded_data_size_estimated}{info.embedded_data_size_actual}{info.embedded_data_count}